github.com/AntonOrnatskyi/goproxy@v0.0.0-20190205095733-4526a9fa18b4/core/tproxy/tproxy.go (about) 1 // Package tproxy provides the TCPDial and TCPListen tproxy equivalent of the 2 // net package Dial and Listen with tproxy support for linux ONLY. 3 package tproxy 4 5 import ( 6 "fmt" 7 "net" 8 "os" 9 "time" 10 11 "golang.org/x/sys/unix" 12 ) 13 14 const big = 0xFFFFFF 15 const IP_ORIGADDRS = 20 16 17 // Debug outs the library in Debug mode 18 var Debug = false 19 20 func ipToSocksAddr(family int, ip net.IP, port int, zone string) (unix.Sockaddr, error) { 21 switch family { 22 case unix.AF_INET: 23 if len(ip) == 0 { 24 ip = net.IPv4zero 25 } 26 if ip = ip.To4(); ip == nil { 27 return nil, net.InvalidAddrError("non-IPv4 address") 28 } 29 sa := new(unix.SockaddrInet4) 30 for i := 0; i < net.IPv4len; i++ { 31 sa.Addr[i] = ip[i] 32 } 33 sa.Port = port 34 return sa, nil 35 case unix.AF_INET6: 36 if len(ip) == 0 { 37 ip = net.IPv6zero 38 } 39 // IPv4 callers use 0.0.0.0 to mean "announce on any available address". 40 // In IPv6 mode, Linux treats that as meaning "announce on 0.0.0.0", 41 // which it refuses to do. Rewrite to the IPv6 unspecified address. 42 if ip.Equal(net.IPv4zero) { 43 ip = net.IPv6zero 44 } 45 if ip = ip.To16(); ip == nil { 46 return nil, net.InvalidAddrError("non-IPv6 address") 47 } 48 sa := new(unix.SockaddrInet6) 49 for i := 0; i < net.IPv6len; i++ { 50 sa.Addr[i] = ip[i] 51 } 52 sa.Port = port 53 sa.ZoneId = uint32(zoneToInt(zone)) 54 return sa, nil 55 } 56 return nil, net.InvalidAddrError("unexpected socket family") 57 } 58 59 func zoneToInt(zone string) int { 60 if zone == "" { 61 return 0 62 } 63 if ifi, err := net.InterfaceByName(zone); err == nil { 64 return ifi.Index 65 } 66 n, _, _ := dtoi(zone, 0) 67 return n 68 } 69 70 func dtoi(s string, i0 int) (n int, i int, ok bool) { 71 n = 0 72 for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { 73 n = n*10 + int(s[i]-'0') 74 if n >= big { 75 return 0, i, false 76 } 77 } 78 if i == i0 { 79 return 0, i, false 80 } 81 return n, i, true 82 } 83 84 // IPTcpAddrToUnixSocksAddr returns Sockaddr for specified TCP addr. 85 func IPTcpAddrToUnixSocksAddr(addr string) (sa unix.Sockaddr, err error) { 86 if Debug { 87 fmt.Println("DEBUG: IPTcpAddrToUnixSocksAddr recieved address:", addr) 88 } 89 addressNet := "tcp6" 90 if addr[0] != '[' { 91 addressNet = "tcp4" 92 } 93 tcpAddr, err := net.ResolveTCPAddr(addressNet, addr) 94 if err != nil { 95 return nil, err 96 } 97 return ipToSocksAddr(ipType(addr), tcpAddr.IP, tcpAddr.Port, tcpAddr.Zone) 98 } 99 100 // IPv6UdpAddrToUnixSocksAddr returns Sockaddr for specified IPv6 addr. 101 func IPv6UdpAddrToUnixSocksAddr(addr string) (sa unix.Sockaddr, err error) { 102 tcpAddr, err := net.ResolveTCPAddr("udp6", addr) 103 if err != nil { 104 return nil, err 105 } 106 return ipToSocksAddr(unix.AF_INET6, tcpAddr.IP, tcpAddr.Port, tcpAddr.Zone) 107 } 108 109 // TCPListen is listening for incoming IP packets which are being intercepted. 110 // In conflict to regular Listen mehtod the socket destination and source addresses 111 // are of the intercepted connection. 112 // Else then that it works exactly like net package net.Listen. 113 func TCPListen(listenAddr string) (listener net.Listener, err error) { 114 s, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, 0) 115 if err != nil { 116 return nil, err 117 } 118 defer unix.Close(s) 119 err = unix.SetsockoptInt(s, unix.SOL_IP, unix.IP_TRANSPARENT, 1) 120 if err != nil { 121 return nil, err 122 } 123 124 sa, err := IPTcpAddrToUnixSocksAddr(listenAddr) 125 if err != nil { 126 return nil, err 127 } 128 err = unix.Bind(s, sa) 129 if err != nil { 130 return nil, err 131 } 132 err = unix.Listen(s, unix.SOMAXCONN) 133 if err != nil { 134 return nil, err 135 } 136 f := os.NewFile(uintptr(s), "TProxy") 137 defer f.Close() 138 return net.FileListener(f) 139 } 140 func ipType(localAddr string) int { 141 host, _, _ := net.SplitHostPort(localAddr) 142 if host != "" { 143 ip := net.ParseIP(host) 144 if ip == nil || ip.To4() != nil { 145 return unix.AF_INET 146 } 147 return unix.AF_INET6 148 } 149 return unix.AF_INET 150 } 151 152 // TCPDial is a special tcp connection which binds a non local address as the source. 153 // Except then the option to bind to a specific local address which the machine doesn't posses 154 // it is exactly like any other net.Conn connection. 155 // It is advised to use port numbered 0 in the localAddr and leave the kernel to choose which 156 // Local port to use in order to avoid errors and binding conflicts. 157 func TCPDial(localAddr, remoteAddr string, timeout time.Duration) (conn net.Conn, err error) { 158 timer := time.NewTimer(timeout) 159 defer timer.Stop() 160 if Debug { 161 fmt.Println("TCPDial from:", localAddr, "to:", remoteAddr) 162 } 163 s, err := unix.Socket(ipType(localAddr), unix.SOCK_STREAM, 0) 164 165 //In a case there was a need for a non-blocking socket an example 166 //s, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM |unix.SOCK_NONBLOCK, 0) 167 if err != nil { 168 fmt.Println(err) 169 return nil, err 170 } 171 defer unix.Close(s) 172 err = unix.SetsockoptInt(s, unix.SOL_IP, unix.IP_TRANSPARENT, 1) 173 if err != nil { 174 if Debug { 175 fmt.Println("ERROR setting the socket in IP_TRANSPARENT mode", err) 176 } 177 178 return nil, err 179 } 180 err = unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) 181 if err != nil { 182 if Debug { 183 fmt.Println("ERROR setting the socket in unix.SO_REUSEADDR mode", err) 184 } 185 return nil, err 186 } 187 188 rhost, _, err := net.SplitHostPort(localAddr) 189 if err != nil { 190 if Debug { 191 // fmt.Fprintln(os.Stderr, err) 192 fmt.Println("ERROR", err, "running net.SplitHostPort on address:", localAddr) 193 } 194 } 195 196 sa, err := IPTcpAddrToUnixSocksAddr(rhost + ":0") 197 if err != nil { 198 if Debug { 199 fmt.Println("ERROR creating a hostaddres for the socker with IPTcpAddrToUnixSocksAddr", err) 200 } 201 return nil, err 202 } 203 204 remoteSocket, err := IPTcpAddrToUnixSocksAddr(remoteAddr) 205 if err != nil { 206 if Debug { 207 fmt.Println("ERROR creating a remoteSocket for the socker with IPTcpAddrToUnixSocksAddr on the remote addres", err) 208 } 209 return nil, err 210 } 211 212 err = unix.Bind(s, sa) 213 if err != nil { 214 fmt.Println(err) 215 return nil, err 216 } 217 218 errChn := make(chan error, 1) 219 func() { 220 err = unix.Connect(s, remoteSocket) 221 if err != nil { 222 if Debug { 223 fmt.Println("ERROR Connecting from", s, "to:", remoteSocket, "ERROR:", err) 224 } 225 } 226 errChn <- err 227 }() 228 229 select { 230 case err = <-errChn: 231 if err != nil { 232 return nil, err 233 } 234 case <-timer.C: 235 return nil, fmt.Errorf("ERROR connect to %s timeout", remoteAddr) 236 } 237 f := os.NewFile(uintptr(s), "TProxyTCPClient") 238 client, err := net.FileConn(f) 239 if err != nil { 240 if Debug { 241 fmt.Println("ERROR os.NewFile", err) 242 } 243 return nil, err 244 } 245 if Debug { 246 fmt.Println("FINISHED Creating net.coo from:", client.LocalAddr().String(), "to:", client.RemoteAddr().String()) 247 } 248 return client, err 249 }