mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-04-04 12:27:39 +03:00
672 lines
16 KiB
Go
672 lines
16 KiB
Go
package tun
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/buf"
|
|
"github.com/sagernet/sing/common/bufio"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
N "github.com/sagernet/sing/common/network"
|
|
"golang.org/x/net/route"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const IFHEADOffset = 4
|
|
const PacketOffset = IFHEADOffset
|
|
|
|
const (
|
|
_TUNSIFHEAD = 0x80047460
|
|
|
|
_TUNSIFMODE = 0x8004745e
|
|
_TUNGIFNAME = 0x4020745d
|
|
_TUNSIFPID = 0x2000745f
|
|
|
|
_SIOCGIFINFO_IN6 = 0xc048696c
|
|
_SIOCSIFINFO_IN6 = 0xc048696d
|
|
|
|
_ND6_IFF_AUTO_LINKLOCAL = 0x20
|
|
_ND6_IFF_NO_DAD = 0x100
|
|
|
|
// NOTE: SIOCSxxx deprecated
|
|
_SIOCAIFADDR_IN6 = 0x8088691b // netinet6/in6_var.h
|
|
_IN6_IFF_NODAD = 0x20 // netinet6/in6_var.h
|
|
_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
|
|
)
|
|
|
|
var _ Tun = (*NativeTun)(nil)
|
|
|
|
type NativeTun struct {
|
|
name string
|
|
tunFile *os.File
|
|
|
|
fd int
|
|
mtu uint32
|
|
unix.RawSockaddrInet6
|
|
tunWriter N.VectorisedWriter
|
|
|
|
inet4Address [4]byte
|
|
inet6Address [16]byte
|
|
|
|
routeCleanFns []func() error
|
|
}
|
|
|
|
func New(options Options) (Tun, error) {
|
|
if len(options.Name) > unix.IFNAMSIZ-1 {
|
|
return nil, E.New("tun name too long: ", options.Name)
|
|
}
|
|
|
|
// See if interface already exists
|
|
if iface, _ := net.InterfaceByName(options.Name); iface != nil {
|
|
if err := destoryIf(options.Name); err != nil {
|
|
return nil, E.New("unable able to destory already existed interface: ", options.Name)
|
|
}
|
|
}
|
|
|
|
tunFile, err := os.OpenFile("/dev/tun", unix.O_RDWR|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, E.New("unable able to open /dev/tun: ", err)
|
|
}
|
|
|
|
tun := &NativeTun{
|
|
name: options.Name,
|
|
tunFile: tunFile,
|
|
fd: int(tunFile.Fd()),
|
|
mtu: options.MTU,
|
|
routeCleanFns: make([]func() error, 0),
|
|
}
|
|
|
|
var assignedName string
|
|
if assignedName, err = fdevName(tun.tunFile); err != nil {
|
|
tunFile.Close()
|
|
destoryIf(assignedName)
|
|
return nil, err
|
|
}
|
|
|
|
if err := enableIfHeadMode(tun.tunFile); err != nil {
|
|
tun.tunFile.Close()
|
|
destoryIf(assignedName)
|
|
return nil, err
|
|
}
|
|
|
|
if err := setIfMode(tun.tunFile, syscall.IFF_POINTOPOINT|syscall.IFF_MULTICAST); err != nil {
|
|
tun.tunFile.Close()
|
|
destoryIf(assignedName)
|
|
return nil, err
|
|
}
|
|
|
|
if err := disableLinkLocalV6(assignedName); err != nil {
|
|
tun.tunFile.Close()
|
|
destoryIf(assignedName)
|
|
return nil, err
|
|
}
|
|
|
|
if len(options.Name) > 0 {
|
|
if err := setIfName(assignedName, options.Name); err != nil {
|
|
tun.tunFile.Close()
|
|
destoryIf(assignedName)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := becomeCtrlProc(tun.tunFile); err != nil {
|
|
tun.tunFile.Close()
|
|
destoryIf(tun.name)
|
|
return nil, err
|
|
}
|
|
|
|
err = unix.SetNonblock(tun.fd, true)
|
|
if err != nil {
|
|
tun.tunFile.Close()
|
|
destoryIf(tun.name)
|
|
return nil, err
|
|
}
|
|
|
|
// update if name here?
|
|
if err := setMTU(tun.name, options.MTU); err != nil {
|
|
tun.tunFile.Close()
|
|
destoryIf(tun.name)
|
|
return nil, err
|
|
}
|
|
|
|
if err := setIpV4(tun.name, options.Inet4Address); err != nil {
|
|
tun.tunFile.Close()
|
|
return nil, err
|
|
}
|
|
if len(options.Inet4Address) > 0 {
|
|
tun.inet4Address = options.Inet4Address[0].Addr().As4()
|
|
}
|
|
|
|
if err := setIpV6(tun.name, options.Inet6Address); err != nil {
|
|
tun.tunFile.Close()
|
|
return nil, err
|
|
}
|
|
if len(options.Inet6Address) > 0 {
|
|
tun.inet6Address = options.Inet6Address[0].Addr().As16()
|
|
}
|
|
|
|
// can work?
|
|
var ok bool
|
|
tun.tunWriter, ok = bufio.CreateVectorisedWriter(tun.tunFile)
|
|
if !ok {
|
|
panic("create vectorised writer")
|
|
}
|
|
|
|
// same as darwin
|
|
if options.AutoRoute {
|
|
var routeRanges []netip.Prefix
|
|
routeRanges, _ = options.BuildAutoRouteRanges(false)
|
|
for _, routeRange := range routeRanges {
|
|
var fn func() error
|
|
if routeRange.Addr().Is4() {
|
|
fn, err = addRoute(routeRange, options.Inet4Address[0].Addr())
|
|
} else {
|
|
fn, err = addRoute(routeRange, options.Inet6Address[0].Addr())
|
|
}
|
|
if err != nil {
|
|
return nil, E.Cause(err, "add route: ", routeRange)
|
|
}
|
|
tun.routeCleanFns = append(tun.routeCleanFns, fn)
|
|
}
|
|
}
|
|
|
|
if err := ifUp(tun.name); err != nil {
|
|
tun.tunFile.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return tun, nil
|
|
}
|
|
|
|
// Read implements Tun.
|
|
func (t *NativeTun) Read(p []byte) (n int, err error) {
|
|
return t.tunFile.Read(p)
|
|
}
|
|
|
|
// Write implements Tun.
|
|
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
|
return t.tunFile.Write(p)
|
|
}
|
|
|
|
// Close implements Tun.
|
|
func (t *NativeTun) Close() error {
|
|
|
|
for _, fn := range t.routeCleanFns {
|
|
if err := fn(); err != nil {
|
|
// TODO: deal with undeleted route?
|
|
continue
|
|
}
|
|
}
|
|
|
|
if err := t.tunFile.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := destoryIf(t.name); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
packetHeader4 = [IFHEADOffset]byte{0x00, 0x00, 0x00, unix.AF_INET}
|
|
packetHeader6 = [IFHEADOffset]byte{0x00, 0x00, 0x00, unix.AF_INET6}
|
|
)
|
|
|
|
// WriteVectorised implements Tun. work?
|
|
// buffers is full ip pkg without IFHEAD setted, before write add the 4 bytes header.
|
|
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
|
|
var packetHeader []byte
|
|
if buffers[0].Byte(0)>>4 == 4 {
|
|
packetHeader = packetHeader4[:]
|
|
} else {
|
|
packetHeader = packetHeader6[:]
|
|
}
|
|
return t.tunWriter.WriteVectorised(append([]*buf.Buffer{buf.As(packetHeader)}, buffers...))
|
|
}
|
|
|
|
func operateOnFd(theFile *os.File, fn func(fd uintptr)) error {
|
|
sysconn, err := theFile.SyscallConn()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to find sysconn for tunfile: %s", err.Error())
|
|
}
|
|
err = sysconn.Control(fn)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to control sysconn for tunfile: %s", err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// useSocket from tun_darwin
|
|
func useSocket(domain, typ, proto int, block func(socketFd int) error) error {
|
|
socketFd, err := unix.Socket(domain, typ, proto)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(socketFd)
|
|
return block(socketFd)
|
|
}
|
|
|
|
func fdevName(theFile *os.File) (string, error) {
|
|
ifreq := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
_ [16]byte
|
|
}{}
|
|
|
|
var errno syscall.Errno
|
|
operateOnFd(theFile, func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNGIFNAME, uintptr(unsafe.Pointer(&ifreq)))
|
|
})
|
|
|
|
if errno != 0 {
|
|
return "", os.NewSyscallError("TUNGIFNAME", errno)
|
|
}
|
|
return unix.ByteSliceToString(ifreq.Name[:]), nil
|
|
}
|
|
|
|
// enableIfHeadMode https://man.freebsd.org/cgi/man.cgi?query=tun&sektion=4
|
|
func enableIfHeadMode(theFile *os.File) error {
|
|
ifheadmode := 1
|
|
|
|
var errno syscall.Errno
|
|
operateOnFd(theFile, func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFHEAD, uintptr(unsafe.Pointer(&ifheadmode)))
|
|
})
|
|
|
|
if errno != 0 {
|
|
return os.NewSyscallError("TUNSIFHEAD", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// setIfMode TUNSIFMODE
|
|
func setIfMode(theFile *os.File, mode int) error {
|
|
ifflags := mode
|
|
var errno syscall.Errno
|
|
operateOnFd(theFile, func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, uintptr(_TUNSIFMODE), uintptr(unsafe.Pointer(&ifflags)))
|
|
})
|
|
|
|
if errno != 0 {
|
|
return os.NewSyscallError("TUNSIFMODE", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func disableLinkLocalV6(name string) error {
|
|
// Disable link-local v6, not just because WireGuard doesn't do that anyway, but
|
|
// also because there are serious races with attaching and detaching LLv6 addresses
|
|
// in relation to interface lifetime within the FreeBSD kernel.
|
|
|
|
// ND6 flag manipulation
|
|
ndireq := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Linkmtu uint32
|
|
Maxmtu uint32
|
|
Basereachable uint32
|
|
Reachable uint32
|
|
Retrans uint32
|
|
Flags uint32
|
|
Recalctm int
|
|
Chlim uint8
|
|
Initialized uint8
|
|
Randomseed0 [8]byte
|
|
Randomseed1 [8]byte
|
|
Randomid [8]byte
|
|
}{}
|
|
copy(ndireq.Name[:], name)
|
|
|
|
return useSocket(unix.AF_INET6, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
var errno syscall.Errno
|
|
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(_SIOCGIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCGIFINFO_IN6", errno)
|
|
}
|
|
|
|
ndireq.Flags = ndireq.Flags &^ _ND6_IFF_AUTO_LINKLOCAL
|
|
ndireq.Flags = ndireq.Flags | _ND6_IFF_NO_DAD
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(_SIOCSIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCSIFINFO_IN6", errno)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
func setIfName(targetIfName, name string) error {
|
|
var newnp [unix.IFNAMSIZ]byte
|
|
copy(newnp[:], name)
|
|
|
|
// Iface requests with a pointer
|
|
ifr := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Data uintptr
|
|
_ [16 - unsafe.Sizeof(uintptr(0))]byte
|
|
}{}
|
|
copy(ifr.Name[:], targetIfName)
|
|
ifr.Data = uintptr(unsafe.Pointer(&newnp[0]))
|
|
|
|
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCSIFNAME), uintptr(unsafe.Pointer(&ifr)))
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCSIFNAME", errno)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
func becomeCtrlProc(theFile *os.File) error {
|
|
var errno syscall.Errno
|
|
operateOnFd(theFile, func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFPID, uintptr(0))
|
|
})
|
|
if errno != 0 {
|
|
return os.NewSyscallError("TUNSIFPID", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setMTU(ifName string, n uint32) error {
|
|
|
|
ifr := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
MTU uint32
|
|
_ [12]byte
|
|
}{}
|
|
copy(ifr.Name[:], ifName)
|
|
ifr.MTU = uint32(n)
|
|
|
|
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCSIFMTU", errno)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
// getMTU get mtu of interface
|
|
func getMTU(ifName string) (int, error) {
|
|
|
|
ifr := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
MTU uint32
|
|
_ [12]byte
|
|
}{}
|
|
copy(ifr.Name[:], ifName)
|
|
|
|
if err := useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCGIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCGIFMTU", errno)
|
|
}
|
|
return nil
|
|
}); err == nil {
|
|
return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil
|
|
} else {
|
|
return 0, err
|
|
}
|
|
|
|
}
|
|
|
|
// setIpV4 set v4 ip for specific interface, but the
|
|
// ip will be removed if the tun dev was cloed
|
|
func setIpV4(ifName string, addresses []netip.Prefix) error {
|
|
|
|
if len(addresses) <= 0 {
|
|
return nil
|
|
}
|
|
|
|
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
|
|
for _, address := range addresses {
|
|
ifr := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Addr unix.RawSockaddrInet4
|
|
BroadAddr unix.RawSockaddrInet4
|
|
Mask unix.RawSockaddrInet4
|
|
}{
|
|
Addr: unix.RawSockaddrInet4{
|
|
Family: unix.AF_INET,
|
|
Len: unix.SizeofSockaddrInet4,
|
|
Addr: address.Addr().As4(),
|
|
},
|
|
BroadAddr: unix.RawSockaddrInet4{
|
|
Family: unix.AF_INET,
|
|
Len: unix.SizeofSockaddrInet4,
|
|
Addr: broadAddr(address),
|
|
},
|
|
Mask: unix.RawSockaddrInet4{
|
|
Family: unix.AF_INET,
|
|
Len: unix.SizeofSockaddrInet4,
|
|
Addr: mustParseSubnetMask4(address),
|
|
},
|
|
}
|
|
copy(ifr.Name[:], ifName)
|
|
|
|
_, _, errno := unix.Syscall(
|
|
unix.SYS_IOCTL,
|
|
uintptr(socketFd),
|
|
uintptr(unix.SIOCAIFADDR),
|
|
uintptr(unsafe.Pointer(&ifr)),
|
|
)
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCAIFADDR", errno)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
func setIpV6(ifName string, addresses []netip.Prefix) error {
|
|
if len(addresses) <= 0 {
|
|
return nil
|
|
}
|
|
|
|
return useSocket(unix.AF_INET6, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
|
|
for _, address := range addresses {
|
|
|
|
// netinet6/in6_var.h: struct in6_addrlifetime
|
|
type addrLifetime6 struct {
|
|
Expire float64
|
|
Preferred float64
|
|
Vltime uint32
|
|
Pltime uint32
|
|
}
|
|
|
|
// netinet6/in6_var.h: struct in6_aliasreq
|
|
in6_ifreq := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Addr unix.RawSockaddrInet6
|
|
Mask unix.RawSockaddrInet6
|
|
// Dstaddr contain the destination address of the point-to-point interface
|
|
Dstaddr unix.RawSockaddrInet6
|
|
Flags uint32
|
|
Lifetime addrLifetime6
|
|
// Vhid uint32
|
|
}{
|
|
Addr: unix.RawSockaddrInet6{
|
|
Len: unix.SizeofSockaddrInet6,
|
|
Family: unix.AF_INET6,
|
|
Addr: address.Addr().As16(),
|
|
},
|
|
Mask: unix.RawSockaddrInet6{
|
|
Len: unix.SizeofSockaddrInet6,
|
|
Family: unix.AF_INET6,
|
|
Addr: mustParseSubnetMask6(address),
|
|
},
|
|
Flags: _IN6_IFF_NODAD,
|
|
Lifetime: addrLifetime6{
|
|
Vltime: _ND6_INFINITE_LIFETIME,
|
|
Pltime: _ND6_INFINITE_LIFETIME,
|
|
}}
|
|
copy(in6_ifreq.Name[:], []byte(ifName))
|
|
|
|
if address.Bits() == 128 {
|
|
in6_ifreq.Dstaddr = unix.RawSockaddrInet6{
|
|
Len: unix.SizeofSockaddrInet6,
|
|
Family: unix.AF_INET6,
|
|
Addr: address.Addr().Next().As16(),
|
|
}
|
|
}
|
|
|
|
_, _, errno := unix.Syscall(
|
|
unix.SYS_IOCTL,
|
|
uintptr(socketFd),
|
|
uintptr(_SIOCAIFADDR_IN6),
|
|
uintptr(unsafe.Pointer(&in6_ifreq)),
|
|
)
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCAIFADDR_IN6", errno)
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
func ifUp(ifName string) error {
|
|
|
|
ifrFlags := struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Flags uint16
|
|
}{
|
|
Flags: unix.IFF_UP | unix.IFF_RUNNING,
|
|
}
|
|
copy(ifrFlags.Name[:], ifName)
|
|
|
|
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
_, _, errno := unix.Syscall(
|
|
unix.SYS_IOCTL,
|
|
uintptr(socketFd),
|
|
uintptr(unix.SIOCSIFFLAGS),
|
|
uintptr(unsafe.Pointer(&ifrFlags)),
|
|
)
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCSIFFLAGS", errno)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
func destoryIf(name string) error {
|
|
|
|
var ifr [32]byte
|
|
copy(ifr[:], name)
|
|
|
|
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCIFDESTROY), uintptr(unsafe.Pointer(&ifr[0])))
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SIOCIFDESTROY", errno)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
func a4ToUint32(a4 [4]byte) uint32 {
|
|
|
|
buffer := make([]byte, 4)
|
|
for i, v := range a4 {
|
|
buffer[i] = v
|
|
}
|
|
return binary.BigEndian.Uint32(buffer)
|
|
}
|
|
|
|
func uint32ToA4(val uint32) (a4 [4]byte) {
|
|
buffer := new(bytes.Buffer)
|
|
binary.Write(buffer, binary.BigEndian, val)
|
|
var out [4]byte
|
|
for i, v := range buffer.Bytes() {
|
|
out[i] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
func mustParseSubnetMask4(address netip.Prefix) [4]byte {
|
|
return netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), address.Addr().BitLen())).String()).As4()
|
|
}
|
|
|
|
func mustParseSubnetMask6(address netip.Prefix) [16]byte {
|
|
return netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), address.Addr().BitLen())).String()).As16()
|
|
}
|
|
|
|
func networkAddr(address netip.Prefix) [4]byte {
|
|
networkAddrUint32 := a4ToUint32(address.Addr().As4()) & a4ToUint32(mustParseSubnetMask4(address))
|
|
return uint32ToA4(networkAddrUint32)
|
|
}
|
|
|
|
func broadAddr(address netip.Prefix) [4]byte {
|
|
broadAddrUint32 := a4ToUint32(networkAddr(address)) | (^a4ToUint32(mustParseSubnetMask4(address)))
|
|
return uint32ToA4(broadAddrUint32)
|
|
}
|
|
|
|
func addRoute(destination netip.Prefix, gateway netip.Addr) (func() error, error) {
|
|
routeMessage := &route.RouteMessage{
|
|
Type: unix.RTM_ADD,
|
|
Flags: unix.RTF_UP | unix.RTF_STATIC | unix.RTF_GATEWAY,
|
|
Version: unix.RTM_VERSION,
|
|
ID: uintptr(os.Getpid()),
|
|
Seq: 1,
|
|
}
|
|
if gateway.Is4() {
|
|
routeMessage.Addrs = []route.Addr{
|
|
unix.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()},
|
|
unix.RTAX_NETMASK: &route.Inet4Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 32)).String()).As4()},
|
|
unix.RTAX_GATEWAY: &route.Inet4Addr{IP: gateway.As4()},
|
|
}
|
|
} else {
|
|
routeMessage.Addrs = []route.Addr{
|
|
unix.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()},
|
|
unix.RTAX_NETMASK: &route.Inet6Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 128)).String()).As16()},
|
|
unix.RTAX_GATEWAY: &route.Inet6Addr{IP: gateway.As16()},
|
|
}
|
|
}
|
|
request, err := routeMessage.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := useSocket(unix.AF_ROUTE, unix.SOCK_RAW, 0, func(socketFd int) error {
|
|
return common.Error(unix.Write(socketFd, request))
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// for cleanup
|
|
return func() error {
|
|
routeMessage.Type = unix.RTM_DELETE
|
|
desc := fmt.Sprintf("to %s via %s", destination.String(), gateway.String())
|
|
|
|
request, err := routeMessage.Marshal()
|
|
if err != nil {
|
|
return E.New("route message marshal error: ", err)
|
|
}
|
|
err = useSocket(unix.AF_ROUTE, unix.SOCK_RAW, 0, func(socketFd int) error {
|
|
return common.Error(unix.Write(socketFd, request))
|
|
})
|
|
if err != nil {
|
|
return E.New("unable to delete route ", desc, ": ", err)
|
|
}
|
|
return nil
|
|
}, nil
|
|
}
|