Merge pull request #653 from yggdrasil-network/develop

Version 0.3.13
This commit is contained in:
Neil Alexander 2020-02-21 18:30:59 +00:00 committed by GitHub
commit c3f8db6991
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 969 additions and 974 deletions

View File

@ -174,13 +174,6 @@ jobs:
GOOS=freebsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-amd64;
GOOS=freebsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-i386;
- run:
name: Build for NetBSD
command: |
rm -f {yggdrasil,yggdrasilctl}
GOOS=netbsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-netbsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-netbsd-amd64;
GOOS=netbsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-netbsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-netbsd-i386;
- run:
name: Build for Windows
command: |

View File

@ -25,6 +25,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities.
-->
## [0.3.13] - 2020-02-21
### Added
- Support for the Wireguard TUN driver, which now replaces Water and provides far better support and performance on Windows
- Windows `.msi` installer files are now supported (bundling the Wireguard TUN driver)
- NodeInfo code is now actorised, should be more reliable
- The DHT now tries to store the two closest nodes in either direction instead of one, such that if a node goes offline, the replacement is already known
- The Yggdrasil API now supports dialing a remote node using the public key instead of the Node ID
### Changed
- The `-loglevel` command line parameter is now cumulative and automatically includes all levels below the one specified
- DHT search code has been significantly simplified and processes rumoured nodes in parallel, speeding up search time
- DHT search results are now sorted
- The systemd service now handles configuration generation in a different unit
- The Yggdrasil API now returns public keys instead of node IDs when querying for local and remote addresses
### Fixed
- The multicast code no longer panics when shutting down the node
- A potential OOB error when calculating IPv4 flow labels (when tunnel routing is enabled) has been fixed
- A bug resulting in incorrect idle notifications in the switch should now be fixed
- MTUs are now using a common datatype throughout the codebase
### Removed
- TAP mode has been removed entirely, since it is no longer supported with the Wireguard TUN package. Please note that if you are using TAP mode, you may need to revise your config!
- NetBSD support has been removed until the Wireguard TUN package supports NetBSD
## [0.3.12] - 2019-11-24
### Added
- New API functions `SetMaximumSessionMTU` and `GetMaximumSessionMTU`
@ -66,7 +91,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed
- On recent Linux kernels, Yggdrasil will now set the `tcp_congestion_control` algorithm used for its own TCP sockets to [BBR](https://github.com/google/bbr), which reduces latency under load
- The systemd service configuration in `contrib` (and, by extension, some of our packages) now attemps to load the `tun` module, in case TUN/TAP support is available but not loaded, and it restricts Yggdrasil to the `CAP_NET_ADMIN` capability for managing the TUN/TAP adapter, rather than letting it do whatever the (typically `root`) user can do
- The systemd service configuration in `contrib` (and, by extension, some of our packages) now attempts to load the `tun` module, in case TUN/TAP support is available but not loaded, and it restricts Yggdrasil to the `CAP_NET_ADMIN` capability for managing the TUN/TAP adapter, rather than letting it do whatever the (typically `root`) user can do
### Fixed
- The `yggdrasil.Conn.RemoteAddr()` function no longer blocks, fixing a deadlock when CKR is used while under heavy load
@ -90,7 +115,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Some minor memory leaks in the switch have been fixed, which improves memory usage on mobile builds
- A memory leak in the add-peer loop has been fixed
- The admin socket now reports the correct URI strings for SOCKS peers in `getPeers`
- A race condition when dialling a remote node by both the node address and routed prefix simultaneously has been fixed
- A race condition when dialing a remote node by both the node address and routed prefix simultaneously has been fixed
- A race condition between the router and the dial code resulting in a panic has been fixed
- A panic which could occur when the TUN/TAP interface disappears (e.g. during soft-shutdown) has been fixed
- A bug in the semantic versioning script which accompanies Yggdrasil for builds has been fixed
@ -180,7 +205,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [0.3.4] - 2019-03-12
### Added
- Support for multiple listeners (although currently only TCP listeners are supported)
- New multicast behaviour where each multicast interface is given it's own link-local listener and does not depend on the `Listen` configuration
- New multicast behaviour where each multicast interface is given its own link-local listener and does not depend on the `Listen` configuration
- Blocking detection in the switch to avoid parenting a blocked peer
- Support for adding and removing listeners and multicast interfaces when reloading configuration during runtime
- Yggdrasil will now attempt to clean up UNIX admin sockets on startup if left behind by a previous crash

View File

@ -34,7 +34,6 @@ some of the below:
- Windows
- FreeBSD
- OpenBSD
- NetBSD
- OpenWrt
Please see our [Platforms](https://yggdrasil-network.github.io/platforms.html) pages for more

20
appveyor.yml Normal file
View File

@ -0,0 +1,20 @@
version: '{build}'
pull_requests:
do_not_increment_build_number: true
os: Visual Studio 2017
shallow_clone: false
environment:
MSYS2_PATH_TYPE: inherit
CHERE_INVOKING: enabled_from_arguments
build_script:
- cmd: >-
cd %APPVEYOR_BUILD_FOLDER%
- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64"
- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86"
test: off
artifacts:
- path: '*.msi'

3
build
View File

@ -9,7 +9,7 @@ PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
ARGS="-v"
while getopts "uaitc:l:dro:" option
while getopts "uaitc:l:dro:p" option
do
case "$option"
in
@ -22,6 +22,7 @@ do
d) ARGS="$ARGS -tags debug" DEBUG=true;;
r) ARGS="$ARGS -race";;
o) ARGS="$ARGS -o $OPTARG";;
p) ARGS="$ARGS -buildmode=pie";;
esac
done

View File

@ -3,7 +3,7 @@
This file generates crypto keys.
It prints out a new set of keys each time if finds a "better" one.
By default, "better" means a higher NodeID (-> higher IP address).
This is because the IP address format can compress leading 1s in the address, to incrase the number of ID bits in the address.
This is because the IP address format can compress leading 1s in the address, to increase the number of ID bits in the address.
If run with the "-sig" flag, it generates signing keys instead.
A "better" signing key means one with a higher TreeID.

View File

