First commit.

This commit is contained in:
GRMrGecko 2025-01-05 22:22:24 -06:00
commit fcddaabed2
47 changed files with 9415 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.release-env
virtual-vxlan.exe
virtual-vxlan
dist
sysroot/linux*

82
.goreleaser.yaml Normal file
View File

@ -0,0 +1,82 @@
version: 2
before:
hooks:
- go mod tidy
- go get golang.org/x/tools/cmd/stringer@latest
- go generate
builds:
- id: linux-arm64
main: ./
goos:
- linux
goarch:
- arm64
env:
- CGO_ENABLED=1
- CC=aarch64-linux-gnu-gcc
- CXX=aarch64-linux-gnu-g++
- CGO_CFLAGS=--sysroot=/sysroot/linux_arm64
- CGO_LDFLAGS=--sysroot=/sysroot/linux_arm64 -lresolv
- PKG_CONFIG_SYSROOT_DIR=/sysroot/linux_arm64
- PKG_CONFIG_PATH=/sysroot/linux_arm64/opt/vc/lib/pkgconfig:/sysroot/linux_arm64/usr/lib/aarch64-linux-gnu/pkgconfig:/sysroot/linux_arm64/usr/lib/pkgconfig:/sysroot/linux_arm64/usr/local/lib/pkgconfig
flags:
- -mod=readonly
ldflags:
- -s -w -X main.version={{.Version}}
- id: linux-amd64
main: ./
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=1
- CC=x86_64-linux-gnu-gcc
- CXX=x86_64-linux-gnu-g++
- CGO_CFLAGS=--sysroot=/sysroot/linux_amd64
- CGO_LDFLAGS=--sysroot=/sysroot/linux_amd64 -lresolv
- PKG_CONFIG_SYSROOT_DIR=/sysroot/linux_amd64
- PKG_CONFIG_PATH=/sysroot/linux_amd64/opt/vc/lib/pkgconfig:/sysroot/linux_amd64/usr/lib/x86_64-linux-gnu/pkgconfig:/sysroot/linux_amd64/usr/lib/pkgconfig:/sysroot/linux_amd64/usr/local/lib/pkgconfig
flags:
- -mod=readonly
ldflags:
- -s -w -X main.version={{.Version}}
- id: windows-x64
main: ./
goos:
- windows
goarch:
- amd64
ldflags: -buildmode=exe
env:
- CGO_ENABLED=1
- CC=x86_64-w64-mingw32-gcc
- CXX=x86_64-w64-mingw32-g++
archives:
- id: standard
format: tar.gz
builds:
- linux-arm64
- linux-amd64
name_template: "{{ .ProjectName }}-{{ .Version }}.{{ .Os }}-{{ .Arch }}"
wrap_in_directory: true
- id: windows-x64
format: zip
builds:
- windows-x64
name_template: "{{ .ProjectName }}-{{ .Version }}.{{ .Os }}-{{ .Arch }}"
wrap_in_directory: false
files:
- LICENSE.txt
- README.md
- src: wintun/bin/amd64/wintun.dll
dst: wintun.dll
- src: wintun/LICENSE.txt
dst: wintun-LICENSE.txt
checksum:
name_template: "checksums.txt"

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
ARG GO_BUILDER_VERSION
FROM ghcr.io/gythialy/golang-cross:$GO_BUILDER_VERSION
RUN apt-get update; \
apt-get --no-install-recommends -y -q install protobuf-compiler; \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest; \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
ENV PATH="/root/go/bin:$PATH"

19
LICENSE.txt Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2025 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

69
Makefile Normal file
View File

@ -0,0 +1,69 @@
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
.PHONY: default
default: build ;
.PHONY: build-sysroot
build-sysroot:
./sysroot/build.sh
.PHONY: build-docker-image
build-docker-image:
docker build -t goreleaser-cross --build-arg GO_BUILDER_VERSION=$(GO_BUILDER_VERSION) .
.PHONY: deps
deps:
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
go get -u github.com/git-chglog/git-chglog/cmd/git-chglog
go get -u golang.org/x/tools/cmd/goimports
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
.PHONY: build
build:
go build
.PHONY: generate
generate:
go mod tidy
go get golang.org/x/tools/cmd/stringer@latest
go generate
.PHONY: clean
clean:
rm -rf virtual-vxlan* dist CHANGELOG.md
.PHONY: changelog
changelog:
git-chglog $(VERSION) > CHANGELOG.md
.PHONY: snapshot
snapshot:
docker run \
--rm --privileged \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(CURDIR):/go/src/$(PACKAGE_NAME) \
-v $(CURDIR)/sysroot:/sysroot \
-w /go/src/$(PACKAGE_NAME) \
goreleaser-cross:latest \
--clean --skip=publish --snapshot --verbose
.PHONY: release
release: changelog
docker run \
--rm --privileged \
--env-file .release-env \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(CURDIR):/go/src/$(PACKAGE_NAME) \
-v $(CURDIR)/sysroot:/sysroot \
-w /go/src/$(PACKAGE_NAME) \
goreleaser-cross:latest \
--clean --release-notes=CHANGELOG.md
.PHONY: lint
lint:
golangci-lint run --fix

76
README.md Normal file
View File

@ -0,0 +1,76 @@
# virtual-vxlan
Virtual VXLAN is a tool written to allow VXLAN interfaces to be used on Windows. I may also add support for other operating systems as well, but for now this is a Windows and Linux only project due to immediate needs. The tool uses [Wintun](https://www.wintun.net/) and the [WireGuard-go tun drivers](https://github.com/WireGuard/wireguard-go/tree/master/tun) to make virtual interfaces, then it listens for vxlan packets over UDP and does translations between the tun interface and the vxlan listener.
## Install
You can either download the latest binary from the releases, or you can build this project. For better performance on Windows, install [Npcap](https://npcap.com/#download).
## Building
You can build as follows:
```bash
make deps
make
```
## Running as a service
This project includes service support built in, simply install and start it as follows:
```bash
virtual-vxlan service install
virtual-vxlan service start
```
## Running from cli
If you are developing the software, or need more debug output. It may be worth running from the cli.
```bash
virtual-vxlan server --log-level=debug
```
## Config
The configuration is mainly managed by the service itself, however you may set manual configurations according to the `config.go` file.
## Usage
The cli has extensive help available via the following:
```bash
virtual-vxlan --help
```
Basic setup of a vxlan service is as follows.
- Start the service and/or server.
- Add vxlan listener:
```bash
virtual-vxlan listener vxlan add --address='10.0.0.2:4789' --permanent
```
Note: It is important to use the IP adddress of an interface on the system to allow the interface to be put into promiscuous mode, which is required as otherwise hardware vxlan filtering will block the packets from being received and sent. This is a known issue on Windows, however it has not been tested on other operating systems.
- Add tun interface to the vxlan listener:
```bash
virtual-vxlan listener vxlan interface vxlan20 add --vni=20 --permanent
```
- Set an IP address on the interface:
```bash
virtual-vxlan listener vxlan interface vxlan20 set-ip-addresses --ip-address=192.168.30.0/24
```
- Set a default destination for vxlan packets:
```bash
virtual-vxlan listener vxlan interface vxlan20 add-mac-entry --mac="00:00:00:00:00:00" --destination="10.0.0.3" --permanent
```
- Save the configuration:
```bash
virtual-vxlan config save
```

512
config.go Normal file
View File

@ -0,0 +1,512 @@
package main
import (
"fmt"
"net"
"net/netip"
"os"
"path/filepath"
"time"
"github.com/kkyr/fig"
"github.com/shibukawa/configdir"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
// Just bare minimal configuration for reading in cli calls.
type ConfigMinimal struct {
RPCPath string `fig:"rpc_path" yaml:"rpc_path"`
Update *UpdateConfig `fig:"update" yaml:"update"`
Log *LogConfig `fig:"log" yaml:"log"`
}
// Main configuration structure.
type Config struct {
ConfigMinimal
Listeners []ListenerConfig `fig:"listeners" yaml:"listeners"`
}
type LogConfig struct {
Level string `fig:"level" yaml:"level" enum:"debug,info,warn,error" default:"info"`
Type string `fig:"level" yaml:"level" enum:"json,console" default:"console"`
}
// Configuration for updating.
type UpdateConfig struct {
Owner string `fig:"owner" yaml:"owner"`
Repo string `fig:"repo" yaml:"repo"`
Disabled bool `fig:"disabled" yaml:"disabled"`
CurrentVersion string `fig:"-" yaml:"-"`
ShouldRelaunch bool `fig:"-" yaml:"-"`
PreUpdate func() `fig:"-" yaml:"-"`
IsSuccessMsg func(string) bool `fig:"-" yaml:"-"`
StartupTimeout time.Duration `fig:"-" yaml:"-"`
AbortUpdate func() `fig:"-" yaml:"-"`
}
// Listener configuration structure.
type ListenerConfig struct {
Name string `fig:"name" yaml:"name"`
Address string `fig:"address" yaml:"address"`
MaxMessageSize int `fig:"max_message_size" yaml:"max_message_size"`
Interfaces []InterfaceConfig `fig:"interfaces" yaml:"interfaces"`
}
// 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"`
}
// Permanent ARP entries.
type ARPEntryConfig struct {
IPAddress string `fig:"ip_address" yaml:"ip_address"`
MACAddress string `fig:"mac_address" yaml:"mac_address"`
}
// Permanent MAC entries.
type MACEntryConfig struct {
MACAddress string `fig:"mac_address" yaml:"mac_address"`
Destination string `fig:"destination" yaml:"destination"`
}
// Applies common filters to the read configuration.
func (c *Config) ApplyFilters() {
// If the RPC path isn't set, set it to temp dir.
if c.RPCPath == "" {
c.RPCPath = filepath.Join(os.TempDir(), "virtual-vxlan.sock")
}
// Check if the RPC socket already exists.
_, err := os.Stat(c.RPCPath)
if err == nil {
// If the socket exists, see if its listening.
_, err = net.Dial("unix", c.RPCPath)
// If its not listening, remove it to allow us to start.
if err != nil {
os.Remove(c.RPCPath)
}
}
}
// Get the config path/
func ConfigPath() (fileDir, fileName string) {
// Find the configuration directory.
configDirs := configdir.New(serviceVendor, serviceName)
folders := configDirs.QueryFolders(configdir.System)
if len(folders) == 0 {
log.Fatalf("Unable to find config path.")
}
// Find the file name.
fileName = defaultConfigFile
fileDir = folders[0].Path
if flags.ConfigPath != "" {
fileDir, fileName = filepath.Split(flags.ConfigPath)
}
return
}
// Makes the default config for reading.
func DefaultConfig() *Config {
config := new(Config)
config.Update = &UpdateConfig{
Owner: "grmrgecko",
Repo: "virtual-vxlan",
}
config.Log = flags.Log
return config
}
// Read configuration file and return the current config.
func ReadMinimalConfig() *Config {
// Setup default minimal config.
config := DefaultConfig()
// Find the file name.
fileDir, fileName := ConfigPath()
// Read the configuration file if it exists.
err := fig.Load(&config.ConfigMinimal, fig.File(fileName), fig.Dirs(fileDir))
// On error, just print as we want to return a default config.
if err != nil {
log.Debug("Unable to load config file:", err)
}
// Apply config filters.
config.ApplyFilters()
// Apply any log configurations loaded from file.
config.Log.Apply()
return config
}
// Read configuration file and return the current config.
func ReadConfig() *Config {
// Setup default config.
config := DefaultConfig()
// Find the file name.
fileDir, fileName := ConfigPath()
// Read the configuration file if it exists.
err := fig.Load(config, fig.File(fileName), fig.Dirs(fileDir))
// On error, just print as we want to return a default config.
if err != nil {
log.Debug("Unable to load config file:", err)
}
// Apply config filters.
config.ApplyFilters()
// Apply any log configurations loaded from file.
config.Log.Apply()
return config
}
// Apply the supplied configuration file to this app instance.
func ApplyConfig(config *Config) (err error) {
// Find listeners and interfaces that are permanent
// and not in the config being applied. Remove them
// so that we only have configured permanent entries.
{
var listenersToRemove []*Listener
var interfacesToRemove []*Interface
// Lock to prevent other actions.
app.Net.Lock()
for _, listener := range app.Net.Listeners {
if listener.Permanent {
// The address is the pretty name.
addr := listener.PrettyName()
// Find listeners in the config that match this address.
var found *ListenerConfig
for _, list := range config.Listeners {
if addr == list.Address && listener.Name == list.Name {
found = &list
break
}
}
// If we found a listener, check its interfaces.
if found != nil {
// Look for interfaces on this listener which
// are no longer in the config.
listener.net.RLock()
for _, iface := range listener.net.interfaces {
if iface.Permanent {
// Match by both name and VNI, either change
// and we need to remake it.
name := iface.Name()
vni := iface.VNI()
// Loop to find.
foundIfce := false
for _, ifce := range found.Interfaces {
if ifce.Name == name && ifce.VNI == vni {
foundIfce = true
}
}
// If we didn't find this interface, add to remove list.
if !foundIfce {
interfacesToRemove = append(interfacesToRemove, iface)
}
}
}
listener.net.RUnlock()
} else {
// This listener wasn't found, lets remove it.
listenersToRemove = append(listenersToRemove, listener)
}
}
}
// Unlock net as we're done looking at lists and we
// need it unlocked to remove items.
app.Net.Unlock()
// Remove listeners not found.
for _, list := range listenersToRemove {
list.Close()
}
// Remove interfaces not found.
for _, ifce := range interfacesToRemove {
ifce.Close()
}
}
// Loop through listeners in the config to add/change the
// configurations of both listeners and their interfaces.
for _, listener := range config.Listeners {
// First check to see if an existing listener is there.
var l *Listener
app.Net.Lock()
for _, list := range app.Net.Listeners {
if list.PrettyName() == listener.Address {
l = list
l.Permanent = true
break
}
}
app.Net.Unlock()
// If no existing listeners, add a new listener.
if l == nil {
l, err = NewListener(listener.Name, listener.Address, listener.MaxMessageSize, true)
if err != nil {
return fmt.Errorf("failed to start listener: %s %v", listener.Address, err)
}
} else {
// If the listener was already existing, update the max message size.
l.SetMaxMessageSize(listener.MaxMessageSize)
}
// Loop through interfaces on this listener to add/upate them.
for _, iface := range listener.Interfaces {
// See if this interface is already on the listener.
var i *Interface
l.net.RLock()
for _, ifce := range l.net.interfaces {
if ifce.VNI() == iface.VNI {
i = ifce
i.Permanent = true
}
}
l.net.RUnlock()
// If this interface isn't existing, add it.
if i == nil {
i, err = NewInterface(iface.Name, iface.VNI, iface.MTU, l, true)
if err != nil {
return fmt.Errorf("failed to make interface: %s %v", iface.Name, err)
}
} else {
// If the interface is existing, update the MTU.
i.SetMTU(iface.MTU)
}
// Parse the interface's MAC address.
mac, err := net.ParseMAC(iface.MACAddress)
if err != nil {
return fmt.Errorf("failed tp parse MAC: %s %v", iface.MACAddress, err)
}
// Set the interface's MAC address.
err = i.SetMACAddress(mac)
if err != nil {
return fmt.Errorf("failed to set MAC address %s on interface %s: %v", iface.MACAddress, iface.Name, err)
}
// Parse the interface's IP addresses CIDRs.
var prefixes []netip.Prefix
for _, addr := range iface.IPAddressCIDRS {
prefix, err := netip.ParsePrefix(addr)
if err != nil {
return fmt.Errorf("failed to parse CIDR: %s %v", addr, err)
}
prefixes = append(prefixes, prefix)
}
// If IP addresses are set for this interface, set them.
if len(prefixes) != 0 {
err = i.SetIPAddresses(prefixes)
if err != nil {
return fmt.Errorf("failed to set IP addresses on interface: %s %v", iface.Name, err)
}
}
// Flush the ARP table of any permanent entry.
i.tables.Lock()
found := true
for found {
found = false
for p, ent := range i.tables.arp {
if ent.Permanent {
found = true
i.tables.arp = append(i.tables.arp[:p], i.tables.arp[p+1:]...)
break
}
}
}
i.tables.Unlock()
// Add permanent ARP entries from the config.
for _, ent := range iface.ARPEntries {
addr, err := netip.ParseAddr(ent.IPAddress)
if err != nil {
return fmt.Errorf("failed to parse IP: %s %v", ent.IPAddress, err)
}
mac, err := net.ParseMAC(ent.MACAddress)
if err != nil {
return fmt.Errorf("failed to parse MAC: %s %v", ent.MACAddress, err)
}
i.AddStaticARPEntry(addr, mac, true)
}
// Flush the MAC table of any permanent entry.
i.tables.Lock()
found = true
for found {
found = false
for p, ent := range i.tables.mac {
if ent.Permanent {
found = true
i.tables.mac = append(i.tables.mac[:p], i.tables.mac[p+1:]...)
break
}
}
}
i.tables.Unlock()
// Add permanent entries from this config.
for _, ent := range iface.MACEntries {
mac, err := net.ParseMAC(ent.MACAddress)
if err != nil {
return fmt.Errorf("failed to parse MAC: %s %v", ent.MACAddress, err)
}
dst := net.ParseIP(ent.Destination)
if dst == nil {
return fmt.Errorf("failed to parse destination: %s", ent.Destination)
}
i.AddMACEntry(mac, dst, true)
}
}
}
// No errors occurred, configuration is applied.
return nil
}
// Take the current application state and save permanent configurations
// to the configuration file.
func SaveConfig() error {
// Start a new configuration file.
config := new(Config)
config.RPCPath = app.grpcServer.RPCPath
config.Update = app.UpdateConfig
config.Log = flags.Log
// Look the global app config during this process.
app.Net.Lock()
defer app.Net.Unlock()
// Loop through listeners and add permanent listeners to the config.
for _, listener := range app.Net.Listeners {
// If this listener is permanent, add it.
if listener.Permanent {
// Make a listener config.
listnr := ListenerConfig{
Address: listener.PrettyName(),
MaxMessageSize: listener.MaxMessageSize(),
Name: listener.Name,
}
// Loop through interfaces on this listener and add to the config.
listener.net.Lock()
for _, iface := range listener.net.interfaces {
// Only add interface if its permanent.
if iface.Permanent {
// Get the MAC Address.
mac, err := iface.GetMACAddress()
if err != nil {
return err
}
// Get the IP addresses on this interface.
ipAddrs, err := iface.GetIPAddresses()
if err != nil {
return err
}
// Make the config for this interface.
ifce := InterfaceConfig{
Name: iface.Name(),
VNI: iface.VNI(),
MTU: iface.MTU(),
MACAddress: mac.String(),
}
// Add the CIDRs for this interface.
for _, addr := range ipAddrs {
ifce.IPAddressCIDRS = append(ifce.IPAddressCIDRS, addr.String())
}
// Get and add permanent ARP entries to the config.
for _, ent := range iface.GetARPEntries() {
if ent.Permanent {
entry := ARPEntryConfig{
IPAddress: ent.Addr.String(),
MACAddress: ent.MAC.String(),
}
ifce.ARPEntries = append(ifce.ARPEntries, entry)
}
}
// Get and add permanent MAC enties to the config.
for _, ent := range iface.GetMACEntries() {
if ent.Permanent {
entry := MACEntryConfig{
MACAddress: ent.MAC.String(),
Destination: ent.Dst.IP.String(),
}
ifce.MACEntries = append(ifce.MACEntries, entry)
}
}
// Add this interface config to the listener config.
listnr.Interfaces = append(listnr.Interfaces, ifce)
}
}
listener.net.Unlock()
// Add this listener to the list of listeners in the config.
config.Listeners = append(config.Listeners, listnr)
}
}
// Encode YAML data.
data, err := yaml.Marshal(config)
if err != nil {
return err
}
// 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
}
func (l *LogConfig) Apply() {
switch l.Level {
case "debug":
log.SetLevel(log.DebugLevel)
case "info":
log.SetLevel(log.InfoLevel)
case "warn":
log.SetLevel(log.WarnLevel)
default:
log.SetLevel(log.ErrorLevel)
}
switch l.Type {
case "json":
log.SetFormatter(&log.JSONFormatter{})
default:
log.SetFormatter(&log.TextFormatter{})
}
}

66
config_cmd.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"context"
"fmt"
"time"
pb "github.com/grmrgecko/virtual-vxlan/vxlan"
)
// The command for saving configuration.
type ConfigSaveCmd struct {
}
func (a *ConfigSaveCmd) Run() (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 save configuration.
_, err = c.SaveConfig(ctx, &pb.Empty{})
if err != nil {
return
}
fmt.Println("Configuration saved")
return
}
type ConfigReloadCmd struct {
}
func (a *ConfigReloadCmd) Run() (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 reload the configuration.
_, err = c.ReloadConfig(ctx, &pb.Empty{})
if err != nil {
return
}
fmt.Println("Configuration reloaded")
return
}
// Commands for managing the configuration.
type ConfigCmd struct {
Save ConfigSaveCmd `cmd:""`
Reload ConfigReloadCmd `cmd:""`
}

30
config_grpc.go Normal file
View File

@ -0,0 +1,30 @@
package main
import (
"context"
pb "github.com/grmrgecko/virtual-vxlan/vxlan"
log "github.com/sirupsen/logrus"
)
// Save configuration to yaml file.
func (s *GRPCServer) SaveConfig(ctx context.Context, in *pb.Empty) (*pb.Empty, error) {
log.Println("Saving configurations.")
err := SaveConfig()
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Reload the configuration from the yaml file.
func (s *GRPCServer) ReloadConfig(ctx context.Context, in *pb.Empty) (*pb.Empty, error) {
log.Println("Reloading configurations.")
config := ReadConfig()
err := ApplyConfig(config)
if err != nil {
log.Println(err)
return nil, err
}
return new(pb.Empty), nil
}

25
devicestate_string.go Normal file
View File

@ -0,0 +1,25 @@
// Code generated by "stringer -type deviceState -trimprefix=deviceState"; DO NOT EDIT.
package main
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[deviceStateDown-0]
_ = x[deviceStateUp-1]
_ = x[deviceStateClosed-2]
}
const _deviceState_name = "DownUpClosed"
var _deviceState_index = [...]uint8{0, 4, 6, 12}
func (i deviceState) String() string {
if i >= deviceState(len(_deviceState_index)-1) {
return "deviceState(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _deviceState_name[_deviceState_index[i]:_deviceState_index[i+1]]
}

51
flags.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/alecthomas/kong"
)
type VersionFlag bool
func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil }
func (v VersionFlag) IsBool() bool { return true }
func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error {
fmt.Println(serviceName + ": " + serviceVersion)
os.Exit(0)
return nil
}
// Flags supplied to cli.
type Flags struct {
Version VersionFlag `name:"version" help:"Print version information and quit"`
ConfigPath string `help:"The path to the config file" optional:"" type:"existingfile"`
Log *LogConfig `embed:"" prefix:"log-"`
Server ServerCmd `cmd:"" help:"Run the Virtual VXLAN service"`
Service ServiceCmd `cmd:"" help:"Manage the Virtual VXLAN service"`
Listener ListenerCmd `cmd:"" help:"Manage listeners"`
Config ConfigCmd `cmd:"" help:"Manage configuration"`
Update UpdateCmd `cmd:"" help:"Check for updates and apply"`
}
var flags *Flags
// Parse the supplied flags.
func ParseFlags() *kong.Context {
flags = new(Flags)
ctx := kong.Parse(flags,
kong.Name(serviceName),
kong.Description(serviceDescription),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kong.Vars{
"serviceActions": strings.Join(ServiceAction, ","),
},
)
return ctx
}

