mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-02 17:58:46 +00:00
661 lines
17 KiB
Go
661 lines
17 KiB
Go
//go:build darwin
|
|
|
|
package tun
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/xtls/xray-core/common/buf"
|
|
xerrors "github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/platform"
|
|
"golang.org/x/net/route"
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
)
|
|
|
|
const (
|
|
utunControlName = "com.apple.net.utun_control"
|
|
sysprotoControl = 2
|
|
gateway = "169.254.10.1/30"
|
|
utunHeaderSize = 4
|
|
UTUN_OPT_IFNAME = 2
|
|
)
|
|
|
|
const (
|
|
SIOCAIFADDR6 = 2155899162 // netinet6/in6_var.h
|
|
IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h
|
|
IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h
|
|
ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
|
|
)
|
|
|
|
//go:linkname procyield runtime.procyield
|
|
func procyield(cycles uint32)
|
|
|
|
type DarwinTun struct {
|
|
tunFile *os.File
|
|
options *Config
|
|
tunFd int
|
|
ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system)
|
|
|
|
routeMonitor *os.File
|
|
routeMonitorOnce sync.Once
|
|
systemRoutes []netip.Prefix
|
|
}
|
|
|
|
var (
|
|
_ Tun = (*DarwinTun)(nil)
|
|
_ GVisorDevice = (*DarwinTun)(nil)
|
|
)
|
|
|
|
func NewTun(options *Config) (Tun, error) {
|
|
// Check if fd is provided via environment (iOS mode)
|
|
fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" })
|
|
if fdStr != "" {
|
|
// iOS: use provided fd from NetworkExtension
|
|
fd, err := strconv.Atoi(fdStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = unix.SetNonblock(fd, true); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &DarwinTun{
|
|
tunFile: os.NewFile(uintptr(fd), "utun"),
|
|
options: options,
|
|
tunFd: fd,
|
|
ownsFd: false,
|
|
}, nil
|
|
}
|
|
|
|
// macOS: create our own utun interface
|
|
tunFile, err := open(options.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = setup(options.Name, options.MTU)
|
|
if err != nil {
|
|
_ = tunFile.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return &DarwinTun{
|
|
tunFile: tunFile,
|
|
options: options,
|
|
tunFd: int(tunFile.Fd()),
|
|
ownsFd: true,
|
|
}, nil
|
|
}
|
|
|
|
func (t *DarwinTun) Start() error {
|
|
if !t.ownsFd {
|
|
return nil
|
|
}
|
|
|
|
if err := t.setSystemRoutes(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if updater != nil {
|
|
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
|
if err != nil {
|
|
_ = t.unsetSystemRoutes()
|
|
return err
|
|
}
|
|
t.routeMonitor = os.NewFile(uintptr(fd), "xray-route-monitor")
|
|
go t.monitorRouteChanges()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *DarwinTun) Close() error {
|
|
t.routeMonitorOnce.Do(func() {
|
|
if t.routeMonitor != nil {
|
|
_ = t.routeMonitor.Close()
|
|
}
|
|
})
|
|
routeErr := t.unsetSystemRoutes()
|
|
if t.ownsFd {
|
|
return xerrors.Combine(routeErr, t.tunFile.Close())
|
|
}
|
|
// iOS: don't close the fd, it's owned by NetworkExtension
|
|
return routeErr
|
|
}
|
|
|
|
func (t *DarwinTun) monitorRouteChanges() {
|
|
buffer := make([]byte, 64*1024)
|
|
for {
|
|
if _, err := t.routeMonitor.Read(buffer); err != nil {
|
|
if !errors.Is(err, os.ErrClosed) {
|
|
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to monitor route changes")
|
|
}
|
|
return
|
|
}
|
|
if updater != nil {
|
|
updater.Update()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *DarwinTun) Name() (string, error) {
|
|
return unix.GetsockoptString(t.tunFd, sysprotoControl, UTUN_OPT_IFNAME)
|
|
}
|
|
|
|
func (t *DarwinTun) Index() (int, error) {
|
|
name, err := t.Name()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
iface, err := net.InterfaceByName(name)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return iface.Index, nil
|
|
}
|
|
|
|
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
|
func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
|
// request memory to write from reusable buffer pool
|
|
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
|
|
defer b.Release()
|
|
|
|
// prepare Darwin specific packet header
|
|
_, _ = b.Write([]byte{0x0, 0x0, 0x0, 0x0})
|
|
// copy the bytes of slices that compose the packet into the allocated buffer
|
|
for _, packetElement := range packet.AsSlices() {
|
|
_, _ = b.Write(packetElement)
|
|
}
|
|
// fill Darwin specific header from the first raw packet byte, that we can access now
|
|
var family byte
|
|
switch b.Byte(4) >> 4 {
|
|
case 4:
|
|
family = unix.AF_INET
|
|
case 6:
|
|
family = unix.AF_INET6
|
|
default:
|
|
return &tcpip.ErrAborted{}
|
|
}
|
|
b.SetByte(3, family)
|
|
|
|
if _, err := t.tunFile.Write(b.Bytes()); err != nil {
|
|
if errors.Is(err, unix.EAGAIN) {
|
|
return &tcpip.ErrWouldBlock{}
|
|
}
|
|
return &tcpip.ErrAborted{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadPacket implements GVisorDevice method to read one packet from the tun device
|
|
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
|
|
// which will make the stack call Wait which should implement desired push-back
|
|
func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
|
// request memory to write from reusable buffer pool
|
|
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
|
|
|
|
// read the bytes to the interface file
|
|
n, err := b.ReadFrom(t.tunFile)
|
|
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
|
|
b.Release()
|
|
return 0, nil, ErrQueueEmpty
|
|
}
|
|
if err != nil {
|
|
b.Release()
|
|
return 0, nil, err
|
|
}
|
|
|
|
// discard empty or sub-empty packets
|
|
if n <= utunHeaderSize {
|
|
b.Release()
|
|
return 0, nil, ErrQueueEmpty
|
|
}
|
|
|
|
// network protocol version from first byte of the raw packet, the one that follows Darwin specific header
|
|
version := b.Byte(utunHeaderSize) >> 4
|
|
packetBuffer := buffer.MakeWithData(b.BytesFrom(utunHeaderSize))
|
|
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Payload: packetBuffer,
|
|
IsForwardedPacket: true,
|
|
OnRelease: func() {
|
|
b.Release()
|
|
},
|
|
}), nil
|
|
}
|
|
|
|
// Wait some cpu cycles
|
|
func (t *DarwinTun) Wait() {
|
|
procyield(1)
|
|
}
|
|
|
|
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
|
}
|
|
|
|
// open the interface, by creating new utunN if in the system and returning its file descriptor
|
|
func open(name string) (*os.File, error) {
|
|
ifIndex := -1
|
|
_, err := fmt.Sscanf(name, "utun%d", &ifIndex)
|
|
if err != nil || ifIndex < 0 {
|
|
return nil, errors.New("interface name must be utunN, where N is a number, e.g. utun9, utun11 and so on")
|
|
}
|
|
|
|
fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysprotoControl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctlInfo := &unix.CtlInfo{}
|
|
copy(ctlInfo.Name[:], utunControlName)
|
|
if err := unix.IoctlCtlInfo(fd, ctlInfo); err != nil {
|
|
_ = unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
|
|
sockaddr := &unix.SockaddrCtl{
|
|
ID: ctlInfo.Id,
|
|
Unit: uint32(ifIndex) + 1,
|
|
}
|
|
if err := unix.Connect(fd, sockaddr); err != nil {
|
|
_ = unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
|
|
if err := unix.SetNonblock(fd, true); err != nil {
|
|
_ = unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
|
|
return os.NewFile(uintptr(fd), name), nil
|
|
}
|
|
|
|
// setup the interface by name
|
|
func setup(name string, MTU uint32) error {
|
|
if err := setMTU(name, MTU); err != nil {
|
|
return err
|
|
}
|
|
|
|
/*
|
|
* Darwin routing require tunnel type interface to have local and remote address, to be routable.
|
|
* To simplify inevitable task, assign the interface static ip address, which in current implementation
|
|
* is just some random ip from link-local pool, allowing to not bother about existing routing intersection.
|
|
*/
|
|
syntheticIP, _ := netip.ParsePrefix(gateway)
|
|
if err := setIPAddress(name, syntheticIP); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setMTU sets MTU on the interface by given name
|
|
func setMTU(name string, mtu uint32) error {
|
|
socket, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(socket)
|
|
|
|
ifr := unix.IfreqMTU{MTU: int32(mtu)}
|
|
copy(ifr.Name[:], name)
|
|
return unix.IoctlSetIfreqMTU(socket, &ifr)
|
|
}
|
|
|
|
type ifAliasReq4 struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Addr unix.RawSockaddrInet4
|
|
Dstaddr unix.RawSockaddrInet4
|
|
Mask unix.RawSockaddrInet4
|
|
}
|
|
|
|
type ifAliasReq6 struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Addr unix.RawSockaddrInet6
|
|
Dstaddr unix.RawSockaddrInet6
|
|
Mask unix.RawSockaddrInet6
|
|
Flags uint32
|
|
Lifetime addrLifetime6
|
|
}
|
|
|
|
type addrLifetime6 struct {
|
|
Expire float64
|
|
Preferred float64
|
|
Vltime uint32
|
|
Pltime uint32
|
|
}
|
|
|
|
// setIPAddress sets ipv4 and ipv6 addresses to the interface, required for the routing to work
|
|
func setIPAddress(name string, gateway netip.Prefix) error {
|
|
socket4, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(socket4)
|
|
|
|
// assume local ip address is next one from the remote address
|
|
local4 := gateway.Addr().As4()
|
|
local4[3]++
|
|
|
|
// fill the configuration for ipv4
|
|
ifReq4 := ifAliasReq4{
|
|
Addr: unix.RawSockaddrInet4{
|
|
Len: unix.SizeofSockaddrInet4,
|
|
Family: unix.AF_INET,
|
|
Addr: local4,
|
|
},
|
|
Dstaddr: unix.RawSockaddrInet4{
|
|
Len: unix.SizeofSockaddrInet4,
|
|
Family: unix.AF_INET,
|
|
Addr: gateway.Addr().As4(),
|
|
},
|
|
Mask: unix.RawSockaddrInet4{
|
|
Len: unix.SizeofSockaddrInet4,
|
|
Family: unix.AF_INET,
|
|
Addr: netip.MustParseAddr(net.IP(net.CIDRMask(gateway.Bits(), 32)).String()).As4(),
|
|
},
|
|
}
|
|
copy(ifReq4.Name[:], name)
|
|
if err = ioctlPtr(socket4, unix.SIOCAIFADDR, unsafe.Pointer(&ifReq4)); err != nil {
|
|
return os.NewSyscallError("SIOCAIFADDR", err)
|
|
}
|
|
|
|
socket6, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(socket6)
|
|
|
|
// link-local ipv6 address with suffix from ipv6
|
|
local6 := netip.AddrFrom16([16]byte{0: 0xfe, 1: 0x80, 12: local4[0], 13: local4[1], 14: local4[2], 15: local4[3]})
|
|
|
|
// fill the configuration for ipv6
|
|
// only link-local address without the destination is enough for it
|
|
ifReq6 := ifAliasReq6{
|
|
Addr: unix.RawSockaddrInet6{
|
|
Len: unix.SizeofSockaddrInet6,
|
|
Family: unix.AF_INET6,
|
|
Addr: local6.As16(),
|
|
},
|
|
Mask: unix.RawSockaddrInet6{
|
|
Len: unix.SizeofSockaddrInet6,
|
|
Family: unix.AF_INET6,
|
|
Addr: netip.MustParseAddr(net.IP(net.CIDRMask(64, 128)).String()).As16(),
|
|
},
|
|
Flags: IN6_IFF_NODAD,
|
|
Lifetime: addrLifetime6{
|
|
Vltime: ND6_INFINITE_LIFETIME,
|
|
Pltime: ND6_INFINITE_LIFETIME,
|
|
},
|
|
}
|
|
// assign link-local ipv6 address to the interface.
|
|
// this will additionally trigger OS level autoconfiguration, which will result two different link-local
|
|
// addresses - the requested one, and autoconfigured one.
|
|
// this really has no known side effects, just look excessive. and actually considered pretty normal way to
|
|
// enable the ipv6 on the interface by macOS concepts.
|
|
copy(ifReq6.Name[:], name)
|
|
if err = ioctlPtr(socket6, SIOCAIFADDR6, unsafe.Pointer(&ifReq6)); err != nil {
|
|
return os.NewSyscallError("SIOCAIFADDR6", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
|
|
if errno != 0 {
|
|
return errno
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
|
var err1, err2 error
|
|
|
|
switch network {
|
|
case "tcp6", "udp6", "ip6":
|
|
err1 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
|
|
fallthrough
|
|
case "tcp4", "udp4", "ip4":
|
|
err2 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
|
|
default:
|
|
panic(network + " " + address)
|
|
}
|
|
|
|
return errors.Join(err1, err2)
|
|
}
|
|
|
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
|
if fixedName != "" {
|
|
iface, err := net.InterfaceByName(fixedName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if iface.Index == tunIndex {
|
|
return nil, errors.New("outbound interface cannot be the TUN interface")
|
|
}
|
|
return iface, nil
|
|
}
|
|
|
|
rib, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
messages, err := route.ParseRIB(route.RIBTypeRoute, rib)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ipv6Index int
|
|
for _, message := range messages {
|
|
routeMessage, ok := message.(*route.RouteMessage)
|
|
if !ok || routeMessage.Index == tunIndex {
|
|
continue
|
|
}
|
|
if routeMessage.Flags&unix.RTF_UP == 0 || routeMessage.Flags&unix.RTF_GATEWAY == 0 {
|
|
continue
|
|
}
|
|
|
|
family, ok := defaultRouteFamily(routeMessage)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if family == unix.AF_INET {
|
|
return usableDarwinInterface(routeMessage.Index)
|
|
}
|
|
if family == unix.AF_INET6 && ipv6Index == 0 {
|
|
ipv6Index = routeMessage.Index
|
|
}
|
|
}
|
|
|
|
if ipv6Index != 0 {
|
|
return usableDarwinInterface(ipv6Index)
|
|
}
|
|
return nil, errors.New("default route not found")
|
|
}
|
|
|
|
func defaultRouteFamily(message *route.RouteMessage) (int, bool) {
|
|
if len(message.Addrs) <= unix.RTAX_NETMASK {
|
|
return 0, false
|
|
}
|
|
|
|
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
|
case *route.Inet4Addr:
|
|
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr)
|
|
if !ok || destination.IP != netip.IPv4Unspecified().As4() {
|
|
return 0, false
|
|
}
|
|
ones, bits := net.IPMask(mask.IP[:]).Size()
|
|
return unix.AF_INET, ones == 0 && bits == 32
|
|
case *route.Inet6Addr:
|
|
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet6Addr)
|
|
if !ok || destination.IP != netip.IPv6Unspecified().As16() {
|
|
return 0, false
|
|
}
|
|
ones, bits := net.IPMask(mask.IP[:]).Size()
|
|
return unix.AF_INET6, ones == 0 && bits == 128
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func usableDarwinInterface(index int) (*net.Interface, error) {
|
|
iface, err := net.InterfaceByIndex(index)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
|
return nil, errors.New("default route interface is not usable")
|
|
}
|
|
return iface, nil
|
|
}
|
|
|
|
func (t *DarwinTun) setSystemRoutes() error {
|
|
routes, err := buildDarwinSystemRoutes(t.options.AutoSystemRoutingTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(routes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
tunIndex, err := t.Index()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, destination := range routes {
|
|
if err := execDarwinRoute(unix.RTM_ADD, tunIndex, destination); err != nil {
|
|
_ = t.unsetSystemRoutes()
|
|
return xerrors.New("failed to add system route ", destination).Base(err)
|
|
}
|
|
t.systemRoutes = append(t.systemRoutes, destination)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *DarwinTun) unsetSystemRoutes() error {
|
|
var errs []error
|
|
tunIndex, indexErr := t.Index()
|
|
if indexErr != nil && len(t.systemRoutes) > 0 {
|
|
errs = append(errs, indexErr)
|
|
}
|
|
for i := len(t.systemRoutes) - 1; i >= 0; i-- {
|
|
destination := t.systemRoutes[i]
|
|
if err := execDarwinRoute(unix.RTM_DELETE, tunIndex, destination); err != nil && !errors.Is(err, unix.ESRCH) {
|
|
errs = append(errs, xerrors.New("failed to delete system route ", destination).Base(err))
|
|
}
|
|
}
|
|
t.systemRoutes = nil
|
|
return xerrors.Combine(errs...)
|
|
}
|
|
|
|
func buildDarwinSystemRoutes(configured []string) ([]netip.Prefix, error) {
|
|
routes := make([]netip.Prefix, 0, len(configured))
|
|
seen := make(map[netip.Prefix]struct{})
|
|
|
|
appendRoute := func(prefix netip.Prefix) {
|
|
prefix = prefix.Masked()
|
|
if _, found := seen[prefix]; found {
|
|
return
|
|
}
|
|
seen[prefix] = struct{}{}
|
|
routes = append(routes, prefix)
|
|
}
|
|
|
|
for _, value := range configured {
|
|
prefix, err := netip.ParsePrefix(value)
|
|
if err != nil {
|
|
return nil, xerrors.New("invalid system route ", value).Base(err)
|
|
}
|
|
prefix = prefix.Masked()
|
|
if prefix.Bits() == 0 {
|
|
for _, protected := range darwinProtectedDefaultRoutes(prefix.Addr().Is4()) {
|
|
appendRoute(protected)
|
|
}
|
|
continue
|
|
}
|
|
appendRoute(prefix)
|
|
}
|
|
|
|
return routes, nil
|
|
}
|
|
|
|
func darwinProtectedDefaultRoutes(ipv4 bool) []netip.Prefix {
|
|
routes := make([]netip.Prefix, 0, 8)
|
|
for i := 0; i < 8; i++ {
|
|
if ipv4 {
|
|
var address [4]byte
|
|
address[0] = 1 << i
|
|
routes = append(routes, netip.PrefixFrom(netip.AddrFrom4(address), 8-i))
|
|
} else {
|
|
var address [16]byte
|
|
address[0] = 1 << i
|
|
routes = append(routes, netip.PrefixFrom(netip.AddrFrom16(address), 8-i))
|
|
}
|
|
}
|
|
return routes
|
|
}
|
|
|
|
func execDarwinRoute(messageType int, interfaceIndex int, destination netip.Prefix) error {
|
|
message := route.RouteMessage{
|
|
Type: messageType,
|
|
Version: unix.RTM_VERSION,
|
|
Flags: unix.RTF_STATIC | unix.RTF_GATEWAY,
|
|
Seq: 1,
|
|
}
|
|
if messageType == unix.RTM_ADD {
|
|
message.Flags |= unix.RTF_UP
|
|
}
|
|
|
|
if destination.Addr().Is4() {
|
|
gatewayPrefix := netip.MustParsePrefix(gateway)
|
|
message.Addrs = []route.Addr{
|
|
unix.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()},
|
|
unix.RTAX_NETMASK: &route.Inet4Addr{IP: prefixMask4(destination.Bits())},
|
|
unix.RTAX_GATEWAY: &route.Inet4Addr{IP: gatewayPrefix.Addr().As4()},
|
|
}
|
|
} else {
|
|
message.Flags &^= unix.RTF_GATEWAY
|
|
message.Index = interfaceIndex
|
|
message.Addrs = []route.Addr{
|
|
unix.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()},
|
|
unix.RTAX_NETMASK: &route.Inet6Addr{IP: prefixMask6(destination.Bits())},
|
|
unix.RTAX_GATEWAY: &route.LinkAddr{Index: interfaceIndex},
|
|
}
|
|
}
|
|
|
|
request, err := message.Marshal()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(fd)
|
|
_, err = unix.Write(fd, request)
|
|
return err
|
|
}
|
|
|
|
func prefixMask4(bits int) [4]byte {
|
|
var mask [4]byte
|
|
copy(mask[:], net.CIDRMask(bits, 32))
|
|
return mask
|
|
}
|
|
|
|
func prefixMask6(bits int) [16]byte {
|
|
var mask [16]byte
|
|
copy(mask[:], net.CIDRMask(bits, 128))
|
|
return mask
|
|
}
|