Created
March 13, 2013 13:55
-
-
Save physacco/5152312 to your computer and use it in GitHub Desktop.
This is a high performance socks5 proxy server written in go. It is much faster than those I wrote before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "io" | |
| "os" | |
| "fmt" | |
| "log" | |
| "net" | |
| "time" | |
| "strings" | |
| "strconv" | |
| ) | |
| type SockAddr struct { | |
| Host string | |
| Port int | |
| } | |
| // Convert a "host:port" string to SockAddr | |
| func MakeSockAddr(HostPort string) SockAddr { | |
| pair := strings.Split(HostPort, ":") | |
| host, portstr := pair[0], pair[1] | |
| port, err := strconv.Atoi(portstr) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return SockAddr{host, port} | |
| } | |
| func (addr *SockAddr) String() string { | |
| return fmt.Sprintf("%s:%d", addr.Host, addr.Port) | |
| } | |
| func (addr *SockAddr) ByteArray() []byte { | |
| bytes := make([]byte, 6) | |
| copy(bytes[:4], net.ParseIP(addr.Host).To4()) | |
| bytes[4] = byte(addr.Port / 256) | |
| bytes[5] = byte(addr.Port % 256) | |
| return bytes | |
| } | |
| func isUseOfClosedConn(err error) bool { | |
| operr, ok := err.(*net.OpError) | |
| return ok && operr.Err.Error() == "use of closed network connection" | |
| } | |
| func iobridge(src io.Reader, dst io.Writer, quit chan bool) { | |
| defer func() { quit <- true }() | |
| buf := make([]byte, 8192) | |
| for { | |
| n, err := src.Read(buf) | |
| if err != nil { | |
| if !(err == io.EOF || isUseOfClosedConn(err)) { | |
| log.Println("error reading socket:", err) | |
| } | |
| break | |
| } | |
| _, err = dst.Write(buf[:n]) | |
| if err != nil { | |
| log.Println("error writing socket:", err) | |
| break | |
| } | |
| } | |
| } | |
| func readBytes(conn net.Conn, count int) (buf []byte) { | |
| buf = make([]byte, count) | |
| if _, err := io.ReadFull(conn, buf); err != nil { | |
| panic(err) | |
| } | |
| return | |
| } | |
| func protocolCheck(assert bool) { | |
| if !assert { | |
| panic("protocol error") | |
| } | |
| } | |
| func errorReplyConnect(reason byte) []byte { | |
| return []byte{0x05, reason, 0x00, 0x01, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} | |
| } | |
| func handleConnection(conn net.Conn) { | |
| clientaddr := MakeSockAddr(conn.RemoteAddr().String()) | |
| log.Printf("accepted from frontend %s\n", clientaddr.String()) | |
| defer func() { | |
| if err := recover(); err != nil { | |
| log.Printf("an error occurred with frontend %s: %s\n", | |
| clientaddr, err) | |
| } | |
| conn.Close() | |
| log.Printf("disconnected from frontend %s\n", clientaddr.String()) | |
| }() | |
| conn.SetDeadline(time.Now().Add(30 * time.Second)) | |
| // receive auth packet | |
| buf1 := readBytes(conn, 2) | |
| protocolCheck(buf1[0] == 0x05) | |
| nom := int(buf1[1]) // number of methods | |
| methods := readBytes(conn, nom) | |
| var support bool | |
| for _, meth := range methods { | |
| if meth == 0x00 { | |
| support = true | |
| break | |
| } | |
| } | |
| if !support { | |
| conn.Write([]byte{0x05, 0xff}) | |
| return | |
| } | |
| // reply to the auth packet | |
| conn.Write([]byte{0x05, 0x00}) | |
| // receive command | |
| buf3 := readBytes(conn, 4) | |
| protocolCheck(buf3[0] == 0x05) | |
| protocolCheck(buf3[2] == 0x00) | |
| command := buf3[1] | |
| if command != 0x01 { // 0x01: CONNECT | |
| conn.Write(errorReplyConnect(0x07)) | |
| return | |
| } | |
| addrtype := buf3[3] | |
| if addrtype != 0x01 && addrtype != 0x03 { | |
| conn.Write(errorReplyConnect(0x08)) | |
| return | |
| } | |
| var target string | |
| if addrtype == 0x01 { // 0x01: IP V4 address | |
| buf4 := readBytes(conn, 6) | |
| target = fmt.Sprintf("%d.%d.%d.%d:%d", buf4[0], buf4[1], | |
| buf4[2], buf4[3], int(buf4[4]) * 256 + int(buf4[5])) | |
| } else { // 0x03: DOMAINNAME | |
| buf4 := readBytes(conn, 1) | |
| nmlen := int(buf4[0]) // domain name length | |
| if nmlen > 253 { | |
| panic("domain name too long") | |
| } | |
| buf5 := readBytes(conn, nmlen + 2) | |
| target = fmt.Sprintf("%s:%d", buf5[0:nmlen], | |
| int(buf5[nmlen]) * 256 + int(buf5[nmlen+1])) | |
| } | |
| handleConnect(target, conn) | |
| } | |
| func handleConnect(target string, conn net.Conn) { | |
| log.Printf("trying to connect to %s...\n", target) | |
| bconn, err := net.Dial("tcp", target) | |
| if err != nil { | |
| log.Printf("failed to connect to %s: %s\n", target, err) | |
| conn.Write(errorReplyConnect(0x05)) | |
| return | |
| } | |
| remoteaddr := MakeSockAddr(bconn.RemoteAddr().String()) | |
| log.Printf("connected to backend %s\n", remoteaddr.String()) | |
| defer func() { | |
| bconn.Close() | |
| log.Printf("disconnected from backend %s\n", remoteaddr.String()) | |
| }() | |
| // reply to the CONNECT command | |
| buf := make([]byte, 10) | |
| copy(buf, []byte{0x05, 0x00, 0x00, 0x01}) | |
| copy(buf[4:], remoteaddr.ByteArray()) | |
| conn.Write(buf) | |
| // reset deadline | |
| conn.SetDeadline(time.Now().Add(2 * time.Hour)) | |
| bconn.SetDeadline(time.Now().Add(2 * time.Hour)) | |
| // bridge connection | |
| quit := make(chan bool) | |
| go iobridge(conn, bconn, quit) | |
| go iobridge(bconn, conn, quit) | |
| // wait for either side to close | |
| select { | |
| case <-quit: | |
| return | |
| } | |
| } | |
| func main() { | |
| listenaddr := "0.0.0.0:1080" | |
| ln, err := net.Listen("tcp", listenaddr) | |
| if err != nil { | |
| log.Printf("Listen error: %s\n", err) | |
| os.Exit(1) | |
| } | |
| log.Printf("listening on %s...\n", listenaddr) | |
| for { | |
| conn, err := ln.Accept() | |
| if err != nil { | |
| log.Printf("Accept error: %s\n", err) | |
| continue | |
| } | |
| go handleConnection(conn) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
it may have a bug, please review following code:
// bridge connection
// shutdown := make(chan bool) it may block one goroutine, when shutdown <- true(at iobridge's defer)
shutdown := make(chan bool, 2)
go iobridge(frontconn, backconn, shutdown)
go iobridge(backconn, frontconn, shutdown)
// wait for either side to close
/select {
case <-shutdown:
return
}/
<-shutdown // it's enough