59
go.mod Normal file
View File

@ -0,0 +1,59 @@
module github.com/grmrgecko/virtual-vxlan
go 1.23.4
require (
github.com/alecthomas/kong v1.6.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/creativeprojects/go-selfupdate v1.4.0
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/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.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
gopkg.in/yaml.v3 v3.0.1
)
require (
code.gitea.io/sdk/gitea v0.19.0 // 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
github.com/fatih/color v1.18.0 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
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.31.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

178
go.sum Normal file
View File

@ -0,0 +1,178 @@
code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y=
code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
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/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/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=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
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/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=
github.com/kkyr/fig v0.4.0/go.mod h1:U4Rq/5eUNJ8o5UvOEc9DiXtNf41srOLn2r/BfCyuc58=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM=
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=
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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
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/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.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
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/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/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=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/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/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/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.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
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=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
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=
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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

77
grpc.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"fmt"
"net"
pb "github.com/grmrgecko/virtual-vxlan/vxlan"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// Allows go generate to compile the protobuf to golang.
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./vxlan/vxlan.proto
// GRPC server structure.
type GRPCServer struct {
pb.UnimplementedVxlanServer
RPCPath string
server *grpc.Server
}
// Start serving GRPC requests.
func (s *GRPCServer) Serve(li net.Listener) {
err := s.server.Serve(li)
if err != nil {
log.Errorf("Error serving grpc: %v", err)
}
}
// Stop GRPC server.
func (s *GRPCServer) Close() {
s.server.Stop()
}
// Start GRPC server.
func NewGRPCServer(rpcPath string) (s *GRPCServer, err error) {
// Verify another server doesn't exist.
if app.grpcServer != nil {
return nil, fmt.Errorf("grpc server is already running")
}
// Connect to RPC path.
li, err := net.Listen("unix", rpcPath)
if err != nil {
return nil, fmt.Errorf("failed to listen on socket: %v", err)
}
// Setup server.
s = new(GRPCServer)
s.server = grpc.NewServer()
// Register the vxlan service to this server.
pb.RegisterVxlanServer(s.server, s)
// Update the global app gRPC server.
app.grpcServer = s
// Start serving requests.
go s.Serve(li)
return s, nil
}
// Start a connection to the gRPC Server.
func NewGRPCClient() (c pb.VxlanClient, conn *grpc.ClientConn, err error) {
// Read the minimal config.
config := ReadMinimalConfig()
// Start an gRPC client connection to the unix socket.
conn, err = grpc.NewClient(fmt.Sprintf("unix:%s", config.RPCPath), grpc.WithTransportCredentials(insecure.NewCredentials()))
// If connection is successful, provide client to the vxlan service.
if err == nil {
c = pb.NewVxlanClient(conn)
}
return
}

1154
interface.go Normal file

File diff suppressed because it is too large Load Diff

595
interface_cmd.go Normal file
View File

@ -0,0 +1,595 @@
package main
import (
"context"
"fmt"
"os"
"time"
pb "github.com/grmrgecko/virtual-vxlan/vxlan"
"github.com/jedib0t/go-pretty/v6/table"
)
// Command to list interfaces on an listener.
type InterfaceListCmd struct{}
func (a *InterfaceListCmd) Run(l *ListenerCmd) (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()
// Pull list of interface on the requested listener.
listener := &pb.ListenerRequestWithName{Name: l.name()}
r, err := c.ListInterfaces(ctx, listener)
if err != nil {
return
}
// Setup table for interfaces.
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Name", "VNI", "MTU", "Permanent"})
// Add row for each interface.
for _, ifce := range r.Interfaces {
t.AppendRow([]interface{}{ifce.Name, ifce.Vni, ifce.Mtu, ifce.Permanent})
}
// Render the table.
t.Render()
return
}
// Command to add interface to an listener.
type InterfaceAddCmd struct {
VNI uint32 `help:"VXLAN VNI" required:""`
MTU int32 `help:"MTU of interface, 0 will result in calculated value" default:"0"`
Permanent bool `help:"Should listener be saved to disk?"`
}
func (a *InterfaceAddCmd) 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()
// Setup interface request.
ifce := &pb.AddInterfaceRequest{
ListenerName: l.name(),
Name: i.name(),
Vni: a.VNI,
Mtu: a.MTU,
Permanent: a.Permanent,
}
// Attempt to add the interface.
_, err = c.AddInterface(ctx, ifce)
if err != nil {
return
}
fmt.Println("Added interface to listener.")
return
}
// Command to remove an interface on an listener.
type InterfaceRemoveCmd struct{}
func (a *InterfaceRemoveCmd) 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()
// Setup interface request.
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
// Attempt to add the interface.
_, err = c.RemoveInterface(ctx, ifce)
if err != nil {
return
}
fmt.Println("Removed interface from listener.")
return
}
// Command to set interface's MTU.
type InterfaceSetMTUCmd struct {
MTU int32 `help:"Interface MTU value (0 will result in calculated value)" default:"0"`
}
func (a *InterfaceSetMTUCmd) 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 update interface's MTU.
ifce := &pb.InterfaceMTURequest{
ListenerName: l.name(),
Name: i.name(),
Mtu: a.MTU,
}
_, err = c.SetInterfaceMTU(ctx, ifce)
if err != nil {
return
}
fmt.Println("Updated MTU.")
return
}
// Command to set interface's MAC address.
type InterfaceSetMACAddressCmd struct {
MAC string `help:"The MAC Address to set"`
}
func (a *InterfaceSetMACAddressCmd) 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 update interface's MAC address.
ifce := &pb.InterfaceMACAddressRequest{
ListenerName: l.name(),
Name: i.name(),
Mac: a.MAC,
}
_, err = c.SetInterfaceMACAddress(ctx, ifce)
if err != nil {
return
}
fmt.Println("Updated MAC Address.")
return
}
// Command to get interface's MAC address.
type InterfaceGetMACAddressCmd struct{}
func (a *InterfaceGetMACAddressCmd) 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 interface's MAC address.
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
r, err := c.GetInterfaceMACAddress(ctx, ifce)
if err != nil {
return
}
// Setup table for MAC listing.
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"MAC Address"})
// Add row for the MAC address.
t.AppendRow([]interface{}{r.Mac})
// Render the table.
t.Render()
return
}
// Command to set interface's IP addresses.
type InterfaceSetIPAddressesCmd struct {
IPAddress []string `help:"The IP address(es) to set"`
}
func (a *InterfaceSetIPAddressesCmd) 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 update interface's IP address(es).
ifce := &pb.InterfaceIPAddressesRequest{
ListenerName: l.name(),
Name: i.name(),
IpAddress: a.IPAddress,
}
_, err = c.SetInterfaceIPAddresses(ctx, ifce)
if err != nil {
return
}
fmt.Println("Updated IP Address(es).")
return
}
// Command to get interface's MAC Address.
type InterfaceGetIPAddressesCmd struct{}
func (a *InterfaceGetIPAddressesCmd) 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 interface's IP address(es).
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
r, err := c.GetInterfaceIPAddresses(ctx, ifce)
if err != nil {
return
}
// Setup table for IP list.
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"IP Address"})
// Add row for the IP address(es).
for _, ipAddress := range r.IpAddress {
t.AppendRow([]interface{}{ipAddress})
}
// Render the table.
t.Render()
return
}
// Command to add an MAC entry to an interface.
type InterfaceAddMACEntryCmd struct {
MAC string `help:"MAC address to route (00:00:00:00:00:00 is treated as default route)" default:"00:00:00:00:00:00"`
Destination string `help:"The IP address to send vxlan traffic" required:""`
Permanent bool `help:"Should the MAC entry be saved to disk?"`
}
func (a *InterfaceAddMACEntryCmd) 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 MAC entry to an interface.
ifce := &pb.InterfaceMacEntryRequest{
ListenerName: l.name(),
Name: i.name(),
Mac: a.MAC,
Destination: a.Destination,
Permanent: a.Permanent,
}
_, err = c.InterfaceAddMACEntry(ctx, ifce)
if err != nil {
return
}
fmt.Println("Added MAC entry.")
return
}
// Command to remove an MAC entry from an interface.
type InterfaceRemoveMACEntryCmd struct {
MAC string `help:"MAC address" required:""`
Destination string `help:"The IP address" required:""`
}
func (a *InterfaceRemoveMACEntryCmd) 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 an MAC entry from an interface.
ifce := &pb.InterfaceRemoveMacEntryRequest{
ListenerName: l.name(),
Name: i.name(),
Mac: a.MAC,
Destination: a.Destination,
}
_, err = c.InterfaceRemoveMACEntry(ctx, ifce)
if err != nil {
return
}
fmt.Println("Removed MAC entry.")
return
}
// Command to get MAC entries on an interface.
type InterfaceGetMACEntriesCmd struct{}
func (a *InterfaceGetMACEntriesCmd) 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 MAC entries on an interface.
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
r, err := c.InterfaceGetMACEntries(ctx, ifce)
if err != nil {
return
}
// Setup table for MAC entries.
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"MAC Address", "Destination", "Permanent"})
// Add rows for entries.
for _, ent := range r.Entries {
t.AppendRow([]interface{}{ent.Mac, ent.Destination, ent.Permanent})
}
// Render the table.
t.Render()
return
}
// Command to remove all mac entries from an interface.
type InterfaceFlushMACTableCmd struct{}
func (a *InterfaceFlushMACTableCmd) 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 flush the MAC entry table on an interface.
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
_, err = c.InterfaceFlushMACTable(ctx, ifce)
if err != nil {
return
}
fmt.Println("Flushed MAC entry table.")
return
}
// Command to add an MAC entry to an interface.
type InterfaceAddStaticARPEntryCmd struct {
Address string `help:"IP address" required:""`
MAC string `help:"MAC address" required:""`
Permanent bool `help:"Should the ARP entry be saved to disk?"`
}
func (a *InterfaceAddStaticARPEntryCmd) 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 an static ARP entry to an interface.
ifce := &pb.InterfaceARPEntryRequest{
ListenerName: l.name(),
Name: i.name(),
Address: a.Address,
Mac: a.MAC,
Permanent: a.Permanent,
}
_, err = c.InterfaceAddStaticARPEntry(ctx, ifce)
if err != nil {
return
}
fmt.Println("Added static ARP entry.")
return
}
// Command to remove an MAC entry from an interface.
type InterfaceRemoveARPEntryCmd struct {
Address string `help:"IP address" required:""`
}
func (a *InterfaceRemoveARPEntryCmd) 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 an ARP entry from a table.
ifce := &pb.InterfaceRemoveARPEntryRequest{
ListenerName: l.name(),
Name: i.name(),
Address: a.Address,
}
_, err = c.InterfaceRemoveARPEntry(ctx, ifce)
if err != nil {
return
}
fmt.Println("Removed ARP entry.")
return
}
// Command to get MAC entries on an interface.
type InterfaceGetARPEntriesCmd struct{}
func (a *InterfaceGetARPEntriesCmd) 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 tp update listener's max message size.
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
r, err := c.InterfaceGetARPEntries(ctx, ifce)
if err != nil {
return
}
// Setup table for interfaces.
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"IP Address", "MAC Address", "Expires", "Permanent"})
// Add row for the IP address(es).
for _, ent := range r.Entries {
t.AppendRow([]interface{}{ent.Address, ent.Mac, ent.Expires, ent.Permanent})
}
// Render the table.
t.Render()
return
}
// Command to remove all mac entries from an interface.
type InterfaceFlushARPTableCmd struct{}
func (a *InterfaceFlushARPTableCmd) 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 tp update listener's max message size.
ifce := &pb.InterfaceRequestWithName{
ListenerName: l.name(),
Name: i.name(),
}
_, err = c.InterfaceFlushARPTable(ctx, ifce)
if err != nil {
return
}
fmt.Println("Flushed ARP entry table.")
return
}
// Commands managing listeners.
type InterfaceCmd struct {
List InterfaceListCmd `cmd:""`
Name struct {
Name string `arg:"" help:"Interface name" required:""`
Add InterfaceAddCmd `cmd:""`
Remove InterfaceRemoveCmd `cmd:""`
SetMTU InterfaceSetMTUCmd `cmd:""`
SetMACAddress InterfaceSetMACAddressCmd `cmd:""`
GetMACAddress InterfaceGetMACAddressCmd `cmd:""`
SetIPAddresses InterfaceSetIPAddressesCmd `cmd:""`
GetIPAddresses InterfaceGetIPAddressesCmd `cmd:""`
AddMACEntry InterfaceAddMACEntryCmd `cmd:""`
RemoveMACEntry InterfaceRemoveMACEntryCmd `cmd:""`
GetMACEntries InterfaceGetMACEntriesCmd `cmd:""`
FlushMACTable InterfaceFlushMACTableCmd `cmd:""`
AddStaticARPEntry InterfaceAddStaticARPEntryCmd `cmd:""`
RemoveARPEntry InterfaceRemoveARPEntryCmd `cmd:""`
GetARPEntries InterfaceGetARPEntriesCmd `cmd:""`
FlushARPTable InterfaceFlushARPTableCmd `cmd:""`
} `arg:""`
}
// Returns the interface name.
func (i *InterfaceCmd) name() string {
return i.Name.Name
}

