mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-12 09:18:15 +03:00
https://github.com/XTLS/Xray-core/pull/6771#issuecomment-5752154561 https://github.com/XTLS/Xray-core/pull/6795#issuecomment-6095740152 https://github.com/XTLS/Xray-core/pull/7130#issuecomment-6116258188
97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
//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()
|
|
}
|