Compare commits

...

9 Commits
v0.1.4 ... main

20 changed files with 1528 additions and 499 deletions

1
.gitignore vendored
View File

@ -3,5 +3,6 @@ CHANGELOG.md
.cache
virtual-vxlan.exe
virtual-vxlan
virtual-vxlan.log
dist
sysroot/linux*

View File

@ -2,7 +2,7 @@ VERSION ?= $(shell git describe --tags `git rev-list --tags --max-count=1`)
GITREV = $(shell git rev-parse --short HEAD)
BUILDTIME = $(shell date +'%FT%TZ%z')
PACKAGE_NAME := github.com/grmrgecko/virtual-vxlan
GO_BUILDER_VERSION ?= 1.23
GO_BUILDER_VERSION ?= 1.24
.PHONY: default
default: build ;

104
config.go
View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"io"
"net"
"net/netip"
"os"
@ -30,6 +31,7 @@ type Config struct {
type LogConfig struct {
Level string `fig:"level" yaml:"level" enum:"debug,info,warn,error" default:"info"`
Type string `fig:"type" yaml:"type" enum:"json,console" default:"console"`
Path string `fig:"path" yaml:"path" default:""`
}
// Configuration for updating.
@ -55,13 +57,14 @@ type ListenerConfig struct {
// Interface configuration strucuture.
type InterfaceConfig struct {
Name string `fig:"name" yaml:"name"`
VNI uint32 `fig:"vni" yaml:"vni"`
MTU int `fig:"mtu" yaml:"mtu"`
MACAddress string `fig:"mac_address" yaml:"mac_address"`
IPAddressCIDRS []string `fig:"ip_addresess_cidrs" yaml:"ip_addresess_cidrs"`
ARPEntries []ARPEntryConfig `fig:"arp_entries" yaml:"arp_entries"`
MACEntries []MACEntryConfig `fig:"mac_entries" yaml:"mac_entries"`
Name string `fig:"name" yaml:"name"`
VNI uint32 `fig:"vni" yaml:"vni"`
MTU int `fig:"mtu" yaml:"mtu"`
MACAddress string `fig:"mac_address" yaml:"mac_address"`
IPAddressCIDRS []string `fig:"ip_addresess_cidrs" yaml:"ip_addresess_cidrs"`
ARPEntries []ARPEntryConfig `fig:"arp_entries" yaml:"arp_entries"`
MACEntries []MACEntryConfig `fig:"mac_entries" yaml:"mac_entries"`
StaticRoutes []StaticRouteConfig `fig:"static_routes" yaml:"static_routes"`
}
// Permanent ARP entries.
@ -76,11 +79,18 @@ type MACEntryConfig struct {
Destination string `fig:"destination" yaml:"destination"`
}
// Permanent static routes.
type StaticRouteConfig struct {
Destination string `fig:"destination" yaml:"destination"`
Gateway string `fig:"gateway" yaml:"gateway"`
Metric int `fig:"metric" yaml:"metric"`
}
// Applies common filters to the read configuration.
func (c *Config) ApplyFilters(configDir string) {
// If the RPC path isn't set, set it to temp dir.
if c.RPCPath == "" {
c.RPCPath = filepath.Join(configDir, "virtual-vxlan.sock")
c.RPCPath = filepath.Join(configDir, fmt.Sprintf("%s.sock", serviceName))
}
// Check if the RPC socket already exists.
_, err := os.Stat(c.RPCPath)
@ -110,6 +120,14 @@ func ConfigPath() (fileDir, fileName string) {
if flags.ConfigPath != "" {
fileDir, fileName = filepath.Split(flags.ConfigPath)
}
// Verify directory exists.
if _, ferr := os.Stat(fileDir); ferr != nil {
err := os.MkdirAll(fileDir, 0755)
if err != nil {
log.Error("Failed to make directory:", err)
}
}
return
}
@ -377,6 +395,34 @@ func ApplyConfig(config *Config) (err error) {
}
i.AddMACEntry(mac, dst, true)
}
// Flush the route table of any permanent entry.
i.tables.Lock()
found = true
for found {
found = false
for _, ent := range i.tables.route {
if ent.Permanent {
found = true
i.RemoveStaticRoute(ent.Destination, ent.Gateway)
break
}
}
}
i.tables.Unlock()
// Add permanent routes from this config.
for _, ent := range iface.StaticRoutes {
destination, err := netip.ParsePrefix(ent.Destination)
if err != nil {
return fmt.Errorf("failed to parse destination prefix %s: %v", ent.Destination, err)
}
gateway, err := netip.ParseAddr(ent.Gateway)
if err != nil {
return fmt.Errorf("failed to parse gateway %s: %v", ent.Gateway, err)
}
i.AddStaticRoute(destination, gateway, ent.Metric, true)
}
}
}
@ -459,6 +505,18 @@ func SaveConfig() error {
}
}
// Get and add permanent static routes to the config.
for _, ent := range iface.GetStaticRoutes() {
if ent.Permanent {
route := StaticRouteConfig{
Destination: ent.Destination.String(),
Gateway: ent.Gateway.String(),
Metric: ent.Metric,
}
ifce.StaticRoutes = append(ifce.StaticRoutes, route)
}
}
// Add this interface config to the listener config.
listnr.Interfaces = append(listnr.Interfaces, ifce)
}
@ -479,20 +537,14 @@ func SaveConfig() error {
// Find the file name.
fileDir, fileName := ConfigPath()
// Verify directory exists.
if _, ferr := os.Stat(fileDir); ferr != nil {
err = os.MkdirAll(fileDir, 0755)
if err != nil {
log.Error("Failed to make directory:", err)
}
}
// Write the configuration file.
err = os.WriteFile(filepath.Join(fileDir, fileName), data, 0644)
return err
}
// Apply log config.
func (l *LogConfig) Apply() {
// Apply level.
switch l.Level {
case "debug":
log.SetLevel(log.DebugLevel)
@ -503,10 +555,30 @@ func (l *LogConfig) Apply() {
default:
log.SetLevel(log.ErrorLevel)
}
// Apply type.
switch l.Type {
case "json":
log.SetFormatter(&log.JSONFormatter{})
default:
log.SetFormatter(&log.TextFormatter{})
}
// If path isn't set, make it the executable location.
if l.Path == "" {
exe, err := os.Executable()
if err != nil {
panic(err)
}
l.Path = filepath.Join(filepath.Dir(exe), fmt.Sprintf("%s.log", serviceName))
}
// Set the log to save to the logpath.
f, err := os.OpenFile(l.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Println("Failed to open log file: %s %v", l.Path, err)
} else {
mw := io.MultiWriter(f, os.Stdout)
log.SetOutput(mw)
}
}

View File

@ -20,11 +20,20 @@ func (s *GRPCServer) SaveConfig(ctx context.Context, in *pb.Empty) (*pb.Empty, e
// Reload the configuration from the yaml file.
func (s *GRPCServer) ReloadConfig(ctx context.Context, in *pb.Empty) (*pb.Empty, error) {
log.Println("Reloading configurations.")
app.ApplyingConfig = true
config := ReadConfig()
err := ApplyConfig(config)
app.ApplyingConfig = false
if err != nil {
log.Println(err)
return nil, err
}
return new(pb.Empty), nil
}
// Check if the config is being applied.
func (s *GRPCServer) IsApplyingConfig(ctx context.Context, in *pb.Empty) (*pb.IsApplyingConfigReply, error) {
reply := new(pb.IsApplyingConfigReply)
reply.IsApplying = app.ApplyingConfig
return reply, nil
}

38
go.mod
View File

@ -3,28 +3,29 @@ module github.com/grmrgecko/virtual-vxlan
go 1.23.4
require (
github.com/alecthomas/kong v1.6.0
github.com/alecthomas/kong v1.8.1
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/creativeprojects/go-selfupdate v1.4.0
github.com/creativeprojects/go-selfupdate v1.4.1
github.com/google/gopacket v1.1.19
github.com/hashicorp/go-version v1.7.0
github.com/jedib0t/go-pretty/v6 v6.6.5
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/kardianos/service v1.2.2
github.com/kkyr/fig v0.4.0
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
github.com/sirupsen/logrus v1.9.3
github.com/vishvananda/netlink v1.3.0
golang.org/x/sys v0.29.0
golang.org/x/sys v0.31.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/windows v0.5.3
google.golang.org/grpc v1.69.2
google.golang.org/protobuf v1.36.1
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5
gopkg.in/yaml.v3 v3.0.1
)
require (
code.gitea.io/sdk/gitea v0.19.0 // indirect
code.gitea.io/sdk/gitea v0.20.0 // indirect
github.com/42wim/httpsig v1.2.2 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
@ -41,18 +42,17 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xanzy/go-gitlab v0.112.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/xanzy/go-gitlab v0.115.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

99
go.sum
View File

@ -1,18 +1,20 @@
code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y=
code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU=
code.gitea.io/sdk/gitea v0.20.0/go.mod h1:faouBHC/zyx5wLgjmRKR62ydyvMzwWf3QnU0bH7Cw6U=
github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA=
github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.6.0 h1:mwOzbdMR7uv2vul9J0FU3GYxE7ls/iX1ieMg5WIM6gE=
github.com/alecthomas/kong v1.6.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/kong v1.8.1 h1:6aamvWBE/REnR/BCq10EcozmcpUPc5aGI1lPAWdB0EE=
github.com/alecthomas/kong v1.8.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creativeprojects/go-selfupdate v1.4.0 h1:4ePPd2CPCNl/YoPXeVxpuBLDUZh8rMEKP5ac+1Y/r5c=
github.com/creativeprojects/go-selfupdate v1.4.0/go.mod h1:oPG7LmzEmS6OxfqEm620k5VKxP45xFZNKMkp4V5qqUY=
github.com/creativeprojects/go-selfupdate v1.4.1 h1:bgS6RThbKm9LnuRxGQwKOcu6RIOe/rsfWlDkWnkvkrU=
github.com/creativeprojects/go-selfupdate v1.4.1/go.mod h1:BRTk+0jZWjqB2Sq0sh9P6MktiieRtoOMpawYDX3+nX0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -52,8 +54,8 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo=
github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs=
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kkyr/fig v0.4.0 h1:4D/g72a8ij1fgRypuIbEoqIT7ukf2URVBtE777/gkbc=
@ -97,42 +99,45 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xanzy/go-gitlab v0.112.0 h1:6Z0cqEooCvBMfBIHw+CgO4AKGRV8na/9781xOb0+DKw=
github.com/xanzy/go-gitlab v0.112.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xanzy/go-gitlab v0.115.0 h1:6DmtItNcVe+At/liXSgfE/DZNZrGfalQmBRmOcJjOn8=
github.com/xanzy/go-gitlab v0.115.0/go.mod h1:5XCDtM7AM6WMKmfDdOiEpyRWUqui2iS9ILfvCZ2gJ5M=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -140,21 +145,21 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
@ -164,12 +169,12 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQX
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"math"
"math/rand"
"net"
"net/netip"
@ -27,6 +28,14 @@ type ArpEntry struct {
Updating bool
}
// Static Route.
type StaticRoute struct {
Destination netip.Prefix
Gateway netip.Addr
Metric int
Permanent bool
}
// MAC Table Entry.
type MacEntry struct {
MAC net.HardwareAddr
@ -57,6 +66,7 @@ type Interface struct {
tables struct {
arp []ArpEntry
arpEvent map[netip.Addr][]chan net.HardwareAddr
route []StaticRoute
mac []MacEntry
sync.RWMutex
}
@ -392,6 +402,83 @@ func (i *Interface) GetDestinationFor(mac net.HardwareAddr) *net.UDPAddr {
return nil
}
// Add static route to interface.
func (i *Interface) AddStaticRoute(destination netip.Prefix, gateway netip.Addr, metric int, perm bool) error {
// Prevent concurrent table modifications.
i.tables.Lock()
defer i.tables.Unlock()
i.tun.Lock()
defer i.tun.Unlock()
// Default to 256 metric.
if metric == 0 {
metric = 256
}
// Try adding via tunnel device.
err := i.tun.device.AddRoute(destination, gateway, metric)
if err != nil {
return err
}
// If route successfully added, add to route table.
route := StaticRoute{
Destination: destination,
Gateway: gateway,
Metric: metric,
Permanent: perm,
}
i.tables.route = append(i.tables.route, route)
return nil
}
// Remove static route from interface.
func (i *Interface) RemoveStaticRoute(destination netip.Prefix, gateway netip.Addr) error {
// Prevent concurrent table modifications.
i.tables.Lock()
defer i.tables.Unlock()
i.tun.Lock()
defer i.tun.Unlock()
// Remove from device first.
err := i.tun.device.RemoveRoute(destination, gateway)
if err != nil {
return err
}
// Remove from table.
for r, route := range i.tables.route {
// If route matches, remove it.
if route.Destination == destination && route.Gateway == gateway {
i.tables.route = append(i.tables.route[:r], i.tables.route[r+1:]...)
break
}
}
return nil
}
// Returns the static route table.
func (i *Interface) GetStaticRoutes() []StaticRoute {
i.tables.RLock()
defer i.tables.RUnlock()
return i.tables.route
}
// Find gateway for IP.
func (i *Interface) GetGateway(destination netip.Addr) (gateway netip.Addr) {
// Get read lock on table.
i.tables.RLock()
defer i.tables.RUnlock()
metric := math.MaxInt
for _, route := range i.tables.route {
if route.Destination.Contains(destination) && route.Metric < metric {
gateway = route.Gateway
metric = route.Metric
}
}
return
}
// Add an ARP entry that is static.
func (i *Interface) AddStaticARPEntry(addr netip.Addr, mac net.HardwareAddr, perm bool) {
// Prevent concurrent table modifications.
@ -598,6 +685,13 @@ func (i *Interface) updateARPFor(addr netip.Addr, brdMac net.HardwareAddr, ifceA
// Find the MAC address for an IP address from the ARP table.
// If an entry doesn't exist, we will attempt to request it.
func (i *Interface) GetMacFor(addr netip.Addr) (net.HardwareAddr, error) {
// First, check if a static route defines a replacement address.
gateway := i.GetGateway(addr)
// If an gateway is defined, replace the requested address with the gateway.
if gateway.IsValid() {
addr = gateway
}
// Lots of math depending on is4.
is4 := addr.Is4()

View File

@ -429,7 +429,119 @@ func (a *InterfaceFlushMACTableCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err er
return
}
// Command to add an MAC entry to an interface.
// Command to add a static route to an interface.
type InterfaceAddStaticRouteCmd struct {
Destination string `help:"The CIDR of the destination network" required:""`
Gateway string `help:"The IP address to route traffic via." required:""`
Metric int `help:"Metric value to set route priority." required:""`
Permanent bool `help:"Should the static route be saved to disk?"`
}
func (a *InterfaceAddStaticRouteCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err error) {
// Connect to GRPC.
c, conn, err := NewGRPCClient()
if err != nil {
return
}
defer conn.Close()
// Setup call timeout of 10 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Attempt to add static route to an interface.
ifce := &pb.InterfaceAddStaticRouteRequest{
ListenerName: l.name(),
Name: i.name(),
Destination: a.Destination,
Gateway: a.Gateway,
Metric: int32(a.Metric),
Permanent: a.Permanent,
}
_, err = c.InterfaceAddStaticRoute(ctx, ifce)
if err != nil {
return
}
fmt.Println("Added static route.")
return
}
// Command to remove a static route from an interface.
type InterfaceRemoveStaticRouteCmd struct {
Destination string `help:"The CIDR of the destination network" required:""`
Gateway string `help:"The IP address to route traffic via." required:""`
}
func (a *InterfaceRemoveStaticRouteCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err error) {
// Connect to GRPC.
c, conn, err := NewGRPCClient()
if err != nil {
return
}
defer conn.Close()
// Setup call timeout of 10 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Attempt to remove a static route from an interface.
ifce := &pb.InterfaceRemoveStaticRouteRequest{
ListenerName: l.name(),
Name: i.name(),
Destination: a.Destination,
Gateway: a.Gateway,
}
_, err = c.InterfaceRemoveStaticRoute(ctx, ifce)
if err != nil {
return
}
fmt.Println("Removed static route.")
return
}
// Command to get static routes on an interface.
type InterfaceGetStaticRoutesCmd struct{}
func (a *InterfaceGetStaticRoutesCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err error) {
// Connect to GRPC.
c, conn, err := NewGRPCClient()
if err != nil {
return
}
defer conn.Close()
// Setup call timeout of 10 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Attempt to get static routes on an interface.
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
r, err := c.InterfaceGetStaticRoutes(ctx, ifce)
if err != nil {
return
}
// Setup table for static routes.
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Destination", "Gateway", "Metric", "Permanent"})
// Add rows for entries.
for _, ent := range r.Routes {
t.AppendRow([]interface{}{ent.Destination, ent.Gateway, ent.Metric, ent.Permanent})
}
// Render the table.
t.Render()
return
}
// Command to add an ARP entry to an interface.
type InterfaceAddStaticARPEntryCmd struct {
Address string `help:"IP address" required:""`
MAC string `help:"MAC address" required:""`
@ -465,7 +577,7 @@ func (a *InterfaceAddStaticARPEntryCmd) Run(l *ListenerCmd, i *InterfaceCmd) (er
return
}
// Command to remove an MAC entry from an interface.
// Command to remove an ARP entry from an interface.
type InterfaceRemoveARPEntryCmd struct {
Address string `help:"IP address" required:""`
}
@ -497,7 +609,7 @@ func (a *InterfaceRemoveARPEntryCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err e
return
}
// Command to get MAC entries on an interface.
// Command to get ARP entries on an interface.
type InterfaceGetARPEntriesCmd struct{}
func (a *InterfaceGetARPEntriesCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err error) {
@ -537,7 +649,7 @@ func (a *InterfaceGetARPEntriesCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err er
return
}
// Command to remove all mac entries from an interface.
// Command to remove all ARP entries from an interface.
type InterfaceFlushARPTableCmd struct{}
func (a *InterfaceFlushARPTableCmd) Run(l *ListenerCmd, i *InterfaceCmd) (err error) {
@ -582,6 +694,9 @@ type InterfaceCmd struct {
RemoveMACEntry InterfaceRemoveMACEntryCmd `cmd:""`
GetMACEntries InterfaceGetMACEntriesCmd `cmd:""`
FlushMACTable InterfaceFlushMACTableCmd `cmd:""`
AddStaticRoute InterfaceAddStaticRouteCmd `cmd:""`
RemoveStaticRoute InterfaceRemoveStaticRouteCmd `cmd:""`
GetStaticRoutes InterfaceGetStaticRoutesCmd `cmd:""`
AddStaticARPEntry InterfaceAddStaticARPEntryCmd `cmd:""`
RemoveARPEntry InterfaceRemoveARPEntryCmd `cmd:""`
GetARPEntries InterfaceGetARPEntriesCmd `cmd:""`

View File

@ -310,6 +310,88 @@ func (s *GRPCServer) InterfaceFlushMACTable(ctx context.Context, in *pb.Interfac
return new(pb.Empty), nil
}
// Add static route to an interface.
func (s *GRPCServer) InterfaceAddStaticRoute(ctx context.Context, in *pb.InterfaceAddStaticRouteRequest) (*pb.Empty, error) {
// Parse destination prefix.
dst, err := netip.ParsePrefix(in.Destination)
if err != nil {
return nil, fmt.Errorf("failed to parse destination prefix %s: %v", in.Destination, err)
}
// Parse gateway.
gateway, err := netip.ParseAddr(in.Gateway)
if err != nil {
return nil, fmt.Errorf("failed to parse gateway %s: %v", in.Gateway, err)
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Add the static route.
err = ifce.AddStaticRoute(dst, gateway, int(in.Metric), in.Permanent)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Remove static route from an interface.
func (s *GRPCServer) InterfaceRemoveStaticRoute(ctx context.Context, in *pb.InterfaceRemoveStaticRouteRequest) (*pb.Empty, error) {
// Parse destination prefix.
dst, err := netip.ParsePrefix(in.Destination)
if err != nil {
return nil, fmt.Errorf("failed to parse destination prefix %s: %v", in.Destination, err)
}
// Parse gateway.
gateway, err := netip.ParseAddr(in.Gateway)
if err != nil {
return nil, fmt.Errorf("failed to parse gateway %s: %v", in.Gateway, err)
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Remove the static route.
err = ifce.RemoveStaticRoute(dst, gateway)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Get static routes on interface.
func (s *GRPCServer) InterfaceGetStaticRoutes(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.InterfaceStaticRouteReply, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Get static routes and make reply.
routes := ifce.GetStaticRoutes()
reply := new(pb.InterfaceStaticRouteReply)
for _, route := range routes {
ent := &pb.StaticRoute{
Destination: route.Destination.String(),
Gateway: route.Gateway.String(),
Metric: int32(route.Metric),
Permanent: route.Permanent,
}
reply.Routes = append(reply.Routes, ent)
}
return reply, nil
}
// Add static ARP entry to an interface.
func (s *GRPCServer) InterfaceAddStaticARPEntry(ctx context.Context, in *pb.InterfaceARPEntryRequest) (*pb.Empty, error) {
// Parse IP address

View File

@ -36,16 +36,6 @@ type Listener struct {
log *log.Entry
}
// Check if IP address is all zero.
func isZeroAddr(ip net.IP) bool {
for _, b := range ip {
if b != 0x0 {
return false
}
}
return true
}
// Make a new listener on the specified address. This
// listener is added to the app listener list, and errors
// on existing listeners for the specified address.

View File

@ -6,7 +6,7 @@ const (
serviceDisplayName = "Virtual VXLAN"
serviceVendor = "com.mrgeckosmedia"
serviceDescription = "Virtual VXLAN using TUN interfaces"
serviceVersion = "0.1.4"
serviceVersion = "0.2.2"
defaultConfigFile = "config.yaml"
)

View File

@ -7,6 +7,7 @@ import (
"net"
"strings"
"syscall"
"time"
"unsafe"
"github.com/google/gopacket/pcap"
@ -121,7 +122,25 @@ func (p *Promiscuous) tryICMPListen(ifaceIP net.IP) (err error) {
}
// Use listen packet to start a connection.
p.conn, err = cfg.ListenPacket(context.Background(), network, ifaceIP.String())
tries := 0
for {
p.conn, err = cfg.ListenPacket(context.Background(), network, ifaceIP.String())
if err == nil {
break
}
// If the bind address wasn't found on an interface, try again for 5 minutes.
tries++
if tries < 5 {
log.Printf("Error putting interface in promiscuous mode, trying again: %v", err)
time.Sleep(time.Minute)
} else {
// If we passed 5 minutes, we should stop...
break
}
}
// If we failed too many times, stop.
if err != nil {
return
}

View File

@ -24,6 +24,7 @@ type App struct {
}
ControllerMac net.HardwareAddr
grpcServer *GRPCServer
ApplyingConfig bool
Stop chan struct{}
UpdateConfig *UpdateConfig
}
@ -41,18 +42,27 @@ func (a *ServerCmd) Run() error {
config := ReadConfig()
app.UpdateConfig = config.Update
// So that other services interacting can confirm the config is applied prior to working.
app.ApplyingConfig = true
// Start the GRPC server for cli communication.
_, err := NewGRPCServer(config.RPCPath)
if err != nil {
return err
}
// Apply the configuration read.
err = ApplyConfig(config)
// If error applying the config, we should fail.
if err != nil {
return err
}
// Apply the configuration in the background, to allow service start to notify
// the service managers fast.
go func() {
err = ApplyConfig(config)
// If error applying the config, log.
if err != nil {
log.Println("An error occurred applying configuration:", err)
}
// Other services may now work.
app.ApplyingConfig = false
}()
}
// Send notification that the service is ready.

View File

@ -45,6 +45,12 @@ type Device interface {
// Get IP Addresses for the device.
GetIPAddresses() ([]netip.Prefix, error)
// Add static route.
AddRoute(destination netip.Prefix, gateway netip.Addr, metric int) error
// Remove static route.
RemoveRoute(destination netip.Prefix, gateway netip.Addr) error
// Events returns a channel of type Event, which is fed Device events.
Events() <-chan Event

View File

@ -11,6 +11,7 @@ package tun
import (
"errors"
"fmt"
"net"
"net/netip"
"os"
"sync"
@ -314,6 +315,41 @@ func (tun *NativeTun) GetIPAddresses() ([]netip.Prefix, error) {
return prefixes, nil
}
func (tun *NativeTun) AddRoute(destination netip.Prefix, gateway netip.Addr, metric int) error {
family := netlink.FAMILY_V4
if gateway.Is6() {
family = netlink.FAMILY_V6
}
staticRoute := &netlink.Route{
Family: family,
LinkIndex: int(tun.index),
Dst: &net.IPNet{
IP: destination.Addr().AsSlice(),
Mask: net.CIDRMask(destination.Bits(), destination.Addr().BitLen()),
},
Gw: gateway.AsSlice(),
Priority: metric,
}
return netlink.RouteAdd(staticRoute)
}
func (tun *NativeTun) RemoveRoute(destination netip.Prefix, gateway netip.Addr) error {
family := netlink.FAMILY_V4
if gateway.Is6() {
family = netlink.FAMILY_V6
}
staticRoute := &netlink.Route{
Family: family,
LinkIndex: int(tun.index),
Dst: &net.IPNet{
IP: destination.Addr().AsSlice(),
Mask: net.CIDRMask(destination.Bits(), destination.Addr().BitLen()),
},
Gw: gateway.AsSlice(),
}
return netlink.RouteDel(staticRoute)
}
func (tun *NativeTun) MTU() (int, error) {
name, err := tun.Name()
if err != nil {

View File

@ -315,6 +315,16 @@ func (tun *NativeTun) GetIPAddresses() ([]netip.Prefix, error) {
return prefixes, nil
}
func (tun *NativeTun) AddRoute(destination netip.Prefix, gateway netip.Addr, metric int) error {
luid := winipcfg.LUID(tun.LUID())
return luid.AddRoute(destination, gateway, uint32(metric))
}
func (tun *NativeTun) RemoveRoute(destination netip.Prefix, gateway netip.Addr) error {
luid := winipcfg.LUID(tun.LUID())
return luid.DeleteRoute(destination, gateway)
}
// RunningVersion returns the running version of the Wintun driver.
func (tun *NativeTun) RunningVersion() (version uint32, err error) {
return wintun.RunningVersion()

View File

@ -18,3 +18,13 @@ func generateRandomMAC() net.HardwareAddr {
return mac
}
// Check if IP address is all zero.
func isZeroAddr(ip net.IP) bool {
for _, b := range ip {
if b != 0x0 {
return false
}
}
return true
}

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ service vxlan {
// Config commands.
rpc SaveConfig (Empty) returns (Empty) {}
rpc ReloadConfig (Empty) returns (Empty) {}
rpc IsApplyingConfig (Empty) returns (IsApplyingConfigReply) {}
// Listener commands.
rpc ListListeners (Empty) returns (ListListenersReply) {}
@ -29,6 +30,9 @@ service vxlan {
rpc InterfaceRemoveMACEntry (InterfaceRemoveMacEntryRequest) returns (Empty) {}
rpc InterfaceGetMACEntries (InterfaceRequestWithName) returns (InterfaceMacEntryReply) {}
rpc InterfaceFlushMACTable (InterfaceRequestWithName) returns (Empty) {}
rpc InterfaceAddStaticRoute (InterfaceAddStaticRouteRequest) returns (Empty) {}
rpc InterfaceRemoveStaticRoute (InterfaceRemoveStaticRouteRequest) returns (Empty) {}
rpc InterfaceGetStaticRoutes (InterfaceRequestWithName) returns (InterfaceStaticRouteReply) {}
rpc InterfaceAddStaticARPEntry (InterfaceARPEntryRequest) returns (Empty) {}
rpc InterfaceRemoveARPEntry (InterfaceRemoveARPEntryRequest) returns (Empty) {}
rpc InterfaceGetARPEntries (InterfaceRequestWithName) returns (InterfaceArpEntryReply) {}
@ -39,6 +43,11 @@ service vxlan {
message Empty {
}
// Response to is applying config.
message IsApplyingConfigReply {
bool isApplying = 1;
}
// Listener messages.
message ListenerRequestWithName {
string name = 1;
@ -144,6 +153,33 @@ message InterfaceMacEntryReply {
repeated MacEntry entries = 1;
}
message InterfaceAddStaticRouteRequest {
string listenerName = 1;
string name = 2;
string destination = 3;
string gateway = 4;
int32 metric = 5;
bool permanent = 6;
}
message InterfaceRemoveStaticRouteRequest {
string listenerName = 1;
string name = 2;
string destination = 3;
string gateway = 4;
}
message StaticRoute {
string destination = 1;
string gateway = 2;
int32 metric = 3;
bool permanent = 4;
}
message InterfaceStaticRouteReply {
repeated StaticRoute routes = 1;
}
message InterfaceARPEntryRequest {
string listenerName = 1;
string name = 2;

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v3.21.12
// - protoc v5.29.2
// source: vxlan/vxlan.proto
package vxlan
@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9
const (
Vxlan_SaveConfig_FullMethodName = "/vxlan.vxlan/SaveConfig"
Vxlan_ReloadConfig_FullMethodName = "/vxlan.vxlan/ReloadConfig"
Vxlan_IsApplyingConfig_FullMethodName = "/vxlan.vxlan/IsApplyingConfig"
Vxlan_ListListeners_FullMethodName = "/vxlan.vxlan/ListListeners"
Vxlan_AddListener_FullMethodName = "/vxlan.vxlan/AddListener"
Vxlan_RemoveListener_FullMethodName = "/vxlan.vxlan/RemoveListener"
@ -39,6 +40,9 @@ const (
Vxlan_InterfaceRemoveMACEntry_FullMethodName = "/vxlan.vxlan/InterfaceRemoveMACEntry"
Vxlan_InterfaceGetMACEntries_FullMethodName = "/vxlan.vxlan/InterfaceGetMACEntries"
Vxlan_InterfaceFlushMACTable_FullMethodName = "/vxlan.vxlan/InterfaceFlushMACTable"
Vxlan_InterfaceAddStaticRoute_FullMethodName = "/vxlan.vxlan/InterfaceAddStaticRoute"
Vxlan_InterfaceRemoveStaticRoute_FullMethodName = "/vxlan.vxlan/InterfaceRemoveStaticRoute"
Vxlan_InterfaceGetStaticRoutes_FullMethodName = "/vxlan.vxlan/InterfaceGetStaticRoutes"
Vxlan_InterfaceAddStaticARPEntry_FullMethodName = "/vxlan.vxlan/InterfaceAddStaticARPEntry"
Vxlan_InterfaceRemoveARPEntry_FullMethodName = "/vxlan.vxlan/InterfaceRemoveARPEntry"
Vxlan_InterfaceGetARPEntries_FullMethodName = "/vxlan.vxlan/InterfaceGetARPEntries"
@ -52,6 +56,7 @@ type VxlanClient interface {
// Config commands.
SaveConfig(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
ReloadConfig(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
IsApplyingConfig(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*IsApplyingConfigReply, error)
// Listener commands.
ListListeners(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListListenersReply, error)
AddListener(ctx context.Context, in *Listener, opts ...grpc.CallOption) (*Empty, error)
@ -72,6 +77,9 @@ type VxlanClient interface {
InterfaceRemoveMACEntry(ctx context.Context, in *InterfaceRemoveMacEntryRequest, opts ...grpc.CallOption) (*Empty, error)
InterfaceGetMACEntries(ctx context.Context, in *InterfaceRequestWithName, opts ...grpc.CallOption) (*InterfaceMacEntryReply, error)
InterfaceFlushMACTable(ctx context.Context, in *InterfaceRequestWithName, opts ...grpc.CallOption) (*Empty, error)
InterfaceAddStaticRoute(ctx context.Context, in *InterfaceAddStaticRouteRequest, opts ...grpc.CallOption) (*Empty, error)
InterfaceRemoveStaticRoute(ctx context.Context, in *InterfaceRemoveStaticRouteRequest, opts ...grpc.CallOption) (*Empty, error)
InterfaceGetStaticRoutes(ctx context.Context, in *InterfaceRequestWithName, opts ...grpc.CallOption) (*InterfaceStaticRouteReply, error)
InterfaceAddStaticARPEntry(ctx context.Context, in *InterfaceARPEntryRequest, opts ...grpc.CallOption) (*Empty, error)
InterfaceRemoveARPEntry(ctx context.Context, in *InterfaceRemoveARPEntryRequest, opts ...grpc.CallOption) (*Empty, error)
InterfaceGetARPEntries(ctx context.Context, in *InterfaceRequestWithName, opts ...grpc.CallOption) (*InterfaceArpEntryReply, error)
@ -106,6 +114,16 @@ func (c *vxlanClient) ReloadConfig(ctx context.Context, in *Empty, opts ...grpc.
return out, nil
}
func (c *vxlanClient) IsApplyingConfig(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*IsApplyingConfigReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IsApplyingConfigReply)
err := c.cc.Invoke(ctx, Vxlan_IsApplyingConfig_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *vxlanClient) ListListeners(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListListenersReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListListenersReply)
@ -286,6 +304,36 @@ func (c *vxlanClient) InterfaceFlushMACTable(ctx context.Context, in *InterfaceR
return out, nil
}
func (c *vxlanClient) InterfaceAddStaticRoute(ctx context.Context, in *InterfaceAddStaticRouteRequest, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, Vxlan_InterfaceAddStaticRoute_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *vxlanClient) InterfaceRemoveStaticRoute(ctx context.Context, in *InterfaceRemoveStaticRouteRequest, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, Vxlan_InterfaceRemoveStaticRoute_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *vxlanClient) InterfaceGetStaticRoutes(ctx context.Context, in *InterfaceRequestWithName, opts ...grpc.CallOption) (*InterfaceStaticRouteReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InterfaceStaticRouteReply)
err := c.cc.Invoke(ctx, Vxlan_InterfaceGetStaticRoutes_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *vxlanClient) InterfaceAddStaticARPEntry(ctx context.Context, in *InterfaceARPEntryRequest, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
@ -333,6 +381,7 @@ type VxlanServer interface {
// Config commands.
SaveConfig(context.Context, *Empty) (*Empty, error)
ReloadConfig(context.Context, *Empty) (*Empty, error)
IsApplyingConfig(context.Context, *Empty) (*IsApplyingConfigReply, error)
// Listener commands.
ListListeners(context.Context, *Empty) (*ListListenersReply, error)
AddListener(context.Context, *Listener) (*Empty, error)
@ -353,6 +402,9 @@ type VxlanServer interface {
InterfaceRemoveMACEntry(context.Context, *InterfaceRemoveMacEntryRequest) (*Empty, error)
InterfaceGetMACEntries(context.Context, *InterfaceRequestWithName) (*InterfaceMacEntryReply, error)
InterfaceFlushMACTable(context.Context, *InterfaceRequestWithName) (*Empty, error)
InterfaceAddStaticRoute(context.Context, *InterfaceAddStaticRouteRequest) (*Empty, error)
InterfaceRemoveStaticRoute(context.Context, *InterfaceRemoveStaticRouteRequest) (*Empty, error)
InterfaceGetStaticRoutes(context.Context, *InterfaceRequestWithName) (*InterfaceStaticRouteReply, error)
InterfaceAddStaticARPEntry(context.Context, *InterfaceARPEntryRequest) (*Empty, error)
InterfaceRemoveARPEntry(context.Context, *InterfaceRemoveARPEntryRequest) (*Empty, error)
InterfaceGetARPEntries(context.Context, *InterfaceRequestWithName) (*InterfaceArpEntryReply, error)
@ -373,6 +425,9 @@ func (UnimplementedVxlanServer) SaveConfig(context.Context, *Empty) (*Empty, err
func (UnimplementedVxlanServer) ReloadConfig(context.Context, *Empty) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReloadConfig not implemented")
}
func (UnimplementedVxlanServer) IsApplyingConfig(context.Context, *Empty) (*IsApplyingConfigReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsApplyingConfig not implemented")
}
func (UnimplementedVxlanServer) ListListeners(context.Context, *Empty) (*ListListenersReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListListeners not implemented")
}
@ -427,6 +482,15 @@ func (UnimplementedVxlanServer) InterfaceGetMACEntries(context.Context, *Interfa
func (UnimplementedVxlanServer) InterfaceFlushMACTable(context.Context, *InterfaceRequestWithName) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method InterfaceFlushMACTable not implemented")
}
func (UnimplementedVxlanServer) InterfaceAddStaticRoute(context.Context, *InterfaceAddStaticRouteRequest) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method InterfaceAddStaticRoute not implemented")
}
func (UnimplementedVxlanServer) InterfaceRemoveStaticRoute(context.Context, *InterfaceRemoveStaticRouteRequest) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method InterfaceRemoveStaticRoute not implemented")
}
func (UnimplementedVxlanServer) InterfaceGetStaticRoutes(context.Context, *InterfaceRequestWithName) (*InterfaceStaticRouteReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method InterfaceGetStaticRoutes not implemented")
}
func (UnimplementedVxlanServer) InterfaceAddStaticARPEntry(context.Context, *InterfaceARPEntryRequest) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method InterfaceAddStaticARPEntry not implemented")
}
@ -496,6 +560,24 @@ func _Vxlan_ReloadConfig_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _Vxlan_IsApplyingConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(VxlanServer).IsApplyingConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Vxlan_IsApplyingConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(VxlanServer).IsApplyingConfig(ctx, req.(*Empty))
}
return interceptor(ctx, in, info, handler)
}
func _Vxlan_ListListeners_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty)
if err := dec(in); err != nil {
@ -820,6 +902,60 @@ func _Vxlan_InterfaceFlushMACTable_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _Vxlan_InterfaceAddStaticRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InterfaceAddStaticRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(VxlanServer).InterfaceAddStaticRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Vxlan_InterfaceAddStaticRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(VxlanServer).InterfaceAddStaticRoute(ctx, req.(*InterfaceAddStaticRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Vxlan_InterfaceRemoveStaticRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InterfaceRemoveStaticRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(VxlanServer).InterfaceRemoveStaticRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Vxlan_InterfaceRemoveStaticRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(VxlanServer).InterfaceRemoveStaticRoute(ctx, req.(*InterfaceRemoveStaticRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Vxlan_InterfaceGetStaticRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InterfaceRequestWithName)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(VxlanServer).InterfaceGetStaticRoutes(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Vxlan_InterfaceGetStaticRoutes_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(VxlanServer).InterfaceGetStaticRoutes(ctx, req.(*InterfaceRequestWithName))
}
return interceptor(ctx, in, info, handler)
}
func _Vxlan_InterfaceAddStaticARPEntry_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InterfaceARPEntryRequest)
if err := dec(in); err != nil {
@ -907,6 +1043,10 @@ var Vxlan_ServiceDesc = grpc.ServiceDesc{
MethodName: "ReloadConfig",
Handler: _Vxlan_ReloadConfig_Handler,
},
{
MethodName: "IsApplyingConfig",
Handler: _Vxlan_IsApplyingConfig_Handler,
},
{
MethodName: "ListListeners",
Handler: _Vxlan_ListListeners_Handler,
@ -979,6 +1119,18 @@ var Vxlan_ServiceDesc = grpc.ServiceDesc{
MethodName: "InterfaceFlushMACTable",
Handler: _Vxlan_InterfaceFlushMACTable_Handler,
},
{
MethodName: "InterfaceAddStaticRoute",
Handler: _Vxlan_InterfaceAddStaticRoute_Handler,
},
{
MethodName: "InterfaceRemoveStaticRoute",
Handler: _Vxlan_InterfaceRemoveStaticRoute_Handler,
},
{
MethodName: "InterfaceGetStaticRoutes",
Handler: _Vxlan_InterfaceGetStaticRoutes_Handler,
},
{
MethodName: "InterfaceAddStaticARPEntry",
Handler: _Vxlan_InterfaceAddStaticARPEntry_Handler,