395
interface_grpc.go Normal file
View File

@ -0,0 +1,395 @@
package main
import (
"context"
"fmt"
"net"
"net/netip"
pb "github.com/grmrgecko/virtual-vxlan/vxlan"
)
// List interfaces on a listener.
func (s *GRPCServer) ListInterfaces(ctx context.Context, in *pb.ListenerRequestWithName) (*pb.ListInterfacesReply, error) {
reply := new(pb.ListInterfacesReply)
app.Net.Lock()
for _, listener := range app.Net.Listeners {
if listener.Name == in.Name {
listener.net.RLock()
for _, iface := range listener.net.interfaces {
ifce := &pb.Interface{
Name: iface.Name(),
Vni: iface.VNI(),
Mtu: int32(iface.MTU()),
Permanent: iface.Permanent,
}
reply.Interfaces = append(reply.Interfaces, ifce)
}
listener.net.RUnlock()
break
}
}
app.Net.Unlock()
return reply, nil
}
// Add interface to the listener.
func (s *GRPCServer) AddInterface(ctx context.Context, in *pb.AddInterfaceRequest) (*pb.Empty, error) {
// Find listener.
list, err := s.FindListener(in.ListenerName)
if err != nil {
return nil, err
}
// Add interface to listener.
_, err = NewInterface(in.Name, in.Vni, int(in.Mtu), list, in.Permanent)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Find interface on listener.
func (s *GRPCServer) FindInterface(listenerName, name string) (list *Listener, ifce *Interface, err error) {
// Find listener
list, err = s.FindListener(listenerName)
if err != nil {
return nil, nil, err
}
// Find interface.
list.net.RLock()
for _, iface := range list.net.interfaces {
if iface.Name() == name {
ifce = iface
break
}
}
list.net.RUnlock()
// If no interface found, error.
if ifce == nil {
return nil, nil, fmt.Errorf("no interface with name: %s", name)
}
return
}
// Remove interface from the listener.
func (s *GRPCServer) RemoveInterface(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.Empty, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Close the interface.
err = ifce.Close()
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Set interface's MTU.
func (s *GRPCServer) SetInterfaceMTU(ctx context.Context, in *pb.InterfaceMTURequest) (*pb.Empty, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Set the MTU for the interface.
ifce.SetMTU(int(in.Mtu))
return new(pb.Empty), nil
}
// Get interface's MTU.
func (s *GRPCServer) GetInterfaceMTU(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.InterfaceMTUReply, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Get the MTU for the interface.
reply := &pb.InterfaceMTUReply{
Mtu: int32(ifce.MTU()),
}
return reply, nil
}
// Set interface's MAC address.
func (s *GRPCServer) SetInterfaceMACAddress(ctx context.Context, in *pb.InterfaceMACAddressRequest) (*pb.Empty, error) {
// Parse MAC Address.
mac, err := net.ParseMAC(in.Mac)
if err != nil {
return nil, fmt.Errorf("unable to parse MAC address: %v", err)
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Set the MAC Address for the interface.
err = ifce.SetMACAddress(mac)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Get interface's MAC address.
func (s *GRPCServer) GetInterfaceMACAddress(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.InterfaceMACAddressReply, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Get the MAC address of the interface.
mac, err := ifce.GetMACAddress()
if err != nil {
return nil, err
}
// Set reply with MAC address.
reply := &pb.InterfaceMACAddressReply{
Mac: mac.String(),
}
return reply, nil
}
// Set interface's IP addresses.
func (s *GRPCServer) SetInterfaceIPAddresses(ctx context.Context, in *pb.InterfaceIPAddressesRequest) (*pb.Empty, error) {
// Parse provided IP addresses.
var prefixes []netip.Prefix
for _, ipAddress := range in.IpAddress {
prefix, err := netip.ParsePrefix(ipAddress)
if err != nil {
return nil, fmt.Errorf("failed to parse IP Address %s: %v", ipAddress, err)
}
prefixes = append(prefixes, prefix)
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Set the IP Addresses for the interface.
err = ifce.SetIPAddresses(prefixes)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Get interface's IP addresses.
func (s *GRPCServer) GetInterfaceIPAddresses(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.InterfaceIPAddressesReply, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Get the IP address(es) of the interface.
prefixes, err := ifce.GetIPAddresses()
if err != nil {
return nil, err
}
// Set reply with IP address(es).
reply := new(pb.InterfaceIPAddressesReply)
for _, prefix := range prefixes {
reply.IpAddress = append(reply.IpAddress, prefix.String())
}
return reply, nil
}
// Add MAC entry to an interface.
func (s *GRPCServer) InterfaceAddMACEntry(ctx context.Context, in *pb.InterfaceMacEntryRequest) (*pb.Empty, error) {
// Parse MAC Address.
mac, err := net.ParseMAC(in.Mac)
if err != nil {
return nil, fmt.Errorf("unable to parse MAC address: %v", err)
}
// Parse destination
dst := net.ParseIP(in.Destination)
if dst == nil {
return nil, fmt.Errorf("unable to parse IP address")
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Add the MAC entry.
err = ifce.AddMACEntry(mac, dst, in.Permanent)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Remove MAC entry from an interface.
func (s *GRPCServer) InterfaceRemoveMACEntry(ctx context.Context, in *pb.InterfaceRemoveMacEntryRequest) (*pb.Empty, error) {
// Parse MAC Address.
mac, err := net.ParseMAC(in.Mac)
if err != nil {
return nil, fmt.Errorf("unable to parse MAC address: %v", err)
}
// Parse destination
dst := net.ParseIP(in.Destination)
if dst == nil {
return nil, fmt.Errorf("unable to parse IP address")
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Remove the MAC entry.
err = ifce.RemoveMACEntry(mac, dst)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Get MAC entries on interface.
func (s *GRPCServer) InterfaceGetMACEntries(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.InterfaceMacEntryReply, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Get MAC entries and make reply.
entries := ifce.GetMACEntries()
reply := new(pb.InterfaceMacEntryReply)
for _, entry := range entries {
ent := &pb.MacEntry{
Mac: entry.MAC.String(),
Destination: entry.Dst.IP.String(),
Permanent: entry.Permanent,
}
reply.Entries = append(reply.Entries, ent)
}
return reply, nil
}
// Flush MAC table on interface.
func (s *GRPCServer) InterfaceFlushMACTable(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.Empty, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Flush MAC table.
ifce.FlushMACTable()
return new(pb.Empty), nil
}
// Add static ARP entry to an interface.
func (s *GRPCServer) InterfaceAddStaticARPEntry(ctx context.Context, in *pb.InterfaceARPEntryRequest) (*pb.Empty, error) {
// Parse IP address
addr, err := netip.ParseAddr(in.Address)
if err != nil {
return nil, fmt.Errorf("unable to parse IP address: %v", err)
}
// Parse MAC Address.
mac, err := net.ParseMAC(in.Mac)
if err != nil {
return nil, fmt.Errorf("unable to parse MAC address: %v", err)
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Add the ARP entry.
ifce.AddStaticARPEntry(addr, mac, in.Permanent)
return new(pb.Empty), nil
}
// Remove ARP entry from an interface.
func (s *GRPCServer) InterfaceRemoveARPEntry(ctx context.Context, in *pb.InterfaceRemoveARPEntryRequest) (*pb.Empty, error) {
// Parse IP address
addr, err := netip.ParseAddr(in.Address)
if err != nil {
return nil, fmt.Errorf("unable to parse IP address: %v", err)
}
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Remove the ARP entry.
err = ifce.RemoveARPEntry(addr)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Get ARP entries on interface.
func (s *GRPCServer) InterfaceGetARPEntries(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.InterfaceArpEntryReply, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Get MAC entries and make reply.
entries := ifce.GetARPEntries()
reply := new(pb.InterfaceArpEntryReply)
for _, entry := range entries {
ent := &pb.ArpEntry{
Address: entry.Addr.String(),
Mac: entry.MAC.String(),
Expires: entry.Expires.String(),
Permanent: entry.Permanent,
}
reply.Entries = append(reply.Entries, ent)
}
return reply, nil
}
// Flush ARP table on interface.
func (s *GRPCServer) InterfaceFlushARPTable(ctx context.Context, in *pb.InterfaceRequestWithName) (*pb.Empty, error) {
// Find interface.
_, ifce, err := s.FindInterface(in.ListenerName, in.Name)
if err != nil {
return nil, err
}
// Flush ARP table.
ifce.FlushARPTable()
return new(pb.Empty), nil
}

257
listener.go Normal file
View File

@ -0,0 +1,257 @@
package main
import (
"errors"
"fmt"
"net"
"sync"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
log "github.com/sirupsen/logrus"
)
// The base of a vxlan connection is the port listener.
// The port listener listens for vxlan packets on a port,
// and it passes matching VNI values with interfaces.
// Interfaces are added to the listener, which processes
// vxlan packets.
type Listener struct {
net struct {
stopping sync.WaitGroup
addr *net.UDPAddr
is4 bool
maxMessageSize int
maxMessageSizeC chan int
conn *net.UDPConn
promisc *Promiscuous
interfaces []*Interface
sync.RWMutex
}
Name string
Permanent bool
closed chan 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.
func NewListener(name, address string, maxMessageSize int, perm bool) (l *Listener, err error) {
// Verify the specified address is valid.
addr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return
}
is4 := addr.IP.To4() != nil
// Verify no listeners exist with this address.
app.Net.Lock()
defer app.Net.Unlock()
for _, listener := range app.Net.Listeners {
if listener.PrettyName() == addr.String() {
return nil, fmt.Errorf("listener already exists with address %s", addr.String())
}
if listener.Name == name {
return nil, fmt.Errorf("listener already exists with name %s", name)
}
}
// On Windows, there is no public way to configure hardware vxlan offloading.
// This in term filters packets that are destined to non-broadcast MAC addresses
// in vxlan packets. Which prevents us from receiving the packets, so we set the
// interface to promiscuous mode to allow us to receive packets. We can only do
// this if the IP address provided is an absolute address.
var promisc *Promiscuous
if !isZeroAddr(addr.IP) {
promisc, err = SetInterfacePromiscuous(addr.IP)
if err != nil {
return
}
}
// Start listening on the specified address.
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return
}
// Save listener details.
l = new(Listener)
l.Name = name
l.net.addr = addr
l.net.is4 = is4
l.net.maxMessageSize = maxMessageSize
l.net.maxMessageSizeC = make(chan int)
l.net.conn = conn
l.net.promisc = promisc
l.log = log.WithFields(log.Fields{
"listener": addr.String(),
})
l.closed = make(chan struct{})
l.Permanent = perm
app.Net.Listeners = append(app.Net.Listeners, l)
// Start reading packets on the listener.
go l.packetReader()
// Inform that we started a listern.
l.log.Print("Listener started.")
return
}
// Get the current maximum message size.
func (l *Listener) MaxMessageSize() int {
l.net.RLock()
defer l.net.RUnlock()
return l.net.maxMessageSize
}
// Set the maximum message size to a new value.
func (l *Listener) SetMaxMessageSize(size int) {
if size <= 1 {
return
}
l.net.Lock()
l.net.maxMessageSize = size
l.net.Unlock()
go func() {
l.net.maxMessageSizeC <- size
}()
}
// Close the listener, and its interfaces.
func (l *Listener) Close() (err error) {
// Ensure proper multi tasking.
l.net.Lock()
l.log.Debug("Listener is closing.")
// Close the connection.
err = l.net.conn.Close()
if l.net.promisc != nil {
l.net.promisc.Close()
}
close(l.closed)
// Wait for packet readers to stop.
l.net.stopping.Wait()
// Remove self from app.
app.Net.Lock()
defer app.Net.Unlock()
for i, listener := range app.Net.Listeners {
if listener == l {
app.Net.Listeners = append(app.Net.Listeners[:i], app.Net.Listeners[i+1:]...)
break
}
}
// The interfaces will be acquiring lock here while closing.
l.net.Unlock()
for len(l.net.interfaces) >= 1 {
l.net.interfaces[0].Close()
}
l.log.Print("Listener closed.")
return
}
// Get listener name.
func (l *Listener) PrettyName() string {
return l.net.addr.String()
}
// Get listener address.
func (l *Listener) Addr() net.Addr {
return l.net.addr
}
// Read packets and parse.
func (l *Listener) packetReader() {
// When stopping, we need to wait for this packet reader to finish.
l.net.stopping.Add(1)
defer func() {
l.log.Debug("packet reader - stopped")
l.net.stopping.Done()
}()
// Setup packet decoder.
var vxlan layers.VXLAN
var eth layers.Ethernet
var ip4 layers.IPv4
var ip6 layers.IPv6
var arp layers.ARP
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeVXLAN, &vxlan, &eth, &ip4, &ip6, &arp)
parser.IgnoreUnsupported = true
var decoded []gopacket.LayerType
// Start reading packets, with current buffer size.
l.log.Debug("packet reader - started")
buf := make([]byte, l.net.maxMessageSize)
for {
// Read packet.
n, err := l.net.conn.Read(buf)
if err != nil {
if errors.Is(err, net.ErrClosed) {
break
}
l.log.Errorf("received error reading from listener: %v", err)
}
// Only process packets larger than a vxlan header.
if n > 38 {
// Attempt to parse vxlan and its layers, up to IP and ARP.
// Parsing any further is a waste of processing power.
decoded = nil
packet := buf[:n]
err = parser.DecodeLayers(packet, &decoded)
// If we successfully parsed a packet, route it accordingly.
if err == nil {
// To ensure the interfaces do not change as we're handling the packet, lock it.
l.net.RLock()
for _, ifce := range l.net.interfaces {
// If this interface has the decoded VNI, pass the packet to it.
if ifce.vni == vxlan.VNI {
// Depending on what layer type was decoded, pass to the interface.
for _, layerType := range decoded {
if layerType == layers.LayerTypeARP {
ifce.HandleARP(arp)
} else if layerType == layers.LayerTypeIPv4 {
ifce.HandleIPv4(eth, ip4)
} else if layerType == layers.LayerTypeIPv6 {
ifce.HandleIPv6(eth, ip6)
}
}
}
}
l.net.RUnlock()
}
}
// If the max message size has a change request, make a new buffer and save change.
select {
case newSize, ok := <-l.net.maxMessageSizeC:
if ok {
buf = make([]byte, newSize)
}
default:
}
}
}
// Write data to destination.
func (l *Listener) WriteTo(b []byte, addr *net.UDPAddr) (int, error) {
return l.net.conn.WriteTo(b, addr)
}

164
listener_cmd.go Normal file
View File

@ -0,0 +1,164 @@
package main
import (
"context"
"fmt"
"os"
"time"
pb "github.com/grmrgecko/virtual-vxlan/vxlan"
"github.com/jedib0t/go-pretty/v6/table"
)
// Command to list listeners
type ListenerListCmd struct{}
func (a *ListenerListCmd) Run() (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()
// Read listener list.
r, err := c.ListListeners(ctx, &pb.Empty{})
if err != nil {
return
}
// Verify there are listeners.
if len(r.Listeners) == 0 {
fmt.Println("No listeners running.")
return
}
// Setup table for listener list.
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Name", "Address", "Max Message Size", "Permanent"})
// Add rows for each listener.
for _, list := range r.Listeners {
t.AppendRow([]interface{}{list.Name, list.Address, list.MaxMessageSize, list.Permanent})
}
// Print the table.
t.Render()
return
}
// Command to add an listener.
type ListenerAddCmd struct {
Address string `help:"Bind address (':4789' or '10.0.0.2:4789')" required:""`
MaxMessageSize int32 `help:"Max UDP message size" default:"1500"`
Permanent bool `help:"Should listener be saved to disk?"`
}
func (a *ListenerAddCmd) Run(l *ListenerCmd) (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()
// Setup listener config.
listener := &pb.Listener{
Name: l.name(),
Address: a.Address,
MaxMessageSize: a.MaxMessageSize,
Permanent: a.Permanent,
}
// Attempt to add an listener.
_, err = c.AddListener(ctx, listener)
if err != nil {
return
}
fmt.Println("Added listener.")
return
}
// Command to remove an listener.
type ListenerRemoveCmd struct{}
func (a *ListenerRemoveCmd) Run(l *ListenerCmd) (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 the listener.
listener := &pb.ListenerRequestWithName{Name: l.name()}
_, err = c.RemoveListener(ctx, listener)
if err != nil {
return
}
fmt.Println("Removed listener.")
return
}
// Command to set listener's max message size.
type ListenerSetMaxMessageSizeCmd struct {
Size int32 `help:"Max UDP message size" default:"1500"`
}
func (a *ListenerSetMaxMessageSizeCmd) Run(l *ListenerCmd) (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 tp update listener's max message size.
listener := &pb.ListenerMaxMessageSizeRequest{
Name: l.name(),
Size: a.Size,
}
_, err = c.SetListenerMaxMessageSize(ctx, listener)
if err != nil {
return
}
fmt.Println("Updated max message size.")
return
}
// Commands managing listeners.
type ListenerCmd struct {
List ListenerListCmd `cmd:""`
Name struct {
Name string `arg:"" help:"Listener name" required:""`
Add ListenerAddCmd `cmd:""`
Remove ListenerRemoveCmd `cmd:""`
SetMaxMessageSize ListenerSetMaxMessageSizeCmd `cmd:""`
Interface InterfaceCmd `cmd:""`
} `arg:""`
}
// Returns the listener name.
func (l *ListenerCmd) name() string {
return l.Name.Name
}

97
listener_grpc.go Normal file
View File

@ -0,0 +1,97 @@
package main
import (
"context"
"fmt"
pb "github.com/grmrgecko/virtual-vxlan/vxlan"
)
// Provide list of listeners.
func (s *GRPCServer) ListListeners(ctx context.Context, in *pb.Empty) (*pb.ListListenersReply, error) {
reply := new(pb.ListListenersReply)
app.Net.Lock()
for _, listener := range app.Net.Listeners {
list := &pb.Listener{
Name: listener.Name,
Address: listener.PrettyName(),
MaxMessageSize: int32(listener.net.maxMessageSize),
Permanent: listener.Permanent,
}
reply.Listeners = append(reply.Listeners, list)
}
app.Net.Unlock()
return reply, nil
}
// Add listener.
func (s *GRPCServer) AddListener(ctx context.Context, in *pb.Listener) (*pb.Empty, error) {
_, err := NewListener(in.Name, in.Address, int(in.MaxMessageSize), in.Permanent)
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Find listener.
func (s *GRPCServer) FindListener(name string) (list *Listener, err error) {
// Find existing listener.
app.Net.Lock()
for _, listener := range app.Net.Listeners {
if listener.Name == name {
list = listener
break
}
}
app.Net.Unlock()
// If no listener found, error.
if list == nil {
return nil, fmt.Errorf("no listener with name: %s", name)
}
return
}
// Remove listener.
func (s *GRPCServer) RemoveListener(ctx context.Context, in *pb.ListenerRequestWithName) (*pb.Empty, error) {
list, err := s.FindListener(in.Name)
if err != nil {
return nil, err
}
// Try and close the listener.
err = list.Close()
if err != nil {
return nil, err
}
return new(pb.Empty), nil
}
// Set listener's max message size.
func (s *GRPCServer) SetListenerMaxMessageSize(ctx context.Context, in *pb.ListenerMaxMessageSizeRequest) (*pb.Empty, error) {
list, err := s.FindListener(in.Name)
if err != nil {
return nil, err
}
// Set the max message size for the listener.
list.SetMaxMessageSize(int(in.Size))
return new(pb.Empty), nil
}
// Get listener's max message size.
func (s *GRPCServer) GetListenerMaxMessageSize(ctx context.Context, in *pb.ListenerRequestWithName) (*pb.ListenerMaxMessageSizeReply, error) {
list, err := s.FindListener(in.Name)
if err != nil {
return nil, err
}
// Get the max message size for the listener.
reply := &pb.ListenerMaxMessageSizeReply{
Size: int32(list.MaxMessageSize()),
}
return reply, nil
}

24
main.go Normal file
View File

@ -0,0 +1,24 @@
package main
// Basic application info.
const (
serviceName = "virtual-vxlan"
serviceDisplayName = "Virtual VXLAN"
serviceVendor = "com.mrgeckosmedia"
serviceDescription = "Virtual VXLAN using TUN interfaces"
serviceVersion = "0.1"
defaultConfigFile = "config.yaml"
)
// The application start.
func main() {
// Parse the flags.
ctx := ParseFlags()
// Configure logging.
flags.Log.Apply()
// Run the command and exit.
err := ctx.Run()
ctx.FatalIfErrorf(err)
}

37
masquerade.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"fmt"
"github.com/google/gopacket"
)
// A packet layer that just contains pre-computed bytes.
type Masquerade struct {
MData []byte
MLayerType gopacket.LayerType
}
// Return the layer type of this layer that we're masquerading.
func (m *Masquerade) LayerType() gopacket.LayerType {
return m.MLayerType
}
// Encode data to buffer.
func (m *Masquerade) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
// Get the data length, and ensure there is data.
length := len(m.MData)
if length < 1 {
return fmt.Errorf("invalid data")
}
// Allocate bytes in buffer for this data.
bytes, err := b.PrependBytes(length)
if err != nil {
return err
}
// copy data into bytes.
copy(bytes, m.MData)
return nil
}

23
promiscuous_unix.go Normal file
View File

@ -0,0 +1,23 @@
//go:build !windows
package main
import "net"
// I am focusing on Windows development currently, may fill this out if I find Linux needing promiscuous mode as well.
// Also may work on darwin support/bsd, depending on how I feel.
// For now, this is just a stub to make code happy.
// Structure to store connections.
type Promiscuous struct {
}
// Set interface to promiscuous mode, using the interface IP to identify the interface.
func SetInterfacePromiscuous(ifaceIP net.IP) (promisc *Promiscuous, err error) {
return new(Promiscuous), nil
}
// Close promiscuous mode connection.
func (p *Promiscuous) Close() error {
return nil
}

164
promiscuous_windows.go Normal file
View File

@ -0,0 +1,164 @@
package main
import (
"context"
"errors"
"fmt"
"net"
"strings"
"syscall"
"unsafe"
"github.com/google/gopacket/pcap"
log "github.com/sirupsen/logrus"
)
// Constants for setting promiscuous mode on windows. See Microsoft's documentation below:
// https://learn.microsoft.com/en-us/windows/win32/winsock/sio-rcvall
const (
SIO_RCVALL = syscall.IOC_IN | syscall.IOC_VENDOR | 1
RCVALL_OFF = 0
RCVALL_ON = 1
RCVALL_SOCKETLEVELONLY = 2
RCVALL_IPLEVEL = 3
)
// Structure to store connections.
type Promiscuous struct {
conn net.PacketConn
pcap *pcap.Handle
}
// Set interface to promiscuous mode, using the interface IP to identify the interface.
func SetInterfacePromiscuous(ifaceIP net.IP) (promisc *Promiscuous, err error) {
promisc = new(Promiscuous)
// I have found npcap to be most performant, however due to its commercial nature,
// I do not include it in this project. You may install it to get its benefit,
// or the alternative will come into play. I am open to hearing of better solutions
// to this packet filtering problem, and better ideas.
err = promisc.tryPCap(ifaceIP)
if err != nil {
// If it fails because wpcap.dll is missing, try alternative method.
if strings.Contains(err.Error(), "wpcap.dll") {
log.Debug("Missing npcap, putting interface into promiscuous mode using SIO_RCVALL.")
// Put interface in promiscuous using ICMP listener.
err = promisc.tryICMPListen(ifaceIP)
if err != nil {
promisc = nil
return
}
// If regular error, just return the error.
} else {
promisc = nil
return
}
}
return
}
// Use npcap to put interface in promiscuous mode.
func (p *Promiscuous) tryPCap(ifaceIP net.IP) (err error) {
// Find the windows interface name for the adapter with the IP we're binding.
ifs, err := pcap.FindAllDevs()
if err != nil {
return
}
foundIface := false
var piface pcap.Interface
for _, piface = range ifs {
// Find matching IP address.
for _, paddr := range piface.Addresses {
// If we found it, stop to keep interface reference.
if paddr.IP.Equal(ifaceIP) {
foundIface = true
break
}
}
// Stop here to prevent reference from being replaced.
if foundIface {
break
}
}
// If we didn't find the interface being bound to, stop here.
if !foundIface {
return fmt.Errorf("unable to find the interface with vxlan")
}
// Open the pcap connection to put the interface in promiscuous mode.
log.Debugf("Putting adapter in promiscuous mode: %s (%s)", piface.Description, piface.Name)
p.pcap, err = pcap.OpenLive(piface.Name, 1, true, pcap.BlockForever)
if err != nil {
return
}
// To prevent the pcap from receiving packets, filter to just localhost
// traffic which should result in zero packets to capture.
err = p.pcap.SetBPFFilter("host 127.0.0.1")
if err != nil {
return
}
return
}
// Use SIO_RCVALL to put interface in promiscuous mode.
func (p *Promiscuous) tryICMPListen(ifaceIP net.IP) (err error) {
// We need the syscall handle to put the interface in promiscuous mode with WSAIoctl.
var socketHandle syscall.Handle
// Use listen config to get the syscall handle via the control function.
cfg := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(s uintptr) {
socketHandle = syscall.Handle(s)
})
},
}
// Depending on IP address network, setup ICMP network.
network := "ip4:icmp"
if ifaceIP.To4() == nil {
network = "ip6:ipv6-icmp"
}
// Use listen packet to start a connection.
p.conn, err = cfg.ListenPacket(context.Background(), network, ifaceIP.String())
if err != nil {
return
}
// Put interface in promiscuous mode.
cbbr := uint32(0)
flag := uint32(RCVALL_ON)
size := uint32(unsafe.Sizeof(flag))
err = syscall.WSAIoctl(socketHandle, SIO_RCVALL, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &cbbr, nil, 0)
if err != nil {
p.conn.Close()
return
}
go p.connReader()
return
}
// Read and discard packets read.
func (p *Promiscuous) connReader() {
buf := make([]byte, 500)
for {
_, _, err := p.conn.ReadFrom(buf)
if err != nil {
if errors.Is(err, net.ErrClosed) {
break
}
log.Errorf("received error reading in promiscuous: %v", err)
}
}
}
// Close promiscuous mode connection.
func (p *Promiscuous) Close() (err error) {
if p.pcap != nil {
p.pcap.Close()
} else {
err = p.conn.Close()
}
return
}

130
server_cmd.go Normal file
View File

@ -0,0 +1,130 @@
package main
import (
"net"
"os"
"os/signal"
"sync"
"syscall"
"github.com/coreos/go-systemd/daemon"
"github.com/kardianos/service"
log "github.com/sirupsen/logrus"
)
// Flags for the server command.
type ServerCmd struct {
}
// The main App structure.
type App struct {
Net struct {
Listeners []*Listener
sync.Mutex
}
ControllerMac net.HardwareAddr
grpcServer *GRPCServer
Stop chan struct{}
UpdateConfig *UpdateConfig
}
var app *App
// Run the server.
func (a *ServerCmd) Run() error {
// Start a new app structure.
app = new(App)
app.ControllerMac = net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
app.Stop = make(chan struct{})
{
// Read the configuration from file.
config := ReadConfig()
app.UpdateConfig = config.Update
// 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
}
}
// Send notification that the service is ready.
daemon.SdNotify(false, daemon.SdNotifyReady)
// Setup service.
if !service.Interactive() {
s := new(ServiceCmd)
svc, err := s.service()
if err != nil {
return err
}
go svc.Run()
}
// Run the update loop to check for updates.
go app.RunUpdateLoop()
// Inform that the service has started.
log.Println("Service started.")
// Monitor common signals.
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
// Run program signal handler.
for {
// If this service is stopped by signal, this flag will be changed.
done := false
// Check for a signal.
select {
case sig := <-c:
switch sig {
// If hangup signal receivied, reload the configurations.
case syscall.SIGHUP:
log.Println("Reloading configurations.")
// Read the config.
config := ReadConfig()
app.UpdateConfig = config.Update
// Apply any changes.
err := ApplyConfig(config)
if err != nil {
log.Println(err)
}
// The default signal is either termination or interruption,
// so we should stop the loop.
default:
done = true
}
// If the app stops itself, mark as done.
case <-app.Stop:
done = true
}
if done {
close(app.Stop)
break
}
}
// We're quitting, close out all listeners.
for len(app.Net.Listeners) >= 1 {
app.Net.Listeners[0].Close()
}
// Stop the grpc server.
if app.grpcServer != nil {
app.grpcServer.Close()
}
return nil
}

77
service_cmd.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"fmt"
"github.com/kardianos/service"
)
var ServiceAction = []string{"start", "stop", "status", "restart", "install", "uninstall"}
// Command to manage this service.
type ServiceCmd struct {
Action struct {
Action string `arg:"" enum:"${serviceActions}" help:"${serviceActions}" required:""`
} `arg:""`
}
func (s *ServiceCmd) action() string {
return s.Action.Action
}
func (s *ServiceCmd) Run() (err error) {
svc, err := s.service()
if err != nil {
return err
}
switch s.action() {
case ServiceAction[0]:
err = svc.Start()
case ServiceAction[1]:
err = svc.Stop()
case ServiceAction[2]:
status, err := svc.Status()
if err == nil {
switch status {
case service.StatusRunning:
fmt.Println("Service is running.")
case service.StatusStopped:
fmt.Println("Service is stopped.")
default:
fmt.Println("Service is in an unknown state.")
}
}
case ServiceAction[3]:
err = svc.Restart()
case ServiceAction[4]:
err = svc.Install()
case ServiceAction[5]:
err = svc.Uninstall()
}
if err != nil {
return err
}
if s.action() != ServiceAction[2] {
fmt.Println("Command executed successfully.")
}
return
}
func (s *ServiceCmd) service() (service.Service, error) {
svcConfig := &service.Config{
Name: serviceName,
DisplayName: serviceDisplayName,
Description: serviceDescription,
Arguments: []string{"server"},
}
return service.New(s, svcConfig)
}
func (s *ServiceCmd) Start(svc service.Service) error {
return nil
}
func (s *ServiceCmd) Stop(svc service.Service) error {
app.Stop <- struct{}{}
return nil
}

21
sysroot/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM debian:bookworm
ARG PCAP_VER=1.10.5
RUN apt-get update; \
apt-get --no-install-recommends -y -q install \
build-essential \
flex \
bison \
ca-certificates \
wget
RUN wget http://www.tcpdump.org/release/libpcap-${PCAP_VER}.tar.gz && \
tar xvf libpcap-${PCAP_VER}.tar.gz && \
cd libpcap-${PCAP_VER} && \
./configure --with-pcap=linux --prefix=/usr/ \
--disable-usb --disable-bluetooth --disable-dbus --disable-rdma --disable-shared && \
make && \
make install && \
cd ../ && \
rm -Rf libpcap-${PCAP_VER}

18
sysroot/README.md Normal file
View File

@ -0,0 +1,18 @@
# Building sysroot
- Install qemu binfmt support, and install docker with the buildx extension.
- Ensure qemu is registered with:
```bash
ls /proc/sys/fs/binfmt_misc/
```
- Setup buildx:
```bash
docker buildx create --name mybuilder --use
```
- Build the sysroot:
```bash
./build.sh
```

131
sysroot/build.sh Executable file
View File

@ -0,0 +1,131 @@
#!/bin/env bash
# Ensure we're in this directory when running the script.
cd -P -- "$(dirname -- "$0")" || exit 1
# Remove existing builds.
rm -Rf ./linux_*/
# Build the different sysroots in docker.
if ! docker buildx build --platform=linux/amd64,linux/arm64 --tag 'cross-sysroot:latest' --output 'type=local,dest=.' .; then
echo "Failed to build sysroot, please review sysroot/README.md for instructions on configuring environment."
exit 1
fi
# Setup exclude and include list for minimal sysroot building.
exclude_list=()
include_list=()
exclude_list+=(--exclude "/bin")
exclude_list+=(--exclude "/boot")
exclude_list+=(--exclude "/boot*")
exclude_list+=(--exclude "/dev")
exclude_list+=(--exclude "/etc")
exclude_list+=(--exclude "/home")
exclude_list+=(--exclude "/lib/dhcpd")
exclude_list+=(--exclude "/lib/firmware")
exclude_list+=(--exclude "/lib/hdparm")
exclude_list+=(--exclude "/lib/ifupdown")
exclude_list+=(--exclude "/lib/modules")
exclude_list+=(--exclude "/lib/modprobe.d")
exclude_list+=(--exclude "/lib/modules-load.d")
exclude_list+=(--exclude "/lib/resolvconf")
exclude_list+=(--exclude "/lib/startpar")
exclude_list+=(--exclude "/lib/systemd")
exclude_list+=(--exclude "/lib/terminfo")
exclude_list+=(--exclude "/lib/udev")
exclude_list+=(--exclude "/lib/xtables")
exclude_list+=(--exclude "/lib/ssl/private")
exclude_list+=(--exclude "/lost+found")
exclude_list+=(--exclude "/media")
exclude_list+=(--exclude "/mnt")
exclude_list+=(--exclude "/proc")
exclude_list+=(--exclude "/root")
exclude_list+=(--exclude "/run")
exclude_list+=(--exclude "/sbin")
exclude_list+=(--exclude "/srv")
exclude_list+=(--exclude "/sys")
exclude_list+=(--exclude "/tmp")
exclude_list+=(--exclude "/usr/bin")
exclude_list+=(--exclude "/usr/games")
exclude_list+=(--exclude "/usr/sbin")
exclude_list+=(--exclude "/usr/share")
exclude_list+=(--exclude "/usr/src")
exclude_list+=(--exclude "/usr/local/bin")
exclude_list+=(--exclude "/usr/local/etc")
exclude_list+=(--exclude "/usr/local/games")
exclude_list+=(--exclude "/usr/local/man")
exclude_list+=(--exclude "/usr/local/sbin")
exclude_list+=(--exclude "/usr/local/share")
exclude_list+=(--exclude "/usr/local/src")
exclude_list+=(--exclude "/usr/lib/ssl/private")
exclude_list+=(--exclude "/var")
exclude_list+=(--exclude "/snap")
exclude_list+=(--exclude "*python*")
include_list+=(--include "*.a")
include_list+=(--include "*.so")
include_list+=(--include "*.so.*")
include_list+=(--include "*.h")
include_list+=(--include "*.hh")
include_list+=(--include "*.hpp")
include_list+=(--include "*.hxx")
include_list+=(--include "*.pc")
include_list+=(--include "/lib")
include_list+=(--include "/lib32")
include_list+=(--include "/lib64")
include_list+=(--include "/libx32")
include_list+=(--include "*/")
args=()
args+=(-a)
args+=(-z)
args+=(-m)
args+=(-d)
args+=(-h)
args+=(--keep-dirlinks)
args+=("--info=progress2")
args+=(--delete)
args+=(--prune-empty-dirs)
args+=(--sparse)
args+=(--links)
args+=(--copy-unsafe-links)
args+=("${exclude_list[@]}")
args+=("${include_list[@]}")
args+=(--exclude "*")
# Make the sysroot environments minimal.
echo "Making sysroot the bare minimal."
for arch in linux_*; do
# Skip already minimal dirs.
if [[ $arch =~ _minimal ]]; then
continue
fi
# Fix symbolic links.
(
cd "$arch" || exit 1
for slink in ./usr/lib64/ld-linux-x86-64.so.2 ./usr/lib/*-linux-gnu/*.so; do
if [[ -L $slink ]]; then
spath=$(readlink "$slink")
if grep -q "/${arch}/" <<<"$spath"; then
npath=$(sed "s/\/$arch\//.\//" <<<"$spath")
rpath=$(realpath -m --relative-to="$(dirname "$slink")" "$npath")
unlink "$slink"
ln -s "$rpath" "$slink"
fi
fi
done
)
# Run rsync to minimal.
rsync "${args[@]}" "$arch/" "${arch}_minimal/"
rm -Rf "${arch:?}/"
mv "${arch}_minimal/" "$arch/"
done
# Clear build cache.
echo "Clearing build cache."
docker buildx prune --all --force
echo "Done building sysroot."

12
tun/errors.go Normal file
View File

@ -0,0 +1,12 @@
package tun
import (
"errors"
)
var (
// ErrTooManySegments is returned by Device.Read() when segmentation
// overflows the length of supplied buffers. This error should not cause
// reads to cease.
ErrTooManySegments = errors.New("too many segments")
)

53
tun/tun.go Normal file
View File

@ -0,0 +1,53 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
*/
package tun
import (
"net/netip"
"os"
)
type Event int
const (
EventUp = 1 << iota
EventDown
EventMTUUpdate
)
type Device interface {
// File returns the file descriptor of the device.
File() *os.File
// Read one packet from the Device (without any additional headers).
// On a successful read it returns the number of bytes read.
Read(b []byte) (int, error)
// Write one packet to the device (without any additional headers).
// On a successful write it returns the number of bytes written.
Write(b []byte) (int, error)
// Set MTU on the device.
SetMTU(int) error
// MTU returns the MTU of the Device.
MTU() (int, error)
// Name returns the current name of the Device.
Name() (string, error)
// Set IP Addresses for the device.
SetIPAddresses(addresses []netip.Prefix) error
// Get IP Addresses for the device.
GetIPAddresses() ([]netip.Prefix, error)
// Events returns a channel of type Event, which is fed Device events.
Events() <-chan Event
// Close stops the Device and closes the Event channel.
Close() error
}

562
tun/tun_linux.go Normal file
View File

@ -0,0 +1,562 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
*/
package tun
/* Implementation of the TUN device interface for linux
*/
import (
"errors"
"fmt"
"net/netip"
"os"
"sync"
"syscall"
"time"
"unsafe"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/rwcancel"
)
const (
cloneDevicePath = "/dev/net/tun"
ifReqSize = unix.IFNAMSIZ + 64
)
type NativeTun struct {
tunFile *os.File
index int32 // if index
errors chan error // async error handling
events chan Event // device related events
netlinkSock int
netlinkCancel *rwcancel.RWCancel
hackListenerClosed sync.Mutex
statusListenersShutdown chan struct{}
closeOnce sync.Once
nameOnce sync.Once // guards calling initNameCache, which sets following fields
nameCache string // name of interface
nameErr error
}
func (tun *NativeTun) File() *os.File {
return tun.tunFile
}
func (tun *NativeTun) routineHackListener() {
defer tun.hackListenerClosed.Unlock()
/* This is needed for the detection to work across network namespaces
* If you are reading this and know a better method, please get in touch.
*/
last := 0
const (
up = 1
down = 2
)
for {
sysconn, err := tun.tunFile.SyscallConn()
if err != nil {
return
}
err2 := sysconn.Control(func(fd uintptr) {
_, err = unix.Write(int(fd), nil)
})
if err2 != nil {
return
}
switch err {
case unix.EINVAL:
if last != up {
// If the tunnel is up, it reports that write() is
// allowed but we provided invalid data.
tun.events <- EventUp
last = up
}
case unix.EIO:
if last != down {
// If the tunnel is down, it reports that no I/O
// is possible, without checking our provided data.
tun.events <- EventDown
last = down
}
default:
return
}
select {
case <-time.After(time.Second):
// nothing
case <-tun.statusListenersShutdown:
return
}
}
}
func createNetlinkSocket() (int, error) {
sock, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC, unix.NETLINK_ROUTE)
if err != nil {
return -1, err
}
saddr := &unix.SockaddrNetlink{
Family: unix.AF_NETLINK,
Groups: unix.RTMGRP_LINK | unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR,
}
err = unix.Bind(sock, saddr)
if err != nil {
return -1, err
}
return sock, nil
}
func (tun *NativeTun) routineNetlinkListener() {
defer func() {
unix.Close(tun.netlinkSock)
tun.hackListenerClosed.Lock()
close(tun.events)
tun.netlinkCancel.Close()
}()
for msg := make([]byte, 1<<16); ; {
var err error
var msgn int
for {
msgn, _, _, _, err = unix.Recvmsg(tun.netlinkSock, msg[:], nil, 0)
if err == nil || !rwcancel.RetryAfterError(err) {
break
}
if !tun.netlinkCancel.ReadyRead() {
tun.errors <- fmt.Errorf("netlink socket closed: %w", err)
return
}
}
if err != nil {
tun.errors <- fmt.Errorf("failed to receive netlink message: %w", err)
return
}
select {
case <-tun.statusListenersShutdown:
return
default:
}
wasEverUp := false
for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
if int(hdr.Len) > len(remain) {
break
}
switch hdr.Type {
case unix.NLMSG_DONE:
remain = []byte{}
case unix.RTM_NEWLINK:
info := *(*unix.IfInfomsg)(unsafe.Pointer(&remain[unix.SizeofNlMsghdr]))
remain = remain[hdr.Len:]
if info.Index != tun.index {
// not our interface
continue
}
if info.Flags&unix.IFF_RUNNING != 0 {
tun.events <- EventUp
wasEverUp = true
}
if info.Flags&unix.IFF_RUNNING == 0 {
// Don't emit EventDown before we've ever emitted EventUp.
// This avoids a startup race with HackListener, which
// might detect Up before we have finished reporting Down.
if wasEverUp {
tun.events <- EventDown
}
}
tun.events <- EventMTUUpdate
default:
remain = remain[hdr.Len:]
}
}
}
}
func getIFIndex(name string) (int32, error) {
fd, err := unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM|unix.SOCK_CLOEXEC,
0,
)
if err != nil {
return 0, err
}
defer unix.Close(fd)
var ifr [ifReqSize]byte
copy(ifr[:], name)
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.SIOCGIFINDEX),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return 0, errno
}
return *(*int32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])), nil
}
func (tun *NativeTun) setMTU(n int) error {
name, err := tun.Name()
if err != nil {
return err
}
// open datagram socket
fd, err := unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM|unix.SOCK_CLOEXEC,
0,
)
if err != nil {
return err
}
defer unix.Close(fd)
// do ioctl call
var ifr [ifReqSize]byte
copy(ifr[:], name)
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.SIOCSIFMTU),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return fmt.Errorf("failed to set MTU of TUN device: %w", errno)
}
return nil
}
func (tun *NativeTun) SetMTU(mtu int) error {
return tun.setMTU(mtu)
}
func (tun *NativeTun) SetIPAddresses(addresses []netip.Prefix) error {
link, err := netlink.LinkByIndex(int(tun.index))
if err != nil {
return err
}
var setAddr []*netlink.Addr
for _, address := range addresses {
addr, err := netlink.ParseAddr(address.String())
if err != nil {
return err
}
setAddr = append(setAddr, addr)
}
addrList, err := netlink.AddrList(link, netlink.FAMILY_ALL)
for _, address := range addrList {
found := -1
for i, addr := range setAddr {
if addr.Equal(address) {
found = i
}
}
if found == -1 {
err = netlink.AddrDel(link, &address)
if err != nil {
return err
}
} else {
setAddr = append(setAddr[:found], setAddr[found+1:]...)
}
}
for _, addr := range setAddr {
err = netlink.AddrAdd(link, addr)
if err != nil {
return err
}
}
return nil
}
func (tun *NativeTun) GetIPAddresses() ([]netip.Prefix, error) {
link, err := netlink.LinkByIndex(int(tun.index))
if err != nil {
return nil, err
}
var prefixes []netip.Prefix
addrList, err := netlink.AddrList(link, netlink.FAMILY_ALL)
for _, address := range addrList {
prefix, err := netip.ParsePrefix(address.String())
if err != nil {
return nil, err
}
prefixes = append(prefixes, prefix)
}
return prefixes, nil
}
func (tun *NativeTun) MTU() (int, error) {
name, err := tun.Name()
if err != nil {
return 0, err
}
// open datagram socket
fd, err := unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM|unix.SOCK_CLOEXEC,
0,
)
if err != nil {
return 0, err
}
defer unix.Close(fd)
// do ioctl call
var ifr [ifReqSize]byte
copy(ifr[:], name)
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.SIOCGIFMTU),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return 0, fmt.Errorf("failed to get MTU of TUN device: %w", errno)
}
return int(*(*int32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ]))), nil
}
func (tun *NativeTun) Name() (string, error) {
tun.nameOnce.Do(tun.initNameCache)
return tun.nameCache, tun.nameErr
}
func (tun *NativeTun) initNameCache() {
tun.nameCache, tun.nameErr = tun.nameSlow()
}
func (tun *NativeTun) nameSlow() (string, error) {
sysconn, err := tun.tunFile.SyscallConn()
if err != nil {
return "", err
}
var ifr [ifReqSize]byte
var errno syscall.Errno
err = sysconn.Control(func(fd uintptr) {
_, _, errno = unix.Syscall(
unix.SYS_IOCTL,
fd,
uintptr(unix.TUNGETIFF),
uintptr(unsafe.Pointer(&ifr[0])),
)
})
if err != nil {
return "", fmt.Errorf("failed to get name of TUN device: %w", err)
}
if errno != 0 {
return "", fmt.Errorf("failed to get name of TUN device: %w", errno)
}
return unix.ByteSliceToString(ifr[:]), nil
}
func (tun *NativeTun) Write(b []byte) (int, error) {
n, err := tun.tunFile.Write(b)
if errors.Is(err, syscall.EBADFD) {
return 0, os.ErrClosed
}
if err != nil {
return 0, err
}
return n, nil
}
func (tun *NativeTun) Read(b []byte) (int, error) {
select {
case err := <-tun.errors:
return 0, err
default:
n, err := tun.tunFile.Read(b)
if errors.Is(err, syscall.EBADFD) {
err = os.ErrClosed
}
if err != nil {
return 0, err
}
return n, nil
}
}
func (tun *NativeTun) Events() <-chan Event {
return tun.events
}
func (tun *NativeTun) Close() error {
var err1, err2 error
tun.closeOnce.Do(func() {
if tun.statusListenersShutdown != nil {
close(tun.statusListenersShutdown)
if tun.netlinkCancel != nil {
err1 = tun.netlinkCancel.Cancel()
}
} else if tun.events != nil {
close(tun.events)
}
err2 = tun.tunFile.Close()
})
if err1 != nil {
return err1
}
return err2
}
func (tun *NativeTun) initFromFlags(name string) error {
sc, err := tun.tunFile.SyscallConn()
if err != nil {
return err
}
if e := sc.Control(func(fd uintptr) {
var (
ifr *unix.Ifreq
)
ifr, err = unix.NewIfreq(name)
if err != nil {
return
}
err = unix.IoctlIfreq(int(fd), unix.TUNGETIFF, ifr)
if err != nil {
return
}
}); e != nil {
return e
}
return err
}
// CreateTUN creates a Device with the provided name and MTU.
func CreateTUN(name string, mtu int) (Device, error) {
nfd, err := unix.Open(cloneDevicePath, unix.O_RDWR|unix.O_CLOEXEC, 0)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("CreateTUN(%q) failed; %s does not exist", name, cloneDevicePath)
}
return nil, err
}
ifr, err := unix.NewIfreq(name)
if err != nil {
return nil, err
}
// IFF_VNET_HDR enables the "tun status hack" via routineHackListener()
// where a null write will return EINVAL indicating the TUN is up.
ifr.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_VNET_HDR)
err = unix.IoctlIfreq(nfd, unix.TUNSETIFF, ifr)
if err != nil {
return nil, err
}
err = unix.SetNonblock(nfd, true)
if err != nil {
unix.Close(nfd)
return nil, err
}
// Note that the above -- open,ioctl,nonblock -- must happen prior to handing it to netpoll as below this line.
fd := os.NewFile(uintptr(nfd), cloneDevicePath)
return CreateTUNFromFile(fd, mtu)
}
// CreateTUNFromFile creates a Device from an os.File with the provided MTU.
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
tun := &NativeTun{
tunFile: file,
events: make(chan Event, 5),
errors: make(chan error, 5),
statusListenersShutdown: make(chan struct{}),
}
name, err := tun.Name()
if err != nil {
return nil, err
}
err = tun.initFromFlags(name)
if err != nil {
return nil, err
}
// start event listener
tun.index, err = getIFIndex(name)
if err != nil {
return nil, err
}
tun.netlinkSock, err = createNetlinkSocket()
if err != nil {
return nil, err
}
tun.netlinkCancel, err = rwcancel.NewRWCancel(tun.netlinkSock)
if err != nil {
unix.Close(tun.netlinkSock)
return nil, err
}
tun.hackListenerClosed.Lock()
go tun.routineNetlinkListener()
go tun.routineHackListener() // cross namespace
err = tun.setMTU(mtu)
if err != nil {
unix.Close(tun.netlinkSock)
return nil, err
}
return tun, nil
}
// CreateUnmonitoredTUNFromFD creates a Device from the provided file
// descriptor.
func CreateUnmonitoredTUNFromFD(fd int) (Device, string, error) {
err := unix.SetNonblock(fd, true)
if err != nil {
return nil, "", err
}
file := os.NewFile(uintptr(fd), "/dev/tun")
tun := &NativeTun{
tunFile: file,
events: make(chan Event, 5),
errors: make(chan error, 5),
}
name, err := tun.Name()
if err != nil {
return nil, "", err
}
err = tun.initFromFlags(name)
if err != nil {
return nil, "", err
}
return tun, name, err
}

