//go:build linux package tun import ( "encoding/binary" "os" "sync" "golang.org/x/sys/unix" ) // vnetSize is the size of the virtio_net_hdr that a tun with IFF_VNET_HDR has in front of every packet, // unless it was told another one const vnetSize = 10 // fdTun reads and writes the packets of a tun that is a file descriptor type fdTun struct { fd int // file is what packets are read from: a read waits in the poller of the runtime and ends when file is closed file *os.File // vnet is the size of the virtio_net_hdr in front of every packet, zero if the tun has none. // With it the system does not split what it sends to the tun and takes what is not split from it vnet int writeLock sync.RWMutex closed bool } // newFdTun takes a file descriptor that does not block func newFdTun(fd, vnet int) *fdTun { return &fdTun{fd: fd, file: os.NewFile(uintptr(fd), "/dev/net/tun"), vnet: vnet} } func (t *fdTun) ReadPacket(b []byte) ([]byte, error) { n, err := t.file.Read(b) // what the virtio_net_hdr says is of no use here: no checksum is looked at, and a segment may have any size return b[min(t.vnet, n):n], err } func (t *fdTun) WritePacket(header, payload []byte) error { // a virtio_net_hdr of zeros leaves nothing to the system var vnet [packetRoom]byte return t.writev(vnet[:t.vnet], header, payload) } // Segments implements segmenter func (t *fdTun) Segments() bool { return t.vnet != 0 } // WriteSegment implements segmenter func (t *fdTun) WriteSegment(header []byte, payload [][]byte, mss int) error { // the virtio_net_hdr and the headers go as one part var b [packetRoom + ipv6HeaderSize + 60]byte ip, gso := ipv4HeaderSize, byte(unix.VIRTIO_NET_HDR_GSO_TCPV4) if header[0]>>4 == 6 { ip, gso = ipv6HeaderSize, unix.VIRTIO_NET_HDR_GSO_TCPV6 } size := 0 for _, p := range payload { size += len(p) } b[0] = unix.VIRTIO_NET_HDR_F_NEEDS_CSUM if size > mss { b[1] = gso binary.NativeEndian.PutUint16(b[4:], uint16(mss)) } binary.NativeEndian.PutUint16(b[2:], uint16(len(header))) // where the checksum starts, and where it is put from there binary.NativeEndian.PutUint16(b[6:], uint16(ip)) binary.NativeEndian.PutUint16(b[8:], 16) var parts [8][]byte return t.writev(append(append(parts[:0], b[:t.vnet+copy(b[t.vnet:], header)]), payload...)...) } func (t *fdTun) writev(parts ...[]byte) error { t.writeLock.RLock() defer t.writeLock.RUnlock() if t.closed { return os.ErrClosed } for { _, err := unix.Writev(t.fd, parts) if err != unix.EINTR { return err } } } func (t *fdTun) closeFile() error { t.writeLock.Lock() defer t.writeLock.Unlock() t.closed = true return t.file.Close() }