'How to read TCP packets on a tun/tap interface?

I'm working on a simple project that listens on a tun interface and modified the packets then re-sends them to the real interface.

I have tried songgao/water, pkg/tuntap and even writing my own based on some C code floating around but no matter what I tried, I can't receive TCP packets (ICMP/UDP works fine).

I feel like i'm missing something extremely obvious but I can't figure it for the life of me...

The code:

package main

import (
    "log"
    "os"
    "os/exec"

    "golang.org/x/net/ipv4"

    "github.com/songgao/water"
)

const (
    // I use TUN interface, so only plain IP packet, no ethernet header + mtu is set to 1300
    BUFFERSIZE = 1600
    MTU        = "1300"
)

func main() {
    iface, err := water.New(water.Config{})
    fatalIf(err)

    log.Printf("tun interface: %s", iface.Name())
    runBin("/bin/ip", "link", "set", "dev", iface.Name(), "mtu", MTU)
    runBin("/bin/ip", "addr", "add", "10.2.0.10/24", "dev", iface.Name())
    runBin("/bin/ip", "link", "set", "dev", iface.Name(), "up")

    buf := make([]byte, BUFFERSIZE)

    for {
        n, err := iface.Read(buf)
        if err != nil {
            log.Fatal(err)
        }

        header, _ := ipv4.ParseHeader(buf[:n])

        log.Printf("isTCP: %v, header: %s", header.Protocol == 6, header)
    }
}

func fatalIf(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func runBin(bin string, args ...string) {
    cmd := exec.Command(bin, args...)
    cmd.Stderr = os.Stderr
    cmd.Stdout = os.Stdout
    cmd.Stdin = os.Stdin
    fatalIf(cmd.Run())
}


Solution 1:[1]

Your code works for me. If I run it, then I can see TCP packets after I initiate some TCP-related activity. For this instance, I used "ssh 10.2.0.11". Here is what I got:

2020/03/07 15:52:23 isTCP: false, header: ver=6 hdrlen=0 tos=0x0 totallen=0 id=0x8 flags=0x1 fragoff=0x1aff ttl=254 proto=128 cksum=0x0 src=0.0.0.0 dst=126.87.58.227
2020/03/07 15:52:27 isTCP: false, header: ver=6 hdrlen=0 tos=0x0 totallen=0 id=0x8 flags=0x1 fragoff=0x1aff ttl=254 proto=128 cksum=0x0 src=0.0.0.0 dst=126.87.58.227
2020/03/07 15:52:34 isTCP: false, header: ver=6 hdrlen=0 tos=0x0 totallen=0 id=0x8 flags=0x1 fragoff=0x1aff ttl=254 proto=128 cksum=0x0 src=0.0.0.0 dst=126.87.58.227
2020/03/07 15:52:36 isTCP: true, header: ver=4 hdrlen=20 tos=0x0 totallen=60 id=0x9cec flags=0x2 fragoff=0x0 ttl=64 proto=6 cksum=0x89b7 src=10.2.0.10 dst=10.2.0.11
2020/03/07 15:52:37 isTCP: true, header: ver=4 hdrlen=20 tos=0x0 totallen=60 id=0x9ced flags=0x2 fragoff=0x0 ttl=64 proto=6 cksum=0x89b6 src=10.2.0.10 dst=10.2.0.11
2020/03/07 15:52:39 isTCP: true, header: ver=4 hdrlen=20 tos=0x0 totallen=60 id=0x9cee flags=0x2 fragoff=0x0 ttl=64 proto=6 cksum=0x89b5 src=10.2.0.10 dst=10.2.0.11
2020/03/07 15:52:43 isTCP: true, header: ver=4 hdrlen=20 tos=0x0 totallen=60 id=0x9cef flags=0x2 fragoff=0x0 ttl=64 proto=6 cksum=0x89b4 src=10.2.0.10 dst=10.2.0.11

As you can see, a TCP client at 10.2.0.10 is trying to establish (TCP-SYN) a connection to 10.2.0.11.

Solution 2:[2]

I had the same problem. Sending to a different IP on the same network made it through. Sending to the address bound to the tunnel (ip addr add ...) did not. I'm guessing kernel TCP stack takes priority for the bound address?

Adding a route to the routing table, instead of binding an address to the interface, solved it for me:

ip link set dev tun0 up
ip route add 10.2.0.10/32 dev tun0

NOTE: the link has to be up before the route can be applied.

A point-to-point configuration instead of a route will also do the trick:

ip link set dev tun0 up
ip addr add 10.2.0.0 peer 10.2.0.10 dev tun0

You will send packets to the peer address.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 kmeaw
Solution 2