336
tun/tun_windows.go Normal file
View File

@ -0,0 +1,336 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
*/
package tun
import (
"crypto/md5"
"errors"
"fmt"
"net/netip"
"os"
"sync"
"sync/atomic"
"time"
"unsafe"
_ "unsafe"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wintun"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
const (
rateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond)
spinloopRateThreshold = 800000000 / 8 // 800mbps
spinloopDuration = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s
)
type rateJuggler struct {
current atomic.Uint64
nextByteCount atomic.Uint64
nextStartTime atomic.Int64
changing atomic.Bool
}
type NativeTun struct {
wt *wintun.Adapter
name string
handle windows.Handle
rate rateJuggler
session wintun.Session
readWait windows.Handle
events chan Event
running sync.WaitGroup
closeOnce sync.Once
close atomic.Bool
mtu int
}
var (
WintunTunnelType = "vxlan"
WintunGUIDPrefix = "virtual-vxlan Windows GUID v1"
)
//go:linkname procyield runtime.procyield
func procyield(cycles uint32)
//go:linkname nanotime runtime.nanotime
func nanotime() int64
// Create an 128bit GUID using interface name.
func generateGUIDByDeviceName(name string) (*windows.GUID, error) {
hash := md5.New()
_, err := hash.Write([]byte(WintunGUIDPrefix + name))
if err != nil {
return nil, err
}
sum := hash.Sum(nil)
return (*windows.GUID)(unsafe.Pointer(&sum[0])), nil
}
// CreateTUN creates a Wintun interface with the given name. Should a Wintun
// interface with the same name exist, it is reused.
func CreateTUN(ifname string, mtu int) (Device, error) {
guid, err := generateGUIDByDeviceName(ifname)
if err != nil {
return nil, err
}
return CreateTUNWithRequestedGUID(ifname, guid, mtu)
}
// CreateTUNWithRequestedGUID creates a Wintun interface with the given name and
// a requested GUID. Should a Wintun interface with the same name exist, it is reused.
func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int) (Device, error) {
wt, err := wintun.CreateAdapter(ifname, WintunTunnelType, requestedGUID)
if err != nil {
return nil, fmt.Errorf("error creating interface: %w", err)
}
forcedMTU := 1420
if mtu > 0 {
forcedMTU = mtu
}
tun := &NativeTun{
wt: wt,
name: ifname,
handle: windows.InvalidHandle,
events: make(chan Event, 10),
mtu: forcedMTU,
}
err = tun.setMTU(windows.AF_INET, forcedMTU)
if err != nil {
wt.Close()
close(tun.events)
return nil, fmt.Errorf("error setting MTU for IPv4: %w", err)
}
err = tun.setMTU(windows.AF_INET6, forcedMTU)
if err != nil {
wt.Close()
close(tun.events)
return nil, fmt.Errorf("error setting MTU for IPv6: %w", err)
}
tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB
if err != nil {
tun.wt.Close()
close(tun.events)
return nil, fmt.Errorf("error starting session: %w", err)
}
tun.readWait = tun.session.ReadWaitEvent()
return tun, nil
}
func (tun *NativeTun) Name() (string, error) {
return tun.name, nil
}
func (tun *NativeTun) File() *os.File {
return nil
}
func (tun *NativeTun) Events() <-chan Event {
return tun.events
}
func (tun *NativeTun) Close() error {
var err error
tun.closeOnce.Do(func() {
tun.close.Store(true)
windows.SetEvent(tun.readWait)
tun.running.Wait()
tun.session.End()
if tun.wt != nil {
tun.wt.Close()
}
close(tun.events)
})
return err
}
func (tun *NativeTun) setMTU(family winipcfg.AddressFamily, mtu int) error {
luid := winipcfg.LUID(tun.LUID())
ipif, err := luid.IPInterface(family)
if err != nil {
return err
}
ipif.NLMTU = uint32(mtu)
return ipif.Set()
}
func (tun *NativeTun) SetMTU(mtu int) error {
if tun.close.Load() {
return nil
}
err := tun.setMTU(windows.AF_INET, mtu)
if err != nil {
return fmt.Errorf("error setting MTU for IPv4: %v", err)
}
err = tun.setMTU(windows.AF_INET6, mtu)
if err != nil {
return fmt.Errorf("error setting MTU for IPv6: %v", err)
}
update := tun.mtu != mtu
tun.mtu = mtu
if update {
tun.events <- EventMTUUpdate
}
return nil
}
func (tun *NativeTun) MTU() (int, error) {
return tun.mtu, nil
}
// Note: Read() and Write() assume the caller comes only from a single thread; there's no locking.
func (tun *NativeTun) Read(b []byte) (int, error) {
tun.running.Add(1)
defer tun.running.Done()
retry:
if tun.close.Load() {
return 0, os.ErrClosed
}
start := nanotime()
shouldSpin := tun.rate.current.Load() >= spinloopRateThreshold && uint64(start-tun.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2
for {
if tun.close.Load() {
return 0, os.ErrClosed
}
packet, err := tun.session.ReceivePacket()
switch err {
case nil:
n := copy(b, packet)
tun.session.ReleaseReceivePacket(packet)
tun.rate.update(uint64(n))
return n, nil
case windows.ERROR_NO_MORE_ITEMS:
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
windows.WaitForSingleObject(tun.readWait, windows.INFINITE)
goto retry
}
procyield(1)
continue
case windows.ERROR_HANDLE_EOF:
return 0, os.ErrClosed
case windows.ERROR_INVALID_DATA:
return 0, errors.New("send ring corrupt")
}
return 0, fmt.Errorf("Read failed: %w", err)
}
}
func (tun *NativeTun) Write(b []byte) (int, error) {
tun.running.Add(1)
defer tun.running.Done()
if tun.close.Load() {
return 0, os.ErrClosed
}
packetSize := len(b)
tun.rate.update(uint64(packetSize))
packet, err := tun.session.AllocateSendPacket(packetSize)
switch err {
case nil:
// TODO: Explore options to eliminate this copy.
copy(packet, b)
tun.session.SendPacket(packet)
return packetSize, nil
case windows.ERROR_HANDLE_EOF:
return 0, os.ErrClosed
case windows.ERROR_BUFFER_OVERFLOW:
return 0, nil // Dropping when ring is full.
}
return 0, fmt.Errorf("Write failed: %w", err)
}
// LUID returns Windows interface instance ID.
func (tun *NativeTun) LUID() uint64 {
tun.running.Add(1)
defer tun.running.Done()
if tun.close.Load() {
return 0
}
return tun.wt.LUID()
}
func (tun *NativeTun) SetIPAddresses(addresses []netip.Prefix) error {
luid := winipcfg.LUID(tun.LUID())
err := luid.SetIPAddresses(addresses)
if err != nil {
return fmt.Errorf("failed to set address: %w", err)
}
return nil
}
func addrFromSocketAddress(sockAddr windows.SocketAddress) netip.Addr {
ip := sockAddr.IP()
ip4 := ip.To4()
var addr netip.Addr
if ip4 != nil {
addr = netip.AddrFrom4([4]byte(ip4))
} else {
addr = netip.AddrFrom16([16]byte(ip))
}
return addr
}
func (tun *NativeTun) GetIPAddresses() ([]netip.Prefix, error) {
luid := winipcfg.LUID(tun.LUID())
ipAdaters, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagSkipAnycast|winipcfg.GAAFlagSkipMulticast|winipcfg.GAAFlagSkipDNSServer|winipcfg.GAAFlagSkipFriendlyName|winipcfg.GAAFlagSkipDNSInfo)
if err != nil {
return nil, fmt.Errorf("failed to get IP adapters: %w", err)
}
var prefixes []netip.Prefix
for _, ipAdater := range ipAdaters {
if ipAdater.LUID == luid {
unicast := ipAdater.FirstUnicastAddress
for unicast != nil {
addr := addrFromSocketAddress(unicast.Address)
prefix := ipAdater.FirstPrefix
for prefix != nil {
pAddr := addrFromSocketAddress(prefix.Address)
if (pAddr.Is4() && prefix.PrefixLength != 32) || (pAddr.Is6() && prefix.PrefixLength != 128) {
nPrefix := netip.PrefixFrom(pAddr, int(prefix.PrefixLength))
if nPrefix.Contains(addr) {
prefixes = append(prefixes, netip.PrefixFrom(addr, int(prefix.PrefixLength)))
prefix = nil
continue
}
}
prefix = prefix.Next
}
unicast = unicast.Next
}
}
}
return prefixes, nil
}
// RunningVersion returns the running version of the Wintun driver.
func (tun *NativeTun) RunningVersion() (version uint32, err error) {
return wintun.RunningVersion()
}
func (rate *rateJuggler) update(packetLen uint64) {
now := nanotime()
total := rate.nextByteCount.Add(packetLen)
period := uint64(now - rate.nextStartTime.Load())
if period >= rateMeasurementGranularity {
if !rate.changing.CompareAndSwap(false, true) {
return
}
rate.nextStartTime.Store(now)
rate.current.Store(total * uint64(time.Second/time.Nanosecond) / period)
rate.nextByteCount.Store(0)
rate.changing.Store(false)
}
}

