From e13657d2cad33cec5a58a6ebb9d31c407b5047a0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 18 Apr 2022 15:27:47 +0100 Subject: [PATCH 01/40] Version 0.4.4 changelog --- CHANGELOG.md | 193 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 158 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9c7e471..498d8338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) @@ -25,89 +26,114 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.4.4] - 2022-04-18 + +### Fixed + +- ICMPv6 "Packet Too Big" payload size has been increased, which should fix Path MTU Discovery (PMTUD) when two nodes have different `IfMTU` values configured + +### Changed + +- Go 1.17 is now required + ## [0.4.3] - 2022-02-06 + ### Added + - `bytes_sent`, `bytes_recvd` and `uptime` have been added to `getPeers` - Clearer logging when connections are rejected due to incompatible peer versions ### Fixed + - Latency-based parent selection tiebreak is now reliable on platforms even with low timer resolution - Tree distance calculation offsets have been corrected ## [0.4.2] - 2021-11-03 + ### Fixed + - Reverted a dependency update which resulted in problems building with Go 1.16 and running on Windows ## [0.4.1] - 2021-11-03 + ### Added + - TLS peerings now support Server Name Indication (SNI) - - The SNI is sent automatically if the peering URI contains a DNS name - - A custom SNI can be specified by adding the `?sni=domain.com` parameter to the peering URI + - The SNI is sent automatically if the peering URI contains a DNS name + - A custom SNI can be specified by adding the `?sni=domain.com` parameter to the peering URI - A new `ipv6rwc` API package now implements the IPv6-specific logic separate from the `tun` package ### Fixed + - A crash when calculating the partial public key for very high IPv6 addresses has been fixed - A crash due to a concurrent map write has been fixed - A crash due to missing TUN configuration has been fixed - A race condition in the keystore code has been fixed ## [0.4.0] - 2021-07-04 + ### Added + - New routing scheme, which is backwards incompatible with previous versions of Yggdrasil - - The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4 - - Nodes running this new version **will not** be able to peer with earlier versions of Yggdrasil - - Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release + - The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4 + - Nodes running this new version **will not** be able to peer with earlier versions of Yggdrasil + - Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release - TLS connections now use public key pinning - - If no public key was already pinned, then the public key received as part of the TLS handshake is pinned to the connection - - The public key received as part of the handshake is checked against the pinned keys, and if no match is found, the connection is rejected + - If no public key was already pinned, then the public key received as part of the TLS handshake is pinned to the connection + - The public key received as part of the handshake is checked against the pinned keys, and if no match is found, the connection is rejected ### Changed + - IP addresses are now derived from ed25519 public (signing) keys - - Previously, addresses were derived from a hash of X25519 (Diffie-Hellman) keys - - Importantly, this means that **all internal IPv6 addresses will change with this release** — this will affect anyone running public services or relying on Yggdrasil for remote access + - Previously, addresses were derived from a hash of X25519 (Diffie-Hellman) keys + - Importantly, this means that **all internal IPv6 addresses will change with this release** — this will affect anyone running public services or relying on Yggdrasil for remote access - It is now recommended to peer over TLS - - Link-local peers from multicast peer discovery will now connect over TLS, with the key from the multicast beacon pinned to the connection - - `socks://` peers now expect the destination endpoint to be a `tls://` listener, instead of a `tcp://` listener + - Link-local peers from multicast peer discovery will now connect over TLS, with the key from the multicast beacon pinned to the connection + - `socks://` peers now expect the destination endpoint to be a `tls://` listener, instead of a `tcp://` listener - Multicast peer discovery is now more configurable - - There are separate configuration options to control if beacons are sent, what port to listen on for incoming connections (if sending beacons), and whether or not to listen for beacons from other nodes (and open connections when receiving a beacon) - - Each configuration entry in the list specifies a regular expression to match against interface names - - If an interface matches multiple regex in the list, it will use the settings for the first entry in the list that it matches with + - There are separate configuration options to control if beacons are sent, what port to listen on for incoming connections (if sending beacons), and whether or not to listen for beacons from other nodes (and open connections when receiving a beacon) + - Each configuration entry in the list specifies a regular expression to match against interface names + - If an interface matches multiple regex in the list, it will use the settings for the first entry in the list that it matches with - The session and routing code has been entirely redesigned and rewritten - - This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base — please bear with us for these next few releases as we work through any bugs or issues - - Generally speaking, we expect to see reduced bandwidth use and improved reliability with the new design, especially in cases where nodes move around or change peerings frequently - - Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (currently rotated at least once per round trip exchange of traffic, subject to change in future releases) - - Source routing has been added. Under normal circumstances, this is what is used to forward session traffic (e.g. the user's IPv6 traffic) - - DHT-based routing has been added. This is used when the sender does not know a source route to the destination. Forwarding through the DHT is less efficient, but the only information that it requires the sender to know is the destination node's (static) key. This is primarily used during the key exchange at session setup, or as a temporary fallback when a source route fails due to changes in the network - - The new DHT design is no longer RPC-based, does not support crawling and does not inherently allow nodes to look up the owner of an arbitrary key. Responding to lookups is now implemented at the application level and a response is only sent if the destination key matches the node's `/128` IP or `/64` prefix - - The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) - - The routing logic now lives in a [standalone library](https://github.com/Arceliar/ironwood). You are encouraged **not** to use it, as it's still considered pre-alpha, but it's available for those who want to experiment with the new routing algorithm in other contexts - - Session MTUs may be slightly lower now, in order to accommodate large packet headers if required + - This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base — please bear with us for these next few releases as we work through any bugs or issues + - Generally speaking, we expect to see reduced bandwidth use and improved reliability with the new design, especially in cases where nodes move around or change peerings frequently + - Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (currently rotated at least once per round trip exchange of traffic, subject to change in future releases) + - Source routing has been added. Under normal circumstances, this is what is used to forward session traffic (e.g. the user's IPv6 traffic) + - DHT-based routing has been added. This is used when the sender does not know a source route to the destination. Forwarding through the DHT is less efficient, but the only information that it requires the sender to know is the destination node's (static) key. This is primarily used during the key exchange at session setup, or as a temporary fallback when a source route fails due to changes in the network + - The new DHT design is no longer RPC-based, does not support crawling and does not inherently allow nodes to look up the owner of an arbitrary key. Responding to lookups is now implemented at the application level and a response is only sent if the destination key matches the node's `/128` IP or `/64` prefix + - The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) + - The routing logic now lives in a [standalone library](https://github.com/Arceliar/ironwood). You are encouraged **not** to use it, as it's still considered pre-alpha, but it's available for those who want to experiment with the new routing algorithm in other contexts + - Session MTUs may be slightly lower now, in order to accommodate large packet headers if required - Many of the admin functions available over `yggdrasilctl` have been changed or removed as part of rewrites to the code - - Several remote `debug` functions have been added temporarily, to allow for crawling and census gathering during the transition to the new version, but we intend to remove this at some point in the (possibly distant) future - - The list of available functions will likely be expanded in future releases + - Several remote `debug` functions have been added temporarily, to allow for crawling and census gathering during the transition to the new version, but we intend to remove this at some point in the (possibly distant) future + - The list of available functions will likely be expanded in future releases - The configuration file format has been updated in response to the changed/removed features ### Removed + - Tunnel routing (a.k.a. crypto-key routing or "CKR") has been removed - - It was far too easy to accidentally break routing altogether by capturing the route to peers with the TUN adapter - - We recommend tunnelling an existing standard over Yggdrasil instead (e.g. `ip6gre`, `ip6gretap` or other similar encapsulations, using Yggdrasil IPv6 addresses as the tunnel endpoints) - - All `TunnelRouting` configuration options will no longer take effect + - It was far too easy to accidentally break routing altogether by capturing the route to peers with the TUN adapter + - We recommend tunnelling an existing standard over Yggdrasil instead (e.g. `ip6gre`, `ip6gretap` or other similar encapsulations, using Yggdrasil IPv6 addresses as the tunnel endpoints) + - All `TunnelRouting` configuration options will no longer take effect - Session firewall has been removed - - This was never a true firewall — it didn't behave like a stateful IP firewall, often allowed return traffic unexpectedly and was simply a way to prevent a node from being flooded with unwanted sessions, so the name could be misleading and usually lead to a false sense of security - - Due to design changes, the new code needs to address the possible memory exhaustion attacks in other ways and a single configurable list no longer makes sense - - Users who want a firewall or other packet filter mechansim should configure something supported by their OS instead (e.g. `ip6tables`) - - All `SessionFirewall` configuration options will no longer take effect + - This was never a true firewall — it didn't behave like a stateful IP firewall, often allowed return traffic unexpectedly and was simply a way to prevent a node from being flooded with unwanted sessions, so the name could be misleading and usually lead to a false sense of security + - Due to design changes, the new code needs to address the possible memory exhaustion attacks in other ways and a single configurable list no longer makes sense + - Users who want a firewall or other packet filter mechansim should configure something supported by their OS instead (e.g. `ip6tables`) + - All `SessionFirewall` configuration options will no longer take effect - `SIGHUP` handling to reload the configuration at runtime has been removed - - It was not obvious which parts of the configuration could be reloaded at runtime, and which required the application to be killed and restarted to take effect - - Reloading the config without restarting was also a delicate and bug-prone process, and was distracting from more important developments - - `SIGHUP` will be handled normally (i.e. by exiting) + - It was not obvious which parts of the configuration could be reloaded at runtime, and which required the application to be killed and restarted to take effect + - Reloading the config without restarting was also a delicate and bug-prone process, and was distracting from more important developments + - `SIGHUP` will be handled normally (i.e. by exiting) - `cmd/yggrasilsim` has been removed, and is unlikely to return to this repository ## [0.3.16] - 2021-03-18 + ### Added + - New simulation code under `cmd/yggdrasilsim` (work-in-progress) ### Changed + - Multi-threading in the switch - Swich lookups happen independently for each (incoming) peer connection, instead of being funneled to a single dedicated switch worker - Packets are queued for each (outgoing) peer connection, instead of being handled by a single dedicated switch worker @@ -123,13 +149,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Upgrade build to Go 1.16 ### Fixed + - Fixed a bug where the connection listener could exit prematurely due to resoruce exhaustion (if e.g. too many connections were opened) - Fixed DefaultIfName for OpenBSD (`/dev/tun0` -> `tun0`) - Fixed an issue where a peer could sometimes never be added to the switch - Fixed a goroutine leak that could occur if a peer with an open connection continued to spam additional connection attempts ## [0.3.15] - 2020-09-27 + ### Added + - Support for pinning remote public keys in peering strings has been added, e.g. - By signing public key: `tcp://host:port?ed25519=key` - By encryption public key: `tcp://host:port?curve25519=key` @@ -139,25 +168,32 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added support for SOCKS proxy authentication, e.g. `socks://user@password:host/...` ### Fixed + - Some bugs in the multicast code that could cause unnecessary CPU usage have been fixed - A possible multicast deadlock on macOS when enumerating interfaces has been fixed - A deadlock in the connection code has been fixed - Updated HJSON dependency that caused some build problems ### Changed + - `DisconnectPeer` and `RemovePeer` have been separated and implemented properly now - Less nodes are stored in the DHT now, reducing ambient network traffic and possible instability - Default config file for FreeBSD is now at `/usr/local/etc/yggdrasil.conf` instead of `/etc/yggdrasil.conf` ## [0.3.14] - 2020-03-28 + ### Fixed + - Fixes a memory leak that may occur if packets are incorrectly never removed from a switch queue ### Changed + - Make DHT searches a bit more reliable by tracking the 16 most recently visited nodes ## [0.3.13] - 2020-02-21 + ### Added + - Support for the Wireguard TUN driver, which now replaces Water and provides far better support and performance on Windows - Windows `.msi` installer files are now supported (bundling the Wireguard TUN driver) - NodeInfo code is now actorised, should be more reliable @@ -165,6 +201,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The Yggdrasil API now supports dialing a remote node using the public key instead of the Node ID ### Changed + - The `-loglevel` command line parameter is now cumulative and automatically includes all levels below the one specified - DHT search code has been significantly simplified and processes rumoured nodes in parallel, speeding up search time - DHT search results are now sorted @@ -172,26 +209,32 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The Yggdrasil API now returns public keys instead of node IDs when querying for local and remote addresses ### Fixed + - The multicast code no longer panics when shutting down the node - A potential OOB error when calculating IPv4 flow labels (when tunnel routing is enabled) has been fixed - A bug resulting in incorrect idle notifications in the switch should now be fixed - MTUs are now using a common datatype throughout the codebase ### Removed + - TAP mode has been removed entirely, since it is no longer supported with the Wireguard TUN package. Please note that if you are using TAP mode, you may need to revise your config! - NetBSD support has been removed until the Wireguard TUN package supports NetBSD ## [0.3.12] - 2019-11-24 + ### Added + - New API functions `SetMaximumSessionMTU` and `GetMaximumSessionMTU` - New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file, for use with `-useconffile` or `-useconf` - A warning is now produced in the Yggdrasil output at startup when the MTU in the config is invalid or has been adjusted for some reason ### Changed + - On Linux, outgoing `InterfacePeers` connections now use `SO_BINDTODEVICE` to prefer an outgoing interface - The `genkeys` utility is now in `cmd` rather than `misc` ### Fixed + - A data race condition has been fixed when updating session coordinates - A crash when shutting down when no multicast interfaces are configured has been fixed - A deadlock when calling `AddPeer` multiple times has been fixed @@ -200,10 +243,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The MTU calculation now correctly accounts for ethernet headers when running in TAP mode ## [0.3.11] - 2019-10-25 + ### Added + - Support for TLS listeners and peers has been added, allowing the use of `tls://host:port` in `Peers`, `InterfacePeers` and `Listen` configuration settings - this allows hiding Yggdrasil peerings inside regular TLS connections ### Changed + - Go 1.13 or later is now required for building Yggdrasil - Some exported API functions have been updated to work with standard Go interfaces: - `net.Conn` instead of `yggdrasil.Conn` @@ -213,27 +259,35 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Multicast module reloading behaviour has been improved ### Fixed + - An incorrectly held mutex in the crypto-key routing code has been fixed - Multicast module no longer opens a listener socket if no multicast interfaces are configured ## [0.3.10] - 2019-10-10 + ### Added + - The core library now includes several unit tests for peering and `yggdrasil.Conn` connections ### Changed + - On recent Linux kernels, Yggdrasil will now set the `tcp_congestion_control` algorithm used for its own TCP sockets to [BBR](https://github.com/google/bbr), which reduces latency under load - The systemd service configuration in `contrib` (and, by extension, some of our packages) now attempts to load the `tun` module, in case TUN/TAP support is available but not loaded, and it restricts Yggdrasil to the `CAP_NET_ADMIN` capability for managing the TUN/TAP adapter, rather than letting it do whatever the (typically `root`) user can do ### Fixed + - The `yggdrasil.Conn.RemoteAddr()` function no longer blocks, fixing a deadlock when CKR is used while under heavy load ## [0.3.9] - 2019-09-27 + ### Added + - Yggdrasil will now complain more verbosely when a peer URI is incorrectly formatted - Soft-shutdown methods have been added, allowing a node to shut down gracefully when terminated - New multicast interval logic which sends multicast beacons more often when Yggdrasil is first started to increase the chance of finding nearby nodes quickly after startup ### Changed + - The switch now buffers packets more eagerly in an attempt to give the best link a chance to send, which appears to reduce packet reordering when crossing aggregate sets of peerings - Substantial amounts of the codebase have been refactored to use the actor model, which should substantially reduce the chance of deadlocks - Nonce tracking in sessions has been modified so that memory usage is reduced whilst still only allowing duplicate packets within a small window @@ -242,6 +296,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The maximum queue size is now managed exclusively by the switch rather than by the core ### Fixed + - The broken `hjson-go` dependency which affected builds of the previous version has now been resolved in the module manifest - Some minor memory leaks in the switch have been fixed, which improves memory usage on mobile builds - A memory leak in the add-peer loop has been fixed @@ -253,10 +308,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - A panic which could occur when the TUN/TAP interface reads an undersized/corrupted packet has been fixed ### Removed + - A number of legacy debug functions have now been removed and a number of exported API functions are now better documented ## [0.3.8] - 2019-08-21 + ### Changed + - Yggdrasil can now send multiple packets from the switch at once, which results in improved throughput with smaller packets or lower MTUs - Performance has been slightly improved by not allocating cancellations where not necessary - Crypto-key routing options have been renamed for clarity @@ -269,20 +327,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New nonce tracking should help to reduce the number of packets dropped as a result of multiple/aggregate paths or congestion control in the switch ### Fixed + - A deadlock was fixed in the session code which could result in Yggdrasil failing to pass traffic after some time ### Security + - Address verification was not strict enough, which could result in a malicious session sending traffic with unexpected or spoofed source or destination addresses which Yggdrasil could fail to reject - Versions `0.3.6` and `0.3.7` are vulnerable - users of these versions should upgrade as soon as possible - Versions `0.3.5` and earlier are not affected ## [0.3.7] - 2019-08-14 + ### Changed + - The switch should now forward packets along a single path more consistently in cases where congestion is low and multiple equal-length paths exist, which should improve stability and result in fewer out-of-order packets - Sessions should now be more tolerant of out-of-order packets, by replacing a bitmask with a variable sized heap+map structure to track recently received nonces, which should reduce the number of packets dropped due to reordering when multiple paths are used or multiple independent flows are transmitted through the same session - The admin socket can no longer return a dotfile representation of the known parts of the network, this could be rebuilt by clients using information from `getSwitchPeers`,`getDHT` and `getSessions` ### Fixed + - A number of significant performance regressions introduced in version 0.3.6 have been fixed, resulting in better performance - Flow labels are now used to prioritise traffic flows again correctly - In low-traffic scenarios where there are multiple peerings between a pair of nodes, Yggdrasil now prefers the most active peering instead of the least active, helping to reduce packet reordering @@ -296,13 +359,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - A number of minor allocation and pointer fixes ## [0.3.6] - 2019-08-03 + ### Added + - Yggdrasil now has a public API with interfaces such as `yggdrasil.ConnDialer`, `yggdrasil.ConnListener` and `yggdrasil.Conn` for using Yggdrasil as a transport directly within applications - Session gatekeeper functions, part of the API, which can be used to control whether to allow or reject incoming or outgoing sessions dynamically (compared to the previous fixed whitelist/blacklist approach) - Support for logging to files or syslog (where supported) - Platform defaults now include the ability to set sane defaults for multicast interfaces ### Changed + - Following a massive refactoring exercise, Yggdrasil's codebase has now been broken out into modules - Core node functionality in the `yggdrasil` package with a public API - This allows Yggdrasil to be integrated directly into other applications and used as a transport @@ -315,6 +381,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Upstream dependency references have been updated, which includes a number of fixes in the Water library ### Fixed + - Multicast discovery is no longer disabled if the nominated interfaces aren't available on the system yet, e.g. during boot - Multicast interfaces are now re-evaluated more frequently so that Yggdrasil doesn't need to be restarted to use interfaces that have become available since startup - Admin socket error cases are now handled better @@ -329,12 +396,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Lots of small bug tweaks and clean-ups throughout the codebase ## [0.3.5] - 2019-03-13 + ### Fixed + - The `AllowedEncryptionPublicKeys` option has now been fixed to handle incoming connections properly and no longer blocks outgoing connections (this was broken in v0.3.4) - Multicast TCP listeners will now be stopped correctly when the link-local address on the interface changes or disappears altogether ## [0.3.4] - 2019-03-12 + ### Added + - Support for multiple listeners (although currently only TCP listeners are supported) - New multicast behaviour where each multicast interface is given its own link-local listener and does not depend on the `Listen` configuration - Blocking detection in the switch to avoid parenting a blocked peer @@ -345,6 +416,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added `LinkLocalTCPPort` option for controlling the port number that link-local TCP listeners will listen on by default when setting up `MulticastInterfaces` (a node restart is currently required for changes to `LinkLocalTCPPort` to take effect - it cannot be updated by reloading config during runtime) ### Changed + - The `Listen` configuration statement is now an array instead of a string - The `Listen` configuration statement should now conform to the same formatting as peers with the protocol prefix, e.g. `tcp://[::]:0` - Session workers are now non-blocking @@ -353,6 +425,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Peer forwarding is now prioritised instead of randomised ### Fixed + - Admin socket `getTunTap` call now returns properly instead of claiming no interface is enabled in all cases - Handling of `getRoutes` etc in `yggdrasilctl` is now working - Local interface names are no longer leaked in multicast packets @@ -360,7 +433,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Yggdrasil now correctly responds to multicast interfaces going up and down during runtime ## [0.3.3] - 2019-02-18 + ### Added + - Dynamic reconfiguration, which allows reloading the configuration file to make changes during runtime by sending a `SIGHUP` signal (note: this only works with `-useconffile` and not `-useconf` and currently reconfiguring TUN/TAP is not supported) - Support for building Yggdrasil as an iOS or Android framework if the appropriate tools (e.g. `gomobile`/`gobind` + SDKs) are available - Connection contexts used for TCP connections which allow more exotic socket options to be set, e.g. @@ -369,26 +444,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Flexible logging support, which allows for logging at different levels of verbosity ### Changed + - Switch changes to improve parent selection - Node configuration is now stored centrally, rather than having fragments/copies distributed at startup time - Significant refactoring in various areas, including for link types (TCP, AWDL etc), generic streams and adapters - macOS builds through CircleCI are now 64-bit only ### Fixed + - Simplified `systemd` service now in `contrib` ### Removed + - `ReadTimeout` option is now deprecated ## [0.3.2] - 2018-12-26 + ### Added + - The admin socket is now multithreaded, greatly improving performance of the crawler and allowing concurrent lookups to take place - The ability to hide NodeInfo defaults through either setting the `NodeInfoPrivacy` option or through setting individual `NodeInfo` attributes to `null` ### Changed + - The `armhf` build now targets ARMv6 instead of ARMv7, adding support for Raspberry Pi Zero and other older models, amongst others ### Fixed + - DHT entries are now populated using a copy in memory to fix various potential DHT bugs - DHT traffic should now throttle back exponentially to reduce idle traffic - Adjust how nodes are inserted into the DHT which should help to reduce some incorrect DHT traffic @@ -396,7 +478,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - In TUN mode, ICMPv6 packets are now ignored whereas they were incorrectly processed before ## [0.3.1] - 2018-12-17 + ### Added + - Build name and version is now imprinted onto the binaries if available/specified during build - Ability to disable admin socket with `AdminListen: none` - `AF_UNIX` domain sockets for the admin socket @@ -406,18 +490,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Adds flags `-c`, `-l` and `-t` to `build` script for specifying `GCFLAGS`, `LDFLAGS` or whether to keep symbol/DWARF tables ### Changed + - Default `AdminListen` in newly generated config is now `unix:///var/run/yggdrasil.sock` - Formatting of `getRoutes` in the admin socket has been improved - Debian package now adds `yggdrasil` group to assist with `AF_UNIX` admin socket permissions - Crypto, address and other utility code refactored into separate Go packages ### Fixed + - Switch peer convergence is now much faster again (previously it was taking up to a minute once the peering was established) - `yggdrasilctl` is now less prone to crashing when parameters are specified incorrectly - Panic fixed when `Peers` or `InterfacePeers` was commented out ## [0.3.0] - 2018-12-12 + ### Added + - Crypto-key routing support for tunnelling both IPv4 and IPv6 over Yggdrasil - Add advanced `SwitchOptions` in configuration file for tuning the switch - Add `dhtPing` to the admin socket to aid in crawling the network @@ -430,6 +518,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `yggdrasilctl` now returns more useful logging in the event of a fatal error ### Changed + - Switched to Chord DHT (instead of Kademlia, although still compatible at the protocol level) - The `AdminListen` option and `yggdrasilctl` now default to `unix:///var/run/yggdrasil.sock` on BSDs, macOS and Linux - Cleaned up some of the parameter naming in the admin socket @@ -439,12 +528,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `yggdrasilctl` now has more useful help text (with `-help` or when no arguments passed) ### Fixed + - Memory leaks in the DHT fixed - Crash fixed where the ICMPv6 NDP goroutine would incorrectly start in TUN mode - Removing peers from the switch table if they stop sending switch messages but keep the TCP connection alive ## [0.2.7] - 2018-10-13 + ### Added + - Session firewall, which makes it possible to control who can open sessions with your node - Add `getSwitchQueues` to admin socket - Add `InterfacePeers` for configuring static peerings via specific network interfaces @@ -452,81 +544,106 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - FreeBSD service script in `contrib` ## Changed + - CircleCI builds are now built with Go 1.11 instead of Go 1.9 ## Fixed + - Race condition in the switch table, reported by trn - Debug builds are now tested by CircleCI as well as platform release builds - Port number fixed on admin graph from unknown nodes ## [0.2.6] - 2018-07-31 + ### Added + - Configurable TCP timeouts to assist in peering over Tor/I2P - Prefer IPv6 flow label when extending coordinates to sort backpressure queues - `arm64` builds through CircleCI ### Changed + - Sort dot graph links by integer value ## [0.2.5] - 2018-07-19 + ### Changed + - Make `yggdrasilctl` less case sensitive - More verbose TCP disconnect messages ### Fixed + - Fixed debug builds - Cap maximum MTU on Linux in TAP mode - Process successfully-read TCP traffic before checking for / handling errors (fixes EOF behavior) ## [0.2.4] - 2018-07-08 + ### Added + - Support for UNIX domain sockets for the admin socket using `unix:///path/to/file.sock` - Centralised platform-specific defaults ### Changed + - Backpressure tuning, including reducing resource consumption ### Fixed + - macOS local ping bug, which previously prevented you from pinging your own `utun` adapter's IPv6 address ## [0.2.3] - 2018-06-29 + ### Added + - Begin keeping changelog (incomplete and possibly inaccurate information before this point). - Build RPMs in CircleCI using alien. This provides package support for Fedora, Red Hat Enterprise Linux, CentOS and other RPM-based distributions. ### Changed + - Local backpressure improvements. - Change `box_pub_key` to `key` in admin API for simplicity. - Session cleanup. ## [0.2.2] - 2018-06-21 + ### Added + - Add `yggdrasilconf` utility for testing with the `vyatta-yggdrasil` package. - Add a randomized retry delay after TCP disconnects, to prevent synchronization livelocks. ### Changed + - Update build script to strip by default, which significantly reduces the size of the binary. - Add debug `-d` and UPX `-u` flags to the `build` script. - Start pprof in debug builds based on an environment variable (e.g. `PPROFLISTEN=localhost:6060`), instead of a flag. ### Fixed + - Fix typo in big-endian BOM so that both little-endian and big-endian UTF-16 files are detected correctly. ## [0.2.1] - 2018-06-15 + ### Changed + - The address range was moved from `fd00::/8` to `200::/7`. This range was chosen as it is marked as deprecated. The change prevents overlap with other ULA privately assigned ranges. ### Fixed + - UTF-16 detection conversion for configuration files, which can particularly be a problem on Windows 10 if a configuration file is generated from within PowerShell. - Fixes to the Debian package control file. - Fixes to the launchd service for macOS. - Fixes to the DHT and switch. ## [0.2.0] - 2018-06-13 + ### Added + - Exchange version information during connection setup, to prevent connections with incompatible versions. ### Changed + - Wire format changes (backwards incompatible). - Less maintenance traffic per peer. - Exponential back-off for DHT maintenance traffic (less maintenance traffic for known good peers). @@ -534,18 +651,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Use local queue sizes for a sort of local-only backpressure routing, instead of the removed bandwidth estimates, when deciding where to send a packet. ### Removed + - UDP peering, this may be added again if/when a better implementation appears. - Per peer bandwidth estimation, as this has been replaced with an early local backpressure implementation. ## [0.1.0] - 2018-02-01 + ### Added + - Adopt semantic versioning. ### Changed + - Wire format changes (backwards incompatible). - Many other undocumented changes leading up to this release and before the next one. ## [0.0.1] - 2017-12-28 + ### Added + - First commit. - Initial public release. From 4ddebb338d6e115d6c09fffadb338869c4745b12 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 18 Apr 2022 15:29:43 +0100 Subject: [PATCH 02/40] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 498d8338..6c6aa506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed -- Go 1.17 is now required +- Go 1.17 is now required to build Yggdrasil ## [0.4.3] - 2022-02-06 From c19319df5e4e773d8668d5a5b4999a639c8113c0 Mon Sep 17 00:00:00 2001 From: Rubikoid Date: Thu, 17 Mar 2022 02:43:28 +0300 Subject: [PATCH 03/40] Fix coords print --- cmd/yggdrasilctl/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 788b4f19..180bea09 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -300,7 +300,7 @@ func handleGetSelf(res map[string]interface{}, verbose bool) { if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok { fmt.Println("Public key:", boxSigKey) } - if coords, ok := v.(map[string]interface{})["coords"].(string); ok { + if coords, ok := v.(map[string]interface{})["coords"].([]interface{}); ok { fmt.Println("Coords:", coords) } if verbose { From 88a0a3e8fbdb174938f7222acc1dad41eca76df0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 7 Jul 2022 17:03:29 +0100 Subject: [PATCH 04/40] Fix data races in `handleProto` (observed by @majestrate) --- src/core/proto.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/proto.go b/src/core/proto.go index 5af59cc8..3c68c0e3 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -65,10 +65,16 @@ func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) { case typeProtoNodeInfoResponse: p.nodeinfo.handleRes(p, key, bs[1:]) case typeProtoDebug: - p._handleDebug(key, bs[1:]) + p.handleDebug(from, key, bs[1:]) } } +func (p *protoHandler) handleDebug(from phony.Actor, key keyArray, bs []byte) { + p.Act(from, func() { + p._handleDebug(key, bs) + }) +} + func (p *protoHandler) _handleDebug(key keyArray, bs []byte) { if len(bs) == 0 { return From 234addc81f3495e110db31d1f9fb9822adcae701 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 7 Jul 2022 18:17:27 +0100 Subject: [PATCH 05/40] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6aa506..0341e6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - ICMPv6 "Packet Too Big" payload size has been increased, which should fix Path MTU Discovery (PMTUD) when two nodes have different `IfMTU` values configured +- A crash has been fixed when handling debug packet responses +- `yggdrasilctl getSelf` should now report coordinates correctly again ### Changed From df7ca3a5b888ac487026e469bcb9af037d8c9aa6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 7 Jul 2022 18:17:39 +0100 Subject: [PATCH 06/40] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0341e6d9..34001548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> -## [0.4.4] - 2022-04-18 +## [0.4.4] - 2022-07-07 ### Fixed From 8c454a146cb70aa07ee2c87af964f5c1394da299 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 7 Jul 2022 18:19:15 +0100 Subject: [PATCH 07/40] Silence incorrect linter warning --- src/address/address.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/address/address.go b/src/address/address.go index c7f2eb46..d56be80d 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -108,7 +108,7 @@ func SubnetForKey(publicKey ed25519.PublicKey) *Subnet { } var snet Subnet copy(snet[:], addr[:]) - prefix := GetPrefix() + prefix := GetPrefix() // nolint:staticcheck snet[len(prefix)-1] |= 0x01 return &snet } @@ -117,7 +117,7 @@ func SubnetForKey(publicKey ed25519.PublicKey) *Subnet { // This is used for key lookup. func (a *Address) GetKey() ed25519.PublicKey { var key [ed25519.PublicKeySize]byte - prefix := GetPrefix() + prefix := GetPrefix() // nolint:staticcheck ones := int(a[len(prefix)]) for idx := 0; idx < ones; idx++ { key[idx/8] |= 0x80 >> byte(idx%8) From 5616b9fc847cabb23b96b470e13a90f00b5bad15 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 24 Jul 2022 10:23:25 +0100 Subject: [PATCH 08/40] Don't lose my work --- cmd/yggdrasil/main.go | 37 +++++++-- src/core/api.go | 6 +- src/core/core.go | 174 +++++++++++++++++------------------------- src/core/link.go | 9 +-- src/core/options.go | 30 ++++++++ src/core/tcp.go | 8 +- 6 files changed, 140 insertions(+), 124 deletions(-) create mode 100644 src/core/options.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 58b8230d..7f751833 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -36,7 +36,7 @@ import ( ) type node struct { - core core.Core + core *core.Core config *config.NodeConfig tuntap *tuntap.TunAdapter multicast *multicast.Multicast @@ -327,11 +327,32 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. + sk, err := hex.DecodeString(cfg.PrivateKey) + if err != nil { + panic(err) + } + options := []core.SetupOption{ + core.IfName(cfg.IfName), + core.IfMTU(cfg.IfMTU), + } + for _, peer := range cfg.Peers { + options = append(options, core.Peer{URI: peer}) + } + for intf, peers := range cfg.InterfacePeers { + for _, peer := range peers { + options = append(options, core.Peer{URI: peer, SourceInterface: intf}) + } + } + for _, allowed := range cfg.AllowedPublicKeys { + k, err := hex.DecodeString(allowed) + if err != nil { + panic(err) + } + options = append(options, core.AllowedPublicKey(k[:])) + } n := node{config: cfg} - // Now start Yggdrasil - this starts the DHT, router, switch and other core - // components needed for Yggdrasil to operate - if err = n.core.Start(cfg, logger); err != nil { - logger.Errorln("An error occurred during startup") + n.core, err = core.New(sk[:], options...) + if err != nil { panic(err) } // Register the session firewall gatekeeper function @@ -340,21 +361,21 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { n.multicast = &multicast.Multicast{} n.tuntap = &tuntap.TunAdapter{} // Start the admin socket - if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { + if err := n.admin.Init(n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising admin socket:", err) } else if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } n.admin.SetupAdminHandlers(n.admin) // Start the multicast interface - if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil { + if err := n.multicast.Init(n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) } else if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } n.multicast.SetupAdminHandlers(n.admin) // Start the TUN/TAP interface - rwc := ipv6rwc.NewReadWriteCloser(&n.core) + rwc := ipv6rwc.NewReadWriteCloser(n.core) if err := n.tuntap.Init(rwc, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising TUN/TAP:", err) } else if err := n.tuntap.Start(); err != nil { diff --git a/src/core/api.go b/src/core/api.go index fabd7439..30e7e0f6 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -2,8 +2,6 @@ package core import ( "crypto/ed25519" - "sync/atomic" - "time" //"encoding/hex" "encoding/json" @@ -27,6 +25,7 @@ type Self struct { Coords []uint64 } +/* type Peer struct { Key ed25519.PublicKey Root ed25519.PublicKey @@ -37,6 +36,7 @@ type Peer struct { TXBytes uint64 Uptime time.Duration } +*/ type DHTEntry struct { Key ed25519.PublicKey @@ -62,6 +62,7 @@ func (c *Core) GetSelf() Self { return self } +/* func (c *Core) GetPeers() []Peer { var peers []Peer names := make(map[net.Conn]string) @@ -90,6 +91,7 @@ func (c *Core) GetPeers() []Peer { } return peers } +*/ func (c *Core) GetDHT() []DHTEntry { var dhts []DHTEntry diff --git a/src/core/core.go b/src/core/core.go index 0332980b..df3888af 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -3,12 +3,11 @@ package core import ( "context" "crypto/ed25519" - "encoding/hex" - "errors" "fmt" "io/ioutil" "net" "net/url" + "os" "time" iwe "github.com/Arceliar/ironwood/encrypted" @@ -16,9 +15,9 @@ import ( "github.com/Arceliar/phony" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/config" - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/version" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) // The Core object represents the Yggdrasil node. You should create a Core @@ -29,62 +28,95 @@ type Core struct { // guarantee that it will be covered by the mutex phony.Inbox *iwe.PacketConn - config *config.NodeConfig // Config + ctx context.Context + cancel context.CancelFunc secret ed25519.PrivateKey public ed25519.PublicKey links links proto protoHandler log *log.Logger addPeerTimer *time.Timer - ctx context.Context - ctxCancel context.CancelFunc + config struct { + _peers map[Peer]struct{} // configurable after startup + _listeners map[ListenAddress]struct{} // configurable after startup + nodeinfo NodeInfo // immutable after startup + nodeinfoPrivacy NodeInfoPrivacy // immutable after startup + ifname IfName // immutable after startup + ifmtu IfMTU // immutable after startup + _allowedPublicKeys map[[32]byte]struct{} // configurable after startup + } } -func (c *Core) _init() error { - // 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 - c.config.RLock() - defer c.config.RUnlock() +func New(secret ed25519.PrivateKey, opts ...SetupOption) (*Core, error) { + if len(secret) != ed25519.PrivateKeySize { + return nil, fmt.Errorf("private key is incorrect length") + } + c := &Core{ + secret: secret, + public: secret.Public().(ed25519.PublicKey), + log: log.New(os.Stdout, "", 0), // TODO: not this + } + c.ctx, c.cancel = context.WithCancel(context.Background()) + var err error + if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil { + return nil, fmt.Errorf("error creating encryption: %w", err) + } + for _, opt := range opts { + c._applyOption(opt) + } if c.log == nil { c.log = log.New(ioutil.Discard, "", 0) } - - sigPriv, err := hex.DecodeString(c.config.PrivateKey) - if err != nil { - return err - } - if len(sigPriv) < ed25519.PrivateKeySize { - return errors.New("PrivateKey is incorrect length") - } - - c.secret = ed25519.PrivateKey(sigPriv) - c.public = c.secret.Public().(ed25519.PublicKey) - // TODO check public against current.PublicKey, error if they don't match - - c.PacketConn, err = iwe.NewPacketConn(c.secret) - c.ctx, c.ctxCancel = context.WithCancel(context.Background()) c.proto.init(c) - if err := c.proto.nodeinfo.setNodeInfo(c.config.NodeInfo, c.config.NodeInfoPrivacy); err != nil { - return fmt.Errorf("setNodeInfo: %w", err) + if err := c.links.init(c); err != nil { + return nil, fmt.Errorf("error initialising links: %w", err) + } + if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil { + return nil, fmt.Errorf("error setting node info: %w", err) + } + c.addPeerTimer = time.AfterFunc(time.Minute, func() { + c.Act(nil, c._addPeerLoop) + }) + if name := version.BuildName(); name != "unknown" { + c.log.Infoln("Build name:", name) + } + if version := version.BuildVersion(); version != "unknown" { + c.log.Infoln("Build version:", version) + } + return c, nil +} + +func (c *Core) _applyOption(opt SetupOption) { + switch v := opt.(type) { + case Peer: + c.config._peers[v] = struct{}{} + case ListenAddress: + c.config._listeners[v] = struct{}{} + case NodeInfo: + c.config.nodeinfo = v + case NodeInfoPrivacy: + c.config.nodeinfoPrivacy = v + case IfName: + c.config.ifname = v + case IfMTU: + c.config.ifmtu = v + case AllowedPublicKey: + pk := crypto.SigPubKey{} + copy(pk[:], v) + c.config._allowedPublicKeys[pk] = struct{}{} } - return err } // If any static peers were provided in the configuration above then we should // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. func (c *Core) _addPeerLoop() { - c.config.RLock() - defer c.config.RUnlock() - if c.addPeerTimer == nil { return } // Add peers from the Peers section - for _, peer := range c.config.Peers { + for peer := range c.config._peers { go func(peer string, intf string) { u, err := url.Parse(peer) if err != nil { @@ -93,22 +125,7 @@ func (c *Core) _addPeerLoop() { if err := c.CallPeer(u, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } - }(peer, "") // TODO: this should be acted and not in a goroutine? - } - - // Add peers from the InterfacePeers section - for intf, intfpeers := range c.config.InterfacePeers { - for _, peer := range intfpeers { - go func(peer string, intf string) { - u, err := url.Parse(peer) - if err != nil { - c.log.Errorln("Failed to parse peer url:", peer, err) - } - if err := c.CallPeer(u, intf); err != nil { - c.log.Errorln("Failed to add peer:", err) - } - }(peer, intf) // TODO: this should be acted and not in a goroutine? - } + }(peer.URI, peer.SourceInterface) // TODO: this should be acted and not in a goroutine? } c.addPeerTimer = time.AfterFunc(time.Minute, func() { @@ -116,49 +133,6 @@ func (c *Core) _addPeerLoop() { }) } -// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs -// debug logging through the provided log.Logger. The started stack will include -// TCP and UDP sockets, a multicast discovery socket, an admin socket, router, -// switch and DHT node. A config.NodeState is returned which contains both the -// current and previous configurations (from reconfigures). -func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (err error) { - phony.Block(c, func() { - err = c._start(nc, log) - }) - return -} - -// This function is unsafe and should only be ran by the core actor. -func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) error { - c.log = log - c.config = nc - - if name := version.BuildName(); name != "unknown" { - c.log.Infoln("Build name:", name) - } - if version := version.BuildVersion(); version != "unknown" { - c.log.Infoln("Build version:", version) - } - - c.log.Infoln("Starting up...") - if err := c._init(); err != nil { - c.log.Errorln("Failed to initialize core") - return err - } - - if err := c.links.init(c); err != nil { - c.log.Errorln("Failed to start link interfaces") - return err - } - - c.addPeerTimer = time.AfterFunc(0, func() { - c.Act(nil, c._addPeerLoop) - }) - - c.log.Infoln("Startup complete") - return nil -} - // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { phony.Block(c, func() { @@ -168,17 +142,9 @@ func (c *Core) Stop() { }) } -func (c *Core) Close() error { - var err error - phony.Block(c, func() { - err = c._close() - }) - return err -} - // This function is unsafe and should only be ran by the core actor. func (c *Core) _close() error { - c.ctxCancel() + c.cancel() err := c.PacketConn.Close() if c.addPeerTimer != nil { c.addPeerTimer.Stop() diff --git a/src/core/link.go b/src/core/link.go index f96c9be9..0b7e50a9 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "crypto/ed25519" "encoding/hex" "errors" @@ -215,12 +216,10 @@ func (intf *link) handler() (chan struct{}, error) { } } // Check if we're authorized to connect to this key / IP - intf.links.core.config.RLock() - allowed := intf.links.core.config.AllowedPublicKeys - intf.links.core.config.RUnlock() + allowed := intf.links.core.config._allowedPublicKeys isallowed := len(allowed) == 0 - for _, k := range allowed { - if k == hex.EncodeToString(meta.key) { // TODO: this is yuck + for k := range allowed { + if bytes.Equal(k[:], meta.key) { isallowed = true break } diff --git a/src/core/options.go b/src/core/options.go new file mode 100644 index 00000000..0210a4ef --- /dev/null +++ b/src/core/options.go @@ -0,0 +1,30 @@ +package core + +import ( + "crypto/ed25519" +) + +type SetupOption interface { + isSetupOption() +} + +type ListenAddress string +type AdminListenAddress string +type Peer struct { + URI string + SourceInterface string +} +type NodeInfo map[string]interface{} +type NodeInfoPrivacy bool +type IfName string +type IfMTU uint16 +type AllowedPublicKey ed25519.PublicKey + +func (a ListenAddress) isSetupOption() {} +func (a AdminListenAddress) isSetupOption() {} +func (a Peer) isSetupOption() {} +func (a NodeInfo) isSetupOption() {} +func (a NodeInfoPrivacy) isSetupOption() {} +func (a IfName) isSetupOption() {} +func (a IfMTU) isSetupOption() {} +func (a AllowedPublicKey) isSetupOption() {} diff --git a/src/core/tcp.go b/src/core/tcp.go index 7b1773b8..8e1435ef 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -96,7 +96,7 @@ func (t *tcp) getAddr() *net.TCPAddr { } // Initializes the struct. -func (t *tcp) init(l *links) error { +func (t *tcp) init(l *links, listeners []ListenAddress) error { t.links = l t.tls.init(t) t.mutex.Lock() @@ -105,10 +105,8 @@ func (t *tcp) init(l *links) error { t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() - t.links.core.config.RLock() - defer t.links.core.config.RUnlock() - for _, listenaddr := range t.links.core.config.Listen { - u, err := url.Parse(listenaddr) + for _, listenaddr := range listeners { + u, err := url.Parse(string(listenaddr)) if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") } From 4c889703b135f459232f0fa1a9f01851a6e334ab Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Aug 2022 15:05:12 +0100 Subject: [PATCH 09/40] Continue refactoring --- contrib/mobile/mobile.go | 45 ++++++++++++++++++++++++++++++---------- go.mod | 2 +- go.sum | 2 ++ src/core/api.go | 44 +++++++++++++++++++-------------------- src/core/core.go | 6 ++++-- src/core/core_test.go | 29 ++++++++++---------------- src/core/link.go | 14 ++++++++----- 7 files changed, 82 insertions(+), 60 deletions(-) diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index ba7cfdf6..fc850368 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -25,7 +25,7 @@ import ( // functions. Note that in the case of iOS we handle reading/writing to/from TUN // in Swift therefore we use the "dummy" TUN interface instead. type Yggdrasil struct { - core core.Core + core *core.Core iprwc *ipv6rwc.ReadWriteCloser config *config.NodeConfig multicast multicast.Multicast @@ -48,19 +48,42 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { if err := json.Unmarshal(configjson, &m.config); err != nil { return err } - m.config.IfName = "none" - if err := m.core.Start(m.config, logger); err != nil { - logger.Errorln("An error occured starting Yggdrasil:", err) - return err + // Setup the Yggdrasil node itself. + sk, err := hex.DecodeString(m.config.PrivateKey) + if err != nil { + panic(err) + } + options := []core.SetupOption{ + core.IfName("none"), + core.IfMTU(m.config.IfMTU), + } + for _, peer := range m.config.Peers { + options = append(options, core.Peer{URI: peer}) + } + for intf, peers := range m.config.InterfacePeers { + for _, peer := range peers { + options = append(options, core.Peer{URI: peer, SourceInterface: intf}) + } + } + for _, allowed := range m.config.AllowedPublicKeys { + k, err := hex.DecodeString(allowed) + if err != nil { + panic(err) + } + options = append(options, core.AllowedPublicKey(k[:])) + } + m.core, err = core.New(sk[:], options...) + if err != nil { + panic(err) } mtu := m.config.IfMTU - m.iprwc = ipv6rwc.NewReadWriteCloser(&m.core) + m.iprwc = ipv6rwc.NewReadWriteCloser(m.core) if m.iprwc.MaxMTU() < mtu { mtu = m.iprwc.MaxMTU() } m.iprwc.SetMTU(mtu) if len(m.config.MulticastInterfaces) > 0 { - if err := m.multicast.Init(&m.core, m.config, logger, nil); err != nil { + if err := m.multicast.Init(m.core, m.config, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) return err } @@ -139,18 +162,18 @@ func (m *Yggdrasil) GetCoordsString() string { func (m *Yggdrasil) GetPeersJSON() (result string) { peers := []struct { - core.Peer + core.PeerInfo IP string }{} for _, v := range m.core.GetPeers() { a := address.AddrForKey(v.Key) ip := net.IP(a[:]).String() peers = append(peers, struct { - core.Peer + core.PeerInfo IP string }{ - Peer: v, - IP: ip, + PeerInfo: v, + IP: ip, }) } if res, err := json.Marshal(peers); err == nil { diff --git a/go.mod b/go.mod index c93b8a0b..11170a1b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/kardianos/minwinsvc v1.0.0 github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 - golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 + golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b diff --git a/go.sum b/go.sum index c87112f7..0fc5cadd 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/src/core/api.go b/src/core/api.go index 30e7e0f6..3ab26ee5 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -2,6 +2,8 @@ package core import ( "crypto/ed25519" + "sync/atomic" + "time" //"encoding/hex" "encoding/json" @@ -19,14 +21,13 @@ import ( //"github.com/Arceliar/phony" ) -type Self struct { +type SelfInfo struct { Key ed25519.PublicKey Root ed25519.PublicKey Coords []uint64 } -/* -type Peer struct { +type PeerInfo struct { Key ed25519.PublicKey Root ed25519.PublicKey Coords []uint64 @@ -36,25 +37,24 @@ type Peer struct { TXBytes uint64 Uptime time.Duration } -*/ -type DHTEntry struct { +type DHTEntryInfo struct { Key ed25519.PublicKey Port uint64 Rest uint64 } -type PathEntry struct { +type PathEntryInfo struct { Key ed25519.PublicKey Path []uint64 } -type Session struct { +type SessionInfo struct { Key ed25519.PublicKey } -func (c *Core) GetSelf() Self { - var self Self +func (c *Core) GetSelf() SelfInfo { + var self SelfInfo s := c.PacketConn.PacketConn.Debug.GetSelf() self.Key = s.Key self.Root = s.Root @@ -62,9 +62,8 @@ func (c *Core) GetSelf() Self { return self } -/* -func (c *Core) GetPeers() []Peer { - var peers []Peer +func (c *Core) GetPeers() []PeerInfo { + var peers []PeerInfo names := make(map[net.Conn]string) c.links.mutex.Lock() for _, info := range c.links.links { @@ -73,7 +72,7 @@ func (c *Core) GetPeers() []Peer { c.links.mutex.Unlock() ps := c.PacketConn.PacketConn.Debug.GetPeers() for _, p := range ps { - var info Peer + var info PeerInfo info.Key = p.Key info.Root = p.Root info.Coords = p.Coords @@ -91,13 +90,12 @@ func (c *Core) GetPeers() []Peer { } return peers } -*/ -func (c *Core) GetDHT() []DHTEntry { - var dhts []DHTEntry +func (c *Core) GetDHT() []DHTEntryInfo { + var dhts []DHTEntryInfo ds := c.PacketConn.PacketConn.Debug.GetDHT() for _, d := range ds { - var info DHTEntry + var info DHTEntryInfo info.Key = d.Key info.Port = d.Port info.Rest = d.Rest @@ -106,11 +104,11 @@ func (c *Core) GetDHT() []DHTEntry { return dhts } -func (c *Core) GetPaths() []PathEntry { - var paths []PathEntry +func (c *Core) GetPaths() []PathEntryInfo { + var paths []PathEntryInfo ps := c.PacketConn.PacketConn.Debug.GetPaths() for _, p := range ps { - var info PathEntry + var info PathEntryInfo info.Key = p.Key info.Path = p.Path paths = append(paths, info) @@ -118,11 +116,11 @@ func (c *Core) GetPaths() []PathEntry { return paths } -func (c *Core) GetSessions() []Session { - var sessions []Session +func (c *Core) GetSessions() []SessionInfo { + var sessions []SessionInfo ss := c.PacketConn.Debug.GetSessions() for _, s := range ss { - var info Session + var info SessionInfo info.Key = s.Key sessions = append(sessions, info) } diff --git a/src/core/core.go b/src/core/core.go index df3888af..f41b6531 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -15,7 +15,6 @@ import ( "github.com/Arceliar/phony" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/version" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -61,6 +60,9 @@ func New(secret ed25519.PrivateKey, opts ...SetupOption) (*Core, error) { if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil { return nil, fmt.Errorf("error creating encryption: %w", err) } + c.config._peers = map[Peer]struct{}{} + c.config._listeners = map[ListenAddress]struct{}{} + c.config._allowedPublicKeys = map[[32]byte]struct{}{} for _, opt := range opts { c._applyOption(opt) } @@ -101,7 +103,7 @@ func (c *Core) _applyOption(opt SetupOption) { case IfMTU: c.config.ifmtu = v case AllowedPublicKey: - pk := crypto.SigPubKey{} + pk := [32]byte{} copy(pk[:], v) c.config._allowedPublicKeys[pk] = struct{}{} } diff --git a/src/core/core_test.go b/src/core/core_test.go index fcfe2e31..823e5e95 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -2,6 +2,7 @@ package core import ( "bytes" + "crypto/ed25519" "math/rand" "net/url" "os" @@ -9,21 +10,8 @@ import ( "time" "github.com/gologme/log" - - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) -// GenerateConfig produces default configuration with suitable modifications for tests. -func GenerateConfig() *config.NodeConfig { - cfg := defaults.GenerateConfig() - cfg.AdminListen = "none" - cfg.Listen = []string{"tcp://127.0.0.1:0"} - cfg.IfName = "none" - - return cfg -} - // GetLoggerWithPrefix creates a new logger instance with prefix. // If verbose is set to true, three log levels are enabled: "info", "warn", "error". func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { @@ -40,13 +28,18 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { // CreateAndConnectTwo creates two nodes. nodeB connects to nodeA. // Verbosity flag is passed to logger. func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) { - nodeA = new(Core) - if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil { + var err error + var skA, skB ed25519.PrivateKey + if _, skA, err = ed25519.GenerateKey(nil); err != nil { t.Fatal(err) } - - nodeB = new(Core) - if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil { + if _, skB, err = ed25519.GenerateKey(nil); err != nil { + t.Fatal(err) + } + if nodeA, err = New(skA, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { + t.Fatal(err) + } + if nodeB, err = New(skB, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { t.Fatal(err) } diff --git a/src/core/link.go b/src/core/link.go index 0b7e50a9..099a8af0 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -17,6 +17,7 @@ import ( "sync/atomic" + "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" "golang.org/x/net/proxy" @@ -62,7 +63,14 @@ func (l *links) init(c *Core) error { l.mutex.Unlock() l.stopped = make(chan struct{}) - if err := l.tcp.init(l); err != nil { + var listeners []ListenAddress + phony.Block(c, func() { + listeners = make([]ListenAddress, 0, len(c.config._listeners)) + for listener := range c.config._listeners { + listeners = append(listeners, listener) + } + }) + if err := l.tcp.init(l, listeners); err != nil { c.log.Errorln("Failed to start TCP interface") return err } @@ -71,10 +79,6 @@ func (l *links) init(c *Core) error { } func (l *links) call(u *url.URL, sintf string) error { - //u, err := url.Parse(uri) - //if err != nil { - // return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) - //} tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["key"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{}) From d5c0dc9beeb7dfb3d9c1158be150c2ec19ed5574 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Aug 2022 15:19:01 +0100 Subject: [PATCH 10/40] Go 1.19 in CI --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b41e4336..7915026d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.19 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: ["1.17", "1.18"] + goversion: ["1.17", "1.18", "1.19"] name: Build & Test (Linux, Go ${{ matrix.goversion }}) needs: [lint] @@ -75,7 +75,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: ["1.17", "1.18"] + goversion: ["1.17", "1.18", "1.19"] name: Build & Test (Windows, Go ${{ matrix.goversion }}) needs: [lint] @@ -99,7 +99,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: ["1.17", "1.18"] + goversion: ["1.17", "1.18", "1.19"] name: Build & Test (macOS, Go ${{ matrix.goversion }}) needs: [lint] @@ -148,7 +148,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.19 - name: Build package env: @@ -180,7 +180,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.19 - name: Build package env: @@ -212,7 +212,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.19 - name: Build package run: sh contrib/msi/build-msi.sh ${{ matrix.pkgarch }} @@ -248,7 +248,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.19 - name: Build package env: From 16b81490525392bb87cc81e80f0118e1e5cf6184 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Aug 2022 15:21:21 +0100 Subject: [PATCH 11/40] No longer use `ioutil` which is deprecated --- cmd/yggdrasil/main.go | 6 +++--- cmd/yggdrasilctl/cmd_line_env.go | 3 +-- src/core/core.go | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 58b8230d..30cdd5e5 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -8,7 +8,7 @@ import ( "encoding/json" "flag" "fmt" - "io/ioutil" + "io" "net" "os" "os/signal" @@ -51,10 +51,10 @@ func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf var err error if useconffile != "" { // Read the file from the filesystem - conf, err = ioutil.ReadFile(useconffile) + conf, err = os.ReadFile(useconffile) } else { // Read the file from stdin. - conf, err = ioutil.ReadAll(os.Stdin) + conf, err = io.ReadAll(os.Stdin) } if err != nil { panic(err) diff --git a/cmd/yggdrasilctl/cmd_line_env.go b/cmd/yggdrasilctl/cmd_line_env.go index bd6df8fc..c1acacd6 100644 --- a/cmd/yggdrasilctl/cmd_line_env.go +++ b/cmd/yggdrasilctl/cmd_line_env.go @@ -4,7 +4,6 @@ import ( "bytes" "flag" "fmt" - "io/ioutil" "log" "os" @@ -61,7 +60,7 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() { func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) { if cmdLineEnv.server == cmdLineEnv.endpoint { - if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { + if config, err := os.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) diff --git a/src/core/core.go b/src/core/core.go index 0332980b..f77648b2 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "errors" "fmt" - "io/ioutil" + "io" "net" "net/url" "time" @@ -48,7 +48,7 @@ func (c *Core) _init() error { c.config.RLock() defer c.config.RUnlock() if c.log == nil { - c.log = log.New(ioutil.Discard, "", 0) + c.log = log.New(io.Discard, "", 0) } sigPriv, err := hex.DecodeString(c.config.PrivateKey) From f8e626dbe1ad9d498b8e097da3452192f2ce6150 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 11 Aug 2022 05:54:02 +0800 Subject: [PATCH 12/40] Fix Android multicast crash (#930) * Do not exit on multicast errors (mobile) * Consistency with cmd/yggdrasil/main.go Co-authored-by: Neil Alexander --- contrib/mobile/mobile.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index ba7cfdf6..d8c2d6b5 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -62,11 +62,8 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { if len(m.config.MulticastInterfaces) > 0 { if err := m.multicast.Init(&m.core, m.config, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) - return err - } - if err := m.multicast.Start(); err != nil { + } else if err := m.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) - return err } } return nil From 486ffebedd41ff3b359c2b9bab7bfe678e8e2dfd Mon Sep 17 00:00:00 2001 From: Karandashov Daniil Date: Mon, 29 Aug 2022 22:40:19 +0300 Subject: [PATCH 13/40] Delete unused param (#935) --- cmd/yggdrasil/main.go | 2 +- src/admin/admin.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 5d1c76ac..826df007 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -366,7 +366,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { } else if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } - n.admin.SetupAdminHandlers(n.admin) + n.admin.SetupAdminHandlers() // Start the multicast interface if err := n.multicast.Init(n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) diff --git a/src/admin/admin.go b/src/admin/admin.go index 56164533..c7c0f148 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -86,7 +86,7 @@ func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, return a.core.SetAdmin(a) } -func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { +func (a *AdminSocket) SetupAdminHandlers() { _ = a.AddHandler("getSelf", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetSelfRequest{} res := &GetSelfResponse{} From 4f2abece817c1be59505dbd7c55f02bf47a7874d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 1 Sep 2022 16:56:42 +0100 Subject: [PATCH 14/40] Fix panic in `tcp.init` for incorrectly formatted listen addresses --- src/core/tcp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/tcp.go b/src/core/tcp.go index 8e1435ef..ab952806 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -109,6 +109,7 @@ func (t *tcp) init(l *links, listeners []ListenAddress) error { u, err := url.Parse(string(listenaddr)) if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") + continue } if _, err := t.listenURL(u, ""); err != nil { return err From c6fe81b5d282ae8166b373e1920b26676dcf5aac Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 10:50:43 +0100 Subject: [PATCH 15/40] Admin socket and `yggdrasilctl` refactoring (#939) --- cmd/yggdrasilctl/cmd_line_env.go | 8 +- cmd/yggdrasilctl/main.go | 548 ++++++++++--------------------- go.mod | 20 +- go.sum | 114 ++++++- src/admin/admin.go | 69 ++-- src/admin/getdht.go | 19 +- src/admin/getpaths.go | 19 +- src/admin/getpeers.go | 26 +- src/admin/getself.go | 20 +- src/admin/getsessions.go | 19 +- src/core/nodeinfo.go | 9 +- 11 files changed, 401 insertions(+), 470 deletions(-) diff --git a/cmd/yggdrasilctl/cmd_line_env.go b/cmd/yggdrasilctl/cmd_line_env.go index c1acacd6..9fcabad9 100644 --- a/cmd/yggdrasilctl/cmd_line_env.go +++ b/cmd/yggdrasilctl/cmd_line_env.go @@ -14,9 +14,9 @@ import ( ) type CmdLineEnv struct { - args []string - endpoint, server string - injson, verbose, ver bool + args []string + endpoint, server string + injson, ver bool } func newCmdLineEnv() CmdLineEnv { @@ -46,7 +46,6 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() { server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint") injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") - verbose := flag.Bool("v", false, "Verbose output (includes public keys)") ver := flag.Bool("version", false, "Prints the version of this build") flag.Parse() @@ -54,7 +53,6 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() { cmdLineEnv.args = flag.Args() cmdLineEnv.server = *server cmdLineEnv.injson = *injson - cmdLineEnv.verbose = *verbose cmdLineEnv.ver = *ver } diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 180bea09..5e8bee2a 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -10,15 +10,15 @@ import ( "net" "net/url" "os" - "sort" - "strconv" "strings" + "time" + "github.com/olekukonko/tablewriter" + "github.com/yggdrasil-network/yggdrasil-go/src/admin" + "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) -type admin_info map[string]interface{} - func main() { // makes sure we can use defer and still return an error code to the OS os.Exit(run()) @@ -54,103 +54,13 @@ func run() int { cmdLineEnv.setEndpoint(logger) - conn := connect(cmdLineEnv.endpoint, logger) - logger.Println("Connected") - defer conn.Close() - - decoder := json.NewDecoder(conn) - encoder := json.NewEncoder(conn) - send := make(admin_info) - recv := make(admin_info) - - for c, a := range cmdLineEnv.args { - if c == 0 { - if strings.HasPrefix(a, "-") { - logger.Printf("Ignoring flag %s as it should be specified before other parameters\n", a) - continue - } - logger.Printf("Sending request: %v\n", a) - send["request"] = a - continue - } - tokens := strings.Split(a, "=") - if len(tokens) == 1 { - send[tokens[0]] = true - } else if len(tokens) > 2 { - send[tokens[0]] = strings.Join(tokens[1:], "=") - } else if len(tokens) == 2 { - if i, err := strconv.Atoi(tokens[1]); err == nil { - logger.Printf("Sending parameter %s: %d\n", tokens[0], i) - send[tokens[0]] = i - } else { - switch strings.ToLower(tokens[1]) { - case "true": - send[tokens[0]] = true - case "false": - send[tokens[0]] = false - default: - send[tokens[0]] = tokens[1] - } - logger.Printf("Sending parameter %s: %v\n", tokens[0], send[tokens[0]]) - } - } - } - - if err := encoder.Encode(&send); err != nil { - panic(err) - } - - logger.Printf("Request sent") - - if err := decoder.Decode(&recv); err == nil { - logger.Printf("Response received") - if recv["status"] == "error" { - if err, ok := recv["error"]; ok { - fmt.Println("Admin socket returned an error:", err) - } else { - fmt.Println("Admin socket returned an error but didn't specify any error text") - } - return 1 - } - if _, ok := recv["request"]; !ok { - fmt.Println("Missing request in response (malformed response?)") - return 1 - } - if _, ok := recv["response"]; !ok { - fmt.Println("Missing response body (malformed response?)") - return 1 - } - res := recv["response"].(map[string]interface{}) - - if cmdLineEnv.injson { - if json, err := json.MarshalIndent(res, "", " "); err == nil { - fmt.Println(string(json)) - } - return 0 - } - - handleAll(recv, cmdLineEnv.verbose) - } else { - logger.Println("Error receiving response:", err) - } - - if v, ok := recv["status"]; ok && v != "success" { - return 1 - } - - return 0 -} - -func connect(endpoint string, logger *log.Logger) net.Conn { var conn net.Conn - - u, err := url.Parse(endpoint) - + u, err := url.Parse(cmdLineEnv.endpoint) if err == nil { switch strings.ToLower(u.Scheme) { case "unix": - logger.Println("Connecting to UNIX socket", endpoint[7:]) - conn, err = net.Dial("unix", endpoint[7:]) + logger.Println("Connecting to UNIX socket", cmdLineEnv.endpoint[7:]) + conn, err = net.Dial("unix", cmdLineEnv.endpoint[7:]) case "tcp": logger.Println("Connecting to TCP socket", u.Host) conn, err = net.Dial("tcp", u.Host) @@ -160,298 +70,174 @@ func connect(endpoint string, logger *log.Logger) net.Conn { } } else { logger.Println("Connecting to TCP socket", u.Host) - conn, err = net.Dial("tcp", endpoint) + conn, err = net.Dial("tcp", cmdLineEnv.endpoint) } - if err != nil { panic(err) } - return conn -} + logger.Println("Connected") + defer conn.Close() -func handleAll(recv map[string]interface{}, verbose bool) { - req := recv["request"].(map[string]interface{}) - res := recv["response"].(map[string]interface{}) + decoder := json.NewDecoder(conn) + encoder := json.NewEncoder(conn) + send := &admin.AdminSocketRequest{} + recv := &admin.AdminSocketResponse{} - switch strings.ToLower(req["request"].(string)) { - case "dot": - handleDot(res) - case "list", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping": - handleVariousInfo(res, verbose) - case "gettuntap", "settuntap": - handleGetAndSetTunTap(res) - case "getself": - handleGetSelf(res, verbose) - case "getswitchqueues": - handleGetSwitchQueues(res) - case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute", "removesourcesubnet", "removeroute": - handleAddsAndRemoves(res) - case "getallowedencryptionpublickeys": - handleGetAllowedEncryptionPublicKeys(res) - case "getmulticastinterfaces": - handleGetMulticastInterfaces(res) - case "getsourcesubnets": - handleGetSourceSubnets(res) - case "getroutes": - handleGetRoutes(res) - case "settunnelrouting": - fallthrough - case "gettunnelrouting": - handleGetTunnelRouting(res) - default: - if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { + for c, a := range cmdLineEnv.args { + if c == 0 { + if strings.HasPrefix(a, "-") { + logger.Printf("Ignoring flag %s as it should be specified before other parameters\n", a) + continue + } + logger.Printf("Sending request: %v\n", a) + send.Name = a + continue + } + tokens := strings.SplitN(a, "=", 1) + switch { + case len(tokens) == 1: + panic("incomplete argument supplied") + default: + send.Arguments[tokens[0]] = tokens[1] + } + } + + if err := encoder.Encode(&send); err != nil { + panic(err) + } + logger.Printf("Request sent") + if err := decoder.Decode(&recv); err != nil { + panic(err) + } + if recv.Status == "error" { + if err := recv.Error; err != "" { + fmt.Println("Admin socket returned an error:", err) + } else { + fmt.Println("Admin socket returned an error but didn't specify any error text") + } + return 1 + } + if cmdLineEnv.injson { + if json, err := json.MarshalIndent(recv.Response, "", " "); err == nil { fmt.Println(string(json)) } + return 0 } -} - -func handleDot(res map[string]interface{}) { - fmt.Println(res["dot"]) -} - -func handleVariousInfo(res map[string]interface{}, verbose bool) { - maxWidths := make(map[string]int) - var keyOrder []string - keysOrdered := false - - for _, tlv := range res { - for slk, slv := range tlv.(map[string]interface{}) { - if !keysOrdered { - for k := range slv.(map[string]interface{}) { - if !verbose { - if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" { - continue - } - } - keyOrder = append(keyOrder, fmt.Sprint(k)) - } - sort.Strings(keyOrder) - keysOrdered = true - } - for k, v := range slv.(map[string]interface{}) { - if len(fmt.Sprint(slk)) > maxWidths["key"] { - maxWidths["key"] = len(fmt.Sprint(slk)) - } - if len(fmt.Sprint(v)) > maxWidths[k] { - maxWidths[k] = len(fmt.Sprint(v)) - if maxWidths[k] < len(k) { - maxWidths[k] = len(k) - } - } - } - } - - if len(keyOrder) > 0 { - fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", "") - for _, v := range keyOrder { - fmt.Printf("%-"+fmt.Sprint(maxWidths[v])+"s ", v) - } - fmt.Println() - } - - for slk, slv := range tlv.(map[string]interface{}) { - fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", slk) - for _, k := range keyOrder { - preformatted := slv.(map[string]interface{})[k] - var formatted string - switch k { - case "bytes_sent", "bytes_recvd": - formatted = fmt.Sprintf("%d", uint(preformatted.(float64))) - case "uptime", "last_seen": - seconds := uint(preformatted.(float64)) % 60 - minutes := uint(preformatted.(float64)/60) % 60 - hours := uint(preformatted.(float64) / 60 / 60) - formatted = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - default: - formatted = fmt.Sprint(preformatted) - } - fmt.Printf("%-"+fmt.Sprint(maxWidths[k])+"s ", formatted) - } - fmt.Println() - } - } -} - -func handleGetAndSetTunTap(res map[string]interface{}) { - for k, v := range res { - fmt.Println("Interface name:", k) - if mtu, ok := v.(map[string]interface{})["mtu"].(float64); ok { - fmt.Println("Interface MTU:", mtu) - } - if tap_mode, ok := v.(map[string]interface{})["tap_mode"].(bool); ok { - fmt.Println("TAP mode:", tap_mode) - } - } -} - -func handleGetSelf(res map[string]interface{}, verbose bool) { - for k, v := range res["self"].(map[string]interface{}) { - if buildname, ok := v.(map[string]interface{})["build_name"].(string); ok && buildname != "unknown" { - fmt.Println("Build name:", buildname) - } - if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok && buildversion != "unknown" { - fmt.Println("Build version:", buildversion) - } - fmt.Println("IPv6 address:", k) - if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok { - fmt.Println("IPv6 subnet:", subnet) - } - if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok { - fmt.Println("Public key:", boxSigKey) - } - if coords, ok := v.(map[string]interface{})["coords"].([]interface{}); ok { - fmt.Println("Coords:", coords) - } - if verbose { - if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok { - fmt.Println("Node ID:", nodeID) - } - if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { - fmt.Println("Public encryption key:", boxPubKey) - } - if boxSigKey, ok := v.(map[string]interface{})["box_sig_key"].(string); ok { - fmt.Println("Public signing key:", boxSigKey) - } - } - } -} - -func handleGetSwitchQueues(res map[string]interface{}) { - maximumqueuesize := float64(4194304) - portqueues := make(map[float64]float64) - portqueuesize := make(map[float64]float64) - portqueuepackets := make(map[float64]float64) - v := res["switchqueues"].(map[string]interface{}) - if queuecount, ok := v["queues_count"].(float64); ok { - fmt.Printf("Active queue count: %d queues\n", uint(queuecount)) - } - if queuesize, ok := v["queues_size"].(float64); ok { - fmt.Printf("Active queue size: %d bytes\n", uint(queuesize)) - } - if highestqueuecount, ok := v["highest_queues_count"].(float64); ok { - fmt.Printf("Highest queue count: %d queues\n", uint(highestqueuecount)) - } - if highestqueuesize, ok := v["highest_queues_size"].(float64); ok { - fmt.Printf("Highest queue size: %d bytes\n", uint(highestqueuesize)) - } - if m, ok := v["maximum_queues_size"].(float64); ok { - maximumqueuesize = m - fmt.Printf("Maximum queue size: %d bytes\n", uint(maximumqueuesize)) - } - if queues, ok := v["queues"].([]interface{}); ok { - if len(queues) != 0 { - fmt.Println("Active queues:") - for _, v := range queues { - queueport := v.(map[string]interface{})["queue_port"].(float64) - queuesize := v.(map[string]interface{})["queue_size"].(float64) - queuepackets := v.(map[string]interface{})["queue_packets"].(float64) - queueid := v.(map[string]interface{})["queue_id"].(string) - portqueues[queueport]++ - portqueuesize[queueport] += queuesize - portqueuepackets[queueport] += queuepackets - queuesizepercent := (100 / maximumqueuesize) * queuesize - fmt.Printf("- Switch port %d, Stream ID: %v, size: %d bytes (%d%% full), %d packets\n", - uint(queueport), []byte(queueid), uint(queuesize), - uint(queuesizepercent), uint(queuepackets)) - } - } - } - if len(portqueuesize) > 0 && len(portqueuepackets) > 0 { - fmt.Println("Aggregated statistics by switchport:") - for k, v := range portqueuesize { - queuesizepercent := (100 / (portqueues[k] * maximumqueuesize)) * v - fmt.Printf("- Switch port %d, size: %d bytes (%d%% full), %d packets\n", - uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k])) - } - } -} - -func handleAddsAndRemoves(res map[string]interface{}) { - if _, ok := res["added"]; ok { - for _, v := range res["added"].([]interface{}) { - fmt.Println("Added:", fmt.Sprint(v)) - } - } - if _, ok := res["not_added"]; ok { - for _, v := range res["not_added"].([]interface{}) { - fmt.Println("Not added:", fmt.Sprint(v)) - } - } - if _, ok := res["removed"]; ok { - for _, v := range res["removed"].([]interface{}) { - fmt.Println("Removed:", fmt.Sprint(v)) - } - } - if _, ok := res["not_removed"]; ok { - for _, v := range res["not_removed"].([]interface{}) { - fmt.Println("Not removed:", fmt.Sprint(v)) - } - } -} - -func handleGetAllowedEncryptionPublicKeys(res map[string]interface{}) { - if _, ok := res["allowed_box_pubs"]; !ok { - fmt.Println("All connections are allowed") - } else if res["allowed_box_pubs"] == nil { - fmt.Println("All connections are allowed") - } else { - fmt.Println("Connections are allowed only from the following public box keys:") - for _, v := range res["allowed_box_pubs"].([]interface{}) { - fmt.Println("-", v) - } - } -} - -func handleGetMulticastInterfaces(res map[string]interface{}) { - if _, ok := res["multicast_interfaces"]; !ok { - fmt.Println("No multicast interfaces found") - } else if res["multicast_interfaces"] == nil { - fmt.Println("No multicast interfaces found") - } else { - fmt.Println("Multicast peer discovery is active on:") - for _, v := range res["multicast_interfaces"].([]interface{}) { - fmt.Println("-", v) - } - } -} - -func handleGetSourceSubnets(res map[string]interface{}) { - if _, ok := res["source_subnets"]; !ok { - fmt.Println("No source subnets found") - } else if res["source_subnets"] == nil { - fmt.Println("No source subnets found") - } else { - fmt.Println("Source subnets:") - for _, v := range res["source_subnets"].([]interface{}) { - fmt.Println("-", v) - } - } -} - -func handleGetRoutes(res map[string]interface{}) { - if routes, ok := res["routes"].(map[string]interface{}); !ok { - fmt.Println("No routes found") - } else { - if res["routes"] == nil || len(routes) == 0 { - fmt.Println("No routes found") - } else { - fmt.Println("Routes:") - for k, v := range routes { - if pv, ok := v.(string); ok { - fmt.Println("-", k, " via ", pv) - } - } - } - } -} - -func handleGetTunnelRouting(res map[string]interface{}) { - if enabled, ok := res["enabled"].(bool); !ok { - fmt.Println("Tunnel routing is disabled") - } else if !enabled { - fmt.Println("Tunnel routing is disabled") - } else { - fmt.Println("Tunnel routing is enabled") - } + + table := tablewriter.NewWriter(os.Stdout) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetAutoFormatHeaders(false) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetTablePadding("\t") // pad with tabs + table.SetNoWhiteSpace(true) + + switch strings.ToLower(recv.Request.Name) { + case "list": + var resp admin.ListResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.SetHeader([]string{"Command", "Arguments"}) + for _, entry := range resp.List { + table.Append([]string{entry.Command, strings.Join(entry.Fields, ", ")}) + } + table.Render() + + case "getself": + var resp admin.GetSelfResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.Append([]string{"Build name:", resp.BuildName}) + table.Append([]string{"Build version:", resp.BuildVersion}) + table.Append([]string{"IPv6 address:", resp.IPAddress}) + table.Append([]string{"IPv6 subnet:", resp.Subnet}) + table.Append([]string{"Coordinates:", fmt.Sprintf("%v", resp.Coords)}) + table.Append([]string{"Public key:", resp.PublicKey}) + table.Render() + + case "getpeers": + var resp admin.GetPeersResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.SetHeader([]string{"Port", "Public Key", "IP Address", "Peering URI", "Uptime", "RX", "TX"}) + for _, peer := range resp.Peers { + table.Append([]string{ + fmt.Sprintf("%d", peer.Port), + peer.PublicKey, + peer.IPAddress, + peer.Remote, + (time.Duration(peer.Uptime) * time.Second).String(), + peer.RXBytes.String(), + peer.TXBytes.String(), + }) + } + table.Render() + + case "getdht": + var resp admin.GetDHTResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"}) + for _, dht := range resp.DHT { + table.Append([]string{ + dht.PublicKey, + dht.IPAddress, + fmt.Sprintf("%d", dht.Port), + fmt.Sprintf("%d", dht.Rest), + }) + } + table.Render() + + case "getpaths": + var resp admin.GetPathsResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.SetHeader([]string{"Public Key", "IP Address", "Path"}) + for _, p := range resp.Paths { + table.Append([]string{ + p.PublicKey, + p.IPAddress, + fmt.Sprintf("%v", p.Path), + }) + } + table.Render() + + case "getsessions": + var resp admin.GetSessionsResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.SetHeader([]string{"Public Key", "IP Address"}) + for _, p := range resp.Sessions { + table.Append([]string{ + p.PublicKey, + p.IPAddress, + }) + } + table.Render() + + case "getnodeinfo": + var resp core.GetNodeInfoResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + for _, v := range resp { + fmt.Println(string(v)) + break + } + + default: + panic("unknown response type: " + recv.Request.Name) + } + + return 0 } diff --git a/go.mod b/go.mod index 11170a1b..c790b19d 100644 --- a/go.mod +++ b/go.mod @@ -13,23 +13,27 @@ require ( github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 - golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 - golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 + golang.org/x/net v0.0.0-20220722155237-a158d28d115b + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a golang.zx2c4.com/wireguard/windows v0.4.12 ) +require ( + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/tools v0.1.12 // indirect +) + require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/fatih/color v1.12.0 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 + github.com/onsi/gomega v1.20.2 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect - golang.org/x/mod v0.4.2 // indirect - golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/go.sum b/go.sum index 0fc5cadd..5d22d679 100644 --- a/go.sum +++ b/go.sum @@ -8,15 +8,40 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw= github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAENALA= github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= @@ -26,23 +51,48 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= +github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -50,59 +100,105 @@ golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9t golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= -golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc= -golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0= golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 h1:YuekqPskqwCCPM79F1X5Dhv4ezTCj+Ki1oNwiafxkA0= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw= golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA= golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/admin/admin.go b/src/admin/admin.go index c7c0f148..9a00d8a1 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -7,6 +7,7 @@ import ( "net" "net/url" "os" + "sort" "strings" "time" @@ -28,13 +29,17 @@ type AdminSocket struct { done chan struct{} } +type AdminSocketRequest struct { + Name string `json:"request"` + Arguments map[string]string `json:"arguments,omitempty"` + KeepAlive bool `json:"keepalive,omitempty"` +} + type AdminSocketResponse struct { - Status string `json:"status"` - Request struct { - Name string `json:"request"` - KeepAlive bool `json:"keepalive"` - } `json:"request"` - Response interface{} `json:"response"` + Status string `json:"status"` + Error string `json:"error,omitempty"` + Request AdminSocketRequest `json:"request"` + Response json.RawMessage `json:"response"` } type handler struct { @@ -43,11 +48,12 @@ type handler struct { } type ListResponse struct { - List map[string]ListEntry `json:"list"` + List []ListEntry `json:"list"` } type ListEntry struct { - Fields []string `json:"fields"` + Command string `json:"command"` + Fields []string `json:"fields,omitempty"` } // AddHandler is called for each admin function to add the handler and help documentation to the API. @@ -73,14 +79,16 @@ func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, a.done = make(chan struct{}) close(a.done) // Start in a done / not-started state _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { - res := &ListResponse{ - List: map[string]ListEntry{}, - } + res := &ListResponse{} for name, handler := range a.handlers { - res.List[name] = ListEntry{ - Fields: handler.args, - } + res.List = append(res.List, ListEntry{ + Command: name, + Fields: handler.args, + }) } + sort.SliceStable(res.List, func(i, j int) bool { + return strings.Compare(res.List[i].Command, res.List[j].Command) < 0 + }) return res, nil }) return a.core.SetAdmin(a) @@ -277,22 +285,28 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { if err = json.Unmarshal(buf, &resp.Request); err == nil { if resp.Request.Name == "" { resp.Status = "error" - resp.Response = &ErrorResponse{ + resp.Response, _ = json.Marshal(ErrorResponse{ Error: "No request specified", - } + }) } else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok { - resp.Response, err = h.handler(buf) + res, err := h.handler(buf) if err != nil { resp.Status = "error" - resp.Response = &ErrorResponse{ + resp.Response, _ = json.Marshal(ErrorResponse{ Error: err.Error(), - } + }) + } + if resp.Response, err = json.Marshal(res); err != nil { + resp.Status = "error" + resp.Response, _ = json.Marshal(ErrorResponse{ + Error: err.Error(), + }) } } else { resp.Status = "error" - resp.Response = &ErrorResponse{ + resp.Response, _ = json.Marshal(ErrorResponse{ Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name), - } + }) } } if err = encoder.Encode(resp); err != nil { @@ -305,3 +319,16 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { } } } + +type DataUnit uint64 + +func (d DataUnit) String() string { + switch { + case d > 1024*1024*1024: + return fmt.Sprintf("%2.fgb", float64(d)/1024/1024/1024) + case d > 1024*1024: + return fmt.Sprintf("%2.fmb", float64(d)/1024/1024) + default: + return fmt.Sprintf("%2.fkb", float64(d)/1024) + } +} diff --git a/src/admin/getdht.go b/src/admin/getdht.go index 5dc95547..bfb21818 100644 --- a/src/admin/getdht.go +++ b/src/admin/getdht.go @@ -3,6 +3,8 @@ package admin import ( "encoding/hex" "net" + "sort" + "strings" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) @@ -10,25 +12,30 @@ import ( type GetDHTRequest struct{} type GetDHTResponse struct { - DHT map[string]DHTEntry `json:"dht"` + DHT []DHTEntry `json:"dht"` } type DHTEntry struct { + IPAddress string `json:"address"` PublicKey string `json:"key"` Port uint64 `json:"port"` Rest uint64 `json:"rest"` } func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error { - res.DHT = map[string]DHTEntry{} - for _, d := range a.core.GetDHT() { + dht := a.core.GetDHT() + res.DHT = make([]DHTEntry, 0, len(dht)) + for _, d := range dht { addr := address.AddrForKey(d.Key) - so := net.IP(addr[:]).String() - res.DHT[so] = DHTEntry{ + res.DHT = append(res.DHT, DHTEntry{ + IPAddress: net.IP(addr[:]).String(), PublicKey: hex.EncodeToString(d.Key[:]), Port: d.Port, Rest: d.Rest, - } + }) } + sort.SliceStable(res.DHT, func(i, j int) bool { + return strings.Compare(res.DHT[i].PublicKey, res.DHT[j].PublicKey) < 0 + }) return nil } diff --git a/src/admin/getpaths.go b/src/admin/getpaths.go index c8e97d01..fbd52481 100644 --- a/src/admin/getpaths.go +++ b/src/admin/getpaths.go @@ -3,6 +3,8 @@ package admin import ( "encoding/hex" "net" + "sort" + "strings" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) @@ -11,23 +13,28 @@ type GetPathsRequest struct { } type GetPathsResponse struct { - Paths map[string]PathEntry `json:"paths"` + Paths []PathEntry `json:"paths"` } type PathEntry struct { + IPAddress string `json:"address"` PublicKey string `json:"key"` Path []uint64 `json:"path"` } func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error { - res.Paths = map[string]PathEntry{} - for _, p := range a.core.GetPaths() { + paths := a.core.GetPaths() + res.Paths = make([]PathEntry, 0, len(paths)) + for _, p := range paths { addr := address.AddrForKey(p.Key) - so := net.IP(addr[:]).String() - res.Paths[so] = PathEntry{ + res.Paths = append(res.Paths, PathEntry{ + IPAddress: net.IP(addr[:]).String(), PublicKey: hex.EncodeToString(p.Key), Path: p.Path, - } + }) } + sort.SliceStable(res.Paths, func(i, j int) bool { + return strings.Compare(res.Paths[i].PublicKey, res.Paths[j].PublicKey) < 0 + }) return nil } diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go index ecb28726..61d0937f 100644 --- a/src/admin/getpeers.go +++ b/src/admin/getpeers.go @@ -3,6 +3,7 @@ package admin import ( "encoding/hex" "net" + "sort" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) @@ -11,33 +12,38 @@ type GetPeersRequest struct { } type GetPeersResponse struct { - Peers map[string]PeerEntry `json:"peers"` + Peers []PeerEntry `json:"peers"` } type PeerEntry struct { + IPAddress string `json:"address"` PublicKey string `json:"key"` Port uint64 `json:"port"` Coords []uint64 `json:"coords"` Remote string `json:"remote"` - RXBytes uint64 `json:"bytes_recvd"` - TXBytes uint64 `json:"bytes_sent"` + RXBytes DataUnit `json:"bytes_recvd"` + TXBytes DataUnit `json:"bytes_sent"` Uptime float64 `json:"uptime"` } func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error { - res.Peers = map[string]PeerEntry{} - for _, p := range a.core.GetPeers() { + peers := a.core.GetPeers() + res.Peers = make([]PeerEntry, 0, len(peers)) + for _, p := range peers { addr := address.AddrForKey(p.Key) - so := net.IP(addr[:]).String() - res.Peers[so] = PeerEntry{ + res.Peers = append(res.Peers, PeerEntry{ + IPAddress: net.IP(addr[:]).String(), PublicKey: hex.EncodeToString(p.Key), Port: p.Port, Coords: p.Coords, Remote: p.Remote, - RXBytes: p.RXBytes, - TXBytes: p.TXBytes, + RXBytes: DataUnit(p.RXBytes), + TXBytes: DataUnit(p.TXBytes), Uptime: p.Uptime.Seconds(), - } + }) } + sort.Slice(res.Peers, func(i, j int) bool { + return res.Peers[i].Port < res.Peers[j].Port + }) return nil } diff --git a/src/admin/getself.go b/src/admin/getself.go index 7effcc46..f42dc750 100644 --- a/src/admin/getself.go +++ b/src/admin/getself.go @@ -9,28 +9,22 @@ import ( type GetSelfRequest struct{} type GetSelfResponse struct { - Self map[string]SelfEntry `json:"self"` -} - -type SelfEntry struct { BuildName string `json:"build_name"` BuildVersion string `json:"build_version"` PublicKey string `json:"key"` + IPAddress string `json:"address"` Coords []uint64 `json:"coords"` Subnet string `json:"subnet"` } func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error { - res.Self = make(map[string]SelfEntry) self := a.core.GetSelf() - addr := a.core.Address().String() snet := a.core.Subnet() - res.Self[addr] = SelfEntry{ - BuildName: version.BuildName(), - BuildVersion: version.BuildVersion(), - PublicKey: hex.EncodeToString(self.Key[:]), - Subnet: snet.String(), - Coords: self.Coords, - } + res.BuildName = version.BuildName() + res.BuildVersion = version.BuildVersion() + res.PublicKey = hex.EncodeToString(self.Key[:]) + res.IPAddress = a.core.Address().String() + res.Subnet = snet.String() + res.Coords = self.Coords return nil } diff --git a/src/admin/getsessions.go b/src/admin/getsessions.go index 3a0c19b6..58ce378e 100644 --- a/src/admin/getsessions.go +++ b/src/admin/getsessions.go @@ -3,6 +3,8 @@ package admin import ( "encoding/hex" "net" + "sort" + "strings" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) @@ -10,21 +12,26 @@ import ( type GetSessionsRequest struct{} type GetSessionsResponse struct { - Sessions map[string]SessionEntry `json:"sessions"` + Sessions []SessionEntry `json:"sessions"` } type SessionEntry struct { + IPAddress string `json:"address"` PublicKey string `json:"key"` } func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error { - res.Sessions = map[string]SessionEntry{} - for _, s := range a.core.GetSessions() { + sessions := a.core.GetSessions() + res.Sessions = make([]SessionEntry, 0, len(sessions)) + for _, s := range sessions { addr := address.AddrForKey(s.Key) - so := net.IP(addr[:]).String() - res.Sessions[so] = SessionEntry{ + res.Sessions = append(res.Sessions, SessionEntry{ + IPAddress: net.IP(addr[:]).String(), PublicKey: hex.EncodeToString(s.Key[:]), - } + }) } + sort.SliceStable(res.Sessions, func(i, j int) bool { + return strings.Compare(res.Sessions[i].PublicKey, res.Sessions[j].PublicKey) < 0 + }) return nil } diff --git a/src/core/nodeinfo.go b/src/core/nodeinfo.go index 4ca21d73..a6132ec2 100644 --- a/src/core/nodeinfo.go +++ b/src/core/nodeinfo.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "net" "runtime" "strings" "time" @@ -13,7 +12,7 @@ import ( "github.com/Arceliar/phony" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/version" ) @@ -154,7 +153,7 @@ func (m *nodeinfo) _sendRes(key keyArray) { type GetNodeInfoRequest struct { Key string `json:"key"` } -type GetNodeInfoResponse map[string]interface{} +type GetNodeInfoResponse map[string]json.RawMessage func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) { var req GetNodeInfoRequest @@ -182,8 +181,8 @@ func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) if err := msg.UnmarshalJSON(info); err != nil { return nil, err } - ip := net.IP(address.AddrForKey(kbs)[:]) - res := GetNodeInfoResponse{ip.String(): msg} + key := hex.EncodeToString(kbs[:]) + res := GetNodeInfoResponse{key: msg} return res, nil } } From dad0b10dfeb801049c6473e047fd22ff0e7b8e61 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 10:51:44 +0100 Subject: [PATCH 16/40] Move `Core._applyOption` --- src/core/core.go | 21 --------------------- src/core/options.go | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/core/core.go b/src/core/core.go index 37a1d841..bc7ac831 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -88,27 +88,6 @@ func New(secret ed25519.PrivateKey, opts ...SetupOption) (*Core, error) { return c, nil } -func (c *Core) _applyOption(opt SetupOption) { - switch v := opt.(type) { - case Peer: - c.config._peers[v] = struct{}{} - case ListenAddress: - c.config._listeners[v] = struct{}{} - case NodeInfo: - c.config.nodeinfo = v - case NodeInfoPrivacy: - c.config.nodeinfoPrivacy = v - case IfName: - c.config.ifname = v - case IfMTU: - c.config.ifmtu = v - case AllowedPublicKey: - pk := [32]byte{} - copy(pk[:], v) - c.config._allowedPublicKeys[pk] = struct{}{} - } -} - // If any static peers were provided in the configuration above then we should // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. diff --git a/src/core/options.go b/src/core/options.go index 0210a4ef..d46c9def 100644 --- a/src/core/options.go +++ b/src/core/options.go @@ -4,6 +4,27 @@ import ( "crypto/ed25519" ) +func (c *Core) _applyOption(opt SetupOption) { + switch v := opt.(type) { + case Peer: + c.config._peers[v] = struct{}{} + case ListenAddress: + c.config._listeners[v] = struct{}{} + case NodeInfo: + c.config.nodeinfo = v + case NodeInfoPrivacy: + c.config.nodeinfoPrivacy = v + case IfName: + c.config.ifname = v + case IfMTU: + c.config.ifmtu = v + case AllowedPublicKey: + pk := [32]byte{} + copy(pk[:], v) + c.config._allowedPublicKeys[pk] = struct{}{} + } +} + type SetupOption interface { isSetupOption() } From 493208fb378b76760c8cf6b7625ccbe0b60185d3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 11:42:05 +0100 Subject: [PATCH 17/40] Refactor multicast setup (isolated config, etc) --- cmd/yggdrasil/main.go | 76 +++++++++------ contrib/mobile/mobile.go | 75 +++++++++------ src/core/api.go | 10 +- src/core/core.go | 8 +- src/core/core_test.go | 5 +- src/core/options.go | 16 ++-- src/multicast/multicast.go | 131 ++++++++++++-------------- src/multicast/multicast_darwin_cgo.go | 2 +- src/multicast/options.go | 28 ++++++ src/util/util.go | 14 +++ 10 files changed, 215 insertions(+), 150 deletions(-) create mode 100644 src/multicast/options.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 826df007..71453aaa 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -12,6 +12,7 @@ import ( "net" "os" "os/signal" + "regexp" "strings" "syscall" @@ -325,40 +326,59 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { default: } - // Setup the Yggdrasil node itself. The node{} type includes a Core, so we - // don't need to create this manually. - sk, err := hex.DecodeString(cfg.PrivateKey) - if err != nil { - panic(err) - } - options := []core.SetupOption{ - core.IfName(cfg.IfName), - core.IfMTU(cfg.IfMTU), - } - for _, peer := range cfg.Peers { - options = append(options, core.Peer{URI: peer}) - } - for intf, peers := range cfg.InterfacePeers { - for _, peer := range peers { - options = append(options, core.Peer{URI: peer, SourceInterface: intf}) - } - } - for _, allowed := range cfg.AllowedPublicKeys { - k, err := hex.DecodeString(allowed) + n := &node{config: cfg} + + // Setup the Yggdrasil node itself. + { + sk, err := hex.DecodeString(cfg.PrivateKey) + if err != nil { + panic(err) + } + options := []core.SetupOption{ + core.IfName(cfg.IfName), + core.IfMTU(cfg.IfMTU), + } + for _, peer := range cfg.Peers { + options = append(options, core.Peer{URI: peer}) + } + for intf, peers := range cfg.InterfacePeers { + for _, peer := range peers { + options = append(options, core.Peer{URI: peer, SourceInterface: intf}) + } + } + for _, allowed := range cfg.AllowedPublicKeys { + k, err := hex.DecodeString(allowed) + if err != nil { + panic(err) + } + options = append(options, core.AllowedPublicKey(k[:])) + } + n.core, err = core.New(sk[:], logger, options...) if err != nil { panic(err) } - options = append(options, core.AllowedPublicKey(k[:])) } - n := node{config: cfg} - n.core, err = core.New(sk[:], options...) - if err != nil { - panic(err) + + // Setup the multicast module. + { + options := []multicast.SetupOption{} + for _, intf := range cfg.MulticastInterfaces { + options = append(options, multicast.MulticastInterface{ + Regex: regexp.MustCompile(intf.Regex), + Beacon: intf.Beacon, + Listen: intf.Listen, + Port: intf.Port, + }) + } + n.multicast, err = multicast.New(n.core, logger, options...) + if err != nil { + panic(err) + } } + // Register the session firewall gatekeeper function // Allocate our modules n.admin = &admin.AdminSocket{} - n.multicast = &multicast.Multicast{} n.tuntap = &tuntap.TunAdapter{} // Start the admin socket if err := n.admin.Init(n.core, cfg, logger, nil); err != nil { @@ -368,10 +388,8 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { } n.admin.SetupAdminHandlers() // Start the multicast interface - if err := n.multicast.Init(n.core, cfg, logger, nil); err != nil { + if n.multicast, err = multicast.New(n.core, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) - } else if err := n.multicast.Start(); err != nil { - logger.Errorln("An error occurred starting multicast:", err) } n.multicast.SetupAdminHandlers(n.admin) // Start the TUN/TAP interface diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index 85535ee8..d7da03de 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net" + "regexp" "github.com/gologme/log" @@ -28,7 +29,7 @@ type Yggdrasil struct { core *core.Core iprwc *ipv6rwc.ReadWriteCloser config *config.NodeConfig - multicast multicast.Multicast + multicast *multicast.Multicast log MobileLogger } @@ -49,46 +50,60 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { return err } // Setup the Yggdrasil node itself. - sk, err := hex.DecodeString(m.config.PrivateKey) - if err != nil { - panic(err) - } - options := []core.SetupOption{ - core.IfName("none"), - core.IfMTU(m.config.IfMTU), - } - for _, peer := range m.config.Peers { - options = append(options, core.Peer{URI: peer}) - } - for intf, peers := range m.config.InterfacePeers { - for _, peer := range peers { - options = append(options, core.Peer{URI: peer, SourceInterface: intf}) - } - } - for _, allowed := range m.config.AllowedPublicKeys { - k, err := hex.DecodeString(allowed) + { + sk, err := hex.DecodeString(m.config.PrivateKey) + if err != nil { + panic(err) + } + options := []core.SetupOption{ + core.IfName("none"), + core.IfMTU(m.config.IfMTU), + } + for _, peer := range m.config.Peers { + options = append(options, core.Peer{URI: peer}) + } + for intf, peers := range m.config.InterfacePeers { + for _, peer := range peers { + options = append(options, core.Peer{URI: peer, SourceInterface: intf}) + } + } + for _, allowed := range m.config.AllowedPublicKeys { + k, err := hex.DecodeString(allowed) + if err != nil { + panic(err) + } + options = append(options, core.AllowedPublicKey(k[:])) + } + m.core, err = core.New(sk[:], logger, options...) if err != nil { panic(err) } - options = append(options, core.AllowedPublicKey(k[:])) } - m.core, err = core.New(sk[:], options...) - if err != nil { - panic(err) + + // Setup the multicast module. + if len(m.config.MulticastInterfaces) > 0 { + var err error + options := []multicast.SetupOption{} + for _, intf := range m.config.MulticastInterfaces { + options = append(options, multicast.MulticastInterface{ + Regex: regexp.MustCompile(intf.Regex), + Beacon: intf.Beacon, + Listen: intf.Listen, + Port: intf.Port, + }) + } + m.multicast, err = multicast.New(m.core, logger, options...) + if err != nil { + panic(err) + } } + mtu := m.config.IfMTU m.iprwc = ipv6rwc.NewReadWriteCloser(m.core) if m.iprwc.MaxMTU() < mtu { mtu = m.iprwc.MaxMTU() } m.iprwc.SetMTU(mtu) - if len(m.config.MulticastInterfaces) > 0 { - if err := m.multicast.Init(m.core, m.config, logger, nil); err != nil { - logger.Errorln("An error occurred initialising multicast:", err) - } else if err := m.multicast.Start(); err != nil { - logger.Errorln("An error occurred starting multicast:", err) - } - } return nil } diff --git a/src/core/api.go b/src/core/api.go index 3ab26ee5..679d43d5 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -15,8 +15,8 @@ import ( //"sort" //"time" - "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/util" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" //"github.com/Arceliar/phony" ) @@ -159,7 +159,7 @@ func (c *Core) Subnet() net.IPNet { // may be useful if you want to redirect the output later. Note that this // expects a Logger from the github.com/gologme/log package and not from Go's // built-in log package. -func (c *Core) SetLogger(log *log.Logger) { +func (c *Core) SetLogger(log util.Logger) { c.log = log } @@ -239,8 +239,10 @@ func (c *Core) RemovePeer(addr string, sintf string) error { // CallPeer calls a peer once. This should be specified in the peer URI format, // e.g.: -// tcp://a.b.c.d:e -// socks://a.b.c.d:e/f.g.h.i:j +// +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// // This does not add the peer to the peer list, so if the connection drops, the // peer will not be called again automatically. func (c *Core) CallPeer(u *url.URL, sintf string) error { diff --git a/src/core/core.go b/src/core/core.go index bc7ac831..47389a83 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -7,7 +7,6 @@ import ( "io" "net" "net/url" - "os" "time" iwe "github.com/Arceliar/ironwood/encrypted" @@ -15,6 +14,7 @@ import ( "github.com/Arceliar/phony" "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/version" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -33,7 +33,7 @@ type Core struct { public ed25519.PublicKey links links proto protoHandler - log *log.Logger + log util.Logger addPeerTimer *time.Timer config struct { _peers map[Peer]struct{} // configurable after startup @@ -46,14 +46,14 @@ type Core struct { } } -func New(secret ed25519.PrivateKey, opts ...SetupOption) (*Core, error) { +func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*Core, error) { if len(secret) != ed25519.PrivateKeySize { return nil, fmt.Errorf("private key is incorrect length") } c := &Core{ secret: secret, public: secret.Public().(ed25519.PublicKey), - log: log.New(os.Stdout, "", 0), // TODO: not this + log: logger, } c.ctx, c.cancel = context.WithCancel(context.Background()) var err error diff --git a/src/core/core_test.go b/src/core/core_test.go index 823e5e95..ed5b4255 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -36,10 +36,11 @@ func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) if _, skB, err = ed25519.GenerateKey(nil); err != nil { t.Fatal(err) } - if nodeA, err = New(skA, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { + logger := GetLoggerWithPrefix("", false) + if nodeA, err = New(skA, logger, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { t.Fatal(err) } - if nodeB, err = New(skB, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { + if nodeB, err = New(skB, logger, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { t.Fatal(err) } diff --git a/src/core/options.go b/src/core/options.go index d46c9def..b3d06c63 100644 --- a/src/core/options.go +++ b/src/core/options.go @@ -30,7 +30,6 @@ type SetupOption interface { } type ListenAddress string -type AdminListenAddress string type Peer struct { URI string SourceInterface string @@ -41,11 +40,10 @@ type IfName string type IfMTU uint16 type AllowedPublicKey ed25519.PublicKey -func (a ListenAddress) isSetupOption() {} -func (a AdminListenAddress) isSetupOption() {} -func (a Peer) isSetupOption() {} -func (a NodeInfo) isSetupOption() {} -func (a NodeInfoPrivacy) isSetupOption() {} -func (a IfName) isSetupOption() {} -func (a IfMTU) isSetupOption() {} -func (a AllowedPublicKey) isSetupOption() {} +func (a ListenAddress) isSetupOption() {} +func (a Peer) isSetupOption() {} +func (a NodeInfo) isSetupOption() {} +func (a NodeInfoPrivacy) isSetupOption() {} +func (a IfName) isSetupOption() {} +func (a IfMTU) isSetupOption() {} +func (a AllowedPublicKey) isSetupOption() {} diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 9093e4cf..b6d290e1 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -9,13 +9,11 @@ import ( "fmt" "net" "net/url" - "regexp" "time" "github.com/Arceliar/phony" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/core" "golang.org/x/net/ipv6" ) @@ -27,13 +25,15 @@ import ( type Multicast struct { phony.Inbox core *core.Core - config *config.NodeConfig log *log.Logger sock *ipv6.PacketConn - groupAddr string - listeners map[string]*listenerInfo - isOpen bool - _interfaces map[string]interfaceInfo + _isOpen bool + _listeners map[string]*listenerInfo + _interfaces map[string]*interfaceInfo + config struct { + _groupAddr GroupAddress + _interfaces map[MulticastInterface]struct{} + } } type interfaceInfo struct { @@ -51,40 +51,38 @@ type listenerInfo struct { port uint16 } -// Init prepares the multicast interface for use. -func (m *Multicast) Init(core *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { - m.core = core - m.config = nc - m.log = log - m.listeners = make(map[string]*listenerInfo) - m._interfaces = make(map[string]interfaceInfo) - m.groupAddr = "[ff02::114]:9001" - return nil -} - // Start starts the multicast interface. This launches goroutines which will // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. -func (m *Multicast) Start() error { +func New(core *core.Core, log *log.Logger, opts ...SetupOption) (*Multicast, error) { + m := &Multicast{ + core: core, + log: log, + _listeners: make(map[string]*listenerInfo), + _interfaces: make(map[string]*interfaceInfo), + } + m.config._interfaces = map[MulticastInterface]struct{}{} + m.config._groupAddr = GroupAddress("[ff02::114]:9001") + for _, opt := range opts { + m._applyOption(opt) + } var err error phony.Block(m, func() { err = m._start() }) - m.log.Debugln("Started multicast module") - return err + return m, err } func (m *Multicast) _start() error { - if m.isOpen { + if m._isOpen { return fmt.Errorf("multicast module is already started") } - m.config.RLock() - defer m.config.RUnlock() - if len(m.config.MulticastInterfaces) == 0 { + if len(m.config._interfaces) == 0 { return nil } m.log.Infoln("Starting multicast module") - addr, err := net.ResolveUDPAddr("udp", m.groupAddr) + defer m.log.Infoln("Started multicast module") + addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr)) if err != nil { return err } @@ -101,7 +99,7 @@ func (m *Multicast) _start() error { // Windows can't set this flag, so we need to handle it in other ways } - m.isOpen = true + m._isOpen = true go m.listen() m.Act(nil, m._multicastStarted) m.Act(nil, m._announce) @@ -113,7 +111,7 @@ func (m *Multicast) _start() error { func (m *Multicast) IsStarted() bool { var isOpen bool phony.Block(m, func() { - isOpen = m.isOpen + isOpen = m._isOpen }) return isOpen } @@ -130,7 +128,7 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") - m.isOpen = false + m._isOpen = false if m.sock != nil { m.sock.Close() } @@ -138,7 +136,7 @@ func (m *Multicast) _stop() error { } func (m *Multicast) _updateInterfaces() { - interfaces := m.getAllowedInterfaces() + interfaces := m._getAllowedInterfaces() for name, info := range interfaces { addrs, err := info.iface.Addrs() if err != nil { @@ -163,10 +161,8 @@ func (m *Multicast) Interfaces() map[string]net.Interface { } // getAllowedInterfaces returns the currently known/enabled multicast interfaces. -func (m *Multicast) getAllowedInterfaces() map[string]interfaceInfo { - interfaces := make(map[string]interfaceInfo) - // Get interface expressions from config - ifcfgs := m.config.MulticastInterfaces +func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo { + interfaces := make(map[string]*interfaceInfo) // Ask the system for network interfaces allifaces, err := net.Interfaces() if err != nil { @@ -176,62 +172,55 @@ func (m *Multicast) getAllowedInterfaces() map[string]interfaceInfo { } // Work out which interfaces to announce on for _, iface := range allifaces { - if iface.Flags&net.FlagUp == 0 { - // Ignore interfaces that are down - continue + switch { + case iface.Flags&net.FlagUp == 0: + continue // Ignore interfaces that are down + case iface.Flags&net.FlagMulticast == 0: + continue // Ignore non-multicast interfaces + case iface.Flags&net.FlagPointToPoint != 0: + continue // Ignore point-to-point interfaces } - if iface.Flags&net.FlagMulticast == 0 { - // Ignore non-multicast interfaces - continue - } - if iface.Flags&net.FlagPointToPoint != 0 { - // Ignore point-to-point interfaces - continue - } - for _, ifcfg := range ifcfgs { + for ifcfg := range m.config._interfaces { // Compile each regular expression - e, err := regexp.Compile(ifcfg.Regex) - if err != nil { - panic(err) - } // Does the interface match the regular expression? Store it if so - if e.MatchString(iface.Name) { - if ifcfg.Beacon || ifcfg.Listen { - info := interfaceInfo{ - iface: iface, - beacon: ifcfg.Beacon, - listen: ifcfg.Listen, - port: ifcfg.Port, - } - interfaces[iface.Name] = info - } - break + if !ifcfg.Beacon && !ifcfg.Listen { + continue } + if !ifcfg.Regex.MatchString(iface.Name) { + continue + } + interfaces[iface.Name] = &interfaceInfo{ + iface: iface, + beacon: ifcfg.Beacon, + listen: ifcfg.Listen, + port: ifcfg.Port, + } + break } } return interfaces } func (m *Multicast) _announce() { - if !m.isOpen { + if !m._isOpen { return } m._updateInterfaces() - groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + groupAddr, err := net.ResolveUDPAddr("udp6", string(m.config._groupAddr)) if err != nil { panic(err) } - destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + destAddr, err := net.ResolveUDPAddr("udp6", string(m.config._groupAddr)) if err != nil { panic(err) } // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners - for name, info := range m.listeners { + for name, info := range m._listeners { // Prepare our stop function! stop := func() { info.listener.Stop() - delete(m.listeners, name) + delete(m._listeners, name) m.log.Debugln("No longer multicasting on", name) } // If the interface is no longer visible on the system then stop the @@ -290,7 +279,7 @@ func (m *Multicast) _announce() { } // Try and see if we already have a TCP listener for this interface var linfo *listenerInfo - if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { + if nfo, ok := m._listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port) u, err := url.Parse(urlString) @@ -301,13 +290,13 @@ func (m *Multicast) _announce() { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port} - m.listeners[iface.Name] = linfo + m._listeners[iface.Name] = linfo } else { m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) } } else { // An existing listener was found - linfo = m.listeners[iface.Name] + linfo = m._listeners[iface.Name] } // Make sure nothing above failed for some reason if linfo == nil { @@ -340,7 +329,7 @@ func (m *Multicast) _announce() { } func (m *Multicast) listen() { - groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + groupAddr, err := net.ResolveUDPAddr("udp6", string(m.config._groupAddr)) if err != nil { panic(err) } @@ -388,7 +377,7 @@ func (m *Multicast) listen() { if !from.IP.Equal(addr.IP) { continue } - var interfaces map[string]interfaceInfo + var interfaces map[string]*interfaceInfo phony.Block(m, func() { interfaces = m._interfaces }) diff --git a/src/multicast/multicast_darwin_cgo.go b/src/multicast/multicast_darwin_cgo.go index b7d7358c..5c2af7ab 100644 --- a/src/multicast/multicast_darwin_cgo.go +++ b/src/multicast/multicast_darwin_cgo.go @@ -31,7 +31,7 @@ import ( ) func (m *Multicast) _multicastStarted() { - if !m.isOpen { + if !m._isOpen { return } C.StopAWDLBrowsing() diff --git a/src/multicast/options.go b/src/multicast/options.go new file mode 100644 index 00000000..a03b0677 --- /dev/null +++ b/src/multicast/options.go @@ -0,0 +1,28 @@ +package multicast + +import "regexp" + +func (m *Multicast) _applyOption(opt SetupOption) { + switch v := opt.(type) { + case MulticastInterface: + m.config._interfaces[v] = struct{}{} + case GroupAddress: + m.config._groupAddr = v + } +} + +type SetupOption interface { + isSetupOption() +} + +type MulticastInterface struct { + Regex *regexp.Regexp + Beacon bool + Listen bool + Port uint16 +} + +type GroupAddress string + +func (a MulticastInterface) isSetupOption() {} +func (a GroupAddress) isSetupOption() {} diff --git a/src/util/util.go b/src/util/util.go index 507426d0..e2e21464 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -8,6 +8,20 @@ import ( "time" ) +// Any logger that satisfies this interface is suitable for Yggdrasil. +type Logger interface { + Printf(string, ...interface{}) + Println(...interface{}) + Infof(string, ...interface{}) + Infoln(...interface{}) + Warnf(string, ...interface{}) + Warnln(...interface{}) + Errorf(string, ...interface{}) + Errorln(...interface{}) + Debugf(string, ...interface{}) + Debugln(...interface{}) +} + // TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. func TimerStop(t *time.Timer) bool { stopped := t.Stop() From b1f61fb0a8d2776164bf151868f85c937c111311 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 11:54:46 +0100 Subject: [PATCH 18/40] Refactor admin socket setup (isolated config) --- cmd/yggdrasil/main.go | 31 ++++++++--------- src/admin/admin.go | 81 +++++++++++++++++++++---------------------- src/admin/options.go | 16 +++++++++ 3 files changed, 70 insertions(+), 58 deletions(-) create mode 100644 src/admin/options.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 71453aaa..f8d882d6 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -359,6 +359,17 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { } } + // Setup the admin socket. + { + options := []admin.SetupOption{ + admin.ListenAddress(cfg.AdminListen), + } + n.admin, err = admin.New(n.core, logger, options...) + if err != nil { + panic(err) + } + } + // Setup the multicast module. { options := []multicast.SetupOption{} @@ -374,25 +385,13 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { if err != nil { panic(err) } + if n.admin != nil { + n.multicast.SetupAdminHandlers(n.admin) + } } - // Register the session firewall gatekeeper function - // Allocate our modules - n.admin = &admin.AdminSocket{} - n.tuntap = &tuntap.TunAdapter{} - // Start the admin socket - if err := n.admin.Init(n.core, cfg, logger, nil); err != nil { - logger.Errorln("An error occurred initialising admin socket:", err) - } else if err := n.admin.Start(); err != nil { - logger.Errorln("An error occurred starting admin socket:", err) - } - n.admin.SetupAdminHandlers() - // Start the multicast interface - if n.multicast, err = multicast.New(n.core, logger, nil); err != nil { - logger.Errorln("An error occurred initialising multicast:", err) - } - n.multicast.SetupAdminHandlers(n.admin) // Start the TUN/TAP interface + n.tuntap = &tuntap.TunAdapter{} rwc := ipv6rwc.NewReadWriteCloser(n.core) if err := n.tuntap.Init(rwc, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising TUN/TAP:", err) diff --git a/src/admin/admin.go b/src/admin/admin.go index 9a00d8a1..d3e95dfa 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -12,21 +12,21 @@ import ( "strings" "time" - "github.com/gologme/log" - - "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/core" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // TODO: Add authentication type AdminSocket struct { - core *core.Core - log *log.Logger - listenaddr string - listener net.Listener - handlers map[string]handler - done chan struct{} + core *core.Core + log util.Logger + listener net.Listener + handlers map[string]handler + done chan struct{} + config struct { + listenaddr ListenAddress + } } type AdminSocketRequest struct { @@ -69,15 +69,18 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc core.Ad } // Init runs the initial admin setup. -func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { - a.core = c - a.log = log - a.handlers = make(map[string]handler) - nc.RLock() - a.listenaddr = nc.AdminListen - nc.RUnlock() - a.done = make(chan struct{}) - close(a.done) // Start in a done / not-started state +func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, error) { + a := &AdminSocket{ + core: c, + log: log, + handlers: make(map[string]handler), + } + for _, opt := range opts { + a._applyOption(opt) + } + if a.config.listenaddr == "none" || a.config.listenaddr == "" { + return nil, nil + } _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { res := &ListResponse{} for name, handler := range a.handlers { @@ -91,7 +94,9 @@ func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, }) return res, nil }) - return a.core.SetAdmin(a) + a.done = make(chan struct{}) + go a.listen() + return a, a.core.SetAdmin(a) } func (a *AdminSocket) SetupAdminHandlers() { @@ -156,15 +161,6 @@ func (a *AdminSocket) SetupAdminHandlers() { //_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler) } -// Start runs the admin API socket to listen for / respond to admin API calls. -func (a *AdminSocket) Start() error { - if a.listenaddr != "none" && a.listenaddr != "" { - a.done = make(chan struct{}) - go a.listen() - } - return nil -} - // IsStarted returns true if the module has been started. func (a *AdminSocket) IsStarted() bool { select { @@ -192,31 +188,32 @@ func (a *AdminSocket) Stop() error { // listen is run by start and manages API connections. func (a *AdminSocket) listen() { - u, err := url.Parse(a.listenaddr) + listenaddr := string(a.config.listenaddr) + u, err := url.Parse(listenaddr) if err == nil { switch strings.ToLower(u.Scheme) { case "unix": - if _, err := os.Stat(a.listenaddr[7:]); err == nil { - a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up") - if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() { - a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process") + if _, err := os.Stat(listenaddr[7:]); err == nil { + a.log.Debugln("Admin socket", listenaddr[7:], "already exists, trying to clean up") + if _, err := net.DialTimeout("unix", listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() { + a.log.Errorln("Admin socket", listenaddr[7:], "already exists and is in use by another process") os.Exit(1) } else { - if err := os.Remove(a.listenaddr[7:]); err == nil { - a.log.Debugln(a.listenaddr[7:], "was cleaned up") + if err := os.Remove(listenaddr[7:]); err == nil { + a.log.Debugln(listenaddr[7:], "was cleaned up") } else { - a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err) + a.log.Errorln(listenaddr[7:], "already exists and was not cleaned up:", err) os.Exit(1) } } } - a.listener, err = net.Listen("unix", a.listenaddr[7:]) + a.listener, err = net.Listen("unix", listenaddr[7:]) if err == nil { - switch a.listenaddr[7:8] { + switch listenaddr[7:8] { case "@": // maybe abstract namespace default: - if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { - a.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") + if err := os.Chmod(listenaddr[7:], 0660); err != nil { + a.log.Warnln("WARNING:", listenaddr[:7], "may have unsafe permissions!") } } } @@ -224,10 +221,10 @@ func (a *AdminSocket) listen() { a.listener, err = net.Listen("tcp", u.Host) default: // err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme)) - a.listener, err = net.Listen("tcp", a.listenaddr) + a.listener, err = net.Listen("tcp", listenaddr) } } else { - a.listener, err = net.Listen("tcp", a.listenaddr) + a.listener, err = net.Listen("tcp", listenaddr) } if err != nil { a.log.Errorf("Admin socket failed to listen: %v", err) diff --git a/src/admin/options.go b/src/admin/options.go new file mode 100644 index 00000000..4607384f --- /dev/null +++ b/src/admin/options.go @@ -0,0 +1,16 @@ +package admin + +func (c *AdminSocket) _applyOption(opt SetupOption) { + switch v := opt.(type) { + case ListenAddress: + c.config.listenaddr = v + } +} + +type SetupOption interface { + isSetupOption() +} + +type ListenAddress string + +func (a ListenAddress) isSetupOption() {} From a7d06e048aec7068d31c2564580af7f20d79eeb6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 12:20:57 +0100 Subject: [PATCH 19/40] Refactor TUN setup (isolated config) --- cmd/yggdrasil/main.go | 26 +++++++++++++-------- src/multicast/multicast.go | 4 ++-- src/tuntap/options.go | 20 ++++++++++++++++ src/tuntap/tun.go | 47 +++++++++++++++----------------------- 4 files changed, 57 insertions(+), 40 deletions(-) create mode 100644 src/tuntap/options.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index f8d882d6..f239ed34 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -38,7 +38,6 @@ import ( type node struct { core *core.Core - config *config.NodeConfig tuntap *tuntap.TunAdapter multicast *multicast.Multicast admin *admin.AdminSocket @@ -326,7 +325,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { default: } - n := &node{config: cfg} + n := &node{} // Setup the Yggdrasil node itself. { @@ -390,15 +389,22 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { } } - // Start the TUN/TAP interface - n.tuntap = &tuntap.TunAdapter{} - rwc := ipv6rwc.NewReadWriteCloser(n.core) - if err := n.tuntap.Init(rwc, cfg, logger, nil); err != nil { - logger.Errorln("An error occurred initialising TUN/TAP:", err) - } else if err := n.tuntap.Start(); err != nil { - logger.Errorln("An error occurred starting TUN/TAP:", err) + // Setup the TUN module. + { + options := []tuntap.SetupOption{ + tuntap.InterfaceName(cfg.IfName), + tuntap.InterfaceMTU(cfg.IfMTU), + } + rwc := ipv6rwc.NewReadWriteCloser(n.core) + n.tuntap, err = tuntap.New(rwc, logger, options...) + if err != nil { + panic(err) + } + if n.admin != nil { + n.tuntap.SetupAdminHandlers(n.admin) + } } - n.tuntap.SetupAdminHandlers(n.admin) + // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index b6d290e1..24e9d04a 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -80,8 +80,8 @@ func (m *Multicast) _start() error { if len(m.config._interfaces) == 0 { return nil } - m.log.Infoln("Starting multicast module") - defer m.log.Infoln("Started multicast module") + m.log.Debugln("Starting multicast module") + defer m.log.Debugln("Started multicast module") addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr)) if err != nil { return err diff --git a/src/tuntap/options.go b/src/tuntap/options.go new file mode 100644 index 00000000..10af8d96 --- /dev/null +++ b/src/tuntap/options.go @@ -0,0 +1,20 @@ +package tuntap + +func (m *TunAdapter) _applyOption(opt SetupOption) { + switch v := opt.(type) { + case InterfaceName: + m.config.name = v + case InterfaceMTU: + m.config.mtu = v + } +} + +type SetupOption interface { + isSetupOption() +} + +type InterfaceName string +type InterfaceMTU uint64 + +func (a InterfaceName) isSetupOption() {} +func (a InterfaceMTU) isSetupOption() {} diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index eddccbcd..b0c444f4 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -16,13 +16,12 @@ import ( //"sync" "github.com/Arceliar/phony" - "github.com/gologme/log" "golang.zx2c4.com/wireguard/tun" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" + "github.com/yggdrasil-network/yggdrasil-go/src/util" ) type MTU uint16 @@ -33,8 +32,7 @@ type MTU uint16 // calling yggdrasil.Start(). type TunAdapter struct { rwc *ipv6rwc.ReadWriteCloser - config *config.NodeConfig - log *log.Logger + log util.Logger addr address.Address subnet address.Subnet mtu uint64 @@ -43,6 +41,10 @@ type TunAdapter struct { //mutex sync.RWMutex // Protects the below isOpen bool isEnabled bool // Used by the writer to drop sessionTraffic if not enabled + config struct { + name InterfaceName + mtu InterfaceMTU + } } // Gets the maximum supported MTU for the platform based on the defaults in @@ -93,50 +95,39 @@ func MaximumMTU() uint64 { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(rwc *ipv6rwc.ReadWriteCloser, config *config.NodeConfig, log *log.Logger, options interface{}) error { - tun.rwc = rwc - tun.config = config - tun.log = log - return nil -} - -// Start the setup process for the TUN adapter. If successful, starts the -// reader actor to handle packets on that interface. -func (tun *TunAdapter) Start() error { - var err error - phony.Block(tun, func() { - err = tun._start() - }) - return err +func New(rwc *ipv6rwc.ReadWriteCloser, log util.Logger, opts ...SetupOption) (*TunAdapter, error) { + tun := &TunAdapter{ + rwc: rwc, + log: log, + } + for _, opt := range opts { + tun._applyOption(opt) + } + return tun, tun._start() } func (tun *TunAdapter) _start() error { if tun.isOpen { return errors.New("TUN module is already started") } - if tun.config == nil { - return errors.New("no configuration available to TUN") - } - tun.config.RLock() - defer tun.config.RUnlock() tun.addr = tun.rwc.Address() tun.subnet = tun.rwc.Subnet() addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) - if tun.config.IfName == "none" || tun.config.IfName == "dummy" { + if tun.config.name == "none" || tun.config.name == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") tun.isEnabled = false go tun.write() return nil } - mtu := tun.config.IfMTU + mtu := uint64(tun.config.mtu) if tun.rwc.MaxMTU() < mtu { mtu = tun.rwc.MaxMTU() } - if err := tun.setup(tun.config.IfName, addr, mtu); err != nil { + if err := tun.setup(string(tun.config.name), addr, mtu); err != nil { return err } if tun.MTU() != mtu { - tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.IfMTU, tun.MTU(), MaximumMTU()) + tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.mtu, tun.MTU(), MaximumMTU()) } tun.rwc.SetMTU(tun.MTU()) tun.isOpen = true From 9cdfd5947618046df68047fd31375a9ae73586db Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 12:34:29 +0100 Subject: [PATCH 20/40] Tidy up a bit, make sure to copy the private key at startup --- cmd/yggdrasil/main.go | 56 ++++--------------------------------------- src/core/core.go | 14 ++++++----- 2 files changed, 12 insertions(+), 58 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index f239ed34..3235c4ca 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -81,48 +81,6 @@ func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf if err := hjson.Unmarshal(conf, &dat); err != nil { panic(err) } - // Check if we have old field names - if _, ok := dat["TunnelRouting"]; ok { - log.Warnln("WARNING: Tunnel routing is no longer supported") - } - if old, ok := dat["SigningPrivateKey"]; ok { - log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"") - if _, ok := dat["PrivateKey"]; !ok { - if privstr, err := hex.DecodeString(old.(string)); err == nil { - priv := ed25519.PrivateKey(privstr) - pub := priv.Public().(ed25519.PublicKey) - dat["PrivateKey"] = hex.EncodeToString(priv[:]) - dat["PublicKey"] = hex.EncodeToString(pub[:]) - } else { - log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option contains an invalid value and will be ignored") - } - } - } - if oldmc, ok := dat["MulticastInterfaces"]; ok { - if oldmcvals, ok := oldmc.([]interface{}); ok { - var newmc []config.MulticastInterfaceConfig - for _, oldmcval := range oldmcvals { - if str, ok := oldmcval.(string); ok { - newmc = append(newmc, config.MulticastInterfaceConfig{ - Regex: str, - Beacon: true, - Listen: true, - }) - } - } - if newmc != nil { - if oldport, ok := dat["LinkLocalTCPPort"]; ok { - // numbers parse to float64 by default - if port, ok := oldport.(float64); ok { - for idx := range newmc { - newmc[idx].Port = uint16(port) - } - } - } - dat["MulticastInterfaces"] = newmc - } - } - } // Sanitise the config confJson, err := json.Marshal(dat) if err != nil { @@ -322,7 +280,6 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { fmt.Println(ipnet.String()) } return - default: } n := &node{} @@ -352,8 +309,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { } options = append(options, core.AllowedPublicKey(k[:])) } - n.core, err = core.New(sk[:], logger, options...) - if err != nil { + if n.core, err = core.New(sk[:], logger, options...); err != nil { panic(err) } } @@ -363,8 +319,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { options := []admin.SetupOption{ admin.ListenAddress(cfg.AdminListen), } - n.admin, err = admin.New(n.core, logger, options...) - if err != nil { + if n.admin, err = admin.New(n.core, logger, options...); err != nil { panic(err) } } @@ -380,8 +335,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { Port: intf.Port, }) } - n.multicast, err = multicast.New(n.core, logger, options...) - if err != nil { + if n.multicast, err = multicast.New(n.core, logger, options...); err != nil { panic(err) } if n.admin != nil { @@ -395,9 +349,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { tuntap.InterfaceName(cfg.IfName), tuntap.InterfaceMTU(cfg.IfMTU), } - rwc := ipv6rwc.NewReadWriteCloser(n.core) - n.tuntap, err = tuntap.New(rwc, logger, options...) - if err != nil { + if n.tuntap, err = tuntap.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil { panic(err) } if n.admin != nil { diff --git a/src/core/core.go b/src/core/core.go index 47389a83..22baa58a 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -47,15 +47,17 @@ type Core struct { } func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*Core, error) { + c := &Core{ + log: logger, + } + c.ctx, c.cancel = context.WithCancel(context.Background()) + // Take a copy of the private key so that it is in our own memory space. if len(secret) != ed25519.PrivateKeySize { return nil, fmt.Errorf("private key is incorrect length") } - c := &Core{ - secret: secret, - public: secret.Public().(ed25519.PublicKey), - log: logger, - } - c.ctx, c.cancel = context.WithCancel(context.Background()) + c.secret = make(ed25519.PrivateKey, 0, ed25519.PrivateKeySize) + copy(c.secret, secret) + c.public = secret.Public().(ed25519.PublicKey) var err error if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil { return nil, fmt.Errorf("error creating encryption: %w", err) From 5477566fa9cb1b5dc2552f8a0f70e1857adad246 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 12:38:42 +0100 Subject: [PATCH 21/40] Length not capacity --- src/core/core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/core.go b/src/core/core.go index 22baa58a..6b9fe547 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -55,7 +55,7 @@ func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*C if len(secret) != ed25519.PrivateKeySize { return nil, fmt.Errorf("private key is incorrect length") } - c.secret = make(ed25519.PrivateKey, 0, ed25519.PrivateKeySize) + c.secret = make(ed25519.PrivateKey, ed25519.PrivateKeySize) copy(c.secret, secret) c.public = secret.Public().(ed25519.PublicKey) var err error From dc9720e580e09a150beca5b1e2a637b2290cc650 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 16:55:57 +0100 Subject: [PATCH 22/40] Extend `getSessions` admin call to include uptime/TX/RX --- cmd/yggdrasil/main.go | 1 + cmd/yggdrasilctl/main.go | 5 ++- go.mod | 3 +- go.sum | 93 +--------------------------------------- src/admin/getsessions.go | 10 ++++- src/core/api.go | 8 +++- 6 files changed, 23 insertions(+), 97 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 3235c4ca..a2aec15b 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -322,6 +322,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { if n.admin, err = admin.New(n.core, logger, options...); err != nil { panic(err) } + n.admin.SetupAdminHandlers() } // Setup the multicast module. diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 5e8bee2a..78700a40 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -216,11 +216,14 @@ func run() int { if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } - table.SetHeader([]string{"Public Key", "IP Address"}) + table.SetHeader([]string{"Public Key", "IP Address", "Uptime", "RX", "TX"}) for _, p := range resp.Sessions { table.Append([]string{ p.PublicKey, p.IPAddress, + (time.Duration(p.Uptime) * time.Second).String(), + p.RXBytes.String(), + p.TXBytes.String(), }) } table.Render() diff --git a/go.mod b/go.mod index c790b19d..b72bf534 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20220409035209-b7f71f05435a + github.com/Arceliar/ironwood v0.0.0-20220903132624-ee60c16bcfcf github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 @@ -34,6 +34,5 @@ require ( github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/gomega v1.20.2 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect ) diff --git a/go.sum b/go.sum index 5d22d679..ec5ea18f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20220409035209-b7f71f05435a h1:yfbnOyqPcx2gi5cFIJ2rlPz5M6rFPHT/c8FgZmFjCdc= -github.com/Arceliar/ironwood v0.0.0-20220409035209-b7f71f05435a/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20220903132624-ee60c16bcfcf h1:kjPkmDHUTWUma/4tqDl208bOk3jsUEqOJA6TsMZo5Jk= +github.com/Arceliar/ironwood v0.0.0-20220903132624-ee60c16bcfcf/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -8,40 +8,15 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw= github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAENALA= github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= @@ -57,42 +32,21 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= -github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -103,59 +57,36 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -170,35 +101,15 @@ golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw= golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA= golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/admin/getsessions.go b/src/admin/getsessions.go index 58ce378e..324119d4 100644 --- a/src/admin/getsessions.go +++ b/src/admin/getsessions.go @@ -16,8 +16,11 @@ type GetSessionsResponse struct { } type SessionEntry struct { - IPAddress string `json:"address"` - PublicKey string `json:"key"` + IPAddress string `json:"address"` + PublicKey string `json:"key"` + RXBytes DataUnit `json:"bytes_recvd"` + TXBytes DataUnit `json:"bytes_sent"` + Uptime float64 `json:"uptime"` } func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error { @@ -28,6 +31,9 @@ func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessio res.Sessions = append(res.Sessions, SessionEntry{ IPAddress: net.IP(addr[:]).String(), PublicKey: hex.EncodeToString(s.Key[:]), + RXBytes: DataUnit(s.RXBytes), + TXBytes: DataUnit(s.TXBytes), + Uptime: s.Uptime.Seconds(), }) } sort.SliceStable(res.Sessions, func(i, j int) bool { diff --git a/src/core/api.go b/src/core/api.go index 679d43d5..9f56983b 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -50,7 +50,10 @@ type PathEntryInfo struct { } type SessionInfo struct { - Key ed25519.PublicKey + Key ed25519.PublicKey + RXBytes uint64 + TXBytes uint64 + Uptime time.Duration } func (c *Core) GetSelf() SelfInfo { @@ -122,6 +125,9 @@ func (c *Core) GetSessions() []SessionInfo { for _, s := range ss { var info SessionInfo info.Key = s.Key + info.RXBytes = s.RX + info.TXBytes = s.TX + info.Uptime = s.Uptime sessions = append(sessions, info) } return sessions From 88a393a7b334d51983f042e835820586e937b275 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 3 Sep 2022 17:26:12 +0100 Subject: [PATCH 23/40] Load listen addresses --- cmd/yggdrasil/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index a2aec15b..2486df45 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -294,6 +294,9 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { core.IfName(cfg.IfName), core.IfMTU(cfg.IfMTU), } + for _, addr := range cfg.Listen { + options = append(options, core.ListenAddress(addr)) + } for _, peer := range cfg.Peers { options = append(options, core.Peer{URI: peer}) } From 414aaf6eb942e0ccf9656ea5eb03808c968f3838 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Mon, 5 Sep 2022 19:55:35 +0800 Subject: [PATCH 24/40] Update mobile.go (#942) --- contrib/mobile/mobile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index d7da03de..1e6d3780 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -94,7 +94,7 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { } m.multicast, err = multicast.New(m.core, logger, options...) if err != nil { - panic(err) + logger.Errorln("An error occurred starting multicast:", err) } } From 5ef61faeff9028b928600ce116c6b069a5de171e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 17 Sep 2022 20:07:00 +0100 Subject: [PATCH 25/40] Link refactor (#941) * Link refactoring * More refactoring * More tweaking * Cleaner shutdowns, UNIX socket support, more tweaks * Actorise links, remove mutex * SOCKS support --- cmd/yggdrasil/main.go | 15 +- contrib/mobile/mobile.go | 5 +- src/admin/admin.go | 5 + src/core/api.go | 25 +- src/core/core.go | 33 +- src/core/core_test.go | 4 +- src/core/link.go | 278 +++++++----- src/core/link_socks.go | 52 +++ src/core/link_tcp.go | 183 ++++++++ .../{tcp_darwin.go => link_tcp_darwin.go} | 4 +- src/core/{tcp_linux.go => link_tcp_linux.go} | 4 +- src/core/{tcp_other.go => link_tcp_other.go} | 4 +- src/core/link_tls.go | 171 +++++++ src/core/link_unix.go | 98 ++++ src/core/options.go | 8 - src/core/tcp.go | 417 ------------------ src/core/tls.go | 126 ------ src/multicast/multicast.go | 4 +- 18 files changed, 738 insertions(+), 698 deletions(-) create mode 100644 src/core/link_socks.go create mode 100644 src/core/link_tcp.go rename src/core/{tcp_darwin.go => link_tcp_darwin.go} (74%) rename src/core/{tcp_linux.go => link_tcp_linux.go} (86%) rename src/core/{tcp_other.go => link_tcp_other.go} (55%) create mode 100644 src/core/link_tls.go create mode 100644 src/core/link_unix.go delete mode 100644 src/core/tcp.go delete mode 100644 src/core/tls.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 2486df45..1ef6738b 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -28,9 +28,9 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/core" - "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" @@ -290,10 +290,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { if err != nil { panic(err) } - options := []core.SetupOption{ - core.IfName(cfg.IfName), - core.IfMTU(cfg.IfMTU), - } + options := []core.SetupOption{} for _, addr := range cfg.Listen { options = append(options, core.ListenAddress(addr)) } @@ -325,7 +322,9 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { if n.admin, err = admin.New(n.core, logger, options...); err != nil { panic(err) } - n.admin.SetupAdminHandlers() + if n.admin != nil { + n.admin.SetupAdminHandlers() + } } // Setup the multicast module. @@ -342,7 +341,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { if n.multicast, err = multicast.New(n.core, logger, options...); err != nil { panic(err) } - if n.admin != nil { + if n.admin != nil && n.multicast != nil { n.multicast.SetupAdminHandlers(n.admin) } } @@ -356,7 +355,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { if n.tuntap, err = tuntap.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil { panic(err) } - if n.admin != nil { + if n.admin != nil && n.tuntap != nil { n.tuntap.SetupAdminHandlers(n.admin) } } diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index 1e6d3780..0cf87180 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -55,10 +55,7 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { if err != nil { panic(err) } - options := []core.SetupOption{ - core.IfName("none"), - core.IfMTU(m.config.IfMTU), - } + options := []core.SetupOption{} for _, peer := range m.config.Peers { options = append(options, core.Peer{URI: peer}) } diff --git a/src/admin/admin.go b/src/admin/admin.go index d3e95dfa..4e98c891 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -175,6 +175,9 @@ func (a *AdminSocket) IsStarted() bool { // Stop will stop the admin API and close the socket. func (a *AdminSocket) Stop() error { + if a == nil { + return nil + } if a.listener != nil { select { case <-a.done: @@ -321,6 +324,8 @@ type DataUnit uint64 func (d DataUnit) String() string { switch { + case d > 1024*1024*1024*1024: + return fmt.Sprintf("%2.ftb", float64(d)/1024/1024/1024/1024) case d > 1024*1024*1024: return fmt.Sprintf("%2.fgb", float64(d)/1024/1024/1024) case d > 1024*1024: diff --git a/src/core/api.go b/src/core/api.go index 9f56983b..657b5510 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -2,6 +2,7 @@ package core import ( "crypto/ed25519" + "fmt" "sync/atomic" "time" @@ -15,6 +16,7 @@ import ( //"sort" //"time" + "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -68,11 +70,11 @@ func (c *Core) GetSelf() SelfInfo { func (c *Core) GetPeers() []PeerInfo { var peers []PeerInfo names := make(map[net.Conn]string) - c.links.mutex.Lock() - for _, info := range c.links.links { - names[info.conn] = info.lname - } - c.links.mutex.Unlock() + phony.Block(&c.links, func() { + for _, info := range c.links._links { + names[info.conn] = info.lname + } + }) ps := c.PacketConn.PacketConn.Debug.GetPeers() for _, p := range ps { var info PeerInfo @@ -136,8 +138,17 @@ func (c *Core) GetSessions() []SessionInfo { // Listen starts a new listener (either TCP or TLS). The input should be a url.URL // parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a // link-local address, the interface should be provided as the second argument. -func (c *Core) Listen(u *url.URL, sintf string) (*TcpListener, error) { - return c.links.tcp.listenURL(u, sintf) +func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) { + switch u.Scheme { + case "tcp": + return c.links.tcp.listen(u, sintf) + case "tls": + return c.links.tls.listen(u, sintf) + case "unix": + return c.links.unix.listen(u, sintf) + default: + return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme) + } } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 diff --git a/src/core/core.go b/src/core/core.go index 6b9fe547..4cc08ad6 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -40,8 +40,6 @@ type Core struct { _listeners map[ListenAddress]struct{} // configurable after startup nodeinfo NodeInfo // immutable after startup nodeinfoPrivacy NodeInfoPrivacy // immutable after startup - ifname IfName // immutable after startup - ifmtu IfMTU // immutable after startup _allowedPublicKeys map[[32]byte]struct{} // configurable after startup } } @@ -50,6 +48,12 @@ func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*C c := &Core{ log: logger, } + if name := version.BuildName(); name != "unknown" { + c.log.Infoln("Build name:", name) + } + if version := version.BuildVersion(); version != "unknown" { + c.log.Infoln("Build version:", version) + } c.ctx, c.cancel = context.WithCancel(context.Background()) // Take a copy of the private key so that it is in our own memory space. if len(secret) != ed25519.PrivateKeySize { @@ -78,15 +82,17 @@ func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*C if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil { return nil, fmt.Errorf("error setting node info: %w", err) } - c.addPeerTimer = time.AfterFunc(time.Minute, func() { - c.Act(nil, c._addPeerLoop) - }) - if name := version.BuildName(); name != "unknown" { - c.log.Infoln("Build name:", name) - } - if version := version.BuildVersion(); version != "unknown" { - c.log.Infoln("Build version:", version) + for listenaddr := range c.config._listeners { + u, err := url.Parse(string(listenaddr)) + if err != nil { + c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr) + continue + } + if _, err = c.links.listen(u, ""); err != nil { + c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err) + } } + c.Act(nil, c._addPeerLoop) return c, nil } @@ -94,10 +100,11 @@ func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*C // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. func (c *Core) _addPeerLoop() { - if c.addPeerTimer == nil { + select { + case <-c.ctx.Done(): return + default: } - // Add peers from the Peers section for peer := range c.config._peers { go func(peer string, intf string) { @@ -128,12 +135,12 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _close() error { c.cancel() + _ = c.links.shutdown() err := c.PacketConn.Close() if c.addPeerTimer != nil { c.addPeerTimer.Stop() c.addPeerTimer = nil } - _ = c.links.stop() return err } diff --git a/src/core/core_test.go b/src/core/core_test.go index ed5b4255..8d57f336 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -37,10 +37,10 @@ func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) t.Fatal(err) } logger := GetLoggerWithPrefix("", false) - if nodeA, err = New(skA, logger, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { + if nodeA, err = New(skA, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil { t.Fatal(err) } - if nodeB, err = New(skB, logger, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { + if nodeB, err = New(skB, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil { t.Fatal(err) } diff --git a/src/core/link.go b/src/core/link.go index 099a8af0..08e8dfc2 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -2,7 +2,6 @@ package core import ( "bytes" - "crypto/ed25519" "encoding/hex" "errors" "fmt" @@ -10,7 +9,6 @@ import ( "net" "net/url" "strings" - "sync" //"sync/atomic" "time" @@ -20,22 +18,22 @@ import ( "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" - "golang.org/x/net/proxy" //"github.com/Arceliar/phony" // TODO? use instead of mutexes ) type links struct { - core *Core - mutex sync.RWMutex // protects links below - links map[linkInfo]*link - tcp tcp // TCP interface support - stopped chan struct{} + phony.Inbox + core *Core + tcp *linkTCP // TCP interface support + tls *linkTLS // TLS interface support + unix *linkUNIX // UNIX interface support + socks *linkSOCKS // SOCKS interface support + _links map[linkInfo]*link // *link is nil if connection in progress // TODO timeout (to remove from switch), read from config.ReadTimeout } // linkInfo is used as a map key type linkInfo struct { - key keyArray linkType string // Type of link, e.g. TCP, AWDL local string // Local name or address remote string // Remote name or address @@ -49,19 +47,30 @@ type link struct { info linkInfo incoming bool force bool - closed chan struct{} } type linkOptions struct { pinnedEd25519Keys map[keyArray]struct{} } +type Listener struct { + net.Listener + closed chan struct{} +} + +func (l *Listener) Close() error { + err := l.Listener.Close() + <-l.closed + return err +} + func (l *links) init(c *Core) error { l.core = c - l.mutex.Lock() - l.links = make(map[linkInfo]*link) - l.mutex.Unlock() - l.stopped = make(chan struct{}) + l.tcp = l.newLinkTCP() + l.tls = l.newLinkTLS(l.tcp) + l.unix = l.newLinkUNIX() + l.socks = l.newLinkSOCKS() + l._links = make(map[linkInfo]*link) var listeners []ListenAddress phony.Block(c, func() { @@ -70,96 +79,160 @@ func (l *links) init(c *Core) error { listeners = append(listeners, listener) } }) - if err := l.tcp.init(l, listeners); err != nil { - c.log.Errorln("Failed to start TCP interface") - return err - } return nil } +func (l *links) shutdown() error { + phony.Block(l.tcp, func() { + for l := range l.tcp._listeners { + l.Close() + } + }) + phony.Block(l.tls, func() { + for l := range l.tls._listeners { + l.Close() + } + }) + phony.Block(l.unix, func() { + for l := range l.unix._listeners { + l.Close() + } + }) + return nil +} + +func (l *links) isConnectedTo(info linkInfo) bool { + var isConnected bool + phony.Block(l, func() { + _, isConnected = l._links[info] + }) + return isConnected +} + func (l *links) call(u *url.URL, sintf string) error { - tcpOpts := tcpOptions{} - if pubkeys, ok := u.Query()["key"]; ok && len(pubkeys) > 0 { - tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{}) - for _, pubkey := range pubkeys { - if sigPub, err := hex.DecodeString(pubkey); err == nil { - var sigPubKey keyArray - copy(sigPubKey[:], sigPub) - tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{} - } + info := linkInfoFor(u.Scheme, sintf, u.Host) + if l.isConnectedTo(info) { + return fmt.Errorf("already connected to this node") + } + options := linkOptions{ + pinnedEd25519Keys: map[keyArray]struct{}{}, + } + for _, pubkey := range u.Query()["key"] { + if sigPub, err := hex.DecodeString(pubkey); err == nil { + var sigPubKey keyArray + copy(sigPubKey[:], sigPub) + options.pinnedEd25519Keys[sigPubKey] = struct{}{} } } - switch u.Scheme { + switch info.linkType { case "tcp": - l.tcp.call(u.Host, tcpOpts, sintf) + go func() { + if err := l.tcp.dial(u, options, sintf); err != nil { + l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err) + } + }() + case "socks": - tcpOpts.socksProxyAddr = u.Host - if u.User != nil { - tcpOpts.socksProxyAuth = &proxy.Auth{} - tcpOpts.socksProxyAuth.User = u.User.Username() - tcpOpts.socksProxyAuth.Password, _ = u.User.Password() - } - tcpOpts.upgrade = l.tcp.tls.forDialer // TODO make this configurable - pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") - l.tcp.call(pathtokens[0], tcpOpts, sintf) + go func() { + if err := l.socks.dial(u, options); err != nil { + l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err) + } + }() + case "tls": - tcpOpts.upgrade = l.tcp.tls.forDialer // SNI headers must contain hostnames and not IP addresses, so we must make sure // that we do not populate the SNI with an IP literal. We do this by splitting // the host-port combo from the query option and then seeing if it parses to an // IP address successfully or not. + var tlsSNI string if sni := u.Query().Get("sni"); sni != "" { if net.ParseIP(sni) == nil { - tcpOpts.tlsSNI = sni + tlsSNI = sni } } // If the SNI is not configured still because the above failed then we'll try // again but this time we'll use the host part of the peering URI instead. - if tcpOpts.tlsSNI == "" { + if tlsSNI == "" { if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil { - tcpOpts.tlsSNI = host + tlsSNI = host } } - l.tcp.call(u.Host, tcpOpts, sintf) + go func() { + if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil { + l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err) + } + }() + + case "unix": + go func() { + if err := l.unix.dial(u, options, sintf); err != nil { + l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err) + } + }() + default: return errors.New("unknown call scheme: " + u.Scheme) } return nil } -func (l *links) create(conn net.Conn, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*link, error) { - // Technically anything unique would work for names, but let's pick something human readable, just for debugging +func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { + var listener *Listener + var err error + switch u.Scheme { + case "tcp": + listener, err = l.tcp.listen(u, sintf) + case "tls": + listener, err = l.tls.listen(u, sintf) + case "unix": + listener, err = l.unix.listen(u, sintf) + default: + return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme) + } + return listener, err +} + +func (l *links) create(conn net.Conn, name string, info linkInfo, incoming, force bool, options linkOptions) error { intf := link{ conn: &linkConn{ Conn: conn, up: time.Now(), }, - lname: name, - links: l, - options: options, - info: linkInfo{ - linkType: linkType, - local: local, - remote: remote, - }, + lname: name, + links: l, + options: options, + info: info, incoming: incoming, force: force, } - return &intf, nil -} - -func (l *links) stop() error { - close(l.stopped) - if err := l.tcp.stop(); err != nil { - return err - } + go func() { + if err := intf.handler(); err != nil { + l.core.log.Errorf("Link handler %s error (%s): %s", name, conn.RemoteAddr(), err) + } + }() return nil } -func (intf *link) handler() (chan struct{}, error) { - // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later +func (intf *link) handler() error { defer intf.conn.Close() + + // Don't connect to this link more than once. + if intf.links.isConnectedTo(intf.info) { + return fmt.Errorf("already connected to this node") + } + + // Mark the connection as in progress. + phony.Block(intf.links, func() { + intf.links._links[intf.info] = nil + }) + + // When we're done, clean up the connection entry. + defer phony.Block(intf.links, func() { + delete(intf.links._links, intf.info) + }) + + // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later meta := version_getBaseMetadata() meta.key = intf.links.core.public metaBytes := meta.encode() @@ -172,10 +245,10 @@ func (intf *link) handler() (chan struct{}, error) { err = errors.New("incomplete metadata send") } }) { - return nil, errors.New("timeout on metadata send") + return errors.New("timeout on metadata send") } if err != nil { - return nil, err + return fmt.Errorf("write handshake: %w", err) } if !util.FuncTimeout(30*time.Second, func() { var n int @@ -184,15 +257,15 @@ func (intf *link) handler() (chan struct{}, error) { err = errors.New("incomplete metadata recv") } }) { - return nil, errors.New("timeout on metadata recv") + return errors.New("timeout on metadata recv") } if err != nil { - return nil, err + return fmt.Errorf("read handshake: %w", err) } meta = version_metadata{} base := version_getBaseMetadata() if !meta.decode(metaBytes) { - return nil, errors.New("failed to decode metadata") + return errors.New("failed to decode metadata") } if !meta.check() { var connectError string @@ -207,16 +280,16 @@ func (intf *link) handler() (chan struct{}, error) { fmt.Sprintf("%d.%d", base.ver, base.minorVer), fmt.Sprintf("%d.%d", meta.ver, meta.minorVer), ) - return nil, errors.New("remote node is incompatible version") + return errors.New("remote node is incompatible version") } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. - if pinned := intf.options.pinnedEd25519Keys; pinned != nil { + if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 { var key keyArray copy(key[:], meta.key) if _, allowed := pinned[key]; !allowed { intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name()) - return nil, fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") + return fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } } // Check if we're authorized to connect to this key / IP @@ -232,55 +305,50 @@ func (intf *link) handler() (chan struct{}, error) { intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key)) intf.close() - return nil, nil + return fmt.Errorf("forbidden connection") } - // Check if we already have a link to this node - copy(intf.info.key[:], meta.key) - intf.links.mutex.Lock() - if oldIntf, isIn := intf.links.links[intf.info]; isIn { - intf.links.mutex.Unlock() - // FIXME we should really return an error and let the caller block instead - // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. - intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name()) - return oldIntf.closed, nil - } else { - intf.closed = make(chan struct{}) - intf.links.links[intf.info] = intf - defer func() { - intf.links.mutex.Lock() - delete(intf.links.links, intf.info) - intf.links.mutex.Unlock() - close(intf.closed) - }() - intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name()) - } - intf.links.mutex.Unlock() - themAddr := address.AddrForKey(ed25519.PublicKey(intf.info.key[:])) - themAddrString := net.IP(themAddr[:]).String() - themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) + + phony.Block(intf.links, func() { + intf.links._links[intf.info] = intf + }) + + remoteAddr := net.IP(address.AddrForKey(meta.key)[:]).String() + remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote) + localStr := intf.conn.LocalAddr() intf.links.core.log.Infof("Connected %s: %s, source %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local) - // Run the handler - err = intf.links.core.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) + strings.ToUpper(intf.info.linkType), remoteStr, localStr) + // TODO don't report an error if it's just a 'use of closed network connection' - if err != nil { + if err = intf.links.core.HandleConn(meta.key, intf.conn); err != nil && err != io.EOF { intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) + strings.ToUpper(intf.info.linkType), remoteStr, localStr, err) } else { intf.links.core.log.Infof("Disconnected %s: %s, source %s", - strings.ToUpper(intf.info.linkType), themString, intf.info.local) + strings.ToUpper(intf.info.linkType), remoteStr, localStr) } - return nil, err + + return nil } -func (intf *link) close() { - intf.conn.Close() +func (intf *link) close() error { + return intf.conn.Close() } func (intf *link) name() string { return intf.lname } +func linkInfoFor(linkType, sintf, remote string) linkInfo { + if h, _, err := net.SplitHostPort(remote); err == nil { + remote = h + } + return linkInfo{ + linkType: linkType, + local: sintf, + remote: remote, + } +} + type linkConn struct { // tx and rx are at the beginning of the struct to ensure 64-bit alignment // on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG diff --git a/src/core/link_socks.go b/src/core/link_socks.go new file mode 100644 index 00000000..ad5b8c98 --- /dev/null +++ b/src/core/link_socks.go @@ -0,0 +1,52 @@ +package core + +import ( + "fmt" + "net" + "net/url" + "strings" + + "golang.org/x/net/proxy" +) + +type linkSOCKS struct { + *links +} + +func (l *links) newLinkSOCKS() *linkSOCKS { + lt := &linkSOCKS{ + links: l, + } + return lt +} + +func (l *linkSOCKS) dial(url *url.URL, options linkOptions) error { + info := linkInfoFor("socks", "", url.Path) + if l.links.isConnectedTo(info) { + return fmt.Errorf("duplicate connection attempt") + } + proxyAuth := &proxy.Auth{} + proxyAuth.User = url.User.Username() + proxyAuth.Password, _ = url.User.Password() + dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct) + if err != nil { + return fmt.Errorf("failed to configure proxy") + } + pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/") + conn, err := dialer.Dial("tcp", pathtokens[0]) + if err != nil { + return err + } + return l.handler(url.String(), info, conn, options, false) +} + +func (l *linkSOCKS) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { + return l.links.create( + conn, // connection + name, // connection name + info, // connection info + incoming, // not incoming + false, // not forced + options, // connection options + ) +} diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go new file mode 100644 index 00000000..fcac8b4d --- /dev/null +++ b/src/core/link_tcp.go @@ -0,0 +1,183 @@ +package core + +import ( + "context" + "fmt" + "net" + "net/url" + "strings" + "time" + + "github.com/Arceliar/phony" +) + +type linkTCP struct { + phony.Inbox + *links + listener *net.ListenConfig + _listeners map[*Listener]context.CancelFunc +} + +func (l *links) newLinkTCP() *linkTCP { + lt := &linkTCP{ + links: l, + listener: &net.ListenConfig{ + KeepAlive: -1, + }, + _listeners: map[*Listener]context.CancelFunc{}, + } + lt.listener.Control = lt.tcpContext + return lt +} + +func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { + info := linkInfoFor("tcp", sintf, strings.SplitN(url.Host, "%", 2)[0]) + if l.links.isConnectedTo(info) { + return fmt.Errorf("duplicate connection attempt") + } + addr, err := net.ResolveTCPAddr("tcp", url.Host) + if err != nil { + return err + } + addr.Zone = sintf + dialer, err := l.dialerFor(addr.String(), sintf) + if err != nil { + return err + } + conn, err := dialer.DialContext(l.core.ctx, "tcp", addr.String()) + if err != nil { + return err + } + return l.handler(url.String(), info, conn, options, false) +} + +func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { + ctx, cancel := context.WithCancel(l.core.ctx) + hostport := url.Host + if sintf != "" { + if host, port, err := net.SplitHostPort(hostport); err == nil { + hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) + } + } + listener, err := l.listener.Listen(ctx, "tcp", hostport) + if err != nil { + cancel() + return nil, err + } + entry := &Listener{ + Listener: listener, + closed: make(chan struct{}), + } + phony.Block(l, func() { + l._listeners[entry] = cancel + }) + l.core.log.Printf("TCP listener started on %s", listener.Addr()) + go func() { + defer phony.Block(l, func() { + delete(l._listeners, entry) + }) + for { + conn, err := listener.Accept() + if err != nil { + cancel() + break + } + addr := conn.RemoteAddr().(*net.TCPAddr) + name := fmt.Sprintf("tls://%s", addr) + info := linkInfoFor("tcp", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) + if err = l.handler(name, info, conn, linkOptions{}, true); err != nil { + l.core.log.Errorln("Failed to create inbound link:", err) + } + } + listener.Close() + close(entry.closed) + l.core.log.Printf("TCP listener stopped on %s", listener.Addr()) + }() + return entry, nil +} + +func (l *linkTCP) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { + return l.links.create( + conn, // connection + name, // connection name + info, // connection info + incoming, // not incoming + false, // not forced + options, // connection options + ) +} + +// Returns the address of the listener. +func (l *linkTCP) getAddr() *net.TCPAddr { + // TODO: Fix this, because this will currently only give a single address + // to multicast.go, which obviously is not great, but right now multicast.go + // doesn't have the ability to send more than one address in a packet either + var addr *net.TCPAddr + phony.Block(l, func() { + for listener := range l._listeners { + addr = listener.Addr().(*net.TCPAddr) + } + }) + return addr +} + +func (l *linkTCP) dialerFor(saddr, sintf string) (*net.Dialer, error) { + dst, err := net.ResolveTCPAddr("tcp", saddr) + if err != nil { + return nil, err + } + if dst.IP.IsLinkLocalUnicast() { + dst.Zone = sintf + if dst.Zone == "" { + return nil, fmt.Errorf("link-local address requires a zone") + } + } + dialer := &net.Dialer{ + Timeout: time.Second * 5, + KeepAlive: -1, + Control: l.tcpContext, + } + if sintf != "" { + dialer.Control = l.getControl(sintf) + ief, err := net.InterfaceByName(sintf) + if err != nil { + return nil, fmt.Errorf("interface %q not found", sintf) + } + if ief.Flags&net.FlagUp == 0 { + return nil, fmt.Errorf("interface %q is not up", sintf) + } + addrs, err := ief.Addrs() + if err != nil { + return nil, fmt.Errorf("interface %q addresses not available: %w", sintf, err) + } + for addrindex, addr := range addrs { + src, _, err := net.ParseCIDR(addr.String()) + if err != nil { + continue + } + if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() { + continue + } + bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast() + bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast() + if !bothglobal && !bothlinklocal { + continue + } + if (src.To4() != nil) != (dst.IP.To4() != nil) { + continue + } + if bothglobal || bothlinklocal || addrindex == len(addrs)-1 { + dialer.LocalAddr = &net.TCPAddr{ + IP: src, + Port: 0, + Zone: sintf, + } + break + } + } + if dialer.LocalAddr == nil { + return nil, fmt.Errorf("no suitable source address found on interface %q", sintf) + } + } + return dialer, nil +} diff --git a/src/core/tcp_darwin.go b/src/core/link_tcp_darwin.go similarity index 74% rename from src/core/tcp_darwin.go rename to src/core/link_tcp_darwin.go index 2ea3abc8..daa51df0 100644 --- a/src/core/tcp_darwin.go +++ b/src/core/link_tcp_darwin.go @@ -11,7 +11,7 @@ import ( // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go -func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { +func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error { var control error var recvanyif error @@ -28,6 +28,6 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { } } -func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { +func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error { return t.tcpContext } diff --git a/src/core/tcp_linux.go b/src/core/link_tcp_linux.go similarity index 86% rename from src/core/tcp_linux.go rename to src/core/link_tcp_linux.go index e59c3121..9e875fee 100644 --- a/src/core/tcp_linux.go +++ b/src/core/link_tcp_linux.go @@ -11,7 +11,7 @@ import ( // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go -func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { +func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error { var control error var bbr error @@ -31,7 +31,7 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { return nil } -func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { +func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { var err error btd := func(fd uintptr) { diff --git a/src/core/tcp_other.go b/src/core/link_tcp_other.go similarity index 55% rename from src/core/tcp_other.go rename to src/core/link_tcp_other.go index 8dd76f28..f8a5aece 100644 --- a/src/core/tcp_other.go +++ b/src/core/link_tcp_other.go @@ -9,10 +9,10 @@ import ( // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go -func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { +func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error { return nil } -func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { +func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error { return t.tcpContext } diff --git a/src/core/link_tls.go b/src/core/link_tls.go new file mode 100644 index 00000000..e6ffd5a8 --- /dev/null +++ b/src/core/link_tls.go @@ -0,0 +1,171 @@ +package core + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/hex" + "encoding/pem" + "fmt" + "math/big" + "net" + "net/url" + "strings" + "time" + + "github.com/Arceliar/phony" +) + +type linkTLS struct { + phony.Inbox + *links + tcp *linkTCP + listener *net.ListenConfig + config *tls.Config + _listeners map[*Listener]context.CancelFunc +} + +func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS { + lt := &linkTLS{ + links: l, + tcp: tcp, + listener: &net.ListenConfig{ + Control: tcp.tcpContext, + KeepAlive: -1, + }, + _listeners: map[*Listener]context.CancelFunc{}, + } + var err error + lt.config, err = lt.generateConfig() + if err != nil { + panic(err) + } + return lt +} + +func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error { + info := linkInfoFor("tls", sintf, strings.SplitN(url.Host, "%", 2)[0]) + if l.links.isConnectedTo(info) { + return fmt.Errorf("duplicate connection attempt") + } + addr, err := net.ResolveTCPAddr("tcp", url.Host) + if err != nil { + return err + } + addr.Zone = sintf + dialer, err := l.tcp.dialerFor(addr.String(), sintf) + if err != nil { + return err + } + tlsconfig := l.config.Clone() + tlsconfig.ServerName = sni + tlsdialer := &tls.Dialer{ + NetDialer: dialer, + Config: tlsconfig, + } + conn, err := tlsdialer.DialContext(l.core.ctx, "tcp", addr.String()) + if err != nil { + return err + } + return l.handler(url.String(), info, conn, options, false) +} + +func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { + ctx, cancel := context.WithCancel(l.core.ctx) + hostport := url.Host + if sintf != "" { + if host, port, err := net.SplitHostPort(hostport); err == nil { + hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) + } + } + listener, err := l.listener.Listen(ctx, "tcp", hostport) + if err != nil { + cancel() + return nil, err + } + tlslistener := tls.NewListener(listener, l.config) + entry := &Listener{ + Listener: tlslistener, + closed: make(chan struct{}), + } + phony.Block(l, func() { + l._listeners[entry] = cancel + }) + l.core.log.Printf("TLS listener started on %s", listener.Addr()) + go func() { + defer phony.Block(l, func() { + delete(l._listeners, entry) + }) + for { + conn, err := tlslistener.Accept() + if err != nil { + cancel() + break + } + addr := conn.RemoteAddr().(*net.TCPAddr) + name := fmt.Sprintf("tls://%s", addr) + info := linkInfoFor("tls", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) + if err = l.handler(name, info, conn, linkOptions{}, true); err != nil { + l.core.log.Errorln("Failed to create inbound link:", err) + } + } + tlslistener.Close() + close(entry.closed) + l.core.log.Printf("TLS listener stopped on %s", listener.Addr()) + }() + return entry, nil +} + +func (l *linkTLS) generateConfig() (*tls.Config, error) { + certBuf := &bytes.Buffer{} + + // TODO: because NotAfter is finite, we should add some mechanism to + // regenerate the certificate and restart the listeners periodically + // for nodes with very high uptimes. Perhaps regenerate certs and restart + // listeners every few months or so. + cert := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: hex.EncodeToString(l.links.core.public[:]), + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + certbytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, l.links.core.public, l.links.core.secret) + if err != nil { + return nil, err + } + + if err := pem.Encode(certBuf, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certbytes, + }); err != nil { + return nil, err + } + + rootCAs := x509.NewCertPool() + rootCAs.AppendCertsFromPEM(certbytes) + + return &tls.Config{ + RootCAs: rootCAs, + Certificates: []tls.Certificate{ + { + Certificate: [][]byte{certbytes}, + PrivateKey: l.links.core.secret, + }, + }, + InsecureSkipVerify: true, + MinVersion: tls.VersionTLS13, + }, nil +} + +func (l *linkTLS) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { + return l.tcp.handler(name, info, conn, options, incoming) +} diff --git a/src/core/link_unix.go b/src/core/link_unix.go new file mode 100644 index 00000000..d63c1d90 --- /dev/null +++ b/src/core/link_unix.go @@ -0,0 +1,98 @@ +package core + +import ( + "context" + "fmt" + "net" + "net/url" + "time" + + "github.com/Arceliar/phony" +) + +type linkUNIX struct { + phony.Inbox + *links + dialer *net.Dialer + listener *net.ListenConfig + _listeners map[*Listener]context.CancelFunc +} + +func (l *links) newLinkUNIX() *linkUNIX { + lt := &linkUNIX{ + links: l, + dialer: &net.Dialer{ + Timeout: time.Second * 5, + KeepAlive: -1, + }, + listener: &net.ListenConfig{ + KeepAlive: -1, + }, + _listeners: map[*Listener]context.CancelFunc{}, + } + return lt +} + +func (l *linkUNIX) dial(url *url.URL, options linkOptions, _ string) error { + info := linkInfoFor("unix", "", url.Path) + if l.links.isConnectedTo(info) { + return fmt.Errorf("duplicate connection attempt") + } + addr, err := net.ResolveUnixAddr("unix", url.Path) + if err != nil { + return err + } + conn, err := l.dialer.DialContext(l.core.ctx, "unix", addr.String()) + if err != nil { + return err + } + return l.handler(url.String(), info, conn, options, false) +} + +func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) { + ctx, cancel := context.WithCancel(l.core.ctx) + listener, err := l.listener.Listen(ctx, "unix", url.Path) + if err != nil { + cancel() + return nil, err + } + entry := &Listener{ + Listener: listener, + closed: make(chan struct{}), + } + phony.Block(l, func() { + l._listeners[entry] = cancel + }) + l.core.log.Printf("UNIX listener started on %s", listener.Addr()) + go func() { + defer phony.Block(l, func() { + delete(l._listeners, entry) + }) + for { + conn, err := listener.Accept() + if err != nil { + cancel() + break + } + info := linkInfoFor("unix", "", url.String()) + if err = l.handler(url.String(), info, conn, linkOptions{}, true); err != nil { + l.core.log.Errorln("Failed to create inbound link:", err) + } + } + listener.Close() + close(entry.closed) + l.core.log.Printf("UNIX listener stopped on %s", listener.Addr()) + }() + return entry, nil +} + +func (l *linkUNIX) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { + return l.links.create( + conn, // connection + name, // connection name + info, // connection info + incoming, // not incoming + false, // not forced + options, // connection options + ) +} diff --git a/src/core/options.go b/src/core/options.go index b3d06c63..8009aa3b 100644 --- a/src/core/options.go +++ b/src/core/options.go @@ -14,10 +14,6 @@ func (c *Core) _applyOption(opt SetupOption) { c.config.nodeinfo = v case NodeInfoPrivacy: c.config.nodeinfoPrivacy = v - case IfName: - c.config.ifname = v - case IfMTU: - c.config.ifmtu = v case AllowedPublicKey: pk := [32]byte{} copy(pk[:], v) @@ -36,14 +32,10 @@ type Peer struct { } type NodeInfo map[string]interface{} type NodeInfoPrivacy bool -type IfName string -type IfMTU uint16 type AllowedPublicKey ed25519.PublicKey func (a ListenAddress) isSetupOption() {} func (a Peer) isSetupOption() {} func (a NodeInfo) isSetupOption() {} func (a NodeInfoPrivacy) isSetupOption() {} -func (a IfName) isSetupOption() {} -func (a IfMTU) isSetupOption() {} func (a AllowedPublicKey) isSetupOption() {} diff --git a/src/core/tcp.go b/src/core/tcp.go deleted file mode 100644 index ab952806..00000000 --- a/src/core/tcp.go +++ /dev/null @@ -1,417 +0,0 @@ -package core - -// This sends packets to peers using TCP as a transport -// It's generally better tested than the UDP implementation -// Using it regularly is insane, but I find TCP easier to test/debug with it -// Updating and optimizing the UDP version is a higher priority - -// TODO: -// Something needs to make sure we're getting *valid* packets -// Could be used to DoS (connect, give someone else's keys, spew garbage) -// I guess the "peer" part should watch for link packets, disconnect? - -// TCP connections start with a metadata exchange. -// It involves exchanging version numbers and crypto keys -// See version.go for version metadata format - -import ( - "context" - "fmt" - "math/rand" - "net" - "net/url" - "strings" - "sync" - "time" - - "golang.org/x/net/proxy" - - "github.com/yggdrasil-network/yggdrasil-go/src/address" - //"github.com/yggdrasil-network/yggdrasil-go/src/util" -) - -const default_timeout = 6 * time.Second - -// The TCP listener and information about active TCP connections, to avoid duplication. -type tcp struct { - links *links - waitgroup sync.WaitGroup - mutex sync.Mutex // Protecting the below - listeners map[string]*TcpListener - calls map[string]struct{} - conns map[linkInfo](chan struct{}) - tls tcptls -} - -// TcpListener is a stoppable TCP listener interface. These are typically -// returned from calls to the ListenTCP() function and are also used internally -// to represent listeners created by the "Listen" configuration option and for -// multicast interfaces. -type TcpListener struct { - Listener net.Listener - opts tcpOptions - stop chan struct{} -} - -type TcpUpgrade struct { - upgrade func(c net.Conn, o *tcpOptions) (net.Conn, error) - name string -} - -type tcpOptions struct { - linkOptions - upgrade *TcpUpgrade - socksProxyAddr string - socksProxyAuth *proxy.Auth - socksPeerAddr string - tlsSNI string -} - -func (l *TcpListener) Stop() { - defer func() { _ = recover() }() - close(l.stop) -} - -// Wrapper function to set additional options for specific connection types. -func (t *tcp) setExtraOptions(c net.Conn) { - switch sock := c.(type) { - case *net.TCPConn: - _ = sock.SetNoDelay(true) - // TODO something for socks5 - default: - } -} - -// Returns the address of the listener. -func (t *tcp) getAddr() *net.TCPAddr { - // TODO: Fix this, because this will currently only give a single address - // to multicast.go, which obviously is not great, but right now multicast.go - // doesn't have the ability to send more than one address in a packet either - t.mutex.Lock() - defer t.mutex.Unlock() - for _, l := range t.listeners { - return l.Listener.Addr().(*net.TCPAddr) - } - return nil -} - -// Initializes the struct. -func (t *tcp) init(l *links, listeners []ListenAddress) error { - t.links = l - t.tls.init(t) - t.mutex.Lock() - t.calls = make(map[string]struct{}) - t.conns = make(map[linkInfo](chan struct{})) - t.listeners = make(map[string]*TcpListener) - t.mutex.Unlock() - - for _, listenaddr := range listeners { - u, err := url.Parse(string(listenaddr)) - if err != nil { - t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") - continue - } - if _, err := t.listenURL(u, ""); err != nil { - return err - } - } - - return nil -} - -func (t *tcp) stop() error { - t.mutex.Lock() - for _, listener := range t.listeners { - listener.Stop() - } - t.mutex.Unlock() - t.waitgroup.Wait() - return nil -} - -func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) { - var listener *TcpListener - var err error - hostport := u.Host // Used for tcp and tls - if len(sintf) != 0 { - host, port, err := net.SplitHostPort(hostport) - if err == nil { - hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) - } - } - switch u.Scheme { - case "tcp": - listener, err = t.listen(hostport, nil) - case "tls": - listener, err = t.listen(hostport, t.tls.forListener) - default: - t.links.core.log.Errorln("Failed to add listener: listener", u.String(), "is not correctly formatted, ignoring") - } - return listener, err -} - -func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) { - var err error - - ctx := t.links.core.ctx - lc := net.ListenConfig{ - Control: t.tcpContext, - } - listener, err := lc.Listen(ctx, "tcp", listenaddr) - if err == nil { - l := TcpListener{ - Listener: listener, - opts: tcpOptions{upgrade: upgrade}, - stop: make(chan struct{}), - } - t.waitgroup.Add(1) - go t.listener(&l, listenaddr) - return &l, nil - } - - return nil, err -} - -// Runs the listener, which spawns off goroutines for incoming connections. -func (t *tcp) listener(l *TcpListener, listenaddr string) { - defer t.waitgroup.Done() - if l == nil { - return - } - // Track the listener so that we can find it again in future - t.mutex.Lock() - if _, isIn := t.listeners[listenaddr]; isIn { - t.mutex.Unlock() - l.Listener.Close() - return - } - callproto := "TCP" - if l.opts.upgrade != nil { - callproto = strings.ToUpper(l.opts.upgrade.name) - } - t.listeners[listenaddr] = l - t.mutex.Unlock() - // And here we go! - defer func() { - t.links.core.log.Infoln("Stopping", callproto, "listener on:", l.Listener.Addr().String()) - l.Listener.Close() - t.mutex.Lock() - delete(t.listeners, listenaddr) - t.mutex.Unlock() - }() - t.links.core.log.Infoln("Listening for", callproto, "on:", l.Listener.Addr().String()) - go func() { - <-l.stop - l.Listener.Close() - }() - defer l.Stop() - for { - sock, err := l.Listener.Accept() - if err != nil { - t.links.core.log.Errorln("Failed to accept connection:", err) - select { - case <-l.stop: - return - default: - } - time.Sleep(time.Second) // So we don't busy loop - continue - } - t.waitgroup.Add(1) - options := l.opts - go t.handler(sock, true, options) - } -} - -// Checks if we already are calling this address -func (t *tcp) startCalling(saddr string) bool { - t.mutex.Lock() - defer t.mutex.Unlock() - _, isIn := t.calls[saddr] - t.calls[saddr] = struct{}{} - return !isIn -} - -// Checks if a connection already exists. -// If not, it adds it to the list of active outgoing calls (to block future attempts) and dials the address. -// If the dial is successful, it launches the handler. -// When finished, it removes the outgoing call, so reconnection attempts can be made later. -// This all happens in a separate goroutine that it spawns. -func (t *tcp) call(saddr string, options tcpOptions, sintf string) { - go func() { - callname := saddr - callproto := "TCP" - if options.upgrade != nil { - callproto = strings.ToUpper(options.upgrade.name) - } - if sintf != "" { - callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf) - } - if !t.startCalling(callname) { - return - } - defer func() { - // Block new calls for a little while, to mitigate livelock scenarios - rand.Seed(time.Now().UnixNano()) - delay := default_timeout + time.Duration(rand.Intn(10000))*time.Millisecond - time.Sleep(delay) - t.mutex.Lock() - delete(t.calls, callname) - t.mutex.Unlock() - }() - var conn net.Conn - var err error - if options.socksProxyAddr != "" { - if sintf != "" { - return - } - dialerdst, er := net.ResolveTCPAddr("tcp", options.socksProxyAddr) - if er != nil { - return - } - var dialer proxy.Dialer - dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), options.socksProxyAuth, proxy.Direct) - if err != nil { - return - } - ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout) - conn, err = dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", saddr) - done() - if err != nil { - return - } - t.waitgroup.Add(1) - options.socksPeerAddr = saddr - if ch := t.handler(conn, false, options); ch != nil { - <-ch - } - } else { - dst, err := net.ResolveTCPAddr("tcp", saddr) - if err != nil { - return - } - if dst.IP.IsLinkLocalUnicast() { - dst.Zone = sintf - if dst.Zone == "" { - return - } - } - dialer := net.Dialer{ - Control: t.tcpContext, - } - if sintf != "" { - dialer.Control = t.getControl(sintf) - ief, err := net.InterfaceByName(sintf) - if err != nil { - return - } - if ief.Flags&net.FlagUp == 0 { - return - } - addrs, err := ief.Addrs() - if err == nil { - for addrindex, addr := range addrs { - src, _, err := net.ParseCIDR(addr.String()) - if err != nil { - continue - } - if src.Equal(dst.IP) { - continue - } - if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() { - continue - } - bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast() - bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast() - if !bothglobal && !bothlinklocal { - continue - } - if (src.To4() != nil) != (dst.IP.To4() != nil) { - continue - } - if bothglobal || bothlinklocal || addrindex == len(addrs)-1 { - dialer.LocalAddr = &net.TCPAddr{ - IP: src, - Port: 0, - Zone: sintf, - } - break - } - } - if dialer.LocalAddr == nil { - return - } - } - } - ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout) - conn, err = dialer.DialContext(ctx, "tcp", dst.String()) - done() - if err != nil { - t.links.core.log.Debugf("Failed to dial %s: %s", callproto, err) - return - } - t.waitgroup.Add(1) - if ch := t.handler(conn, false, options); ch != nil { - <-ch - } - } - }() -} - -func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) chan struct{} { - defer t.waitgroup.Done() // Happens after sock.close - defer sock.Close() - t.setExtraOptions(sock) - var upgraded bool - if options.upgrade != nil { - var err error - if sock, err = options.upgrade.upgrade(sock, &options); err != nil { - t.links.core.log.Errorln("TCP handler upgrade failed:", err) - return nil - } - upgraded = true - } - var name, proto, local, remote string - if options.socksProxyAddr != "" { - name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksPeerAddr - proto = "socks" - local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ = net.SplitHostPort(options.socksPeerAddr) - } else { - if upgraded { - proto = options.upgrade.name - name = proto + "://" + sock.RemoteAddr().String() - } else { - proto = "tcp" - name = proto + "://" + sock.RemoteAddr().String() - } - local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) - } - localIP := net.ParseIP(local) - if localIP = localIP.To16(); localIP != nil { - var laddr address.Address - var lsubnet address.Subnet - copy(laddr[:], localIP) - copy(lsubnet[:], localIP) - if laddr.IsValid() || lsubnet.IsValid() { - // The local address is with the network address/prefix range - // This would route ygg over ygg, which we don't want - // FIXME ideally this check should happen outside of the core library - // Maybe dial/listen at the application level - // Then pass a net.Conn to the core library (after these kinds of checks are done) - t.links.core.log.Debugln("Dropping ygg-tunneled connection", local, remote) - return nil - } - } - force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() - link, err := t.links.create(sock, name, proto, local, remote, incoming, force, options.linkOptions) - if err != nil { - t.links.core.log.Println(err) - panic(err) - } - t.links.core.log.Debugln("DEBUG: starting handler for", name) - ch, err := link.handler() - t.links.core.log.Debugln("DEBUG: stopped handler for", name, err) - return ch -} diff --git a/src/core/tls.go b/src/core/tls.go deleted file mode 100644 index 9e340ac4..00000000 --- a/src/core/tls.go +++ /dev/null @@ -1,126 +0,0 @@ -package core - -import ( - "bytes" - "crypto/ed25519" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/hex" - "encoding/pem" - "errors" - "log" - "math/big" - "net" - "time" -) - -type tcptls struct { - tcp *tcp - config *tls.Config - forDialer *TcpUpgrade - forListener *TcpUpgrade -} - -func (t *tcptls) init(tcp *tcp) { - t.tcp = tcp - t.forDialer = &TcpUpgrade{ - upgrade: t.upgradeDialer, - name: "tls", - } - t.forListener = &TcpUpgrade{ - upgrade: t.upgradeListener, - name: "tls", - } - - edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) - copy(edpriv[:], tcp.links.core.secret[:]) - - certBuf := &bytes.Buffer{} - - // TODO: because NotAfter is finite, we should add some mechanism to regenerate the certificate and restart the listeners periodically for nodes with very high uptimes. Perhaps regenerate certs and restart listeners every few months or so. - pubtemp := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: hex.EncodeToString(tcp.links.core.public[:]), - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24 * 365), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - derbytes, err := x509.CreateCertificate(rand.Reader, &pubtemp, &pubtemp, edpriv.Public(), edpriv) - if err != nil { - log.Fatalf("Failed to create certificate: %s", err) - } - - if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derbytes}); err != nil { - panic("failed to encode certificate into PEM") - } - - cpool := x509.NewCertPool() - cpool.AppendCertsFromPEM(derbytes) - - t.config = &tls.Config{ - RootCAs: cpool, - Certificates: []tls.Certificate{ - { - Certificate: [][]byte{derbytes}, - PrivateKey: edpriv, - }, - }, - InsecureSkipVerify: true, - MinVersion: tls.VersionTLS13, - } -} - -func (t *tcptls) configForOptions(options *tcpOptions) *tls.Config { - config := t.config.Clone() - config.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { - if len(rawCerts) != 1 { - return errors.New("tls not exactly 1 cert") - } - cert, err := x509.ParseCertificate(rawCerts[0]) - if err != nil { - return errors.New("tls failed to parse cert") - } - if cert.PublicKeyAlgorithm != x509.Ed25519 { - return errors.New("tls wrong cert algorithm") - } - pk := cert.PublicKey.(ed25519.PublicKey) - var key keyArray - copy(key[:], pk) - // If options does not have a pinned key, then pin one now - if options.pinnedEd25519Keys == nil { - options.pinnedEd25519Keys = make(map[keyArray]struct{}) - options.pinnedEd25519Keys[key] = struct{}{} - } - if _, isIn := options.pinnedEd25519Keys[key]; !isIn { - return errors.New("tls key does not match pinned key") - } - return nil - } - return config -} - -func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, error) { - config := t.configForOptions(options) - conn := tls.Server(c, config) - if err := conn.Handshake(); err != nil { - return c, err - } - return conn, nil -} - -func (t *tcptls) upgradeDialer(c net.Conn, options *tcpOptions) (net.Conn, error) { - config := t.configForOptions(options) - config.ServerName = options.tlsSNI - conn := tls.Client(c, config) - if err := conn.Handshake(); err != nil { - return c, err - } - return conn, nil -} diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 24e9d04a..d40bcfc0 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -45,7 +45,7 @@ type interfaceInfo struct { } type listenerInfo struct { - listener *core.TcpListener + listener *core.Listener time time.Time interval time.Duration port uint16 @@ -219,7 +219,7 @@ func (m *Multicast) _announce() { for name, info := range m._listeners { // Prepare our stop function! stop := func() { - info.listener.Stop() + info.listener.Close() delete(m._listeners, name) m.log.Debugln("No longer multicasting on", name) } From b67c313f449427845b46da123f4767683fdf83b3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 12:22:38 +0100 Subject: [PATCH 26/40] Admin socket and `yggdrasilctl` improvements This refactors the request parsing, as well as improving the output for some request types. It also tweaks `yggdrasilctl` output, which should help with #947. --- cmd/yggdrasilctl/main.go | 35 ++++++- src/admin/admin.go | 194 +++++++++++++++++++++------------------ src/core/api.go | 22 ++++- src/multicast/admin.go | 25 ++--- src/tuntap/admin.go | 40 ++++---- 5 files changed, 193 insertions(+), 123 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 78700a40..0ec0ccf8 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -16,6 +16,8 @@ import ( "github.com/olekukonko/tablewriter" "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/core" + "github.com/yggdrasil-network/yggdrasil-go/src/multicast" + "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) @@ -135,6 +137,7 @@ func run() int { table.SetBorder(false) table.SetTablePadding("\t") // pad with tabs table.SetNoWhiteSpace(true) + table.SetAutoWrapText(false) switch strings.ToLower(recv.Request.Name) { case "list": @@ -142,9 +145,12 @@ func run() int { if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } - table.SetHeader([]string{"Command", "Arguments"}) + table.SetHeader([]string{"Command", "Arguments", "Description"}) for _, entry := range resp.List { - table.Append([]string{entry.Command, strings.Join(entry.Fields, ", ")}) + for i := range entry.Fields { + entry.Fields[i] = entry.Fields[i] + "=..." + } + table.Append([]string{entry.Command, strings.Join(entry.Fields, ", "), entry.Description}) } table.Render() @@ -238,8 +244,31 @@ func run() int { break } + case "getmulticastinterfaces": + var resp multicast.GetMulticastInterfacesResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.SetHeader([]string{"Interface"}) + for _, p := range resp.Interfaces { + table.Append([]string{p}) + } + table.Render() + + case "gettun": + var resp tuntap.GetTUNResponse + if err := json.Unmarshal(recv.Response, &resp); err != nil { + panic(err) + } + table.Append([]string{"TUN enabled:", fmt.Sprintf("%#v", resp.Enabled)}) + if resp.Enabled { + table.Append([]string{"Interface name:", resp.Name}) + table.Append([]string{"Interface MTU:", fmt.Sprintf("%d", resp.MTU)}) + } + table.Render() + default: - panic("unknown response type: " + recv.Request.Name) + fmt.Println(string(recv.Response)) } return 0 diff --git a/src/admin/admin.go b/src/admin/admin.go index 4e98c891..376f79a6 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -43,6 +43,7 @@ type AdminSocketResponse struct { } type handler struct { + desc string // What does the endpoint do? args []string // List of human-readable argument names handler core.AddHandlerFunc // First is input map, second is output } @@ -52,16 +53,18 @@ type ListResponse struct { } type ListEntry struct { - Command string `json:"command"` - Fields []string `json:"fields,omitempty"` + Command string `json:"command"` + Description string `json:"description"` + Fields []string `json:"fields,omitempty"` } // AddHandler is called for each admin function to add the handler and help documentation to the API. -func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc core.AddHandlerFunc) error { +func (a *AdminSocket) AddHandler(name, desc string, args []string, handlerfunc core.AddHandlerFunc) error { if _, ok := a.handlers[strings.ToLower(name)]; ok { return errors.New("handler already exists") } a.handlers[strings.ToLower(name)] = handler{ + desc: desc, args: args, handler: handlerfunc, } @@ -81,12 +84,13 @@ func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, erro if a.config.listenaddr == "none" || a.config.listenaddr == "" { return nil, nil } - _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { + _ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) { res := &ListResponse{} for name, handler := range a.handlers { res.List = append(res.List, ListEntry{ - Command: name, - Fields: handler.args, + Command: name, + Description: handler.desc, + Fields: handler.args, }) } sort.SliceStable(res.List, func(i, j int) bool { @@ -100,61 +104,76 @@ func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, erro } func (a *AdminSocket) SetupAdminHandlers() { - _ = a.AddHandler("getSelf", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetSelfRequest{} - res := &GetSelfResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getSelfHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getPeers", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetPeersRequest{} - res := &GetPeersResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getPeersHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getDHT", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetDHTRequest{} - res := &GetDHTResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getDHTHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getPaths", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetPathsRequest{} - res := &GetPathsResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getPathsHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) - _ = a.AddHandler("getSessions", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetSessionsRequest{} - res := &GetSessionsResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := a.getSessionsHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) + _ = a.AddHandler( + "getSelf", "Show details about this node", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetSelfRequest{} + res := &GetSelfResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getSelfHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getPeers", "Show directly connected peers", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetPeersRequest{} + res := &GetPeersResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getPeersHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getDHT", "Show known DHT entries", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetDHTRequest{} + res := &GetDHTResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getDHTHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getPaths", "Show established paths through this node", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetPathsRequest{} + res := &GetPathsResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getPathsHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "getSessions", "Show established traffic sessions with remote nodes", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetSessionsRequest{} + res := &GetSessionsResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.getSessionsHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) @@ -279,35 +298,34 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { for { var err error var buf json.RawMessage - _ = decoder.Decode(&buf) var resp AdminSocketResponse - resp.Status = "success" - if err = json.Unmarshal(buf, &resp.Request); err == nil { - if resp.Request.Name == "" { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: "No request specified", - }) - } else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok { - res, err := h.handler(buf) - if err != nil { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: err.Error(), - }) - } - if resp.Response, err = json.Marshal(res); err != nil { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: err.Error(), - }) - } - } else { - resp.Status = "error" - resp.Response, _ = json.Marshal(ErrorResponse{ - Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name), - }) + if err := func() error { + if err = decoder.Decode(&buf); err != nil { + return fmt.Errorf("Failed to find request") } + if err = json.Unmarshal(buf, &resp.Request); err != nil { + return fmt.Errorf("Failed to unmarshal request") + } + if resp.Request.Name == "" { + return fmt.Errorf("No request specified") + } + reqname := strings.ToLower(resp.Request.Name) + handler, ok := a.handlers[reqname] + if !ok { + return fmt.Errorf("Unknown action '%s', try 'list' for help", reqname) + } + res, err := handler.handler(buf) + if err != nil { + return fmt.Errorf("Handler returned error: %w", err) + } + if resp.Response, err = json.Marshal(res); err != nil { + return fmt.Errorf("Failed to marshal response: %w", err) + } + resp.Status = "success" + return nil + }(); err != nil { + resp.Status = "error" + resp.Error = err.Error() } if err = encoder.Encode(resp); err != nil { a.log.Debugln("Encode error:", err) diff --git a/src/core/api.go b/src/core/api.go index 657b5510..a67ecf3f 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -273,7 +273,7 @@ func (c *Core) PublicKey() ed25519.PublicKey { // Hack to get the admin stuff working, TODO something cleaner type AddHandler interface { - AddHandler(name string, args []string, handlerfunc AddHandlerFunc) error + AddHandler(name, desc string, args []string, handlerfunc AddHandlerFunc) error } type AddHandlerFunc func(json.RawMessage) (interface{}, error) @@ -281,16 +281,28 @@ type AddHandlerFunc func(json.RawMessage) (interface{}, error) // SetAdmin must be called after Init and before Start. // It sets the admin handler for NodeInfo and the Debug admin functions. func (c *Core) SetAdmin(a AddHandler) error { - if err := a.AddHandler("getNodeInfo", []string{"key"}, c.proto.nodeinfo.nodeInfoAdminHandler); err != nil { + if err := a.AddHandler( + "getNodeInfo", "Request nodeinfo from a remote node by its public key", []string{"key"}, + c.proto.nodeinfo.nodeInfoAdminHandler, + ); err != nil { return err } - if err := a.AddHandler("debug_remoteGetSelf", []string{"key"}, c.proto.getSelfHandler); err != nil { + if err := a.AddHandler( + "debug_remoteGetSelf", "Debug use only", []string{"key"}, + c.proto.getSelfHandler, + ); err != nil { return err } - if err := a.AddHandler("debug_remoteGetPeers", []string{"key"}, c.proto.getPeersHandler); err != nil { + if err := a.AddHandler( + "debug_remoteGetPeers", "Debug use only", []string{"key"}, + c.proto.getPeersHandler, + ); err != nil { return err } - if err := a.AddHandler("debug_remoteGetDHT", []string{"key"}, c.proto.getDHTHandler); err != nil { + if err := a.AddHandler( + "debug_remoteGetDHT", "Debug use only", []string{"key"}, + c.proto.getDHTHandler, + ); err != nil { return err } return nil diff --git a/src/multicast/admin.go b/src/multicast/admin.go index 2ae6ec08..9bcc257b 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -20,15 +20,18 @@ func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesReq } func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { - _ = a.AddHandler("getMulticastInterfaces", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetMulticastInterfacesRequest{} - res := &GetMulticastInterfacesResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := m.getMulticastInterfacesHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) + _ = a.AddHandler( + "getMulticastInterfaces", "Show which interfaces multicast is enabled on", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetMulticastInterfacesRequest{} + res := &GetMulticastInterfacesResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := m.getMulticastInterfacesHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 862a3c66..24521fe2 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -7,31 +7,39 @@ import ( ) type GetTUNRequest struct{} -type GetTUNResponse map[string]TUNEntry +type GetTUNResponse struct { + Enabled bool `json:"enabled"` + Name string `json:"name,omitempty"` + MTU uint64 `json:"mtu,omitempty"` +} type TUNEntry struct { MTU uint64 `json:"mtu"` } func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { - *res = GetTUNResponse{ - t.Name(): TUNEntry{ - MTU: t.MTU(), - }, + res.Enabled = t.isEnabled + if !t.isEnabled { + return nil } + res.Name = t.Name() + res.MTU = t.MTU() return nil } func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { - _ = a.AddHandler("getTunTap", []string{}, func(in json.RawMessage) (interface{}, error) { - req := &GetTUNRequest{} - res := &GetTUNResponse{} - if err := json.Unmarshal(in, &req); err != nil { - return nil, err - } - if err := t.getTUNHandler(req, res); err != nil { - return nil, err - } - return res, nil - }) + _ = a.AddHandler( + "getTun", "Show information about the node's TUN interface", []string{}, + func(in json.RawMessage) (interface{}, error) { + req := &GetTUNRequest{} + res := &GetTUNResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := t.getTUNHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) } From 5ad8c33d26501a59412042adc2d90c89a9cd4aa7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 13:38:14 +0100 Subject: [PATCH 27/40] Remove packaging from main CI run --- .github/workflows/ci.yml | 134 +------------------------------------ .github/workflows/pkg.yml | 137 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 133 deletions(-) create mode 100644 .github/workflows/pkg.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7915026d..0c99ac06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,136 +128,4 @@ jobs: - name: Check all tests passed uses: re-actors/alls-green@release/v1 with: - jobs: ${{ toJSON(needs) }} - - build-packages-debian: - strategy: - fail-fast: false - matrix: - pkgarch: ["amd64", "i386", "mips", "mipsel", "armhf", "armel", "arm64"] - - name: Create Package (Debian, ${{ matrix.pkgarch }}) - needs: [tests-ok] - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.19 - - - name: Build package - env: - PKGARCH: ${{ matrix.pkgarch }} - run: sh contrib/deb/generate.sh - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: Debian package (${{ matrix.pkgarch }}) - path: "*.deb" - if-no-files-found: error - - build-packages-macos: - strategy: - fail-fast: false - matrix: - pkgarch: ["amd64", "arm64"] - - name: Create Package (macOS, ${{ matrix.pkgarch }}) - needs: [tests-ok] - - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.19 - - - name: Build package - env: - PKGARCH: ${{ matrix.pkgarch }} - run: sh contrib/macos/create-pkg.sh - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: macOS package (${{ matrix.pkgarch }}) - path: "*.pkg" - if-no-files-found: error - - build-packages-windows: - strategy: - fail-fast: false - matrix: - pkgarch: ["x64", "x86", "arm", "arm64"] - - name: Create Package (Windows, ${{ matrix.pkgarch }}) - needs: [tests-ok] - - runs-on: windows-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.19 - - - name: Build package - run: sh contrib/msi/build-msi.sh ${{ matrix.pkgarch }} - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: Windows package (${{ matrix.pkgarch }}) - path: "*.msi" - if-no-files-found: error - - build-packages-router: - strategy: - fail-fast: false - matrix: - pkgarch: ["edgerouter-x", "edgerouter-lite", "vyos-amd64", "vyos-i386"] - - name: Create Package (Router, ${{ matrix.pkgarch }}) - needs: [tests-ok] - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - path: yggdrasil - - - uses: actions/checkout@v3 - with: - repository: neilalexander/vyatta-yggdrasil - path: vyatta-yggdrasil - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.19 - - - name: Build package - env: - BUILDDIR_YGG: /home/runner/work/yggdrasil-go/yggdrasil-go/yggdrasil - run: cd /home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil && ./build-${{ matrix.pkgarch }} - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: Router package (${{ matrix.pkgarch }}) - path: "/home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil/*.deb" - if-no-files-found: error + jobs: ${{ toJSON(needs) }} \ No newline at end of file diff --git a/.github/workflows/pkg.yml b/.github/workflows/pkg.yml new file mode 100644 index 00000000..f282cc21 --- /dev/null +++ b/.github/workflows/pkg.yml @@ -0,0 +1,137 @@ +name: Packages + +on: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-packages-debian: + strategy: + fail-fast: false + matrix: + pkgarch: ["amd64", "i386", "mips", "mipsel", "armhf", "armel", "arm64"] + + name: Package (Debian, ${{ matrix.pkgarch }}) + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Build package + env: + PKGARCH: ${{ matrix.pkgarch }} + run: sh contrib/deb/generate.sh + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: Debian package (${{ matrix.pkgarch }}) + path: "*.deb" + if-no-files-found: error + + build-packages-macos: + strategy: + fail-fast: false + matrix: + pkgarch: ["amd64", "arm64"] + + name: Package (macOS, ${{ matrix.pkgarch }}) + + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Build package + env: + PKGARCH: ${{ matrix.pkgarch }} + run: sh contrib/macos/create-pkg.sh + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: macOS package (${{ matrix.pkgarch }}) + path: "*.pkg" + if-no-files-found: error + + build-packages-windows: + strategy: + fail-fast: false + matrix: + pkgarch: ["x64", "x86", "arm", "arm64"] + + name: Package (Windows, ${{ matrix.pkgarch }}) + + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Build package + run: sh contrib/msi/build-msi.sh ${{ matrix.pkgarch }} + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: Windows package (${{ matrix.pkgarch }}) + path: "*.msi" + if-no-files-found: error + + build-packages-router: + strategy: + fail-fast: false + matrix: + pkgarch: ["edgerouter-x", "edgerouter-lite", "vyos-amd64", "vyos-i386"] + + name: Package (Router, ${{ matrix.pkgarch }}) + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + path: yggdrasil + + - uses: actions/checkout@v3 + with: + repository: neilalexander/vyatta-yggdrasil + path: vyatta-yggdrasil + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Build package + env: + BUILDDIR_YGG: /home/runner/work/yggdrasil-go/yggdrasil-go/yggdrasil + run: cd /home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil && ./build-${{ matrix.pkgarch }} + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: Router package (${{ matrix.pkgarch }}) + path: "/home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil/*.deb" + if-no-files-found: error From 0abfe78858ae60b2f047ce314503223b21eec07b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 13:46:22 +0100 Subject: [PATCH 28/40] Silence error when reconnecting to already connected peer --- src/core/link.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index 08e8dfc2..4d5cff57 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -113,17 +113,19 @@ func (l *links) isConnectedTo(info linkInfo) bool { func (l *links) call(u *url.URL, sintf string) error { info := linkInfoFor(u.Scheme, sintf, u.Host) if l.isConnectedTo(info) { - return fmt.Errorf("already connected to this node") + return nil } options := linkOptions{ pinnedEd25519Keys: map[keyArray]struct{}{}, } for _, pubkey := range u.Query()["key"] { - if sigPub, err := hex.DecodeString(pubkey); err == nil { - var sigPubKey keyArray - copy(sigPubKey[:], sigPub) - options.pinnedEd25519Keys[sigPubKey] = struct{}{} + sigPub, err := hex.DecodeString(pubkey) + if err != nil { + return fmt.Errorf("pinned key contains invalid hex characters") } + var sigPubKey keyArray + copy(sigPubKey[:], sigPub) + options.pinnedEd25519Keys[sigPubKey] = struct{}{} } switch info.linkType { case "tcp": From 217ac39e77152949889bbb7461635fba962ebb40 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 14:09:08 +0100 Subject: [PATCH 29/40] Allow setting default config path and `AdminListen` at compile time By providing the following items to `LDFLAGS`: * `-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultConfig=/path/to/config` * '-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultAdminListen=unix://path/to/sock' Closes #818. --- src/defaults/defaults.go | 24 ++++++++++++++++++++---- src/defaults/defaults_darwin.go | 2 +- src/defaults/defaults_freebsd.go | 2 +- src/defaults/defaults_linux.go | 2 +- src/defaults/defaults_openbsd.go | 2 +- src/defaults/defaults_other.go | 2 +- src/defaults/defaults_windows.go | 2 +- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 7912fc76..a7492de1 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -4,6 +4,9 @@ import "github.com/yggdrasil-network/yggdrasil-go/src/config" type MulticastInterfaceConfig = config.MulticastInterfaceConfig +var defaultConfig = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultConfig=/path/to/config +var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultAdminListen=unix://path/to/sock' + // Defines which parameters are expected by default for configuration on a // specific platform. These values are populated in the relevant defaults_*.go // for the platform being targeted. They must be set. @@ -23,21 +26,34 @@ type platformDefaultParameters struct { DefaultIfName string } +func GetDefaults() platformDefaultParameters { + defaults := getDefaults() + if defaultConfig != "" { + defaults.DefaultConfigFile = defaultConfig + } + if defaultAdminListen != "" { + defaults.DefaultAdminListen = defaultAdminListen + } + return defaults +} + // Generates default configuration and returns a pointer to the resulting // NodeConfig. This is used when outputting the -genconf parameter and also when // using -autoconf. func GenerateConfig() *config.NodeConfig { + // Get the defaults for the platform. + defaults := GetDefaults() // Create a node configuration and populate it. cfg := new(config.NodeConfig) cfg.NewKeys() cfg.Listen = []string{} - cfg.AdminListen = GetDefaults().DefaultAdminListen + cfg.AdminListen = defaults.DefaultAdminListen cfg.Peers = []string{} cfg.InterfacePeers = map[string][]string{} cfg.AllowedPublicKeys = []string{} - cfg.MulticastInterfaces = GetDefaults().DefaultMulticastInterfaces - cfg.IfName = GetDefaults().DefaultIfName - cfg.IfMTU = GetDefaults().DefaultIfMTU + cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces + cfg.IfName = defaults.DefaultIfName + cfg.IfMTU = defaults.DefaultIfMTU cfg.NodeInfoPrivacy = false return cfg diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 060ce814..a848342d 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -5,7 +5,7 @@ package defaults // Sane defaults for the macOS/Darwin platform. The "default" options may be // may be replaced by the running configuration. -func GetDefaults() platformDefaultParameters { +func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index 84df48ad..c8f918bf 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -5,7 +5,7 @@ package defaults // Sane defaults for the BSD platforms. The "default" options may be // may be replaced by the running configuration. -func GetDefaults() platformDefaultParameters { +func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index c7f5f119..ea646dbb 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -5,7 +5,7 @@ package defaults // Sane defaults for the Linux platform. The "default" options may be // may be replaced by the running configuration. -func GetDefaults() platformDefaultParameters { +func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index 0ec877ca..6e9d1743 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -5,7 +5,7 @@ package defaults // Sane defaults for the BSD platforms. The "default" options may be // may be replaced by the running configuration. -func GetDefaults() platformDefaultParameters { +func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index 37637425..f42b0875 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -5,7 +5,7 @@ package defaults // Sane defaults for the other platforms. The "default" options may be // may be replaced by the running configuration. -func GetDefaults() platformDefaultParameters { +func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "tcp://localhost:9001", diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index c1ea9689..90f3b9e6 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -5,7 +5,7 @@ package defaults // Sane defaults for the Windows platform. The "default" options may be // may be replaced by the running configuration. -func GetDefaults() platformDefaultParameters { +func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "tcp://localhost:9001", From 01c44a087ba8a8fee3ffad561e0c603f2a4c2d1d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 14:41:47 +0100 Subject: [PATCH 30/40] Rename `tuntap` package to `tun` We haven't had TAP support in ages. --- cmd/yggdrasil/main.go | 20 ++++++++++---------- cmd/yggdrasilctl/main.go | 4 ++-- src/defaults/defaults.go | 2 +- src/defaults/defaults_darwin.go | 2 +- src/defaults/defaults_freebsd.go | 2 +- src/defaults/defaults_linux.go | 2 +- src/defaults/defaults_openbsd.go | 2 +- src/defaults/defaults_other.go | 2 +- src/defaults/defaults_windows.go | 2 +- src/{tuntap => tun}/admin.go | 2 +- src/{tuntap => tun}/iface.go | 2 +- src/{tuntap => tun}/options.go | 2 +- src/{tuntap => tun}/tun.go | 7 +------ src/{tuntap => tun}/tun_bsd.go | 2 +- src/{tuntap => tun}/tun_darwin.go | 2 +- src/{tuntap => tun}/tun_linux.go | 4 ++-- src/{tuntap => tun}/tun_other.go | 2 +- src/{tuntap => tun}/tun_windows.go | 6 +++--- 18 files changed, 31 insertions(+), 36 deletions(-) rename src/{tuntap => tun}/admin.go (98%) rename src/{tuntap => tun}/iface.go (98%) rename src/{tuntap => tun}/options.go (95%) rename src/{tuntap => tun}/tun.go (96%) rename src/{tuntap => tun}/tun_bsd.go (99%) rename src/{tuntap => tun}/tun_darwin.go (99%) rename src/{tuntap => tun}/tun_linux.go (94%) rename src/{tuntap => tun}/tun_other.go (98%) rename src/{tuntap => tun}/tun_windows.go (97%) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 1ef6738b..abce0cea 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -32,13 +32,13 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" - "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" + "github.com/yggdrasil-network/yggdrasil-go/src/tun" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type node struct { core *core.Core - tuntap *tuntap.TunAdapter + tun *tun.TunAdapter multicast *multicast.Multicast admin *admin.AdminSocket } @@ -219,7 +219,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { return case args.autoconf: // Use an autoconf-generated config, this will give us random keys and - // port numbers, and will use an automatically selected TUN/TAP interface. + // port numbers, and will use an automatically selected TUN interface. cfg = defaults.GenerateConfig() case args.useconffile != "" || args.useconf: // Read the configuration from either stdin or from the filesystem @@ -348,15 +348,15 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { // Setup the TUN module. { - options := []tuntap.SetupOption{ - tuntap.InterfaceName(cfg.IfName), - tuntap.InterfaceMTU(cfg.IfMTU), + options := []tun.SetupOption{ + tun.InterfaceName(cfg.IfName), + tun.InterfaceMTU(cfg.IfMTU), } - if n.tuntap, err = tuntap.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil { + if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil { panic(err) } - if n.admin != nil && n.tuntap != nil { - n.tuntap.SetupAdminHandlers(n.admin) + if n.admin != nil && n.tun != nil { + n.tun.SetupAdminHandlers(n.admin) } } @@ -378,7 +378,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { func (n *node) shutdown() { _ = n.admin.Stop() _ = n.multicast.Stop() - _ = n.tuntap.Stop() + _ = n.tun.Stop() n.core.Stop() } diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 0ec0ccf8..1e93b98f 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -17,7 +17,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" - "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" + "github.com/yggdrasil-network/yggdrasil-go/src/tun" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) @@ -256,7 +256,7 @@ func run() int { table.Render() case "gettun": - var resp tuntap.GetTUNResponse + var resp tun.GetTUNResponse if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index a7492de1..6374f4ed 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -20,7 +20,7 @@ type platformDefaultParameters struct { // Multicast interfaces DefaultMulticastInterfaces []MulticastInterfaceConfig - // TUN/TAP + // TUN MaximumIfMTU uint64 DefaultIfMTU uint64 DefaultIfName string diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index a848342d..3ffd9ffb 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -19,7 +19,7 @@ func getDefaults() platformDefaultParameters { {Regex: "bridge.*", Beacon: true, Listen: true}, }, - // TUN/TAP + // TUN MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "auto", diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index c8f918bf..43359389 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -18,7 +18,7 @@ func getDefaults() platformDefaultParameters { {Regex: ".*", Beacon: true, Listen: true}, }, - // TUN/TAP + // TUN MaximumIfMTU: 32767, DefaultIfMTU: 32767, DefaultIfName: "/dev/tun0", diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index ea646dbb..bad6233e 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -18,7 +18,7 @@ func getDefaults() platformDefaultParameters { {Regex: ".*", Beacon: true, Listen: true}, }, - // TUN/TAP + // TUN MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "auto", diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index 6e9d1743..e5d1fa9a 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -18,7 +18,7 @@ func getDefaults() platformDefaultParameters { {Regex: ".*", Beacon: true, Listen: true}, }, - // TUN/TAP + // TUN MaximumIfMTU: 16384, DefaultIfMTU: 16384, DefaultIfName: "tun0", diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index f42b0875..bb22864e 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -18,7 +18,7 @@ func getDefaults() platformDefaultParameters { {Regex: ".*", Beacon: true, Listen: true}, }, - // TUN/TAP + // TUN MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "none", diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index 90f3b9e6..e2601bf7 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -18,7 +18,7 @@ func getDefaults() platformDefaultParameters { {Regex: ".*", Beacon: true, Listen: true}, }, - // TUN/TAP + // TUN MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "Yggdrasil", diff --git a/src/tuntap/admin.go b/src/tun/admin.go similarity index 98% rename from src/tuntap/admin.go rename to src/tun/admin.go index 24521fe2..90f712e9 100644 --- a/src/tuntap/admin.go +++ b/src/tun/admin.go @@ -1,4 +1,4 @@ -package tuntap +package tun import ( "encoding/json" diff --git a/src/tuntap/iface.go b/src/tun/iface.go similarity index 98% rename from src/tuntap/iface.go rename to src/tun/iface.go index f629399a..c98b56d6 100644 --- a/src/tuntap/iface.go +++ b/src/tun/iface.go @@ -1,4 +1,4 @@ -package tuntap +package tun const TUN_OFFSET_BYTES = 4 diff --git a/src/tuntap/options.go b/src/tun/options.go similarity index 95% rename from src/tuntap/options.go rename to src/tun/options.go index 10af8d96..7be79211 100644 --- a/src/tuntap/options.go +++ b/src/tun/options.go @@ -1,4 +1,4 @@ -package tuntap +package tun func (m *TunAdapter) _applyOption(opt SetupOption) { switch v := opt.(type) { diff --git a/src/tuntap/tun.go b/src/tun/tun.go similarity index 96% rename from src/tuntap/tun.go rename to src/tun/tun.go index b0c444f4..0f7a70e3 100644 --- a/src/tuntap/tun.go +++ b/src/tun/tun.go @@ -1,10 +1,7 @@ -package tuntap +package tun // This manages the tun driver to send/recv packets to/from applications -// TODO: Crypto-key routing support -// TODO: Set MTU of session properly -// TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery // TODO: Connection timeouts (call Conn.Close() when we want to time out) // TODO: Don't block in reader on writes that are pending searches @@ -13,8 +10,6 @@ import ( "fmt" "net" - //"sync" - "github.com/Arceliar/phony" "golang.zx2c4.com/wireguard/tun" diff --git a/src/tuntap/tun_bsd.go b/src/tun/tun_bsd.go similarity index 99% rename from src/tuntap/tun_bsd.go rename to src/tun/tun_bsd.go index fe36266b..9a8f70ce 100644 --- a/src/tuntap/tun_bsd.go +++ b/src/tun/tun_bsd.go @@ -1,7 +1,7 @@ //go:build openbsd || freebsd // +build openbsd freebsd -package tuntap +package tun import ( "encoding/binary" diff --git a/src/tuntap/tun_darwin.go b/src/tun/tun_darwin.go similarity index 99% rename from src/tuntap/tun_darwin.go rename to src/tun/tun_darwin.go index 6f6e2528..a6d87a0c 100644 --- a/src/tuntap/tun_darwin.go +++ b/src/tun/tun_darwin.go @@ -1,7 +1,7 @@ //go:build !mobile // +build !mobile -package tuntap +package tun // The darwin platform specific tun parts diff --git a/src/tuntap/tun_linux.go b/src/tun/tun_linux.go similarity index 94% rename from src/tuntap/tun_linux.go rename to src/tun/tun_linux.go index f849c00f..1e42b7b8 100644 --- a/src/tuntap/tun_linux.go +++ b/src/tun/tun_linux.go @@ -1,7 +1,7 @@ //go:build !mobile // +build !mobile -package tuntap +package tun // The linux platform specific tun parts @@ -28,7 +28,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { return tun.setupAddress(addr) } -// Configures the TAP adapter with the correct IPv6 address and MTU. Netlink +// Configures the TUN adapter with the correct IPv6 address and MTU. Netlink // 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). diff --git a/src/tuntap/tun_other.go b/src/tun/tun_other.go similarity index 98% rename from src/tuntap/tun_other.go rename to src/tun/tun_other.go index 8ce24953..c618d837 100644 --- a/src/tuntap/tun_other.go +++ b/src/tun/tun_other.go @@ -1,7 +1,7 @@ //go:build !linux && !darwin && !windows && !openbsd && !freebsd && !mobile // +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile -package tuntap +package tun // This is to catch unsupported platforms // If your platform supports tun devices, you could try configuring it manually diff --git a/src/tuntap/tun_windows.go b/src/tun/tun_windows.go similarity index 97% rename from src/tuntap/tun_windows.go rename to src/tun/tun_windows.go index 8dce7274..c3e36596 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tun/tun_windows.go @@ -1,7 +1,7 @@ //go:build windows // +build windows -package tuntap +package tun import ( "bytes" @@ -50,7 +50,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { }) } -// Sets the MTU of the TAP adapter. +// Sets the MTU of the TUN adapter. func (tun *TunAdapter) setupMTU(mtu uint64) error { if tun.iface == nil || tun.Name() == "" { return errors.New("Can't configure MTU as TUN adapter is not present") @@ -77,7 +77,7 @@ func (tun *TunAdapter) setupMTU(mtu uint64) error { return nil } -// Sets the IPv6 address of the TAP adapter. +// Sets the IPv6 address of the TUN adapter. func (tun *TunAdapter) setupAddress(addr string) error { if tun.iface == nil || tun.Name() == "" { return errors.New("Can't configure IPv6 address as TUN adapter is not present") From e165b1fa0ca6bdcbf386410f875d8b0ff49365a6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 14:44:50 +0100 Subject: [PATCH 31/40] Add quote marks to `InterfacePeers` comment Fixes #945. --- src/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.go b/src/config/config.go index 041147b8..c93699e1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -28,7 +28,7 @@ import ( type NodeConfig struct { sync.RWMutex `json:"-"` Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` - InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` + InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` 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."` MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."` From d24d3fa047c0115fd56a98b076c0b13dc6b47e87 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 16:51:31 +0100 Subject: [PATCH 32/40] Use deadline for link handshake (#949) This uses a 6 second deadline for timeouts instead of using `util.FuncTimeout` at 30 seconds for the read and then again for the write. If the handshake doesn't complete within 6 seconds then it's going to probably collapse when we give the connection to Ironwood and it tries to do a keepalive anyway. --- src/core/link.go | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index 4d5cff57..939bb253 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -17,7 +17,6 @@ import ( "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/util" //"github.com/Arceliar/phony" // TODO? use instead of mutexes ) @@ -234,36 +233,25 @@ func (intf *link) handler() error { delete(intf.links._links, intf.info) }) - // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later meta := version_getBaseMetadata() meta.key = intf.links.core.public metaBytes := meta.encode() - // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) - var err error - if !util.FuncTimeout(30*time.Second, func() { - var n int - n, err = intf.conn.Write(metaBytes) - if err == nil && n != len(metaBytes) { - err = errors.New("incomplete metadata send") - } - }) { - return errors.New("timeout on metadata send") + if err := intf.conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil { + return fmt.Errorf("failed to set handshake deadline: %w", err) } - if err != nil { + n, err := intf.conn.Write(metaBytes) + switch { + case err != nil: return fmt.Errorf("write handshake: %w", err) + case err == nil && n != len(metaBytes): + return fmt.Errorf("incomplete handshake send") } - if !util.FuncTimeout(30*time.Second, func() { - var n int - n, err = io.ReadFull(intf.conn, metaBytes) - if err == nil && n != len(metaBytes) { - err = errors.New("incomplete metadata recv") - } - }) { - return errors.New("timeout on metadata recv") - } - if err != nil { + if _, err = io.ReadFull(intf.conn, metaBytes); err != nil { return fmt.Errorf("read handshake: %w", err) } + if err := intf.conn.SetDeadline(time.Time{}); err != nil { + return fmt.Errorf("failed to clear handshake deadline: %w", err) + } meta = version_metadata{} base := version_getBaseMetadata() if !meta.decode(metaBytes) { From d9fe6f72ac9368537f1ee915d9851d6d215b7599 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 17:05:44 +0100 Subject: [PATCH 33/40] Lint tweaks --- src/core/core.go | 4 ++-- src/core/link.go | 15 +++++++-------- src/core/link_tcp.go | 2 +- src/core/link_tls.go | 2 +- src/core/link_unix.go | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/core/core.go b/src/core/core.go index 4cc08ad6..5cfcc7a2 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -127,7 +127,7 @@ func (c *Core) _addPeerLoop() { func (c *Core) Stop() { phony.Block(c, func() { c.log.Infoln("Stopping...") - c._close() + _ = c._close() c.log.Infoln("Stopped") }) } @@ -135,7 +135,7 @@ func (c *Core) Stop() { // This function is unsafe and should only be ran by the core actor. func (c *Core) _close() error { c.cancel() - _ = c.links.shutdown() + c.links.shutdown() err := c.PacketConn.Close() if c.addPeerTimer != nil { c.addPeerTimer.Stop() diff --git a/src/core/link.go b/src/core/link.go index 939bb253..26e44669 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -82,23 +82,22 @@ func (l *links) init(c *Core) error { return nil } -func (l *links) shutdown() error { +func (l *links) shutdown() { phony.Block(l.tcp, func() { for l := range l.tcp._listeners { - l.Close() + _ = l.Close() } }) phony.Block(l.tls, func() { for l := range l.tls._listeners { - l.Close() + _ = l.Close() } }) phony.Block(l.unix, func() { for l := range l.unix._listeners { - l.Close() + _ = l.Close() } }) - return nil } func (l *links) isConnectedTo(info linkInfo) bool { @@ -216,7 +215,7 @@ func (l *links) create(conn net.Conn, name string, info linkInfo, incoming, forc } func (intf *link) handler() error { - defer intf.conn.Close() + defer intf.conn.Close() // nolint:errcheck // Don't connect to this link more than once. if intf.links.isConnectedTo(intf.info) { @@ -249,7 +248,7 @@ func (intf *link) handler() error { if _, err = io.ReadFull(intf.conn, metaBytes); err != nil { return fmt.Errorf("read handshake: %w", err) } - if err := intf.conn.SetDeadline(time.Time{}); err != nil { + if err = intf.conn.SetDeadline(time.Time{}); err != nil { return fmt.Errorf("failed to clear handshake deadline: %w", err) } meta = version_metadata{} @@ -294,7 +293,7 @@ func (intf *link) handler() error { if intf.incoming && !intf.force && !isallowed { intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key)) - intf.close() + _ = intf.close() return fmt.Errorf("forbidden connection") } diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index fcac8b4d..c5a73c9e 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -89,7 +89,7 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { l.core.log.Errorln("Failed to create inbound link:", err) } } - listener.Close() + _ = listener.Close() close(entry.closed) l.core.log.Printf("TCP listener stopped on %s", listener.Addr()) }() diff --git a/src/core/link_tls.go b/src/core/link_tls.go index e6ffd5a8..1e932b66 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -112,7 +112,7 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { l.core.log.Errorln("Failed to create inbound link:", err) } } - tlslistener.Close() + _ = tlslistener.Close() close(entry.closed) l.core.log.Printf("TLS listener stopped on %s", listener.Addr()) }() diff --git a/src/core/link_unix.go b/src/core/link_unix.go index d63c1d90..e71f9362 100644 --- a/src/core/link_unix.go +++ b/src/core/link_unix.go @@ -79,7 +79,7 @@ func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) { l.core.log.Errorln("Failed to create inbound link:", err) } } - listener.Close() + _ = listener.Close() close(entry.closed) l.core.log.Printf("UNIX listener stopped on %s", listener.Addr()) }() From 1de587a971ee2f93d7e7d8b6fd894f32458590cd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 17:06:24 +0100 Subject: [PATCH 34/40] Update to Arceliar/ironwood@ed4b6d4 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b72bf534..0a0f8fa6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20220903132624-ee60c16bcfcf + github.com/Arceliar/ironwood v0.0.0-20220924160422-ed4b6d4750b6 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 diff --git a/go.sum b/go.sum index ec5ea18f..e18dafa8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20220903132624-ee60c16bcfcf h1:kjPkmDHUTWUma/4tqDl208bOk3jsUEqOJA6TsMZo5Jk= -github.com/Arceliar/ironwood v0.0.0-20220903132624-ee60c16bcfcf/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20220924160422-ed4b6d4750b6 h1:iwL6nm2ibyuHXYimRNtFof7RJfe8JB+6CPDskV7K7gA= +github.com/Arceliar/ironwood v0.0.0-20220924160422-ed4b6d4750b6/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= From c922eba2d82503e6ef187d3d3f06572d4b0bacec Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 24 Sep 2022 21:28:09 +0100 Subject: [PATCH 35/40] Fix sending arguments to the admin socket in `yggdrasilctl` --- cmd/yggdrasilctl/main.go | 14 ++++++++------ src/admin/admin.go | 27 ++++++++++++++------------- src/core/nodeinfo.go | 8 ++++++-- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 1e93b98f..f57ab79a 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -85,7 +85,7 @@ func run() int { encoder := json.NewEncoder(conn) send := &admin.AdminSocketRequest{} recv := &admin.AdminSocketResponse{} - + args := map[string]string{} for c, a := range cmdLineEnv.args { if c == 0 { if strings.HasPrefix(a, "-") { @@ -96,15 +96,17 @@ func run() int { send.Name = a continue } - tokens := strings.SplitN(a, "=", 1) + tokens := strings.SplitN(a, "=", 2) switch { case len(tokens) == 1: - panic("incomplete argument supplied") + logger.Println("Ignoring invalid argument:", a) default: - send.Arguments[tokens[0]] = tokens[1] + args[tokens[0]] = tokens[1] } } - + if send.Arguments, err = json.Marshal(args); err != nil { + panic(err) + } if err := encoder.Encode(&send); err != nil { panic(err) } @@ -139,7 +141,7 @@ func run() int { table.SetNoWhiteSpace(true) table.SetAutoWrapText(false) - switch strings.ToLower(recv.Request.Name) { + switch strings.ToLower(send.Name) { case "list": var resp admin.ListResponse if err := json.Unmarshal(recv.Response, &resp); err != nil { diff --git a/src/admin/admin.go b/src/admin/admin.go index 376f79a6..ca1d95ef 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -30,16 +30,16 @@ type AdminSocket struct { } type AdminSocketRequest struct { - Name string `json:"request"` - Arguments map[string]string `json:"arguments,omitempty"` - KeepAlive bool `json:"keepalive,omitempty"` + Name string `json:"request"` + Arguments json.RawMessage `json:"arguments,omitempty"` + KeepAlive bool `json:"keepalive,omitempty"` } type AdminSocketResponse struct { - Status string `json:"status"` - Error string `json:"error,omitempty"` - Request AdminSocketRequest `json:"request"` - Response json.RawMessage `json:"response"` + Status string `json:"status"` + Error string `json:"error,omitempty"` + Request json.RawMessage `json:"request"` + Response json.RawMessage `json:"response"` } type handler struct { @@ -298,25 +298,26 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { for { var err error var buf json.RawMessage + var req AdminSocketRequest var resp AdminSocketResponse if err := func() error { if err = decoder.Decode(&buf); err != nil { return fmt.Errorf("Failed to find request") } - if err = json.Unmarshal(buf, &resp.Request); err != nil { + if err = json.Unmarshal(buf, &req); err != nil { return fmt.Errorf("Failed to unmarshal request") } - if resp.Request.Name == "" { + if req.Name == "" { return fmt.Errorf("No request specified") } - reqname := strings.ToLower(resp.Request.Name) + reqname := strings.ToLower(req.Name) handler, ok := a.handlers[reqname] if !ok { return fmt.Errorf("Unknown action '%s', try 'list' for help", reqname) } - res, err := handler.handler(buf) + res, err := handler.handler(req.Arguments) if err != nil { - return fmt.Errorf("Handler returned error: %w", err) + return err } if resp.Response, err = json.Marshal(res); err != nil { return fmt.Errorf("Failed to marshal response: %w", err) @@ -330,7 +331,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { if err = encoder.Encode(resp); err != nil { a.log.Debugln("Encode error:", err) } - if !resp.Request.KeepAlive { + if !req.KeepAlive { break } else { continue diff --git a/src/core/nodeinfo.go b/src/core/nodeinfo.go index a6132ec2..e02c79c7 100644 --- a/src/core/nodeinfo.go +++ b/src/core/nodeinfo.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "runtime" "strings" "time" @@ -160,11 +161,14 @@ func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) if err := json.Unmarshal(in, &req); err != nil { return nil, err } + if req.Key == "" { + return nil, fmt.Errorf("No remote public key supplied") + } var key keyArray var kbs []byte var err error if kbs, err = hex.DecodeString(req.Key); err != nil { - return nil, err + return nil, fmt.Errorf("Failed to decode public key: %w", err) } copy(key[:], kbs) ch := make(chan []byte, 1) @@ -175,7 +179,7 @@ func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) defer timer.Stop() select { case <-timer.C: - return nil, errors.New("timeout") + return nil, errors.New("Timed out waiting for response") case info := <-ch: var msg json.RawMessage if err := msg.UnmarshalJSON(info); err != nil { From 7db934488ea271dea050d8c03c3d7b5ae559118f Mon Sep 17 00:00:00 2001 From: ehmry Date: Sun, 2 Oct 2022 06:35:43 -0500 Subject: [PATCH 36/40] Reimplement AddPeer and RemovePeer for admin socket (#951) * Reimplement AddPeer and RemovePeer for admin socket Fix #950 * Disconnect the peer on `removePeer` Co-authored-by: Neil Alexander --- src/admin/addpeer.go | 12 +++++ src/admin/admin.go | 28 +++++++++++ src/admin/removepeer.go | 12 +++++ src/core/api.go | 102 +++++++++++++++------------------------- src/core/core.go | 4 +- src/core/link.go | 10 ++-- src/core/options.go | 2 +- 7 files changed, 97 insertions(+), 73 deletions(-) create mode 100644 src/admin/addpeer.go create mode 100644 src/admin/removepeer.go diff --git a/src/admin/addpeer.go b/src/admin/addpeer.go new file mode 100644 index 00000000..f5e37ee9 --- /dev/null +++ b/src/admin/addpeer.go @@ -0,0 +1,12 @@ +package admin + +type AddPeerRequest struct { + Uri string `json:"uri"` + Sintf string `json:"interface,omitempty"` +} + +type AddPeerResponse struct{} + +func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, res *AddPeerResponse) error { + return a.core.AddPeer(req.Uri, req.Sintf) +} diff --git a/src/admin/admin.go b/src/admin/admin.go index ca1d95ef..7f5f467e 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -174,6 +174,34 @@ func (a *AdminSocket) SetupAdminHandlers() { return res, nil }, ) + _ = a.AddHandler( + "addPeer", "Add a peer to the peer list", []string{"uri", "interface"}, + func(in json.RawMessage) (interface{}, error) { + req := &AddPeerRequest{} + res := &AddPeerResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.addPeerHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) + _ = a.AddHandler( + "removePeer", "Remove a peer from the peer list", []string{"uri", "interface"}, + func(in json.RawMessage) (interface{}, error) { + req := &RemovePeerRequest{} + res := &RemovePeerResponse{} + if err := json.Unmarshal(in, &req); err != nil { + return nil, err + } + if err := a.removePeerHandler(req, res); err != nil { + return nil, err + } + return res, nil + }, + ) //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) diff --git a/src/admin/removepeer.go b/src/admin/removepeer.go new file mode 100644 index 00000000..145b8524 --- /dev/null +++ b/src/admin/removepeer.go @@ -0,0 +1,12 @@ +package admin + +type RemovePeerRequest struct { + Uri string `json:"uri"` + Sintf string `json:"interface,omitempty"` +} + +type RemovePeerResponse struct{} + +func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, res *RemovePeerResponse) error { + return a.core.RemovePeer(req.Uri, req.Sintf) +} diff --git a/src/core/api.go b/src/core/api.go index a67ecf3f..75d9d7b3 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -181,78 +181,49 @@ func (c *Core) SetLogger(log util.Logger) { } // AddPeer adds a peer. This should be specified in the peer URI format, e.g.: -// tcp://a.b.c.d:e -// socks://a.b.c.d:e/f.g.h.i:j +// +// tcp://a.b.c.d:e +// socks://a.b.c.d:e/f.g.h.i:j +// // This adds the peer to the peer list, so that they will be called again if the // connection drops. -/* -func (c *Core) AddPeer(addr string, sintf string) error { - if err := c.CallPeer(addr, sintf); err != nil { - // TODO: We maybe want this to write the peer to the persistent - // configuration even if a connection attempt fails, but first we'll need to - // move the code to check the peer URI so that we don't deliberately save a - // peer with a known bad URI. Loading peers from config should really do the - // same thing too but I don't think that happens today +func (c *Core) AddPeer(uri string, sourceInterface string) error { + u, err := url.Parse(uri) + if err != nil { return err } - c.config.Mutex.Lock() - defer c.config.Mutex.Unlock() - if sintf == "" { - for _, peer := range c.config.Current.Peers { - if peer == addr { - return errors.New("peer already added") - } - } - c.config.Current.Peers = append(c.config.Current.Peers, addr) - } else { - if _, ok := c.config.Current.InterfacePeers[sintf]; ok { - for _, peer := range c.config.Current.InterfacePeers[sintf] { - if peer == addr { - return errors.New("peer already added") - } - } - } - if _, ok := c.config.Current.InterfacePeers[sintf]; !ok { - c.config.Current.InterfacePeers[sintf] = []string{addr} - } else { - c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) - } + info, err := c.links.call(u, sourceInterface) + if err != nil { + return err } - return nil -} -*/ - -/* -func (c *Core) RemovePeer(addr string, sintf string) error { - if sintf == "" { - for i, peer := range c.config.Current.Peers { - if peer == addr { - c.config.Current.Peers = append(c.config.Current.Peers[:i], c.config.Current.Peers[i+1:]...) - break - } - } - } else if _, ok := c.config.Current.InterfacePeers[sintf]; ok { - for i, peer := range c.config.Current.InterfacePeers[sintf] { - if peer == addr { - c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf][:i], c.config.Current.InterfacePeers[sintf][i+1:]...) - break - } - } - } - - panic("TODO") // Get the net.Conn to this peer (if any) and close it - c.peers.Act(nil, func() { - ports := c.peers.ports - for _, peer := range ports { - if addr == peer.intf.name() { - c.peers._removePeer(peer) - } - } + phony.Block(c, func() { + c.config._peers[Peer{uri, sourceInterface}] = &info }) - return nil } -*/ + +// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer. +// The peer is not disconnected immediately. +func (c *Core) RemovePeer(uri string, sourceInterface string) error { + var err error + phony.Block(c, func() { + peer := Peer{uri, sourceInterface} + linkInfo, ok := c.config._peers[peer] + if !ok { + err = fmt.Errorf("peer not configured") + return + } + if ok && linkInfo != nil { + c.links.Act(nil, func() { + if link := c.links._links[*linkInfo]; link != nil { + _ = link.close() + } + }) + } + delete(c.config._peers, peer) + }) + return err +} // CallPeer calls a peer once. This should be specified in the peer URI format, // e.g.: @@ -263,7 +234,8 @@ func (c *Core) RemovePeer(addr string, sintf string) error { // This does not add the peer to the peer list, so if the connection drops, the // peer will not be called again automatically. func (c *Core) CallPeer(u *url.URL, sintf string) error { - return c.links.call(u, sintf) + _, err := c.links.call(u, sintf) + return err } func (c *Core) PublicKey() ed25519.PublicKey { diff --git a/src/core/core.go b/src/core/core.go index 5cfcc7a2..b8315416 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -36,7 +36,7 @@ type Core struct { log util.Logger addPeerTimer *time.Timer config struct { - _peers map[Peer]struct{} // configurable after startup + _peers map[Peer]*linkInfo // configurable after startup _listeners map[ListenAddress]struct{} // configurable after startup nodeinfo NodeInfo // immutable after startup nodeinfoPrivacy NodeInfoPrivacy // immutable after startup @@ -66,7 +66,7 @@ func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*C if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil { return nil, fmt.Errorf("error creating encryption: %w", err) } - c.config._peers = map[Peer]struct{}{} + c.config._peers = map[Peer]*linkInfo{} c.config._listeners = map[ListenAddress]struct{}{} c.config._allowedPublicKeys = map[[32]byte]struct{}{} for _, opt := range opts { diff --git a/src/core/link.go b/src/core/link.go index 26e44669..963963a4 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -108,10 +108,10 @@ func (l *links) isConnectedTo(info linkInfo) bool { return isConnected } -func (l *links) call(u *url.URL, sintf string) error { +func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { info := linkInfoFor(u.Scheme, sintf, u.Host) if l.isConnectedTo(info) { - return nil + return info, nil } options := linkOptions{ pinnedEd25519Keys: map[keyArray]struct{}{}, @@ -119,7 +119,7 @@ func (l *links) call(u *url.URL, sintf string) error { for _, pubkey := range u.Query()["key"] { sigPub, err := hex.DecodeString(pubkey) if err != nil { - return fmt.Errorf("pinned key contains invalid hex characters") + return info, fmt.Errorf("pinned key contains invalid hex characters") } var sigPubKey keyArray copy(sigPubKey[:], sigPub) @@ -172,9 +172,9 @@ func (l *links) call(u *url.URL, sintf string) error { }() default: - return errors.New("unknown call scheme: " + u.Scheme) + return info, errors.New("unknown call scheme: " + u.Scheme) } - return nil + return info, nil } func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { diff --git a/src/core/options.go b/src/core/options.go index 8009aa3b..66aa16ce 100644 --- a/src/core/options.go +++ b/src/core/options.go @@ -7,7 +7,7 @@ import ( func (c *Core) _applyOption(opt SetupOption) { switch v := opt.(type) { case Peer: - c.config._peers[v] = struct{}{} + c.config._peers[v] = nil case ListenAddress: c.config._listeners[v] = struct{}{} case NodeInfo: From 8cf76f841d3f613febdbd697f349ae2f5e31cd70 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Oct 2022 12:36:51 +0100 Subject: [PATCH 37/40] Silence `already connected to this node` --- src/core/link.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/link.go b/src/core/link.go index 963963a4..860c3b83 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -219,7 +219,7 @@ func (intf *link) handler() error { // Don't connect to this link more than once. if intf.links.isConnectedTo(intf.info) { - return fmt.Errorf("already connected to this node") + return nil } // Mark the connection as in progress. From 428d2375da391b15ac9e2b525ad65351f5128d0c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Oct 2022 12:39:18 +0100 Subject: [PATCH 38/40] Don't allow configuring the same peer more than once --- src/core/api.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/api.go b/src/core/api.go index 75d9d7b3..dfc309d3 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -188,6 +188,13 @@ func (c *Core) SetLogger(log util.Logger) { // This adds the peer to the peer list, so that they will be called again if the // connection drops. func (c *Core) AddPeer(uri string, sourceInterface string) error { + var known bool + phony.Block(c, func() { + _, known = c.config._peers[Peer{uri, sourceInterface}] + }) + if known { + return fmt.Errorf("peer already configured") + } u, err := url.Parse(uri) if err != nil { return err From 962665189c7d77096b90ef5ea7b3c58293146d9e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Oct 2022 13:15:11 +0100 Subject: [PATCH 39/40] Tweaks to `yggdrasilctl` --- cmd/yggdrasilctl/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index f57ab79a..324550bd 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -174,16 +174,16 @@ func run() int { if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } - table.SetHeader([]string{"Port", "Public Key", "IP Address", "Peering URI", "Uptime", "RX", "TX"}) + table.SetHeader([]string{"Port", "Public Key", "IP Address", "Uptime", "RX", "TX", "URI"}) for _, peer := range resp.Peers { table.Append([]string{ fmt.Sprintf("%d", peer.Port), peer.PublicKey, peer.IPAddress, - peer.Remote, (time.Duration(peer.Uptime) * time.Second).String(), peer.RXBytes.String(), peer.TXBytes.String(), + peer.Remote, }) } table.Render() @@ -269,6 +269,8 @@ func run() int { } table.Render() + case "addpeer", "removepeer": + default: fmt.Println(string(recv.Response)) } From 69632bacb516e8fd7ded1fbb6860d3f224429f08 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Oct 2022 13:20:39 +0100 Subject: [PATCH 40/40] Tidy up --- src/admin/admin.go | 5 ++--- src/config/config.go | 2 -- src/core/api.go | 18 ++++------------ src/core/core.go | 19 +++++++++++++---- src/core/debug.go | 1 - src/core/link.go | 7 +----- src/core/nodeinfo.go | 3 --- src/tun/tun.go | 6 +++--- src/util/util.go | 51 -------------------------------------------- 9 files changed, 25 insertions(+), 87 deletions(-) delete mode 100644 src/util/util.go diff --git a/src/admin/admin.go b/src/admin/admin.go index 7f5f467e..b24bf0de 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -13,14 +13,13 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/core" - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) // TODO: Add authentication type AdminSocket struct { core *core.Core - log util.Logger + log core.Logger listener net.Listener handlers map[string]handler done chan struct{} @@ -72,7 +71,7 @@ func (a *AdminSocket) AddHandler(name, desc string, args []string, handlerfunc c } // Init runs the initial admin setup. -func New(c *core.Core, log util.Logger, opts ...SetupOption) (*AdminSocket, error) { +func New(c *core.Core, log core.Logger, opts ...SetupOption) (*AdminSocket, error) { a := &AdminSocket{ core: c, log: log, diff --git a/src/config/config.go b/src/config/config.go index c93699e1..5bdeec4b 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -19,14 +19,12 @@ package config import ( "crypto/ed25519" "encoding/hex" - "sync" ) // NodeConfig is the main configuration structure, containing configuration // options that are necessary for an Yggdrasil node to run. You will need to // supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { - sync.RWMutex `json:"-"` Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` diff --git a/src/core/api.go b/src/core/api.go index dfc309d3..0fa6dd33 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -2,25 +2,15 @@ package core import ( "crypto/ed25519" + "encoding/json" "fmt" + "net" + "net/url" "sync/atomic" "time" - //"encoding/hex" - "encoding/json" - //"errors" - //"fmt" - "net" - "net/url" - - //"sort" - //"time" - "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/util" - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" - //"github.com/Arceliar/phony" ) type SelfInfo struct { @@ -176,7 +166,7 @@ func (c *Core) Subnet() net.IPNet { // may be useful if you want to redirect the output later. Note that this // expects a Logger from the github.com/gologme/log package and not from Go's // built-in log package. -func (c *Core) SetLogger(log util.Logger) { +func (c *Core) SetLogger(log Logger) { c.log = log } diff --git a/src/core/core.go b/src/core/core.go index b8315416..67f927a6 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -14,9 +14,7 @@ import ( "github.com/Arceliar/phony" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/version" - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) // The Core object represents the Yggdrasil node. You should create a Core @@ -33,7 +31,7 @@ type Core struct { public ed25519.PublicKey links links proto protoHandler - log util.Logger + log Logger addPeerTimer *time.Timer config struct { _peers map[Peer]*linkInfo // configurable after startup @@ -44,7 +42,7 @@ type Core struct { } } -func New(secret ed25519.PrivateKey, logger util.Logger, opts ...SetupOption) (*Core, error) { +func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core, error) { c := &Core{ log: logger, } @@ -193,3 +191,16 @@ func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) { } return } + +type Logger interface { + Printf(string, ...interface{}) + Println(...interface{}) + Infof(string, ...interface{}) + Infoln(...interface{}) + Warnf(string, ...interface{}) + Warnln(...interface{}) + Errorf(string, ...interface{}) + Errorln(...interface{}) + Debugf(string, ...interface{}) + Debugln(...interface{}) +} diff --git a/src/core/debug.go b/src/core/debug.go index ee1f1ed8..6085ceeb 100644 --- a/src/core/debug.go +++ b/src/core/debug.go @@ -5,7 +5,6 @@ package core import ( "fmt" - "net/http" _ "net/http/pprof" "os" diff --git a/src/core/link.go b/src/core/link.go index 860c3b83..b4515276 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -9,15 +9,11 @@ import ( "net" "net/url" "strings" - - //"sync/atomic" - "time" - "sync/atomic" + "time" "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" - //"github.com/Arceliar/phony" // TODO? use instead of mutexes ) type links struct { @@ -28,7 +24,6 @@ type links struct { unix *linkUNIX // UNIX interface support socks *linkSOCKS // SOCKS interface support _links map[linkInfo]*link // *link is nil if connection in progress - // TODO timeout (to remove from switch), read from config.ReadTimeout } // linkInfo is used as a map key diff --git a/src/core/nodeinfo.go b/src/core/nodeinfo.go index e02c79c7..bac0935b 100644 --- a/src/core/nodeinfo.go +++ b/src/core/nodeinfo.go @@ -11,9 +11,6 @@ import ( iwt "github.com/Arceliar/ironwood/types" "github.com/Arceliar/phony" - - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" - "github.com/yggdrasil-network/yggdrasil-go/src/version" ) diff --git a/src/tun/tun.go b/src/tun/tun.go index 0f7a70e3..ce1bd169 100644 --- a/src/tun/tun.go +++ b/src/tun/tun.go @@ -14,9 +14,9 @@ import ( "golang.zx2c4.com/wireguard/tun" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" - "github.com/yggdrasil-network/yggdrasil-go/src/util" ) type MTU uint16 @@ -27,7 +27,7 @@ type MTU uint16 // calling yggdrasil.Start(). type TunAdapter struct { rwc *ipv6rwc.ReadWriteCloser - log util.Logger + log core.Logger addr address.Address subnet address.Subnet mtu uint64 @@ -90,7 +90,7 @@ func MaximumMTU() uint64 { // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func New(rwc *ipv6rwc.ReadWriteCloser, log util.Logger, opts ...SetupOption) (*TunAdapter, error) { +func New(rwc *ipv6rwc.ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) { tun := &TunAdapter{ rwc: rwc, log: log, diff --git a/src/util/util.go b/src/util/util.go deleted file mode 100644 index e2e21464..00000000 --- a/src/util/util.go +++ /dev/null @@ -1,51 +0,0 @@ -// Package util contains miscellaneous utilities used by yggdrasil. -// In particular, this includes a crypto worker pool, Cancellation machinery, and a sync.Pool used to reuse []byte. -package util - -// These are misc. utility functions that didn't really fit anywhere else - -import ( - "time" -) - -// Any logger that satisfies this interface is suitable for Yggdrasil. -type Logger interface { - Printf(string, ...interface{}) - Println(...interface{}) - Infof(string, ...interface{}) - Infoln(...interface{}) - Warnf(string, ...interface{}) - Warnln(...interface{}) - Errorf(string, ...interface{}) - Errorln(...interface{}) - Debugf(string, ...interface{}) - Debugln(...interface{}) -} - -// TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. -func TimerStop(t *time.Timer) bool { - stopped := t.Stop() - select { - case <-t.C: - default: - } - return stopped -} - -// FuncTimeout runs the provided function in a separate goroutine, and returns true if the function finishes executing before the timeout passes, or false if the timeout passes. -// It includes no mechanism to stop the function if the timeout fires, so the user is expected to do so on their own (such as with a Cancellation or a context). -func FuncTimeout(timeout time.Duration, f func()) bool { - success := make(chan struct{}) - go func() { - defer close(success) - f() - }() - timer := time.NewTimer(timeout) - defer TimerStop(timer) - select { - case <-success: - return true - case <-timer.C: - return false - } -}