mirror of https://github.com/ehang-io/nps
				
				
				
			
		
			
				
	
	
		
			276 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
			
		
		
	
	
			276 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
package proxy
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/binary"
 | 
						|
	"errors"
 | 
						|
	"github.com/cnlh/nps/lib/common"
 | 
						|
	"github.com/cnlh/nps/lib/conn"
 | 
						|
	"github.com/cnlh/nps/lib/file"
 | 
						|
	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
	"strconv"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	ipV4            = 1
 | 
						|
	domainName      = 3
 | 
						|
	ipV6            = 4
 | 
						|
	connectMethod   = 1
 | 
						|
	bindMethod      = 2
 | 
						|
	associateMethod = 3
 | 
						|
	// The maximum packet size of any udp Associate packet, based on ethernet's max size,
 | 
						|
	// minus the IP and UDP headers. IPv4 has a 20 byte header, UDP adds an
 | 
						|
	// additional 4 bytes.  This is a total overhead of 24 bytes.  Ethernet's
 | 
						|
	// max packet size is 1500 bytes,  1500 - 24 = 1476.
 | 
						|
	maxUDPPacketSize = 1476
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	succeeded uint8 = iota
 | 
						|
	serverFailure
 | 
						|
	notAllowed
 | 
						|
	networkUnreachable
 | 
						|
	hostUnreachable
 | 
						|
	connectionRefused
 | 
						|
	ttlExpired
 | 
						|
	commandNotSupported
 | 
						|
	addrTypeNotSupported
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	UserPassAuth    = uint8(2)
 | 
						|
	userAuthVersion = uint8(1)
 | 
						|
	authSuccess     = uint8(0)
 | 
						|
	authFailure     = uint8(1)
 | 
						|
)
 | 
						|
 | 
						|
type Sock5ModeServer struct {
 | 
						|
	BaseServer
 | 
						|
	listener net.Listener
 | 
						|
}
 | 
						|
 | 
						|
//req
 | 
						|