10
update_cmd.go Normal file
View File

@ -0,0 +1,10 @@
package main
// Command to manage this service.
type UpdateCmd struct{}
func (s *UpdateCmd) Run() (err error) {
config := ReadMinimalConfig()
CheckForUpdate(config.Update, false)
return
}

262
updater.go Normal file
View File

@ -0,0 +1,262 @@
package main
import (
"bufio"
"context"
"errors"
"fmt"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/coreos/go-systemd/daemon"
"github.com/creativeprojects/go-selfupdate"
"github.com/hashicorp/go-version"
log "github.com/sirupsen/logrus"
)
// Check for update and update if one is available.
func Update(c *UpdateConfig) error {
log.Println("Checking for update.")
// Setup source.
source, err := selfupdate.NewGitHubSource(selfupdate.GitHubConfig{})
if err != nil {
return err
}
// Get the path to ourself.
exe, err := selfupdate.ExecutablePath()
if err != nil {
return fmt.Errorf("could not locate executable path: %s", err)
}
updateDir, cmd := filepath.Split(exe)
oldSavePath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", cmd))
// Get updater with source and validator.
updater, err := selfupdate.NewUpdater(selfupdate.Config{
Source: source,
Validator: &selfupdate.ChecksumValidator{UniqueFilename: "checksums.txt"},
OldSavePath: oldSavePath,
})
if err != nil {
return err
}
// Find the latest release.
release, found, err := updater.DetectLatest(context.Background(), selfupdate.NewRepositorySlug(c.Owner, c.Repo))
if err != nil {
return err
}
if !found {
log.Println("No updates available.")
return nil
}
// Compare the versions.
thisVersion, err := version.NewVersion(c.CurrentVersion)
if err != nil {
return err
}
latestVersion, err := version.NewVersion(release.Version())
if err != nil {
return err
}
// If an update isn't available, end.
if !thisVersion.LessThan(latestVersion) {
log.Println("No updates available.")
return nil
}
log.Println("Updating to version:", release.Version())
// We're updating, tell the app so services can be stopped.
c.PreUpdate()
// Perform the update.
err = updater.UpdateTo(context.Background(), release, exe)
// If update failed, rollback and tell the app we failed.
abortUpdate:
if err != nil {
rerr := os.Rename(oldSavePath, exe)
if rerr != nil {
log.Println("Failed to rollback update:", rerr)
}
c.AbortUpdate()
return err
}
log.Println("Updated.")
// If relaunch is requested, then start the new exe and confirm it works.
if c.ShouldRelaunch {
// Make process and get its stdout/stderr.
log.Println("Starting new process.")
p := exec.Command(exe, os.Args[1:]...)
p.Env = os.Environ()
p.Env = append(p.Env, "UPDATER_UPDATE=1")
stdout, err := p.StdoutPipe()
if err != nil {
goto abortUpdate
}
stderr, err := p.StderrPipe()
if err != nil {
goto abortUpdate
}
// Start process.
err = p.Start()
if err != nil {
goto abortUpdate
}
// Set timeout to kill process.
timer := time.AfterFunc(c.StartupTimeout, func() {
p.Process.Kill()
})
// Channels to confirm success or failure.
success := make(chan bool)
failure := make(chan error)
// Scan the stdout for success message.
go func() {
stdoutScanner := bufio.NewScanner(stdout)
for stdoutScanner.Scan() {
line := stdoutScanner.Text()
fmt.Fprintln(os.Stdout, line)
if c.IsSuccessMsg(line) {
success <- true
return
}
}
}()
// Scan the stderr for sucess message.
go func() {
stderrScanner := bufio.NewScanner(stderr)
for stderrScanner.Scan() {
line := stderrScanner.Text()
fmt.Fprintln(os.Stderr, line)
if c.IsSuccessMsg(line) {
success <- true
return
}
}
}()
// Wait for command exit, and pass errors.
go func() {
err := p.Wait()
failure <- err
}()
// Wait for one of the channels to be sent information.
select {
case <-success:
case err = <-failure:
if err == nil {
err = errors.New("program exited without error")
}
}
// Stop the timer as the process is either going to be left running or is stopped.
timer.Stop()
// If stop due to error, abort.
if err != nil {
goto abortUpdate
}
// The update was successful, so we can remove the old binary.
os.Remove(oldSavePath)
// Set the new process stdio to ours.
p.Stdout = os.Stdout
p.Stderr = os.Stderr
p.Stdin = os.Stdin
// Notify systemd to watch the new process.
// Those babysitting fees are too high to have it watch us too.
// Amy said SystemD doesn't do babysitting.
daemon.SdNotify(false, fmt.Sprintf("MAINPID=%d", p.Process.Pid))
// Quit this process as the new process will continue running as a fork.
os.Exit(0)
}
// The update was successful, so we can remove the old binary.
os.Remove(oldSavePath)
return nil
}
// Check for updates, and apply.
func CheckForUpdate(c *UpdateConfig, relaunch bool) {
// Set update config local variables.
c.CurrentVersion = serviceVersion
c.ShouldRelaunch = relaunch
c.PreUpdate = func() {
// Stop all listeners to allow updated service to start.
for len(app.Net.Listeners) >= 1 {
app.Net.Listeners[0].Close()
}
// Stop the grpc server.
if app.grpcServer != nil {
app.grpcServer.Close()
}
}
c.IsSuccessMsg = func(msg string) bool {
if strings.Contains(msg, "Service started.") {
return true
}
return false
}
c.StartupTimeout = 5 * time.Minute
// If update is aborted, we should restart the service.
c.AbortUpdate = func() {
// Read the configuration from file.
config := ReadConfig()
// Start the GRPC server for cli communication.
_, err := NewGRPCServer(config.RPCPath)
if err != nil {
log.Fatalln(err)
}
// Apply the configuration read.
err = ApplyConfig(config)
// If error applying the config, we should fail.
if err != nil {
log.Fatalln(err)
}
}
err := Update(c)
if err != nil {
log.Println("Failure checking for update:", err)
}
}
// Every 24 hours, check for updates.
func (a *App) RunUpdateLoop() {
// If disabled, don't run loop.
if app.UpdateConfig.Disabled {
return
}
// Randomly check for updates at first start.
if os.Getenv("UPDATER_UPDATE") != "1" && rand.Intn(20) == 2 {
CheckForUpdate(app.UpdateConfig, true)
}
// Run update check every 24 hours.
for {
nextUpdate := time.Hour * 24
nextUpdate += time.Duration(rand.Intn(18000)) * time.Second
time.Sleep(nextUpdate)
CheckForUpdate(app.UpdateConfig, true)
}
}

