Skip to content

Instantly share code, notes, and snippets.

@physacco
Created March 13, 2013 13:55
Show Gist options
  • Select an option

  • Save physacco/5152312 to your computer and use it in GitHub Desktop.

Select an option

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.
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)
}
}
Copy link
Copy Markdown

ghost commented Dec 17, 2013

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment