5
0
mirror of https://github.com/cwinfo/yggdrasil-go.git synced 2024-12-22 21:05:40 +00:00

Merge pull request #2 from yggdrasil-network/develop

Branch Develop: Base to Fork
This commit is contained in:
Christer Warén 2018-12-17 04:40:57 +02:00 committed by GitHub
commit 566f3a9d50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1154 additions and 898 deletions

View File

@ -12,6 +12,7 @@ import (
"os"
"os/signal"
"regexp"
"strings"
"syscall"
"time"
@ -188,6 +189,35 @@ func main() {
}
}
}
// Check to see if the peers are in a parsable format, if not then default
// them to the TCP scheme
if peers, ok := dat["Peers"].([]interface{}); ok {
for index, peer := range peers {
uri := peer.(string)
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
continue
}
if strings.HasPrefix(uri, "tcp:") {
uri = uri[4:]
}
(dat["Peers"].([]interface{}))[index] = "tcp://" + uri
}
}
// Now do the same with the interface peers
if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok {
for intf, peers := range interfacepeers {
for index, peer := range peers.([]interface{}) {
uri := peer.(string)
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
continue
}
if strings.HasPrefix(uri, "tcp:") {
uri = uri[4:]
}
((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri
}
}
}
// Overlay our newly mapped configuration onto the autoconf node config that
// we generated above.
if err = mapstructure.Decode(dat, &cfg); err != nil {

View File

@ -87,6 +87,7 @@ func main() {
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen)
}
} else {
endpoint = *server
logger.Println("Using endpoint", endpoint, "from command line")
}

View File

@ -52,8 +52,11 @@ Architecture: $PKGARCH
Replaces: $PKGREPLACES
Conflicts: $PKGREPLACES
Maintainer: Neil Alexander <neilalexander@users.noreply.github.com>
Description: Debian yggdrasil package
Binary yggdrasil package for Debian and Ubuntu
Description: Yggdrasil Network
Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6
network. It is lightweight, self-arranging, supported on multiple platforms and
allows pretty much any IPv6-capable application to communicate securely with
other Yggdrasil nodes.
EOF
cat > /tmp/$PKGNAME/debian/copyright << EOF
Please see https://github.com/yggdrasil-network/yggdrasil-go/
@ -68,21 +71,40 @@ etc/systemd/system/*.service etc/systemd/system
EOF
cat > /tmp/$PKGNAME/debian/postinst << EOF
#!/bin/sh
if ! getent group yggdrasil 2>&1 > /dev/null; then
addgroup --system --quiet yggdrasil
fi
if [ -f /etc/yggdrasil.conf ];
then
mkdir -p /var/backups
echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`"
cp /etc/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d`
echo "Normalising /etc/yggdrasil.conf"
echo "Normalising and updating /etc/yggdrasil.conf"
/usr/bin/yggdrasil -useconffile /var/backups/yggdrasil.conf.`date +%Y%m%d` -normaliseconf > /etc/yggdrasil.conf
chgrp yggdrasil /etc/yggdrasil.conf
if command -v systemctl >/dev/null; then
systemctl daemon-reload >/dev/null || true
systemctl enable yggdrasil || true
systemctl start yggdrasil || true
fi
else
echo "Generating initial configuration file /etc/yggdrasil.conf"
echo "Please familiarise yourself with this file before starting Yggdrasil"
/usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf
chgrp yggdrasil /etc/yggdrasil.conf
fi
systemctl enable yggdrasil
systemctl start yggdrasil
EOF
cat > /tmp/$PKGNAME/debian/prerm << EOF
#!/bin/sh
systemctl disable yggdrasil
systemctl stop yggdrasil
if command -v systemctl >/dev/null; then
if systemctl is-active --quiet yggdrasil; then
systemctl stop yggdrasil || true
fi
systemctl disable yggdrasil || true
fi
EOF
cp yggdrasil /tmp/$PKGNAME/usr/bin/

View File

@ -4,6 +4,7 @@ Wants=network.target
After=network.target
[Service]
Group=yggdrasil
ProtectHome=true
ProtectSystem=true
SyslogIdentifier=yggdrasil
@ -12,7 +13,7 @@ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \
yggdrasil -genconf > /etc/yggdrasil.conf; \
echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \
fi"
ExecStart=/bin/sh -c "exec yggdrasil -useconf < /etc/yggdrasil.conf"
ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf
Restart=always
[Install]

View File

@ -1,188 +0,0 @@
# Yggdrasil-go
## What is it?
This is a toy implementation of an encrypted IPv6 network.
A number of years ago, I started to spend some of my free time studying and routing schemes, and eventually decided that it made sense to come up with my own.
After much time spent reflecting on the problem, and a few failed starts, I eventually cobbled together one that seemed to have, more or less, the performance characteristics I was looking for.
I resolved to eventually write a proof-of-principle / test implementation, and I thought it would make sense to include many of the nice bells and whistles that I've grown accustomed to from using [cjdns](https://github.com/cjdelisle/cjdns), plus a few additional features that I wanted to test.
Fast forward through a couple years of procrastination, and I've finally started working on it in my limited spare time.
I've found that it's now marginally more interesting than embarrassing, so here it is.
The routing scheme was designed for scalable name-independent routing on graphs with an internet-like topology.
By internet-like, I mean that the network has a densely connected core with many triangles, a diameter that increases slowly with network size, and where any sparse edges tend to be relatively tree-like, all of which appear to be common features of large graphs describing "organically" grown relationships.
By scalable name-independent routing, I mean:
1. Scalable: resource consumption should grow slowly with the size of the network.
In particular, for internet-like networks, the goal is to use only a (poly)logarithmic amount of memory, use a logarithmic amount of bandwidth per one-hop neighbor for control traffic, and to maintain low average multiplicative path stretch (introducing overhead of perhaps a few percent) that does not become worse as the network grows.
2. Name-independent: a node's identifier should be independent of network topology and state, such that a node may freely change their identifier in a static network, or keep it static under state changes in a dynamic network.
In particular, addresses are self-assigned and derived from a public key, which circumvents the use of a centralized addressing authority or public key infrastructure.
Running this code will:
1. Set up a `tun` device and assign it a Unique Local Address (ULA) in `fd00::/8`.
2. Connect to other nodes running the software.
3. Route traffic for and through other nodes.
A device's ULA is actually from `fd00::/9`, and a matching `/64` prefix is available under `fd80::/9`. This allows the node to advertise a route on its LAN, as a workaround for unsupported devices.
## Building
1. Install Go (tested on 1.9, I use [godeb](https://github.com/niemeyer/godeb)).
2. Clone this repository.
2. `./build`
It's written in Go because I felt like learning a new language, and Go seemed like an easy language to learn while still being a reasonable choice for language to prototype network code.
Note that the build script defines its own `$GOPATH`, so the build and its dependencies should be self contained.
It only works on Linux at this time, because a little code (related to the `tun` device) is platform dependent, and changing that hasn't been a high priority.
## Running
To run the program, you'll need permission to create a `tun` device and configure it using `ip`.
If you don't want to mess with capabilities for the `tun` device, then using `sudo` should work, with the usual security caveats about running a program as root.
To run with default settings:
1. `./yggdrasil --autoconf`
That will generate a new set of keys (and an IP address) each time the program is run.
The program will bind to all addresses on a random port and listen for incoming connections.
It will send announcements over IPv6 link-local multicast, and attempt to start a connection if it hears an announcement from another device.
In practice, you probably want to run this instead:
1. `./yggdrasil --genconf > conf.json`
2. `./yggdrasil --useconf < conf.json`
The first step generates a configuration file with a set of cryptographic keys and default settings.
The second step runs the program using the configuration provided in that file.
Because ULAs are derived from keys, using a fixed set of keys causes a node to keep the same address each time the program is run.
If you want to use it as an overlay network on top of e.g. the internet, then you can do so by adding the address and port of the device you want to connect to (as a string, e.g. `"1.2.3.4:5678"`) to the list of `Peers` in the configuration file.
This should accept IPv4 and IPv6 addresses, and I think it should resolve host/domain names, but I haven't really tested that, so your mileage may vary.
You can also configure which address and/or port to listen on by editing the configuration file, in case you want to bind to a specific address or listen for incoming connections on a fixed port.
Also note that the nodes is connected to the network through a `tun` device, so it follows point-to-point semantics.
This means it's limited to routing traffic with source and destination addresses in `fd00::/8`--you can't add a prefix to your routing table "via" an address in that range, as the router has no idea who you meant to send it to.
In particular, this means you can't set a working default route that *directly* uses the overlay network, but I've had success *indirectly* using it to connect to an off-the-shelf VPN that I can use as a default route for internet access.
## Optional: advertise a prefix locally
Suppose a node has been given the address: `fd00:1111:2222:3333:4444:5555:6666:7777`
Then the node may also use addresses from the prefix: `fd80:1111:2222:3333::/64` (note the `fd00` -> `fd80`, a separate `/9` is used for prefixes).
To advertise this prefix and a route to `fd00::/8`, the following seems to work for me:
1. Enable IPv6 forwarding (e.g. `sysctl -w net.ipv6.conf.all.forwarding=1` or add it to sysctl.conf).
2. `ip addr add fd80:1111:2222:3333::1/64 dev eth0` or similar, to assign an address for the router to use in that prefix, where the LAN is reachable through `eth0`.
3. Install/run `radvd` with something like the following in `/etc/radvd.conf`:
```
interface eth0
{
AdvSendAdvert on;
prefix fd80:1111:2222:3333::/64 {
AdvOnLink on;
AdvAutonomous on;
};
route fd00::/8 {};
};
```
Now any IPv6-enabled device in the LAN can use stateless address auto-configuration to assign itself a working `fd00::/8` address from the `/64` prefix, and communicate with the wider network through the router, without requiring any special configuration for each device.
I've used this to e.g. get my phone on the network.
Note that there are a some differences when accessing the network this way:
1. There are 64 fewer bits of address space available for self-certifying addresses.
This means that it is 64 bits easier to brute force a prefix collision than collision for a full node's IP address. As such, you may want to change addresses frequently, or else brute force an address with more security bits (see: `misc/genkeys.go`).
2. The LAN depends on the router for cryptography.
So while traffic going through the WAN is encrypted, the LAN is still just a LAN. You may want to secure your network.
3. Related to the above, the cryptography and I/O through the `tun` device both place additional load on the router, above what is normally present from forwarding packets between full nodes in the network, so the router may need more computing power to reach line rate.
## How does it work?
Consider the internet, which uses a network-of-networks model with address aggregation.
Addresses are allocated by a central authority, as blocks of contiguous addresses with a matching prefix.
Within a network, each node may represent one or more prefixes, with each prefix representing a network of one or more nodes.
On the largest scale, BGP is used to route traffic between networks (autonomous systems), and other protocols can be used to route within a network.
The effectiveness of such hierarchical addressing and routing strategies depend on network topology, with the internet's observed topology being the worst case of all known topologies from a scalability standpoint (see [arxiv:0708.2309](https://arxiv.org/abs/0708.2309) for a better explanation of the issue, but the problem is essentially that address aggregation is ineffective in a network with a large number of nodes and a small diameter).
The routing scheme implemented by this code tries a different approach.
Instead of using assigned addresses and a routing table based on prefixes and address aggregation, routing and addressing are handled through a combination of:
1. Self-assigned cryptographically generated addresses, to handle address allocation without a central authority.
2. A kademlia-like distributed hash table, to look up a node's (name-dependent) routing information from their (name-independent routing) IP address.
3. A name-dependent routing scheme based on greedy routing in a metric space, constructed from an arbitrarily rooted spanning tree, which gives a reasonable approximation of the true distance between nodes for certain network topologies (namely the scale-free topology that seems to emerge in many large graphs, including the internet). The spanning tree embedding takes stability into account when selecting which one-hop neighbor to use as a parent, and path selection uses (poorly) estimated available bandwidth as a criteria, subject to the constraint that metric space distances must decrease with each hop. Incidentally, the name `yggdrasil` was selected for this test code because that's obviously what you call an immense tree that connects worlds.
The network then presents itself as having a single "flat" address with no aggregation.
Under the hood, it runs as an overlay on top of existing IP networks.
Link-local IPv6 multicast traffic is used to advertise on the underlying networks, which can as easily be a wired or wireless LAN, a direct (e.g. ethernet) connection between two devices, a wireless ad-hoc network, etc.
Additional connections can be added manually to peer over networks where link-local multicast is insufficient, which allows you to e.g. use the internet to bridge local networks.
The name-dependent routing layer uses cryptographically signed (`Ed25519`) path-vector-like routing messages, similar to S-BGP, which should prevent route poisoning and related attacks.
For encryption, it uses the Go implementation of the `nacl/box` scheme, which is built from a Curve25519 key exchange with XSalsa20 as a stream cypher and Poly1305 for integrity and authentication.
Permanent keys are used for protocol traffic, including the ephemeral key exchange, and a hash of a node's permanent public key is used to construct a node's address.
Ephemeral keys are used for encapsulated IP(v6) traffic, which provides forward secrecy.
Go's `crypto/rand` library is used for nonce generation.
In short, I've tried to not make this a complete security disaster, but the code hasn't been independently audited and I'm nothing close to a security expert, so it should be considered a proof-of-principle rather than a safe implementation.
At a minimum, I know of no way to prevent gray hole attacks.
I realize that this is a terribly short description of how it works, so I may elaborate further in another document if the need arises.
Otherwise, I guess you could try to read my terrible and poorly documented code if you want to know more.
## Related work
A lot of inspiration comes from [cjdns](https://github.com/cjdelisle/cjdns).
I'm a contributor to that project, and I wanted to test out some ideas that weren't convenient to prototype in the existing code base, which is why I wrote this toy.
On the routing side, a lot of influence came from compact routing.
A number of compact routing schemes are evaluated in [arxiv:0708.2309](https://arxiv.org/abs/0708.2309) and may be used as a basis for comparison.
When tested in a simplified simulation environment on CAIDA's 9204-node "skitter" network graph used in that paper, I observed an average multiplicative stretch of about 1.08 with my routing scheme, as implemented here.
This can be lowered to less than 1.02 using a source-routed version of the algorithm and including node degree as an additional parameter of the embedding, which is of academic interest, but degree's unverifiability makes it impractical for this implementation.
In either case, this only requires 1 routing table entry per one-hop neighbor (this averages ~6 for in the skitter network graph), plus a logarithmic number of DHT entries (expected to be ~26, based on extrapolations from networks with a few hundred nodes--running the full implementation on the skitter graph is impractical on my machine).
I don't think stretch is really an appropriate metric, as it doesn't consider the difference to total network cost from a high-stretch short path vs a high-stretch long path.
In this scheme, and I believe in most compact routing schemes, longer paths tend to have lower multiplicative stretch, and shorter paths are more likely to have longer stretch.
I would argue that this is preferable to the alternative.
While I use a slightly different approach, the idea to try a greedy routing scheme was inspired by the use of greedy routing on networks embedded in the hyperbolic plane (such as [Kleinberg's work](https://doi.org/10.1109%2FINFCOM.2007.221) and [Greedy Forwarding on the NDN Testbed](https://www.caida.org/research/routing/greedy_forwarding_ndn/)).
I use distance on a spanning tree as the metric, as seems to work well on the types of networks I'm concerned with, and it simplifies other aspects of the implementation.
The hyperbolic embedding algorithms I'm aware of, or specifically the distributed ones, operate by constructing a spanning tree of the network and then embedding the tree.
So I don't see much harm, at present, of skipping the hyperbolic plane and directly using the tree for the metric space.
## Misc. notes
This is a toy experiment / proof-of-concept.
It's only meant to test if / how well some ideas work.
I have no idea what I'm doing, so for all I know it's entirely possible that it could crash your computer, eat your homework, or set fire to your house.
Some parts are also written to be as bad as I could make them while still being technically correct, in an effort to make bugs obvious if they occur, which means that even when it does work it may be fragile and error prone.
In particular, you should expect it to perform poorly under mobility events, and to converge slowly in dynamic networks. All else being equal, this implementation should tend to prefer long-lived links over short-lived ones when embedding, and (poorly estimated) high bandwidth links over low bandwidth ones when forwarding traffic. As such, in multi-homed or mobile scenarios, there may be some tendency for it to make decisions you disagree with.
While stretch is low on internet-like graphs, the best upper bound I've established on the *additive* stretch of this scheme, after convergence, is the same as for tree routing: proportional to network diameter. For sparse graphs with a large diameter, the scheme may not find particularly efficient paths, even under ideal circumstances. I would argue that such networks tend not to grow large enough for scalability to be an issue, so another routing scheme is better suited to those networks.
Regarding the announce-able prefix thing, what I wanted to do is use `fc00::/7`, where `fc00::/8` is for nodes and `fd00::/8` is for prefixes.
I would also possibly widen the prefixes to `/48`, to match [rfc4193](https://tools.ietf.org/html/rfc4193), and possibly provide an option to keep using a `/64` by splitting it into two `/9` blocks (where `/64` prefixes would continue to live in `fd80::/9`), or else convince myself that the security implications of another 16 bits don't matter (to avoid the complexity of splitting it into two `/9` ranges for prefixes).
Using `fc00::/8` this way would cause issues if trying to also run cjdns.
Since I like cjdns, and want the option of running it on the same nodes, I've decided not to do that.
If I ever give up on avoiding cjdns conflicts, then I may change the addressing scheme to match the above.
Despite the tree being constructed from path-vector-like routing messages, there's no support for routing policy right now.
As a result, peer relationships are bimodal: either you're not connected to someone, or you're connected and you'll route traffic *to* and *through* them.
Nodes also accept all incoming connections, so if you want to limit who can connect then you'll need to provide some other kind of access controls.
The current implementation does all of its setup when the program starts, and then nothing can be reconfigured without restarting the program.
At some point I may add a remote API, so a running node can be reconfigured (to e.g. add/remove peers) without restarting, or probe the internal state of the router to get useful debugging info.
So far, things seem to work the way I want/expect without much trouble, so I haven't felt the need to do this yet.
Some parts of the implementation can take advantage of multiple cores, but other parts that could simply do not.
Some parts are fast, but other parts are slower than they have any right to be, e.g. I can't figure out why some syscalls are as expensive as they are, so the `tun` in particular tends to be a CPU bottleneck (multi-queue could help in some cases, but that just spreads the cost around, and it doesn't help with single streams of traffic).
The Go runtime's GC tends to have short pauses, but it does have pauses.
So even if the ideas that went into this routing scheme turn out to be useful, this implementation is likely to remain mediocre at best for the foreseeable future.
If the is thing works well and the protocol stabilizes, then it's worth considering re-implementation and/or a formal spec and RFC.
In such a case, it's entirely reasonable to change parts of the spec purely to make the efficient implementation easier (e.g. it makes sense to want zero-copy networking, but a couple parts of the current protocol might make that impractical).

View File

@ -87,14 +87,17 @@ These signatures prevent nodes from forging arbitrary routing advertisements.
The first hop, from the root, also includes a sequence number, which must be updated periodically.
A node will blacklist the current root (keeping a record of the last sequence number observed) if the root fails to update for longer than some timeout (currently hard coded at 1 minute).
Normally, a root node will update their sequence number for frequently than this (once every 30 seconds).
Nodes are throttled to ignore updates with a new sequence number for some period after updating their most recently seen sequence number (currently this cooldown is 10 seconds).
Nodes are throttled to ignore updates with a new sequence number for some period after updating their most recently seen sequence number (currently this cooldown is 15 seconds).
The implementation chooses to set the sequence number equal to the unix time on the root's clock, so that a new (higher) sequence number will be selected if the root is restarted and the clock is not set back.
Other than the root node, every other node in the network must select one of its neighbors to use as their parent.
This selection is done by maximizing: `<uptime + timeout> / <distance to the root>`.
Here, `uptime` is the time between when we first and last received a message from the node which advertised the node's current location in the tree (resetting to zero if the location changes), and timeout is the time we wait before dropping a root due to inactivity.
This essentially means the numerator is at least as long as the amount of time between when the neighbor was first seen at its present location, and when the advertisement from the neighbor becomes invalid due to root timeout.
Resetting the uptime with each coordinate change causes nodes to favor long-lived stable paths over short-lived unstable ones, for the purposes of tree construction (indirectly impacting route selection).
This selection is done by tracking when each neighbor first sends us a message with a new timestamp from the root, to determine the ordering of the latency of each path from the root, to each neighbor, and then to the node that's searching for a parent.
These relative latencies are tracked by, for each neighbor, keeping a score vs each other neighbor.
If a neighbor sends a message with an updated timestamp before another neighbor, then the faster neighbor's score is increased by 1.
If the neighbor sends a message slower, then the score is decreased by 2, to make sure that a node must be reliably faster (at least 2/3 of the time) to see a net score increase over time.
If a node begins to advertise new coordinates, then its score vs all other nodes is reset to 0.
A node switches to a new parent if a neighbor's score (vs the current parent) reaches some threshold, currently 240, which corresponds to about 2 hours of being a reliably faster path.
The intended outcome of this process is that stable connections from fixed infrastructure near the "core" of the network should (eventually) select parents that minimize latency from the root to themselves, while the more dynamic parts of the network, presumably more towards the edges, will try to favor reliability when selecting a parent.
The distance metric between nodes is simply the distance between the nodes if they routed on the spanning tree.
This is equal to the sum of the distance from each node to the last common ancestor of the two nodes being compared.
@ -103,15 +106,14 @@ In practice, only the coords are used for routing, while the root and timestamp,
## Name-independent routing
A [Kademlia](https://en.wikipedia.org/wiki/Kademlia)-like Distributed Hash Table (DHT) is used as a distributed database that maps NodeIDs onto coordinates in the spanning tree metric space.
The DHT is Kademlia-like in that it uses the `xor` metric and structures the hash table into k-buckets (with 2 nodes per bucket in the normal case, plus some additional slots for keyspace neighbors and one-hop neighbors at the router level).
It differs from kademlia in that there are no values in the key:value store -- it only stores information about DHT peers.
A [Chord](https://en.wikipedia.org/wiki/Chord_(peer-to-peer))-like Distributed Hash Table (DHT) is used as a distributed database that maps NodeIDs onto coordinates in the spanning tree metric space.
The DHT is Chord-like in that it uses a successor/predecessor structure to do lookups in `O(n)` time with `O(1)` entries, then augments this with some additional information, adding roughly `O(logn)` additional entries, to reduce the lookup time to something around `O(logn)`.
In the long term, the idea is to favor spending our bandwidth making sure the minimum `O(1)` part is right, to prioritize correctness, and then try to conserve bandwidth (and power) by being a bit lazy about checking the remaining `O(logn)` portion when it's not in use.
The main complication is that, when the DHT is bootstrapped off of a node's one-hop neighbors, with no special measures taken about which nodes are included in each bucket, then the network may diverge (settle into a stable bad state, where at least some lookups will always fail).
The current strategy is to place additional preferences on which nodes are kept in each bucket -- in particular, we try to keep the closest nodes in xor space in each bucket.
This seems to mitigate the issue in some quick tests, but it's a topic that could use additional study.
Other than these differences, the DHT is more-or-less what you might expect from a kad implementation.
To be specific, the DHT stores the immediate successor of a node, plus the next node it manages to find which is strictly closer (by the tree hop-count metric) than all previous nodes.
The same process is repeated for predecessor nodes, and lookups walk the network in the predecessor direction, with each key being owned by its successor (to make sure defaulting to 0 for unknown bits of a `NodeID` doesn't cause us to overshoot the target during a lookup).
In addition, all of a node's one-hop neighbors are included in the DHT, since we get this information "for free", and we must include it in our DHT to ensure that the network doesn't diverge to a broken state (though I suspect that only adding parents or parent-child relationships may be sufficient -- worth trying to prove or disprove, if somebody's bored).
The DHT differs from Chord in that there are no values in the key:value store -- it only stores information about DHT peers -- and that it uses a [Kademlia](https://en.wikipedia.org/wiki/Kademlia)-inspired iterative-parallel lookup process.
To summarize the entire routing procedure, when given only a node's IP address, the goal is to find a route to the destination.
That happens through 3 steps:

View File

@ -12,11 +12,16 @@ This only matters if it's high enough to make you the root of the tree.
*/
package main
import "encoding/hex"
import "flag"
import "fmt"
import "runtime"
import . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
import (
"encoding/hex"
"flag"
"fmt"
"net"
"runtime"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
var doSig = flag.Bool("sig", false, "generate new signing keys instead")
@ -82,12 +87,7 @@ func isBetter(oldID, newID []byte) bool {
}
func doBoxKeys(out chan<- keySet, in <-chan []byte) {
c := Core{}
pub, _ := c.DEBUG_newBoxKeys()
bestID := c.DEBUG_getNodeID(pub)
for idx := range bestID {
bestID[idx] = 0
}
var bestID crypto.NodeID
for {
select {
case newBestID := <-in:
@ -95,22 +95,20 @@ func doBoxKeys(out chan<- keySet, in <-chan []byte) {
copy(bestID[:], newBestID)
}
default:
pub, priv := c.DEBUG_newBoxKeys()
id := c.DEBUG_getNodeID(pub)
pub, priv := crypto.NewBoxKeys()
id := crypto.GetNodeID(pub)
if !isBetter(bestID[:], id[:]) {
continue
}
bestID = id
ip := c.DEBUG_addrForNodeID(id)
bestID = *id
ip := net.IP(address.AddrForNodeID(id)[:]).String()
out <- keySet{priv[:], pub[:], id[:], ip}
}
}
}
func doSigKeys(out chan<- keySet, in <-chan []byte) {
c := Core{}
pub, _ := c.DEBUG_newSigKeys()
bestID := c.DEBUG_getTreeID(pub)
var bestID crypto.TreeID
for idx := range bestID {
bestID[idx] = 0
}
@ -122,12 +120,12 @@ func doSigKeys(out chan<- keySet, in <-chan []byte) {
}
default:
}
pub, priv := c.DEBUG_newSigKeys()
id := c.DEBUG_getTreeID(pub)
pub, priv := crypto.NewSigKeys()
id := crypto.GetTreeID(pub)
if !isBetter(bestID[:], id[:]) {
continue
}
bestID = id
bestID = *id
out <- keySet{priv[:], pub[:], id[:], ""}
}
}

View File

@ -1,21 +1,26 @@
package yggdrasil
package address
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
// address represents an IPv6 address in the yggdrasil address range.
type address [16]byte
type Address [16]byte
// subnet represents an IPv6 /64 subnet in the yggdrasil subnet range.
type subnet [8]byte
type Subnet [8]byte
// address_prefix is the prefix used for all addresses and subnets in the network.
// The current implementation requires this to be a muliple 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 eachother, though routing and the DHT machinery *should* still work.
var address_prefix = [...]byte{0x02}
func GetPrefix() [1]byte {
return [...]byte{0x02}
}
// isValid returns true if an address falls within the range used by nodes in the network.
func (a *address) isValid() bool {
for idx := range address_prefix {
if (*a)[idx] != address_prefix[idx] {
func (a *Address) IsValid() bool {
prefix := GetPrefix()
for idx := range prefix {
if (*a)[idx] != prefix[idx] {
return false
}
}
@ -23,28 +28,29 @@ func (a *address) isValid() bool {
}
// isValid returns true if a prefix falls within the range usable by the network.
func (s *subnet) isValid() bool {
l := len(address_prefix)
for idx := range address_prefix[:l-1] {
if (*s)[idx] != address_prefix[idx] {
func (s *Subnet) IsValid() bool {
prefix := GetPrefix()
l := len(prefix)
for idx := range prefix[:l-1] {
if (*s)[idx] != prefix[idx] {
return false
}
}
return (*s)[l-1] == address_prefix[l-1]|0x01
return (*s)[l-1] == prefix[l-1]|0x01
}
// address_addrForNodeID takes a *NodeID as an argument and returns an *address.
// This subnet begins with the address prefix, with the last bit set to 0 to indicate an address.
// The following 8 bits are set to the number of leading 1 bits in the NodeID.
// The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address.
func address_addrForNodeID(nid *NodeID) *address {
func AddrForNodeID(nid *crypto.NodeID) *Address {
// 128 bit address
// Begins with prefix
// Next bit is a 0
// Next 7 bits, interpreted as a uint, are # of leading 1s in the NodeID
// Leading 1s and first leading 0 of the NodeID are truncated off
// The rest is appended to the IPv6 address (truncated to 128 bits total)
var addr address
var addr Address
var temp []byte
done := false
ones := byte(0)
@ -67,9 +73,10 @@ func address_addrForNodeID(nid *NodeID) *address {
temp = append(temp, bits)
}
}
copy(addr[:], address_prefix[:])
addr[len(address_prefix)] = ones
copy(addr[len(address_prefix)+1:], temp)
prefix := GetPrefix()
copy(addr[:], prefix[:])
addr[len(prefix)] = ones
copy(addr[len(prefix)+1:], temp)
return &addr
}
@ -77,14 +84,15 @@ func address_addrForNodeID(nid *NodeID) *address {
// This subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix.
// The following 8 bits are set to the number of leading 1 bits in the NodeID.
// The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet.
func address_subnetForNodeID(nid *NodeID) *subnet {
func SubnetForNodeID(nid *crypto.NodeID) *Subnet {
// Exactly as the address version, with two exceptions:
// 1) The first bit after the fixed prefix is a 1 instead of a 0
// 2) It's truncated to a subnet prefix length instead of 128 bits
addr := *address_addrForNodeID(nid)
var snet subnet
addr := *AddrForNodeID(nid)
var snet Subnet
copy(snet[:], addr[:])
snet[len(address_prefix)-1] |= 0x01
prefix := GetPrefix()
snet[len(prefix)-1] |= 0x01
return &snet
}
@ -92,17 +100,18 @@ func address_subnetForNodeID(nid *NodeID) *subnet {
// The first is a NodeID with all the bits known from the address set to their correct values.
// The second is a bitmask with 1 bit set for each bit that was known from the address.
// This is used to look up NodeIDs in the DHT and tell if they match an address.
func (a *address) getNodeIDandMask() (*NodeID, *NodeID) {
func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) {
// Mask is a bitmask to mark the bits visible from the address
// This means truncated leading 1s, first leading 0, and visible part of addr
var nid NodeID
var mask NodeID
ones := int(a[len(address_prefix)])
var nid crypto.NodeID
var mask crypto.NodeID
prefix := GetPrefix()
ones := int(a[len(prefix)])
for idx := 0; idx < ones; idx++ {
nid[idx/8] |= 0x80 >> byte(idx%8)
}
nidOffset := ones + 1
addrOffset := 8*len(address_prefix) + 8
addrOffset := 8*len(prefix) + 8
for idx := addrOffset; idx < 8*len(a); idx++ {
bits := a[idx/8] & (0x80 >> byte(idx%8))
bits <<= byte(idx % 8)
@ -110,7 +119,7 @@ func (a *address) getNodeIDandMask() (*NodeID, *NodeID) {
bits >>= byte(nidIdx % 8)
nid[nidIdx/8] |= bits
}
maxMask := 8*(len(a)-len(address_prefix)-1) + ones + 1
maxMask := 8*(len(a)-len(prefix)-1) + ones + 1
for idx := 0; idx < maxMask; idx++ {
mask[idx/8] |= 0x80 >> byte(idx%8)
}
@ -121,16 +130,17 @@ func (a *address) getNodeIDandMask() (*NodeID, *NodeID) {
// The first is a NodeID with all the bits known from the address set to their correct values.
// The second is a bitmask with 1 bit set for each bit that was known from the subnet.
// This is used to look up NodeIDs in the DHT and tell if they match a subnet.
func (s *subnet) getNodeIDandMask() (*NodeID, *NodeID) {
func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) {
// As with the address version, but visible parts of the subnet prefix instead
var nid NodeID
var mask NodeID
ones := int(s[len(address_prefix)])
var nid crypto.NodeID
var mask crypto.NodeID
prefix := GetPrefix()
ones := int(s[len(prefix)])
for idx := 0; idx < ones; idx++ {
nid[idx/8] |= 0x80 >> byte(idx%8)
}
nidOffset := ones + 1
addrOffset := 8*len(address_prefix) + 8
addrOffset := 8*len(prefix) + 8
for idx := addrOffset; idx < 8*len(s); idx++ {
bits := s[idx/8] & (0x80 >> byte(idx%8))
bits <<= byte(idx % 8)
@ -138,7 +148,7 @@ func (s *subnet) getNodeIDandMask() (*NodeID, *NodeID) {
bits >>= byte(nidIdx % 8)
nid[nidIdx/8] |= bits
}
maxMask := 8*(len(s)-len(address_prefix)-1) + ones + 1
maxMask := 8*(len(s)-len(prefix)-1) + ones + 1
for idx := 0; idx < maxMask; idx++ {
mask[idx/8] |= 0x80 >> byte(idx%8)
}

View File

@ -2,23 +2,24 @@ package config
// NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct {
Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."`
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."`
InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."`
ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."`
AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."`
EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."`
EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"`
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!"`
MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."`
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."`
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."`
Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."`
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."`
InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."`
ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."`
AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."`
EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."`
EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"`
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!"`
MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."`
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."`
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."`
NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
//Net NetConfig `comment:"Extended options for connecting to peers over other networks."`
}

187
src/crypto/crypto.go Normal file
View File

@ -0,0 +1,187 @@
package crypto
/*
This part of the package wraps crypto operations needed elsewhere
In particular, it exposes key generation for ed25519 and nacl box
It also defines NodeID and TreeID as hashes of keys, and wraps hash functions
*/
import (
"crypto/rand"
"crypto/sha512"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
////////////////////////////////////////////////////////////////////////////////
// NodeID and TreeID
const NodeIDLen = sha512.Size
const TreeIDLen = sha512.Size
const handleLen = 8
type NodeID [NodeIDLen]byte
type TreeID [TreeIDLen]byte
type Handle [handleLen]byte
func GetNodeID(pub *BoxPubKey) *NodeID {
h := sha512.Sum512(pub[:])
return (*NodeID)(&h)
}
func GetTreeID(pub *SigPubKey) *TreeID {
h := sha512.Sum512(pub[:])
return (*TreeID)(&h)
}
func NewHandle() *Handle {
var h Handle
_, err := rand.Read(h[:])
if err != nil {
panic(err)
}
return &h
}
////////////////////////////////////////////////////////////////////////////////
// Signatures
const SigPubKeyLen = ed25519.PublicKeySize
const SigPrivKeyLen = ed25519.PrivateKeySize
const SigLen = ed25519.SignatureSize
type SigPubKey [SigPubKeyLen]byte
type SigPrivKey [SigPrivKeyLen]byte
type SigBytes [SigLen]byte
func NewSigKeys() (*SigPubKey, *SigPrivKey) {
var pub SigPubKey
var priv SigPrivKey
pubSlice, privSlice, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
copy(pub[:], pubSlice)
copy(priv[:], privSlice)
return &pub, &priv
}
func Sign(priv *SigPrivKey, msg []byte) *SigBytes {
var sig SigBytes
sigSlice := ed25519.Sign(priv[:], msg)
copy(sig[:], sigSlice)
return &sig
}
func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool {
// Should sig be an array instead of a slice?...
// It's fixed size, but
return ed25519.Verify(pub[:], msg, sig[:])
}
////////////////////////////////////////////////////////////////////////////////
// NaCl-like crypto "box" (curve25519+xsalsa20+poly1305)
const BoxPubKeyLen = 32
const BoxPrivKeyLen = 32
const BoxSharedKeyLen = 32
const BoxNonceLen = 24
const BoxOverhead = box.Overhead
type BoxPubKey [BoxPubKeyLen]byte
type BoxPrivKey [BoxPrivKeyLen]byte
type BoxSharedKey [BoxSharedKeyLen]byte
type BoxNonce [BoxNonceLen]byte
func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) {
pubBytes, privBytes, err := box.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
pub := (*BoxPubKey)(pubBytes)
priv := (*BoxPrivKey)(privBytes)
return pub, priv
}
func GetSharedKey(myPrivKey *BoxPrivKey,
othersPubKey *BoxPubKey) *BoxSharedKey {
var shared [BoxSharedKeyLen]byte
priv := (*[BoxPrivKeyLen]byte)(myPrivKey)
pub := (*[BoxPubKeyLen]byte)(othersPubKey)
box.Precompute(&shared, pub, priv)
return (*BoxSharedKey)(&shared)
}
func BoxOpen(shared *BoxSharedKey,
boxed []byte,
nonce *BoxNonce) ([]byte, bool) {
out := util.GetBytes()
s := (*[BoxSharedKeyLen]byte)(shared)
n := (*[BoxNonceLen]byte)(nonce)
unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s)
return unboxed, success
}
func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *BoxNonce) {
if nonce == nil {
nonce = NewBoxNonce()
}
nonce.Increment()
out := util.GetBytes()
s := (*[BoxSharedKeyLen]byte)(shared)
n := (*[BoxNonceLen]byte)(nonce)
boxed := box.SealAfterPrecomputation(out, unboxed, n, s)
return boxed, nonce
}
func NewBoxNonce() *BoxNonce {
var nonce BoxNonce
_, err := rand.Read(nonce[:])
for ; err == nil && nonce[0] == 0xff; _, err = rand.Read(nonce[:]) {
// Make sure nonce isn't too high
// This is just to make rollover unlikely to happen
// Rollover is fine, but it may kill the session and force it to reopen
}
if err != nil {
panic(err)
}
return &nonce
}
func (n *BoxNonce) Increment() {
oldNonce := *n
n[len(n)-1] += 2
for i := len(n) - 2; i >= 0; i-- {
if n[i+1] < oldNonce[i+1] {
n[i] += 1
}
}
}
// Used to subtract one nonce from another, staying in the range +- 64.
// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask.
// It's basically part of the machinery that prevents replays and duplicate packets.
func (n *BoxNonce) Minus(m *BoxNonce) int64 {
diff := int64(0)
for idx := range n {
diff *= 256
diff += int64(n[idx]) - int64(m[idx])
if diff > 64 {
diff = 64
}
if diff < -64 {
diff = -64
}
}
return diff
}

View File

@ -1,21 +1,21 @@
package yggdrasil
package util
// These are misc. utility functions that didn't really fit anywhere else
import "runtime"
// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere.
func util_yield() {
func Yield() {
runtime.Gosched()
}
// A wrapper around runtime.LockOSThread() so it doesn't need to be imported elsewhere.
func util_lockthread() {
func LockThread() {
runtime.LockOSThread()
}
// A wrapper around runtime.UnlockOSThread() so it doesn't need to be imported elsewhere.
func util_unlockthread() {
func UnlockThread() {
runtime.UnlockOSThread()
}
@ -23,15 +23,12 @@ func util_unlockthread() {
// It's used like a sync.Pool, but with a fixed size and typechecked without type casts to/from interface{} (which were making the profiles look ugly).
var byteStore chan []byte
// Initializes the byteStore
func util_initByteStore() {
if byteStore == nil {
byteStore = make(chan []byte, 32)
}
func init() {
byteStore = make(chan []byte, 32)
}
// Gets an empty slice from the byte store, if one is available, or else returns a new nil slice.
func util_getBytes() []byte {
func GetBytes() []byte {
select {
case bs := <-byteStore:
return bs[:0]
@ -41,7 +38,7 @@ func util_getBytes() []byte {
}
// Puts a slice in the store, if there's room, or else returns and lets the slice get collected.
func util_putBytes(bs []byte) {
func PutBytes(bs []byte) {
select {
case byteStore <- bs:
default:

25
src/yggdrasil/adapter.go Normal file
View File

@ -0,0 +1,25 @@
package yggdrasil
// Defines the minimum required functions for an adapter type.
type AdapterInterface interface {
init(core *Core, send chan<- []byte, recv <-chan []byte)
read() error
write() error
close() error
}
// Defines the minimum required struct members for an adapter type (this is
// now the base type for tunAdapter in tun.go)
type Adapter struct {
AdapterInterface
core *Core
send chan<- []byte
recv <-chan []byte
}
// Initialises the adapter.
func (adapter *Adapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
adapter.core = core
adapter.send = send
adapter.recv = recv
}

View File

@ -14,6 +14,8 @@ import (
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
@ -160,9 +162,9 @@ func (a *admin) init(c *Core, listenaddr string) {
}()
return admin_info{
a.core.tun.iface.Name(): admin_info{
"tap_mode": a.core.tun.iface.IsTAP(),
"mtu": a.core.tun.mtu,
a.core.router.tun.iface.Name(): admin_info{
"tap_mode": a.core.router.tun.iface.IsTAP(),
"mtu": a.core.router.tun.mtu,
},
}, nil
})
@ -185,8 +187,8 @@ func (a *admin) init(c *Core, listenaddr string) {
return admin_info{}, errors.New("Failed to configure adapter")
} else {
return admin_info{
a.core.tun.iface.Name(): admin_info{
"tap_mode": a.core.tun.iface.IsTAP(),
a.core.router.tun.iface.Name(): admin_info{
"tap_mode": a.core.router.tun.iface.IsTAP(),
"mtu": ifmtu,
},
}, nil
@ -314,7 +316,7 @@ func (a *admin) init(c *Core, listenaddr string) {
"box_pub_key": hex.EncodeToString(dinfo.key[:]),
"coords": fmt.Sprintf("%v", dinfo.coords),
}
addr := net.IP(address_addrForNodeID(getNodeID(&dinfo.key))[:]).String()
addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String()
infos[addr] = info
}
return admin_info{"nodes": infos}, nil
@ -322,6 +324,23 @@ func (a *admin) init(c *Core, listenaddr string) {
return admin_info{}, err
}
})
a.addHandler("getNodeInfo", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) {
var nocache bool
if in["nocache"] != nil {
nocache = in["nocache"].(string) == "true"
}
result, err := a.admin_getNodeInfo(in["box_pub_key"].(string), in["coords"].(string), nocache)
if err == nil {
var m map[string]interface{}
if err = json.Unmarshal(result, &m); err == nil {
return admin_info{"nodeinfo": m}, nil
} else {
return admin_info{}, err
}
} else {
return admin_info{}, err
}
})
}
// start runs the admin API socket to listen for / respond to admin API calls.
@ -515,13 +534,7 @@ func (a *admin) addPeer(addr string, sintf string) error {
return errors.New("invalid peer: " + addr)
}
} else {
// no url scheme provided
addr = strings.ToLower(addr)
if strings.HasPrefix(addr, "tcp:") {
addr = addr[4:]
}
a.core.tcp.connect(addr, "")
return nil
return errors.New("invalid peer: " + addr)
}
return nil
}
@ -539,12 +552,12 @@ func (a *admin) removePeer(p string) error {
// startTunWithMTU creates the tun/tap device, sets its address, and sets the MTU to the provided value.
func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error {
// Close the TUN first if open
_ = a.core.tun.close()
_ = a.core.router.tun.close()
// Then reconfigure and start it
addr := a.core.router.addr
straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix)-1)
straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix())-1)
if ifname != "none" {
err := a.core.tun.setup(ifname, iftapmode, straddr, ifmtu)
err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu)
if err != nil {
return err
}
@ -559,9 +572,9 @@ func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error
a.core.sessions.sendPingPong(sinfo, false)
}
// Aaaaand... go!
go a.core.tun.read()
go a.core.router.tun.read()
}
go a.core.tun.write()
go a.core.router.tun.write()
return nil
}
@ -596,7 +609,7 @@ func (a *admin) getData_getPeers() []admin_nodeInfo {
sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
for _, port := range ps {
p := ports[port]
addr := *address_addrForNodeID(getNodeID(&p.box))
addr := *address.AddrForNodeID(crypto.GetNodeID(&p.box))
info := admin_nodeInfo{
{"ip", net.IP(addr[:]).String()},
{"port", port},
@ -621,7 +634,7 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo {
if !isIn {
continue
}
addr := *address_addrForNodeID(getNodeID(&peer.box))
addr := *address.AddrForNodeID(crypto.GetNodeID(&peer.box))
coords := elem.locator.getCoords()
info := admin_nodeInfo{
{"ip", net.IP(addr[:]).String()},
@ -679,7 +692,7 @@ func (a *admin) getData_getDHT() []admin_nodeInfo {
return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID())
})
for _, v := range dhtInfos {
addr := *address_addrForNodeID(v.getNodeID())
addr := *address.AddrForNodeID(v.getNodeID())
info := admin_nodeInfo{
{"ip", net.IP(addr[:]).String()},
{"coords", fmt.Sprint(v.coords)},
@ -729,7 +742,7 @@ func (a *admin) getAllowedEncryptionPublicKeys() []string {
func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) {
boxBytes, err := hex.DecodeString(bstr)
if err == nil {
var box boxPubKey
var box crypto.BoxPubKey
copy(box[:], boxBytes)
a.core.peers.addAllowedEncryptionPublicKey(&box)
}
@ -741,7 +754,7 @@ func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) {
func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) {
boxBytes, err := hex.DecodeString(bstr)
if err == nil {
var box boxPubKey
var box crypto.BoxPubKey
copy(box[:], boxBytes)
a.core.peers.removeAllowedEncryptionPublicKey(&box)
}
@ -750,7 +763,7 @@ func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) {
// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID.
func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) {
var key boxPubKey
var key crypto.BoxPubKey
if keyBytes, err := hex.DecodeString(keyString); err != nil {
return dhtRes{}, err
} else {
@ -781,7 +794,7 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR
} else if len(targetBytes) != len(target) {
return dhtRes{}, errors.New("Incorrect target NodeID length")
} else {
target = NodeID{}
var target crypto.NodeID
copy(target[:], targetBytes)
}
rq := dhtReqKey{info.key, target}
@ -806,6 +819,52 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR
return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString))
}
func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) {
var key crypto.BoxPubKey
if keyBytes, err := hex.DecodeString(keyString); err != nil {
return nodeinfoPayload{}, err
} else {
copy(key[:], keyBytes)
}
if !nocache {
if response, err := a.core.nodeinfo.getCachedNodeInfo(key); err == nil {
return response, nil
}
}
var coords []byte
for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
if cstr == "" {
// Special case, happens if trimmed is the empty string, e.g. this is the root
continue
}
if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
return nodeinfoPayload{}, err
} else {
coords = append(coords, uint8(u64))
}
}
response := make(chan *nodeinfoPayload, 1)
sendNodeInfoRequest := func() {
a.core.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) {
defer func() { recover() }()
select {
case response <- nodeinfo:
default:
}
})
a.core.nodeinfo.sendNodeInfo(key, coords, false)
}
a.core.router.doAdmin(sendNodeInfoRequest)
go func() {
time.Sleep(6 * time.Second)
close(response)
}()
for res := range response {
return *res, nil
}
return nodeinfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString))
}
// getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network.
// This is color-coded and labeled, and includes the self node, switch peers, nodes known to the DHT, and nodes with open sessions.
// The graph is structured as a tree with directed links leading away from the root.

View File

@ -7,6 +7,9 @@ import (
"fmt"
"net"
"sort"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// This module implements crypto-key routing, similar to Wireguard, where we
@ -17,15 +20,15 @@ type cryptokey struct {
enabled bool
ipv4routes []cryptokey_route
ipv6routes []cryptokey_route
ipv4cache map[address]cryptokey_route
ipv6cache map[address]cryptokey_route
ipv4cache map[address.Address]cryptokey_route
ipv6cache map[address.Address]cryptokey_route
ipv4sources []net.IPNet
ipv6sources []net.IPNet
}
type cryptokey_route struct {
subnet net.IPNet
destination boxPubKey
destination crypto.BoxPubKey
}
// Initialise crypto-key routing. This must be done before any other CKR calls.
@ -33,8 +36,8 @@ func (c *cryptokey) init(core *Core) {
c.core = core
c.ipv4routes = make([]cryptokey_route, 0)
c.ipv6routes = make([]cryptokey_route, 0)
c.ipv4cache = make(map[address]cryptokey_route, 0)
c.ipv6cache = make(map[address]cryptokey_route, 0)
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
c.ipv4sources = make([]net.IPNet, 0)
c.ipv6sources = make([]net.IPNet, 0)
}
@ -52,7 +55,7 @@ func (c *cryptokey) isEnabled() bool {
// Check whether the given address (with the address length specified in bytes)
// matches either the current node's address, the node's routed subnet or the
// list of subnets specified in IPv4Sources/IPv6Sources.
func (c *cryptokey) isValidSource(addr address, addrlen int) bool {
func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
ip := net.IP(addr[:addrlen])
if addrlen == net.IPv6len {
@ -143,7 +146,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
// Build our references to the routing table and cache
var routingtable *[]cryptokey_route
var routingcache *map[address]cryptokey_route
var routingcache *map[address.Address]cryptokey_route
// Check if the prefix is IPv4 or IPv6
if prefixsize == net.IPv6len*8 {
@ -157,11 +160,11 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
}
// Is the route an Yggdrasil destination?
var addr address
var snet subnet
var addr address.Address
var snet address.Subnet
copy(addr[:], ipaddr)
copy(snet[:], ipnet.IP)
if addr.isValid() || snet.isValid() {
if addr.IsValid() || snet.IsValid() {
return errors.New("Can't specify Yggdrasil destination as crypto-key route")
}
// Do we already have a route for this subnet?
@ -173,11 +176,11 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
// Decode the public key
if bpk, err := hex.DecodeString(dest); err != nil {
return err
} else if len(bpk) != boxPubKeyLen {
} else if len(bpk) != crypto.BoxPubKeyLen {
return errors.New(fmt.Sprintf("Incorrect key length for %s", dest))
} else {
// Add the new crypto-key route
var key boxPubKey
var key crypto.BoxPubKey
copy(key[:], bpk)
*routingtable = append(*routingtable, cryptokey_route{
subnet: *ipnet,
@ -205,16 +208,16 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
// Looks up the most specific route for the given address (with the address
// length specified in bytes) from the crypto-key routing table. An error is
// returned if the address is not suitable or no route was found.
func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey, error) {
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) {
// Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking
if addr.isValid() {
return boxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses")
if addr.IsValid() {
return crypto.BoxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses")
}
// Build our references to the routing table and cache
var routingtable *[]cryptokey_route
var routingcache *map[address]cryptokey_route
var routingcache *map[address.Address]cryptokey_route
// Check if the prefix is IPv4 or IPv6
if addrlen == net.IPv6len {
@ -224,7 +227,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey
routingtable = &c.ipv4routes
routingcache = &c.ipv4cache
} else {
return boxPubKey{}, errors.New("Unexpected prefix size")
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
}
// Check if there's a cache entry for this addr
@ -260,7 +263,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey
}
// No route was found if we got to this point
return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String()))
return crypto.BoxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String()))
}
// Removes a source subnet, which allows traffic with these source addresses to
@ -312,7 +315,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
// Build our references to the routing table and cache
var routingtable *[]cryptokey_route
var routingcache *map[address]cryptokey_route
var routingcache *map[address.Address]cryptokey_route
// Check if the prefix is IPv4 or IPv6
if prefixsize == net.IPv6len*8 {
@ -329,7 +332,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
bpk, err := hex.DecodeString(dest)
if err != nil {
return err
} else if len(bpk) != boxPubKeyLen {
} else if len(bpk) != crypto.BoxPubKeyLen {
return errors.New(fmt.Sprintf("Incorrect key length for %s", dest))
}
netStr := ipnet.String()

View File

@ -8,7 +8,9 @@ import (
"net"
"regexp"
"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"
)
@ -19,33 +21,32 @@ var buildVersion string
// object for each Yggdrasil node you plan to run.
type Core struct {
// This is the main data structure that holds everything else for a node
boxPub boxPubKey
boxPriv boxPrivKey
sigPub sigPubKey
sigPriv sigPrivKey
boxPub crypto.BoxPubKey
boxPriv crypto.BoxPrivKey
sigPub crypto.SigPubKey
sigPriv crypto.SigPrivKey
switchTable switchTable
peers peers
sessions sessions
router router
dht dht
tun tunDevice
admin admin
searches searches
multicast multicast
nodeinfo nodeinfo
tcp tcpInterface
log *log.Logger
ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this
}
func (c *Core) init(bpub *boxPubKey,
bpriv *boxPrivKey,
spub *sigPubKey,
spriv *sigPrivKey) {
func (c *Core) init(bpub *crypto.BoxPubKey,
bpriv *crypto.BoxPrivKey,
spub *crypto.SigPubKey,
spriv *crypto.SigPrivKey) {
// TODO separate init and start functions
// Init sets up structs
// Start launches goroutines that depend on structs being set up
// This is pretty much required to completely avoid race conditions
util_initByteStore()
if c.log == nil {
c.log = log.New(ioutil.Discard, "", 0)
}
@ -59,7 +60,6 @@ func (c *Core) init(bpub *boxPubKey,
c.peers.init(c)
c.router.init(c)
c.switchTable.init(c, c.sigPub) // TODO move before peers? before router?
c.tun.init(c)
}
// Get the current build name. This is usually injected if built from git,
@ -96,10 +96,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
c.log.Println("Starting up...")
var boxPub boxPubKey
var boxPriv boxPrivKey
var sigPub sigPubKey
var sigPriv sigPrivKey
var boxPub crypto.BoxPubKey
var boxPriv crypto.BoxPrivKey
var sigPub crypto.SigPubKey
var sigPriv crypto.SigPrivKey
boxPubHex, err := hex.DecodeString(nc.EncryptionPublicKey)
if err != nil {
return err
@ -124,6 +124,9 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)
c.admin.init(c, nc.AdminListen)
c.nodeinfo.init(c)
c.nodeinfo.setNodeInfo(nc.NodeInfo)
if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil {
c.log.Println("Failed to start TCP interface")
return err
@ -188,7 +191,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
}
ip := net.IP(c.router.addr[:]).String()
if err := c.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address_prefix)-1), nc.IfMTU); err != nil {
if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address.GetPrefix())-1), nc.IfMTU); err != nil {
c.log.Println("Failed to start TUN/TAP")
return err
}
@ -200,45 +203,55 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
// Stops the Yggdrasil node.
func (c *Core) Stop() {
c.log.Println("Stopping...")
c.tun.close()
c.router.tun.close()
c.admin.close()
}
// Generates a new encryption keypair. The encryption keys are used to
// encrypt traffic and to derive the IPv6 address/subnet of the node.
func (c *Core) NewEncryptionKeys() (*boxPubKey, *boxPrivKey) {
return newBoxKeys()
func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
return crypto.NewBoxKeys()
}
// Generates a new signing keypair. The signing keys are used to derive the
// structure of the spanning tree.
func (c *Core) NewSigningKeys() (*sigPubKey, *sigPrivKey) {
return newSigKeys()
func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
return crypto.NewSigKeys()
}
// Gets the node ID.
func (c *Core) GetNodeID() *NodeID {
return getNodeID(&c.boxPub)
func (c *Core) GetNodeID() *crypto.NodeID {
return crypto.GetNodeID(&c.boxPub)
}
// Gets the tree ID.
func (c *Core) GetTreeID() *TreeID {
return getTreeID(&c.sigPub)
func (c *Core) GetTreeID() *crypto.TreeID {
return crypto.GetTreeID(&c.sigPub)
}
// Gets the IPv6 address of the Yggdrasil node. This is always a /128.
func (c *Core) GetAddress() *net.IP {
address := net.IP(address_addrForNodeID(c.GetNodeID())[:])
address := net.IP(address.AddrForNodeID(c.GetNodeID())[:])
return &address
}
// Gets the routed IPv6 subnet of the Yggdrasil node. This is always a /64.
func (c *Core) GetSubnet() *net.IPNet {
subnet := address_subnetForNodeID(c.GetNodeID())[:]
subnet := address.SubnetForNodeID(c.GetNodeID())[:]
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
}
// Gets the nodeinfo.
func (c *Core) GetNodeInfo() nodeinfoPayload {
return c.nodeinfo.getNodeInfo()
}
// Sets the nodeinfo.
func (c *Core) SetNodeInfo(nodeinfo interface{}) {
c.nodeinfo.setNodeInfo(nodeinfo)
}
// Sets the output logger of the Yggdrasil node after startup. This may be
// useful if you want to redirect the output later.
func (c *Core) SetLogger(log *log.Logger) {
@ -293,10 +306,10 @@ func (c *Core) GetTUNDefaultIfTAPMode() bool {
// Gets the current TUN/TAP interface name.
func (c *Core) GetTUNIfName() string {
return c.tun.iface.Name()
return c.router.tun.iface.Name()
}
// Gets the current TUN/TAP interface MTU.
func (c *Core) GetTUNIfMTU() int {
return c.tun.mtu
return c.router.tun.mtu
}

View File

@ -1,167 +0,0 @@
package yggdrasil
/*
This part of the package wraps crypto operations needed elsewhere
In particular, it exposes key generation for ed25519 and nacl box
It also defines NodeID and TreeID as hashes of keys, and wraps hash functions
*/
import (
"crypto/rand"
"crypto/sha512"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
)
////////////////////////////////////////////////////////////////////////////////
// NodeID and TreeID
const NodeIDLen = sha512.Size
const TreeIDLen = sha512.Size
const handleLen = 8
type NodeID [NodeIDLen]byte
type TreeID [TreeIDLen]byte
type handle [handleLen]byte
func getNodeID(pub *boxPubKey) *NodeID {
h := sha512.Sum512(pub[:])
return (*NodeID)(&h)
}
func getTreeID(pub *sigPubKey) *TreeID {
h := sha512.Sum512(pub[:])
return (*TreeID)(&h)
}
func newHandle() *handle {
var h handle
_, err := rand.Read(h[:])
if err != nil {
panic(err)
}
return &h
}
////////////////////////////////////////////////////////////////////////////////
// Signatures
const sigPubKeyLen = ed25519.PublicKeySize
const sigPrivKeyLen = ed25519.PrivateKeySize
const sigLen = ed25519.SignatureSize
type sigPubKey [sigPubKeyLen]byte
type sigPrivKey [sigPrivKeyLen]byte
type sigBytes [sigLen]byte
func newSigKeys() (*sigPubKey, *sigPrivKey) {
var pub sigPubKey
var priv sigPrivKey
pubSlice, privSlice, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
copy(pub[:], pubSlice)
copy(priv[:], privSlice)
return &pub, &priv
}
func sign(priv *sigPrivKey, msg []byte) *sigBytes {
var sig sigBytes
sigSlice := ed25519.Sign(priv[:], msg)
copy(sig[:], sigSlice)
return &sig
}
func verify(pub *sigPubKey, msg []byte, sig *sigBytes) bool {
// Should sig be an array instead of a slice?...
// It's fixed size, but
return ed25519.Verify(pub[:], msg, sig[:])
}
////////////////////////////////////////////////////////////////////////////////
// NaCl-like crypto "box" (curve25519+xsalsa20+poly1305)
const boxPubKeyLen = 32
const boxPrivKeyLen = 32
const boxSharedKeyLen = 32
const boxNonceLen = 24
const boxOverhead = box.Overhead
type boxPubKey [boxPubKeyLen]byte
type boxPrivKey [boxPrivKeyLen]byte
type boxSharedKey [boxSharedKeyLen]byte
type boxNonce [boxNonceLen]byte
func newBoxKeys() (*boxPubKey, *boxPrivKey) {
pubBytes, privBytes, err := box.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
pub := (*boxPubKey)(pubBytes)
priv := (*boxPrivKey)(privBytes)
return pub, priv
}
func getSharedKey(myPrivKey *boxPrivKey,
othersPubKey *boxPubKey) *boxSharedKey {
var shared [boxSharedKeyLen]byte
priv := (*[boxPrivKeyLen]byte)(myPrivKey)
pub := (*[boxPubKeyLen]byte)(othersPubKey)
box.Precompute(&shared, pub, priv)
return (*boxSharedKey)(&shared)
}
func boxOpen(shared *boxSharedKey,
boxed []byte,
nonce *boxNonce) ([]byte, bool) {
out := util_getBytes()
s := (*[boxSharedKeyLen]byte)(shared)
n := (*[boxNonceLen]byte)(nonce)
unboxed, success := box.OpenAfterPrecomputation(out, boxed, n, s)
return unboxed, success
}
func boxSeal(shared *boxSharedKey, unboxed []byte, nonce *boxNonce) ([]byte, *boxNonce) {
if nonce == nil {
nonce = newBoxNonce()
}
nonce.update()
out := util_getBytes()
s := (*[boxSharedKeyLen]byte)(shared)
n := (*[boxNonceLen]byte)(nonce)
boxed := box.SealAfterPrecomputation(out, unboxed, n, s)
return boxed, nonce
}
func newBoxNonce() *boxNonce {
var nonce boxNonce
_, err := rand.Read(nonce[:])
for ; err == nil && nonce[0] == 0xff; _, err = rand.Read(nonce[:]) {
// Make sure nonce isn't too high
// This is just to make rollover unlikely to happen
// Rollover is fine, but it may kill the session and force it to reopen
}
if err != nil {
panic(err)
}
return &nonce
}
func (n *boxNonce) update() {
oldNonce := *n
n[len(n)-1] += 2
for i := len(n) - 2; i >= 0; i-- {
if n[i+1] < oldNonce[i+1] {
n[i] += 1
}
}
}

View File

@ -22,6 +22,8 @@ import "net/http"
import "runtime"
import "os"
import "github.com/yggdrasil-network/yggdrasil-go/src/address"
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
import "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
// Start the profiler in debug builds, if the required environment variable is set.
@ -48,8 +50,8 @@ func StartProfiler(log *log.Logger) error {
// This function is only called by the simulator to set up a node with random
// keys. It should not be used and may be removed in the future.
func (c *Core) Init() {
bpub, bpriv := newBoxKeys()
spub, spriv := newSigKeys()
bpub, bpriv := crypto.NewBoxKeys()
spub, spriv := crypto.NewSigKeys()
c.init(bpub, bpriv, spub, spriv)
c.switchTable.start()
c.router.start()
@ -59,20 +61,20 @@ func (c *Core) Init() {
// Core
func (c *Core) DEBUG_getSigningPublicKey() sigPubKey {
return (sigPubKey)(c.sigPub)
func (c *Core) DEBUG_getSigningPublicKey() crypto.SigPubKey {
return (crypto.SigPubKey)(c.sigPub)
}
func (c *Core) DEBUG_getEncryptionPublicKey() boxPubKey {
return (boxPubKey)(c.boxPub)
func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey {
return (crypto.BoxPubKey)(c.boxPub)
}
func (c *Core) DEBUG_getSend() chan<- []byte {
return c.tun.send
return c.router.tun.send
}
func (c *Core) DEBUG_getRecv() <-chan []byte {
return c.tun.recv
return c.router.tun.recv
}
// Peer
@ -81,7 +83,7 @@ func (c *Core) DEBUG_getPeers() *peers {
return &c.peers
}
func (ps *peers) DEBUG_newPeer(box boxPubKey, sig sigPubKey, link boxSharedKey) *peer {
func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer {
//in <-chan []byte,
//out chan<- []byte) *peer {
return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out)
@ -98,7 +100,7 @@ func (ps *peers) DEBUG_startPeers() {
}
*/
func (ps *peers) DEBUG_hasPeer(key sigPubKey) bool {
func (ps *peers) DEBUG_hasPeer(key crypto.SigPubKey) bool {
ports := ps.ports.Load().(map[switchPort]*peer)
for _, p := range ports {
if p == nil {
@ -120,7 +122,7 @@ func (ps *peers) DEBUG_getPorts() map[switchPort]*peer {
return newPeers
}
func (p *peer) DEBUG_getSigKey() sigPubKey {
func (p *peer) DEBUG_getSigKey() crypto.SigPubKey {
return p.sig
}
@ -292,8 +294,8 @@ func (c *Core) DEBUG_startLoopbackUDPInterface() {
////////////////////////////////////////////////////////////////////////////////
func (c *Core) DEBUG_getAddr() *address {
return address_addrForNodeID(&c.dht.nodeID)
func (c *Core) DEBUG_getAddr() *address.Address {
return address.AddrForNodeID(&c.dht.nodeID)
}
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
@ -302,56 +304,56 @@ func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) {
addr := c.DEBUG_getAddr()
straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix))
straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix()))
if ifname != "none" {
err := c.tun.setup(ifname, iftapmode, straddr, mtu)
err := c.router.tun.setup(ifname, iftapmode, straddr, mtu)
if err != nil {
panic(err)
}
c.log.Println("Setup TUN/TAP:", c.tun.iface.Name(), straddr)
go func() { panic(c.tun.read()) }()
c.log.Println("Setup TUN/TAP:", c.router.tun.iface.Name(), straddr)
go func() { panic(c.router.tun.read()) }()
}
go func() { panic(c.tun.write()) }()
go func() { panic(c.router.tun.write()) }()
}
func (c *Core) DEBUG_stopTun() {
c.tun.close()
c.router.tun.close()
}
////////////////////////////////////////////////////////////////////////////////
func (c *Core) DEBUG_newBoxKeys() (*boxPubKey, *boxPrivKey) {
return newBoxKeys()
func (c *Core) DEBUG_newBoxKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
return crypto.NewBoxKeys()
}
func (c *Core) DEBUG_getSharedKey(myPrivKey *boxPrivKey, othersPubKey *boxPubKey) *boxSharedKey {
return getSharedKey(myPrivKey, othersPubKey)
func (c *Core) DEBUG_getSharedKey(myPrivKey *crypto.BoxPrivKey, othersPubKey *crypto.BoxPubKey) *crypto.BoxSharedKey {
return crypto.GetSharedKey(myPrivKey, othersPubKey)
}
func (c *Core) DEBUG_newSigKeys() (*sigPubKey, *sigPrivKey) {
return newSigKeys()
func (c *Core) DEBUG_newSigKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
return crypto.NewSigKeys()
}
func (c *Core) DEBUG_getNodeID(pub *boxPubKey) *NodeID {
return getNodeID(pub)
func (c *Core) DEBUG_getNodeID(pub *crypto.BoxPubKey) *crypto.NodeID {
return crypto.GetNodeID(pub)
}
func (c *Core) DEBUG_getTreeID(pub *sigPubKey) *TreeID {
return getTreeID(pub)
func (c *Core) DEBUG_getTreeID(pub *crypto.SigPubKey) *crypto.TreeID {
return crypto.GetTreeID(pub)
}
func (c *Core) DEBUG_addrForNodeID(nodeID *NodeID) string {
return net.IP(address_addrForNodeID(nodeID)[:]).String()
func (c *Core) DEBUG_addrForNodeID(nodeID *crypto.NodeID) string {
return net.IP(address.AddrForNodeID(nodeID)[:]).String()
}
func (c *Core) DEBUG_init(bpub []byte,
bpriv []byte,
spub []byte,
spriv []byte) {
var boxPub boxPubKey
var boxPriv boxPrivKey
var sigPub sigPubKey
var sigPriv sigPrivKey
var boxPub crypto.BoxPubKey
var boxPriv crypto.BoxPrivKey
var sigPub crypto.SigPubKey
var sigPriv crypto.SigPrivKey
copy(boxPub[:], bpub)
copy(boxPriv[:], bpriv)
copy(sigPub[:], spub)
@ -546,20 +548,20 @@ func DEBUG_simLinkPeers(p, q *peer) {
}
func (c *Core) DEBUG_simFixMTU() {
c.tun.mtu = 65535
c.router.tun.mtu = 65535
}
////////////////////////////////////////////////////////////////////////////////
func Util_testAddrIDMask() {
for idx := 0; idx < 16; idx++ {
var orig NodeID
var orig crypto.NodeID
orig[8] = 42
for bidx := 0; bidx < idx; bidx++ {
orig[bidx/8] |= (0x80 >> uint8(bidx%8))
}
addr := address_addrForNodeID(&orig)
nid, mask := addr.getNodeIDandMask()
addr := address.AddrForNodeID(&orig)
nid, mask := addr.GetNodeIDandMask()
for b := 0; b < len(mask); b++ {
nid[b] &= mask[b]
orig[b] &= mask[b]

View File

@ -8,6 +8,8 @@ package yggdrasil
import (
"sort"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
const dht_lookup_size = 16
@ -15,8 +17,8 @@ const dht_lookup_size = 16
// 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.
type dhtInfo struct {
nodeID_hidden *NodeID
key boxPubKey
nodeID_hidden *crypto.NodeID
key crypto.BoxPubKey
coords []byte
recv time.Time // When we last received a message
pings int // Time out if at least 3 consecutive maintenance pings drop
@ -24,9 +26,9 @@ type dhtInfo struct {
}
// Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times.
func (info *dhtInfo) getNodeID() *NodeID {
func (info *dhtInfo) getNodeID() *crypto.NodeID {
if info.nodeID_hidden == nil {
info.nodeID_hidden = getNodeID(&info.key)
info.nodeID_hidden = crypto.GetNodeID(&info.key)
}
return info.nodeID_hidden
}
@ -34,36 +36,36 @@ func (info *dhtInfo) getNodeID() *NodeID {
// Request for a node to do a lookup.
// Includes our key and coords so they can send a response back, and the destination NodeID we want to ask about.
type dhtReq struct {
Key boxPubKey // Key of whoever asked
Coords []byte // Coords of whoever asked
Dest NodeID // NodeID they're asking about
Key crypto.BoxPubKey // Key of whoever asked
Coords []byte // Coords of whoever asked
Dest crypto.NodeID // NodeID they're asking about
}
// Response to a DHT lookup.
// Includes the key and coords of the node that's responding, and the destination they were asked about.
// The main part is Infos []*dhtInfo, the lookup response.
type dhtRes struct {
Key boxPubKey // key of the sender
Coords []byte // coords of the sender
Dest NodeID
Key crypto.BoxPubKey // key of the sender
Coords []byte // coords of the sender
Dest crypto.NodeID
Infos []*dhtInfo // response
}
// Parts of a DHT req usable as a key in a map.
type dhtReqKey struct {
key boxPubKey
dest NodeID
key crypto.BoxPubKey
dest crypto.NodeID
}
// The main DHT struct.
type dht struct {
core *Core
nodeID NodeID
nodeID crypto.NodeID
peers chan *dhtInfo // other goroutines put incoming dht updates here
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks
// These next two could be replaced by a single linked list or similar...
table map[NodeID]*dhtInfo
table map[crypto.NodeID]*dhtInfo
imp []*dhtInfo
}
@ -80,12 +82,12 @@ func (t *dht) init(c *Core) {
// This empties all info from the DHT and drops outstanding requests.
func (t *dht) reset() {
t.reqs = make(map[dhtReqKey]time.Time)
t.table = make(map[NodeID]*dhtInfo)
t.table = make(map[crypto.NodeID]*dhtInfo)
t.imp = nil
}
// Does a DHT lookup and returns up to dht_lookup_size results.
func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo {
func (t *dht) lookup(nodeID *crypto.NodeID, everything bool) []*dhtInfo {
results := make([]*dhtInfo, 0, len(t.table))
for _, info := range t.table {
results = append(results, info)
@ -133,9 +135,9 @@ func (t *dht) insert(info *dhtInfo) {
}
// Return true if first/second/third are (partially) ordered correctly.
func dht_ordered(first, second, third *NodeID) bool {
lessOrEqual := func(first, second *NodeID) bool {
for idx := 0; idx < NodeIDLen; idx++ {
func dht_ordered(first, second, third *crypto.NodeID) bool {
lessOrEqual := func(first, second *crypto.NodeID) bool {
for idx := 0; idx < crypto.NodeIDLen; idx++ {
if first[idx] > second[idx] {
return false
}
@ -190,7 +192,7 @@ func (t *dht) sendRes(res *dhtRes, req *dhtReq) {
// Send a reply for a dhtReq
bs := res.encode()
shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &req.Key)
payload, nonce := boxSeal(shared, bs, nil)
payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
Coords: req.Coords,
ToKey: req.Key,
@ -252,7 +254,7 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) {
// Send a dhtReq to the node in dhtInfo
bs := req.encode()
shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &dest.key)
payload, nonce := boxSeal(shared, bs, nil)
payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
Coords: dest.coords,
ToKey: dest.key,
@ -267,7 +269,7 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) {
}
// Sends a lookup to this info, looking for the target.
func (t *dht) ping(info *dhtInfo, target *NodeID) {
func (t *dht) ping(info *dhtInfo, target *crypto.NodeID) {
// Creates a req for the node at dhtInfo, asking them about the target (if one is given) or themself (if no target is given)
if target == nil {
target = &t.nodeID

View File

@ -17,6 +17,8 @@ import (
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type macAddress [6]byte
@ -24,10 +26,10 @@ type macAddress [6]byte
const len_ETHER = 14
type icmpv6 struct {
tun *tunDevice
tun *tunAdapter
mylladdr net.IP
mymac macAddress
peermacs map[address]neighbor
peermacs map[address.Address]neighbor
}
type neighbor struct {
@ -57,9 +59,9 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) {
// Initialises the ICMPv6 module by assigning our link-local IPv6 address and
// our MAC address. ICMPv6 messages will always appear to originate from these
// addresses.
func (i *icmpv6) init(t *tunDevice) {
func (i *icmpv6) init(t *tunAdapter) {
i.tun = t
i.peermacs = make(map[address]neighbor)
i.peermacs = make(map[address.Address]neighbor)
// Our MAC address and link-local address
i.mymac = macAddress{
@ -172,7 +174,7 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
}
case ipv6.ICMPTypeNeighborAdvertisement:
if datamac != nil {
var addr address
var addr address.Address
var mac macAddress
copy(addr[:], ipv6Header.Src[:])
copy(mac[:], (*datamac)[:])
@ -254,7 +256,7 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType,
return responsePacket, nil
}
func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) {
func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
// Create the ND payload
var payload [28]byte
copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00})
@ -263,7 +265,7 @@ func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) {
copy(payload[22:28], i.mymac[:6])
// Create the ICMPv6 solicited-node address
var dstaddr address
var dstaddr address.Address
copy(dstaddr[:13], []byte{
0xFF, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
@ -296,13 +298,13 @@ func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) {
// to the Yggdrasil TAP adapter.
func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
// Ignore NDP requests for anything outside of fd00::/8
var source address
var source address.Address
copy(source[:], in[8:])
var snet subnet
var snet address.Subnet
copy(snet[:], in[8:])
switch {
case source.isValid():
case snet.isValid():
case source.IsValid():
case snet.IsValid():
default:
return nil, errors.New("Not an NDP for 0200::/7")
}

177
src/yggdrasil/nodeinfo.go Normal file
View File

@ -0,0 +1,177 @@
package yggdrasil
import (
"encoding/json"
"errors"
"runtime"
"sync"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
type nodeinfo struct {
core *Core
myNodeInfo nodeinfoPayload
myNodeInfoMutex sync.RWMutex
callbacks map[crypto.BoxPubKey]nodeinfoCallback
callbacksMutex sync.Mutex
cache map[crypto.BoxPubKey]nodeinfoCached
cacheMutex sync.RWMutex
}
type nodeinfoPayload []byte
type nodeinfoCached struct {
payload nodeinfoPayload
created time.Time
}
type nodeinfoCallback struct {
call func(nodeinfo *nodeinfoPayload)
created time.Time
}
// Represents a session nodeinfo packet.
type nodeinfoReqRes struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
SendCoords []byte // Sender's coords
IsResponse bool
NodeInfo nodeinfoPayload
}
// 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.core = core
m.callbacks = make(map[crypto.BoxPubKey]nodeinfoCallback)
m.cache = make(map[crypto.BoxPubKey]nodeinfoCached)
go func() {
for {
m.callbacksMutex.Lock()
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.Sleep(time.Second * 30)
}
}()
}
// 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.callbacks[sender] = nodeinfoCallback{
created: time.Now(),
call: call,
}
}
// Handles the callback, if there is one
func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
if callback, ok := m.callbacks[sender]; ok {
callback.call(&nodeinfo)
delete(m.callbacks, sender)
}
}
// Get the current node's nodeinfo
func (m *nodeinfo) getNodeInfo() nodeinfoPayload {
m.myNodeInfoMutex.RLock()
defer m.myNodeInfoMutex.RUnlock()
return m.myNodeInfo
}
// Set the current node's nodeinfo
func (m *nodeinfo) setNodeInfo(given interface{}) error {
m.myNodeInfoMutex.Lock()
defer m.myNodeInfoMutex.Unlock()
newnodeinfo := map[string]interface{}{
"buildname": GetBuildName(),
"buildversion": GetBuildVersion(),
"buildplatform": runtime.GOOS,
"buildarch": runtime.GOARCH,
}
if nodeinfomap, ok := given.(map[string]interface{}); ok {
for key, value := range nodeinfomap {
if _, ok := newnodeinfo[key]; ok {
continue
}
newnodeinfo[key] = value
}
}
if newjson, err := json.Marshal(newnodeinfo); err == nil {
if len(newjson) > 16384 {
return errors.New("NodeInfo exceeds max length of 16384 bytes")
}
m.myNodeInfo = newjson
return nil
} else {
return err
}
}
// Add nodeinfo into the cache for a node
func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPayload) {
m.cacheMutex.Lock()
defer m.cacheMutex.Unlock()
m.cache[key] = nodeinfoCached{
created: time.Now(),
payload: payload,
}
}
// Get a nodeinfo entry from the cache
func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (nodeinfoPayload, error) {
m.cacheMutex.RLock()
defer m.cacheMutex.RUnlock()
if nodeinfo, ok := m.cache[key]; ok {
return nodeinfo.payload, nil
}
return nodeinfoPayload{}, errors.New("No cache entry found")
}
// Handles a nodeinfo request/response - called from the router
func (m *nodeinfo) handleNodeInfo(nodeinfo *nodeinfoReqRes) {
if nodeinfo.IsResponse {
m.callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo)
m.addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo)
} else {
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) {
table := m.core.switchTable.table.Load().(lookupTable)
nodeinfo := nodeinfoReqRes{
SendCoords: table.self.getCoords(),
IsResponse: isResponse,
NodeInfo: m.core.nodeinfo.getNodeInfo(),
}
bs := nodeinfo.encode()
shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key)
payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
Coords: coords,
ToKey: key,
FromKey: m.core.boxPub,
Nonce: *nonce,
Payload: payload,
}
packet := p.encode()
m.core.router.out(packet)
}

View File

@ -8,6 +8,9 @@ import (
"sync"
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// The peers struct represents peers with an active connection.
@ -19,7 +22,7 @@ type peers struct {
mutex sync.Mutex // Synchronize writes to atomic
ports atomic.Value //map[switchPort]*peer, use CoW semantics
authMutex sync.RWMutex
allowedEncryptionPublicKeys map[boxPubKey]struct{}
allowedEncryptionPublicKeys map[crypto.BoxPubKey]struct{}
}
// Initializes the peers struct.
@ -28,11 +31,11 @@ func (ps *peers) init(c *Core) {
defer ps.mutex.Unlock()
ps.putPorts(make(map[switchPort]*peer))
ps.core = c
ps.allowedEncryptionPublicKeys = make(map[boxPubKey]struct{})
ps.allowedEncryptionPublicKeys = make(map[crypto.BoxPubKey]struct{})
}
// Returns true if an incoming peer connection to a key is allowed, either because the key is in the whitelist or because the whitelist is empty.
func (ps *peers) isAllowedEncryptionPublicKey(box *boxPubKey) bool {
func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool {
ps.authMutex.RLock()
defer ps.authMutex.RUnlock()
_, isIn := ps.allowedEncryptionPublicKeys[*box]
@ -40,24 +43,24 @@ func (ps *peers) isAllowedEncryptionPublicKey(box *boxPubKey) bool {
}
// Adds a key to the whitelist.
func (ps *peers) addAllowedEncryptionPublicKey(box *boxPubKey) {
func (ps *peers) addAllowedEncryptionPublicKey(box *crypto.BoxPubKey) {
ps.authMutex.Lock()
defer ps.authMutex.Unlock()
ps.allowedEncryptionPublicKeys[*box] = struct{}{}
}
// Removes a key from the whitelist.
func (ps *peers) removeAllowedEncryptionPublicKey(box *boxPubKey) {
func (ps *peers) removeAllowedEncryptionPublicKey(box *crypto.BoxPubKey) {
ps.authMutex.Lock()
defer ps.authMutex.Unlock()
delete(ps.allowedEncryptionPublicKeys, *box)
}
// Gets the whitelist of allowed keys for incoming connections.
func (ps *peers) getAllowedEncryptionPublicKeys() []boxPubKey {
func (ps *peers) getAllowedEncryptionPublicKeys() []crypto.BoxPubKey {
ps.authMutex.RLock()
defer ps.authMutex.RUnlock()
keys := make([]boxPubKey, 0, len(ps.allowedEncryptionPublicKeys))
keys := make([]crypto.BoxPubKey, 0, len(ps.allowedEncryptionPublicKeys))
for key := range ps.allowedEncryptionPublicKeys {
keys = append(keys, key)
}
@ -79,32 +82,32 @@ type peer struct {
bytesSent uint64 // To track bandwidth usage for getPeers
bytesRecvd uint64 // To track bandwidth usage for getPeers
// BUG: sync/atomic, 32 bit platforms need the above to be the first element
core *Core
port switchPort
box boxPubKey
sig sigPubKey
shared boxSharedKey
linkShared boxSharedKey
endpoint string
friendlyName string
firstSeen time.Time // To track uptime for getPeers
linkOut (chan []byte) // used for protocol traffic (to bypass queues)
doSend (chan struct{}) // tell the linkLoop to send a switchMsg
dinfo *dhtInfo // used to keep the DHT working
out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
close func() // Called when a peer is removed, to close the underlying connection, or via admin api
core *Core
port switchPort
box crypto.BoxPubKey
sig crypto.SigPubKey
shared crypto.BoxSharedKey
linkShared crypto.BoxSharedKey
endpoint string
firstSeen time.Time // To track uptime for getPeers
linkOut (chan []byte) // used for protocol traffic (to bypass queues)
doSend (chan struct{}) // tell the linkLoop to send a switchMsg
dinfo (chan *dhtInfo) // used to keep the DHT working
out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
close func() // Called when a peer is removed, to close the underlying connection, or via admin api
}
// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number.
func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string) *peer {
func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string) *peer {
now := time.Now()
p := peer{box: *box,
sig: *sig,
shared: *getSharedKey(&ps.core.boxPriv, box),
shared: *crypto.GetSharedKey(&ps.core.boxPriv, box),
linkShared: *linkShared,
endpoint: endpoint,
firstSeen: now,
doSend: make(chan struct{}, 1),
dinfo: make(chan *dhtInfo, 1),
core: ps.core}
ps.mutex.Lock()
defer ps.mutex.Unlock()
@ -177,6 +180,8 @@ func (p *peer) doSendSwitchMsgs() {
func (p *peer) linkLoop() {
tick := time.NewTicker(time.Second)
defer tick.Stop()
p.doSendSwitchMsgs()
var dinfo *dhtInfo
for {
select {
case _, ok := <-p.doSend:
@ -184,12 +189,10 @@ func (p *peer) linkLoop() {
return
}
p.sendSwitchMsg()
case dinfo = <-p.dinfo:
case _ = <-tick.C:
//break // FIXME disabled the below completely to test something
pdinfo := p.dinfo // FIXME this is a bad workarond NPE on the next line
if pdinfo != nil {
dinfo := *pdinfo
p.core.dht.peers <- &dinfo
if dinfo != nil {
p.core.dht.peers <- dinfo
}
}
}
@ -212,15 +215,16 @@ func (p *peer) handlePacket(packet []byte) {
case wire_LinkProtocolTraffic:
p.handleLinkTraffic(packet)
default:
util_putBytes(packet)
util.PutBytes(packet)
}
}
// Called to handle traffic or protocolTraffic packets.
// In either case, this reads from the coords of the packet header, does a switch lookup, and forwards to the next node.
func (p *peer) handleTraffic(packet []byte, pTypeLen int) {
if p.port != 0 && p.dinfo == nil {
// Drop traffic until the peer manages to send us at least one good switchMsg
table := p.core.switchTable.getTable()
if _, isIn := table.elems[p.port]; !isIn && p.port != 0 {
// Drop traffic if the peer isn't in the switch
return
}
p.core.switchTable.packetIn <- packet
@ -236,13 +240,13 @@ func (p *peer) sendPacket(packet []byte) {
// This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers.
// It sends it to p.linkOut, which bypasses the usual packet queues.
func (p *peer) sendLinkPacket(packet []byte) {
innerPayload, innerNonce := boxSeal(&p.linkShared, packet, nil)
innerPayload, innerNonce := crypto.BoxSeal(&p.linkShared, packet, nil)
innerLinkPacket := wire_linkProtoTrafficPacket{
Nonce: *innerNonce,
Payload: innerPayload,
}
outerPayload := innerLinkPacket.encode()
bs, nonce := boxSeal(&p.shared, outerPayload, nil)
bs, nonce := crypto.BoxSeal(&p.shared, outerPayload, nil)
linkPacket := wire_linkProtoTrafficPacket{
Nonce: *nonce,
Payload: bs,
@ -258,7 +262,7 @@ func (p *peer) handleLinkTraffic(bs []byte) {
if !packet.decode(bs) {
return
}
outerPayload, isOK := boxOpen(&p.shared, packet.Payload, &packet.Nonce)
outerPayload, isOK := crypto.BoxOpen(&p.shared, packet.Payload, &packet.Nonce)
if !isOK {
return
}
@ -266,7 +270,7 @@ func (p *peer) handleLinkTraffic(bs []byte) {
if !innerPacket.decode(outerPayload) {
return
}
payload, isOK := boxOpen(&p.linkShared, innerPacket.Payload, &innerPacket.Nonce)
payload, isOK := crypto.BoxOpen(&p.linkShared, innerPacket.Payload, &innerPacket.Nonce)
if !isOK {
return
}
@ -278,7 +282,7 @@ func (p *peer) handleLinkTraffic(bs []byte) {
case wire_SwitchMsg:
p.handleSwitchMsg(payload)
default:
util_putBytes(bs)
util.PutBytes(bs)
}
}
@ -292,7 +296,7 @@ func (p *peer) sendSwitchMsg() {
msg.Hops = append(msg.Hops, switchMsgHop{
Port: p.port,
Next: p.sig,
Sig: *sign(&p.core.sigPriv, bs),
Sig: *crypto.Sign(&p.core.sigPriv, bs),
})
packet := msg.encode()
p.sendLinkPacket(packet)
@ -316,7 +320,7 @@ func (p *peer) handleSwitchMsg(packet []byte) {
sigMsg.Hops = msg.Hops[:idx]
loc.coords = append(loc.coords, hop.Port)
bs := getBytesForSig(&hop.Next, &sigMsg)
if !verify(&prevKey, bs, &hop.Sig) {
if !crypto.Verify(&prevKey, bs, &hop.Sig) {
p.core.peers.removePeer(p.port)
}
prevKey = hop.Next
@ -324,9 +328,7 @@ func (p *peer) handleSwitchMsg(packet []byte) {
p.core.switchTable.handleMsg(&msg, p.port)
if !p.core.switchTable.checkRoot(&msg) {
// Bad switch message
// Stop forwarding traffic from it
// Stop refreshing it in the DHT
p.dinfo = nil
p.dinfo <- nil
return
}
// Pass a mesage to the dht informing it that this peer (still) exists
@ -335,13 +337,12 @@ func (p *peer) handleSwitchMsg(packet []byte) {
key: p.box,
coords: loc.getCoords(),
}
//p.core.dht.peers <- &dinfo
p.dinfo = &dinfo
p.dinfo <- &dinfo
}
// This generates the bytes that we sign or check the signature of for a switchMsg.
// It begins with the next node's key, followed by the root and the timetsamp, followed by coords being advertised to the next node.
func getBytesForSig(next *sigPubKey, msg *switchMsg) []byte {
func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte {
var loc switchLocator
for _, hop := range msg.Hops {
loc.coords = append(loc.coords, hop.Port)

View File

@ -28,17 +28,23 @@ import (
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
type router struct {
core *Core
addr address
subnet subnet
addr address.Address
subnet address.Subnet
in <-chan []byte // packets we received from the network, link to peer's "out"
out func([]byte) // packets we're sending to the network, link to peer's "in"
toRecv chan router_recvPacket // packets to handle via recvPacket()
tun tunAdapter // TUN/TAP adapter
adapters []Adapter // Other adapters
recv chan<- []byte // place where the tun pulls received packets from
send <-chan []byte // place where the tun puts outgoing packets
reset chan struct{} // signal that coords changed (re-init sessions/dht)
@ -55,17 +61,17 @@ type router_recvPacket struct {
// Initializes the router struct, which includes setting up channels to/from the tun/tap.
func (r *router) init(core *Core) {
r.core = core
r.addr = *address_addrForNodeID(&r.core.dht.nodeID)
r.subnet = *address_subnetForNodeID(&r.core.dht.nodeID)
r.addr = *address.AddrForNodeID(&r.core.dht.nodeID)
r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID)
in := make(chan []byte, 32) // TODO something better than this...
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)")
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)")
p.out = func(packet []byte) {
// This is to make very sure it never blocks
select {
case in <- packet:
return
default:
util_putBytes(packet)
util.PutBytes(packet)
}
}
r.in = in
@ -75,12 +81,10 @@ func (r *router) init(core *Core) {
send := make(chan []byte, 32)
r.recv = recv
r.send = send
r.core.tun.recv = recv
r.core.tun.send = send
r.reset = make(chan struct{}, 1)
r.admin = make(chan func(), 32)
r.cryptokey.init(r.core)
// go r.mainLoop()
r.tun.init(r.core, send, recv)
}
// Starts the mainLoop goroutine.
@ -121,7 +125,7 @@ func (r *router) mainLoop() {
r.core.switchTable.doMaintenance()
r.core.dht.doMaintenance()
r.core.sessions.cleanup()
util_getBytes() // To slowly drain things
util.GetBytes() // To slowly drain things
}
case f := <-r.admin:
f()
@ -135,11 +139,11 @@ func (r *router) mainLoop() {
// If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly.
// It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled.
func (r *router) sendPacket(bs []byte) {
var sourceAddr address
var destAddr address
var destSnet subnet
var destPubKey *boxPubKey
var destNodeID *NodeID
var sourceAddr address.Address
var destAddr address.Address
var destSnet address.Subnet
var destPubKey *crypto.BoxPubKey
var destNodeID *crypto.NodeID
var addrlen int
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized header
@ -169,19 +173,19 @@ func (r *router) sendPacket(bs []byte) {
// configured crypto-key routing source subnets
return
}
if !destAddr.isValid() && !destSnet.isValid() {
if !destAddr.IsValid() && !destSnet.IsValid() {
// The addresses didn't match valid Yggdrasil node addresses so let's see
// whether it matches a crypto-key routing range instead
if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil {
// A public key was found, get the node ID for the search
destPubKey = &key
destNodeID = getNodeID(destPubKey)
destNodeID = crypto.GetNodeID(destPubKey)
// Do a quick check to ensure that the node ID refers to a vaild Yggdrasil
// address or subnet - this might be superfluous
addr := *address_addrForNodeID(destNodeID)
addr := *address.AddrForNodeID(destNodeID)
copy(destAddr[:], addr[:])
copy(destSnet[:], addr[:])
if !destAddr.isValid() && !destSnet.isValid() {
if !destAddr.IsValid() && !destSnet.IsValid() {
return
}
} else {
@ -190,25 +194,25 @@ func (r *router) sendPacket(bs []byte) {
}
}
doSearch := func(packet []byte) {
var nodeID, mask *NodeID
var nodeID, mask *crypto.NodeID
switch {
case destNodeID != nil:
// We already know the full node ID, probably because it's from a CKR
// route in which the public key is known ahead of time
nodeID = destNodeID
var m NodeID
var m crypto.NodeID
for i := range m {
m[i] = 0xFF
}
mask = &m
case destAddr.isValid():
case destAddr.IsValid():
// We don't know the full node ID - try and use the address to generate
// a truncated node ID
nodeID, mask = destAddr.getNodeIDandMask()
case destSnet.isValid():
nodeID, mask = destAddr.GetNodeIDandMask()
case destSnet.IsValid():
// We don't know the full node ID - try and use the subnet to generate
// a truncated node ID
nodeID, mask = destSnet.getNodeIDandMask()
nodeID, mask = destSnet.GetNodeIDandMask()
default:
return
}
@ -223,10 +227,10 @@ func (r *router) sendPacket(bs []byte) {
}
var sinfo *sessionInfo
var isIn bool
if destAddr.isValid() {
if destAddr.IsValid() {
sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr)
}
if destSnet.isValid() {
if destSnet.IsValid() {
sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet)
}
switch {
@ -267,25 +271,6 @@ func (r *router) sendPacket(bs []byte) {
// Drop packets if the session MTU is 0 - this means that one or other
// side probably has their TUN adapter disabled
if sinfo.getMTU() == 0 {
// Get the size of the oversized payload, up to a max of 900 bytes
window := 900
if len(bs) < window {
window = len(bs)
}
// Create the Destination Unreachable response
ptb := &icmp.DstUnreach{
Data: bs[:window],
}
// Create the ICMPv6 response from it
icmpv6Buf, err := r.core.tun.icmpv6.create_icmpv6_tun(
bs[8:24], bs[24:40],
ipv6.ICMPTypeDestinationUnreachable, 1, ptb)
if err == nil {
r.recv <- icmpv6Buf
}
// Don't continue - drop the packet
return
}
@ -304,7 +289,7 @@ func (r *router) sendPacket(bs []byte) {
}
// Create the ICMPv6 response from it
icmpv6Buf, err := r.core.tun.icmpv6.create_icmpv6_tun(
icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun(
bs[8:24], bs[24:40],
ipv6.ICMPTypePacketTooBig, 0, ptb)
if err == nil {
@ -324,12 +309,12 @@ func (r *router) sendPacket(bs []byte) {
func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) {
// Note: called directly by the session worker, not the router goroutine
if len(bs) < 24 {
util_putBytes(bs)
util.PutBytes(bs)
return
}
var sourceAddr address
var dest address
var snet subnet
var sourceAddr address.Address
var dest address.Address
var snet address.Subnet
var addrlen int
if bs[0]&0xf0 == 0x60 {
// IPv6 address
@ -349,17 +334,17 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) {
// Check that the packet is destined for either our Yggdrasil address or
// subnet, or that it matches one of the crypto-key routing source routes
if !r.cryptokey.isValidSource(dest, addrlen) {
util_putBytes(bs)
util.PutBytes(bs)
return
}
// See whether the packet they sent should have originated from this session
switch {
case sourceAddr.isValid() && sourceAddr == sinfo.theirAddr:
case snet.isValid() && snet == sinfo.theirSubnet:
case sourceAddr.IsValid() && sourceAddr == sinfo.theirAddr:
case snet.IsValid() && snet == sinfo.theirSubnet:
default:
key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen)
if err != nil || key != sinfo.theirPermPub {
util_putBytes(bs)
util.PutBytes(bs)
return
}
}
@ -385,7 +370,7 @@ func (r *router) handleIn(packet []byte) {
// Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets.
// Passes them to the crypto session worker to be decrypted and sent to the tun/tap.
func (r *router) handleTraffic(packet []byte) {
defer util_putBytes(packet)
defer util.PutBytes(packet)
p := wire_trafficPacket{}
if !p.decode(packet) {
return
@ -405,14 +390,14 @@ func (r *router) handleProto(packet []byte) {
return
}
// Now try to open the payload
var sharedKey *boxSharedKey
var sharedKey *crypto.BoxSharedKey
if p.ToKey == r.core.boxPub {
// Try to open using our permanent key
sharedKey = r.core.sessions.getSharedKey(&r.core.boxPriv, &p.FromKey)
} else {
return
}
bs, isOK := boxOpen(sharedKey, p.Payload, &p.Nonce)
bs, isOK := crypto.BoxOpen(sharedKey, p.Payload, &p.Nonce)
if !isOK {
return
}
@ -428,17 +413,21 @@ func (r *router) handleProto(packet []byte) {
r.handlePing(bs, &p.FromKey)
case wire_SessionPong:
r.handlePong(bs, &p.FromKey)
case wire_NodeInfoRequest:
fallthrough
case wire_NodeInfoResponse:
r.handleNodeInfo(bs, &p.FromKey)
case wire_DHTLookupRequest:
r.handleDHTReq(bs, &p.FromKey)
case wire_DHTLookupResponse:
r.handleDHTRes(bs, &p.FromKey)
default:
util_putBytes(packet)
util.PutBytes(packet)
}
}
// Decodes session pings from wire format and passes them to sessions.handlePing where they either create or update a session.
func (r *router) handlePing(bs []byte, fromKey *boxPubKey) {
func (r *router) handlePing(bs []byte, fromKey *crypto.BoxPubKey) {
ping := sessionPing{}
if !ping.decode(bs) {
return
@ -448,12 +437,12 @@ func (r *router) handlePing(bs []byte, fromKey *boxPubKey) {
}
// Handles session pongs (which are really pings with an extra flag to prevent acknowledgement).
func (r *router) handlePong(bs []byte, fromKey *boxPubKey) {
func (r *router) handlePong(bs []byte, fromKey *crypto.BoxPubKey) {
r.handlePing(bs, fromKey)
}
// Decodes dht requests and passes them to dht.handleReq to trigger a lookup/response.
func (r *router) handleDHTReq(bs []byte, fromKey *boxPubKey) {
func (r *router) handleDHTReq(bs []byte, fromKey *crypto.BoxPubKey) {
req := dhtReq{}
if !req.decode(bs) {
return
@ -463,7 +452,7 @@ func (r *router) handleDHTReq(bs []byte, fromKey *boxPubKey) {
}
// Decodes dht responses and passes them to dht.handleRes to update the DHT table and further pass them to the search code (if applicable).
func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) {
func (r *router) handleDHTRes(bs []byte, fromKey *crypto.BoxPubKey) {
res := dhtRes{}
if !res.decode(bs) {
return
@ -472,6 +461,16 @@ func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) {
r.core.dht.handleRes(&res)
}
// Decodes nodeinfo request
func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) {
req := nodeinfoReqRes{}
if !req.decode(bs) {
return
}
req.SendPermPub = *fromKey
r.core.nodeinfo.handleNodeInfo(&req)
}
// Passed a function to call.
// This will send the function to r.admin and block until it finishes.
// It's used by the admin socket to ask the router mainLoop goroutine about information in the session or dht structs, which cannot be read safely from outside that goroutine.

View File

@ -17,6 +17,8 @@ package yggdrasil
import (
"sort"
"time"
"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.
@ -30,28 +32,28 @@ const search_RETRY_TIME = time.Second
// Information about an ongoing search.
// Includes the targed NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
type searchInfo struct {
dest NodeID
mask NodeID
dest crypto.NodeID
mask crypto.NodeID
time time.Time
packet []byte
toVisit []*dhtInfo
visited map[NodeID]bool
visited map[crypto.NodeID]bool
}
// This stores a map of active searches.
type searches struct {
core *Core
searches map[NodeID]*searchInfo
searches map[crypto.NodeID]*searchInfo
}
// Intializes the searches struct.
func (s *searches) init(core *Core) {
s.core = core
s.searches = make(map[NodeID]*searchInfo)
s.searches = make(map[crypto.NodeID]*searchInfo)
}
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
func (s *searches) createSearch(dest *NodeID, mask *NodeID) *searchInfo {
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
now := time.Now()
for dest, sinfo := range s.searches {
if now.Sub(sinfo.time) > time.Minute {
@ -102,7 +104,7 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
}
}
// Deduplicate
vMap := make(map[NodeID]*dhtInfo)
vMap := make(map[crypto.NodeID]*dhtInfo)
for _, info := range sinfo.toVisit {
vMap[*info.getNodeID()] = info
}
@ -163,10 +165,10 @@ func (s *searches) continueSearch(sinfo *searchInfo) {
}
// Calls create search, and initializes the iterative search parts of the struct before returning it.
func (s *searches) newIterSearch(dest *NodeID, mask *NodeID) *searchInfo {
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
sinfo := s.createSearch(dest, mask)
sinfo.toVisit = s.core.dht.lookup(dest, true)
sinfo.visited = make(map[NodeID]bool)
sinfo.visited = make(map[crypto.NodeID]bool)
return sinfo
}
@ -174,10 +176,10 @@ func (s *searches) newIterSearch(dest *NodeID, mask *NodeID) *searchInfo {
// If the response is from the target, get/create a session, trigger a session ping, and return true.
// Otherwise return false.
func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool {
them := getNodeID(&res.Key)
var destMasked NodeID
var themMasked NodeID
for idx := 0; idx < NodeIDLen; idx++ {
them := crypto.GetNodeID(&res.Key)
var destMasked crypto.NodeID
var themMasked crypto.NodeID
for idx := 0; idx < crypto.NodeIDLen; idx++ {
destMasked[idx] = info.dest[idx] & info.mask[idx]
themMasked[idx] = them[idx] & info.mask[idx]
}

View File

@ -8,23 +8,27 @@ import (
"bytes"
"encoding/hex"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// All the information we know about an active session.
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
type sessionInfo struct {
core *Core
theirAddr address
theirSubnet subnet
theirPermPub boxPubKey
theirSesPub boxPubKey
mySesPub boxPubKey
mySesPriv boxPrivKey
sharedSesKey boxSharedKey // derived from session keys
theirHandle handle
myHandle handle
theirNonce boxNonce
myNonce boxNonce
theirAddr address.Address
theirSubnet address.Subnet
theirPermPub crypto.BoxPubKey
theirSesPub crypto.BoxPubKey
mySesPub crypto.BoxPubKey
mySesPriv crypto.BoxPrivKey
sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle
myHandle crypto.Handle
theirNonce crypto.BoxNonce
myNonce crypto.BoxNonce
theirMTU uint16
myMTU uint16
wasMTUFixed bool // Was the MTU fixed by a receive error?
@ -45,9 +49,9 @@ type sessionInfo struct {
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
type sessionPing struct {
SendPermPub boxPubKey // Sender's permanent key
Handle handle // Random number to ID session
SendSesPub boxPubKey // Session key to use
SendPermPub crypto.BoxPubKey // Sender's permanent key
Handle crypto.Handle // Random number to ID session
SendSesPub crypto.BoxPubKey // Session key to use
Coords []byte
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool
@ -69,8 +73,8 @@ func (s *sessionInfo) update(p *sessionPing) bool {
if p.SendSesPub != s.theirSesPub {
s.theirSesPub = p.SendSesPub
s.theirHandle = p.Handle
s.sharedSesKey = *getSharedKey(&s.mySesPriv, &s.theirSesPub)
s.theirNonce = boxNonce{}
s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
s.theirNonce = crypto.BoxNonce{}
s.nonceMask = 0
}
if p.MTU >= 1280 || p.MTU == 0 {
@ -99,15 +103,15 @@ type sessions struct {
core *Core
lastCleanup time.Time
// Maps known permanent keys to their shared key, used by DHT a lot
permShared map[boxPubKey]*boxSharedKey
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey
// Maps (secret) handle onto session info
sinfos map[handle]*sessionInfo
sinfos map[crypto.Handle]*sessionInfo
// Maps mySesPub onto handle
byMySes map[boxPubKey]*handle
byMySes map[crypto.BoxPubKey]*crypto.Handle
// Maps theirPermPub onto handle
byTheirPerm map[boxPubKey]*handle
addrToPerm map[address]*boxPubKey
subnetToPerm map[subnet]*boxPubKey
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
addrToPerm map[address.Address]*crypto.BoxPubKey
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
// Options from the session firewall
sessionFirewallEnabled bool
sessionFirewallAllowsDirect bool
@ -120,12 +124,12 @@ type sessions struct {
// Initializes the session struct.
func (ss *sessions) init(core *Core) {
ss.core = core
ss.permShared = make(map[boxPubKey]*boxSharedKey)
ss.sinfos = make(map[handle]*sessionInfo)
ss.byMySes = make(map[boxPubKey]*handle)
ss.byTheirPerm = make(map[boxPubKey]*handle)
ss.addrToPerm = make(map[address]*boxPubKey)
ss.subnetToPerm = make(map[subnet]*boxPubKey)
ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey)
ss.sinfos = make(map[crypto.Handle]*sessionInfo)
ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.addrToPerm = make(map[address.Address]*crypto.BoxPubKey)
ss.subnetToPerm = make(map[address.Subnet]*crypto.BoxPubKey)
ss.lastCleanup = time.Now()
}
@ -154,18 +158,18 @@ func (ss *sessions) setSessionFirewallBlacklist(blacklist []string) {
// Determines whether the session with a given publickey is allowed based on
// session firewall rules.
func (ss *sessions) isSessionAllowed(pubkey *boxPubKey, initiator bool) bool {
func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool {
// Allow by default if the session firewall is disabled
if !ss.sessionFirewallEnabled {
return true
}
// Prepare for checking whitelist/blacklist
var box boxPubKey
var box crypto.BoxPubKey
// Reject blacklisted nodes
for _, b := range ss.sessionFirewallBlacklist {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:boxPubKeyLen], key)
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return false
}
@ -175,7 +179,7 @@ func (ss *sessions) isSessionAllowed(pubkey *boxPubKey, initiator bool) bool {
for _, b := range ss.sessionFirewallWhitelist {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:boxPubKeyLen], key)
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return true
}
@ -208,7 +212,7 @@ func (ss *sessions) isSessionAllowed(pubkey *boxPubKey, initiator bool) bool {
}
// Gets the session corresponding to a given handle.
func (ss *sessions) getSessionForHandle(handle *handle) (*sessionInfo, bool) {
func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) {
sinfo, isIn := ss.sinfos[*handle]
if isIn && sinfo.timedout() {
// We have a session, but it has timed out
@ -218,7 +222,7 @@ func (ss *sessions) getSessionForHandle(handle *handle) (*sessionInfo, bool) {
}
// Gets a session corresponding to an ephemeral session key used by this node.
func (ss *sessions) getByMySes(key *boxPubKey) (*sessionInfo, bool) {
func (ss *sessions) getByMySes(key *crypto.BoxPubKey) (*sessionInfo, bool) {
h, isIn := ss.byMySes[*key]
if !isIn {
return nil, false
@ -228,7 +232,7 @@ func (ss *sessions) getByMySes(key *boxPubKey) (*sessionInfo, bool) {
}
// Gets a session corresponding to a permanent key used by the remote node.
func (ss *sessions) getByTheirPerm(key *boxPubKey) (*sessionInfo, bool) {
func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) {
h, isIn := ss.byTheirPerm[*key]
if !isIn {
return nil, false
@ -238,7 +242,7 @@ func (ss *sessions) getByTheirPerm(key *boxPubKey) (*sessionInfo, bool) {
}
// Gets a session corresponding to an IPv6 address used by the remote node.
func (ss *sessions) getByTheirAddr(addr *address) (*sessionInfo, bool) {
func (ss *sessions) getByTheirAddr(addr *address.Address) (*sessionInfo, bool) {
p, isIn := ss.addrToPerm[*addr]
if !isIn {
return nil, false
@ -248,7 +252,7 @@ func (ss *sessions) getByTheirAddr(addr *address) (*sessionInfo, bool) {
}
// Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network.
func (ss *sessions) getByTheirSubnet(snet *subnet) (*sessionInfo, bool) {
func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) {
p, isIn := ss.subnetToPerm[*snet]
if !isIn {
return nil, false
@ -259,7 +263,7 @@ func (ss *sessions) getByTheirSubnet(snet *subnet) (*sessionInfo, bool) {
// Creates a new session and lazily cleans up old/timedout existing sessions.
// This includse initializing session info to sane defaults (e.g. lowest supported MTU).
func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo {
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
if ss.sessionFirewallEnabled {
if !ss.isSessionAllowed(theirPermKey, true) {
return nil
@ -268,12 +272,12 @@ func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo {
sinfo := sessionInfo{}
sinfo.core = ss.core
sinfo.theirPermPub = *theirPermKey
pub, priv := newBoxKeys()
pub, priv := crypto.NewBoxKeys()
sinfo.mySesPub = *pub
sinfo.mySesPriv = *priv
sinfo.myNonce = *newBoxNonce()
sinfo.myNonce = *crypto.NewBoxNonce()
sinfo.theirMTU = 1280
sinfo.myMTU = uint16(ss.core.tun.mtu)
sinfo.myMTU = uint16(ss.core.router.tun.mtu)
now := time.Now()
sinfo.time = now
sinfo.mtuTime = now
@ -295,9 +299,9 @@ func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo {
// lower => even nonce
sinfo.myNonce[len(sinfo.myNonce)-1] &= 0xfe
}
sinfo.myHandle = *newHandle()
sinfo.theirAddr = *address_addrForNodeID(getNodeID(&sinfo.theirPermPub))
sinfo.theirSubnet = *address_subnetForNodeID(getNodeID(&sinfo.theirPermPub))
sinfo.myHandle = *crypto.NewHandle()
sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.send = make(chan []byte, 32)
sinfo.recv = make(chan *wire_trafficPacket, 32)
go sinfo.doWorker()
@ -324,32 +328,32 @@ func (ss *sessions) cleanup() {
s.close()
}
}
permShared := make(map[boxPubKey]*boxSharedKey, len(ss.permShared))
permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared))
for k, v := range ss.permShared {
permShared[k] = v
}
ss.permShared = permShared
sinfos := make(map[handle]*sessionInfo, len(ss.sinfos))
sinfos := make(map[crypto.Handle]*sessionInfo, len(ss.sinfos))
for k, v := range ss.sinfos {
sinfos[k] = v
}
ss.sinfos = sinfos
byMySes := make(map[boxPubKey]*handle, len(ss.byMySes))
byMySes := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byMySes))
for k, v := range ss.byMySes {
byMySes[k] = v
}
ss.byMySes = byMySes
byTheirPerm := make(map[boxPubKey]*handle, len(ss.byTheirPerm))
byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm))
for k, v := range ss.byTheirPerm {
byTheirPerm[k] = v
}
ss.byTheirPerm = byTheirPerm
addrToPerm := make(map[address]*boxPubKey, len(ss.addrToPerm))
addrToPerm := make(map[address.Address]*crypto.BoxPubKey, len(ss.addrToPerm))
for k, v := range ss.addrToPerm {
addrToPerm[k] = v
}
ss.addrToPerm = addrToPerm
subnetToPerm := make(map[subnet]*boxPubKey, len(ss.subnetToPerm))
subnetToPerm := make(map[address.Subnet]*crypto.BoxPubKey, len(ss.subnetToPerm))
for k, v := range ss.subnetToPerm {
subnetToPerm[k] = v
}
@ -380,15 +384,15 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing {
Coords: coords,
MTU: sinfo.myMTU,
}
sinfo.myNonce.update()
sinfo.myNonce.Increment()
return ref
}
// Gets the shared key for a pair of box keys.
// Used to cache recently used shared keys for protocol traffic.
// This comes up with dht req/res and session ping/pong traffic.
func (ss *sessions) getSharedKey(myPriv *boxPrivKey,
theirPub *boxPubKey) *boxSharedKey {
func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey,
theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey {
if skey, isIn := ss.permShared[*theirPub]; isIn {
return skey
}
@ -401,7 +405,7 @@ func (ss *sessions) getSharedKey(myPriv *boxPrivKey,
}
delete(ss.permShared, key)
}
ss.permShared[*theirPub] = getSharedKey(myPriv, theirPub)
ss.permShared[*theirPub] = crypto.GetSharedKey(myPriv, theirPub)
return ss.permShared[*theirPub]
}
@ -417,7 +421,7 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
ping.IsPong = isPong
bs := ping.encode()
shared := ss.getSharedKey(&ss.core.boxPriv, &sinfo.theirPermPub)
payload, nonce := boxSeal(shared, bs, nil)
payload, nonce := crypto.BoxSeal(shared, bs, nil)
p := wire_protoTrafficPacket{
Coords: sinfo.coords,
ToKey: sinfo.theirPermPub,
@ -468,24 +472,6 @@ func (ss *sessions) handlePing(ping *sessionPing) {
}
}
// Used to subtract one nonce from another, staying in the range +- 64.
// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask.
// It's basically part of the machinery that prevents replays and duplicate packets.
func (n *boxNonce) minus(m *boxNonce) int64 {
diff := int64(0)
for idx := range n {
diff *= 256
diff += int64(n[idx]) - int64(m[idx])
if diff > 64 {
diff = 64
}
if diff < -64 {
diff = -64
}
}
return diff
}
// 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.
@ -500,9 +486,9 @@ func (sinfo *sessionInfo) getMTU() uint16 {
}
// Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received.
func (sinfo *sessionInfo) nonceIsOK(theirNonce *boxNonce) bool {
func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
// The bitmask is to allow for some non-duplicate out-of-order packets
diff := theirNonce.minus(&sinfo.theirNonce)
diff := theirNonce.Minus(&sinfo.theirNonce)
if diff > 0 {
return true
}
@ -510,10 +496,10 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *boxNonce) bool {
}
// Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce
func (sinfo *sessionInfo) updateNonce(theirNonce *boxNonce) {
func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) {
// Shift nonce mask if needed
// Set bit
diff := theirNonce.minus(&sinfo.theirNonce)
diff := theirNonce.Minus(&sinfo.theirNonce)
if diff > 0 {
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
sinfo.nonceMask <<= uint64(diff)
@ -559,7 +545,7 @@ func (sinfo *sessionInfo) doWorker() {
// This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer.
func (sinfo *sessionInfo) doSend(bs []byte) {
defer util_putBytes(bs)
defer util.PutBytes(bs)
if !sinfo.init {
// To prevent using empty session keys
return
@ -593,8 +579,8 @@ func (sinfo *sessionInfo) doSend(bs []byte) {
coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey
}
// Prepare the payload
payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce)
defer util_putBytes(payload)
payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce)
defer util.PutBytes(payload)
p := wire_trafficPacket{
Coords: coords,
Handle: sinfo.theirHandle,
@ -612,13 +598,13 @@ func (sinfo *sessionInfo) doSend(bs []byte) {
// If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly.
// TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size).
func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
defer util_putBytes(p.Payload)
defer util.PutBytes(p.Payload)
if !sinfo.nonceIsOK(&p.Nonce) {
return
}
bs, isOK := boxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
if !isOK {
util_putBytes(bs)
util.PutBytes(bs)
return
}
sinfo.updateNonce(&p.Nonce)

View File

@ -16,6 +16,9 @@ import (
"sync"
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
const (
@ -30,16 +33,16 @@ const (
// The coords represent a path from the root to a node.
// This path is generally part of a spanning tree, except possibly the last hop (it can loop when sending coords to your parent, but they see this and know not to use a looping path).
type switchLocator struct {
root sigPubKey
root crypto.SigPubKey
tstamp int64
coords []switchPort
}
// Returns true if the first sigPubKey has a higher TreeID.
func firstIsBetter(first, second *sigPubKey) bool {
func firstIsBetter(first, second *crypto.SigPubKey) bool {
// Higher TreeID is better
ftid := getTreeID(first)
stid := getTreeID(second)
ftid := crypto.GetTreeID(first)
stid := crypto.GetTreeID(second)
for idx := 0; idx < len(ftid); idx++ {
if ftid[idx] == stid[idx] {
continue
@ -121,7 +124,7 @@ func (x *switchLocator) isAncestorOf(y *switchLocator) bool {
// Information about a peer, used by the switch to build the tree and eventually make routing decisions.
type peerInfo struct {
key sigPubKey // ID of this peer
key crypto.SigPubKey // ID of this peer
locator switchLocator // Should be able to respond with signatures upon request
degree uint64 // Self-reported degree
time time.Time // Time this node was last seen
@ -159,26 +162,26 @@ type switchData struct {
// All the information stored by the switch.
type switchTable struct {
core *Core
key sigPubKey // Our own key
time time.Time // Time when locator.tstamp was last updated
drop map[sigPubKey]int64 // Tstamp associated with a dropped root
mutex sync.RWMutex // Lock for reads/writes of switchData
parent switchPort // Port of whatever peer is our parent, or self if we're root
data switchData //
updater atomic.Value // *sync.Once
table atomic.Value // lookupTable
packetIn chan []byte // Incoming packets for the worker to handle
idleIn chan switchPort // Incoming idle notifications from peer links
admin chan func() // Pass a lambda for the admin socket to query stuff
queues switch_buffers // Queues - not atomic so ONLY use through admin chan
queueTotalMaxSize uint64 // Maximum combined size of queues
key crypto.SigPubKey // Our own key
time time.Time // Time when locator.tstamp was last updated
drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root
mutex sync.RWMutex // Lock for reads/writes of switchData
parent switchPort // Port of whatever peer is our parent, or self if we're root
data switchData //
updater atomic.Value // *sync.Once
table atomic.Value // lookupTable
packetIn chan []byte // Incoming packets for the worker to handle
idleIn chan switchPort // Incoming idle notifications from peer links
admin chan func() // Pass a lambda for the admin socket to query stuff
queues switch_buffers // Queues - not atomic so ONLY use through admin chan
queueTotalMaxSize uint64 // Maximum combined size of queues
}
// Minimum allowed total size of switch queues.
const SwitchQueueTotalMinSize = 4 * 1024 * 1024
// Initializes the switchTable struct.
func (t *switchTable) init(core *Core, key sigPubKey) {
func (t *switchTable) init(core *Core, key crypto.SigPubKey) {
now := time.Now()
t.core = core
t.key = key
@ -187,7 +190,7 @@ func (t *switchTable) init(core *Core, key sigPubKey) {
t.data = switchData{locator: locator, peers: peers}
t.updater.Store(&sync.Once{})
t.table.Store(lookupTable{})
t.drop = make(map[sigPubKey]int64)
t.drop = make(map[crypto.SigPubKey]int64)
t.packetIn = make(chan []byte, 1024)
t.idleIn = make(chan switchPort, 1024)
t.admin = make(chan func())
@ -302,7 +305,7 @@ func (t *switchTable) cleanDropped() {
// This is exchanged with peers to construct the spanning tree.
// A subset of this information, excluding the signatures, is used to construct locators that are used elsewhere in the code.
type switchMsg struct {
Root sigPubKey
Root crypto.SigPubKey
TStamp int64
Hops []switchMsgHop
}
@ -310,8 +313,8 @@ type switchMsg struct {
// This represents the signed information about the path leading from the root the Next node, via the Port specified here.
type switchMsgHop struct {
Port switchPort
Next sigPubKey
Sig sigBytes
Next crypto.SigPubKey
Sig crypto.SigBytes
}
// This returns a *switchMsg to a copy of this node's current switchMsg, which can safely have additional information appended to Hops and sent to a peer.
@ -690,7 +693,7 @@ func (b *switch_buffers) cleanup(t *switchTable) {
coords := switch_getPacketCoords(packet.bytes)
if t.selfIsClosest(coords) {
for _, packet := range buf.packets {
util_putBytes(packet.bytes)
util.PutBytes(packet.bytes)
}
b.size -= buf.size
delete(b.bufs, streamID)
@ -710,7 +713,7 @@ func (b *switch_buffers) cleanup(t *switchTable) {
packet, buf.packets = buf.packets[0], buf.packets[1:]
buf.size -= uint64(len(packet.bytes))
b.size -= uint64(len(packet.bytes))
util_putBytes(packet.bytes)
util.PutBytes(packet.bytes)
if len(buf.packets) == 0 {
delete(b.bufs, streamID)
} else {

View File

@ -25,20 +25,16 @@ import (
"time"
"golang.org/x/net/proxy"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense
const default_tcp_timeout = 6 * time.Second
const tcp_ping_interval = (default_tcp_timeout * 2 / 3)
// Wrapper function for non tcp/ip connections.
func setNoDelay(c net.Conn, delay bool) {
tcp, ok := c.(*net.TCPConn)
if ok {
tcp.SetNoDelay(delay)
}
}
// The TCP listener and information about active TCP connections, to avoid duplication.
type tcpInterface struct {
core *Core
@ -52,12 +48,24 @@ type tcpInterface struct {
// This is used as the key to a map that tracks existing connections, to prevent multiple connections to the same keys and local/remote address pair from occuring.
// Different address combinations are allowed, so multi-homing is still technically possible (but not necessarily advisable).
type tcpInfo struct {
box boxPubKey
sig sigPubKey
box crypto.BoxPubKey
sig crypto.SigPubKey
localAddr string
remoteAddr string
}
// Wrapper function to set additional options for specific connection types.
func (iface *tcpInterface) setExtraOptions(c net.Conn) {
switch sock := c.(type) {
case *net.TCPConn:
sock.SetNoDelay(true)
sock.SetKeepAlive(true)
sock.SetKeepAlivePeriod(iface.tcp_timeout)
// TODO something for socks5
default:
}
}
// Returns the address of the listener.
func (iface *tcpInterface) getAddr() *net.TCPAddr {
return iface.serv.Addr().(*net.TCPAddr)
@ -205,8 +213,9 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
// It defers a bunch of cleanup stuff to tear down all of these things when the reader exists (e.g. due to a closed connection or a timeout).
func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
defer sock.Close()
iface.setExtraOptions(sock)
// Get our keys
myLinkPub, myLinkPriv := newBoxKeys() // ephemeral link keys
myLinkPub, myLinkPriv := crypto.NewBoxKeys() // ephemeral link keys
meta := version_getBaseMetadata()
meta.box = iface.core.boxPub
meta.sig = iface.core.sigPub
@ -287,7 +296,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
}()
// Note that multiple connections to the same node are allowed
// E.g. over different interfaces
p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String())
p := iface.core.peers.newPeer(&info.box, &info.sig, crypto.GetSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String())
p.linkOut = make(chan []byte, 1)
in := func(bs []byte) {
p.handlePacket(bs)
@ -301,7 +310,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
buf := net.Buffers{tcp_msg[:], msgLen, msg}
buf.WriteTo(sock)
atomic.AddUint64(&p.bytesSent, uint64(len(tcp_msg)+len(msgLen)+len(msg)))
util_putBytes(msg)
util.PutBytes(msg)
}
timerInterval := tcp_ping_interval
timer := time.NewTimer(timerInterval)
@ -342,7 +351,6 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
out <- msg
}
p.close = func() { sock.Close() }
setNoDelay(sock, true)
go p.linkLoop()
defer func() {
// Put all of our cleanup here...
@ -350,8 +358,8 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
}()
us, _, _ := net.SplitHostPort(sock.LocalAddr().String())
them, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
themNodeID := getNodeID(&info.box)
themAddr := address_addrForNodeID(themNodeID)
themNodeID := crypto.GetNodeID(&info.box)
themAddr := address.AddrForNodeID(themNodeID)
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, them)
iface.core.log.Println("Connected:", themString, "source", us)
@ -386,9 +394,9 @@ func (iface *tcpInterface) reader(sock net.Conn, in func([]byte)) error {
// We didn't get the whole message yet
break
}
newMsg := append(util_getBytes(), msg...)
newMsg := append(util.GetBytes(), msg...)
in(newMsg)
util_yield()
util.Yield()
}
frag = append(bs[:0], frag...)
}

View File

@ -5,25 +5,28 @@ package yggdrasil
import (
"bytes"
"errors"
"sync"
"time"
"github.com/songgao/packets/ethernet"
"github.com/yggdrasil-network/water"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
const tun_IPv6_HEADER_LENGTH = 40
const tun_ETHER_HEADER_LENGTH = 14
// Represents a running TUN/TAP interface.
type tunDevice struct {
core *Core
type tunAdapter struct {
Adapter
icmpv6 icmpv6
send chan<- []byte
recv <-chan []byte
mtu int
iface *water.Interface
mutex sync.RWMutex // Protects the below
isOpen bool
}
// Gets the maximum supported MTU for the platform based on the defaults in
@ -36,22 +39,25 @@ func getSupportedMTU(mtu int) int {
}
// Initialises the TUN/TAP adapter.
func (tun *tunDevice) init(core *Core) {
tun.core = core
func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
tun.Adapter.init(core, send, recv)
tun.icmpv6.init(tun)
}
// Starts the setup process for the TUN/TAP adapter, and if successful, starts
// the read/write goroutines to handle packets on that interface.
func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int) error {
if ifname == "none" {
return nil
}
if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil {
return err
}
go func() { panic(tun.read()) }()
go func() { panic(tun.write()) }()
tun.mutex.Lock()
tun.isOpen = true
tun.mutex.Unlock()
go func() { tun.core.log.Println("WARNING: tun.read() exited with error:", tun.read()) }()
go func() { tun.core.log.Println("WARNING: tun.write() exited with error:", tun.write()) }()
if iftapmode {
go func() {
for {
@ -75,14 +81,14 @@ func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int)
// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP
// mode then additional ethernet encapsulation is added for the benefit of the
// host operating system.
func (tun *tunDevice) write() error {
func (tun *tunAdapter) write() error {
for {
data := <-tun.recv
if tun.iface == nil {
continue
}
if tun.iface.IsTAP() {
var destAddr address
var destAddr address.Address
if data[0]&0xf0 == 0x60 {
if len(data) < 40 {
panic("Tried to send a packet shorter than an IPv6 header...")
@ -96,7 +102,7 @@ func (tun *tunDevice) write() error {
} else {
return errors.New("Invalid address family")
}
sendndp := func(destAddr address) {
sendndp := func(destAddr address.Address) {
neigh, known := tun.icmpv6.peermacs[destAddr]
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
if !known {
@ -148,15 +154,29 @@ func (tun *tunDevice) write() error {
len(data)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], data[:])
if _, err := tun.iface.Write(frame); err != nil {
panic(err)
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
panic(err)
}
}
}
} else {
if _, err := tun.iface.Write(data); err != nil {
panic(err)
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
panic(err)
}
}
}
util_putBytes(data)
util.PutBytes(data)
}
}
@ -164,7 +184,7 @@ func (tun *tunDevice) write() error {
// is running in TAP mode then the ethernet headers will automatically be
// processed and stripped if necessary. If an ICMPv6 packet is found, then
// the relevant helper functions in icmpv6.go are called.
func (tun *tunDevice) read() error {
func (tun *tunAdapter) read() error {
mtu := tun.mtu
if tun.iface.IsTAP() {
mtu += tun_ETHER_HEADER_LENGTH
@ -173,8 +193,15 @@ func (tun *tunDevice) read() error {
for {
n, err := tun.iface.Read(buf)
if err != nil {
// panic(err)
return err
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
// panic(err)
return err
}
}
o := 0
if tun.iface.IsTAP() {
@ -193,7 +220,7 @@ func (tun *tunDevice) read() error {
// tun.icmpv6.recv <- b
go tun.icmpv6.parse_packet(b)
}
packet := append(util_getBytes(), buf[o:n]...)
packet := append(util.GetBytes(), buf[o:n]...)
tun.send <- packet
}
}
@ -201,7 +228,10 @@ func (tun *tunDevice) read() error {
// Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil
// process stops. Typically this operation will happen quickly, but on macOS
// it can block until a read operation is completed.
func (tun *tunDevice) close() error {
func (tun *tunAdapter) close() error {
tun.mutex.Lock()
tun.isOpen = false
tun.mutex.Unlock()
if tun.iface == nil {
return nil
}

View File

@ -77,7 +77,7 @@ type in6_ifreq_lifetime struct {
// 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 *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if ifname[:4] == "auto" {
ifname = "/dev/tap0"
@ -103,7 +103,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int)
return tun.setupAddress(addr)
}
func (tun *tunDevice) setupAddress(addr string) error {
func (tun *tunAdapter) setupAddress(addr string) error {
var sfd int
var err error

View File

@ -14,7 +14,7 @@ import (
)
// Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if iftapmode {
tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN")
}
@ -62,7 +62,7 @@ type ifreq struct {
// Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using
// a system socket and making direct syscalls to the kernel.
func (tun *tunDevice) setupAddress(addr string) error {
func (tun *tunAdapter) setupAddress(addr string) error {
var fd int
var err error

View File

@ -13,7 +13,7 @@ import (
)
// Configures the TAP adapter with the correct IPv6 address and MTU.
func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
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}
@ -48,7 +48,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int)
// is used to do this, so there is not a hard requirement on "ip" or "ifconfig"
// to exist on the system, but this will fail if Netlink is not present in the
// kernel (it nearly always is).
func (tun *tunDevice) setupAddress(addr string) error {
func (tun *tunAdapter) setupAddress(addr string) error {
// Set address
var netIF *net.Interface
ifces, err := net.Interfaces()

View File

@ -9,7 +9,7 @@ import water "github.com/yggdrasil-network/water"
// 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 *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
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}
@ -27,7 +27,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int)
// 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 *tunDevice) setupAddress(addr string) error {
func (tun *tunAdapter) setupAddress(addr string) error {
tun.core.log.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
return nil
}

View File

@ -13,7 +13,7 @@ import (
// 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 *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if !iftapmode {
tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP")
}
@ -65,7 +65,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int)
}
// Sets the MTU of the TAP adapter.
func (tun *tunDevice) setupMTU(mtu int) error {
func (tun *tunAdapter) setupMTU(mtu int) error {
// Set MTU
cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface",
fmt.Sprintf("interface=%s", tun.iface.Name()),
@ -82,7 +82,7 @@ func (tun *tunDevice) setupMTU(mtu int) error {
}
// Sets the IPv6 address of the TAP adapter.
func (tun *tunDevice) setupAddress(addr string) error {
func (tun *tunAdapter) setupAddress(addr string) error {
// Set address
cmd := exec.Command("netsh", "interface", "ipv6", "add", "address",
fmt.Sprintf("interface=%s", tun.iface.Name()),

View File

@ -4,6 +4,8 @@ package yggdrasil
// Used in the inital 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.
@ -12,9 +14,9 @@ type version_metadata struct {
ver uint64 // 1 byte in this version
// Everything after this point potentially depends on the version number, and is subject to change in future versions
minorVer uint64 // 1 byte in this version
box boxPubKey
sig sigPubKey
link boxPubKey
box crypto.BoxPubKey
sig crypto.SigPubKey
link crypto.BoxPubKey
}
// Gets a base metadata with no keys set, but with the correct version numbers.
@ -28,12 +30,12 @@ func version_getBaseMetadata() version_metadata {
// Gest the length of the metadata for this version, used to know how many bytes to read from the start of a connection.
func version_getMetaLength() (mlen int) {
mlen += 4 // meta
mlen += 1 // ver, as long as it's < 127, which it is in this version
mlen += 1 // minorVer, as long as it's < 127, which it is in this version
mlen += boxPubKeyLen // box
mlen += sigPubKeyLen // sig
mlen += boxPubKeyLen // link
mlen += 4 // meta
mlen += 1 // ver, as long as it's < 127, which it is in this version
mlen += 1 // minorVer, as long as it's < 127, which it is in this version
mlen += crypto.BoxPubKeyLen // box
mlen += crypto.SigPubKeyLen // sig
mlen += crypto.BoxPubKeyLen // link
return
}

View File

@ -7,6 +7,11 @@ package yggdrasil
// Packet types, as wire_encode_uint64(type) at the start of each packet
import (
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
const (
wire_Traffic = iota // data being routed somewhere, handle for crypto
wire_ProtocolTraffic // protocol traffic, pub keys for crypto
@ -16,6 +21,8 @@ const (
wire_SessionPong // inside protocol traffic header
wire_DHTLookupRequest // inside protocol traffic header
wire_DHTLookupResponse // inside protocol traffic header
wire_NodeInfoRequest // inside protocol traffic header
wire_NodeInfoResponse // inside protocol traffic header
)
// Calls wire_put_uint64 on a nil slice.
@ -191,14 +198,14 @@ func wire_chop_uint64(toUInt64 *uint64, fromSlice *[]byte) bool {
// The wire format for ordinary IPv6 traffic encapsulated by the network.
type wire_trafficPacket struct {
Coords []byte
Handle handle
Nonce boxNonce
Handle crypto.Handle
Nonce crypto.BoxNonce
Payload []byte
}
// Encodes a wire_trafficPacket into its wire format.
func (p *wire_trafficPacket) encode() []byte {
bs := util_getBytes()
bs := util.GetBytes()
bs = wire_put_uint64(wire_Traffic, bs)
bs = wire_put_coords(p.Coords, bs)
bs = append(bs, p.Handle[:]...)
@ -222,16 +229,16 @@ func (p *wire_trafficPacket) decode(bs []byte) bool {
case !wire_chop_slice(p.Nonce[:], &bs):
return false
}
p.Payload = append(util_getBytes(), bs...)
p.Payload = append(util.GetBytes(), bs...)
return true
}
// The wire format for protocol traffic, such as dht req/res or session ping/pong packets.
type wire_protoTrafficPacket struct {
Coords []byte
ToKey boxPubKey
FromKey boxPubKey
Nonce boxNonce
ToKey crypto.BoxPubKey
FromKey crypto.BoxPubKey
Nonce crypto.BoxNonce
Payload []byte
}
@ -273,7 +280,7 @@ func (p *wire_protoTrafficPacket) decode(bs []byte) bool {
// The keys themselves are exchanged as part of the connection setup, and then omitted from the packets.
// The two layer logic is handled in peers.go, but it's kind of ugly.
type wire_linkProtoTrafficPacket struct {
Nonce boxNonce
Nonce crypto.BoxNonce
Payload []byte
}
@ -353,6 +360,47 @@ func (p *sessionPing) decode(bs []byte) bool {
////////////////////////////////////////////////////////////////////////////////
// Encodes a nodeinfoReqRes into its wire format.
func (p *nodeinfoReqRes) encode() []byte {
var pTypeVal uint64
if p.IsResponse {
pTypeVal = wire_NodeInfoResponse
} else {
pTypeVal = wire_NodeInfoRequest
}
bs := wire_encode_uint64(pTypeVal)
bs = wire_put_coords(p.SendCoords, bs)
if pTypeVal == wire_NodeInfoResponse {
bs = append(bs, p.NodeInfo...)
}
return bs
}
// Decodes an encoded nodeinfoReqRes into the struct, returning true if successful.
func (p *nodeinfoReqRes) decode(bs []byte) bool {
var pType uint64
switch {
case !wire_chop_uint64(&pType, &bs):
return false
case pType != wire_NodeInfoRequest && pType != wire_NodeInfoResponse:
return false
case !wire_chop_coords(&p.SendCoords, &bs):
return false
}
if p.IsResponse = pType == wire_NodeInfoResponse; p.IsResponse {
if len(bs) == 0 {
return false
}
p.NodeInfo = make(nodeinfoPayload, len(bs))
if !wire_chop_slice(p.NodeInfo[:], &bs) {
return false
}
}
return true
}
////////////////////////////////////////////////////////////////////////////////
// Encodes a dhtReq into its wire format.
func (r *dhtReq) encode() []byte {
coords := wire_encode_coords(r.Coords)