@ -132,6 +132,32 @@ func doGenconf(isjson bool) string {
return string(bs)
}
func setLogLevel(loglevel string, logger *log.Logger) {
levels := [...]string{"error", "warn", "info", "debug", "trace"}
loglevel = strings.ToLower(loglevel)
contains := func() bool {
for _, l := range levels {
if l == loglevel {
return true
}
}
return false
}
if !contains() { // set default log level
logger.Infoln("Loglevel parse failed. Set default level(info)")
loglevel = "info"
}
for _, l := range levels {
logger.EnableLevel(l)
if l == loglevel {
break
}
}
}
// The main function is responsible for configuring and starting Yggdrasil.
func main() {
// Configure the command line parameters.
@ -142,10 +168,10 @@ func main() {
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
ver := flag.Bool("version", false, "prints the version of this build")
logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable")
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration")
getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration")
loglevel := flag.String("loglevel", "info", "loglevel to enable")
flag.Parse()
var cfg *config.NodeConfig
@ -239,20 +265,9 @@ func main() {
logger = log.New(os.Stdout, "", log.Flags())
logger.Warnln("Logging defaulting to stdout")
}
//logger.EnableLevel("error")
//logger.EnableLevel("warn")
//logger.EnableLevel("info")
if levels := strings.Split(*logging, ","); len(levels) > 0 {
for _, level := range levels {
l := strings.TrimSpace(level)
switch l {
case "error", "warn", "info", "trace", "debug":
logger.EnableLevel(l)
default:
continue
}
}
}
setLogLevel(*loglevel, logger)
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
// don't need to create this manually.
n := node{}

View File

@ -25,14 +25,20 @@ import (
type admin_info map[string]interface{}
func main() {
// makes sure we can use defer and still return an error code to the OS
os.Exit(run())
}
func run() int {
logbuffer := &bytes.Buffer{}
logger := log.New(logbuffer, "", log.Flags())
defer func() {
defer func() int {
if r := recover(); r != nil {
logger.Println("Fatal error:", r)
fmt.Print(logbuffer)
os.Exit(1)
return 1
}
return 0
}()
endpoint := defaults.GetDefaults().DefaultAdminListen
@ -62,12 +68,12 @@ func main() {
fmt.Println("Build name:", version.BuildName())
fmt.Println("Build version:", version.BuildVersion())
fmt.Println("To get the version number of the running Yggdrasil node, run", os.Args[0], "getSelf")
return
return 0
}
if len(args) == 0 {
flag.Usage()
return
return 0
}
if *server == endpoint {
@ -176,15 +182,15 @@ func main() {
} else {
fmt.Println("Admin socket returned an error but didn't specify any error text")
}
os.Exit(1)
return 1
}
if _, ok := recv["request"]; !ok {
fmt.Println("Missing request in response (malformed response?)")
os.Exit(1)
return 1
}
if _, ok := recv["response"]; !ok {
fmt.Println("Missing response body (malformed response?)")
os.Exit(1)
return 1
}
req := recv["request"].(map[string]interface{})
res := recv["response"].(map[string]interface{})
@ -193,7 +199,7 @@ func main() {
if json, err := json.MarshalIndent(res, "", " "); err == nil {
fmt.Println(string(json))
}
os.Exit(0)
return 0
}
switch strings.ToLower(req["request"].(string)) {
@ -434,7 +440,7 @@ func main() {
}
if v, ok := recv["status"]; ok && v != "success" {
os.Exit(1)
return 1
}
os.Exit(0)
return 0
}

View File

@ -5,13 +5,14 @@ WORKDIR /src
ENV CGO_ENABLED=0
RUN apk add git && ./build
RUN apk add git && ./build && go build -o /src/genkeys cmd/genkeys/main.go
FROM docker.io/alpine
LABEL maintainer="Christer Waren/CWINFO <christer.waren@cwinfo.org>"
COPY --from=builder /src/yggdrasil /usr/bin/yggdrasil
COPY --from=builder /src/yggdrasilctl /usr/bin/yggdrasilctl
COPY --from=builder /src/genkeys /usr/bin/genkeys
COPY contrib/docker/entrypoint.sh /usr/bin/entrypoint.sh
# RUN addgroup -g 1000 -S yggdrasil-network \

224
contrib/msi/build-msi.sh Normal file
View File

@ -0,0 +1,224 @@
#!/bin/sh
# This script generates an MSI file for Yggdrasil for a given architecture. It
# needs to run on Windows within MSYS2 and Go 1.13 or later must be installed on
# the system and within the PATH. This is ran currently by Appveyor (see
# appveyor.yml in the repository root) for both x86 and x64.
#
# Author: Neil Alexander <neilalexander@users.noreply.github.com>
# Get arch from command line if given
PKGARCH=$1
if [ "${PKGARCH}" == "" ];
then
echo "tell me the architecture: x86 or x64"
exit 1
fi
# Get the rest of the repository history. This is needed within Appveyor because
# otherwise we don't get all of the branch histories and therefore the semver
# scripts don't work properly.
if [ "${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH}" != "" ];
then
git fetch --all
git checkout ${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH}
elif [ "${APPVEYOR_REPO_BRANCH}" != "" ];
then
git fetch --all
git checkout ${APPVEYOR_REPO_BRANCH}
fi
# Install prerequisites within MSYS2
pacman -S --needed --noconfirm unzip git curl
# Download the wix tools!
if [ ! -d wixbin ];
then
curl -LO https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip
if [ `md5sum wix311-binaries.zip | cut -f 1 -d " "` != "47a506f8ab6666ee3cc502fb07d0ee2a" ];
then
echo "wix package didn't match expected checksum"
exit 1
fi
mkdir -p wixbin
unzip -o wix311-binaries.zip -d wixbin || (
echo "failed to unzip WiX"
exit 1
)
fi
# Build Yggdrasil!
[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build
[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build
# Create the postinstall script
cat > updateconfig.bat << EOF
if not exist %ALLUSERSPROFILE%\\Yggdrasil (
mkdir %ALLUSERSPROFILE%\\Yggdrasil
)
if not exist %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf (
if exist yggdrasil.exe (
yggdrasil.exe -genconf > %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf
)
)
EOF
# Work out metadata for the package info
PKGNAME=$(sh contrib/semver/name.sh)
PKGVERSION=$(sh contrib/semver/version.sh --bare)
PKGVERSIONMS=$(echo $PKGVERSION | tr - .)
[ "${PKGARCH}" == "x64" ] && \
PKGGUID="77757838-1a23-40a5-a720-c3b43e0260cc" PKGINSTFOLDER="ProgramFiles64Folder" || \
PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder"
# Download the Wintun driver
if [ $PKGARCH = "x64" ]; then
PKGMSMNAME=wintun-x64.msm
curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-amd64-0.7.msm || (echo "couldn't get wintun"; exit 1)
elif [ $PKGARCH = "x86" ]; then
PKGMSMNAME=wintun-x86.msm
curl -o ${PKGMSMNAME} https://www.wintun.net/builds/wintun-x86-0.7.msm || (echo "couldn't get wintun"; exit 1)
else
echo "wasn't sure which architecture to get wintun for"
exit 1
fi
if [ $PKGNAME != "master" ]; then
PKGDISPLAYNAME="Yggdrasil Network (${PKGNAME} branch)"
else
PKGDISPLAYNAME="Yggdrasil Network"
fi
# Generate the wix.xml file
cat > wix.xml << EOF
<?xml version="1.0" encoding="windows-1252"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product
Name="${PKGDISPLAYNAME}"
Id="*"
UpgradeCode="${PKGGUID}"
Language="1033"
Codepage="1252"
Version="${PKGVERSIONMS}"
Manufacturer="github.com/yggdrasil-network">
<Package
Id="*"
Keywords="Installer"
Description="Yggdrasil Network Installer"
Comments="Yggdrasil Network standalone router for Windows."
Manufacturer="github.com/yggdrasil-network"
InstallerVersion="200"
InstallScope="perMachine"
Languages="1033"
Compressed="yes"
Platform="${PKGARCH}"
SummaryCodepage="1252" />
<MajorUpgrade
AllowDowngrades="yes" />
<Media
Id="1"
Cabinet="Media.cab"
EmbedCab="yes"
CompressionLevel="high" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="${PKGINSTFOLDER}" Name="PFiles">
<Directory Id="YggdrasilInstallFolder" Name="Yggdrasil">
<Component Id="MainExecutable" Guid="c2119231-2aa3-4962-867a-9759c87beb24">
<File
Id="Yggdrasil"
Name="yggdrasil.exe"
DiskId="1"
Source="yggdrasil.exe"
KeyPath="yes" />
<ServiceInstall
Id="ServiceInstaller"
Account="LocalSystem"
Description="Yggdrasil Network router process"
DisplayName="Yggdrasil Service"
ErrorControl="normal"
LoadOrderGroup="NetworkProvider"
Name="Yggdrasil"
Start="auto"
Type="ownProcess"
Arguments='-useconffile "%ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf" -logto "%ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.log"'
Vital="yes" />
<ServiceControl
Id="ServiceControl"
Name="yggdrasil"
Start="install"
Stop="both"
Remove="uninstall" />
</Component>
<Component Id="CtrlExecutable" Guid="a916b730-974d-42a1-b687-d9d504cbb86a">
<File
Id="Yggdrasilctl"
Name="yggdrasilctl.exe"
DiskId="1"
Source="yggdrasilctl.exe"
KeyPath="yes"/>
</Component>
<Component Id="ConfigScript" Guid="64a3733b-c98a-4732-85f3-20cd7da1a785">
<File
Id="Configbat"
Name="updateconfig.bat"
DiskId="1"
Source="updateconfig.bat"
KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Merge
Id="Wintun"
Language="0"
DiskId="1"
SourceFile="${PKGMSMNAME}" />
</Directory>
<Feature Id="YggdrasilFeature" Title="Yggdrasil" Level="1">
<ComponentRef Id="MainExecutable" />
<ComponentRef Id="CtrlExecutable" />
<ComponentRef Id="ConfigScript" />
</Feature>
<Feature Id="WintunFeature" Title="Wintun" Level="1">
<Condition Level="0">
UPGRADINGPRODUCTCODE
</Condition>
<MergeRef Id="Wintun" />
</Feature>
<CustomAction
Id="UpdateGenerateConfig"
Directory="YggdrasilInstallFolder"
ExeCommand="cmd.exe /c updateconfig.bat"
Execute="deferred"
Return="check"
Impersonate="yes" />
<InstallExecuteSequence>
<Custom
Action="UpdateGenerateConfig"
Before="StartServices">
NOT Installed AND NOT REMOVE
</Custom>
</InstallExecuteSequence>
</Product>
</Wix>
EOF
# Generate the MSI
CANDLEFLAGS="-nologo"
LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61"
wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \
wixbin/light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj

View File

@ -0,0 +1,13 @@
[Unit]
Description=yggdrasil default config generator
ConditionPathExists=|!/etc/yggdrasil.conf
ConditionFileNotEmpty=|!/etc/yggdrasil.conf
Wants=local-fs.target
After=local-fs.target
[Service]
Type=oneshot
Group=yggdrasil
StandardOutput=file:/etc/yggdrasil.conf
ExecStart=/usr/bin/yggdrasil -genconf
ExecStartPost=/usr/bin/chmod 0640 /etc/yggdrasil.conf

View File

@ -1,20 +1,17 @@
[Unit]
Description=yggdrasil
Wants=network.target
Wants=yggdrasil-default-config.service
After=network.target
After=yggdrasil-default-config.service
[Service]
Group=yggdrasil
ProtectHome=true
ProtectSystem=true
SyslogIdentifier=yggdrasil
CapabilityBoundingSet=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW
ExecStartPre=+-/sbin/modprobe tun
ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \
then umask 077; \
yggdrasil -genconf > /etc/yggdrasil.conf; \
echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \
fi"
ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=always

View File

@ -65,12 +65,12 @@ Given the coordinates of any two nodes, it is possible to calculate the length o
Traffic is forwarded using a [greedy routing](https://en.wikipedia.org/wiki/Small-world_routing#Greedy_routing) scheme, where each node forwards the packet to a one-hop neighbor that is closer to the destination (according to this distance metric) than the current node.
In particular, when a packet needs to be forwarded, a node will forward it to whatever peer is closest to the destination in the greedy [metric space](https://en.wikipedia.org/wiki/Metric_space) used by the network, provided that the peer is closer to the destination than the current node.
If no closer peers are idle, then the packet is queued in FIFO order, with separate queues per destination coords (currently, as a bit of a hack, IPv6 flow labels are embedeed after the end of the significant part of the coords, so queues distinguish between different traffic streams with the same destination).
If no closer peers are idle, then the packet is queued in FIFO order, with separate queues per destination coords (currently, as a bit of a hack, IPv6 flow labels are embedded after the end of the significant part of the coords, so queues distinguish between different traffic streams with the same destination).
Whenever the node finishes forwarding a packet to a peer, it checks the queues, and will forward the first packet from the queue with the maximum `<age of first packet>/<queue size in bytes>`, i.e. the bandwidth the queue is attempting to use, subject to the constraint that the peer is a valid next hop (i.e. closer to the destination than the current node).
If no non-empty queue is available, then the peer is added to the idle set, forward packets when the need arises.
This acts as a crude approximation of backpressure routing, where the remote queue sizes are assumed to be equal to the distance of a node from a destination (rather than communicating queue size information), and packets are never forwarded "backwards" through the network, but congestion on a local link is routed around when possible.
The queue selection strategy behaves similar to shortest-queue-first, in that a larger fration of available bandwith to sessions that attempt to use less bandwidth, and is loosely based on the rationale behind some proposed solutions to the [cake-cutting](https://en.wikipedia.org/wiki/Fair_cake-cutting) problem.
The queue selection strategy behaves similar to shortest-queue-first, in that a larger fraction of available bandwidth to sessions that attempt to use less bandwidth, and is loosely based on the rationale behind some proposed solutions to the [cake-cutting](https://en.wikipedia.org/wiki/Fair_cake-cutting) problem.
The queue size is limited to 4 MB. If a packet is added to a queue and the total size of all queues is larger than this threshold, then a random queue is selected (with odds proportional to relative queue sizes), and the first packet from that queue is dropped, with the process repeated until the total queue size drops below the allowed threshold.

11
go.mod
View File

@ -9,13 +9,12 @@ require (
github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0
github.com/mitchellh/mapstructure v1.1.2
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect
github.com/vishvananda/netlink v1.0.0
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20191021144547-ec77196f6094
golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7
golang.org/x/text v0.3.2
golang.zx2c4.com/wireguard v0.0.20200121
golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25
)

34
go.sum
View File

@ -8,29 +8,39 @@ github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible h1:b
github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g=
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo=
github.com/lxn/walk v0.0.0-20191031081659-c0bb82ae46cb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1 h1:h0wbuSK8xUNmMwDdCxZx2OLdkVck6Bb31zj4CxCN5I4=
github.com/lxn/win v0.0.0-20191024121223-cc00c7492fe1/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM=
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I=
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 h1:YY9Pg2BEp0jeUVU60svTOaDr+fs1ySC9RbdC1Qc6wOw=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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-20191024172528-b4ff53e7a1cb h1:ZxSglHghKPYD8WDeRUzRJrUJtDF0PxsTUSxyqr9/5BI=
golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 h1:/W9OPMnnpmFXHYkcp2rQsbFUbRlRzfECQjmAFiOyHE8=
golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1 h1:KxtBKNgJUQG8vwZzJKkwBGOcqp95xLu6A6KIMde1kl0=
golang.zx2c4.com/wireguard v0.0.20191013-0.20191030132932-4cdf805b29b1/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8=
golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25 h1:TreP+furSwdqoSToFrwb1S5cwxb7jhOsnwj2MsDeT+4=
golang.zx2c4.com/wireguard/windows v0.0.35-0.20191123133119-cb4a03094c25/go.mod h1:EO8KCpT944a9CnwHJLZ1sl84FfIrY42fP/fcXUuYhKM=

View File

@ -793,7 +793,7 @@ def timelineDimesTest():
store = makeStoreDimesEdges(path, bestRoot)
rootID = "R" + bestRoot[1:]
assert rootID in store
# Don't forget to set random seed before setitng times
# Don't forget to set random seed before setting times
# To make results reproducible
nodeIDs = sorted(store.keys())
random.seed(12345)

View File

@ -424,7 +424,7 @@ func main() {
//*/
startNetwork(kstore)
//time.Sleep(10*time.Second)
// Note that testPaths only works if pressure is turend off
// Note that testPaths only works if pressure is turned off
// Otherwise congestion can lead to routing loops?
for finished := false; !finished; {
finished = testPaths(kstore)

View File

@ -15,7 +15,7 @@ type Address [16]byte
type Subnet [8]byte
// GetPrefix returns the address prefix used by yggdrasil.
// The current implementation requires this to be a muliple of 8 bits + 7 bits.
// The current implementation requires this to be a multiple of 8 bits + 7 bits.
// The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1).
// Nodes that configure this differently will be unable to communicate with each other using IP packets, though routing and the DHT machinery *should* still work.
func GetPrefix() [1]byte {

View File

@ -5,7 +5,7 @@ Yggdrasil node.
The configuration contains, amongst other things, encryption keys which are used
to derive a node's identity, information about peerings and node information
that is shared with the network. There are also some module-specific options
related to TUN/TAP, multicast and the admin socket.
related to TUN, multicast and the admin socket.
In order for a node to maintain the same identity across restarts, you should
persist the configuration onto the filesystem or into some configuration storage
@ -22,8 +22,11 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/types"
)
type MTU = types.MTU
// NodeState represents the active and previous configuration of an Yggdrasil
// node. A NodeState object is returned when starting an Yggdrasil node. Note
// that this structure and related functions are likely to disappear soon.
@ -70,9 +73,8 @@ type NodeConfig struct {
SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."`
SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"`
LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."`
IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."`
IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."`
IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
IfMTU MTU `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."`
TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."`
SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."`
@ -127,7 +129,6 @@ func GenerateConfig() *NodeConfig {
cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
cfg.SessionFirewall.Enable = false
cfg.SessionFirewall.AllowFromDirect = true
cfg.SessionFirewall.AllowFromRemote = true

View File

@ -194,6 +194,16 @@ type BoxSharedKey [BoxSharedKeyLen]byte
// BoxNonce is the nonce used in NaCl-like crypto "box" operations (curve25519+xsalsa20+poly1305), and must not be reused for different messages encrypted using the same BoxSharedKey.
type BoxNonce [BoxNonceLen]byte
// String returns a string representation of the "box" key.
func (k BoxPubKey) String() string {
return hex.EncodeToString(k[:])
}
// Network returns "curve25519" for "box" keys.
func (n BoxPubKey) Network() string {
return "curve25519"
}
// NewBoxKeys generates a new pair of public/private crypto box keys.
func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) {
pubBytes, privBytes, err := box.GenerateKey(rand.Reader)
@ -215,7 +225,7 @@ func GetSharedKey(myPrivKey *BoxPrivKey,
return (*BoxSharedKey)(&shared)
}
// BoxOpen returns a message and true if it successfull opens a crypto box using the provided shared key and nonce.
// BoxOpen returns a message and true if it successfully opens a crypto box using the provided shared key and nonce.
func BoxOpen(shared *BoxSharedKey,
boxed []byte,
nonce *BoxNonce) ([]byte, bool) {

View File

@ -1,5 +1,7 @@
package defaults
import "github.com/yggdrasil-network/yggdrasil-go/src/types"
// Defines which parameters are expected by default for configuration on a
// specific platform. These values are populated in the relevant defaults_*.go
// for the platform being targeted. They must be set.
@ -14,8 +16,7 @@ type platformDefaultParameters struct {
DefaultMulticastInterfaces []string
// TUN/TAP
MaximumIfMTU int
DefaultIfMTU int
MaximumIfMTU types.MTU
DefaultIfMTU types.MTU
DefaultIfName string
DefaultIfTAPMode bool
}

View File

@ -22,6 +22,5 @@ func GetDefaults() platformDefaultParameters {
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "auto",
DefaultIfTAPMode: false,
}
}

View File

@ -20,7 +20,6 @@ func GetDefaults() platformDefaultParameters {
// TUN/TAP
MaximumIfMTU: 32767,
DefaultIfMTU: 32767,
DefaultIfName: "/dev/tap0",
DefaultIfTAPMode: true,
DefaultIfName: "/dev/tun0",
}
}

View File

@ -21,6 +21,5 @@ func GetDefaults() platformDefaultParameters {
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "auto",
DefaultIfTAPMode: false,
}
}

View File

@ -1,26 +0,0 @@
// +build netbsd
package defaults
// Sane defaults for the BSD platforms. The "default" options may be
// may be replaced by the running configuration.
func GetDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 9000,
DefaultIfMTU: 9000,
DefaultIfName: "/dev/tap0",
DefaultIfTAPMode: true,
}
}

View File

@ -20,7 +20,6 @@ func GetDefaults() platformDefaultParameters {
// TUN/TAP
MaximumIfMTU: 16384,
DefaultIfMTU: 16384,
DefaultIfName: "/dev/tap0",
DefaultIfTAPMode: true,
DefaultIfName: "/dev/tun0",
}
}

View File

@ -1,4 +1,4 @@
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd
// +build !linux,!darwin,!windows,!openbsd,!freebsd
package defaults
@ -21,6 +21,5 @@ func GetDefaults() platformDefaultParameters {
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "none",
DefaultIfTAPMode: false,
}
}

View File

@ -20,7 +20,6 @@ func GetDefaults() platformDefaultParameters {
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "auto",
DefaultIfTAPMode: true,
DefaultIfName: "Yggdrasil",
}
}

View File

@ -112,7 +112,7 @@ func (m *Multicast) Stop() error {
err = m._stop()
})
m.log.Debugln("Stopped multicast module")
return nil
return err
}
func (m *Multicast) _stop() error {

View File

@ -19,8 +19,7 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
}()
return admin.Info{
t.iface.Name(): admin.Info{
"tap_mode": t.iface.IsTAP(),
t.Name(): admin.Info{
"mtu": t.mtu,
},
}, nil

View File

@ -63,7 +63,7 @@ func (s *tunConn) _read(bs []byte) (err error) {
default:
isCGA = false
}
// Check destiantion addresses
// Check destination addresses
switch {
case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination
case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination
@ -92,8 +92,7 @@ func (s *tunConn) _read(bs []byte) (err error) {
// The destination address isn't in our CKR allowed range
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil {
srcNodeID := crypto.GetNodeID(&key)
if *s.conn.RemoteAddr().(*crypto.NodeID) == *srcNodeID {
if *s.conn.RemoteAddr().(*crypto.BoxPubKey) == key {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID
@ -169,8 +168,7 @@ func (s *tunConn) _write(bs []byte) (err error) {
// The source address isn't in our CKR allowed range
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
dstNodeID := crypto.GetNodeID(&key)
if *s.conn.RemoteAddr().(*crypto.NodeID) == *dstNodeID {
if *s.conn.RemoteAddr().(*crypto.BoxPubKey) == key {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID

View File

@ -11,32 +11,16 @@ package tuntap
import (
"encoding/binary"
"errors"
"net"
"sync"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
const len_ETHER = 14
type ICMPv6 struct {
tun *TunAdapter
mylladdr net.IP
mymac net.HardwareAddr
peermacs map[address.Address]neighbor
peermacsmutex sync.RWMutex
}
type neighbor struct {
mac net.HardwareAddr
learned bool
lastadvertisement time.Time
lastsolicitation time.Time
}
// Marshal returns the binary encoding of h.
@ -61,182 +45,6 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) {
// addresses.
func (i *ICMPv6) Init(t *TunAdapter) {
i.tun = t
i.peermacsmutex.Lock()
i.peermacs = make(map[address.Address]neighbor)
i.peermacsmutex.Unlock()
// Our MAC address and link-local address
i.mymac = net.HardwareAddr{
0x02, 0x00, 0x00, 0x00, 0x00, 0x02}
i.mylladdr = net.IP{
0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}
copy(i.mymac[:], i.tun.addr[:])
copy(i.mylladdr[9:], i.tun.addr[1:])
}
// Parses an incoming ICMPv6 packet. The packet provided may be either an
// ethernet frame containing an IP packet, or the IP packet alone. This is
// determined by whether the TUN/TAP adapter is running in TUN (layer 3) or
// TAP (layer 2) mode. Returns an error condition which is nil if the ICMPv6
// module handled the packet or contains the error if not.
func (i *ICMPv6) ParsePacket(datain []byte) error {
var response []byte
var err error
// Parse the frame/packet
if i.tun.IsTAP() {
response, err = i.UnmarshalPacketL2(datain)
} else {
response, err = i.UnmarshalPacket(datain, nil)
}
if err != nil {
return err
}
// Write the packet to TUN/TAP
i.tun.iface.Write(response)
return nil
}
// Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off
// the IP packet to the ParsePacket function for further processing.
// A response buffer is also created for the response message, also complete
// with ethernet headers.
func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) {
// Ignore non-IPv6 frames
if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) {
return nil, errors.New("Ignoring non-IPv6 frame")
}
// Hand over to ParsePacket to interpret the IPv6 packet
mac := datain[6:12]
ipv6packet, err := i.UnmarshalPacket(datain[len_ETHER:], &mac)
if err != nil {
return nil, err
}
// Create the response buffer
dataout := make([]byte, len_ETHER+ipv6.HeaderLen+32)
// Populate the response ethernet headers
copy(dataout[:6], datain[6:12])
copy(dataout[6:12], i.mymac[:])
binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD))
// Copy the returned packet to our response ethernet frame
copy(dataout[len_ETHER:], ipv6packet)
return dataout, nil
}
// Unwraps the IP headers of an incoming IPv6 packet and performs various
// sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the
// ICMPv6 message match a known expected type. The relevant handler function
// is then called and a response packet may be returned.
func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) {
// Parse the IPv6 packet headers
ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen])
if err != nil {
return nil, err
}
// Check if the packet is IPv6
if ipv6Header.Version != ipv6.Version {
return nil, errors.New("Ignoring non-IPv6 packet")
}
// Check if the packet is ICMPv6
if ipv6Header.NextHeader != 58 {
return nil, errors.New("Ignoring non-ICMPv6 packet")
}
// Parse the ICMPv6 message contents
icmpv6Header, err := icmp.ParseMessage(58, datain[ipv6.HeaderLen:])
if err != nil {
return nil, err
}
// Check for a supported message type
switch icmpv6Header.Type {
case ipv6.ICMPTypeNeighborSolicitation:
if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Solicitation in TUN mode")
}
response, err := i.HandleNDP(datain[ipv6.HeaderLen:])
if err == nil {
// Create our ICMPv6 response
responsePacket, err := CreateICMPv6(
ipv6Header.Src, i.mylladdr,
ipv6.ICMPTypeNeighborAdvertisement, 0,
&icmp.DefaultMessageBody{Data: response})
if err != nil {
return nil, err
}
// Send it back
return responsePacket, nil
} else {
return nil, err
}
case ipv6.ICMPTypeNeighborAdvertisement:
if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Advertisement in TUN mode")
}
if datamac != nil {
var addr address.Address
var target address.Address
mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(addr[:], ipv6Header.Src[:])
copy(target[:], datain[48:64])
copy(mac[:], (*datamac)[:])
i.peermacsmutex.Lock()
neighbor := i.peermacs[target]
neighbor.mac = mac
neighbor.learned = true
neighbor.lastadvertisement = time.Now()
i.peermacs[target] = neighbor
i.peermacsmutex.Unlock()
i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String())
/*
i.tun.log.Debugln("Peer MAC table:")
i.peermacsmutex.RLock()
for t, n := range i.peermacs {
if n.learned {
i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String())
} else {
i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet")
}
}
i.peermacsmutex.RUnlock()
*/
}
return nil, errors.New("No response needed")
}
return nil, errors.New("ICMPv6 type not matched")
}
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with ethernet and IP headers, which can be written
// directly to a TAP adapter.
func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Pass through to CreateICMPv6
ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody)
if err != nil {
return nil, err
}
// Create the response buffer
dataout := make([]byte, len_ETHER+len(ipv6packet))
// Populate the response ethernet headers
copy(dataout[:6], dstmac[:6])
copy(dataout[6:12], i.mymac[:])
binary.BigEndian.PutUint16(dataout[12:14], uint16(0x86DD))
// Copy the returned packet to our response ethernet frame
copy(dataout[len_ETHER:], ipv6packet)
return dataout, nil
}
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
@ -281,106 +89,3 @@ func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody
// Send it back
return responsePacket, nil
}
func (i *ICMPv6) Solicit(addr address.Address) {
retries := 5
for retries > 0 {
retries--
i.peermacsmutex.RLock()
if n, ok := i.peermacs[addr]; ok && n.learned {
i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String())
i.peermacsmutex.RUnlock()
return
}
i.peermacsmutex.RUnlock()
i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String())
i.peermacsmutex.Lock()
if n, ok := i.peermacs[addr]; !ok {
i.peermacs[addr] = neighbor{
lastsolicitation: time.Now(),
}
} else {
n.lastsolicitation = time.Now()
}
i.peermacsmutex.Unlock()
request, err := i.createNDPL2(addr)
if err != nil {
panic(err)
}
if _, err := i.tun.iface.Write(request); err != nil {
panic(err)
}
i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String())
time.Sleep(time.Second)
}
}
func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) {
i.peermacsmutex.RLock()
defer i.peermacsmutex.RUnlock()
n, ok := i.peermacs[addr]
return n, ok
}
func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) {
// Create the ND payload
var payload [28]byte
copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags
copy(payload[4:20], dst[:]) // Destination
copy(payload[20:22], []byte{0x01, 0x01}) // Type & length
copy(payload[22:28], i.mymac[:6]) // Link layer address
// Create the ICMPv6 solicited-node address
var dstaddr address.Address
copy(dstaddr[:13], []byte{
0xFF, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF})
copy(dstaddr[13:], dst[13:16])
// Create the multicast MAC
dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(dstmac[:2], []byte{0x33, 0x33})
copy(dstmac[2:6], dstaddr[12:16])
// Create the ND request
requestPacket, err := i.CreateICMPv6L2(
dstmac, dstaddr[:], i.mylladdr,
ipv6.ICMPTypeNeighborSolicitation, 0,
&icmp.DefaultMessageBody{Data: payload[:]})
if err != nil {
return nil, err
}
return requestPacket, nil
}
// Generates a response to an NDP discovery packet. This is effectively called
// when the host operating system generates an NDP request for any address in
// the fd00::/8 range, so that the operating system knows to route that traffic
// to the Yggdrasil TAP adapter.
func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) {
// Ignore NDP requests for anything outside of fd00::/8
var source address.Address
copy(source[:], in[8:])
var snet address.Subnet
copy(snet[:], in[8:])
switch {
case source.IsValid():
case snet.IsValid():
default:
return nil, errors.New("Not an NDP for 0200::/7")
}
// Create our NDP message body response
body := make([]byte, 28)
binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags
copy(body[4:20], in[8:24]) // Target address
body[20] = uint8(2) // Type: Target link-layer address
body[21] = uint8(1) // Length: 1x address (8 bytes)
copy(body[22:28], i.mymac[:6])
// Send it back
return body, nil
}