20
utils.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"crypto/rand"
"net"
)
// Generate an random MAC in the locally administered OUI.
func generateRandomMAC() net.HardwareAddr {
// Start a new MAC address.
mac := make(net.HardwareAddr, 6)
// Just replace all bytes in MAC with random bytes.
rand.Read(mac)
// Set OUI to locally administered space.
mac[0] = 0x0a
mac[1] = 0x00
return mac
}

1743
vxlan/vxlan.pb.go Normal file

File diff suppressed because it is too large Load Diff

170
vxlan/vxlan.proto Normal file
View File

@ -0,0 +1,170 @@
syntax = "proto3";
option go_package = "github.com/grmrgecko/virtual-vxlan/vxlan";
package vxlan;
service vxlan {
// Config commands.
rpc SaveConfig (Empty) returns (Empty) {}
rpc ReloadConfig (Empty) returns (Empty) {}
// Listener commands.
rpc ListListeners (Empty) returns (ListListenersReply) {}
rpc AddListener (Listener) returns (Empty) {}
rpc RemoveListener (ListenerRequestWithName) returns (Empty) {}
rpc SetListenerMaxMessageSize (ListenerMaxMessageSizeRequest) returns (Empty) {}
rpc GetListenerMaxMessageSize (ListenerRequestWithName) returns (ListenerMaxMessageSizeReply) {}
// Interface commands.
rpc ListInterfaces (ListenerRequestWithName) returns (ListInterfacesReply) {}
rpc AddInterface (AddInterfaceRequest) returns (Empty) {}
rpc RemoveInterface (InterfaceRequestWithName) returns (Empty) {}
rpc SetInterfaceMTU (InterfaceMTURequest) returns (Empty) {}
rpc GetInterfaceMTU (InterfaceRequestWithName) returns (InterfaceMTUReply) {}
rpc SetInterfaceMACAddress (InterfaceMACAddressRequest) returns (Empty) {}
rpc GetInterfaceMACAddress (InterfaceRequestWithName) returns (InterfaceMACAddressReply) {}
rpc SetInterfaceIPAddresses (InterfaceIPAddressesRequest) returns (Empty) {}
rpc GetInterfaceIPAddresses (InterfaceRequestWithName) returns (InterfaceIPAddressesReply) {}
rpc InterfaceAddMACEntry (InterfaceMacEntryRequest) returns (Empty) {}
rpc InterfaceRemoveMACEntry (InterfaceRemoveMacEntryRequest) returns (Empty) {}
rpc InterfaceGetMACEntries (InterfaceRequestWithName) returns (InterfaceMacEntryReply) {}
rpc InterfaceFlushMACTable (InterfaceRequestWithName) returns (Empty) {}
rpc InterfaceAddStaticARPEntry (InterfaceARPEntryRequest) returns (Empty) {}
rpc InterfaceRemoveARPEntry (InterfaceRemoveARPEntryRequest) returns (Empty) {}
rpc InterfaceGetARPEntries (InterfaceRequestWithName) returns (InterfaceArpEntryReply) {}
rpc InterfaceFlushARPTable (InterfaceRequestWithName) returns (Empty) {}
}
// Standard empty message.
message Empty {
}
// Listener messages.
message ListenerRequestWithName {
string name = 1;
}
message Listener {
string name = 1;
string address = 2;
int32 maxMessageSize = 3;
bool permanent = 4;
}
message ListListenersReply {
repeated Listener listeners = 1;
}
message ListenerMaxMessageSizeRequest {
string name = 1;
int32 size = 2;
}
message ListenerMaxMessageSizeReply {
int32 size = 1;
}
// Interface messages.
message Interface {
string name = 1;
uint32 vni = 2;
int32 mtu = 3;
bool permanent = 4;
}
message ListInterfacesReply {
repeated Interface interfaces = 1;
}
message AddInterfaceRequest {
string listenerName = 1;
string name = 2;
uint32 vni = 3;
int32 mtu = 4;
bool permanent = 5;
}
message InterfaceRequestWithName {
string listenerName = 1;
string name = 2;
}
message InterfaceMTURequest {
string listenerName = 1;
string name = 2;
int32 mtu = 3;
}
message InterfaceMTUReply {
int32 mtu = 1;
}
message InterfaceMACAddressRequest {
string listenerName = 1;
string name = 2;
string mac = 3;
}
message InterfaceMACAddressReply {
string mac = 1;
}
message InterfaceIPAddressesRequest {
string listenerName = 1;
string name = 2;
repeated string ipAddress = 3;
}
message InterfaceIPAddressesReply {
repeated string ipAddress = 1;
}
message InterfaceMacEntryRequest {
string listenerName = 1;
string name = 2;
string mac = 3;
string destination = 4;
bool permanent = 5;
}
message InterfaceRemoveMacEntryRequest {
string listenerName = 1;
string name = 2;
string mac = 3;
string destination = 4;
}
message MacEntry {
string mac = 1;
string destination = 2;
bool permanent = 3;
}
message InterfaceMacEntryReply {
repeated MacEntry entries = 1;
}
message InterfaceARPEntryRequest {
string listenerName = 1;
string name = 2;
string address = 3;
string mac = 4;
bool permanent = 5;
}
message InterfaceRemoveARPEntryRequest {
string listenerName = 1;
string name = 2;
string address = 3;
}
message ArpEntry {
string address = 1;
string mac = 2;
string expires = 3;
bool permanent = 4;
}
message InterfaceArpEntryReply {
repeated ArpEntry entries = 1;
}

