diff --git a/client.go b/client.go index d93dbf8..2eedaf6 100644 --- a/client.go +++ b/client.go @@ -255,10 +255,37 @@ func (cc *Client) handleRemoteForward(conn net.Conn, local string) { } func (cc *Client) handleDynamicForward(conn net.Conn) { + addr, err := getOriginDst(conn) + if err == nil { + if addr.String() != conn.LocalAddr().String() { + // transparent proxy + // iptables redirect the packet to this port + cc.handleTransparentProxy(conn, addr) + return + } + } else { + // SO_ORIGNAL_DST failed + // ipv6 not support this syscall + // so ignore it + Log(DEBUG, "get original destination on %s failed: %s, ignore", + conn.LocalAddr(), err) + } + + // socks5 to this port s := socks.Conn{Conn: conn, Dial: cc.client.Dial} s.Serve() } +func (cc *Client) handleTransparentProxy(c net.Conn, addr net.Addr) { + c2, err := cc.client.Dial("tcp", addr.String()) + if err != nil { + Log(ERROR, "%s", err) + c.Close() + return + } + PipeAndClose(c2, c) +} + func (cc *Client) keepAlive(interval time.Duration, maxCount int) { count := 0 c := time.NewTicker(interval) diff --git a/redir.go b/redir.go new file mode 100644 index 0000000..76419a0 --- /dev/null +++ b/redir.go @@ -0,0 +1,11 @@ +// +build !linux + +package obfssh + +import ( + "net" +) + +func getOriginDst(c net.Conn) (net.Addr, error) { + return c.LocalAddr(), nil +} diff --git a/redir_iptables.go b/redir_iptables.go new file mode 100644 index 0000000..922a87f --- /dev/null +++ b/redir_iptables.go @@ -0,0 +1,76 @@ +// +build linux + +package obfssh + +import ( + "encoding/binary" + "fmt" + "net" + "syscall" + "unsafe" +) + +const ( + // SO_ORIGINAL_DST in linux/netfilter_ipv4.h + soOriginalDst = 80 +) + +func getOriginDst(c net.Conn) (net.Addr, error) { + var sockaddr syscall.RawSockaddrAny + var len = unsafe.Sizeof(sockaddr) + + cc, ok := c.(*net.TCPConn) + if !ok { + return nil, fmt.Errorf("only tcp socket supported") + } + + f, err := cc.File() + if err != nil { + return nil, err + } + + defer f.Close() + + // get original ip destination, in C like this + // + // struct sockaddr addr; + // memset(&addr, 0, sizeof(addr); + // int len = sizeof(addr); + // getsocketopt(fd, SOL_IP, SO_ORIGINAL_DST, &addr, &len); + // + _, _, errno := syscall.Syscall6(sysGetSockOpt, f.Fd(), + uintptr(syscall.SOL_IP), uintptr(soOriginalDst), + uintptr(unsafe.Pointer(&sockaddr)), + uintptr(unsafe.Pointer(&len)), 0) + + if errno != 0 { + return nil, fmt.Errorf("syscall error %d", errno) + } + + var port uint16 + var ip net.IP + + switch sockaddr.Addr.Family { + case syscall.AF_INET: + a := (*syscall.RawSockaddrInet4)(unsafe.Pointer(&sockaddr)) + ip = net.IP(a.Addr[0:]) + port = ntohl(a.Port) + case syscall.AF_INET6: + a := (*syscall.RawSockaddrInet6)(unsafe.Pointer(&sockaddr)) + ip = net.IP(a.Addr[0:]) + port = ntohl(a.Port) + default: + return nil, fmt.Errorf("unknown socket family: %d", + sockaddr.Addr.Family) + } + + addr := &net.TCPAddr{IP: ip, Port: int(port)} + return addr, nil +} + +func ntohl(a uint16) uint16 { + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, a) + c := binary.LittleEndian.Uint16(b) + return c +} diff --git a/redir_linux_386.go b/redir_linux_386.go new file mode 100644 index 0000000..8b5a394 --- /dev/null +++ b/redir_linux_386.go @@ -0,0 +1,5 @@ +package obfssh + +// getsockopt syscall number +// refer to $GOROOT/src/syscall/syscall_linux_386.go +const sysGetSockOpt = 15 diff --git a/redir_vars.go b/redir_vars.go new file mode 100644 index 0000000..dc7463f --- /dev/null +++ b/redir_vars.go @@ -0,0 +1,11 @@ +// +build linux, !386 +// +build linux + +package obfssh + +import ( + "syscall" +) + +// getsockopt syscall number +const sysGetSockOpt = syscall.SYS_GETSOCKOPT