一、 通过系统调用获取TCP原始目标地址及端口

  1. opnsense+pf(freebsd+pf )

    1. 定义数据结构

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      type PfAddr struct {
      AddrRaw [16]byte
      }
      type PFIOCNatLook struct {
      Saddr PfAddr
      Daddr PfAddr
      RsAddr PfAddr
      RdAddr PfAddr
      Sport uint16
      Dport uint16
      Rsport uint16
      Rdport uint16
      Af uint8
      Proto uint8
      Direction uint8
      }

      https://www.freebsd.org/cgi/man.cgi?query=pf&apropos=0&sektion=4&manpath=FreeBSD+12.1-RELEASE&arch=default&format=html
      https://github.com/freebsd/freebsd/blob/master/sys/net/pfvar.h

    2. 打开pf设备文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      var _devPFFD uintptr = 0
      func initPFFD() {
      fd, err := syscall.Open("/dev/pf", os.O_RDWR, 0600)
      if err != nil {
      panic(err)
      } else {
      _devPFFD = uintptr(fd)
      }
      }
    3. 通过TCP连接获取pf转发前的原始目标地址

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      const   PF_OUT      = uint8(2)
      const DIOCNATLOOK = 0xc04c4417
      //https://github.com/freebsd/freebsd/blob/master/sys/net/pfvar.h

      func GetOriginDst(conn *net.TCPConn) (addr *net.TCPAddr, err error) {
      clientAddr := conn.RemoteAddr().(*net.TCPAddr)
      bindAddr := conn.LocalAddr().(*net.TCPAddr)
      pnl := PFIOCNatLook{
      Af: syscall.AF_INET,
      Proto: syscall.IPPROTO_TCP,
      Direction: PF_OUT,
      Sport: toCPort(clientAddr.Port),
      Dport: toCPort(bindAddr.Port),
      }

      copy(pnl.Daddr.AddrRaw[:], bindAddr.IP.To4())
      copy(pnl.Saddr.AddrRaw[:], clientAddr.IP.To4())
      _, _, e1 := syscall.RawSyscall(syscall.SYS_IOCTL, _devPFFD, uintptr(DIOCNATLOOK), uintptr(unsafe.Pointer(&pnl)))
      if e1 != 0 {
      err = e1
      } else {
      addr = &net.TCPAddr{
      IP: toGoIP(pnl.RdAddr.AddrRaw, pnl.Af),
      Port: toGoPort(pnl.Rdport),
      }
      }
      return
      }

      func toGoIP(raw [16]byte, af uint8) net.IP {
      if af == syscall.AF_INET {
      return net.IPv4(raw[0], raw[1], raw[2], raw[3])
      }
      return raw[:]
      }

      func toCPort(p int) uint16 {
      return uint16((p&0x00FF)<<8 | ((p & 0xFF00) >> 8))
      }
  2. linux + iptables

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    const SO_ORIGINAL_DST = 80

    func GetOriginalDst(tcpConn *net.TCPConn) (addr net.TCPAddr, newTCPConn *net.TCPConn, err error) {
    newTCPConn = tcpConn
    // net.TCPConn.File() will cause the receiver's (clientConn) socket to be placed in blocking mode.
    // The workaround is to take the File returned by .File(), do getsockopt() to get the original
    // destination, then create a new *net.TCPConn by calling net.Conn.FileConn(). The new TCPConn
    // will be in non-blocking mode. What a pain.
    connFile, err := tcpConn.File()
    if err != nil {
    return
    } else {
    tcpConn.Close()
    }

    // Get original destination
    // this is the only syscall in the Golang libs that I can find that returns 16 bytes
    // Example result: &{Multiaddr:[2 0 31 144 206 190 36 45 0 0 0 0 0 0 0 0] Interface:0}
    // port starts at the 3rd byte and is 2 bytes long (31 144 = port 8080)
    // IPv4 address starts at the 5th byte, 4 bytes long (206 190 36 45)
    mreq, err := syscall.GetsockoptIPv6Mreq(int(connFile.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
    if err != nil {
    return
    }
    newConn, err := net.FileConn(connFile)
    if err != nil {
    return
    }

    newTCPConn = newConn.(*net.TCPConn)
    connFile.Close()

    addr.IP = mreq.Multiaddr[4:8]
    addr.Port = int(mreq.Multiaddr[2])<<8 + int(mreq.Multiaddr[3])

    return
    }