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  }