Implement SOCKS5 UDP (ACL not yet integrated)

This commit is contained in:
Toby 2021-03-27 18:50:12 -07:00
parent 4bb5982960
commit 8530211287
3 changed files with 172 additions and 14 deletions

View File

@ -126,6 +126,23 @@ func client(config *clientConfig) {
"dst": reqAddr,
}).Debug("SOCKS5 TCP EOF")
}
},
func(addr net.Addr) {
logrus.WithFields(logrus.Fields{
"src": addr.String(),
}).Debug("SOCKS5 UDP associate")
},
func(addr net.Addr, err error) {
if err != io.EOF {
logrus.WithFields(logrus.Fields{
"error": err,
"src": addr.String(),
}).Info("SOCKS5 UDP error")
} else {
logrus.WithFields(logrus.Fields{
"src": addr.String(),
}).Debug("SOCKS5 UDP EOF")
}
})
if err != nil {
logrus.WithField("error", err).Fatal("Failed to initialize SOCKS5 server")

19
docs/socks5/udpchk.py Normal file
View File

@ -0,0 +1,19 @@
import socks
import socket
def main():
s = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM)
s.set_proxy(socks.SOCKS5, "127.0.0.1", 1080)
# Raw DNS request
req = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x05\x62\x61\x69\x64\x75\x03\x63\x6f\x6d\x00\x00\x01\x00\x01"
s.sendto(req, ("8.8.8.8", 53))
(rsp, address) = s.recvfrom(4096)
if rsp[0] == req[0] and rsp[1] == req[1]:
print("UDP check passed")
else:
print("Invalid response")
if __name__ == "__main__":
main()

View File