1001
vxlan/vxlan_grpc.pb.go Normal file

File diff suppressed because it is too large Load Diff

7
wintun-source.md Normal file
View File

@ -0,0 +1,7 @@
# Wintun
This driver was version [0.14.1](https://www.wintun.net/builds/wintun-0.14.1.zip) downloaded from [wintun.net](https://www.wintun.net/). The source code is provided under the GPL 2.0 and [is available via git](https://git.zx2c4.com/wintun):
```
git clone https://git.zx2c4.com/wintun
```

84
wintun/LICENSE.txt Normal file
View File

@ -0,0 +1,84 @@
Prebuilt Binaries License
-------------------------
1. DEFINITIONS. "Software" means the precise contents of the "wintun.dll"
files that are included in the .zip file that contains this document as
downloaded from wintun.net/builds.
2. LICENSE GRANT. WireGuard LLC grants to you a non-exclusive and
non-transferable right to use Software for lawful purposes under certain
obligations and limited rights as set forth in this agreement.
3. RESTRICTIONS. Software is owned and copyrighted by WireGuard LLC. It is
licensed, not sold. Title to Software and all associated intellectual
property rights are retained by WireGuard. You must not:
a. reverse engineer, decompile, disassemble, extract from, or otherwise
modify the Software;
b. modify or create derivative work based upon Software in whole or in
parts, except insofar as only the API interfaces of the "wintun.h" file
distributed alongside the Software (the "Permitted API") are used;
c. remove any proprietary notices, labels, or copyrights from the Software;
d. resell, redistribute, lease, rent, transfer, sublicense, or otherwise
transfer rights of the Software without the prior written consent of
WireGuard LLC, except insofar as the Software is distributed alongside
other software that uses the Software only via the Permitted API;
e. use the name of WireGuard LLC, the WireGuard project, the Wintun
project, or the names of its contributors to endorse or promote products
derived from the Software without specific prior written consent.
4. LIMITED WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF
ANY KIND. WIREGUARD LLC HEREBY EXCLUDES AND DISCLAIMS ALL IMPLIED OR
STATUTORY WARRANTIES, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE, QUALITY, NON-INFRINGEMENT, TITLE, RESULTS,
EFFORTS, OR QUIET ENJOYMENT. THERE IS NO WARRANTY THAT THE PRODUCT WILL BE
ERROR-FREE OR WILL FUNCTION WITHOUT INTERRUPTION. YOU ASSUME THE ENTIRE
RISK FOR THE RESULTS OBTAINED USING THE PRODUCT. TO THE EXTENT THAT
WIREGUARD LLC MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW,
THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER
SUCH LAW. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR
A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE
EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.
5. LIMITATION OF LIABILITY. To the extent not prohibited by law, in no event
WireGuard LLC or any third-party-developer will be liable for any lost
revenue, profit or data or for special, indirect, consequential, incidental
or punitive damages, however caused regardless of the theory of liability,
arising out of or related to the use of or inability to use Software, even
if WireGuard LLC has been advised of the possibility of such damages.
Solely you are responsible for determining the appropriateness of using
Software and accept full responsibility for all risks associated with its
exercise of rights under this agreement, including but not limited to the
risks and costs of program errors, compliance with applicable laws, damage
to or loss of data, programs or equipment, and unavailability or
interruption of operations. The foregoing limitations will apply even if
the above stated warranty fails of its essential purpose. You acknowledge,
that it is in the nature of software that software is complex and not
completely free of errors. In no event shall WireGuard LLC or any
third-party-developer be liable to you under any theory for any damages
suffered by you or any user of Software or for any special, incidental,
indirect, consequential or similar damages (including without limitation
damages for loss of business profits, business interruption, loss of
business information or any other pecuniary loss) arising out of the use or
inability to use Software, even if WireGuard LLC has been advised of the
possibility of such damages and regardless of the legal or quitable theory
(contract, tort, or otherwise) upon which the claim is based.
6. TERMINATION. This agreement is affected until terminated. You may
terminate this agreement at any time. This agreement will terminate
immediately without notice from WireGuard LLC if you fail to comply with
the terms and conditions of this agreement. Upon termination, you must
delete Software and all copies of Software and cease all forms of
distribution of Software.
7. SEVERABILITY. If any provision of this agreement is held to be
unenforceable, this agreement will remain in effect with the provision
omitted, unless omission would frustrate the intent of the parties, in
which case this agreement will immediately terminate.
8. RESERVATION OF RIGHTS. All rights not expressly granted in this agreement
are reserved by WireGuard LLC. For example, WireGuard LLC reserves the
right at any time to cease development of Software, to alter distribution
details, features, specifications, capabilities, functions, licensing
terms, release dates, APIs, ABIs, general availability, or other
characteristics of the Software.

339
wintun/README.md Normal file
View File

@ -0,0 +1,339 @@
# [Wintun Network Adapter](https://www.wintun.net/)
### TUN Device Driver for Windows
This is a layer 3 TUN driver for Windows 7, 8, 8.1, and 10. Originally created for [WireGuard](https://www.wireguard.com/), it is intended to be useful to a wide variety of projects that require layer 3 tunneling devices with implementations primarily in userspace.
## Installation
Wintun is deployed as a platform-specific `wintun.dll` file. Install the `wintun.dll` file side-by-side with your application. Download the dll from [wintun.net](https://www.wintun.net/), alongside the header file for your application described below.
## Usage
Include the [`wintun.h` file](https://git.zx2c4.com/wintun/tree/api/wintun.h) in your project simply by copying it there and dynamically load the `wintun.dll` using [`LoadLibraryEx()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa) and [`GetProcAddress()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress) to resolve each function, using the typedefs provided in the header file. The [`InitializeWintun` function in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c) provides this in a function that you can simply copy and paste.
With the library setup, Wintun can then be used by first creating an adapter, configuring it, and then setting its status to "up". Adapters have names (e.g. "OfficeNet") and types (e.g. "Wintun").
```C
WINTUN_ADAPTER_HANDLE Adapter1 = WintunCreateAdapter(L"OfficeNet", L"Wintun", &SomeFixedGUID1);
WINTUN_ADAPTER_HANDLE Adapter2 = WintunCreateAdapter(L"HomeNet", L"Wintun", &SomeFixedGUID2);
WINTUN_ADAPTER_HANDLE Adapter3 = WintunCreateAdapter(L"Data Center", L"Wintun", &SomeFixedGUID3);
```
After creating an adapter, we can use it by starting a session:
```C
WINTUN_SESSION_HANDLE Session = WintunStartSession(Adapter2, 0x400000);
```
Then, the `WintunAllocateSendPacket` and `WintunSendPacket` functions can be used for sending packets ([used by `SendPackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)):
```C
BYTE *OutgoingPacket = WintunAllocateSendPacket(Session, PacketDataSize);
if (OutgoingPacket)
{
memcpy(OutgoingPacket, PacketData, PacketDataSize);
WintunSendPacket(Session, OutgoingPacket);
}
else if (GetLastError() != ERROR_BUFFER_OVERFLOW) // Silently drop packets if the ring is full
Log(L"Packet write failed");
```
And the `WintunReceivePacket` and `WintunReleaseReceivePacket` functions can be used for receiving packets ([used by `ReceivePackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)):
```C
for (;;)
{
DWORD IncomingPacketSize;
BYTE *IncomingPacket = WintunReceivePacket(Session, &IncomingPacketSize);
if (IncomingPacket)
{
DoSomethingWithPacket(IncomingPacket, IncomingPacketSize);
WintunReleaseReceivePacket(Session, IncomingPacket);
}
else if (GetLastError() == ERROR_NO_MORE_ITEMS)
WaitForSingleObject(WintunGetReadWaitEvent(Session), INFINITE);
else
{
Log(L"Packet read failed");
break;
}
}
```
Some high performance use cases may want to spin on `WintunReceivePackets` for a number of cycles before falling back to waiting on the read-wait event.
You are **highly encouraged** to read the [**example.c short example**](https://git.zx2c4.com/wintun/tree/example/example.c) to see how to put together a simple userspace network tunnel.
The various functions and definitions are [documented in the reference below](#Reference).
## Reference
### Macro Definitions
#### WINTUN\_MAX\_POOL
`#define WINTUN_MAX_POOL 256`
Maximum pool name length including zero terminator
#### WINTUN\_MIN\_RING\_CAPACITY
`#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */`
Minimum ring capacity.
#### WINTUN\_MAX\_RING\_CAPACITY
`#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */`
Maximum ring capacity.
#### WINTUN\_MAX\_IP\_PACKET\_SIZE
`#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF`
Maximum IP packet size
### Typedefs
#### WINTUN\_ADAPTER\_HANDLE
`typedef void* WINTUN_ADAPTER_HANDLE`
A handle representing Wintun adapter
#### WINTUN\_ENUM\_CALLBACK
`typedef BOOL(* WINTUN_ENUM_CALLBACK) (WINTUN_ADAPTER_HANDLE Adapter, LPARAM Param)`
Called by WintunEnumAdapters for each adapter in the pool.
**Parameters**
- *Adapter*: Adapter handle, which will be freed when this function returns.
- *Param*: An application-defined value passed to the WintunEnumAdapters.
**Returns**
Non-zero to continue iterating adapters; zero to stop.
#### WINTUN\_LOGGER\_CALLBACK
`typedef void(* WINTUN_LOGGER_CALLBACK) (WINTUN_LOGGER_LEVEL Level, DWORD64 Timestamp, const WCHAR *Message)`
Called by internal logger to report diagnostic messages
**Parameters**
- *Level*: Message level.
- *Timestamp*: Message timestamp in in 100ns intervals since 1601-01-01 UTC.
- *Message*: Message text.
#### WINTUN\_SESSION\_HANDLE
`typedef void* WINTUN_SESSION_HANDLE`
A handle representing Wintun session
### Enumeration Types
#### WINTUN\_LOGGER\_LEVEL
`enum WINTUN_LOGGER_LEVEL`
Determines the level of logging, passed to WINTUN\_LOGGER\_CALLBACK.
- *WINTUN\_LOG\_INFO*: Informational
- *WINTUN\_LOG\_WARN*: Warning
- *WINTUN\_LOG\_ERR*: Error
Enumerator
### Functions
#### WintunCreateAdapter()
`WINTUN_ADAPTER_HANDLE WintunCreateAdapter (const WCHAR * Name, const WCHAR * TunnelType, const GUID * RequestedGUID)`
Creates a new Wintun adapter.
**Parameters**
- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters.
- *Name*: Name of the adapter tunnel type. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters.
- *RequestedGUID*: The GUID of the created network adapter, which then influences NLA generation deterministically. If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is created for each new adapter. It is called "requested" GUID because the API it uses is completely undocumented, and so there could be minor interesting complications with its usage.
**Returns**
If the function succeeds, the return value is the adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError.
#### WintunOpenAdapter()
`WINTUN_ADAPTER_HANDLE WintunOpenAdapter (const WCHAR * Name)`
Opens an existing Wintun adapter.
**Parameters**
- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters.
**Returns**
If the function succeeds, the return value is adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError.
#### WintunCloseAdapter()
`void WintunCloseAdapter (WINTUN_ADAPTER_HANDLE Adapter)`
Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter.
**Parameters**
- *Adapter*: Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter.
#### WintunDeleteDriver()
`BOOL WintunDeleteDriver ()`
Deletes the Wintun driver if there are no more adapters in use.
**Returns**
If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError.
#### WintunGetAdapterLuid()
`void WintunGetAdapterLuid (WINTUN_ADAPTER_HANDLE Adapter, NET_LUID * Luid)`
Returns the LUID of the adapter.
**Parameters**
- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter
- *Luid*: Pointer to LUID to receive adapter LUID.
#### WintunGetRunningDriverVersion()
`DWORD WintunGetRunningDriverVersion (void )`
Determines the version of the Wintun driver currently loaded.
**Returns**
If the function succeeds, the return value is the version number. If the function fails, the return value is zero. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_FILE\_NOT\_FOUND Wintun not loaded
#### WintunSetLogger()
`void WintunSetLogger (WINTUN_LOGGER_CALLBACK NewLogger)`
Sets logger callback function.
**Parameters**
- *NewLogger*: Pointer to callback function to use as a new global logger. NewLogger may be called from various threads concurrently. Should the logging require serialization, you must handle serialization in NewLogger. Set to NULL to disable.
#### WintunStartSession()
`WINTUN_SESSION_HANDLE WintunStartSession (WINTUN_ADAPTER_HANDLE Adapter, DWORD Capacity)`
Starts Wintun session.
**Parameters**
- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter
- *Capacity*: Rings capacity. Must be between WINTUN\_MIN\_RING\_CAPACITY and WINTUN\_MAX\_RING\_CAPACITY (incl.) Must be a power of two.
**Returns**
Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is NULL. To get extended error information, call GetLastError.
#### WintunEndSession()
`void WintunEndSession (WINTUN_SESSION_HANDLE Session)`
Ends Wintun session.
**Parameters**
- *Session*: Wintun session handle obtained with WintunStartSession
#### WintunGetReadWaitEvent()
`HANDLE WintunGetReadWaitEvent (WINTUN_SESSION_HANDLE Session)`
Gets Wintun session's read-wait event handle.
**Parameters**
- *Session*: Wintun session handle obtained with WintunStartSession
**Returns**
Pointer to receive event handle to wait for available data when reading. Should WintunReceivePackets return ERROR\_NO\_MORE\_ITEMS (after spinning on it for a while under heavy load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call CloseHandle on this event - it is managed by the session.
#### WintunReceivePacket()
`BYTE* WintunReceivePacket (WINTUN_SESSION_HANDLE Session, DWORD * PacketSize)`
Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned from this function to release internal buffer. This function is thread-safe.
**Parameters**
- *Session*: Wintun session handle obtained with WintunStartSession
- *PacketSize*: Pointer to receive packet size.
**Returns**
Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_HANDLE\_EOF Wintun adapter is terminating; ERROR\_NO\_MORE\_ITEMS Wintun buffer is exhausted; ERROR\_INVALID\_DATA Wintun buffer is corrupt
#### WintunReleaseReceivePacket()
`void WintunReleaseReceivePacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)`
Releases internal buffer after the received packet has been processed by the client. This function is thread-safe.
**Parameters**
- *Session*: Wintun session handle obtained with WintunStartSession
- *Packet*: Packet obtained with WintunReceivePacket
#### WintunAllocateSendPacket()
`BYTE* WintunAllocateSendPacket (WINTUN_SESSION_HANDLE Session, DWORD PacketSize)`
Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of calls define the packet sending order.
**Parameters**
- *Session*: Wintun session handle obtained with WintunStartSession
- *PacketSize*: Exact packet size. Must be less or equal to WINTUN\_MAX\_IP\_PACKET\_SIZE.
**Returns**
Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_HANDLE\_EOF Wintun adapter is terminating; ERROR\_BUFFER\_OVERFLOW Wintun buffer is full;
#### WintunSendPacket()
`void WintunSendPacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)`
Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the WintunSendPacket yet.
**Parameters**
- *Session*: Wintun session handle obtained with WintunStartSession
- *Packet*: Packet obtained with WintunAllocateSendPacket
## Building
**Do not distribute drivers or files named "Wintun", as they will most certainly clash with official deployments. Instead distribute [`wintun.dll` as downloaded from wintun.net](https://www.wintun.net).**
General requirements:
- [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) with Windows SDK
- [Windows Driver Kit](https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk)
`wintun.sln` may be opened in Visual Studio for development and building. Be sure to run `bcdedit /set testsigning on` and then reboot before to enable unsigned driver loading. The default run sequence (F5) in Visual Studio will build the example project and its dependencies.
## License
The entire contents of [the repository](https://git.zx2c4.com/wintun/), including all documentation and example code, is "Copyright © 2018-2021 WireGuard LLC. All Rights Reserved." Source code is licensed under the [GPLv2](COPYING). Prebuilt binaries from [wintun.net](https://www.wintun.net/) are released under a more permissive license suitable for more forms of software contained inside of the .zip files distributed there.

BIN
wintun/bin/amd64/wintun.dll Normal file

Binary file not shown.

BIN
wintun/bin/arm/wintun.dll Normal file

Binary file not shown.

BIN
wintun/bin/arm64/wintun.dll Normal file

Binary file not shown.

BIN
wintun/bin/x86/wintun.dll Normal file

Binary file not shown.

270
wintun/include/wintun.h Normal file
View File

@ -0,0 +1,270 @@
/* SPDX-License-Identifier: GPL-2.0 OR MIT
*
* Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
*/
#pragma once
#include <winsock2.h>
#include <windows.h>
#include <ipexport.h>
#include <ifdef.h>
#include <ws2ipdef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ALIGNED
# if defined(_MSC_VER)
# define ALIGNED(n) __declspec(align(n))
# elif defined(__GNUC__)
# define ALIGNED(n) __attribute__((aligned(n)))
# else
# error "Unable to define ALIGNED"
# endif
#endif
/* MinGW is missing this one, unfortunately. */
#ifndef _Post_maybenull_
# define _Post_maybenull_
#endif
#pragma warning(push)
#pragma warning(disable : 4324) /* structure was padded due to alignment specifier */
/**
* A handle representing Wintun adapter
*/
typedef struct _WINTUN_ADAPTER *WINTUN_ADAPTER_HANDLE;
/**
* Creates a new Wintun adapter.
*
* @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1
* characters.
*
* @param TunnelType Name of the adapter tunnel type. Zero-terminated string of up to MAX_ADAPTER_NAME-1
* characters.
*
* @param RequestedGUID The GUID of the created network adapter, which then influences NLA generation deterministically.
* If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is
* created for each new adapter. It is called "requested" GUID because the API it uses is
* completely undocumented, and so there could be minor interesting complications with its usage.
*
* @return If the function succeeds, the return value is the adapter handle. Must be released with
* WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call
* GetLastError.
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_CREATE_ADAPTER_FUNC)
(_In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelType, _In_opt_ const GUID *RequestedGUID);
/**
* Opens an existing Wintun adapter.
*
* @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1
* characters.
*
* @return If the function succeeds, the return value is the adapter handle. Must be released with
* WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call
* GetLastError.
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_OPEN_ADAPTER_FUNC)(_In_z_ LPCWSTR Name);
/**
* Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter.
*
* @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter.
*/
typedef VOID(WINAPI WINTUN_CLOSE_ADAPTER_FUNC)(_In_opt_ WINTUN_ADAPTER_HANDLE Adapter);
/**
* Deletes the Wintun driver if there are no more adapters in use.
*
* @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To
* get extended error information, call GetLastError.
*/
typedef _Return_type_success_(return != FALSE)
BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID);
/**
* Returns the LUID of the adapter.
*
* @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter
*
* @param Luid Pointer to LUID to receive adapter LUID.
*/
typedef VOID(WINAPI WINTUN_GET_ADAPTER_LUID_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _Out_ NET_LUID *Luid);
/**
* Determines the version of the Wintun driver currently loaded.
*
* @return If the function succeeds, the return value is the version number. If the function fails, the return value is
* zero. To get extended error information, call GetLastError. Possible errors include the following:
* ERROR_FILE_NOT_FOUND Wintun not loaded
*/
typedef _Return_type_success_(return != 0)
DWORD(WINAPI WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC)(VOID);
/**
* Determines the level of logging, passed to WINTUN_LOGGER_CALLBACK.
*/
typedef enum
{
WINTUN_LOG_INFO, /**< Informational */
WINTUN_LOG_WARN, /**< Warning */
WINTUN_LOG_ERR /**< Error */
} WINTUN_LOGGER_LEVEL;
/**
* Called by internal logger to report diagnostic messages
*
* @param Level Message level.
*
* @param Timestamp Message timestamp in in 100ns intervals since 1601-01-01 UTC.
*
* @param Message Message text.
*/
typedef VOID(CALLBACK *WINTUN_LOGGER_CALLBACK)(
_In_ WINTUN_LOGGER_LEVEL Level,
_In_ DWORD64 Timestamp,
_In_z_ LPCWSTR Message);
/**
* Sets logger callback function.
*
* @param NewLogger Pointer to callback function to use as a new global logger. NewLogger may be called from various
* threads concurrently. Should the logging require serialization, you must handle serialization in
* NewLogger. Set to NULL to disable.
*/
typedef VOID(WINAPI WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_CALLBACK NewLogger);
/**
* Minimum ring capacity.
*/
#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */
/**
* Maximum ring capacity.
*/
#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */
/**
* A handle representing Wintun session
*/
typedef struct _TUN_SESSION *WINTUN_SESSION_HANDLE;
/**
* Starts Wintun session.
*
* @param Adapter Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter
*
* @param Capacity Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.)
* Must be a power of two.
*
* @return Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is
* NULL. To get extended error information, call GetLastError.
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
WINTUN_SESSION_HANDLE(WINAPI WINTUN_START_SESSION_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ DWORD Capacity);
/**
* Ends Wintun session.
*
* @param Session Wintun session handle obtained with WintunStartSession
*/
typedef VOID(WINAPI WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);
/**
* Gets Wintun session's read-wait event handle.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @return Pointer to receive event handle to wait for available data when reading. Should
* WintunReceivePackets return ERROR_NO_MORE_ITEMS (after spinning on it for a while under heavy
* load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call
* CloseHandle on this event - it is managed by the session.
*/
typedef HANDLE(WINAPI WINTUN_GET_READ_WAIT_EVENT_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);
/**
* Maximum IP packet size
*/
#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF
/**
* Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned
* from this function to release internal buffer. This function is thread-safe.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param PacketSize Pointer to receive packet size.
*
* @return Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the
* return value is NULL. To get extended error information, call GetLastError. Possible errors include the
* following:
* ERROR_HANDLE_EOF Wintun adapter is terminating;
* ERROR_NO_MORE_ITEMS Wintun buffer is exhausted;
* ERROR_INVALID_DATA Wintun buffer is corrupt
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
_Post_writable_byte_size_(*PacketSize)
BYTE *(WINAPI WINTUN_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _Out_ DWORD *PacketSize);
/**
* Releases internal buffer after the received packet has been processed by the client. This function is thread-safe.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param Packet Packet obtained with WintunReceivePacket
*/
typedef VOID(
WINAPI WINTUN_RELEASE_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);
/**
* Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send
* and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of
* calls define the packet sending order.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param PacketSize Exact packet size. Must be less or equal to WINTUN_MAX_IP_PACKET_SIZE.
*
* @return Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails,
* the return value is NULL. To get extended error information, call GetLastError. Possible errors include the
* following:
* ERROR_HANDLE_EOF Wintun adapter is terminating;
* ERROR_BUFFER_OVERFLOW Wintun buffer is full;
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
_Post_writable_byte_size_(PacketSize)
BYTE *(WINAPI WINTUN_ALLOCATE_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD PacketSize);
/**
* Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket
* order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the
* WintunSendPacket yet.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param Packet Packet obtained with WintunAllocateSendPacket
*/
typedef VOID(WINAPI WINTUN_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);
#pragma warning(pop)
#ifdef __cplusplus
}
#endif