View File

@ -1,11 +1,6 @@
package tuntap
import (
"bytes"
"net"
"time"
"github.com/songgao/packets/ethernet"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
@ -14,6 +9,8 @@ import (
"github.com/Arceliar/phony"
)
const TUN_OFFSET_BYTES = 4
type tunWriter struct {
phony.Inbox
tun *TunAdapter
@ -25,80 +22,29 @@ func (w *tunWriter) writeFrom(from phony.Actor, b []byte) {
})
}
// write is pretty loose with the memory safety rules, e.g. it assumes it can read w.tun.iface.IsTap() safely
// write is pretty loose with the memory safety rules, e.g. it assumes it can
// read w.tun.iface.IsTap() safely
func (w *tunWriter) _write(b []byte) {
defer util.PutBytes(b)
var written int
var err error
n := len(b)
if n == 0 {
return
}
if w.tun.iface.IsTAP() {
sendndp := func(dstAddr address.Address) {
neigh, known := w.tun.icmpv6.getNeighbor(dstAddr)
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
if !known {
w.tun.icmpv6.Solicit(dstAddr)
}
}
peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
var dstAddr address.Address
var peerknown bool
if b[0]&0xf0 == 0x40 {
dstAddr = w.tun.addr
} else if b[0]&0xf0 == 0x60 {
if !bytes.Equal(w.tun.addr[:16], dstAddr[:16]) && !bytes.Equal(w.tun.subnet[:8], dstAddr[:8]) {
dstAddr = w.tun.addr
}
}
if neighbor, ok := w.tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned {
// If we've learned the MAC of a 300::/7 address, for example, or a CKR
// address, use the MAC address of that
peermac = neighbor.mac
peerknown = true
} else if neighbor, ok := w.tun.icmpv6.getNeighbor(w.tun.addr); ok && neighbor.learned {
// Otherwise send directly to the MAC address of the host if that's
// known instead
peermac = neighbor.mac
peerknown = true
} else {
// Nothing has been discovered, try to discover the destination
sendndp(w.tun.addr)
}
if peerknown {
var proto ethernet.Ethertype
switch {
case b[0]&0xf0 == 0x60:
proto = ethernet.IPv6
case b[0]&0xf0 == 0x40:
proto = ethernet.IPv4
}
var frame ethernet.Frame
frame.Prepare(
peermac[:6], // Destination MAC address
w.tun.icmpv6.mymac[:6], // Source MAC address
ethernet.NotTagged, // VLAN tagging
proto, // Ethertype
len(b)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n])
n += tun_ETHER_HEADER_LENGTH
written, err = w.tun.iface.Write(frame[:n])
} else {
w.tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet")
}
} else {
written, err = w.tun.iface.Write(b[:n])
util.PutBytes(b)
}
temp := append(util.ResizeBytes(util.GetBytes(), TUN_OFFSET_BYTES), b...)
defer util.PutBytes(temp)
written, err = w.tun.iface.Write(temp, TUN_OFFSET_BYTES)
if err != nil {
w.tun.Act(w, func() {
if !w.tun.isOpen {
w.tun.log.Errorln("TUN/TAP iface write error:", err)
w.tun.log.Errorln("TUN iface write error:", err)
}
})
}
if written != n {
w.tun.log.Errorln("TUN/TAP iface write mismatch:", written, "bytes written vs", n, "bytes given")
if written != n+TUN_OFFSET_BYTES {
// FIXME some platforms return the wrong number of bytes written, causing error spam
//w.tun.log.Errorln("TUN iface write mismatch:", written, "bytes written vs", n+TUN_OFFSET_BYTES, "bytes given")
}
}
@ -109,13 +55,18 @@ type tunReader struct {
func (r *tunReader) _read() {
// Get a slice to store the packet in
recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH)
// Wait for a packet to be delivered to us through the TUN/TAP adapter
n, err := r.tun.iface.Read(recvd)
if n <= 0 {
recvd := util.ResizeBytes(util.GetBytes(), int(r.tun.mtu)+TUN_OFFSET_BYTES)
// Wait for a packet to be delivered to us through the TUN adapter
n, err := r.tun.iface.Read(recvd, TUN_OFFSET_BYTES)
if n <= TUN_OFFSET_BYTES || err != nil {
r.tun.log.Errorln("Error reading TUN:", err)
ferr := r.tun.iface.Flush()
if ferr != nil {
r.tun.log.Errorln("Unable to flush packets:", ferr)
}
util.PutBytes(recvd)
} else {
r.tun.handlePacketFrom(r, recvd[:n], err)
r.tun.handlePacketFrom(r, recvd[TUN_OFFSET_BYTES:n+TUN_OFFSET_BYTES], err)
}
if err == nil {
// Now read again
@ -132,43 +83,17 @@ func (tun *TunAdapter) handlePacketFrom(from phony.Actor, packet []byte, err err
// does the work of reading a packet and sending it to the correct tunConn
func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
if err != nil {
tun.log.Errorln("TUN/TAP iface read error:", err)
tun.log.Errorln("TUN iface read error:", err)
return
}
// If it's a TAP adapter, update the buffer slice so that we no longer
// include the ethernet headers
offset := 0
if tun.iface.IsTAP() {
// Set our offset to beyond the ethernet headers
offset = tun_ETHER_HEADER_LENGTH
// Check first of all that we can go beyond the ethernet headers
if len(recvd) <= offset {
return
}
}
// Offset the buffer from now on so that we can ignore ethernet frames if
// they are present
bs := recvd[offset:]
bs := recvd[:]
// Check if the packet is long enough to detect if it's an ICMP packet or not
if len(bs) < 7 {
tun.log.Traceln("TUN/TAP iface read undersized unknown packet, length:", len(bs))
tun.log.Traceln("TUN iface read undersized unknown packet, length:", len(bs))
return
}
// If we detect an ICMP packet then hand it to the ICMPv6 module
if bs[6] == 58 {
// Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full
// Ethernet frame rather than just the IPv6 packet as this is needed for
// NDP to work correctly
if err := tun.icmpv6.ParsePacket(recvd); err == nil {
// We acted on the packet in the ICMPv6 module so don't forward or do
// anything else with it
return
}
}
if offset != 0 {
// Shift forward to avoid leaking bytes off the front of the slice when we eventually store it
bs = append(recvd[:0], bs...)
}
// From the IP header, work out what our source and destination addresses
// and node IDs are. We will need these in order to work out where to send
// the packet
@ -181,7 +106,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized IPv6 header
if len(bs) < 40 {
tun.log.Traceln("TUN/TAP iface read undersized ipv6 packet, length:", len(bs))
tun.log.Traceln("TUN iface read undersized ipv6 packet, length:", len(bs))
return
}
// Check the packet size
@ -195,7 +120,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
} else if bs[0]&0xf0 == 0x40 {
// Check if we have a fully-sized IPv4 header
if len(bs) < 20 {
tun.log.Traceln("TUN/TAP iface read undersized ipv4 packet, length:", len(bs))
tun.log.Traceln("TUN iface read undersized ipv4 packet, length:", len(bs))
return
}
// Check the packet size
@ -267,7 +192,7 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) {
if tc, err = tun._wrap(conn.(*yggdrasil.Conn)); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP iface wrap:", err)
tun.log.Debugln("TUN iface wrap:", err)
return
}
for _, packet := range packets {

View File

@ -18,22 +18,24 @@ import (
"github.com/Arceliar/phony"
"github.com/gologme/log"
"github.com/yggdrasil-network/water"
"golang.zx2c4.com/wireguard/tun"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/types"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
const tun_IPv6_HEADER_LENGTH = 40
const tun_ETHER_HEADER_LENGTH = 14
type MTU = types.MTU
// TunAdapter represents a running TUN/TAP interface and extends the
// yggdrasil.Adapter type. In order to use the TUN/TAP adapter with Yggdrasil,
// you should pass this object to the yggdrasil.SetRouterAdapter() function
// before calling yggdrasil.Start().
const tun_IPv6_HEADER_LENGTH = 40
// TunAdapter represents a running TUN interface and extends the
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
// should pass this object to the yggdrasil.SetRouterAdapter() function before
// calling yggdrasil.Start().
type TunAdapter struct {
core *yggdrasil.Core
writer tunWriter
@ -47,8 +49,8 @@ type TunAdapter struct {
subnet address.Subnet
ckr cryptokey
icmpv6 ICMPv6
mtu int
iface *water.Interface
mtu MTU
iface tun.Device
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
//mutex sync.RWMutex // Protects the below
addrToConn map[address.Address]*tunConn
@ -64,12 +66,12 @@ type TunOptions struct {
// Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults().
func getSupportedMTU(mtu int, istapmode bool) int {
func getSupportedMTU(mtu MTU) MTU {
if mtu < 1280 {
return 1280
}
if mtu > MaximumMTU(istapmode) {
return MaximumMTU(istapmode)
if mtu > MaximumMTU() {
return MaximumMTU()
}
return mtu
}
@ -77,55 +79,38 @@ func getSupportedMTU(mtu int, istapmode bool) int {
// Name returns the name of the adapter, e.g. "tun0". On Windows, this may
// return a canonical adapter name instead.
func (tun *TunAdapter) Name() string {
return tun.iface.Name()
if name, err := tun.iface.Name(); err == nil {
return name
}
return ""
}
// MTU gets the adapter's MTU. This can range between 1280 and 65535, although
// the maximum value is determined by your platform. The returned value will
// never exceed that of MaximumMTU().
func (tun *TunAdapter) MTU() int {
return getSupportedMTU(tun.mtu, tun.IsTAP())
func (tun *TunAdapter) MTU() MTU {
return getSupportedMTU(tun.mtu)
}
// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it
// is a TUN adapter (Layer 3).
func (tun *TunAdapter) IsTAP() bool {
return tun.iface.IsTAP()
}
// DefaultName gets the default TUN/TAP interface name for your platform.
// DefaultName gets the default TUN interface name for your platform.
func DefaultName() string {
return defaults.GetDefaults().DefaultIfName
}
// DefaultMTU gets the default TUN/TAP interface MTU for your platform. This can
// DefaultMTU gets the default TUN interface MTU for your platform. This can
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
func DefaultMTU() int {
ehbytes := 0
if DefaultIsTAP() {
ehbytes = tun_ETHER_HEADER_LENGTH
}
return defaults.GetDefaults().DefaultIfMTU - ehbytes
func DefaultMTU() MTU {
return defaults.GetDefaults().DefaultIfMTU
}
// DefaultIsTAP returns true if the default adapter mode for the current
// platform is TAP (Layer 2) and returns false for TUN (Layer 3).
func DefaultIsTAP() bool {
return defaults.GetDefaults().DefaultIfTAPMode
}
// MaximumMTU returns the maximum supported TUN/TAP interface MTU for your
// MaximumMTU returns the maximum supported TUN interface MTU for your
// platform. This can be as high as 65535, depending on platform, but is never
// lower than 1280.
func MaximumMTU(iftapmode bool) int {
ehbytes := 0
if iftapmode {
ehbytes = tun_ETHER_HEADER_LENGTH
}
return defaults.GetDefaults().MaximumIfMTU - ehbytes
func MaximumMTU() MTU {
return defaults.GetDefaults().MaximumIfMTU
}
// Init initialises the TUN/TAP module. You must have acquired a Listener from
// Init initialises the TUN module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere.
func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log *log.Logger, options interface{}) error {
tunoptions, ok := options.(TunOptions)
@ -145,7 +130,7 @@ func (tun *TunAdapter) Init(core *yggdrasil.Core, config *config.NodeState, log
return nil
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// Start the setup process for the TUN adapter. If successful, starts the
// reader actor to handle packets on that interface.
func (tun *TunAdapter) Start() error {
var err error
@ -157,11 +142,11 @@ func (tun *TunAdapter) Start() error {
func (tun *TunAdapter) _start() error {
if tun.isOpen {
return errors.New("TUN/TAP module is already started")
return errors.New("TUN module is already started")
}
current := tun.config.GetCurrent()
if tun.config == nil || tun.listener == nil || tun.dialer == nil {
return errors.New("no configuration available to TUN/TAP")
return errors.New("no configuration available to TUN")
}
var boxPub crypto.BoxPubKey
boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey)
@ -174,23 +159,19 @@ func (tun *TunAdapter) _start() error {
tun.subnet = *address.SubnetForNodeID(nodeID)
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
if current.IfName == "none" || current.IfName == "dummy" {
tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy")
tun.log.Debugln("Not starting TUN as ifname is none or dummy")
return nil
}
if err := tun.setup(current.IfName, current.IfTAPMode, addr, current.IfMTU); err != nil {
if err := tun.setup(current.IfName, addr, current.IfMTU); err != nil {
return err
}
if tun.MTU() != current.IfMTU {
tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU(tun.IsTAP()))
tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU())
}
tun.core.SetMaximumSessionMTU(uint16(tun.MTU()))
tun.core.SetMaximumSessionMTU(tun.MTU())
tun.isOpen = true
go tun.handler()
tun.reader.Act(nil, tun.reader._read) // Start the reader
tun.icmpv6.Init(tun)
if tun.IsTAP() {
go tun.icmpv6.Solicit(tun.addr)
}
tun.ckr.init(tun)
return nil
}
@ -204,7 +185,7 @@ func (tun *TunAdapter) IsStarted() bool {
return isOpen
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// Start the setup process for the TUN adapter. If successful, starts the
// read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Stop() error {
var err error
@ -216,7 +197,7 @@ func (tun *TunAdapter) Stop() error {
func (tun *TunAdapter) _stop() error {
tun.isOpen = false
// by TUN/TAP, e.g. readers/writers, sessions
// by TUN, e.g. readers/writers, sessions
if tun.iface != nil {
// Just in case we failed to start up the iface for some reason, this can apparently happen on Windows
tun.iface.Close()
@ -224,16 +205,16 @@ func (tun *TunAdapter) _stop() error {
return nil
}
// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig
// UpdateConfig updates the TUN module with the provided config.NodeConfig
// and then signals the various module goroutines to reconfigure themselves if
// needed.
func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) {
tun.log.Debugln("Reloading TUN/TAP configuration...")
tun.log.Debugln("Reloading TUN configuration...")
// Replace the active configuration with the supplied one
tun.config.Replace(*config)
// If the MTU has changed in the TUN/TAP module then this is where we would
// If the MTU has changed in the TUN module then this is where we would
// tell the router so that updated session pings can be sent. However, we
// don't currently update the MTU of the adapter once it has been created so
// this doesn't actually happen in the real world yet.
@ -248,14 +229,14 @@ func (tun *TunAdapter) handler() error {
// Accept the incoming connection
conn, err := tun.listener.Accept()
if err != nil {
tun.log.Errorln("TUN/TAP connection accept error:", err)
tun.log.Errorln("TUN connection accept error:", err)
return err
}
phony.Block(tun, func() {
if _, err := tun._wrap(conn.(*yggdrasil.Conn)); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP handler wrap:", err)
tun.log.Debugln("TUN handler wrap:", err)
}
})
}
@ -270,7 +251,8 @@ func (tun *TunAdapter) _wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
}
c = &s
// Get the remote address and subnet of the other side
remoteNodeID := conn.RemoteAddr().(*crypto.NodeID)
remotePubKey := conn.RemoteAddr().(*crypto.BoxPubKey)
remoteNodeID := crypto.GetNodeID(remotePubKey)
s.addr = *address.AddrForNodeID(remoteNodeID)
s.snet = *address.SubnetForNodeID(remoteNodeID)
// Work out if this is already a destination we already know about

View File

@ -1,4 +1,4 @@
// +build openbsd freebsd netbsd
// +build openbsd freebsd
package tuntap
@ -12,7 +12,7 @@ import (
"golang.org/x/sys/unix"
"github.com/yggdrasil-network/water"
wgtun "golang.zx2c4.com/wireguard/tun"
)
const SIOCSIFADDR_IN6 = (0x80000000) | ((288 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 12
@ -72,34 +72,18 @@ type in6_ifreq_lifetime struct {
ifru_addrlifetime in6_addrlifetime
}
// Sets the IPv6 address of the utun adapter. On all BSD platforms (FreeBSD,
// OpenBSD, NetBSD) an attempt is made to set the adapter properties by using
// a system socket and making syscalls to the kernel. This is not refined though
// and often doesn't work (if at all), therefore if a call fails, it resorts
// to calling "ifconfig" instead.
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if ifname[:4] == "auto" {
ifname = "/dev/tap0"
}
if len(ifname) < 9 {
panic("TUN/TAP name must be in format /dev/tunX or /dev/tapX")
}
switch {
case iftapmode || ifname[:8] == "/dev/tap":
config = water.Config{DeviceType: water.TAP}
case !iftapmode || ifname[:8] == "/dev/tun":
panic("TUN mode is not currently supported on this platform, please use TAP instead")
default:
panic("TUN/TAP name must be in format /dev/tunX or /dev/tapX")
}
config.Name = ifname
iface, err := water.New(config)
// Configures the TUN adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error {
iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil {
panic(err)
}
tun.iface = iface
tun.mtu = getSupportedMTU(mtu, iftapmode)
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(MTU(mtu))
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
}
@ -114,13 +98,13 @@ func (tun *TunAdapter) setupAddress(addr string) error {
}
// Friendly output
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface name: %s", tun.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
// Create the MTU request
var ir in6_ifreq_mtu
copy(ir.ifr_name[:], tun.iface.Name())
copy(ir.ifr_name[:], tun.Name())
ir.ifru_mtu = int(tun.mtu)
// Set the MTU
@ -129,7 +113,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
// Fall back to ifconfig to set the MTU
cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu))
cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu))
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
@ -141,7 +125,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
// Create the address request
// FIXME: I don't work!
var ar in6_ifreq_addr
copy(ar.ifr_name[:], tun.iface.Name())
copy(ar.ifr_name[:], tun.Name())
ar.ifru_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifru_addr))
ar.ifru_addr.sin6_family = unix.AF_INET6
parts := strings.Split(strings.Split(addr, "/")[0], ":")
@ -158,7 +142,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
// Fall back to ifconfig to set the address
cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr)
cmd := exec.Command("ifconfig", tun.Name(), "inet6", addr)
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {

View File

@ -12,22 +12,24 @@ import (
"golang.org/x/sys/unix"
water "github.com/yggdrasil-network/water"
wgtun "golang.zx2c4.com/wireguard/tun"
)
// Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if iftapmode {
tun.log.Warnln("Warning: TAP mode is not supported on this platform, defaulting to TUN")
iftapmode = false
func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error {
if ifname == "auto" {
ifname = "utun"
}
config := water.Config{DeviceType: water.TUN}
iface, err := water.New(config)
iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil {
panic(err)
}
tun.iface = iface
tun.mtu = getSupportedMTU(mtu, iftapmode)
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(MTU(mtu))
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
}
@ -80,7 +82,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
}
var ar in6_aliasreq
copy(ar.ifra_name[:], tun.iface.Name())
copy(ar.ifra_name[:], tun.Name())
ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask))
b := make([]byte, 16)
@ -104,7 +106,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME
var ir ifreq
copy(ir.ifr_name[:], tun.iface.Name())
copy(ir.ifr_name[:], tun.Name())
ir.ifru_mtu = uint32(tun.mtu)
tun.log.Infof("Interface name: %s", ar.ifra_name)

View File

@ -6,31 +6,24 @@ package tuntap
import (
"github.com/vishvananda/netlink"
water "github.com/yggdrasil-network/water"
wgtun "golang.zx2c4.com/wireguard/tun"
)
// Configures the TAP adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
} else {
config = water.Config{DeviceType: water.TUN}
// Configures the TUN adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error {
if ifname == "auto" {
ifname = "\000"
}
if ifname != "" && ifname != "auto" {
config.Name = ifname
}
iface, err := water.New(config)
iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil {
panic(err)
}
tun.iface = iface
tun.mtu = getSupportedMTU(mtu, iftapmode)
// Friendly output
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(MTU(mtu))
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
}
@ -43,18 +36,22 @@ func (tun *TunAdapter) setupAddress(addr string) error {
if err != nil {
return err
}
nlintf, err := netlink.LinkByName(tun.iface.Name())
nlintf, err := netlink.LinkByName(tun.Name())
if err != nil {
return err
}
if err := netlink.AddrAdd(nlintf, nladdr); err != nil {
return err
}
if err := netlink.LinkSetMTU(nlintf, tun.mtu); err != nil {
if err := netlink.LinkSetMTU(nlintf, int(tun.mtu)); err != nil {
return err
}
if err := netlink.LinkSetUp(nlintf); err != nil {
return err
}
// Friendly output
tun.log.Infof("Interface name: %s", tun.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return nil
}

View File

@ -1,33 +1,32 @@
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile
package tuntap
import water "github.com/yggdrasil-network/water"
// This is to catch unsupported platforms
// If your platform supports tun devices, you could try configuring it manually
// Creates the TUN/TAP adapter, if supported by the Water library. Note that
// no guarantees are made at this point on an unsupported platform.
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
} else {
config = water.Config{DeviceType: water.TUN}
}
iface, err := water.New(config)
import (
wgtun "golang.zx2c4.com/wireguard/tun"
)
// Configures the TUN adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, addr string, mtu int) error {
iface, err := wgtun.CreateTUN(ifname, mtu)
if err != nil {
panic(err)
}
tun.iface = iface
tun.mtu = getSupportedMTU(mtu, iftapmode)
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(mtu)
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
}
// We don't know how to set the IPv6 address on an unknown platform, therefore
// write about it to stdout and don't try to do anything further.
func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Warnln("Warning: Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
tun.log.Warnln("Warning: Platform not supported, you must set the address of", tun.Name(), "to", addr)
return nil
}

View File

@ -1,116 +1,150 @@
// +build windows
package tuntap
import (
"bytes"
"errors"
"fmt"
"os/exec"
"strings"
"time"
"log"
"net"
water "github.com/yggdrasil-network/water"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"golang.org/x/sys/windows"
wgtun "golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/windows/elevate"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
// This is to catch Windows platforms
// Configures the TAP adapter with the correct IPv6 address and MTU. On Windows
// we don't make use of a direct operating system API to do this - we instead
// delegate the hard work to "netsh".
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if !iftapmode {
tun.log.Warnln("Warning: TUN mode is not supported on this platform, defaulting to TAP")
iftapmode = true
}
config := water.Config{DeviceType: water.TAP}
config.PlatformSpecificParams.ComponentID = "tap0901"
config.PlatformSpecificParams.Network = "169.254.0.1/32"
// Configures the TUN adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, addr string, mtu MTU) error {
if ifname == "auto" {
config.PlatformSpecificParams.InterfaceName = ""
} else {
config.PlatformSpecificParams.InterfaceName = ifname
ifname = defaults.GetDefaults().DefaultIfName
}
iface, err := water.New(config)
if err != nil {
return elevate.DoAsSystem(func() error {
var err error
var iface wgtun.Device
var guid windows.GUID
if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil {
return err
}
if iface.Name() == "" {
return errors.New("unable to find TAP adapter with component ID " + config.PlatformSpecificParams.ComponentID)
}
// Reset the adapter - this invalidates iface so we'll need to get a new one
cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil {
return err
}
time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect
// Bring the interface back up
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err = cmd.CombinedOutput()
if err != nil {
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect
// Get a new iface
iface, err = water.New(config)
if err != nil {
panic(err)
}
tun.iface = iface
tun.mtu = getSupportedMTU(mtu, iftapmode)
err = tun.setupMTU(tun.mtu)
if err != nil {
panic(err)
if err = tun.setupAddress(addr); err != nil {
tun.log.Errorln("Failed to set up TUN address:", err)
return err
}
// Friendly output
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil {
tun.log.Errorln("Failed to set up TUN MTU:", err)
return err
}
if mtu, err := iface.MTU(); err == nil {
tun.mtu = MTU(mtu)
}
return nil
})
}
// Sets the MTU of the TAP adapter.
func (tun *TunAdapter) setupMTU(mtu int) error {
if tun.iface == nil || tun.iface.Name() == "" {
return errors.New("Can't configure MTU as TAP adapter is not present")
func (tun *TunAdapter) setupMTU(mtu MTU) error {
if tun.iface == nil || tun.Name() == "" {
return errors.New("Can't configure MTU as TUN adapter is not present")
}
// Set MTU
cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("mtu=%d", mtu),
"store=active")
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if intf, ok := tun.iface.(*wgtun.NativeTun); ok {
luid := winipcfg.LUID(intf.LUID())
ipfamily, err := luid.IPInterface(windows.AF_INET6)
if err != nil {
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect
ipfamily.NLMTU = uint32(mtu)
intf.ForceMTU(int(ipfamily.NLMTU))
ipfamily.UseAutomaticMetric = false
ipfamily.Metric = 0
ipfamily.DadTransmits = 0
ipfamily.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
if err := ipfamily.Set(); err != nil {
return err
}
}
return nil
}
// Sets the IPv6 address of the TAP adapter.
func (tun *TunAdapter) setupAddress(addr string) error {
if tun.iface == nil || tun.iface.Name() == "" {
return errors.New("Can't configure IPv6 address as TAP adapter is not present")
if tun.iface == nil || tun.Name() == "" {
return errors.New("Can't configure IPv6 address as TUN adapter is not present")
}
if intf, ok := tun.iface.(*wgtun.NativeTun); ok {
if ipaddr, ipnet, err := net.ParseCIDR(addr); err == nil {
luid := winipcfg.LUID(intf.LUID())
addresses := append([]net.IPNet{}, net.IPNet{
IP: ipaddr,
Mask: ipnet.Mask,
})
err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses)
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses)
err = luid.SetIPAddressesForFamily(windows.AF_INET6, addresses)
}
// Set address
cmd := exec.Command("netsh", "interface", "ipv6", "add", "address",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("addr=%s", addr),
"store=active")
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
time.Sleep(time.Second) // FIXME artifical delay to give netsh time to take effect
} else {
return err
}
} else {
return errors.New("unable to get NativeTUN")
}
return nil
}
/*
* cleanupAddressesOnDisconnectedInterfaces
* SPDX-License-Identifier: MIT
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
*/
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) {
if len(addresses) == 0 {
return
}
includedInAddresses := func(a net.IPNet) bool {
// TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer!
for _, addr := range addresses {
ip := addr.IP
if ip4 := ip.To4(); ip4 != nil {
ip = ip4
}
mA, _ := addr.Mask.Size()
mB, _ := a.Mask.Size()
if bytes.Equal(ip, a.IP) && mA == mB {
return true
}
}
return false
}
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
if err != nil {
return
}
for _, iface := range interfaces {
if iface.OperStatus == winipcfg.IfOperStatusUp {
continue
}
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
ip := address.Address.IP()
ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))}
if includedInAddresses(ipnet) {
log.Printf("Cleaning up stale address %s from interface %s", ipnet.String(), iface.FriendlyName())
iface.LUID.DeleteIPAddress(ipnet)
}
}
}
}

3
src/types/types.go Normal file
View File

@ -0,0 +1,3 @@
package types
type MTU uint16

View File

@ -103,11 +103,11 @@ func GetFlowKey(bs []byte) uint64 {
// Get the IP protocol version from the packet
switch bs[0] & 0xf0 {
case 0x40: // IPv4 packet
// Check the packet meets minimum UDP packet length
if len(bs) >= 24 {
// Is the protocol TCP, UDP or SCTP?
if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 {
ihl := bs[0] & 0x0f * 4 // Header length
ihl := (bs[0] & 0x0f) * 4 // whole IPv4 header length (min 20)
// 8 is minimum UDP packet length
if ihl >= 20 && len(bs)-int(ihl) >= 8 {
switch bs[9] /* protocol */ {
case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ :
flowkey = uint64(bs[9])<<32 /* proto */ |
uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ |
uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */
@ -119,8 +119,8 @@ func GetFlowKey(bs []byte) uint64 {
// If the flowlabel isn't present, make protokey from proto | sport | dport
// if the packet meets minimum UDP packet length
if flowkey == 0 && len(bs) >= 48 {
// Is the protocol TCP, UDP or SCTP?
if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 {
switch bs[9] /* protocol */ {
case 0x06 /* TCP */, 0x11 /* UDP */, 0x84 /* SCTP */ :
flowkey = uint64(bs[6])<<32 /* proto */ |
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */

View File

@ -99,7 +99,7 @@ type Session struct {
Coords []uint64 // The coordinates of the remote node
BytesSent uint64 // Bytes sent to the session
BytesRecvd uint64 // Bytes received from the session
MTU uint16 // The maximum supported message size of the session
MTU MTU // The maximum supported message size of the session
Uptime time.Duration // How long this session has been active for
WasMTUFixed bool // This field is no longer used
}
@ -364,8 +364,8 @@ func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
}
// GetMaximumSessionMTU returns the maximum allowed session MTU size.
func (c *Core) GetMaximumSessionMTU() uint16 {
var mtu uint16
func (c *Core) GetMaximumSessionMTU() MTU {
var mtu MTU
phony.Block(&c.router, func() {
mtu = c.router.sessions.myMaximumMTU
})
@ -375,7 +375,7 @@ func (c *Core) GetMaximumSessionMTU() uint16 {
// SetMaximumSessionMTU sets the maximum allowed session MTU size. The default
// value is 65535 bytes. Session pings will be sent to update all open sessions
// if the MTU has changed.
func (c *Core) SetMaximumSessionMTU(mtu uint16) {
func (c *Core) SetMaximumSessionMTU(mtu MTU) {
phony.Block(&c.router, func() {
if c.router.sessions.myMaximumMTU != mtu {
c.router.sessions.myMaximumMTU = mtu
@ -390,7 +390,6 @@ func (c *Core) SetMaximumSessionMTU(mtu uint16) {
// necessary when, e.g. crawling the network.
func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) (NodeInfoPayload, error) {
response := make(chan *NodeInfoPayload, 1)
sendNodeInfoRequest := func() {
c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) {
defer func() { recover() }()
select {
@ -399,8 +398,7 @@ func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool)
}
})
c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false)
}
phony.Block(&c.router, sendNodeInfoRequest)
phony.Block(&c.router.nodeinfo, func() {}) // Wait for sendNodeInfo before starting timer
timer := time.AfterFunc(6*time.Second, func() { close(response) })
defer timer.Stop()
for res := range response {

View File

@ -7,11 +7,14 @@ import (
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/types"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/Arceliar/phony"
)
type MTU = types.MTU
// ConnError implements the net.Error interface
type ConnError struct {
error
@ -65,7 +68,7 @@ type Conn struct {
nodeID *crypto.NodeID
nodeMask *crypto.NodeID
session *sessionInfo
mtu uint16
mtu MTU
readCallback func([]byte)
readBuffer chan []byte
}
@ -93,7 +96,7 @@ func (c *Conn) String() string {
return s
}
func (c *Conn) setMTU(from phony.Actor, mtu uint16) {
func (c *Conn) setMTU(from phony.Actor, mtu MTU) {
c.Act(from, func() { c.mtu = mtu })
}
@ -128,7 +131,7 @@ func (c *Conn) search() error {
}
}
sinfo := c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
sinfo.continueSearch()
sinfo.startSearch()
} else {
err = errors.New("search already exists")
close(done)
@ -152,7 +155,7 @@ func (c *Conn) doSearch() {
sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo)
// Start the search
sinfo.continueSearch()
sinfo.startSearch()
}
}
c.core.router.Act(c.session, routerWork)
@ -164,7 +167,7 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool)
c := util.CancellationWithDeadline(c.session.cancel, *t)
return c, true
} else {
// No deadline was set, so just return the existinc cancellation and a dummy value
// No deadline was set, so just return the existing cancellation and a dummy value
return c.session.cancel, false
}
}
@ -279,7 +282,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error {
}
// WriteFrom should be called by a phony.Actor, and tells the Conn to send a
// message. This is used internaly by Write. If the callback is called with a
// message. This is used internally by Write. If the callback is called with a
// non-nil value, then it is safe to reuse the argument FlowKeyMessage.
func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) {
c.Act(from, func() {
@ -347,16 +350,19 @@ func (c *Conn) Close() (err error) {
return
}
// LocalAddr returns the complete node ID of the local side of the connection.
// This is always going to return your own node's node ID.
// LocalAddr returns the complete public key of the local side of the
// connection. This is always going to return your own node's public key.
func (c *Conn) LocalAddr() net.Addr {
return crypto.GetNodeID(&c.core.boxPub)
return &c.core.boxPub
}
// RemoteAddr returns the complete node ID of the remote side of the connection.
// RemoteAddr returns the complete public key of the remote side of the
// connection.
func (c *Conn) RemoteAddr() net.Addr {
// RemoteAddr is set during the dial or accept, and isn't changed, so it's safe to access directly
return c.nodeID
if c.session != nil {
return &c.session.theirPermPub
}
return nil
}
// SetDeadline is equivalent to calling both SetReadDeadline and

View File

@ -22,7 +22,7 @@ func GenerateConfig() *config.NodeConfig {
return cfg
}
// GetLoggerWithPrefix creates a new logger instance wih prefix.
// GetLoggerWithPrefix creates a new logger instance with prefix.
// If verbose is set to true, three log levels are enabled: "info", "warn", "error".
func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
l := log.New(os.Stderr, prefix, log.Flags())

View File

@ -20,7 +20,7 @@ const (
)
// dhtInfo represents everything we know about a node in the DHT.
// This includes its key, a cache of it's NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance.
// This includes its key, a cache of its NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance.
type dhtInfo struct {
nodeID_hidden *crypto.NodeID
key crypto.BoxPubKey
@ -387,6 +387,8 @@ func (t *dht) getImportant() []*dhtInfo {
if dist < minDist {
minDist = dist
important = append(important, info)
} else if len(important) < 2 {
important = append(important, info)
}
}
var temp []*dhtInfo
@ -397,6 +399,8 @@ func (t *dht) getImportant() []*dhtInfo {
if dist < minDist {
minDist = dist
temp = append(temp, info)
} else if len(temp) < 2 {
temp = append(temp, info)
}
}
for idx := len(temp) - 1; idx >= 0; idx-- {

View File

@ -17,19 +17,32 @@ type Dialer struct {
core *Core
}
// Dial opens a session to the given node. The first paramter should be "nodeid"
// and the second parameter should contain a hexadecimal representation of the
// target node ID. It uses DialContext internally.
// Dial opens a session to the given node. The first parameter should be
// "curve25519" or "nodeid" and the second parameter should contain a
// hexadecimal representation of the target. It uses DialContext internally.
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(nil, network, address)
}
// DialContext is used internally by Dial, and should only be used with a context that includes a timeout. It uses DialByNodeIDandMask internally.
// DialContext is used internally by Dial, and should only be used with a
// context that includes a timeout. It uses DialByNodeIDandMask internally when
// the network is "nodeid", or DialByPublicKey when the network is "curve25519".
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
var nodeID crypto.NodeID
var nodeMask crypto.NodeID
// Process
switch network {
case "curve25519":
dest, err := hex.DecodeString(address)
if err != nil {
return nil, err
}
if len(dest) != crypto.BoxPubKeyLen {
return nil, errors.New("invalid key length supplied")
}
var pubKey crypto.BoxPubKey
copy(pubKey[:], dest)
return d.DialByPublicKey(ctx, &pubKey)
case "nodeid":
// A node ID was provided - we don't need to do anything special with it
if tokens := strings.Split(address, "/"); len(tokens) == 2 {
@ -62,15 +75,19 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.
}
}
// DialByNodeIDandMask opens a session to the given node based on raw
// NodeID parameters. If ctx is nil or has no timeout, then a default timeout of 6 seconds will apply, beginning *after* the search finishes.
// DialByNodeIDandMask opens a session to the given node based on raw NodeID
// parameters. If ctx is nil or has no timeout, then a default timeout of 6
// seconds will apply, beginning *after* the search finishes.
func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *crypto.NodeID) (net.Conn, error) {
startDial := time.Now()
conn := newConn(d.core, nodeID, nodeMask, nil)
if err := conn.search(); err != nil {
// TODO: make searches take a context, so they can be cancelled early
conn.Close()
return nil, err
}
endSearch := time.Now()
d.core.log.Debugln("Dial searched for:", nodeID, "in time:", endSearch.Sub(startDial))
conn.session.setConn(nil, conn)
var cancel context.CancelFunc
if ctx == nil {
@ -80,9 +97,24 @@ func (d *Dialer) DialByNodeIDandMask(ctx context.Context, nodeID, nodeMask *cryp
defer cancel()
select {
case <-conn.session.init:
endInit := time.Now()
d.core.log.Debugln("Dial initialized session for:", nodeID, "in time:", endInit.Sub(endSearch))
d.core.log.Debugln("Finished dial for:", nodeID, "in time:", endInit.Sub(startDial))
return conn, nil
case <-ctx.Done():
conn.Close()
return nil, errors.New("session handshake timeout")
}
}
// DialByPublicKey opens a session to the given node based on the public key. If
// ctx is nil or has no timeout, then a default timeout of 6 seconds will apply,
// beginning *after* the search finishes.
func (d *Dialer) DialByPublicKey(ctx context.Context, pubKey *crypto.BoxPubKey) (net.Conn, error) {
nodeID := crypto.GetNodeID(pubKey)
var nodeMask crypto.NodeID
for i := range nodeMask {
nodeMask[i] = 0xFF
}
return d.DialByNodeIDandMask(ctx, nodeID, &nodeMask)
}

View File

@ -113,9 +113,9 @@ a Dialer:
// ...
}
You can then dial using the 16-byte node ID in hexadecimal format, for example:
You can then dial using the node's public key in hexadecimal format, for example:
conn, err := dialer.Dial("nodeid", "24a58cfce691ec016b0f698f7be1bee983cea263781017e99ad3ef62b4ef710a45d6c1a072c5ce46131bd574b78818c9957042cafeeed13966f349e94eb771bf")
conn, err := dialer.Dial("curve25519", "55071be281f50d0abbda63aadc59755624280c44b2f1f47684317aa4e0325604")
if err != nil {
// ...
}

View File

@ -64,6 +64,7 @@ type linkInterface struct {
closeTimer *time.Timer // Fires when the link has been idle so long we need to close it
inSwitch bool // True if the switch is tracking this link
stalled bool // True if we haven't been receiving any response traffic
unstalled bool // False if an idle notification to the switch hasn't been sent because we stalled (or are first starting up)
}
func (l *link) init(c *Core) error {
@ -122,7 +123,7 @@ func (l *link) listen(uri string) error {
}
func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) {
// Technically anything unique would work for names, but lets pick something human readable, just for debugging
// Technically anything unique would work for names, but let's pick something human readable, just for debugging
intf := linkInterface{
name: name,
link: l,
@ -324,13 +325,17 @@ func (intf *linkInterface) notifySent(size int, isLinkTraffic bool) {
// Notify the switch that we're ready for more traffic, assuming we're not in a stalled state
func (intf *linkInterface) _notifySwitch() {
if !intf.inSwitch && !intf.stalled {
if !intf.inSwitch {
if intf.stalled {
intf.unstalled = false
} else {
intf.inSwitch = true
intf.link.core.switchTable.Act(intf, func() {
intf.link.core.switchTable._idleIn(intf.peer.port)
})
}
}
}
// Set the peer as stalled, to prevent them from returning to the switch until a read succeeds
func (intf *linkInterface) notifyStalled() {
@ -362,7 +367,10 @@ func (intf *linkInterface) notifyRead(size int) {
intf.stallTimer = nil
}
intf.stalled = false
if !intf.unstalled {
intf._notifySwitch()
intf.unstalled = true
}
if size > 0 && intf.stallTimer == nil {
intf.stallTimer = time.AfterFunc(keepAliveTime, intf.notifyDoKeepAlive)
}

View File

@ -39,7 +39,7 @@ func (l *Listener) Close() (err error) {
return nil
}
// Addr is not implemented for this type yet
// Addr returns the address of the listener
func (l *Listener) Addr() net.Addr {
return nil
return &l.core.boxPub
}

View File

@ -5,21 +5,19 @@ import (
"errors"
"runtime"
"strings"
"sync"
"time"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
)
type nodeinfo struct {
phony.Inbox
core *Core
myNodeInfo NodeInfoPayload
myNodeInfoMutex sync.RWMutex
callbacks map[crypto.BoxPubKey]nodeinfoCallback
callbacksMutex sync.Mutex
cache map[crypto.BoxPubKey]nodeinfoCached
cacheMutex sync.RWMutex
}
type nodeinfoCached struct {
@ -43,35 +41,43 @@ type nodeinfoReqRes struct {
// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep
// the cache/callback maps clean of stale entries
func (m *nodeinfo) init(core *Core) {
m.Act(nil, func() {
m._init(core)
})
}
func (m *nodeinfo) _init(core *Core) {
m.core = core
m.callbacks = make(map[crypto.BoxPubKey]nodeinfoCallback)
m.cache = make(map[crypto.BoxPubKey]nodeinfoCached)
var f func()
f = func() {
m.callbacksMutex.Lock()
m._cleanup()
}
func (m *nodeinfo) _cleanup() {
for boxPubKey, callback := range m.callbacks {
if time.Since(callback.created) > time.Minute {
delete(m.callbacks, boxPubKey)
}
}
m.callbacksMutex.Unlock()
m.cacheMutex.Lock()
for boxPubKey, cache := range m.cache {
if time.Since(cache.created) > time.Hour {
delete(m.cache, boxPubKey)
}
}
m.cacheMutex.Unlock()
time.AfterFunc(time.Second*30, f)
}
go f()
time.AfterFunc(time.Second*30, func() {
m.Act(nil, m._cleanup)
})
}
// Add a callback for a nodeinfo lookup
func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
m.Act(nil, func() {
m._addCallback(sender, call)
})
}
func (m *nodeinfo) _addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) {
m.callbacks[sender] = nodeinfoCallback{
created: time.Now(),
call: call,
@ -79,9 +85,7 @@ func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *Node
}
// Handles the callback, if there is one
func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
func (m *nodeinfo) _callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) {
if callback, ok := m.callbacks[sender]; ok {
callback.call(&nodeinfo)
delete(m.callbacks, sender)
@ -89,16 +93,26 @@ func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) {
}
// Get the current node's nodeinfo
func (m *nodeinfo) getNodeInfo() NodeInfoPayload {
m.myNodeInfoMutex.RLock()
defer m.myNodeInfoMutex.RUnlock()
func (m *nodeinfo) getNodeInfo() (p NodeInfoPayload) {
phony.Block(m, func() {
p = m._getNodeInfo()
})
return
}
func (m *nodeinfo) _getNodeInfo() NodeInfoPayload {
return m.myNodeInfo
}
// Set the current node's nodeinfo
func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error {
m.myNodeInfoMutex.Lock()
defer m.myNodeInfoMutex.Unlock()
func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) (err error) {
phony.Block(m, func() {
err = m._setNodeInfo(given, privacy)
})
return
}
func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error {
defaults := map[string]interface{}{
"buildname": version.BuildName(),
"buildversion": version.BuildVersion(),
@ -134,9 +148,7 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error {
}
// Add nodeinfo into the cache for a node
func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) {
m.cacheMutex.Lock()
defer m.cacheMutex.Unlock()
func (m *nodeinfo) _addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) {
m.cache[key] = nodeinfoCached{
created: time.Now(),
payload: payload,
@ -144,9 +156,7 @@ func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPaylo
}
// Get a nodeinfo entry from the cache
func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) {
m.cacheMutex.RLock()
defer m.cacheMutex.RUnlock()
func (m *nodeinfo) _getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) {
if nodeinfo, ok := m.cache[key]; ok {
return nodeinfo.payload, nil
}
@ -154,22 +164,34 @@ func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, err
}
// Handles a nodeinfo request/response - called from the router
func (m *nodeinfo) handleNodeInfo(nodeinfo *nodeinfoReqRes) {
func (m *nodeinfo) handleNodeInfo(from phony.Actor, nodeinfo *nodeinfoReqRes) {
m.Act(from, func() {
m._handleNodeInfo(nodeinfo)
})
}
func (m *nodeinfo) _handleNodeInfo(nodeinfo *nodeinfoReqRes) {
if nodeinfo.IsResponse {
m.callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo)
m.addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo)
m._callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo)
m._addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo)
} else {
m.sendNodeInfo(nodeinfo.SendPermPub, nodeinfo.SendCoords, true)
m._sendNodeInfo(nodeinfo.SendPermPub, nodeinfo.SendCoords, true)
}
}
// Send nodeinfo request or response - called from the router
func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) {
m.Act(nil, func() {
m._sendNodeInfo(key, coords, isResponse)
})
}
func (m *nodeinfo) _sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse bool) {
table := m.core.switchTable.table.Load().(lookupTable)
nodeinfo := nodeinfoReqRes{
SendCoords: table.self.getCoords(),
IsResponse: isResponse,
NodeInfo: m.getNodeInfo(),
NodeInfo: m._getNodeInfo(),
}
bs := nodeinfo.encode()
shared := m.core.router.sessions.getSharedKey(&m.core.boxPriv, &key)

View File

@ -19,7 +19,7 @@ import (
// The peers struct represents peers with an active connection.
// Incoming packets are passed to the corresponding peer, which handles them somehow.
// In most cases, this involves passing the packet to the handler for outgoing traffic to another peer.
// In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch.
// In other cases, its link protocol traffic is used to build the spanning tree, in which case this checks signatures and passes the message along to the switch.
type peers struct {
core *Core
mutex sync.Mutex // Synchronize writes to atomic
@ -90,7 +90,7 @@ func (ps *peers) putPorts(ports map[switchPort]*peer) {
ps.ports.Store(ports)
}
// Information known about a peer, including thier box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic
// Information known about a peer, including their box/sig keys, precomputed shared keys (static and ephemeral) and a handler for their outgoing traffic
type peer struct {
phony.Inbox
core *Core
@ -356,7 +356,7 @@ func (p *peer) _handleSwitchMsg(packet []byte) {
p.dinfo = nil
return
}
// Pass a mesage to the dht informing it that this peer (still) exists
// Pass a message to the dht informing it that this peer (still) exists
loc.coords = loc.coords[:len(loc.coords)-1]
p.dinfo = &dhtInfo{
key: p.box,

View File

@ -78,6 +78,7 @@ func (r *router) init(core *Core) {
func (r *router) reconfigure() {
// Reconfigure the router
current := r.core.config.GetCurrent()
r.core.log.Println("Reloading NodeInfo...")
if err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy); err != nil {
r.core.log.Errorln("Error reloading NodeInfo:", err)
} else {
@ -249,5 +250,5 @@ func (r *router) _handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) {
return
}
req.SendPermPub = *fromKey
r.nodeinfo.handleNodeInfo(&req)
r.nodeinfo.handleNodeInfo(r, &req)
}

View File

@ -22,13 +22,9 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// This defines the maximum number of dhtInfo that we keep track of for nodes to query in an ongoing search.
const search_MAX_SEARCH_SIZE = 16
// This defines the time after which we send a new search packet.
// Search packets are sent automatically immediately after a response is received.
// So this allows for timeouts and for long searches to become increasingly parallel.
const search_RETRY_TIME = time.Second
// This defines the time after which we time out a search (so it can restart).
const search_RETRY_TIME = 3 * time.Second
const search_STEP_TIME = 100 * time.Millisecond
// Information about an ongoing search.
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
@ -37,10 +33,11 @@ type searchInfo struct {
dest crypto.NodeID
mask crypto.NodeID
time time.Time
toVisit []*dhtInfo
visited map[crypto.NodeID]bool
visited crypto.NodeID // Closest address visited so far
callback func(*sessionInfo, error)
// TODO context.Context for timeout and cancellation
send uint64 // log number of requests sent
recv uint64 // log number of responses received
}
// This stores a map of active searches.
@ -78,100 +75,108 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba
// If there is, it adds the response info to the search and triggers a new search step.
// If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more.
func (sinfo *searchInfo) handleDHTRes(res *dhtRes) {
if res == nil || sinfo.checkDHTRes(res) {
// Either we don't recognize this search, or we just finished it
return
if res != nil {
sinfo.recv++
if sinfo.checkDHTRes(res) {
return // Search finished successfully
}
// Add to the search and continue
sinfo.addToSearch(res)
sinfo.doSearchStep()
// Use results to start an additional search thread
infos := append([]*dhtInfo(nil), res.Infos...)
infos = sinfo.getAllowedInfos(infos)
if len(infos) > 0 {
sinfo.continueSearch(infos)
}
// Adds the information from a dhtRes to an ongoing search.
// Info about a node that has already been visited is not re-added to the search.
// Duplicate information about nodes toVisit is deduplicated (the newest information is kept).
// The toVisit list is sorted in ascending order of keyspace distance from the destination.
func (sinfo *searchInfo) addToSearch(res *dhtRes) {
// Add responses to toVisit if closer to dest than the res node
from := dhtInfo{key: res.Key, coords: res.Coords}
sinfo.visited[*from.getNodeID()] = true
for _, info := range res.Infos {
if *info.getNodeID() == sinfo.searches.router.dht.nodeID || sinfo.visited[*info.getNodeID()] {
continue
}
if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) {
// Response is closer to the destination
sinfo.toVisit = append(sinfo.toVisit, info)
}
}
// Deduplicate
vMap := make(map[crypto.NodeID]*dhtInfo)
for _, info := range sinfo.toVisit {
vMap[*info.getNodeID()] = info
}
sinfo.toVisit = sinfo.toVisit[:0]
for _, info := range vMap {
sinfo.toVisit = append(sinfo.toVisit, info)
}
// Sort
sort.SliceStable(sinfo.toVisit, func(i, j int) bool {
// Should return true if i is closer to the destination than j
return dht_ordered(&res.Dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID())
})
// Truncate to some maximum size
if len(sinfo.toVisit) > search_MAX_SEARCH_SIZE {
sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE]
}
}
// If there are no nodes left toVisit, then this cleans up the search.
// If there has been no response in too long, then this cleans up the search.
// Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping.
func (sinfo *searchInfo) doSearchStep() {
if len(sinfo.toVisit) == 0 {
if time.Since(sinfo.time) > search_RETRY_TIME {
// Dead end and no response in too long, do cleanup
delete(sinfo.searches.searches, sinfo.dest)
sinfo.callback(nil, errors.New("search reached dead end"))
}
return
}
func (sinfo *searchInfo) doSearchStep(infos []*dhtInfo) {
if len(infos) > 0 {
// Send to the next search target
var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
next := infos[0]
rq := dhtReqKey{next.key, sinfo.dest}
sinfo.searches.router.dht.addCallback(&rq, sinfo.handleDHTRes)
sinfo.searches.router.dht.ping(next, &sinfo.dest)
sinfo.time = time.Now()
sinfo.send++
}
}
// If we've recenty sent a ping for this search, do nothing.
// Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME.
func (sinfo *searchInfo) continueSearch() {
sinfo.doSearchStep()
// In case the search dies, try to spawn another thread later
// Note that this will spawn multiple parallel searches as time passes
// Any that die aren't restarted, but a new one will start later
time.AfterFunc(search_RETRY_TIME, func() {
// Get a list of search targets that are close enough to the destination to try
// Requires an initial list as input
func (sinfo *searchInfo) getAllowedInfos(infos []*dhtInfo) []*dhtInfo {
sort.SliceStable(infos, func(i, j int) bool {
// Should return true if i is closer to the destination than j
return dht_ordered(&sinfo.dest, infos[i].getNodeID(), infos[j].getNodeID())
})
// Remove anything too far away to be useful
for idx, info := range infos {
if !dht_ordered(&sinfo.dest, info.getNodeID(), &sinfo.visited) {
infos = infos[:idx]
break
}
}
return infos
}
// Run doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME.
// Must not be called with an empty list of infos
func (sinfo *searchInfo) continueSearch(infos []*dhtInfo) {
sinfo.doSearchStep(infos)
infos = infos[1:] // Remove the node we just tried
// In case there's no response, try the next node in infos later
time.AfterFunc(search_STEP_TIME, func() {
sinfo.searches.router.Act(nil, func() {
// FIXME this keeps the search alive forever if not for the searches map, fix that
newSearchInfo := sinfo.searches.searches[sinfo.dest]
if newSearchInfo != sinfo {
return
}
sinfo.continueSearch()
// Get good infos here instead of at the top, to make sure we can always start things off with a continueSearch call to ourself
infos = sinfo.getAllowedInfos(infos)
if len(infos) > 0 {
sinfo.continueSearch(infos)
}
})
})
}
// Initially start a search
func (sinfo *searchInfo) startSearch() {
loc := sinfo.searches.router.core.switchTable.getLocator()
var infos []*dhtInfo
infos = append(infos, &dhtInfo{
key: sinfo.searches.router.core.boxPub,
coords: loc.getCoords(),
})
// Start the search by asking ourself, useful if we're the destination
sinfo.continueSearch(infos)
// Start a timer to clean up the search if everything times out
var cleanupFunc func()
cleanupFunc = func() {
sinfo.searches.router.Act(nil, func() {
// FIXME this keeps the search alive forever if not for the searches map, fix that
newSearchInfo := sinfo.searches.searches[sinfo.dest]
if newSearchInfo != sinfo {
return
}
elapsed := time.Since(sinfo.time)
if elapsed > search_RETRY_TIME {
// cleanup
delete(sinfo.searches.searches, sinfo.dest)
sinfo.callback(nil, errors.New("search reached dead end"))
return
}
time.AfterFunc(search_RETRY_TIME-elapsed, cleanupFunc)
})
}
time.AfterFunc(search_RETRY_TIME, cleanupFunc)
}
// Calls create search, and initializes the iterative search parts of the struct before returning it.
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
sinfo := s.createSearch(dest, mask, callback)
sinfo.visited = make(map[crypto.NodeID]bool)
loc := s.router.core.switchTable.getLocator()
sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{
key: s.router.core.boxPub,
coords: loc.getCoords(),
}) // Start the search by asking ourself, useful if we're the destination
sinfo.visited = s.router.dht.nodeID
return sinfo
}
@ -179,7 +184,14 @@ func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callb
// If the response is from the target, get/create a session, trigger a session ping, and return true.
// Otherwise return false.
func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
them := crypto.GetNodeID(&res.Key)
from := dhtInfo{key: res.Key, coords: res.Coords}
if *from.getNodeID() != sinfo.visited && dht_ordered(&sinfo.dest, from.getNodeID(), &sinfo.visited) {
// Closer to the destination, so update visited
sinfo.searches.router.core.log.Debugln("Updating search:", &sinfo.dest, from.getNodeID(), sinfo.send, sinfo.recv)
sinfo.visited = *from.getNodeID()
sinfo.time = time.Now()
}
them := from.getNodeID()
var destMasked crypto.NodeID
var themMasked crypto.NodeID
for idx := 0; idx < crypto.NodeIDLen; idx++ {
@ -201,8 +213,11 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
sinfo.callback(sess, nil)
}
// Cleanup
if _, isIn := sinfo.searches.searches[sinfo.dest]; isIn {
sinfo.searches.router.core.log.Debugln("Finished search:", &sinfo.dest, sinfo.send, sinfo.recv)
delete(sinfo.searches.searches, res.Dest)
}
}
// They match, so create a session and send a sessionRequest
var err error
sess, isIn := sinfo.searches.router.sessions.getByTheirPerm(&res.Key)

View File

@ -36,14 +36,13 @@ type sessionInfo struct {
myHandle crypto.Handle //
theirNonce crypto.BoxNonce //
myNonce crypto.BoxNonce //
theirMTU uint16 //
myMTU uint16 //
theirMTU MTU //
myMTU MTU //
wasMTUFixed bool // Was the MTU fixed by a receive error?
timeOpened time.Time // Time the sessino was opened
timeOpened time.Time // Time the session was opened
time time.Time // Time we last received a packet
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
coords []byte // coords of destination
reset bool // reset if coords change
tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation
@ -63,7 +62,7 @@ type sessionPing struct {
Coords []byte //
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool //
MTU uint16 //
MTU MTU //
}
// Updates session info in response to a ping, after checking that the ping is OK.
@ -117,7 +116,7 @@ type sessions struct {
lastCleanup time.Time
isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed
isAllowedMutex sync.RWMutex // Protects the above
myMaximumMTU uint16 // Maximum allowed session MTU
myMaximumMTU MTU // Maximum allowed session MTU
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot
sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle
@ -175,7 +174,7 @@ func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) {
}
// Creates a new session and lazily cleans up old existing sessions. This
// includse initializing session info to sane defaults (e.g. lowest supported
// includes initializing session info to sane defaults (e.g. lowest supported
// MTU).
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
// TODO: this check definitely needs to be moved
@ -197,7 +196,6 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.time = now
sinfo.mtuTime = now
sinfo.pingTime = now
sinfo.pingSend = now
sinfo.init = make(chan struct{})
sinfo.cancel = util.NewCancellation()
higher := false
@ -385,7 +383,7 @@ func (ss *sessions) handlePing(ping *sessionPing) {
// Get the MTU of the session.
// Will be equal to the smaller of this node's MTU or the remote node's MTU.
// If sending over links with a maximum message size (this was a thing with the old UDP code), it could be further lowered, to a minimum of 1280.
func (sinfo *sessionInfo) _getMTU() uint16 {
func (sinfo *sessionInfo) _getMTU() MTU {
if sinfo.theirMTU == 0 || sinfo.myMTU == 0 {
return 0
}
@ -415,7 +413,7 @@ func (sinfo *sessionInfo) _updateNonce(theirNonce *crypto.BoxNonce) {
}
// Resets all sessions to an uninitialized state.
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
// Called after coord changes, so attempts to use a session will trigger a new ping and notify the remote end of the coord change.
// Only call this from the router actor.
func (ss *sessions) reset() {
for _, _sinfo := range ss.sinfos {

View File

@ -107,7 +107,7 @@ func (l *switchLocator) getCoords() []byte {
return bs
}
// Returns true if the this locator represents an ancestor of the locator given as an argument.
// Returns true if this locator represents an ancestor of the locator given as an argument.
// Ancestor means that it's the parent node, or the parent of parent, and so on...
func (x *switchLocator) isAncestorOf(y *switchLocator) bool {
if x.root != y.root {
@ -808,12 +808,12 @@ func (t *switchTable) _handleIdle(port switchPort) bool {
packet := buf.packets[0]
coords := switch_getPacketCoords(packet.bytes)
priority := float64(now.Sub(packet.time)) / float64(buf.size)
if priority > bestPriority && t.portIsCloser(coords, port) {
if priority >= bestPriority && t.portIsCloser(coords, port) {
best = streamID
bestPriority = priority
}
}
if bestPriority != 0 {
if best != "" {
buf := t.queues.bufs[best]
var packet switch_packetInfo
// TODO decide if this should be LIFO or FIFO

View File

@ -1,14 +1,14 @@
package yggdrasil
// This file contains the version metadata struct
// Used in the inital connection setup and key exchange
// Used in the initial connection setup and key exchange
// Some of this could arguably go in wire.go instead
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
// This is the version-specific metadata exchanged at the start of a connection.
// It must always beign with the 4 bytes "meta" and a wire formatted uint64 major version number.
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open an connection.
// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number.
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection.
type version_metadata struct {
meta [4]byte
ver uint64 // 1 byte in this version

View File

@ -380,7 +380,7 @@ func (p *sessionPing) decode(bs []byte) bool {
if pType == wire_SessionPong {
p.IsPong = true
}
p.MTU = uint16(mtu)
p.MTU = MTU(mtu)
return true
}