Skip to content

Commit

Permalink
Add comments and rename vars
Browse files Browse the repository at this point in the history
  • Loading branch information
wfg committed Jan 13, 2024
1 parent eba4c36 commit 67a0cee
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 33 deletions.
4 changes: 2 additions & 2 deletions cmd/socks5-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (

func main() {
config := socks5.Config{}
flag.StringVar(&config.InboundAddress, "l", ":1080", "local address")
flag.StringVar(&config.ListenAddress, "l", ":1080", "local address")
flag.StringVar(&config.Username, "u", "", "username")
flag.StringVar(&config.Password, "p", "", "password")
flag.StringVar(&config.OutboundInterface, "iface", "", "specified interface")
flag.StringVar(&config.ExternalInterface, "iface", "", "specified interface")
flag.IntVar(&config.Timeout, "t", 30, "dial timeout in seconds")
flag.BoolVar(&config.Debug, "debug", false, "enable debug logging")
flag.Parse()
Expand Down
3 changes: 2 additions & 1 deletion internal/socks5/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const (

// Commands
const (
Connect uint8 = iota + 1
_ uint8 = iota
Connect
Bind
UDPAssociate
)
Expand Down
8 changes: 4 additions & 4 deletions internal/socks5/socks5.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import (
)

type Config struct {
InboundAddress string
ListenAddress string
Username string
Password string
OutboundInterface string
ExternalInterface string
Timeout int
Debug bool
}

func StartServer(config Config) {
var externalInterface *net.Interface
var err error
if config.OutboundInterface == "" {
if config.ExternalInterface == "" {
externalInterface = nil
} else {
externalInterface, err = net.InterfaceByName(config.OutboundInterface)
externalInterface, err = net.InterfaceByName(config.ExternalInterface)
if err != nil {
log.Fatalln("unable to get external interface", err)
}
Expand Down
140 changes: 114 additions & 26 deletions internal/socks5/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ type TCPServer struct {
}

func (t *TCPServer) Start() {
l, err := net.Listen("tcp", t.config.InboundAddress)
l, err := net.Listen("tcp", t.config.ListenAddress)
if err != nil {
t.log.Fatalf("failed to listen: %v", err)
}
t.log.Printf("listening on %s", t.config.InboundAddress)
t.log.Printf("listening on %s", t.config.ListenAddress)
for {
conn, err := l.Accept()
if err != nil {
Expand All @@ -37,80 +37,120 @@ func (t *TCPServer) Start() {

func (t *TCPServer) handle(srcConn net.Conn) {
defer srcConn.Close()

// Receiving the following on initial connection:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+

// VER is the first octet and must be X'05' for SOCKS5.
clientSocksVer := make([]byte, 1)
n, err := srcConn.Read(clientSocksVer)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read socks version on client greeting: %v", err)
return
}
if clientSocksVer[0] != SocksVersion {
t.log.Debugf("invalid socks version: %d", clientSocksVer[0])
return
}

// NMETHODS is the second octet and is the number of octets in the following METHODS.
numClientAuthMethods := make([]byte, 1)
n, err = srcConn.Read(numClientAuthMethods)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read number of client auth methods: %v", err)
return
}
clientAuthMethods := make([]byte, numClientAuthMethods[0])
n, err = srcConn.Read(clientAuthMethods)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client auth methods: %v", err)
return
}

var authMethod byte
// I am only supporting 'no authentication' and 'username/password authentication' methods.
// Their values are X'00' and X'02' respectively. The following is returned to the client:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+

if t.config.Username != "" && t.config.Password != "" {
authMethod = UserAuth
if !contains(clientAuthMethods, UserAuth) {
t.log.Debugf("client does not support user/pass auth")
t.log.Debugf("client does not support username/password authentication")
_, err = srcConn.Write([]byte{SocksVersion, NoAcceptableMethods})
if err != nil {
t.log.Debugf("failed to write no acceptable methods: %v", err)
}
return
}
_, err = srcConn.Write([]byte{SocksVersion, authMethod})
_, err = srcConn.Write([]byte{SocksVersion, UserAuth})
if err != nil {
t.log.Debugf("failed to write selected auth method: %v", err)
return
}

// After agreeing on the username/password authentication method, the client sends the following:
// +----+------+----------+------+----------+
// |VER | ULEN | UNAME | PLEN | PASSWD |
// +----+------+----------+------+----------+
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+

// VER is the first octet and must be X'01' for username/password authentication.
clientUserAuthVersion := make([]byte, 1)
n, err = srcConn.Read(clientUserAuthVersion)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read user/pass auth version: %v", err)
return
}
if clientUserAuthVersion[0] != UserAuthVersion {
t.log.Debugf("invalid user/pass auth version: %d", clientUserAuthVersion[0])
return
}

// ULEN is the next octet and it is the number of octets required for the username.
usernameLen := make([]byte, 1)
n, err = srcConn.Read(usernameLen)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client username length: %v", err)
return
}
// UNAME is the username itself and requires ULEN octets.
username := make([]byte, usernameLen[0])
n, err = srcConn.Read(username)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client username: %v", err)
return
}

// PLEN is the next octet and it is the number of octets required for the username.
passwordLen := make([]byte, 1)
n, err = srcConn.Read(passwordLen)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client password length: %v", err)
return
}
// PASSWD is the password itself and requires PLEN octets.
password := make([]byte, passwordLen[0])
n, err = srcConn.Read(password)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client password: %v", err)
return
}

// Once the username and password are read, the following is returned to the client:
// +----+--------+
// |VER | STATUS |
// +----+--------+
// | 1 | 1 |
// +----+--------+

// VER must be X'01', and STATUS of X'00' indicates success while X'01' indicates failure.
if string(username) != t.config.Username || string(password) != t.config.Password {
t.log.Printf("invalid username or password")
_, err = srcConn.Write([]byte{UserAuthVersion, Failure})
Expand All @@ -126,7 +166,6 @@ func (t *TCPServer) handle(srcConn net.Conn) {
return
}
} else {
authMethod = NoAuth
if !contains(clientAuthMethods, NoAuth) {
t.log.Printf("client does not support no auth")
_, err = srcConn.Write([]byte{SocksVersion, NoAcceptableMethods})
Expand All @@ -135,16 +174,35 @@ func (t *TCPServer) handle(srcConn net.Conn) {
}
return
}
_, err = srcConn.Write([]byte{SocksVersion, authMethod})
_, err = srcConn.Write([]byte{SocksVersion, NoAuth})
if err != nil {
t.log.Debugf("failed to write selected auth method: %v", err)
return
}
}

// Once authentication negotiation is complete, the client sends its request details.
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// o VER protocol version: X'05'
// o CMD
// o CONNECT X'01'
// o BIND X'02'
// o UDP ASSOCIATE X'03'
// o RSV RESERVED
// o ATYP address type of following address
// o IP V4 address: X'01'
// o DOMAINNAME: X'03'
// o IP V6 address: X'04'
// o DST.ADDR desired destination address
// o DST.PORT desired destination port in network octet order
clientSocksVer = make([]byte, 1)
n, err = srcConn.Read(clientSocksVer)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read socks version on client connection request: %v", err)
return
}
Expand All @@ -154,19 +212,19 @@ func (t *TCPServer) handle(srcConn net.Conn) {
}
command := make([]byte, 1)
n, err = srcConn.Read(command)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read command: %v", err)
return
}
// ignore the reserved byte
_, err = srcConn.Read(make([]byte, 1))
if err != nil {
n, err = srcConn.Read(make([]byte, 1))
if n == 0 || err != nil {
t.log.Debugf("failed to read reserved byte: %v", err)
return
}
clientDstAddrType := make([]byte, 1)
n, err = srcConn.Read(clientDstAddrType)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client destination address type: %v", err)
return
}
Expand All @@ -179,7 +237,7 @@ func (t *TCPServer) handle(srcConn net.Conn) {
case DomainName:
domainNameLen := make([]byte, 1)
n, err = srcConn.Read(domainNameLen)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read domain name length: %v", err)
return
}
Expand All @@ -189,16 +247,25 @@ func (t *TCPServer) handle(srcConn net.Conn) {
return
}
n, err = srcConn.Read(clientDstAddr)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client destination address: %v", err)
return
}
clientDstPort := make([]byte, 2)
n, err = srcConn.Read(clientDstPort)
if err != nil || n == 0 {
if n == 0 || err != nil {
t.log.Debugf("failed to read client destination port: %v", err)
return
}

// After the request is received, the following is sent to the client:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+

// Only CONNECT is supported.
switch command[0] {
case Connect:
dialer := &net.Dialer{
Expand All @@ -214,7 +281,28 @@ func (t *TCPServer) handle(srcConn net.Conn) {

clientDst := net.JoinHostPort(string(clientDstAddr), strconv.Itoa(int(binary.BigEndian.Uint16(clientDstPort))))
// t.log.Debugf("connecting to %s", clientDst)
dstConn, err := dialer.Dial("tcp", clientDst)
clientDstConn, err := dialer.Dial("tcp", clientDst)

// Once the connection is established, the client is sent the following:
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Where:
// o VER protocol version: X'05'
// o REP Reply field:
// o X'00' succeeded
// o X'01' general SOCKS server failure
// o RSV RESERVED
// o ATYP address type of following address
// o IP V4 address: X'01'
// o DOMAINNAME: X'03'
// o IP V6 address: X'04'
// o BND.ADDR server bound address
// o BND.PORT server bound port in network octet order
// Fields marked RESERVED (RSV) must be set to X'00'.

if err != nil {
// t.log.Debugf("failed to connect to %s: %v", clientDst, err)
_, err = srcConn.Write([]byte{SocksVersion, Failure, 0, IPv4, 0, 0, 0, 0, 0, 0})
Expand All @@ -229,7 +317,7 @@ func (t *TCPServer) handle(srcConn net.Conn) {
t.log.Debugf("failed to write connect success reply: %v", err)
return
}
t.proxy(srcConn, dstConn)
t.proxy(srcConn, clientDstConn)
case Bind:
// TODO
t.log.Printf("bind not implemented")
Expand Down

0 comments on commit 67a0cee

Please sign in to comment.