KatelynHaworth / go-tproxy

Linux Transparent Proxy library for Golang
MIT License
305 stars 59 forks source link

set `IP_PKTINFO`, one socket in UDP. #3

Open zzxun opened 5 years ago

zzxun commented 5 years ago

When set IP_PKTINFO, dst ip will in oob as struct in_pktinfo. By using this, it would only need one listening socket.

copy oob from ReadMsgUDP to WriteMsgUDP in golang

func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error)
func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error)

as in C

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

IP_PKTINFO (since Linux 2.2) Pass an IP_PKTINFO ancillary message that contains a pktinfo structure that supplies some information about the incoming packet. This only works for datagram oriented sockets. The argument is a flag that tells the socket whether the IP_PKTINFO message should be passed or not. The message itself can only be sent/retrieved as control message with a packet using recvmsg(2) or sendmsg(2).

struct in_pktinfo {
unsigned int   ipi_ifindex;  /* Interface index */
struct in_addr ipi_spec_dst; /* Local address */
struct in_addr ipi_addr;     /* Header Destination address */
};

ipi_ifindex is the unique index of the interface the packet was received on. ipi_spec_dst is the local address of the packet and ipi_addr is the destination address in the packet header. If IP_PKTINFO is passed to sendmsg(2) and ipi_spec_dst is not zero, then it is used as the local source address for the routing table lookup and for setting up IP source route options. When ipi_ifindex is not zero, the primary local address of the interface specified by the index overwrites ipi_spec_dst for the routing table lookup.

In golang, in_pktinfo is syscall.Inet4Pktinfo

type Inet4Pktinfo struct {
    Ifindex  uint32
    Spec_dst [4]byte /* in_addr */
    Addr     [4]byte /* in_addr */
}

Modify demo:

// tproxy_udp.go
func ListenUDP(network string, laddr *net.UDPAddr) (*net.UDPConn, error) {
...
    if err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
        syscall.Close(fileDescriptor)
        return nil, &net.OpError{Op: "listen", Err: fmt.Errorf("set socket option: SO_REUSEADDR: %s", err)}
    }

    if err = syscall.SetsockoptInt(fileDescriptor, syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil {
        syscall.Close(fileDescriptor)
        return nil, &net.OpError{Op: "listen", Err: fmt.Errorf("set socket option: IP_PKTINFO: %s", err)}
    }
...
}

func ReadFromUDP(conn *net.UDPConn, b []byte) (int, []byte, *net.UDPAddr, *net.UDPAddr, error) {
...
    for _, msg := range msgs {
        if msg.Header.Level == syscall.IPPROTO_IP && msg.Header.Type == syscall.IP_PKTINFO {
            originalDstRaw := &syscall.Inet4Pktinfo{}
            if err = binary.Read(bytes.NewReader(msg.Data), binary.LittleEndian, originalDstRaw); err != nil {
                return 0, nil, nil, nil, fmt.Errorf("reading original destination address: %s", err)
            }

            // switch originalDstRaw.Family {
            // case syscall.AF_INET:
            pp := (*syscall.Inet4Pktinfo)(unsafe.Pointer(originalDstRaw))
            // p := (*[2]byte)(unsafe.Pointer(&pp.Port))
            originalDst = &net.UDPAddr{
                IP:   net.IPv4(pp.Addr[0], pp.Addr[1], pp.Addr[2], pp.Addr[3]),
                Port: 53,
            }
...
// example/tproxy_example.go
func handleUDPConn(data []byte, oob []byte, srcAddr, dstAddr *net.UDPAddr) {
...
  // oob copy from ReadMsgUDP
  bytesWritten, _, err = udpListener.WriteMsgUDP(data, oob, srcAddr)
...
FH0 commented 3 years ago

IP_PKTINFO can't get port. IP_ORIGDSTADDR can.