sing-tun/tun_linux.go
2022-07-15 14:59:26 +08:00

149 lines
3.1 KiB
Go

package tun
import (
"math"
"net"
"net/netip"
"runtime"
"syscall"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/vishvananda/netlink"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
type NativeTun struct {
name string
inet4Address netip.Prefix
inet6Address netip.Prefix
mtu uint32
autoRoute bool
fdList []int
}
func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) (Tun, error) {
tunFd, err := tun.Open(name)
if err != nil {
return nil, err
}
nativeTun := &NativeTun{
name: name,
fdList: []int{tunFd},
mtu: mtu,
inet4Address: inet4Address,
inet6Address: inet6Address,
autoRoute: autoRoute,
}
err = nativeTun.configure()
if err != nil {
return nil, E.Errors(err, syscall.Close(tunFd))
}
return nativeTun, nil
}
func (t *NativeTun) routes(tunLink netlink.Link) []netlink.Route {
var routes []netlink.Route
if t.inet4Address.IsValid() {
routes = append(routes, netlink.Route{
Dst: &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
},
LinkIndex: tunLink.Attrs().Index,
})
}
if t.inet6Address.IsValid() {
routes = append(routes, netlink.Route{
Dst: &net.IPNet{
IP: net.IPv6zero,
Mask: net.CIDRMask(0, 128),
},
LinkIndex: tunLink.Attrs().Index,
})
}
return routes
}
func (t *NativeTun) configure() error {
tunLink, err := netlink.LinkByName(t.name)
if err != nil {
return err
}
if t.inet4Address.IsValid() {
addr4, _ := netlink.ParseAddr(t.inet4Address.String())
err = netlink.AddrAdd(tunLink, addr4)
if err != nil {
return err
}
}
if t.inet6Address.IsValid() {
addr6, _ := netlink.ParseAddr(t.inet6Address.String())
err = netlink.AddrAdd(tunLink, addr6)
if err != nil {
return err
}
}
err = netlink.LinkSetMTU(tunLink, int(t.mtu))
if err != nil {
return err
}
err = netlink.LinkSetUp(tunLink)
if err != nil {
return err
}
if t.autoRoute {
for _, route := range t.routes(tunLink) {
err = netlink.RouteAdd(&route)
if err != nil {
return err
}
}
}
return nil
}
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) {
var packetDispatchMode fdbased.PacketDispatchMode
if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
packetDispatchMode = fdbased.PacketMMap
} else {
packetDispatchMode = fdbased.RecvMMsg
}
dupFdSize := int(math.Max(float64(runtime.NumCPU()/2), 1)) - 1
for i := 0; i < dupFdSize; i++ {
dupFd, err := syscall.Dup(t.fdList[0])
if err != nil {
return nil, err
}
t.fdList = append(t.fdList, dupFd)
}
return fdbased.New(&fdbased.Options{
FDs: t.fdList,
MTU: t.mtu,
PacketDispatchMode: packetDispatchMode,
})
}
func (t *NativeTun) Close() error {
tunLink, err := netlink.LinkByName(t.name)
if err != nil {
return err
}
if t.autoRoute {
for _, route := range t.routes(tunLink) {
err = netlink.RouteDel(&route)
if err != nil {
return err
}
}
}
return E.Errors(common.Map(t.fdList, syscall.Close)...)
}