From 41e7a6df95254720c874e38a46dffa21d0927ef4 Mon Sep 17 00:00:00 2001 From: Yifu Yu Date: Mon, 7 Aug 2017 15:57:17 +0800 Subject: [PATCH] First working prototype. --- main.go | 59 +++++++++++++++++++++++++++++++++++++ server/hack.go | 26 +++++++++++++++++ server/hack_linux.go | 69 ++++++++++++++++++++++++++++++++++++++++++++ server/ipv4.go | 48 ++++++++++++++++++++++++++++++ server/ipv6.go | 48 ++++++++++++++++++++++++++++++ server/listener.go | 17 +++++++++++ 6 files changed, 267 insertions(+) create mode 100644 main.go create mode 100644 server/hack.go create mode 100644 server/hack_linux.go create mode 100644 server/ipv4.go create mode 100644 server/ipv6.go create mode 100644 server/listener.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..a9a0f91 --- /dev/null +++ b/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "git-core.megvii-inc.com/yuyifu/transparent-proxy/server" + "fmt" + "net" + "io" +) + +func main() { + proxy_listener, err := server.NewIPv4TransparentListener(":9091") + if err != nil { + panic(err) + } + for { + conn, err := proxy_listener.Accept() + if err != nil { + fmt.Println("Accept Error: ", err.Error()) + continue + } + fmt.Printf("Accepted connection: %s => %s\n", conn.TCPConn().RemoteAddr().String(), conn.RealAddr().String()) + go handle(conn) + } +} + +func pipeThenClose(reader io.Reader, writer io.WriteCloser) { + defer writer.Close() + buff := make([]byte, 4096) + for { + n, err := reader.Read(buff) + if n > 0 { + _, err := writer.Write(buff[:n]) + if err != nil { + fmt.Printf("Write Error: %#v\n", err.Error()) + return + } + } + if err == io.EOF { + fmt.Println("Reader reached EOF, closing.") + return + } + if err != nil { + // Enable this line ONLY FOR DEBUG PURPOSE. + // fmt.Printf("Read Error: %#v\n", err.Error()) + return + } + } +} + +func handle(conn server.TransparentConnection) { + rconn, err := net.DialTCP("tcp4", nil, conn.RealAddr()) + if err != nil { + fmt.Println("Connect to ", conn.RealAddr().String(), " error: ", err.Error()) + conn.TCPConn().Close() + return + } + go pipeThenClose(conn.TCPConn(), rconn) + go pipeThenClose(rconn, conn.TCPConn()) +} \ No newline at end of file diff --git a/server/hack.go b/server/hack.go new file mode 100644 index 0000000..b56f7f7 --- /dev/null +++ b/server/hack.go @@ -0,0 +1,26 @@ +package server + +import ( + "unsafe" +) + +func isLittleEndian() bool { + var placeHolder uint32 = 0x0000FFFF + return (*[4]byte)(unsafe.Pointer(&placeHolder))[0] == 0xFF +} + +func be32toh(host uint32) uint32 { + if isLittleEndian() { + return ((host & 255) << 24) | (((host >> 8) & 255) << 16) | (((host >> 16) & 255) << 8) | (host >> 24) + } else { + return host + } +} + +func be16toh(host uint16) uint16 { + if isLittleEndian() { + return ((host & 255) << 8) | (host >> 8) + } else { + return host + } +} diff --git a/server/hack_linux.go b/server/hack_linux.go new file mode 100644 index 0000000..2b93b8a --- /dev/null +++ b/server/hack_linux.go @@ -0,0 +1,69 @@ +package server + +/** + * WARNING WARNING WARNING + * This file contains MANY DIRTY HACKS, and most of them made much assume to go internal code layout. + * For every new go version, you MUST try to use every function, make sure they didn't panic for you. + */ + +// WARZONE BEGINS HERE! MIND YOUR HEAD! + +import ( + "reflect" + "net" + "golang.org/x/sys/unix" +) + +const ( + SO_ORIGINAL_DST = 80 +) + +func GetFDFromTCPConn(conn *net.TCPConn) int { + // Actual fd is stored at: (*(*TCPConn).conn.fd).sysfd + v := reflect.ValueOf(*conn) + c := v.FieldByName("conn") + fdp := c.FieldByName("fd") + fd := reflect.Indirect(fdp) + sysfd := fd.FieldByName("sysfd") + return int(sysfd.Int()) +} + +func GetAddr4FromFD(fd int) *net.TCPAddr { + mtuinfo, err := unix.GetsockoptIPv6MTUInfo(fd, unix.IPPROTO_IP, SO_ORIGINAL_DST) + if err != nil { + panic(err) + } + addr := mtuinfo.Addr + /* + RawSockaddrInet6 layout: + Family 2byte ignore or assert == AF_INET + Port 2byte ipv4 port + Flowinfo 4byte ipv4 address + Addr 16byte ignore for ipv4 + Scope_id 4byte ignore for ipv4 + */ + addr.Flowinfo = be32toh(addr.Flowinfo) + a, b, c, d := byte(addr.Flowinfo >> 24), byte((addr.Flowinfo >> 16) & 255), byte((addr.Flowinfo >> 8) & 255), byte(addr.Flowinfo & 255) + ip := net.IPv4(a, b, c, d) + return &net.TCPAddr{IP: ip, Port: int(be16toh(addr.Port)), Zone: ""} +} + +func GetAddr6FromFD(fd int) *net.TCPAddr { + mtuinfo, err := unix.GetsockoptIPv6MTUInfo(fd, unix.IPPROTO_IPV6, SO_ORIGINAL_DST) + if err != nil { + panic(err) + } + addr := mtuinfo.Addr + /* + RawSockaddrInet6 layout: + Family 2byte ignore or assert == AF_INET6 + Port 2byte ipv6 port + Flowinfo 4byte ipv6 flowinfo ignore + Addr 16byte ipv6 addr + Scope_id 4byte ipv6 scope id ignore + */ + v6addr := make(net.IP, 16) + // Make GC happy? + copy(v6addr, addr.Addr[:]) + return &net.TCPAddr{IP: v6addr, Port: int(be16toh(addr.Port)), Zone: ""} +} \ No newline at end of file diff --git a/server/ipv4.go b/server/ipv4.go new file mode 100644 index 0000000..af0d800 --- /dev/null +++ b/server/ipv4.go @@ -0,0 +1,48 @@ +package server + +import ( + "net" +) + +type IPv4TransparentListener struct { + listener *net.TCPListener +} + +type IPv4TransparentConnection struct { + conn *net.TCPConn + remoteAddr *net.TCPAddr +} + +func NewIPv4TransparentListener(addr string) (*IPv4TransparentListener, error) { + tcpaddr, err := net.ResolveTCPAddr("tcp4", addr) + if err != nil { + return nil, err + } + listener, err := net.ListenTCP("tcp4", tcpaddr) + if err != nil { + return nil, err + } + return &IPv4TransparentListener{listener: listener}, nil +} + +func (l *IPv4TransparentListener) Close() error { + return l.listener.Close() +} + +func (l *IPv4TransparentListener) Accept() (*IPv4TransparentConnection, error) { + conn, err := l.listener.AcceptTCP() + if err != nil { + return nil, err + } + fd := GetFDFromTCPConn(conn) + remoteAddr := GetAddr4FromFD(fd) + return &IPv4TransparentConnection{conn: conn, remoteAddr: remoteAddr}, nil +} + +func (c *IPv4TransparentConnection) TCPConn() *net.TCPConn { + return c.conn +} + +func (c *IPv4TransparentConnection) RealAddr() *net.TCPAddr { + return c.remoteAddr +} diff --git a/server/ipv6.go b/server/ipv6.go new file mode 100644 index 0000000..ddbd53f --- /dev/null +++ b/server/ipv6.go @@ -0,0 +1,48 @@ +package server + +import ( + "net" +) + +type IPv6TransparentListener struct { + listener *net.TCPListener +} + +type IPv6TransparentConnection struct { + conn *net.TCPConn + remoteAddr *net.TCPAddr +} + +func NewIPv6TransparentListener(addr string) (*IPv6TransparentListener, error) { + tcpaddr, err := net.ResolveTCPAddr("tcp6", addr) + if err != nil { + return nil, err + } + listener, err := net.ListenTCP("tcp6", tcpaddr) + if err != nil { + return nil, err + } + return &IPv6TransparentListener{listener: listener}, nil +} + +func (l *IPv6TransparentListener) Close() error { + return l.listener.Close() +} + +func (l *IPv6TransparentListener) Accept() (*IPv6TransparentConnection, error) { + conn, err := l.listener.AcceptTCP() + if err != nil { + return nil, err + } + fd := GetFDFromTCPConn(conn) + remoteAddr := GetAddr6FromFD(fd) + return &IPv6TransparentConnection{conn: conn, remoteAddr: remoteAddr}, nil +} + +func (c *IPv6TransparentConnection) TCPConn() *net.TCPConn { + return c.conn +} + +func (c *IPv6TransparentConnection) RealAddr() *net.TCPAddr { + return c.remoteAddr +} diff --git a/server/listener.go b/server/listener.go new file mode 100644 index 0000000..d90009a --- /dev/null +++ b/server/listener.go @@ -0,0 +1,17 @@ +package server + +import ( + "net" +) + +type TransparentListener interface { + Accept() (TransparentConnection, error) + Close() error +} + +type TransparentConnection interface { + TCPConn() *net.TCPConn + RealAddr() *net.TCPAddr +} + +