@ -16,6 +16,8 @@ import (
"time"
)
const udpBufferSize = 65535
var (
ErrUnsupportedCmd = errors.New("unsupported command")
ErrUserPassAuth = errors.New("invalid username or password")
@ -30,8 +32,10 @@ type Server struct {
ACLEngine *acl.Engine
DisableUDP bool
TCPRequestFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
TCPErrorFunc func(addr net.Addr, reqAddr string, err error)
TCPRequestFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
TCPErrorFunc func(addr net.Addr, reqAddr string, err error)
UDPAssociateFunc func(addr net.Addr)
UDPErrorFunc func(addr net.Addr, err error)
tcpListener *net.TCPListener
}
@ -39,7 +43,8 @@ type Server struct {
func NewServer(hyClient *core.Client, addr string, authFunc func(username, password string) bool, tcpTimeout time.Duration,
aclEngine *acl.Engine, disableUDP bool,
tcpReqFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string),
tcpErrorFunc func(addr net.Addr, reqAddr string, err error)) (*Server, error) {
tcpErrorFunc func(addr net.Addr, reqAddr string, err error),
udpAssocFunc func(addr net.Addr), udpErrorFunc func(addr net.Addr, err error)) (*Server, error) {
tAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
@ -49,15 +54,17 @@ func NewServer(hyClient *core.Client, addr string, authFunc func(username, passw
m = socks5.MethodUsernamePassword
}
s := &Server{
HyClient: hyClient,
AuthFunc: authFunc,
Method: m,
TCPAddr: tAddr,
TCPTimeout: tcpTimeout,
ACLEngine: aclEngine,
DisableUDP: disableUDP,
TCPRequestFunc: tcpReqFunc,
TCPErrorFunc: tcpErrorFunc,
HyClient: hyClient,
AuthFunc: authFunc,
Method: m,
TCPAddr: tAddr,
TCPTimeout: tcpTimeout,
ACLEngine: aclEngine,
DisableUDP: disableUDP,
TCPRequestFunc: tcpReqFunc,
TCPErrorFunc: tcpErrorFunc,
UDPAssociateFunc: udpAssocFunc,
UDPErrorFunc: udpErrorFunc,
}
return s, nil
}
@ -142,8 +149,12 @@ func (s *Server) handle(c *net.TCPConn, r *socks5.Request) error {
return s.handleTCP(c, r)
} else if r.Cmd == socks5.CmdUDP {
// UDP
_ = sendReply(c, socks5.RepCommandNotSupported)
return ErrUnsupportedCmd
if !s.DisableUDP {
return s.handleUDP(c, r)
} else {
_ = sendReply(c, socks5.RepCommandNotSupported)
return ErrUnsupportedCmd
}
} else {
_ = sendReply(c, socks5.RepCommandNotSupported)
return ErrUnsupportedCmd
@ -207,6 +218,107 @@ func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
}
}
func (s *Server) handleUDP(c *net.TCPConn, r *socks5.Request) error {
s.UDPAssociateFunc(c.RemoteAddr())
var closeErr error
defer func() {
s.UDPErrorFunc(c.RemoteAddr(), closeErr)
}()
// Start local UDP server
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{
IP: s.TCPAddr.IP,
Zone: s.TCPAddr.Zone,
})
if err != nil {
_ = sendReply(c, socks5.RepServerFailure)
closeErr = err
return err
}
defer udpConn.Close()
// HyClient UDP session
hyUDP, err := s.HyClient.DialUDP()
if err != nil {
_ = sendReply(c, socks5.RepServerFailure)
closeErr = err
return err
}
defer hyUDP.Close()
// Send UDP server addr to the client
atyp, addr, port, err := socks5.ParseAddress(udpConn.LocalAddr().String())
if err != nil {
_ = sendReply(c, socks5.RepServerFailure)
closeErr = err
return err
}
_, _ = socks5.NewReply(socks5.RepSuccess, atyp, addr, port).WriteTo(c)
// Let UDP server do its job, we hold the TCP connection here
go s.udpServer(udpConn, hyUDP)
buf := make([]byte, 1024)
for {
if s.TCPTimeout != 0 {
_ = c.SetDeadline(time.Now().Add(s.TCPTimeout))
}
_, err := c.Read(buf)
if err != nil {
closeErr = err
break
}
}
// As the TCP connection closes, so does the UDP server & HyClient session
return nil
}
func (s *Server) udpServer(c *net.UDPConn, hyUDP core.UDPConn) {
var clientAddr *net.UDPAddr
buf := make([]byte, udpBufferSize)
// Local to remote
for {
n, cAddr, err := c.ReadFromUDP(buf)
if err != nil {
break
}
d, err := socks5.NewDatagramFromBytes(buf[:n])
if err != nil || d.Frag != 0 {
// Ignore bad packets
continue
}
if clientAddr == nil {
// Whoever sends the first valid packet is our client
clientAddr = cAddr
go func() {
// Start remote to local
for {
bs, _, err := hyUDP.ReadFrom()
if err != nil {
break
}
// RFC 1928 is very ambiguous on how to properly use DST.ADDR and DST.PORT in reply packets
// So we just fill in zeros for now. Works fine for all the SOCKS5 clients I tested
d := socks5.NewDatagram(socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}, bs)
_, err = c.WriteTo(d.Bytes(), clientAddr)
if err != nil {
break
}
}
}()
} else if cAddr.String() != clientAddr.String() {
// Not our client, bye
continue
}
_, _, _, addr := parseDatagramRequestAddress(d)
/*
action, arg := acl.ActionProxy, ""
if s.ACLEngine != nil {
action, arg = s.ACLEngine.Lookup(domain, ip)
}
*/
err = hyUDP.WriteTo(d.Data, addr)
if err != nil {
break
}
}
}
func sendReply(conn *net.TCPConn, rep byte) error {
p := socks5.NewReply(rep, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
_, err := p.WriteTo(conn)
@ -222,3 +334,13 @@ func parseRequestAddress(r *socks5.Request) (domain string, ip net.IP, port stri
return "", r.DstAddr, p, net.JoinHostPort(net.IP(r.DstAddr).String(), p)
}
}
func parseDatagramRequestAddress(r *socks5.Datagram) (domain string, ip net.IP, port string, addr string) {
p := strconv.Itoa(int(binary.BigEndian.Uint16(r.DstPort)))
if r.Atyp == socks5.ATYPDomain {
d := string(r.DstAddr[1:])
return d, nil, p, net.JoinHostPort(d, p)
} else {
return "", r.DstAddr, p, net.JoinHostPort(net.IP(r.DstAddr).String(), p)
}
}