- Split cmd/softether-go into main.go (flags, reconnect loop) and session.go (session lifecycle, DHCP orchestration) - Extract network config to pkg/netcfg (TAP config, routing, DNS, policy routes) - Move frame bridging to pkg/client/tunnel.go as Bridge() method - Add -mac, -dhcp, -policy-route-table CLI flags - Add SetMAC() to pkg/tap for deterministic DHCP assignments - Update all docs to reflect new structure and flags Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
160 lines
4.5 KiB
Go
160 lines
4.5 KiB
Go
// Package tap provides Linux TAP (Layer 2) device management.
|
|
//
|
|
// A TAP device is a virtual Ethernet interface that allows userspace programs
|
|
// to read and write raw Ethernet frames. SoftEther VPN operates at Layer 2,
|
|
// so it bridges Ethernet frames between the TAP device and the VPN tunnel.
|
|
//
|
|
// This uses the Linux kernel's TUN/TAP driver via /dev/net/tun with the
|
|
// IFF_TAP | IFF_NO_PI flags (TAP mode, no packet info header).
|
|
//
|
|
// See: https://www.kernel.org/doc/html/latest/networking/tuntap.html
|
|
package tap
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
tunDevice = "/dev/net/tun"
|
|
ifnameSize = 16
|
|
)
|
|
|
|
// Device represents an open TAP network interface.
|
|
type Device struct {
|
|
file *os.File
|
|
Name string // Kernel-assigned interface name (e.g. "tap0")
|
|
}
|
|
|
|
// Open creates a new TAP device with the given name.
|
|
// If name is empty, the kernel assigns one automatically (tap0, tap1, ...).
|
|
// Requires CAP_NET_ADMIN or root privileges.
|
|
func Open(name string) (*Device, error) {
|
|
fd, err := unix.Open(tunDevice, unix.O_RDWR|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open %s: %w", tunDevice, err)
|
|
}
|
|
|
|
var ifr [unix.IFNAMSIZ + 64]byte
|
|
// IFF_TAP: Layer 2 (Ethernet frames), IFF_NO_PI: no 4-byte packet info header
|
|
flags := uint16(unix.IFF_TAP | unix.IFF_NO_PI)
|
|
ifr[ifnameSize] = byte(flags)
|
|
ifr[ifnameSize+1] = byte(flags >> 8)
|
|
|
|
if name != "" {
|
|
copy(ifr[:ifnameSize], name)
|
|
}
|
|
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TUNSETIFF, uintptr(unsafe.Pointer(&ifr[0])))
|
|
if errno != 0 {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("ioctl TUNSETIFF: %w", errno)
|
|
}
|
|
|
|
// Read back the kernel-assigned name
|
|
assignedName := string(ifr[:ifnameSize])
|
|
for i, b := range assignedName {
|
|
if b == 0 {
|
|
assignedName = assignedName[:i]
|
|
break
|
|
}
|
|
}
|
|
|
|
return &Device{
|
|
file: os.NewFile(uintptr(fd), tunDevice),
|
|
Name: assignedName,
|
|
}, nil
|
|
}
|
|
|
|
// Read reads a single Ethernet frame from the TAP device.
|
|
func (d *Device) Read(buf []byte) (int, error) {
|
|
return d.file.Read(buf)
|
|
}
|
|
|
|
// Write writes a single Ethernet frame to the TAP device.
|
|
func (d *Device) Write(buf []byte) (int, error) {
|
|
return d.file.Write(buf)
|
|
}
|
|
|
|
// Close closes the TAP device file descriptor, which also removes the interface.
|
|
func (d *Device) Close() error {
|
|
return d.file.Close()
|
|
}
|
|
|
|
// MAC returns the hardware (MAC) address of the TAP interface.
|
|
func (d *Device) MAC() (net.HardwareAddr, error) {
|
|
sock, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("socket: %w", err)
|
|
}
|
|
defer unix.Close(sock)
|
|
|
|
var ifr [40]byte
|
|
copy(ifr[:ifnameSize], d.Name)
|
|
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sock), unix.SIOCGIFHWADDR, uintptr(unsafe.Pointer(&ifr[0])))
|
|
if errno != 0 {
|
|
return nil, fmt.Errorf("SIOCGIFHWADDR: %w", errno)
|
|
}
|
|
// MAC starts at offset ifnameSize+2 (sa_family is 2 bytes)
|
|
mac := make(net.HardwareAddr, 6)
|
|
copy(mac, ifr[ifnameSize+2:ifnameSize+8])
|
|
return mac, nil
|
|
}
|
|
|
|
// SetMAC sets the hardware (MAC) address of the TAP interface.
|
|
func (d *Device) SetMAC(mac net.HardwareAddr) error {
|
|
sock, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("socket: %w", err)
|
|
}
|
|
defer unix.Close(sock)
|
|
|
|
var ifr [40]byte
|
|
copy(ifr[:ifnameSize], d.Name)
|
|
ifr[ifnameSize] = 1 // sa_family = ARPHRD_ETHER
|
|
ifr[ifnameSize+1] = 0
|
|
copy(ifr[ifnameSize+2:ifnameSize+8], mac)
|
|
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sock), unix.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr[0])))
|
|
if errno != 0 {
|
|
return fmt.Errorf("SIOCSIFHWADDR: %w", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetUp brings the TAP interface up (equivalent to `ip link set <name> up`).
|
|
func (d *Device) SetUp() error {
|
|
sock, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("socket: %w", err)
|
|
}
|
|
defer unix.Close(sock)
|
|
|
|
var ifr [40]byte
|
|
copy(ifr[:ifnameSize], d.Name)
|
|
|
|
// SIOCGIFFLAGS: get current interface flags
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sock), unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifr[0])))
|
|
if errno != 0 {
|
|
return fmt.Errorf("SIOCGIFFLAGS: %w", errno)
|
|
}
|
|
|
|
// Set IFF_UP flag
|
|
flags := uint16(ifr[ifnameSize]) | uint16(ifr[ifnameSize+1])<<8
|
|
flags |= unix.IFF_UP
|
|
ifr[ifnameSize] = byte(flags)
|
|
ifr[ifnameSize+1] = byte(flags >> 8)
|
|
|
|
// SIOCSIFFLAGS: apply updated flags
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(sock), unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifr[0])))
|
|
if errno != 0 {
|
|
return fmt.Errorf("SIOCSIFFLAGS: %w", errno)
|
|
}
|
|
|
|
return nil
|
|
}
|