func (s *Sock5ModeServer) handleRequest(c net.Conn) {
 | 
						|
	/*
 | 
						|
		The SOCKS request is formed as follows:
 | 
						|
		+----+-----+-------+------+----------+----------+
 | 
						|
		|VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
 | 
						|
		+----+-----+-------+------+----------+----------+
 | 
						|
		| 1  |  1  | X'00' |  1   | Variable |    2     |
 | 
						|
		+----+-----+-------+------+----------+----------+
 | 
						|
	*/
 | 
						|
	header := make([]byte, 3)
 | 
						|
 | 
						|
	_, err := io.ReadFull(c, header)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		logs.Warn("illegal request", err)
 | 
						|
		c.Close()
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	switch header[1] {
 | 
						|
	case connectMethod:
 | 
						|
		s.handleConnect(c)
 | 
						|
	case bindMethod:
 | 
						|
		s.handleBind(c)
 | 
						|
	case associateMethod:
 | 
						|
		s.handleUDP(c)
 | 
						|
	default:
 | 
						|
		s.sendReply(c, commandNotSupported)
 | 
						|
		c.Close()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
//reply
 | 
						|
func (s *Sock5ModeServer) sendReply(c net.Conn, rep uint8) {
 | 
						|
	reply := []byte{
 | 
						|
		5,
 | 
						|
		rep,
 | 
						|
		0,
 | 
						|
		1,
 | 
						|
	}
 | 
						|
 | 
						|
	localAddr := c.LocalAddr().String()
 | 
						|
	localHost, localPort, _ := net.SplitHostPort(localAddr)
 | 
						|
	ipBytes := net.ParseIP(localHost).To4()
 | 
						|
	nPort, _ := strconv.Atoi(localPort)
 | 
						|
	reply = append(reply, ipBytes...)
 | 
						|
	portBytes := make([]byte, 2)
 | 
						|
	binary.BigEndian.PutUint16(portBytes, uint16(nPort))
 | 
						|
	reply = append(reply, portBytes...)
 | 
						|
 | 
						|
	c.Write(reply)
 | 
						|
}
 | 
						|
 | 
						|
//do conn
 | 
						|
func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) {
 | 
						|
	addrType := make([]byte, 1)
 | 
						|
	c.Read(addrType)
 | 
						|
	var host string
 | 
						|
	switch addrType[0] {
 | 
						|
	case ipV4:
 | 
						|
		ipv4 := make(net.IP, net.IPv4len)
 | 
						|
		c.Read(ipv4)
 | 
						|
		host = ipv4.String()
 | 
						|
	case ipV6:
 | 
						|
		ipv6 := make(net.IP, net.IPv6len)
 | 
						|
		c.Read(ipv6)
 | 
						|
		host = ipv6.String()
 | 
						|
	case domainName:
 | 
						|
		var domainLen uint8
 | 
						|
		binary.Read(c, binary.BigEndian, &domainLen)
 | 
						|
		domain := make([]byte, domainLen)
 | 
						|
		c.Read(domain)
 | 
						|
		host = string(domain)
 | 
						|
	default:
 | 
						|
		s.sendReply(c, addrTypeNotSupported)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var port uint16
 | 
						|
	binary.Read(c, binary.BigEndian, &port)
 | 
						|
	// connect to host
 | 
						|
	addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
 | 
						|
	var ltype string
 | 
						|
	if command == associateMethod {
 | 
						|
		ltype = common.CONN_UDP
 | 
						|
	} else {
 | 
						|
		ltype = common.CONN_TCP
 | 
						|
	}
 | 
						|
	s.DealClient(conn.NewConn(c), s.task.Client, addr, nil, ltype, func() {
 | 
						|
		s.sendReply(c, succeeded)
 | 
						|
	}, s.task.Flow, s.task.Target.LocalProxy)
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
//conn
 | 
						|
func (s *Sock5ModeServer) handleConnect(c net.Conn) {
 | 
						|
	s.doConnect(c, connectMethod)
 | 
						|
}
 | 
						|
 | 
						|
// passive mode
 | 
						|
func (s *Sock5ModeServer) handleBind(c net.Conn) {
 | 
						|
}
 | 
						|
 | 
						|
//udp
 | 
						|
func (s *Sock5ModeServer) handleUDP(c net.Conn) {
 | 
						|
	/*
 | 
						|
	   +----+------+------+----------+----------+----------+
 | 
						|
	   |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
 | 
						|
	   +----+------+------+----------+----------+----------+
 | 
						|
	   | 2  |  1   |  1   | Variable |    2     | Variable |
 | 
						|
	   +----+------+------+----------+----------+----------+
 | 
						|
	*/
 | 
						|
	buf := make([]byte, 3)
 | 
						|
	c.Read(buf)
 | 
						|
	// relay udp datagram silently, without any notification to the requesting client
 | 
						|
	if buf[2] != 0 {
 | 
						|
		// does not support fragmentation, drop it
 | 
						|
		logs.Warn("does not support fragmentation, drop")
 | 
						|
		dummy := make([]byte, maxUDPPacketSize)
 | 
						|
		c.Read(dummy)
 | 
						|
	}
 | 
						|
 | 
						|
	s.doConnect(c, associateMethod)
 | 
						|
}
 | 
						|
 | 
						|
//new conn
 | 
						|
func (s *Sock5ModeServer) handleConn(c net.Conn) {
 | 
						|
	buf := make([]byte, 2)
 | 
						|
	if _, err := io.ReadFull(c, buf); err != nil {
 | 
						|
		logs.Warn("negotiation err", err)
 | 
						|
		c.Close()
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if version := buf[0]; version != 5 {
 | 
						|
		logs.Warn("only support socks5, request from: ", c.RemoteAddr())
 | 
						|
		c.Close()
 | 
						|
		return
 | 
						|
	}
 | 
						|
	nMethods := buf[1]
 | 
						|
 | 
						|
	methods := make([]byte, nMethods)
 | 
						|
	if len, err := c.Read(methods); len != int(nMethods) || err != nil {
 | 
						|
		logs.Warn("wrong method")
 | 
						|
		c.Close()
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if s.task.Client.Cnf.U != "" && s.task.Client.Cnf.P != "" {
 | 
						|
		buf[1] = UserPassAuth
 | 
						|
		c.Write(buf)
 | 
						|
		if err := s.Auth(c); err != nil {
 | 
						|
			c.Close()
 | 
						|
			logs.Warn("Validation failed:", err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		buf[1] = 0
 | 
						|
		c.Write(buf)
 | 
						|
	}
 | 
						|
	s.handleRequest(c)
 | 
						|
}
 | 
						|
 | 
						|
//socks5 auth
 | 
						|
func (s *Sock5ModeServer) Auth(c net.Conn) error {
 | 
						|
	header := []byte{0, 0}
 | 
						|
	if _, err := io.ReadAtLeast(c, header, 2); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if header[0] != userAuthVersion {
 | 
						|
		return errors.New("验证方式不被支持")
 | 
						|
	}
 | 
						|
	userLen := int(header[1])
 | 
						|
	user := make([]byte, userLen)
 | 
						|
	if _, err := io.ReadAtLeast(c, user, userLen); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err := c.Read(header[:1]); err != nil {
 | 
						|
		return errors.New("密码长度获取错误")
 | 
						|
	}
 | 
						|
	passLen := int(header[0])
 | 
						|
	pass := make([]byte, passLen)
 | 
						|
	if _, err := io.ReadAtLeast(c, pass, passLen); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if string(user) == s.task.Client.Cnf.U && string(pass) == s.task.Client.Cnf.P {
 | 
						|
		if _, err := c.Write([]byte{userAuthVersion, authSuccess}); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	} else {
 | 
						|
		if _, err := c.Write([]byte{userAuthVersion, authFailure}); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return errors.New("验证不通过")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
//start
 | 
						|
func (s *Sock5ModeServer) Start() error {
 | 
						|
	return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) {
 | 
						|
		if err := s.CheckFlowAndConnNum(s.task.Client); err != nil {
 | 
						|
			logs.Warn("client id %d, task id %d, error %s, when socks5 connection", s.task.Client.Id, s.task.Id, err.Error())
 | 
						|
			c.Close()
 | 
						|
			return
 | 
						|
		}
 | 
						|
		logs.Trace("New socks5 connection,client %d,remote address %s", s.task.Client.Id, c.RemoteAddr())
 | 
						|
		s.handleConn(c)
 | 
						|
		s.task.Client.AddConn()
 | 
						|
	}, &s.listener)
 | 
						|
}
 | 
						|
 | 
						|
//new
 | 
						|
func NewSock5ModeServer(bridge NetBridge, task *file.Tunnel) *Sock5ModeServer {
 | 
						|
	s := new(Sock5ModeServer)
 | 
						|
	s.bridge = bridge
 | 
						|
	s.task = task
 | 
						|
	return s
 | 
						|
}
 | 
						|
 | 
						|
//close
 | 
						|
func (s *Sock5ModeServer) Close() error {
 | 
						|
	return s.listener.Close()
 | 
						|
} |