commit e79873c7b5f96ce7c4f401dbeffc26ae9b870d63 Author: fangdingjun Date: Tue Nov 17 16:39:28 2015 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..2ed562a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.exe +*~ +*bak diff --git a/README.md b/README.md new file mode 100755 index 0000000..aa5f47e --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +socks-go +======= + +A socks server implement by golang, support socks4/4a, socks5. + + +usage +==== +Usage example: + + import "github.com/fangdingjun/socks" + + fucn main(){ + l, _ := net.Listen("tcp", ":1080") + for { + conn, _ := l.Accept() + s := socks.SocksConn{conn} + go s.Serve() + } + } diff --git a/cmd/server/README.md b/cmd/server/README.md new file mode 100755 index 0000000..1fb9074 --- /dev/null +++ b/cmd/server/README.md @@ -0,0 +1,15 @@ +socks-server +============ + +this is a socks server example. + +run it + + go run server.go + +test it + + curl --socks4 127.0.0.1:1080 http://www.google.com/ + curl --socks4a 127.0.0.1:1080 http://www.google.com/ + curl --socks5 127.0.0.1:1080 http://www.google.com/ + curl --socks5-hostname 127.0.0.1:1080 http://www.google.com/ diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100755 index 0000000..249ec7f --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/fangdingjun/socks" + "log" + "net" +) + +func main() { + conn, err := net.Listen("tcp", ":1080") + if err != nil { + log.Fatal(err) + } + for { + c, err := conn.Accept() + if err != nil { + log.Println(err) + continue + } + log.Printf("connected from %s", c.RemoteAddr()) + s := socks.SocksConn{ClientConn: c} + go s.Serve() + } +} diff --git a/socks.go b/socks.go new file mode 100755 index 0000000..4c58e97 --- /dev/null +++ b/socks.go @@ -0,0 +1,39 @@ +package socks + +import ( + "io" + "log" + "net" +) + +const ( + socks4Version = 0x04 + socks5Version = 0x05 + cmdConnect = 0x01 + addrTypeIPv4 = 0x01 + addrTypeDomain = 0x03 + addrTypeIPv6 = 0x04 +) + +type SocksConn struct { + ClientConn net.Conn +} + +func (s *SocksConn) Serve() { + buf := make([]byte, 1) + + // read version + io.ReadFull(s.ClientConn, buf) + + switch buf[0] { + case socks4Version: + s4 := socks4Conn{client_conn: s.ClientConn} + s4.Serve() + case socks5Version: + s5 := socks5Conn{client_conn: s.ClientConn} + s5.Serve() + default: + log.Printf("error version %s", buf[0]) + s.ClientConn.Close() + } +} diff --git a/socks4.go b/socks4.go new file mode 100755 index 0000000..9b9da53 --- /dev/null +++ b/socks4.go @@ -0,0 +1,104 @@ +package socks + +import ( + "encoding/binary" + "fmt" + "io" + "log" + "net" +) + +type socks4Conn struct { + server_conn net.Conn + client_conn net.Conn +} + +func (s4 *socks4Conn) Serve() { + defer s4.Close() + if err := s4.processRequest(); err != nil { + log.Println(err) + return + } +} + +func (s4 *socks4Conn) Close() { + if s4.client_conn != nil { + s4.client_conn.Close() + } + if s4.server_conn != nil { + s4.server_conn.Close() + } +} + +func (s4 *socks4Conn) forward() { + go func() { + io.Copy(s4.client_conn, s4.server_conn) + }() + + io.Copy(s4.server_conn, s4.client_conn) +} + +func (s4 *socks4Conn) processRequest() error { + // version has already read out by socksConn.Serve() + // process command and target here + + buf := make([]byte, 128) + n, err := io.ReadAtLeast(s4.client_conn, buf, 8) + if err != nil { + return err + } + + // only support connect + if buf[0] != cmdConnect { + return fmt.Errorf("error command %s", buf[0]) + } + + port := binary.BigEndian.Uint16(buf[1:3]) + + ip := net.IP(buf[3:7]) + + // NULL-terminated user string + // jump to NULL character + var j int + for j = 7; j < n; j++ { + if buf[j] == 0x00 { + break + } + } + + host := ip.String() + + // socks4a + // 0.0.0.x + if ip[0] == 0x00 && ip[1] == 0x00 && ip[2] == 0x00 && ip[3] != 0x00 { + j++ + var i = j + + // jump to the end of hostname + for j = i; j < n; j++ { + if buf[j] == 0x00 { + break + } + } + host = string(buf[i:j]) + } + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + // reply user with connect success + // if dial to target failed, user will receive connection reset + s4.client_conn.Write([]byte{0x00, 0x5a, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00}) + + log.Printf("connecting to %s", target) + + // connect to the target + s4.server_conn, err = net.Dial("tcp", target) + if err != nil { + return err + } + + // enter data exchange + s4.forward() + + return nil +} diff --git a/socks5.go b/socks5.go new file mode 100755 index 0000000..dd2a8f1 --- /dev/null +++ b/socks5.go @@ -0,0 +1,133 @@ +package socks + +import ( + "fmt" + "io" + "log" + "net" + //"strconv" + "encoding/binary" +) + +type socks5Conn struct { + //addr string + client_conn net.Conn + server_conn net.Conn +} + +func (s5 *socks5Conn) Serve() { + defer s5.Close() + if err := s5.handshake(); err != nil { + log.Println(err) + return + } + + if err := s5.processRequest(); err != nil { + log.Println(err) + return + } +} + +func (s5 *socks5Conn) handshake() error { + // version has already readed by socksConn.Serve() + // only process auth methods here + + buf := make([]byte, 258) + n, err := io.ReadAtLeast(s5.client_conn, buf, 1) + if err != nil { + return err + } + + l := int(buf[0]) + 1 + if n < l { + _, err := io.ReadFull(s5.client_conn, buf[n:l]) + if err != nil { + return err + } + } + + // no auth required + s5.client_conn.Write([]byte{0x05, 0x00}) + + return nil +} + +func (s5 *socks5Conn) processRequest() error { + buf := make([]byte, 258) + n, err := io.ReadAtLeast(s5.client_conn, buf, 10) + if err != nil { + return err + } + if buf[0] != socks5Version { + return fmt.Errorf("error version %s", buf[0]) + } + + // only support connect + if buf[1] != cmdConnect { + return fmt.Errorf("unsupported command %s", buf[1]) + } + + hlen := 0 + host := "" + msglen := 0 + switch buf[3] { + case addrTypeIPv4: + hlen = 4 + case addrTypeDomain: + hlen = int(buf[4]) + 1 + case addrTypeIPv6: + hlen = 16 + } + + msglen = 6 + hlen + + if n < msglen { + _, err := io.ReadFull(s5.client_conn, buf[n:msglen]) + if err != nil { + return err + } + } + + // get target address + addr := buf[4 : 4+hlen] + if buf[3] == addrTypeDomain { + host = string(addr[1:]) + } else { + host = net.IP(addr).String() + } + + port := binary.BigEndian.Uint16(buf[msglen-2 : msglen]) + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + // reply user with connect success + // if dial to target failed, user will receive connection reset + s5.client_conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}) + + log.Printf("connecing to %s", target) + + // connect to the target + s5.server_conn, err = net.Dial("tcp", target) + if err != nil { + return err + } + + // enter data exchange + s5.forward() + + return nil +} + +func (s5 *socks5Conn) forward() { + go io.Copy(s5.client_conn, s5.server_conn) + io.Copy(s5.server_conn, s5.client_conn) +} + +func (s5 *socks5Conn) Close() { + if s5.server_conn != nil { + s5.server_conn.Close() + } + if s5.client_conn != nil { + s5.client_conn.Close() + } +}