mirror of
https://github.com/soypat/lneto.git
synced 2026-08-05 23:43:40 +00:00
eab43c4653
* tcp: add test replicating bugs and 32bit panic with GOARCH=386 * tcp: reject SYN on synchronized connections; fix maxSend underflow
315 lines
8.3 KiB
Go
315 lines
8.3 KiB
Go
//go:build linux && !tinygo && amd64
|
|
|
|
package internal
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math/bits"
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
const safamily_hw6 = 1
|
|
|
|
type Tap struct {
|
|
fd int // points to /dev/net/tun device.
|
|
name string
|
|
}
|
|
|
|
func NewTap(name string, ip netip.Prefix) (*Tap, error) {
|
|
if len(name) >= syscall.IFNAMSIZ {
|
|
return nil, errors.New("name too large")
|
|
}
|
|
fd, err := syscall.Open("/dev/net/tun", os.O_RDWR, 0777)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open tun device: %w", err)
|
|
}
|
|
ifr := makeifreq(name)
|
|
// Set the flags (starting at offset IFNAMSIZ).
|
|
flags := uint16(syscall.IFF_TAP | syscall.IFF_NO_PI)
|
|
ifr.setflags(flags)
|
|
// Issue the ioctl to create the interface.
|
|
err = ioctl(fd, syscall.TUNSETIFF, ifr.ptr())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating tap interface: %w", err)
|
|
}
|
|
if ip.IsValid() {
|
|
// Optionally, bring the interface up and assign an IP address.
|
|
// You can do this using the 'ip' command for simplicity.
|
|
err = exec.Command("ip", "link", "set", "dev", name, "up").Run()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to set ip link: %w", err)
|
|
}
|
|
|
|
err = exec.Command("ip", "addr", "add", ip.String(), "dev", name).Run()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to assign IP address: %w", err)
|
|
}
|
|
}
|
|
return &Tap{fd: fd, name: name}, nil
|
|
}
|
|
|
|
func (tap *Tap) IPMask() (netip.Prefix, error) {
|
|
sockfd, err := tap.getSock()
|
|
if err != nil {
|
|
return netip.Prefix{}, err
|
|
}
|
|
return getSocketMask(sockfd, tap.name)
|
|
}
|
|
|
|
func (tap *Tap) Read(b []byte) (int, error) {
|
|
return syscall.Read(tap.fd, b)
|
|
}
|
|
|
|
// Poll waits up to timeout for the tap device to have data available for reading.
|
|
// Returns true if data is available, false if timeout was reached.
|
|
func (tap *Tap) Poll(timeout time.Duration) (bool, error) {
|
|
var readfds syscall.FdSet
|
|
readfds.Bits[tap.fd/64] |= 1 << (uint(tap.fd) % 64)
|
|
tv := syscall.Timeval{
|
|
Sec: int64(timeout / time.Second),
|
|
Usec: int64((timeout % time.Second) / time.Microsecond),
|
|
}
|
|
n, err := syscall.Select(tap.fd+1, &readfds, nil, nil, &tv)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return n > 0, nil
|
|
}
|
|
|
|
func (tap *Tap) Write(b []byte) (int, error) {
|
|
return syscall.Write(tap.fd, b)
|
|
}
|
|
|
|
func (tap *Tap) Close() error {
|
|
return syscall.Close(tap.fd)
|
|
}
|
|
|
|
func ioctl(fd int, request uintptr, argp unsafe.Pointer) error {
|
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, uintptr(argp))
|
|
if errno != 0 {
|
|
return os.NewSyscallError("ioctl", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tap *Tap) MTU() (int, error) {
|
|
sock, err := tap.getSock()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer syscall.Close(sock)
|
|
return getSocketMTU(sock, tap.name)
|
|
}
|
|
|
|
func (tap *Tap) HardwareAddress6() (hw [6]byte, err error) {
|
|
// We cannot use tap.sock to query the hardware address, this is something known by the network stack, so get a sock to network stack.
|
|
sock, err := tap.getSock()
|
|
if err != nil {
|
|
return hw, err
|
|
}
|
|
defer syscall.Close(sock)
|
|
return getSocketHW(sock, tap.name)
|
|
}
|
|
|
|
func (tap *Tap) getSock() (int, error) {
|
|
sock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("tap socket open: %w", err)
|
|
}
|
|
return sock, err
|
|
}
|
|
|
|
func getSocketMTU(sockfd int, ifaceName string) (int, error) {
|
|
ifr := makeifreq(ifaceName)
|
|
err := ioctl(sockfd, syscall.SIOCGIFMTU, ifr.ptr())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
mtu := *(*int32)(unsafe.Pointer(&ifr.Data[0]))
|
|
return int(mtu), nil
|
|
}
|
|
|
|
func getSocketHW(sockfd int, ifaceName string) (hw [6]byte, err error) {
|
|
ifr := makeifreq(ifaceName)
|
|
err = ioctl(sockfd, syscall.SIOCGIFHWADDR, ifr.ptr())
|
|
if err != nil {
|
|
return hw, err
|
|
}
|
|
sa_family := *(*uint16)(unsafe.Pointer(&ifr.Data[0])) // Host order.
|
|
if sa_family != safamily_hw6 {
|
|
return hw, fmt.Errorf("expecting sa_family=1 got %d", sa_family)
|
|
}
|
|
copy(hw[:], ifr.Data[2:]) // first two bytes are sa_family
|
|
return hw, nil
|
|
}
|
|
|
|
func getSocketMask(sockfd int, ifaceName string) (netip.Prefix, error) {
|
|
addrp, err := getSocketIP(sockfd, ifaceName)
|
|
if err != nil {
|
|
return netip.Prefix{}, err
|
|
}
|
|
ifr := makeifreq(ifaceName)
|
|
err = ioctl(sockfd, syscall.SIOCGIFNETMASK, ifr.ptr())
|
|
if err != nil {
|
|
return netip.Prefix{}, err
|
|
}
|
|
addr32 := binary.BigEndian.Uint32(ifr.Data[4:8])
|
|
cidr := bits.OnesCount32(addr32)
|
|
return netip.PrefixFrom(addrp.Addr(), cidr), nil
|
|
}
|
|
|
|
func setSocketHW(sockfd int, ifaceName string, hw [6]byte) error {
|
|
ifr := makeifreq(ifaceName)
|
|
*(*uint16)(unsafe.Pointer(&ifr.Data[0])) = safamily_hw6
|
|
copy(ifr.Data[2:], hw[:])
|
|
err := ioctl(sockfd, syscall.SIOCSIFHWADDR, ifr.ptr())
|
|
if err != nil {
|
|
return fmt.Errorf("setting hw addr: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getSocketIP(sockfd int, ifaceName string) (addrp netip.AddrPort, err error) {
|
|
ifr := makeifreq(ifaceName)
|
|
err = ioctl(sockfd, syscall.SIOCGIFADDR, ifr.ptr())
|
|
if err != nil {
|
|
return netip.AddrPort{}, err
|
|
}
|
|
safamily := *(*uint16)(unsafe.Pointer(&ifr.Data[0]))
|
|
port := *(*uint16)(unsafe.Pointer(&ifr.Data[2]))
|
|
switch safamily {
|
|
case 2:
|
|
addr, _ := netip.AddrFromSlice(ifr.Data[4:8])
|
|
addrp = netip.AddrPortFrom(addr, port)
|
|
default:
|
|
return addrp, fmt.Errorf("unsupported IP addr sa_family=%d", safamily)
|
|
}
|
|
return addrp, nil
|
|
}
|
|
|
|
func makeifreq(name string) ifreq {
|
|
// Set the name; it will be zero-padded automatically.
|
|
var ifr ifreq
|
|
copy(ifr.Name[:], name)
|
|
return ifr
|
|
}
|
|
|
|
type ifreq struct {
|
|
Name [syscall.IFNAMSIZ]byte
|
|
Data [64]byte // union data (covers ifr_hwaddr, etc.)
|
|
}
|
|
|
|
func (ifr *ifreq) setflags(flags uint16) {
|
|
*(*uint16)(unsafe.Pointer(&ifr.Data[0])) = flags
|
|
}
|
|
|
|
func (ifr *ifreq) ptr() unsafe.Pointer { return unsafe.Pointer(ifr) }
|
|
|
|
// Bridge serves as a virtual socket that "bridges" to an existing interface (could be TAP or an actual NIC that connects to internet).
|
|
// Bridge is used in this project to
|
|
type Bridge struct {
|
|
fd int
|
|
name string
|
|
index int
|
|
}
|
|
|
|
func NewBridge(name string) (*Bridge, error) {
|
|
iface, err := interfaceByName(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
proto := htons(syscall.ETH_P_ALL)
|
|
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(proto))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ll := syscall.SockaddrLinklayer{
|
|
Protocol: proto,
|
|
Ifindex: iface.Index,
|
|
}
|
|
if err := syscall.Bind(fd, &ll); err != nil {
|
|
return nil, err
|
|
}
|
|
return &Bridge{fd: fd, name: iface.Name, index: iface.Index}, nil
|
|
}
|
|
|
|
func (br *Bridge) Write(frame []byte) (int, error) {
|
|
return syscall.Write(br.fd, frame)
|
|
}
|
|
|
|
func (br *Bridge) Read(frame []byte) (int, error) {
|
|
return syscall.Read(br.fd, frame)
|
|
}
|
|
|
|
func (br *Bridge) Close() error {
|
|
return syscall.Close(br.fd)
|
|
}
|
|
|
|
func (br *Bridge) HardwareAddress6() (hw [6]byte, err error) {
|
|
return getSocketHW(br.fd, br.name)
|
|
}
|
|
|
|
func (br *Bridge) SetHardwareAddress6(hw [6]byte) error {
|
|
return setSocketHW(br.fd, br.name, hw)
|
|
}
|
|
|
|
func (br *Bridge) IPMask() (netip.Prefix, error) {
|
|
return getSocketMask(br.fd, br.name)
|
|
}
|
|
|
|
// SetReadTimeout sets the receive timeout for the bridge socket.
|
|
// This prevents Read from blocking indefinitely, allowing the caller
|
|
// to periodically call Encapsulate even when no packets arrive.
|
|
func (br *Bridge) SetReadTimeout(timeout time.Duration) error {
|
|
tv := syscall.Timeval{
|
|
Sec: int64(timeout / time.Second),
|
|
Usec: int64((timeout % time.Second) / time.Microsecond),
|
|
}
|
|
return syscall.SetsockoptTimeval(br.fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv)
|
|
}
|
|
|
|
// Poll waits up to timeout for the bridge socket to have data available for reading.
|
|
// Returns true if data is available, false if timeout was reached.
|
|
func (br *Bridge) Poll(timeout time.Duration) (bool, error) {
|
|
var readfds syscall.FdSet
|
|
readfds.Bits[br.fd/64] |= 1 << (uint(br.fd) % 64)
|
|
tv := syscall.Timeval{
|
|
Sec: int64(timeout / time.Second),
|
|
Usec: int64((timeout % time.Second) / time.Microsecond),
|
|
}
|
|
retry := 0
|
|
again:
|
|
n, err := syscall.Select(br.fd+1, &readfds, nil, nil, &tv)
|
|
retry++
|
|
if err != nil {
|
|
if err == syscall.EINTR && retry <= 10 {
|
|
// under linux, select updates tv with the remaining time
|
|
goto again
|
|
}
|
|
return false, err
|
|
}
|
|
return n > 0, nil
|
|
}
|
|
|
|
func (br *Bridge) Addr() (netip.Addr, error) {
|
|
addrp, err := getSocketIP(br.fd, br.name)
|
|
if err != nil {
|
|
return netip.Addr{}, err
|
|
}
|
|
return addrp.Addr(), nil
|
|
}
|
|
|
|
func (br *Bridge) MTU() (int, error) {
|
|
return getSocketMTU(br.fd, br.name)
|
|
}
|
|
|
|
// htons converts a uint16 from host to network byte order.
|
|
func htons(i uint16) uint16 { return (i<<8)&0xff00 | i>>8 }
|