mirror of
https://github.com/slackhq/nebula.git
synced 2025-12-06 02:30:57 -08:00
Switch most everything to netip in prep for ipv6 in the overlay (#1173)
This commit is contained in:
parent
00458302ca
commit
e264a0ff88
79 changed files with 1900 additions and 2682 deletions
|
|
@ -2,16 +2,14 @@ package overlay
|
|||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type Device interface {
|
||||
io.ReadWriteCloser
|
||||
Activate() error
|
||||
Cidr() *net.IPNet
|
||||
Cidr() netip.Prefix
|
||||
Name() string
|
||||
RouteFor(iputil.VpnIp) iputil.VpnIp
|
||||
RouteFor(netip.Addr) netip.Addr
|
||||
NewMultiQueueReader() (io.ReadWriteCloser, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,30 @@
|
|||
package overlay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
)
|
||||
|
||||
type Route struct {
|
||||
MTU int
|
||||
Metric int
|
||||
Cidr *net.IPNet
|
||||
Via *iputil.VpnIp
|
||||
Cidr netip.Prefix
|
||||
Via netip.Addr
|
||||
Install bool
|
||||
}
|
||||
|
||||
// Equal determines if a route that could be installed in the system route table is equal to another
|
||||
// Via is ignored since that is only consumed within nebula itself
|
||||
func (r Route) Equal(t Route) bool {
|
||||
if !r.Cidr.IP.Equal(t.Cidr.IP) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(r.Cidr.Mask, t.Cidr.Mask) {
|
||||
if r.Cidr != t.Cidr {
|
||||
return false
|
||||
}
|
||||
if r.Metric != t.Metric {
|
||||
|
|
@ -51,21 +47,21 @@ func (r Route) String() string {
|
|||
return s
|
||||
}
|
||||
|
||||
func makeRouteTree(l *logrus.Logger, routes []Route, allowMTU bool) (*cidr.Tree4[iputil.VpnIp], error) {
|
||||
routeTree := cidr.NewTree4[iputil.VpnIp]()
|
||||
func makeRouteTree(l *logrus.Logger, routes []Route, allowMTU bool) (*bart.Table[netip.Addr], error) {
|
||||
routeTree := new(bart.Table[netip.Addr])
|
||||
for _, r := range routes {
|
||||
if !allowMTU && r.MTU > 0 {
|
||||
l.WithField("route", r).Warnf("route MTU is not supported in %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
if r.Via != nil {
|
||||
routeTree.AddCIDR(r.Cidr, *r.Via)
|
||||
if r.Via.IsValid() {
|
||||
routeTree.Insert(r.Cidr, r.Via)
|
||||
}
|
||||
}
|
||||
return routeTree, nil
|
||||
}
|
||||
|
||||
func parseRoutes(c *config.C, network *net.IPNet) ([]Route, error) {
|
||||
func parseRoutes(c *config.C, network netip.Prefix) ([]Route, error) {
|
||||
var err error
|
||||
|
||||
r := c.Get("tun.routes")
|
||||
|
|
@ -116,12 +112,12 @@ func parseRoutes(c *config.C, network *net.IPNet) ([]Route, error) {
|
|||
MTU: mtu,
|
||||
}
|
||||
|
||||
_, r.Cidr, err = net.ParseCIDR(fmt.Sprintf("%v", rRoute))
|
||||
r.Cidr, err = netip.ParsePrefix(fmt.Sprintf("%v", rRoute))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entry %v.route in tun.routes failed to parse: %v", i+1, err)
|
||||
}
|
||||
|
||||
if !ipWithin(network, r.Cidr) {
|
||||
if !network.Contains(r.Cidr.Addr()) || r.Cidr.Bits() < network.Bits() {
|
||||
return nil, fmt.Errorf(
|
||||
"entry %v.route in tun.routes is not contained within the network attached to the certificate; route: %v, network: %v",
|
||||
i+1,
|
||||
|
|
@ -136,7 +132,7 @@ func parseRoutes(c *config.C, network *net.IPNet) ([]Route, error) {
|
|||
return routes, nil
|
||||
}
|
||||
|
||||
func parseUnsafeRoutes(c *config.C, network *net.IPNet) ([]Route, error) {
|
||||
func parseUnsafeRoutes(c *config.C, network netip.Prefix) ([]Route, error) {
|
||||
var err error
|
||||
|
||||
r := c.Get("tun.unsafe_routes")
|
||||
|
|
@ -202,9 +198,9 @@ func parseUnsafeRoutes(c *config.C, network *net.IPNet) ([]Route, error) {
|
|||
return nil, fmt.Errorf("entry %v.via in tun.unsafe_routes is not a string: found %T", i+1, rVia)
|
||||
}
|
||||
|
||||
nVia := net.ParseIP(via)
|
||||
if nVia == nil {
|
||||
return nil, fmt.Errorf("entry %v.via in tun.unsafe_routes failed to parse address: %v", i+1, via)
|
||||
viaVpnIp, err := netip.ParseAddr(via)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entry %v.via in tun.unsafe_routes failed to parse address: %v", i+1, err)
|
||||
}
|
||||
|
||||
rRoute, ok := m["route"]
|
||||
|
|
@ -212,8 +208,6 @@ func parseUnsafeRoutes(c *config.C, network *net.IPNet) ([]Route, error) {
|
|||
return nil, fmt.Errorf("entry %v.route in tun.unsafe_routes is not present", i+1)
|
||||
}
|
||||
|
||||
viaVpnIp := iputil.Ip2VpnIp(nVia)
|
||||
|
||||
install := true
|
||||
rInstall, ok := m["install"]
|
||||
if ok {
|
||||
|
|
@ -224,18 +218,18 @@ func parseUnsafeRoutes(c *config.C, network *net.IPNet) ([]Route, error) {
|
|||
}
|
||||
|
||||
r := Route{
|
||||
Via: &viaVpnIp,
|
||||
Via: viaVpnIp,
|
||||
MTU: mtu,
|
||||
Metric: metric,
|
||||
Install: install,
|
||||
}
|
||||
|
||||
_, r.Cidr, err = net.ParseCIDR(fmt.Sprintf("%v", rRoute))
|
||||
r.Cidr, err = netip.ParsePrefix(fmt.Sprintf("%v", rRoute))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entry %v.route in tun.unsafe_routes failed to parse: %v", i+1, err)
|
||||
}
|
||||
|
||||
if ipWithin(network, r.Cidr) {
|
||||
if network.Contains(r.Cidr.Addr()) {
|
||||
return nil, fmt.Errorf(
|
||||
"entry %v.route in tun.unsafe_routes is contained within the network attached to the certificate; route: %v, network: %v",
|
||||
i+1,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ package overlay
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -14,7 +13,8 @@ import (
|
|||
func Test_parseRoutes(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
c := config.NewC(l)
|
||||
_, n, _ := net.ParseCIDR("10.0.0.0/24")
|
||||
n, err := netip.ParsePrefix("10.0.0.0/24")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test no routes config
|
||||
routes, err := parseRoutes(c, n)
|
||||
|
|
@ -67,7 +67,7 @@ func Test_parseRoutes(t *testing.T) {
|
|||
c.Settings["tun"] = map[interface{}]interface{}{"routes": []interface{}{map[interface{}]interface{}{"mtu": "500", "route": "nope"}}}
|
||||
routes, err = parseRoutes(c, n)
|
||||
assert.Nil(t, routes)
|
||||
assert.EqualError(t, err, "entry 1.route in tun.routes failed to parse: invalid CIDR address: nope")
|
||||
assert.EqualError(t, err, "entry 1.route in tun.routes failed to parse: netip.ParsePrefix(\"nope\"): no '/'")
|
||||
|
||||
// below network range
|
||||
c.Settings["tun"] = map[interface{}]interface{}{"routes": []interface{}{map[interface{}]interface{}{"mtu": "500", "route": "1.0.0.0/8"}}}
|
||||
|
|
@ -112,7 +112,8 @@ func Test_parseRoutes(t *testing.T) {
|
|||
func Test_parseUnsafeRoutes(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
c := config.NewC(l)
|
||||
_, n, _ := net.ParseCIDR("10.0.0.0/24")
|
||||
n, err := netip.ParsePrefix("10.0.0.0/24")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test no routes config
|
||||
routes, err := parseUnsafeRoutes(c, n)
|
||||
|
|
@ -157,7 +158,7 @@ func Test_parseUnsafeRoutes(t *testing.T) {
|
|||
c.Settings["tun"] = map[interface{}]interface{}{"unsafe_routes": []interface{}{map[interface{}]interface{}{"mtu": "500", "via": "nope"}}}
|
||||
routes, err = parseUnsafeRoutes(c, n)
|
||||
assert.Nil(t, routes)
|
||||
assert.EqualError(t, err, "entry 1.via in tun.unsafe_routes failed to parse address: nope")
|
||||
assert.EqualError(t, err, "entry 1.via in tun.unsafe_routes failed to parse address: ParseAddr(\"nope\"): unable to parse IP")
|
||||
|
||||
// missing route
|
||||
c.Settings["tun"] = map[interface{}]interface{}{"unsafe_routes": []interface{}{map[interface{}]interface{}{"via": "127.0.0.1", "mtu": "500"}}}
|
||||
|
|
@ -169,7 +170,7 @@ func Test_parseUnsafeRoutes(t *testing.T) {
|
|||
c.Settings["tun"] = map[interface{}]interface{}{"unsafe_routes": []interface{}{map[interface{}]interface{}{"via": "127.0.0.1", "mtu": "500", "route": "nope"}}}
|
||||
routes, err = parseUnsafeRoutes(c, n)
|
||||
assert.Nil(t, routes)
|
||||
assert.EqualError(t, err, "entry 1.route in tun.unsafe_routes failed to parse: invalid CIDR address: nope")
|
||||
assert.EqualError(t, err, "entry 1.route in tun.unsafe_routes failed to parse: netip.ParsePrefix(\"nope\"): no '/'")
|
||||
|
||||
// within network range
|
||||
c.Settings["tun"] = map[interface{}]interface{}{"unsafe_routes": []interface{}{map[interface{}]interface{}{"via": "127.0.0.1", "route": "10.0.0.0/24"}}}
|
||||
|
|
@ -252,7 +253,8 @@ func Test_parseUnsafeRoutes(t *testing.T) {
|
|||
func Test_makeRouteTree(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
c := config.NewC(l)
|
||||
_, n, _ := net.ParseCIDR("10.0.0.0/24")
|
||||
n, err := netip.ParsePrefix("10.0.0.0/24")
|
||||
assert.NoError(t, err)
|
||||
|
||||
c.Settings["tun"] = map[interface{}]interface{}{"unsafe_routes": []interface{}{
|
||||
map[interface{}]interface{}{"via": "192.168.0.1", "route": "1.0.0.0/28"},
|
||||
|
|
@ -264,17 +266,26 @@ func Test_makeRouteTree(t *testing.T) {
|
|||
routeTree, err := makeRouteTree(l, routes, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ip := iputil.Ip2VpnIp(net.ParseIP("1.0.0.2"))
|
||||
ok, r := routeTree.MostSpecificContains(ip)
|
||||
ip, err := netip.ParseAddr("1.0.0.2")
|
||||
assert.NoError(t, err)
|
||||
r, ok := routeTree.Lookup(ip)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, iputil.Ip2VpnIp(net.ParseIP("192.168.0.1")), r)
|
||||
|
||||
ip = iputil.Ip2VpnIp(net.ParseIP("1.0.0.1"))
|
||||
ok, r = routeTree.MostSpecificContains(ip)
|
||||
nip, err := netip.ParseAddr("192.168.0.1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nip, r)
|
||||
|
||||
ip, err = netip.ParseAddr("1.0.0.1")
|
||||
assert.NoError(t, err)
|
||||
r, ok = routeTree.Lookup(ip)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, iputil.Ip2VpnIp(net.ParseIP("192.168.0.2")), r)
|
||||
|
||||
ip = iputil.Ip2VpnIp(net.ParseIP("1.1.0.1"))
|
||||
ok, r = routeTree.MostSpecificContains(ip)
|
||||
nip, err = netip.ParseAddr("192.168.0.2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nip, r)
|
||||
|
||||
ip, err = netip.ParseAddr("1.1.0.1")
|
||||
assert.NoError(t, err)
|
||||
r, ok = routeTree.Lookup(ip)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package overlay
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/config"
|
||||
|
|
@ -11,9 +11,9 @@ import (
|
|||
const DefaultMTU = 1300
|
||||
|
||||
// TODO: We may be able to remove routines
|
||||
type DeviceFactory func(c *config.C, l *logrus.Logger, tunCidr *net.IPNet, routines int) (Device, error)
|
||||
type DeviceFactory func(c *config.C, l *logrus.Logger, tunCidr netip.Prefix, routines int) (Device, error)
|
||||
|
||||
func NewDeviceFromConfig(c *config.C, l *logrus.Logger, tunCidr *net.IPNet, routines int) (Device, error) {
|
||||
func NewDeviceFromConfig(c *config.C, l *logrus.Logger, tunCidr netip.Prefix, routines int) (Device, error) {
|
||||
switch {
|
||||
case c.GetBool("tun.disabled", false):
|
||||
tun := newDisabledTun(tunCidr, c.GetInt("tun.tx_queue", 500), c.GetBool("stats.message_metrics", false), l)
|
||||
|
|
@ -25,12 +25,12 @@ func NewDeviceFromConfig(c *config.C, l *logrus.Logger, tunCidr *net.IPNet, rout
|
|||
}
|
||||
|
||||
func NewFdDeviceFromConfig(fd *int) DeviceFactory {
|
||||
return func(c *config.C, l *logrus.Logger, tunCidr *net.IPNet, routines int) (Device, error) {
|
||||
return func(c *config.C, l *logrus.Logger, tunCidr netip.Prefix, routines int) (Device, error) {
|
||||
return newTunFromFd(c, l, *fd, tunCidr)
|
||||
}
|
||||
}
|
||||
|
||||
func getAllRoutesFromConfig(c *config.C, cidr *net.IPNet, initial bool) (bool, []Route, error) {
|
||||
func getAllRoutesFromConfig(c *config.C, cidr netip.Prefix, initial bool) (bool, []Route, error) {
|
||||
if !initial && !c.HasChanged("tun.routes") && !c.HasChanged("tun.unsafe_routes") {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,27 +6,26 @@ package overlay
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
)
|
||||
|
||||
type tun struct {
|
||||
io.ReadWriteCloser
|
||||
fd int
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
l *logrus.Logger
|
||||
}
|
||||
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr *net.IPNet) (*tun, error) {
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr netip.Prefix) (*tun, error) {
|
||||
// XXX Android returns an fd in non-blocking mode which is necessary for shutdown to work properly.
|
||||
// Be sure not to call file.Fd() as it will set the fd to blocking mode.
|
||||
file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
|
||||
|
|
@ -53,12 +52,12 @@ func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr *net.IPNet)
|
|||
return t, nil
|
||||
}
|
||||
|
||||
func newTun(_ *config.C, _ *logrus.Logger, _ *net.IPNet, _ bool) (*tun, error) {
|
||||
func newTun(_ *config.C, _ *logrus.Logger, _ netip.Prefix, _ bool) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTun not supported in Android")
|
||||
}
|
||||
|
||||
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *tun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +86,7 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) Cidr() *net.IPNet {
|
||||
func (t *tun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
netroute "golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
|
|
@ -25,10 +25,10 @@ import (
|
|||
type tun struct {
|
||||
io.ReadWriteCloser
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
DefaultMTU int
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
linkAddr *netroute.LinkAddr
|
||||
l *logrus.Logger
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ type ifreqMTU struct {
|
|||
pad [8]byte
|
||||
}
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error) {
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, _ bool) (*tun, error) {
|
||||
name := c.GetString("tun.dev", "")
|
||||
ifIndex := -1
|
||||
if name != "" && name != "utun" {
|
||||
|
|
@ -172,7 +172,7 @@ func (t *tun) deviceBytes() (o [16]byte) {
|
|||
return
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*tun, error) {
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ netip.Prefix) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in Darwin")
|
||||
}
|
||||
|
||||
|
|
@ -188,8 +188,13 @@ func (t *tun) Activate() error {
|
|||
|
||||
var addr, mask [4]byte
|
||||
|
||||
copy(addr[:], t.cidr.IP.To4())
|
||||
copy(mask[:], t.cidr.Mask)
|
||||
if !t.cidr.Addr().Is4() {
|
||||
//TODO: IPV6-WORK
|
||||
panic("need ipv6")
|
||||
}
|
||||
|
||||
addr = t.cidr.Addr().As4()
|
||||
copy(mask[:], prefixToMask(t.cidr))
|
||||
|
||||
s, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
|
|
@ -329,13 +334,12 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
ok, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *tun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, ok := t.routeTree.Load().Lookup(ip)
|
||||
if ok {
|
||||
return r
|
||||
}
|
||||
|
||||
return 0
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// Get the LinkAddr for the interface of the given name
|
||||
|
|
@ -384,13 +388,19 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||
maskAddr := &netroute.Inet4Addr{}
|
||||
routes := *t.Routes.Load()
|
||||
for _, r := range routes {
|
||||
if r.Via == nil || !r.Install {
|
||||
if !r.Via.IsValid() || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
||||
copy(routeAddr.IP[:], r.Cidr.IP.To4())
|
||||
copy(maskAddr.IP[:], net.IP(r.Cidr.Mask).To4())
|
||||
if !r.Cidr.Addr().Is4() {
|
||||
//TODO: implement ipv6
|
||||
panic("Cant handle ipv6 routes yet")
|
||||
}
|
||||
|
||||
routeAddr.IP = r.Cidr.Addr().As4()
|
||||
//TODO: we could avoid the copy
|
||||
copy(maskAddr.IP[:], prefixToMask(r.Cidr))
|
||||
|
||||
err := addRoute(routeSock, routeAddr, maskAddr, t.linkAddr)
|
||||
if err != nil {
|
||||
|
|
@ -435,8 +445,13 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||
continue
|
||||
}
|
||||
|
||||
copy(routeAddr.IP[:], r.Cidr.IP.To4())
|
||||
copy(maskAddr.IP[:], net.IP(r.Cidr.Mask).To4())
|
||||
if r.Cidr.Addr().Is6() {
|
||||
//TODO: implement ipv6
|
||||
panic("Cant handle ipv6 routes yet")
|
||||
}
|
||||
|
||||
routeAddr.IP = r.Cidr.Addr().As4()
|
||||
copy(maskAddr.IP[:], prefixToMask(r.Cidr))
|
||||
|
||||
err := delRoute(routeSock, routeAddr, maskAddr, t.linkAddr)
|
||||
if err != nil {
|
||||
|
|
@ -536,7 +551,7 @@ func (t *tun) Write(from []byte) (int, error) {
|
|||
return n - 4, err
|
||||
}
|
||||
|
||||
func (t *tun) Cidr() *net.IPNet {
|
||||
func (t *tun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
@ -547,3 +562,11 @@ func (t *tun) Name() string {
|
|||
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return nil, fmt.Errorf("TODO: multiqueue not implemented for darwin")
|
||||
}
|
||||
|
||||
func prefixToMask(prefix netip.Prefix) []byte {
|
||||
pLen := 128
|
||||
if prefix.Addr().Is4() {
|
||||
pLen = 32
|
||||
}
|
||||
return net.CIDRMask(prefix.Bits(), pLen)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package overlay
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/rcrowley/go-metrics"
|
||||
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
type disabledTun struct {
|
||||
read chan []byte
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
|
||||
// Track these metrics since we don't have the tun device to do it for us
|
||||
tx metrics.Counter
|
||||
|
|
@ -21,7 +21,7 @@ type disabledTun struct {
|
|||
l *logrus.Logger
|
||||
}
|
||||
|
||||
func newDisabledTun(cidr *net.IPNet, queueLen int, metricsEnabled bool, l *logrus.Logger) *disabledTun {
|
||||
func newDisabledTun(cidr netip.Prefix, queueLen int, metricsEnabled bool, l *logrus.Logger) *disabledTun {
|
||||
tun := &disabledTun{
|
||||
cidr: cidr,
|
||||
read: make(chan []byte, queueLen),
|
||||
|
|
@ -43,11 +43,11 @@ func (*disabledTun) Activate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (*disabledTun) RouteFor(iputil.VpnIp) iputil.VpnIp {
|
||||
return 0
|
||||
func (*disabledTun) RouteFor(addr netip.Addr) netip.Addr {
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
func (t *disabledTun) Cidr() *net.IPNet {
|
||||
func (t *disabledTun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
|
@ -17,10 +17,9 @@ import (
|
|||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
)
|
||||
|
||||
|
|
@ -48,10 +47,10 @@ type ifreqDestroy struct {
|
|||
|
||||
type tun struct {
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
MTU int
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
l *logrus.Logger
|
||||
|
||||
io.ReadWriteCloser
|
||||
|
|
@ -79,11 +78,11 @@ func (t *tun) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*tun, error) {
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ netip.Prefix) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in FreeBSD")
|
||||
}
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error) {
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, _ bool) (*tun, error) {
|
||||
// Try to open existing tun device
|
||||
var file *os.File
|
||||
var err error
|
||||
|
|
@ -174,7 +173,7 @@ func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error
|
|||
func (t *tun) Activate() error {
|
||||
var err error
|
||||
// TODO use syscalls instead of exec.Command
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String())
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
|
|
@ -233,12 +232,12 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *tun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *tun) Cidr() *net.IPNet {
|
||||
func (t *tun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +252,7 @@ func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
|||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
for _, r := range routes {
|
||||
if r.Via == nil || !r.Install {
|
||||
if !r.Via.IsValid() || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,32 +7,31 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
)
|
||||
|
||||
type tun struct {
|
||||
io.ReadWriteCloser
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
l *logrus.Logger
|
||||
}
|
||||
|
||||
func newTun(_ *config.C, _ *logrus.Logger, _ *net.IPNet, _ bool) (*tun, error) {
|
||||
func newTun(_ *config.C, _ *logrus.Logger, _ netip.Prefix, _ bool) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTun not supported in iOS")
|
||||
}
|
||||
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr *net.IPNet) (*tun, error) {
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr netip.Prefix) (*tun, error) {
|
||||
file := os.NewFile(uintptr(deviceFd), "/dev/tun")
|
||||
t := &tun{
|
||||
cidr: cidr,
|
||||
|
|
@ -80,8 +79,8 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *tun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +142,7 @@ func (tr *tunReadCloser) Close() error {
|
|||
return tr.f.Close()
|
||||
}
|
||||
|
||||
func (t *tun) Cidr() *net.IPNet {
|
||||
func (t *tun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,19 +4,18 @@
|
|||
package overlay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
|
|
@ -26,7 +25,7 @@ type tun struct {
|
|||
io.ReadWriteCloser
|
||||
fd int
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
MaxMTU int
|
||||
DefaultMTU int
|
||||
TXQueueLen int
|
||||
|
|
@ -34,7 +33,7 @@ type tun struct {
|
|||
ioctlFd uintptr
|
||||
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
routeChan chan struct{}
|
||||
useSystemRoutes bool
|
||||
|
||||
|
|
@ -65,7 +64,7 @@ type ifreqQLEN struct {
|
|||
pad [8]byte
|
||||
}
|
||||
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr *net.IPNet) (*tun, error) {
|
||||
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr netip.Prefix) (*tun, error) {
|
||||
file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
|
||||
|
||||
t, err := newTunGeneric(c, l, file, cidr)
|
||||
|
|
@ -78,7 +77,7 @@ func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, cidr *net.IPNet)
|
|||
return t, nil
|
||||
}
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, multiqueue bool) (*tun, error) {
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, multiqueue bool) (*tun, error) {
|
||||
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
// If /dev/net/tun doesn't exist, try to create it (will happen in docker)
|
||||
|
|
@ -123,7 +122,7 @@ func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, multiqueue bool) (*t
|
|||
return t, nil
|
||||
}
|
||||
|
||||
func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, cidr *net.IPNet) (*tun, error) {
|
||||
func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, cidr netip.Prefix) (*tun, error) {
|
||||
t := &tun{
|
||||
ReadWriteCloser: file,
|
||||
fd: int(file.Fd()),
|
||||
|
|
@ -231,8 +230,8 @@ func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
|||
return file, nil
|
||||
}
|
||||
|
||||
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *tun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
@ -275,8 +274,10 @@ func (t *tun) Activate() error {
|
|||
|
||||
var addr, mask [4]byte
|
||||
|
||||
copy(addr[:], t.cidr.IP.To4())
|
||||
copy(mask[:], t.cidr.Mask)
|
||||
//TODO: IPV6-WORK
|
||||
addr = t.cidr.Addr().As4()
|
||||
tmask := net.CIDRMask(t.cidr.Bits(), 32)
|
||||
copy(mask[:], tmask)
|
||||
|
||||
s, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
|
|
@ -364,14 +365,19 @@ func (t *tun) setMTU() {
|
|||
|
||||
func (t *tun) setDefaultRoute() error {
|
||||
// Default route
|
||||
dr := &net.IPNet{IP: t.cidr.IP.Mask(t.cidr.Mask), Mask: t.cidr.Mask}
|
||||
|
||||
dr := &net.IPNet{
|
||||
IP: t.cidr.Masked().Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(t.cidr.Bits(), t.cidr.Addr().BitLen()),
|
||||
}
|
||||
|
||||
nr := netlink.Route{
|
||||
LinkIndex: t.deviceIndex,
|
||||
Dst: dr,
|
||||
MTU: t.DefaultMTU,
|
||||
AdvMSS: t.advMSS(Route{}),
|
||||
Scope: unix.RT_SCOPE_LINK,
|
||||
Src: t.cidr.IP,
|
||||
Src: net.IP(t.cidr.Addr().AsSlice()),
|
||||
Protocol: unix.RTPROT_KERNEL,
|
||||
Table: unix.RT_TABLE_MAIN,
|
||||
Type: unix.RTN_UNICAST,
|
||||
|
|
@ -392,9 +398,14 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||
continue
|
||||
}
|
||||
|
||||
dr := &net.IPNet{
|
||||
IP: r.Cidr.Masked().Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),
|
||||
}
|
||||
|
||||
nr := netlink.Route{
|
||||
LinkIndex: t.deviceIndex,
|
||||
Dst: r.Cidr,
|
||||
Dst: dr,
|
||||
MTU: r.MTU,
|
||||
AdvMSS: t.advMSS(r),
|
||||
Scope: unix.RT_SCOPE_LINK,
|
||||
|
|
@ -426,9 +437,14 @@ func (t *tun) removeRoutes(routes []Route) {
|
|||
continue
|
||||
}
|
||||
|
||||
dr := &net.IPNet{
|
||||
IP: r.Cidr.Masked().Addr().AsSlice(),
|
||||
Mask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),
|
||||
}
|
||||
|
||||
nr := netlink.Route{
|
||||
LinkIndex: t.deviceIndex,
|
||||
Dst: r.Cidr,
|
||||
Dst: dr,
|
||||
MTU: r.MTU,
|
||||
AdvMSS: t.advMSS(r),
|
||||
Scope: unix.RT_SCOPE_LINK,
|
||||
|
|
@ -447,7 +463,7 @@ func (t *tun) removeRoutes(routes []Route) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *tun) Cidr() *net.IPNet {
|
||||
func (t *tun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
@ -499,7 +515,15 @@ func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
|||
return
|
||||
}
|
||||
|
||||
if !t.cidr.Contains(r.Gw) {
|
||||
//TODO: IPV6-WORK what if not ok?
|
||||
gwAddr, ok := netip.AddrFromSlice(r.Gw)
|
||||
if !ok {
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, invalid gateway address")
|
||||
return
|
||||
}
|
||||
|
||||
gwAddr = gwAddr.Unmap()
|
||||
if !t.cidr.Contains(gwAddr) {
|
||||
// Gateway isn't in our overlay network, ignore
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, not in our network")
|
||||
return
|
||||
|
|
@ -511,28 +535,25 @@ func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
|||
return
|
||||
}
|
||||
|
||||
newTree := cidr.NewTree4[iputil.VpnIp]()
|
||||
if r.Type == unix.RTM_NEWROUTE {
|
||||
for _, oldR := range t.routeTree.Load().List() {
|
||||
newTree.AddCIDR(oldR.CIDR, oldR.Value)
|
||||
}
|
||||
|
||||
t.l.WithField("destination", r.Dst).WithField("via", r.Gw).Info("Adding route")
|
||||
newTree.AddCIDR(r.Dst, iputil.Ip2VpnIp(r.Gw))
|
||||
|
||||
} else {
|
||||
gw := iputil.Ip2VpnIp(r.Gw)
|
||||
for _, oldR := range t.routeTree.Load().List() {
|
||||
if bytes.Equal(oldR.CIDR.IP, r.Dst.IP) && bytes.Equal(oldR.CIDR.Mask, r.Dst.Mask) && oldR.Value == gw {
|
||||
// This is the record to delete
|
||||
t.l.WithField("destination", r.Dst).WithField("via", r.Gw).Info("Removing route")
|
||||
continue
|
||||
}
|
||||
|
||||
newTree.AddCIDR(oldR.CIDR, oldR.Value)
|
||||
}
|
||||
dstAddr, ok := netip.AddrFromSlice(r.Dst.IP)
|
||||
if !ok {
|
||||
t.l.WithField("route", r).Debug("Ignoring route update, invalid destination address")
|
||||
return
|
||||
}
|
||||
|
||||
ones, _ := r.Dst.Mask.Size()
|
||||
dst := netip.PrefixFrom(dstAddr, ones)
|
||||
|
||||
newTree := t.routeTree.Load().Clone()
|
||||
|
||||
if r.Type == unix.RTM_NEWROUTE {
|
||||
t.l.WithField("destination", r.Dst).WithField("via", r.Gw).Info("Adding route")
|
||||
newTree.Insert(dst, gwAddr)
|
||||
|
||||
} else {
|
||||
newTree.Delete(dst)
|
||||
t.l.WithField("destination", r.Dst).WithField("via", r.Gw).Info("Removing route")
|
||||
}
|
||||
t.routeTree.Store(newTree)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package overlay
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
|
|
@ -15,10 +15,9 @@ import (
|
|||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
)
|
||||
|
||||
|
|
@ -29,10 +28,10 @@ type ifreqDestroy struct {
|
|||
|
||||
type tun struct {
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
MTU int
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
l *logrus.Logger
|
||||
|
||||
io.ReadWriteCloser
|
||||
|
|
@ -59,13 +58,13 @@ func (t *tun) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*tun, error) {
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ netip.Prefix) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in NetBSD")
|
||||
}
|
||||
|
||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error) {
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, _ bool) (*tun, error) {
|
||||
// Try to open tun device
|
||||
var file *os.File
|
||||
var err error
|
||||
|
|
@ -109,13 +108,13 @@ func (t *tun) Activate() error {
|
|||
var err error
|
||||
|
||||
// TODO use syscalls instead of exec.Command
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String())
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("/sbin/route", "-n", "add", "-net", t.cidr.String(), t.cidr.IP.String())
|
||||
cmd = exec.Command("/sbin/route", "-n", "add", "-net", t.cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'route add': %s", err)
|
||||
|
|
@ -168,12 +167,12 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *tun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *tun) Cidr() *net.IPNet {
|
||||
func (t *tun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
@ -188,12 +187,12 @@ func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
|||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
for _, r := range routes {
|
||||
if r.Via == nil || !r.Install {
|
||||
if !r.Via.IsValid() || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), t.cidr.IP.String())
|
||||
cmd := exec.Command("/sbin/route", "-n", "add", "-net", r.Cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
retErr := util.NewContextualError("failed to run 'route add' for unsafe_route", map[string]interface{}{"route": r}, err)
|
||||
|
|
@ -214,7 +213,7 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("/sbin/route", "-n", "delete", "-net", r.Cidr.String(), t.cidr.IP.String())
|
||||
cmd := exec.Command("/sbin/route", "-n", "delete", "-net", r.Cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package overlay
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
|
|
@ -14,19 +14,18 @@ import (
|
|||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
)
|
||||
|
||||
type tun struct {
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
MTU int
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
l *logrus.Logger
|
||||
|
||||
io.ReadWriteCloser
|
||||
|
|
@ -43,13 +42,13 @@ func (t *tun) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*tun, error) {
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ netip.Prefix) (*tun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in OpenBSD")
|
||||
}
|
||||
|
||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*tun, error) {
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, _ bool) (*tun, error) {
|
||||
deviceName := c.GetString("tun.dev", "")
|
||||
if deviceName == "" {
|
||||
return nil, fmt.Errorf("a device name in the format of tunN must be specified")
|
||||
|
|
@ -127,7 +126,7 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||
func (t *tun) Activate() error {
|
||||
var err error
|
||||
// TODO use syscalls instead of exec.Command
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.IP.String())
|
||||
cmd := exec.Command("/sbin/ifconfig", t.Device, t.cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
|
|
@ -139,7 +138,7 @@ func (t *tun) Activate() error {
|
|||
return fmt.Errorf("failed to run 'ifconfig': %s", err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("/sbin/route", "-n", "add", "-inet", t.cidr.String(), t.cidr.IP.String())
|
||||
cmd = exec.Command("/sbin/route", "-n", "add", "-inet", t.cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run 'route add': %s", err)
|
||||
|
|
@ -149,20 +148,20 @@ func (t *tun) Activate() error {
|
|||
return t.addRoutes(false)
|
||||
}
|
||||
|
||||
func (t *tun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *tun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *tun) addRoutes(logErrors bool) error {
|
||||
routes := *t.Routes.Load()
|
||||
for _, r := range routes {
|
||||
if r.Via == nil || !r.Install {
|
||||
if !r.Via.IsValid() || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("/sbin/route", "-n", "add", "-inet", r.Cidr.String(), t.cidr.IP.String())
|
||||
cmd := exec.Command("/sbin/route", "-n", "add", "-inet", r.Cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
retErr := util.NewContextualError("failed to run 'route add' for unsafe_route", map[string]interface{}{"route": r}, err)
|
||||
|
|
@ -183,7 +182,7 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||
continue
|
||||
}
|
||||
|
||||
cmd := exec.Command("/sbin/route", "-n", "delete", "-inet", r.Cidr.String(), t.cidr.IP.String())
|
||||
cmd := exec.Command("/sbin/route", "-n", "delete", "-inet", r.Cidr.String(), t.cidr.Addr().String())
|
||||
t.l.Debug("command: ", cmd.String())
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||
|
|
@ -194,7 +193,7 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *tun) Cidr() *net.IPNet {
|
||||
func (t *tun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,21 +6,20 @@ package overlay
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
)
|
||||
|
||||
type TestTun struct {
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
Routes []Route
|
||||
routeTree *cidr.Tree4[iputil.VpnIp]
|
||||
routeTree *bart.Table[netip.Addr]
|
||||
l *logrus.Logger
|
||||
|
||||
closed atomic.Bool
|
||||
|
|
@ -28,7 +27,7 @@ type TestTun struct {
|
|||
TxPackets chan []byte // Packets transmitted outside by nebula
|
||||
}
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*TestTun, error) {
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, _ bool) (*TestTun, error) {
|
||||
_, routes, err := getAllRoutesFromConfig(c, cidr, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -49,7 +48,7 @@ func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*TestTun, e
|
|||
}, nil
|
||||
}
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (*TestTun, error) {
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ netip.Prefix) (*TestTun, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported")
|
||||
}
|
||||
|
||||
|
|
@ -87,8 +86,8 @@ func (t *TestTun) Get(block bool) []byte {
|
|||
// Below this is boilerplate implementation to make nebula actually work
|
||||
//********************************************************************************************************************//
|
||||
|
||||
func (t *TestTun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.MostSpecificContains(ip)
|
||||
func (t *TestTun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +95,7 @@ func (t *TestTun) Activate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *TestTun) Cidr() *net.IPNet {
|
||||
func (t *TestTun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,30 +4,30 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
"github.com/songgao/water"
|
||||
)
|
||||
|
||||
type waterTun struct {
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
cidr netip.Prefix
|
||||
MTU int
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
l *logrus.Logger
|
||||
f *net.Interface
|
||||
*water.Interface
|
||||
}
|
||||
|
||||
func newWaterTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*waterTun, error) {
|
||||
func newWaterTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, _ bool) (*waterTun, error) {
|
||||
// NOTE: You cannot set the deviceName under Windows, so you must check tun.Device after calling .Activate()
|
||||
t := &waterTun{
|
||||
cidr: cidr,
|
||||
|
|
@ -70,8 +70,8 @@ func (t *waterTun) Activate() error {
|
|||
`C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "address",
|
||||
fmt.Sprintf("name=%s", t.Device),
|
||||
"source=static",
|
||||
fmt.Sprintf("addr=%s", t.cidr.IP),
|
||||
fmt.Sprintf("mask=%s", net.IP(t.cidr.Mask)),
|
||||
fmt.Sprintf("addr=%s", t.cidr.Addr()),
|
||||
fmt.Sprintf("mask=%s", net.CIDRMask(t.cidr.Bits(), t.cidr.Addr().BitLen())),
|
||||
"gateway=none",
|
||||
).Run()
|
||||
if err != nil {
|
||||
|
|
@ -141,7 +141,7 @@ func (t *waterTun) addRoutes(logErrors bool) error {
|
|||
// Path routes
|
||||
routes := *t.Routes.Load()
|
||||
for _, r := range routes {
|
||||
if r.Via == nil || !r.Install {
|
||||
if !r.Via.IsValid() || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
|
@ -182,12 +182,12 @@ func (t *waterTun) removeRoutes(routes []Route) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *waterTun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *waterTun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *waterTun) Cidr() *net.IPNet {
|
||||
func (t *waterTun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ package overlay
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
|
@ -15,11 +15,11 @@ import (
|
|||
"github.com/slackhq/nebula/config"
|
||||
)
|
||||
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ *net.IPNet) (Device, error) {
|
||||
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ netip.Prefix) (Device, error) {
|
||||
return nil, fmt.Errorf("newTunFromFd not supported in Windows")
|
||||
}
|
||||
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, multiqueue bool) (Device, error) {
|
||||
func newTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, multiqueue bool) (Device, error) {
|
||||
useWintun := true
|
||||
if err := checkWinTunExists(); err != nil {
|
||||
l.WithError(err).Warn("Check Wintun driver failed, fallback to wintap driver")
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@ import (
|
|||
"crypto"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/cidr"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
"github.com/slackhq/nebula/util"
|
||||
"github.com/slackhq/nebula/wintun"
|
||||
"golang.org/x/sys/windows"
|
||||
|
|
@ -23,11 +21,10 @@ const tunGUIDLabel = "Fixed Nebula Windows GUID v1"
|
|||
|
||||
type winTun struct {
|
||||
Device string
|
||||
cidr *net.IPNet
|
||||
prefix netip.Prefix
|
||||
cidr netip.Prefix
|
||||
MTU int
|
||||
Routes atomic.Pointer[[]Route]
|
||||
routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
|
||||
routeTree atomic.Pointer[bart.Table[netip.Addr]]
|
||||
l *logrus.Logger
|
||||
|
||||
tun *wintun.NativeTun
|
||||
|
|
@ -52,22 +49,16 @@ func generateGUIDByDeviceName(name string) (*windows.GUID, error) {
|
|||
return (*windows.GUID)(unsafe.Pointer(&sum[0])), nil
|
||||
}
|
||||
|
||||
func newWinTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*winTun, error) {
|
||||
func newWinTun(c *config.C, l *logrus.Logger, cidr netip.Prefix, _ bool) (*winTun, error) {
|
||||
deviceName := c.GetString("tun.dev", "")
|
||||
guid, err := generateGUIDByDeviceName(deviceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate GUID failed: %w", err)
|
||||
}
|
||||
|
||||
prefix, err := iputil.ToNetIpPrefix(*cidr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &winTun{
|
||||
Device: deviceName,
|
||||
cidr: cidr,
|
||||
prefix: prefix,
|
||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||
l: l,
|
||||
}
|
||||
|
|
@ -140,7 +131,7 @@ func (t *winTun) reload(c *config.C, initial bool) error {
|
|||
func (t *winTun) Activate() error {
|
||||
luid := winipcfg.LUID(t.tun.LUID())
|
||||
|
||||
err := luid.SetIPAddresses([]netip.Prefix{t.prefix})
|
||||
err := luid.SetIPAddresses([]netip.Prefix{t.cidr})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set address: %w", err)
|
||||
}
|
||||
|
|
@ -159,24 +150,13 @@ func (t *winTun) addRoutes(logErrors bool) error {
|
|||
foundDefault4 := false
|
||||
|
||||
for _, r := range routes {
|
||||
if r.Via == nil || !r.Install {
|
||||
if !r.Via.IsValid() || !r.Install {
|
||||
// We don't allow route MTUs so only install routes with a via
|
||||
continue
|
||||
}
|
||||
|
||||
prefix, err := iputil.ToNetIpPrefix(*r.Cidr)
|
||||
if err != nil {
|
||||
retErr := util.NewContextualError("Failed to parse cidr to netip prefix, ignoring route", map[string]interface{}{"route": r}, err)
|
||||
if logErrors {
|
||||
retErr.Log(t.l)
|
||||
continue
|
||||
} else {
|
||||
return retErr
|
||||
}
|
||||
}
|
||||
|
||||
// Add our unsafe route
|
||||
err = luid.AddRoute(prefix, r.Via.ToNetIpAddr(), uint32(r.Metric))
|
||||
err := luid.AddRoute(r.Cidr, r.Via, uint32(r.Metric))
|
||||
if err != nil {
|
||||
retErr := util.NewContextualError("Failed to add route", map[string]interface{}{"route": r}, err)
|
||||
if logErrors {
|
||||
|
|
@ -190,7 +170,7 @@ func (t *winTun) addRoutes(logErrors bool) error {
|
|||
}
|
||||
|
||||
if !foundDefault4 {
|
||||
if ones, bits := r.Cidr.Mask.Size(); ones == 0 && bits != 0 {
|
||||
if r.Cidr.Bits() == 0 && r.Cidr.Addr().BitLen() == 32 {
|
||||
foundDefault4 = true
|
||||
}
|
||||
}
|
||||
|
|
@ -221,13 +201,7 @@ func (t *winTun) removeRoutes(routes []Route) error {
|
|||
continue
|
||||
}
|
||||
|
||||
prefix, err := iputil.ToNetIpPrefix(*r.Cidr)
|
||||
if err != nil {
|
||||
t.l.WithError(err).WithField("route", r).Info("Failed to convert cidr to netip prefix")
|
||||
continue
|
||||
}
|
||||
|
||||
err = luid.DeleteRoute(prefix, r.Via.ToNetIpAddr())
|
||||
err := luid.DeleteRoute(r.Cidr, r.Via)
|
||||
if err != nil {
|
||||
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||
} else {
|
||||
|
|
@ -237,12 +211,12 @@ func (t *winTun) removeRoutes(routes []Route) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *winTun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
|
||||
_, r := t.routeTree.Load().MostSpecificContains(ip)
|
||||
func (t *winTun) RouteFor(ip netip.Addr) netip.Addr {
|
||||
r, _ := t.routeTree.Load().Lookup(ip)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *winTun) Cidr() *net.IPNet {
|
||||
func (t *winTun) Cidr() netip.Prefix {
|
||||
return t.cidr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,17 @@ package overlay
|
|||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula/config"
|
||||
"github.com/slackhq/nebula/iputil"
|
||||
)
|
||||
|
||||
func NewUserDeviceFromConfig(c *config.C, l *logrus.Logger, tunCidr *net.IPNet, routines int) (Device, error) {
|
||||
func NewUserDeviceFromConfig(c *config.C, l *logrus.Logger, tunCidr netip.Prefix, routines int) (Device, error) {
|
||||
return NewUserDevice(tunCidr)
|
||||
}
|
||||
|
||||
func NewUserDevice(tunCidr *net.IPNet) (Device, error) {
|
||||
func NewUserDevice(tunCidr netip.Prefix) (Device, error) {
|
||||
// these pipes guarantee each write/read will match 1:1
|
||||
or, ow := io.Pipe()
|
||||
ir, iw := io.Pipe()
|
||||
|
|
@ -27,7 +26,7 @@ func NewUserDevice(tunCidr *net.IPNet) (Device, error) {
|
|||
}
|
||||
|
||||
type UserDevice struct {
|
||||
tunCidr *net.IPNet
|
||||
tunCidr netip.Prefix
|
||||
|
||||
outboundReader *io.PipeReader
|
||||
outboundWriter *io.PipeWriter
|
||||
|
|
@ -39,9 +38,9 @@ type UserDevice struct {
|
|||
func (d *UserDevice) Activate() error {
|
||||
return nil
|
||||
}
|
||||
func (d *UserDevice) Cidr() *net.IPNet { return d.tunCidr }
|
||||
func (d *UserDevice) Name() string { return "faketun0" }
|
||||
func (d *UserDevice) RouteFor(ip iputil.VpnIp) iputil.VpnIp { return ip }
|
||||
func (d *UserDevice) Cidr() netip.Prefix { return d.tunCidr }
|
||||
func (d *UserDevice) Name() string { return "faketun0" }
|
||||
func (d *UserDevice) RouteFor(ip netip.Addr) netip.Addr { return ip }
|
||||
func (d *UserDevice) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue