go-tproxy icon indicating copy to clipboard operation
go-tproxy copied to clipboard

set `IP_PKTINFO`, one socket in UDP.

Open zzxun opened this issue 6 years ago • 1 comments

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)
...

zzxun avatar Oct 26 '18 13:10 zzxun

IP_PKTINFO can't get port. IP_ORIGDSTADDR can.

FH0 avatar Dec 25 '20 02:12 FH0