5
0
mirror of https://github.com/cwinfo/yggdrasil-network.github.io.git synced 2024-11-09 15:40:27 +00:00

Merge pull request #11 from yggdrasil-network/about

Rewrite about.md
This commit is contained in:
Neil Alexander 2018-08-08 07:00:22 +01:00 committed by GitHub
commit c02789e8a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 120 additions and 88 deletions

202
about.md
View File

@ -4,117 +4,143 @@ sitemap: true
# About
Yggdrasil is an encrypted IPv6 network running in the [`200::/7` address range](https://en.wikipedia.org/wiki/Unique_local_address).
It is an experimental/toy network, so failure is acceptable, as long as it's instructive to see how it breaks if/when everything falls apart.
Yggdrasil is a proof-of-concept to explore a wholly different approach to
network routing. Whereas current computer networks depend heavily on very
centralised design and configuration, Yggdrasil breaks this mould by making use
of a global spanning tree to form a scalable IPv6 encrypted mesh network.
IP addresses are derived from cryptographic keys, to reduce the need for public key infrastructure.
A form of locator/identifier separation (similar in goal to [LISP](https://en.wikipedia.org/wiki/Locator/Identifier_Separation_Protocol)) is used to map static identifiers (IP addresses) onto dynamic routing information (locators), using a [distributed hash table](https://en.wikipedia.org/wiki/Distributed_hash_table) (DHT).
Locators are used to approximate the distance between nodes in the network, where the approximate distance is the length of a real worst-case-scenario path through the network.
This is (arguably) easier to secure and requires less information about the network than commonly used routing schemes.
The following table illustrates high-level differences between traditional
networks like the internet, and the Yggdrasil network:
While not technically a [compact routing scheme](https://arxiv.org/abs/0708.2309), tests on real-world networks suggest that routing in this style incurs stretch comparable to the name-dependent compact routing schemes designed for static networks.
Compared to compact routing schemes, Yggdrasil appears to have smaller average routing table sizes, works on dynamic networks, and is name-independent.
It currently lacks the provable bounds of compact routing schemes, and there's a serious argument to be made that it cheats by stretching the definition of some of the above terms, but the main point to be emphasized is that *we're not looking for formal proofs and definitions, we just want something efficient in real networks*.
In that sense, Yggdrasil seems to be competitive on paper, and working well in practice so far.
| | Traditional | Yggdrasil |
| --------------------------------------------------------------- | ----------- | --------- |
| End-to-end encryption for all traffic across the network | No | Yes |
| Decentralised routing information shared using a DHT | No | Yes |
| Cryptographically-bound IPv6 addresses | No | Yes |
| Node is aware of its relative location to other nodes | No | Yes |
| IPv6 address remains with the device even if moved | No | Yes |
| Topology extends gracefully across different mediums, i.e. mesh | No | Yes |
## Addressing and NodeIDs
### What are the problems today?
Yggdrasil uses a truncated version of a NodeID to assign addresses.
An address is assigned from the `200::/7` prefix, according to the following:
The internet as we know it today doesn't conform to a well-defined topology.
This has largely happened over time - as the internet has grown, more and more
networks have been "bolted together". The lack of defined topology gives us some
unavoidable problems:
1. Begin with `0x02` as the first byte of the address, or `0x03` if it's a `/64` prefix.
2. Count the number of leading `1` bits in the NodeID.
3. Set the second byte of the address to the number of leading `1` bits in the NodeID (8 bit unsigned integer, at most 255).
4. Append the NodeID to the remaining bits of the address, truncating the leading `1` bits and the first `0` bit, to a total address size of 128 bits.
1. The routing tables that hold a "map" of the internet are huge and inefficient
1. There isn't really any way for a computer to know where it is located on the
internet relative to anything else
1. It's difficult to examine where a packet will go on its journey from source
to destination without actually sending it
1. It's very difficult to install reliable networks into locations that change
often or are non-static, i.e. wireless mesh networks
The last bit of the first byte is used to flag if an address is for a router (`200::/8`), or part of an advertised prefix (`300::/8`), where each router owns a `/64` that matches their address (except with the eight bit set to 1 instead of 0).
This allows the prefix to be advertised to the router's LAN, so unsupported devices can still connect to the network (e.g. network printers).
These problems have been partially mitigated (but not really
solved) through centralisation - rather than your computers at home holding a
copy of the global routing table, your ISP does it for you. Your computers and
network devices are configured just to "send it upstream" and to let your ISP
decide where it goes from there, but this does leave you entirely at the mercy
of your ISP who can redirect your traffic anywhere they like and to inspect,
manipulate or intercept it.
The NodeID is a [sha512sum](https://en.wikipedia.org/wiki/SHA-512) of a node's public encryption key.
Addresses are checked that they match NodeID, to prevent address spoofing.
As such, while a 128 bit IPv6 address is likely too short to be considered secure by cryptographer standards, there is a significant cost in attempting to cause an address collision.
Addresses can be made more secure by brute force generating a large number of leading `1` bits in the NodeID.
In addition, wireless meshing requires you to know a lot about the network
around you, which would not typically be the case when you have outsourced this
knowledge to your ISP. Many existing wireless mesh routing schemes are not
scalable or efficient, and do not bridge well with existing networks.
### Cryptography
### What does Yggdrasil do differently?
Public key encryption is done using the `golang.org/x/crypto/nacl/box`, which uses [Curve25519](https://en.wikipedia.org/wiki/Curve25519), [XSalsa20](https://en.wikipedia.org/wiki/Salsa20), and [Poly1305](https://en.wikipedia.org/wiki/Poly1305) for key exchange, encryption, and authentication (interoperable with [NaCl](https://en.wikipedia.org/wiki/NaCl_(software))).
Permanent keys are used only for protocol traffic, with random nonces generated on a per-packet basis using `crypto/rand` from Go's standard library.
Ephemeral session keys (for [forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy)) are generated for encapsulated IPv6 traffic, using the same set of primitives, with random initial nonces that are subsequently incremented.
A list of recently received session nonces is kept (as a bitmask) and checked to reject duplicated packets, in an effort to block duplicate packets and replay attacks.
A separate set of keys are generated and used for signing with [Ed25519](https://en.wikipedia.org/wiki/Ed25519), which is used by the routing layer to secure construction of a spanning tree.
Yggdrasil takes a very different approach. Rather than just accepting that the
internet is a sprawling conglomeration of unstructured networks connected
together in an unstructured fashion, we instead arrange the routing scheme of
the entire network into a global spanning tree.
## Locators and Routing
A spanning tree has the following properties:
Locators are generated using information from a spanning tree (described below).
The result is that each node has a set of [coordinates in a greedy metric space](https://en.wikipedia.org/wiki/Greedy_embedding).
These coordinates are used as a distance label.
Given the coordinates of any two nodes, it is possible to calculate the length of some real path through the network between the two nodes.
Traffic is forwarded using a [greedy routing](https://en.wikipedia.org/wiki/Small-world_routing#Greedy_routing) scheme, where each node forwards the packet to a one-hop neighbor that is closer to the destination (according to this distance metric) than the current node.
In particular, when a packet needs to be forward, a node will forward it to whatever peer is closest to the destination in the greedy [metric space](https://en.wikipedia.org/wiki/Metric_space) used by the network, provided that the peer is closer to the destination than the current node.
If no closer peers are idle, then the packet is queued in FIFO order, with separate queues per destination+crypto-session (so traffic from different nodes, to the same destination, is tracked separately).
Whenever the node finishes forwarding a packet to a peer, it checks the queues, and will forward the first packet from the queue with the maximum `<age of first packet>/<queue size in bytes>`, subject to the constraint that the peer is a valid next hop (i.e. closer to the destination than the current node).
If no non-empty queue is available, then the peer is added to the idle set, forward packets when the need arises.
This acts as a crude approximation of backpressure routing, where the remote queue sizes are assumed to be equal to the distance of a node from a destination (rather than communicating queue size information), and packets are never forwarded "backwards" through the network.
The queue selection strategy grants a larger fration of available bandwith to sessions that attempt to use less bandwidth, loosely based on the rationale behind some proposed solutions to the [cake-cutting](https://en.wikipedia.org/wiki/Fair_cake-cutting) problem.
The queue size is limited to 4 MB. If a packet is added to a queue and the total size of all queues is larger than this threshold, then a random queue is selected (with odds proportional to relative queue sizes), and the first packet from that queue is dropped, with the process repeated until the total queue size drops below the allowed threshold.
1. There is always a single "root" node at the "top" of the tree
1. Every other node in the spanning tree has:
- Exactly one parent
- One or more children
1. Every node is connected to at least one other node
### Spanning Tree
By arranging every device in such a way that they all agree on the same global
spanning tree, we can do some things that we previously couldn't do before:
A [spanning tree](https://en.wikipedia.org/wiki/Spanning_tree) is constructed with the tree rooted at the highest TreeID, where TreeID is equal to a sha512sum of a node's public [Ed25519](https://en.wikipedia.org/wiki/Ed25519) key (used for signing).
A node sends periodic advertisement messages to each neighbor.
The advertisement contains the coords that match the path from the root through the node, plus one additional hop from the node to the neighbor being advertised to.
Each hop in this advertisement includes a matching ed25519 signature.
These signatures prevent nodes from forging arbitrary routing advertisements.
1. We can assign a "locator" to a device, effectively a path from the root of
the tree downwards
1. We can determine where our destination is on the tree up front relative to
the source
1. We always have a single worst-case-scenario path to every node by
"walking the tree"
1. We have the ability to infer a number of possible routes (both direct and
indirect) from the structure of the tree, and even take "shortcuts"
1. We can spread out information about the tree itself across nodes and not need
to store it all centrally
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).
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.
By using a distributed hash table (DHT) to share routing information, this
allows any single device on the network to find out enough information to route
to another node, without depending on centralised infrastructure.
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).
Cryptographic signatures are used when exchanging routing information between
peers, such that the network can agree on the same candidate to be the global
root and to lay out the coordinates of each node beneath it.
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.
The locator then consists of a root's key, timestamp, and coordinates representing each hop in the path from the root to the node.
In practice, only the coords are used for routing, while the root and timestamp, along with all the per-hop signatures, are needed to securely construct the spanning tree.
### What are the benefits?
## Distributed Hash Table
It should be efficient for the following reasons:
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).
1. It lets every device on the network make the same assumptions about the
topology of the network
1. The use of locators for sending traffic across the network simplifies the
switching layer, as forwarding does not require nodes to maintain state tables
of anything beyond their own peers
1. All route determination is automatic - no manual configuration of routes is
required
1. Devices don't need to store lots of information about the topology of the
network - in fact, storing information about only a small number of nodes is
usually enough to reach the entire network
1. We can bridge reliable/static networks very easily with dynamic/non-static
networks without flooding large amounts of routing information between different
areas (i.e. point-to-point or mesh wireless networks)
1. The network responds gracefully to changes in topology without intervention,
and networks can even join and split without interrupting connectivity between
local nodes
It differs from kademlia in that there are no values in the key:value store--it only stores information about DHT peers.
Furthermore, the network bootstraps by adding one-hop neighbors to the DHT, and (for subtle reasons) this strategy does not behave the same as bootstrapping off of a dedicated node.
To bootstrap successfully, when a bucket is full, old nodes (the least recently pinged) are removed to make room for new nodes.
We believe that Yggdrasil should be able to scale to very large networks as a
result.
To summarize the entire procedure, given only a node's IP address, the goal is to find a route to the destination.
That happens through 3 steps:
### What is the status of the project?
1. The address is unpacked into the known bits of a NodeID and a bitmask to signal which bits of the NodeID are known (the unknown bits are ignored).
2. A DHT search is performed, which normally results in a response from the node closest in the DHT keyspace to the target NodeID. The response contains the node's curve25519 key, which is checked to match the NodeID (and therefore the address), as well as the node's coordinates.
The NodeID is checked to make sure it matches the target IPv6 address.
3. Using the keys and coords from the above step, an ephemeral key exchange occurs between the source and destination nodes. These ephemeral session keys are used to encrypt any ordinary IPv6 traffic that may be encapsulated and sent between the nodes.
Yggdrasil is currently an alpha project, early in development but actively
maintained. Our expectation is that a future “beta” quality release should know
enough to be compatible in the face of wire format changes, and reasonably
feature complete. A “stable” 1.0 release, if it ever happens, would probably be
feature complete, with no expectation of future wire format changes, and free of
known critical bugs.
## Project Status and Plans
The true goal of this project is to test the scalability of the Yggdrasil
routing scheme, therefore it needs as many participants to run and test the
software as possible so that we can study the behaviour of the network as it
grows. We've done our best to support [as many platforms as possible](platforms.md)
and have a number of [public peers](https://github.com/yggdrasil-network/public-peers)
that you can connect to in order to join the network, so please feel free to
experiment.
The current (Go) implementation is pre-alpha, so compatibility with future versions is neither guaranteed nor expected.
While users are discouraged from running anything truly critical on top of it, as of writing, it seems reliable enough for day-to-day use.
The project is likely to reach a number of possible outcomes:
An "alpha" quality release should at least include the ability to detect incompatible versions and warn the users that an update may be needed.
A "beta" quality release should know enough to be compatible in the face of wire format changes, and reasonably feature complete.
A "stable" 1.0 release, if it ever happens, would probably be feature complete, with no expectation of future wire format changes, and free of known critical bugs.
1. The project may reach a reasonably stable state but never attract a large
enough number of users
1. The project may attract a large enough number of users but reveal inherent
design flaws in the process (a learning exercise for a future project perhaps)
1. The project may end up working perfectly even as the network grows, in which
case it will become worthwhile to look at writing better-optimised
implementations and/or moving the important parts into other projects (like
[cjdns](https://github.com/cjdelisle/cjdns))
Roughly speaking, there are a few obvious ways the project could turn out:
### Where can I find more technical details?
1. The developer(s) could lose interest before it goes anywhere.
2. The project could be reasonably complete (beta or stable), but never gain a significant number of users.
3. The network may grow large enough that fundamental (non-fixable) design problems appear, which is hopefully a learning experience, but the project may die as a result.
4. The network may grow large, but never hit any design problems, in which case we need to think about either moving the important parts into other projects ([cjdns](https://github.com/cjdelisle/cjdns)) or rewriting compatible implementations that are better optimized for the target platforms (e.g. a linux kernel module).
That last one is probably impossible, because the speed of light would *eventually* become a problem.
If the only thing limiting network growth turns out to be the underlying physics, then that arguably counts as a win.
Take a look at the [Whitepaper](https://github.com/yggdrasil-network/yggdrasil-go/blob/master/doc/Whitepaper.md)
for a technical deep-dive on how Yggdrasil works, including information about
the name-dependent and name-independent routing components, cryptography and the
behaviour of the distributed hash table (DHT).

6
faq.md
View File

@ -100,3 +100,9 @@ Windows, by default, will classify the TAP adapter as a "Public Network". Config
#### macOS (with built-in firewall)
macOS has an application firewall, therefore any firewall policies applied on other interfaces will also apply to the Yggdrasil interface.
### Is there any benefit to being the "root node" of the network?
No. At worst, the root node may be used in worst-case-scenario paths between
other nodes in the absence of being able to determine better routes, but this
is not advantageous.