First commit.
This commit is contained in:
commit
fcddaabed2
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.release-env
|
||||||
|
virtual-vxlan.exe
|
||||||
|
virtual-vxlan
|
||||||
|
dist
|
||||||
|
sysroot/linux*
|
82
.goreleaser.yaml
Normal file
82
.goreleaser.yaml
Normal 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
10
Dockerfile
Normal 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
19
LICENSE.txt
Normal 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
69
Makefile
Normal 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
76
README.md
Normal 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
512
config.go
Normal 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
66
config_cmd.go
Normal 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
30
config_grpc.go
Normal 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
25
devicestate_string.go
Normal 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
51
flags.go
Normal 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
59
go.mod
Normal 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
178
go.sum
Normal 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
77
grpc.go
Normal 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
1154
interface.go
Normal file
File diff suppressed because it is too large
Load Diff
595
interface_cmd.go
Normal file
595
interface_cmd.go
Normal 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
395
interface_grpc.go
Normal 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
257
listener.go
Normal 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, ð, &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
164
listener_cmd.go
Normal 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
97
listener_grpc.go
Normal 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
24
main.go
Normal 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
37
masquerade.go
Normal 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
23
promiscuous_unix.go
Normal 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
164
promiscuous_windows.go
Normal 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
130
server_cmd.go
Normal 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
77
service_cmd.go
Normal 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
21
sysroot/Dockerfile
Normal 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
18
sysroot/README.md
Normal 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
131
sysroot/build.sh
Executable 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
12
tun/errors.go
Normal 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
53
tun/tun.go
Normal 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
562
tun/tun_linux.go
Normal 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
336
tun/tun_windows.go
Normal 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
10
update_cmd.go
Normal 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
262
updater.go
Normal 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
20
utils.go
Normal 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
1743
vxlan/vxlan.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
170
vxlan/vxlan.proto
Normal file
170
vxlan/vxlan.proto
Normal 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
1001
vxlan/vxlan_grpc.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
7
wintun-source.md
Normal file
7
wintun-source.md
Normal 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
84
wintun/LICENSE.txt
Normal 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
339
wintun/README.md
Normal 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
BIN
wintun/bin/amd64/wintun.dll
Normal file
Binary file not shown.
BIN
wintun/bin/arm/wintun.dll
Normal file
BIN
wintun/bin/arm/wintun.dll
Normal file
Binary file not shown.
BIN
wintun/bin/arm64/wintun.dll
Normal file
BIN
wintun/bin/arm64/wintun.dll
Normal file
Binary file not shown.
BIN
wintun/bin/x86/wintun.dll
Normal file
BIN
wintun/bin/x86/wintun.dll
Normal file
Binary file not shown.
270
wintun/include/wintun.h
Normal file
270
wintun/include/wintun.h
Normal 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
|
Loading…
Reference in New Issue
Block a user