mirror of
https://github.com/slackhq/nebula.git
synced 2025-11-09 00:43:57 +01:00
With the previous implementation, we check if route.MTU is greater than zero, but it will always be because we set it to the default MTU in parseUnsafeRoutes. This change leaves it as zero in parseUnsafeRoutes so it can be examined later.
329 lines
7.0 KiB
Go
329 lines
7.0 KiB
Go
//go:build !android && !e2e_testing
|
|
// +build !android,!e2e_testing
|
|
|
|
package overlay
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/slackhq/nebula/cidr"
|
|
"github.com/slackhq/nebula/iputil"
|
|
"github.com/vishvananda/netlink"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type tun struct {
|
|
io.ReadWriteCloser
|
|
fd int
|
|
Device string
|
|
cidr *net.IPNet
|
|
MaxMTU int
|
|
DefaultMTU int
|
|
TXQueueLen int
|
|
Routes []Route
|
|
routeTree *cidr.Tree4
|
|
l *logrus.Logger
|
|
}
|
|
|
|
type ifReq struct {
|
|
Name [16]byte
|
|
Flags uint16
|
|
pad [8]byte
|
|
}
|
|
|
|
func ioctl(a1, a2, a3 uintptr) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, a1, a2, a3)
|
|
if errno != 0 {
|
|
return errno
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ifreqAddr struct {
|
|
Name [16]byte
|
|
Addr unix.RawSockaddrInet4
|
|
pad [8]byte
|
|
}
|
|
|
|
type ifreqMTU struct {
|
|
Name [16]byte
|
|
MTU int32
|
|
pad [8]byte
|
|
}
|
|
|
|
type ifreqQLEN struct {
|
|
Name [16]byte
|
|
Value int32
|
|
pad [8]byte
|
|
}
|
|
|
|
func newTunFromFd(l *logrus.Logger, deviceFd int, cidr *net.IPNet, defaultMTU int, routes []Route, txQueueLen int) (*tun, error) {
|
|
routeTree, err := makeRouteTree(l, routes, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
|
|
|
|
return &tun{
|
|
ReadWriteCloser: file,
|
|
fd: int(file.Fd()),
|
|
Device: "tun0",
|
|
cidr: cidr,
|
|
DefaultMTU: defaultMTU,
|
|
TXQueueLen: txQueueLen,
|
|
Routes: routes,
|
|
routeTree: routeTree,
|
|
l: l,
|
|
}, nil
|
|
}
|
|
|
|
func newTun(l *logrus.Logger, deviceName string, cidr *net.IPNet, defaultMTU int, routes []Route, txQueueLen int, multiqueue bool) (*tun, error) {
|
|
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var req ifReq
|
|
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
|
if multiqueue {
|
|
req.Flags |= unix.IFF_MULTI_QUEUE
|
|
}
|
|
copy(req.Name[:], deviceName)
|
|
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
|
return nil, err
|
|
}
|
|
name := strings.Trim(string(req.Name[:]), "\x00")
|
|
|
|
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
|
|
|
maxMTU := defaultMTU
|
|
for _, r := range routes {
|
|
if r.MTU == 0 {
|
|
r.MTU = defaultMTU
|
|
}
|
|
|
|
if r.MTU > maxMTU {
|
|
maxMTU = r.MTU
|
|
}
|
|
}
|
|
|
|
routeTree, err := makeRouteTree(l, routes, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &tun{
|
|
ReadWriteCloser: file,
|
|
fd: int(file.Fd()),
|
|
Device: name,
|
|
cidr: cidr,
|
|
MaxMTU: maxMTU,
|
|
DefaultMTU: defaultMTU,
|
|
TXQueueLen: txQueueLen,
|
|
Routes: routes,
|
|
routeTree: routeTree,
|
|
l: l,
|
|
}, nil
|
|
}
|
|
|
|
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
|
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var req ifReq
|
|
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)
|
|
copy(req.Name[:], t.Device)
|
|
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
|
|
|
return file, nil
|
|
}
|
|
|
|
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
|
r := t.routeTree.MostSpecificContains(ip)
|
|
if r != nil {
|
|
return r.(iputil.VpnIp)
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (t *tun) Write(b []byte) (int, error) {
|
|
var nn int
|
|
max := len(b)
|
|
|
|
for {
|
|
n, err := unix.Write(t.fd, b[nn:max])
|
|
if n > 0 {
|
|
nn += n
|
|
}
|
|
if nn == len(b) {
|
|
return nn, err
|
|
}
|
|
|
|
if err != nil {
|
|
return nn, err
|
|
}
|
|
|
|
if n == 0 {
|
|
return nn, io.ErrUnexpectedEOF
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t tun) deviceBytes() (o [16]byte) {
|
|
for i, c := range t.Device {
|
|
o[i] = byte(c)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (t tun) Activate() error {
|
|
devName := t.deviceBytes()
|
|
|
|
var addr, mask [4]byte
|
|
|
|
copy(addr[:], t.cidr.IP.To4())
|
|
copy(mask[:], t.cidr.Mask)
|
|
|
|
s, err := unix.Socket(
|
|
unix.AF_INET,
|
|
unix.SOCK_DGRAM,
|
|
unix.IPPROTO_IP,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fd := uintptr(s)
|
|
|
|
ifra := ifreqAddr{
|
|
Name: devName,
|
|
Addr: unix.RawSockaddrInet4{
|
|
Family: unix.AF_INET,
|
|
Addr: addr,
|
|
},
|
|
}
|
|
|
|
// Set the device ip address
|
|
if err = ioctl(fd, unix.SIOCSIFADDR, uintptr(unsafe.Pointer(&ifra))); err != nil {
|
|
return fmt.Errorf("failed to set tun address: %s", err)
|
|
}
|
|
|
|
// Set the device network
|
|
ifra.Addr.Addr = mask
|
|
if err = ioctl(fd, unix.SIOCSIFNETMASK, uintptr(unsafe.Pointer(&ifra))); err != nil {
|
|
return fmt.Errorf("failed to set tun netmask: %s", err)
|
|
}
|
|
|
|
// Set the device name
|
|
ifrf := ifReq{Name: devName}
|
|
if err = ioctl(fd, unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
|
|
return fmt.Errorf("failed to set tun device name: %s", err)
|
|
}
|
|
|
|
// Set the MTU on the device
|
|
ifm := ifreqMTU{Name: devName, MTU: int32(t.MaxMTU)}
|
|
if err = ioctl(fd, unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm))); err != nil {
|
|
// This is currently a non fatal condition because the route table must have the MTU set appropriately as well
|
|
t.l.WithError(err).Error("Failed to set tun mtu")
|
|
}
|
|
|
|
// Set the transmit queue length
|
|
ifrq := ifreqQLEN{Name: devName, Value: int32(t.TXQueueLen)}
|
|
if err = ioctl(fd, unix.SIOCSIFTXQLEN, uintptr(unsafe.Pointer(&ifrq))); err != nil {
|
|
// If we can't set the queue length nebula will still work but it may lead to packet loss
|
|
t.l.WithError(err).Error("Failed to set tun tx queue length")
|
|
}
|
|
|
|
// Bring up the interface
|
|
ifrf.Flags = ifrf.Flags | unix.IFF_UP
|
|
if err = ioctl(fd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
|
|
return fmt.Errorf("failed to bring the tun device up: %s", err)
|
|
}
|
|
|
|
// Set the routes
|
|
link, err := netlink.LinkByName(t.Device)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get tun device link: %s", err)
|
|
}
|
|
|
|
// Default route
|
|
dr := &net.IPNet{IP: t.cidr.IP.Mask(t.cidr.Mask), Mask: t.cidr.Mask}
|
|
nr := netlink.Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: dr,
|
|
MTU: t.DefaultMTU,
|
|
AdvMSS: t.advMSS(Route{}),
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Src: t.cidr.IP,
|
|
Protocol: unix.RTPROT_KERNEL,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Type: unix.RTN_UNICAST,
|
|
}
|
|
err = netlink.RouteReplace(&nr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set mtu %v on the default route %v; %v", t.DefaultMTU, dr, err)
|
|
}
|
|
|
|
// Path routes
|
|
for _, r := range t.Routes {
|
|
nr := netlink.Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: r.Cidr,
|
|
MTU: r.MTU,
|
|
AdvMSS: t.advMSS(r),
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
}
|
|
|
|
if r.Metric > 0 {
|
|
nr.Priority = r.Metric
|
|
}
|
|
|
|
err = netlink.RouteAdd(&nr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set mtu %v on route %v; %v", r.MTU, r.Cidr, err)
|
|
}
|
|
}
|
|
|
|
// Run the interface
|
|
ifrf.Flags = ifrf.Flags | unix.IFF_UP | unix.IFF_RUNNING
|
|
if err = ioctl(fd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
|
|
return fmt.Errorf("failed to run tun device: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *tun) Cidr() *net.IPNet {
|
|
return t.cidr
|
|
}
|
|
|
|
func (t *tun) Name() string {
|
|
return t.Device
|
|
}
|
|
|
|
func (t tun) advMSS(r Route) int {
|
|
mtu := r.MTU
|
|
if r.MTU == 0 {
|
|
mtu = t.DefaultMTU
|
|
}
|
|
|
|
// We only need to set advmss if the route MTU does not match the device MTU
|
|
if mtu != t.MaxMTU {
|
|
return mtu - 40
|
|
}
|
|
return 0
|
|
}
|