nix-archive-1(type directoryentry(name COPYRIGHTnode(typeregularcontentsqCopyright (c) 2023 The Ironwood Authors. All rights reserved. Please see the LICENSE file for more information. ))entry(nameLICENSEnode(typeregularcontentsUAMozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ))entry(name README.mdnode(typeregularcontents # Ironwood Ironwood is a routing library with a `net.PacketConn`-compatible interface using `ed25519.PublicKey`s as addresses. Basically, you use it when you want to communicate with some other nodes in a network, but you can't guarantee that you can directly connect to every node in that network. It was written to test improvements to / replace the routing logic in [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go), but it may be useful for other network applications. Note: Ironwood is pre-alpha work-in-progress. There's no stable API, versioning, or expectation that any two commits will be compatible with each other. Also, it hasn't been audited by a security expert. While the author is unaware of any security vulnerabilities, it would be wise to think of this as an insecure proof-of-concept. Use it at your own risk. ## Packages Ironwood is split into several sub-packages. ### Types The `types` package exposes a `types.PacketConn` interface type. This is a superset of the `net.PacketConn` with a few extra functions to e.g. pass in `net.Conn` connections to peers. It uses the `types.Addr` as addresses, which is just a wrapper around `ed25519.PublicKey` implementing the `net.Addr` interface. You probably want to write your code in terms of these interface types, and then call `NewPacketConn` from one of the below packages, depending on what the requirements are for your application. ### Network The `network` package implements all of the important routing logic. Packets sent over the `network.PacketConn` are unencrypted, unsigned, and otherwise at least as insecure as UDP. The main use case for this package is to wrap it with a more secure protocol (e.g. DTLS, QUIC, or TLS-over-μTP). Internally, protocol traffic is signed (when necessary for authentication), but never encrypted, so this should be legal in environments where encryption is not permissible (e.g. amateur radio networks). ### Signed The `signed` package is a small proof-of-concept wrapper around `network`. This package signs messages before sending and checks signatures upon receiving. This allows for some level of authentication without encryption, so it should still be legal for e.g. amateur radio networks. ### Encrypted The `encrypted` package wraps `network` with ephemeral key [nacl/box]](https://pkg.go.dev/golang.org/x/crypto/nacl/box) (X25519/XSalsa20/Poly1305) for authenticated encryption, with ratcheting for improved forward secrecy and replay protection. ## Routing The routing logic in `network` is still undocumented. The basic idea is: 1. Packets are normally forwarded using greedy routing in a metric space, where the metric space is defined by the distance between nodes in a spanning tree embedding of the network. 2. If a packet becomes unroutable (e.g. reaches a dead end), then a path broken notification is sent to the sender (via treespace). 3. If the sender does not know the destination's location in treespace, or receives a path broken notification, the sender does a lookup of the node's destination. There are a ton of technical details about building the spanning tree, bootstrapping, responding to link failures, etc., all of which is beyond the scope of a readme file. All of those protocol level details are subject to change, so don't expect adequate documentation any earlier than the late alpha or early beta stage. ### Spanning Tree The spanning tree is made up of a constant size message per node specifying which peer of that node acts as its parent (or itself, in the case of the root). These are stored in a soft-state CRDT-Set, and some subset of the information is gossiped with each peer (specifically, the spanning tree ancestry of the sending node and the peer). CRDT semantics ensures that two peered node's views of their shared relevant part of the tree are eventually consistent, and that updates to a common ancestor of multiple peers are applied atomically to all peer records stored in the local routing table. ### Lookups The key->location lookup protocol resembles ARP from IPv4 or NDP from IPv6 when these are run on an ethernet network. The protocol uses multicast traffic sent over the spanning tree. Each node connected to an on-tree link maintains a bloom filter of which nodes are reachable by routing a message towards that part of the tree. This allows lookups to be routed with only constant state per peer needed at each node. The tradeoff is that there is a non-zero false positive rate: nodes may *appear* to lead to a subtree that contains the destination key, but in fact contain one or more unrelated addresses that set the same bits of the bloom filter, and so the node may end up routing lookup traffic unnecessarily (until it becomes apparent that there is no path to a node with a key that passes the filter). As these lookups are routed as multicast traffic, this does not prevent the intended destination from receiving the lookup traffic, it just causes unnecessary copies of the traffic to be gossiped around some portion of the network. To put precise numbers on this: an 8192-bit bloom filter is used (1024 bytes, small enough to fit into a single minimum unfragmented size 1280-byte IPv6 packet when typical TCP/IP headers are included). The bloom filters use 8 hash functions per key. For a bloom filter that contains a single key (e.g. a leaf node), this results in a false positive rate approximately the same as an 80-bit address collision. For a 1 million node network, the first false positive is expected when a bloom filter contains about 200 nodes (i.e. if you are a gateway to a subtree with 200 nodes, then in a 1 million node network, you can expect 1 node outside of your subtree to match your bloom filter and cause lookups for it to be routed to your gateway node). In the same network, a majority of nodes that pass your bloom filter are expected to be true positives up to subtrees of about 500 nodes. As full knowledge of the destination key may not be available (e.g. in Yggdrasil, we can only rely on knowing at least the bits of the key that fit into a `/64` prefix address), applications configure a transformation to be applied to keys before adding to or querying the bloom filters. Given the ~80-bit collision resistance of leaf node bloom filters, a subnet address collision in Yggdrasil is more likely than a leaf node bloom filter collision until about 32 extra bits of key have been brute forced into the subnet address. In other words: it is statistically implausible that leaf nodes will see *any* unnecessary lookup traffic due to false positives in the lookup structure. This lookup protocol is meant to replace the necessary functionality provided by the DHT routing and pathfinder logic in earlier version of Ironwood (used in Yggdrasil v0.4.X). A change of some kind was needed for a few reasons: 1. The DHT used in Ygg v0.4.X is a hard state routing protocol. This is hard to secure, and can require fairly high memory use per node for some nodes, but the bandwidth use is low (both idle and to route enough traffic to perform pathfinding). 2. The soft state DHT variant (never used in Ygg, similar to what was later adopted by Matrix's pinecone) is more secure against attacks, but it requires relatively frequent keep-alive traffic to prevent the soft state from expiring. 3. Either version of the DHT has a worst case `O(n)` convergence time, ignoring the time needed to route messages between keyspace neighbors. In particular, if two networks are joined, they need to "zip together" 1 node at a time. The new lookup protocol is expected to be at least as secure as the soft state DHT, use (asymptotically) as little bandwidth as the hard state DHT, and converge as fast as the spanning tree itself, while requiring only constant state per peer. The down side is the possibility for higher lookup cost compared to the old DHT-based protocol, for some "core" region of the network, due to the significant false positive rate. Given that nodes in the core of the network are expected to need high bandwidth anyway (to carry user application traffic), this seems like it could be a preferable direction to explore in the tradeoff space. ))entry(namecmdnode(type directoryentry(nameironwood-examplenode(type directoryentry(namego.modnode(typeregularcontents6module github.com/Arceliar/ironwood-example go 1.16 replace github.com/Arceliar/ironwood => ../../ require ( github.com/Arceliar/ironwood v0.0.0-00010101000000-000000000000 github.com/vishvananda/netlink v1.1.0 golang.org/x/net v0.9.0 golang.org/x/sys v0.7.0 golang.zx2c4.com/wireguard v0.0.20201118 ) ))entry(namego.sumnode(typeregularcontentsµgithub.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q= github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bloom/v3 v3.3.1 h1:K2+A19bXT8gJR5mU7y+1yW6hsKfNCjcP2uNfLFKncjQ= github.com/bits-and-blooms/bloom/v3 v3.3.1/go.mod h1:bhUUknWd5khVbTe4UgMCSiOOVJzr3tMoijSK3WwvW90= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= 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 h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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-20201110031124-69a78807bb2b/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard v0.0.20201118 h1:QL8y2C7uO8T6z1GY+UX/hSeWiYEBurQkXjOTRFtCvXU= golang.zx2c4.com/wireguard v0.0.20201118/go.mod h1:Dz+cq5bnrai9EpgYj4GDof/+qaGzbRWbeaAOs1bUYa0= ))entry(namemain.gonode(typeregularcontents·package main import ( "crypto/ed25519" "flag" "fmt" "net" "os" "os/signal" "syscall" iwc "github.com/Arceliar/ironwood/encrypted" iwn "github.com/Arceliar/ironwood/network" iws "github.com/Arceliar/ironwood/signed" iwt "github.com/Arceliar/ironwood/types" "log" "net/http" _ "net/http/pprof" ) var ifname = flag.String("ifname", "\000", "interface name to bind to") var pprof = flag.String("pprof", "", "listen to pprof on this port") var enc = flag.Bool("enc", false, "encrypt traffic (must be enabled on all nodes)") var sign = flag.Bool("sign", false, "sign traffic (must be enabled on all nodes)") func main() { flag.Parse() if pprof != nil && *pprof != "" { go func() { log.Println(http.ListenAndServe(*pprof, nil)) }() } _, key, _ := ed25519.GenerateKey(nil) var pc iwt.PacketConn var opts []iwn.Option var doNotify2 func(key ed25519.PublicKey) doNotify1 := func(key ed25519.PublicKey) { doNotify2(key) } opts = append(opts, iwn.WithBloomTransform(transformKey)) opts = append(opts, iwn.WithPathNotify(doNotify1)) if *enc && *sign { panic("TODO a useful error message (can't use both -unenc and -sign)") } else if *enc { pc, _ = iwc.NewPacketConn(key, opts...) } else if *sign { pc, _ = iws.NewPacketConn(key, opts...) } else { pc, _ = iwn.NewPacketConn(key, opts...) } defer pc.Close() doNotify2 = func(key ed25519.PublicKey) { putKey(key) flushBuffer(pc, key) // Ugly hack, we need the pc for flushBuffer to work } // get address and pc.SetOutOfBandHandler localAddr := pc.LocalAddr() pubKey := ed25519.PublicKey(localAddr.(iwt.Addr)) addrBytes := getAddr(pubKey) // open tun/tap and assign address ip := net.IP(addrBytes[:]) fmt.Println("Our IP address is", ip.String()) if ifname != nil && *ifname != "none" { tun := setupTun(*ifname, ip.String()+"/8") // read/write between tun/tap and packetconn go tunReader(tun, pc) go tunWriter(tun, pc) } // open multicast and start adding peers mc := newMulticastConn() go mcSender(mc, pubKey) go mcListener(mc, pubKey, pc) // listen for TCP, pass connections to packetConn.HandleConn go listenTCP(pc) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs } ))entry(namenet.gonode(typeregularcontentsôpackage main import ( "bytes" "context" "crypto/ed25519" "fmt" "io" "net" "sync" "syscall" "time" "golang.org/x/net/ipv6" "golang.org/x/sys/unix" iwt "github.com/Arceliar/ironwood/types" ) const listenAddrString = ":12345" const groupAddrString = "[ff02::114]:12345" var groupAddr *net.UDPAddr var connectionsMutex sync.RWMutex var connections map[string]net.Conn func init() { var err error if groupAddr, err = net.ResolveUDPAddr("udp6", groupAddrString); err != nil { panic(err) } connections = make(map[string]net.Conn) } func newMulticastConn() *ipv6.PacketConn { reuse := func(network, address string, c syscall.RawConn) (err error) { _ = c.Control(func(fd uintptr) { err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) }) return } lc := net.ListenConfig{ Control: reuse, } conn, err := lc.ListenPacket(context.Background(), "udp", listenAddrString) if err != nil { panic(err) } mc := ipv6.NewPacketConn(conn) return mc } func mcSender(mc *ipv6.PacketConn, key ed25519.PublicKey) { intfs, err := net.Interfaces() if err != nil { panic(err) } for _, intf := range intfs { addrs, err := intf.Addrs() if err != nil { panic(err) } for _, addr := range addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) if addrIP.To4() != nil { continue } else if !addrIP.IsLinkLocalUnicast() { continue } tmp := intf _ = mc.JoinGroup(&tmp, groupAddr) destAddr, err := net.ResolveUDPAddr("udp6", groupAddrString) if err != nil { panic(err) } destAddr.Zone = tmp.Name _, _ = mc.WriteTo(key, nil, destAddr) break } } time.AfterFunc(3*time.Second, func() { mcSender(mc, key) }) } func mcListener(mc *ipv6.PacketConn, key ed25519.PublicKey, pc iwt.PacketConn) { for { bs := make([]byte, 2048) n, _, from, err := mc.ReadFrom(bs) if err != nil { panic(err) } if n != ed25519.PublicKeySize { continue } if bytes.Equal(bs[:n], key) { continue } go func() { destKey := ed25519.PublicKey(bs[:n]) destKeyString := iwt.Addr(destKey).String() tcpAddr := new(net.TCPAddr) uAddr := from.(*net.UDPAddr) tcpAddr.IP = uAddr.IP tcpAddr.Port = 12345 tcpAddr.Zone = uAddr.Zone var isIn bool connectionsMutex.RLock() _, isIn = connections[destKeyString] connectionsMutex.RUnlock() if isIn { return } conn, err := net.DialTimeout(tcpAddr.Network(), tcpAddr.String(), time.Second) if err != nil { //panic(err) return } conn.(*net.TCPConn).SetKeepAlive(true) handleTCP(pc, conn) }() } } func handleTCP(pc iwt.PacketConn, conn net.Conn) { defer conn.Close() localAddr := pc.LocalAddr() pubKey := ed25519.PublicKey(localAddr.(iwt.Addr)) if _, err := conn.Write(pubKey); err != nil { fmt.Println("Error writing our key:", err) return } there := ed25519.PublicKey(make([]byte, ed25519.PublicKeySize)) _ = conn.SetReadDeadline(time.Now().Add(time.Second)) if _, err := io.ReadFull(conn, there); err != nil { fmt.Println("Error reading remote key:", err) return } destKeyString := iwt.Addr(there).String() // TODO? check this against key from UDP announcement connectionsMutex.Lock() if _, isIn := connections[destKeyString]; isIn { connectionsMutex.Unlock() return } connections[destKeyString] = conn connectionsMutex.Unlock() addrBytes := make([]byte, 16) addrBytes[0] = 0xfd copy(addrBytes[1:], there) for idx := 1; idx < len(addrBytes); idx++ { addrBytes[idx] = ^addrBytes[idx] } ip := net.IP(addrBytes) fmt.Println("Connected to", ip.String()) if err := pc.HandleConn(there, conn, 0); err != nil { fmt.Println("Disconnected from", ip.String(), "due to:", err) } else { fmt.Println("Disconnected from", ip.String()) } connectionsMutex.Lock() defer connectionsMutex.Unlock() delete(connections, destKeyString) } func listenTCP(pc iwt.PacketConn) { listener, err := net.Listen("tcp", listenAddrString) if err != nil { panic(err) } for { conn, err := listener.Accept() if err != nil { panic(err) } go handleTCP(pc, conn) } } ))entry(nametun.gonode(typeregularcontentsäpackage main import ( "bytes" "crypto/ed25519" "fmt" "net" "sync" "time" "github.com/vishvananda/netlink" "golang.zx2c4.com/wireguard/tun" iwt "github.com/Arceliar/ironwood/types" ) func setupTun(ifname, address string) tun.Device { dev, err := tun.CreateTUN(ifname, 1500) if err != nil { panic(err) } nladdr, err := netlink.ParseAddr(address) if err != nil { panic(err) } name, err := dev.Name() if err != nil { panic(err) } nlintf, err := netlink.LinkByName(name) if err != nil { panic(err) } else if err := netlink.AddrAdd(nlintf, nladdr); err != nil { panic(err) } else if err := netlink.LinkSetMTU(nlintf, 1500); err != nil { panic(err) } else if err := netlink.LinkSetUp(nlintf); err != nil { panic(err) } return dev } const tunOffsetBytes = 4 func tunReader(dev tun.Device, pc iwt.PacketConn) { localAddr := pc.LocalAddr() pubKey := ed25519.PublicKey(localAddr.(iwt.Addr)) addrBytes := getAddr(pubKey) buf := make([]byte, 2048) for { n, err := dev.Read(buf, tunOffsetBytes) if err != nil { panic(err) } if n <= tunOffsetBytes { panic("tunOffsetBytes") } bs := buf[tunOffsetBytes : tunOffsetBytes+n] if len(bs) < 40 { panic("undersized packet") } var srcAddr, dstAddr [16]byte copy(srcAddr[:], bs[8:24]) copy(dstAddr[:], bs[24:40]) if srcAddr != addrBytes { //panic("wrong source address") continue } if dstAddr[0] != 0xfd { //panic("wrong dest subnet") continue } destKey, isGood := getKey(dstAddr) //destKey, isGood := getTargetKey(dstAddr) if !isGood { destKey, _ := getTargetKey(dstAddr) pc.SendLookup(destKey) pushBufMsg(dstAddr, bs) continue } //destKey = pc.GetKeyFor(destKey) if !checkKey(dstAddr, destKey) { continue } if isGood { dest := iwt.Addr(destKey) n, err = pc.WriteTo(bs, dest) if err != nil { panic(err) } if n != len(bs) { panic("failed to write full packet to packetconn") } } } } func tunWriter(dev tun.Device, pc net.PacketConn) { localAddr := pc.LocalAddr() pubKey := ed25519.PublicKey(localAddr.(iwt.Addr)) addrBytes := getAddr(pubKey) rawBuf := make([]byte, 2048) for { buf := rawBuf n, remote, err := pc.ReadFrom(buf[tunOffsetBytes:]) if err != nil { panic(err) } if n < 40 { panic("undersized packet") } buf = buf[:tunOffsetBytes+n] bs := buf[tunOffsetBytes : tunOffsetBytes+n] var srcAddr, dstAddr [16]byte copy(srcAddr[:], bs[8:24]) copy(dstAddr[:], bs[24:40]) if srcAddr[0] != 0xfd { fmt.Println(net.IP(srcAddr[:]).String()) // FIXME panic("wrong source subnet") continue } if dstAddr[0] != 0xfd { panic("wrong dest subnet") continue } if dstAddr != addrBytes { panic("wrong dest addr") continue } remoteKey := ed25519.PublicKey(remote.(iwt.Addr)) if !checkKey(srcAddr, remoteKey) { continue } //putKey(remoteKey) n, err = dev.Write(buf, tunOffsetBytes) if err != nil { panic(err) } if n != len(buf) { panic("wrong number of bytes written") } } } var keyMutex sync.Mutex var keyMap map[[16]byte]*keyInfo type keyInfo struct { key ed25519.PublicKey timer *time.Timer } func putKey(key ed25519.PublicKey) { addr := getAddr(key) info := new(keyInfo) info.key = ed25519.PublicKey(append([]byte(nil), key...)) info.timer = time.AfterFunc(time.Minute, func() { keyMutex.Lock() defer keyMutex.Unlock() delete(keyMap, addr) }) keyMutex.Lock() defer keyMutex.Unlock() if keyMap == nil { keyMap = make(map[[16]byte]*keyInfo) } if old, isIn := keyMap[addr]; isIn { old.timer.Stop() } keyMap[addr] = info } func getTargetKey(addr [16]byte) (ed25519.PublicKey, bool) { destKey := ed25519.PublicKey(make([]byte, ed25519.PublicKeySize)) copy(destKey, addr[1:]) for idx := range destKey { destKey[idx] = ^destKey[idx] } return destKey, true } func getKey(addr [16]byte) (ed25519.PublicKey, bool) { keyMutex.Lock() info := keyMap[addr] keyMutex.Unlock() if info != nil { //fmt.Println("Found key", net.IP(addr).String(), info.key) return info.key, true } destKey := ed25519.PublicKey(make([]byte, ed25519.PublicKeySize)) copy(destKey, addr[1:]) for idx := range destKey { destKey[idx] = ^destKey[idx] } return destKey, false } func checkKey(addr [16]byte, key ed25519.PublicKey) bool { tmp := addr for idx := range tmp { tmp[idx] = ^tmp[idx] } return bytes.Equal(tmp[1:], key[:len(addr)-1]) } func getAddr(key ed25519.PublicKey) (addr [16]byte) { copy(addr[1:], key) for idx := range addr { addr[idx] = ^addr[idx] } addr[0] = 0xfd return } func transformKey(key ed25519.PublicKey) ed25519.PublicKey { addr := getAddr(key) xform, _ := getTargetKey(addr) return xform } // Buffer traffic while waiting for a key var bufMutex sync.Mutex var bufMap map[[16]byte]*bufInfo type bufInfo struct { msg []byte timer *time.Timer } func pushBufMsg(addr [16]byte, msg []byte) { info := new(bufInfo) info.msg = append(info.msg, msg...) bufMutex.Lock() defer bufMutex.Unlock() if bufMap == nil { bufMap = make(map[[16]byte]*bufInfo) } old := bufMap[addr] bufMap[addr] = info info.timer = time.AfterFunc(time.Minute, func() { bufMutex.Lock() defer bufMutex.Unlock() if n := bufMap[addr]; n == info { delete(bufMap, addr) } }) if old != nil { old.timer.Stop() } } func popBufMsg(addr [16]byte) []byte { bufMutex.Lock() defer bufMutex.Unlock() if info := bufMap[addr]; info != nil { info.timer.Stop() return info.msg } return nil } const ( oobKeyReq = 1 oobKeyRes = 2 ) func flushBuffer(pc net.PacketConn, destKey ed25519.PublicKey) { addr := getAddr(destKey) if bs := popBufMsg(addr); bs != nil { dest := iwt.Addr(destKey) n, err := pc.WriteTo(bs, dest) if err != nil { panic(err) } if n != len(bs) { panic("failed to write full packet to packetconn") } } } ))))))entry(name encryptednode(type directoryentry(name crypto.gonode(typeregularcontentsà package encrypted import ( "bytes" "crypto/ed25519" "crypto/rand" "encoding/binary" "golang.org/x/crypto/nacl/box" "github.com/Arceliar/ironwood/encrypted/internal/e2c" ) /******** * util * ********/ func bytesEqual(a, b []byte) bool { return bytes.Equal(a, b) } func bytesPush(dest, source []byte, offset int) (newOffset int) { copy(dest[offset:], source) return offset + len(source) } func bytesPop(dest, source []byte, offset int) (newOffset int) { copy(dest[:], source[offset:]) return offset + len(dest) } /****** * ed * ******/ const ( edPubSize = 32 edPrivSize = 64 edSigSize = 64 ) type edPub [edPubSize]byte type edPriv [edPrivSize]byte type edSig [edSigSize]byte func edSign(msg []byte, priv *edPriv) *edSig { var sig edSig copy(sig[:], ed25519.Sign(priv[:], msg)) return &sig } func edCheck(msg []byte, sig *edSig, pub *edPub) bool { return ed25519.Verify(pub[:], msg, sig[:]) } func (pub *edPub) asKey() ed25519.PublicKey { return ed25519.PublicKey(pub[:]) } func (pub *edPub) toBox() (*boxPub, error) { var c boxPub e := e2c.Ed25519PublicKeyToCurve25519(pub.asKey()) copy(c[:], e) return &c, nil } func (priv *edPriv) toBox() *boxPriv { var c boxPriv e := e2c.Ed25519PrivateKeyToCurve25519(ed25519.PrivateKey(priv[:])) copy(c[:], e) return &c } func (priv *edPriv) pub() *edPub { pk := ed25519.PrivateKey(priv[:]).Public().(ed25519.PublicKey) pub := new(edPub) copy(pub[:], pk[:]) return pub } /******* * box * *******/ const ( boxPubSize = 32 boxPrivSize = 32 boxSharedSize = 32 boxNonceSize = 24 boxOverhead = box.Overhead ) type boxPub [boxPubSize]byte type boxPriv [boxPrivSize]byte type boxShared [boxSharedSize]byte type boxNonce [boxNonceSize]byte func newBoxKeys() (pub boxPub, priv boxPriv) { bpub, bpriv, err := box.GenerateKey(rand.Reader) if err != nil { panic("failed to generate keys") } pub, priv = boxPub(*bpub), boxPriv(*bpriv) return } func getShared(out *boxShared, pub *boxPub, priv *boxPriv) { box.Precompute((*[32]byte)(out), (*[32]byte)(pub), (*[32]byte)(priv)) } func boxOpen(out, boxed []byte, nonce uint64, shared *boxShared) ([]byte, bool) { n := nonceForUint64(nonce) return box.OpenAfterPrecomputation(out, boxed, (*[24]byte)(&n), (*[32]byte)(shared)) } func boxSeal(out, msg []byte, nonce uint64, shared *boxShared) []byte { n := nonceForUint64(nonce) return box.SealAfterPrecomputation(out, msg, (*[24]byte)(&n), (*[32]byte)(shared)) } // TODO we need to catch if nonce hits its max value and force a rekey // To that end, maybe we can use a smaller nonce size? or a vuint and reset on uint64 max? func nonceForUint64(u64 uint64) boxNonce { var nonce boxNonce slice := nonce[boxNonceSize-8:] binary.BigEndian.PutUint64(slice, u64) return nonce } ))entry(namecrypto_test.gonode(typeregularcontentspackage encrypted import ( "crypto/ed25519" "testing" ) func TestEdX25519(t *testing.T) { bsPub, bsPriv, err := ed25519.GenerateKey(nil) if err != nil { panic("key generation failed") } var ePub edPub var ePriv edPriv copy(ePub[:], bsPub) copy(ePriv[:], bsPriv) pub1, _ := ePub.toBox() priv1 := ePriv.toBox() pub2, priv2 := newBoxKeys() var encShared, decShared boxShared getShared(&encShared, pub1, &priv2) getShared(&decShared, &pub2, priv1) if encShared != decShared { panic("shared secret mismatch") } } ))entry(namedebug.gonode(typeregularcontents‰package encrypted import ( "crypto/ed25519" "time" "github.com/Arceliar/phony" ) type Debug struct { pc *PacketConn } func (d *Debug) init(pc *PacketConn) { d.pc = pc } type DebugSessionInfo struct { Key ed25519.PublicKey Uptime time.Duration RX uint64 TX uint64 } func (d *Debug) GetSessions() (infos []DebugSessionInfo) { phony.Block(&d.pc.sessions, func() { for key, session := range d.pc.sessions.sessions { var info DebugSessionInfo info.Key = append(info.Key, key[:]...) info.Uptime = time.Since(session.since) info.RX, info.TX = session.rx, session.tx infos = append(infos, info) } }) return } ))entry(nameinternalnode(type directoryentry(namee2cnode(type directoryentry(nameLICENSEnode(typeregularcontents¨Copyright 2019 Google LLC Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ))entry(namee2c.gonode(typeregularcontentsG// Copyright 2019 Google LLC // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd // Original code from: https://github.com/FiloSottile/age/blob/bbab440e198a4d67ba78591176c7853e62d29e04/internal/age/ssh.go package e2c import ( "crypto/ed25519" "crypto/sha512" "math/big" "golang.org/x/crypto/curve25519" ) var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) func Ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte { h := sha512.New() h.Write(pk.Seed()) out := h.Sum(nil) return out[:curve25519.ScalarSize] } func Ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte { // ed25519.PublicKey is a little endian representation of the y-coordinate, // with the most significant bit set based on the sign of the x-coordinate. bigEndianY := make([]byte, ed25519.PublicKeySize) for i, b := range pk { bigEndianY[ed25519.PublicKeySize-i-1] = b } bigEndianY[0] &= 0b0111_1111 // The Montgomery u-coordinate is derived through the bilinear map // // u = (1 + y) / (1 - y) // // See https://blog.filippo.io/using-ed25519-keys-for-encryption. y := new(big.Int).SetBytes(bigEndianY) denom := big.NewInt(1) denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y) u := y.Mul(y.Add(y, big.NewInt(1)), denom) u.Mod(u, curve25519P) out := make([]byte, curve25519.PointSize) uBytes := u.Bytes() for i, b := range uBytes { out[len(uBytes)-i-1] = b } return out } ))))))entry(name network.gonode(typeregularcontentsQpackage encrypted import ( "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/types" ) const netBufferSize = 128 * 1024 type netManager struct { phony.Inbox pc *PacketConn reader phony.Inbox readCh chan netReadInfo closed chan struct{} running bool } type netReadInfo struct { from edPub data []byte err error } func (m *netManager) init(pc *PacketConn) { m.pc = pc m.readCh = make(chan netReadInfo, 1) m.closed = make(chan struct{}) } func (m *netManager) recv(from *sessionInfo, data []byte) { m.reader.Act(from, func() { select { case m.readCh <- netReadInfo{from: from.ed, data: data}: case <-m.closed: } }) } func (m *netManager) read() { m.Act(nil, func() { if m.running { return } m.running = true buf := make([]byte, netBufferSize) var rl func() rl = func() { n, from, err := m.pc.PacketConn.ReadFrom(buf) if err != nil { // Exit the loop m.running = false if m.pc.IsClosed() { select { case <-m.closed: default: close(m.closed) } } // FIXME we need something better here // A read deadline could return an error // That would get passed to the next read call // This would happen even if the reader resets the deadline before calling ReadFrom select { case m.readCh <- netReadInfo{err: err}: default: } } else { msg := allocBytes(n) copy(msg, buf[:n]) var fromKey edPub copy(fromKey[:], from.(types.Addr)) m.pc.sessions.handleData(m, &fromKey, msg) m.Act(nil, rl) // continue to loop } } m.Act(nil, rl) // start the loop }) } ))entry(name packetconn.gonode(typeregularcontentspackage encrypted import ( "crypto/ed25519" "net" "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/network" "github.com/Arceliar/ironwood/types" ) type PacketConn struct { actor phony.Inbox *network.PacketConn secretEd edPriv secretBox boxPriv sessions sessionManager network netManager Debug Debug } // NewPacketConn returns a *PacketConn struct which implements the types.PacketConn interface. func NewPacketConn(secret ed25519.PrivateKey, options ...network.Option) (*PacketConn, error) { npc, err := network.NewPacketConn(secret, options...) if err != nil { return nil, err } pc := &PacketConn{PacketConn: npc} copy(pc.secretEd[:], secret[:]) pc.secretBox = *pc.secretEd.toBox() pc.sessions.init(pc) pc.network.init(pc) pc.Debug.init(pc) return pc, nil } func (pc *PacketConn) ReadFrom(p []byte) (n int, from net.Addr, err error) { pc.network.read() info := <-pc.network.readCh if info.err != nil { err = info.err return } n, from = len(info.data), types.Addr(info.from.asKey()) if n > len(p) { n = len(p) } copy(p, info.data[:n]) freeBytes(info.data) return } func (pc *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { select { case <-pc.network.closed: return 0, types.ErrClosed default: } destKey, ok := addr.(types.Addr) if !ok || len(destKey) != edPubSize { return 0, types.ErrBadAddress } if uint64(len(p)) > pc.MTU() { return 0, types.ErrOversizedMessage } n = len(p) var dest edPub copy(dest[:], destKey) pc.sessions.writeTo(dest, append(allocBytes(0), p...)) return } // MTU returns the maximum transmission unit of the PacketConn, i.e. maximum safe message size to send over the network. func (pc *PacketConn) MTU() uint64 { return pc.PacketConn.MTU() - sessionTrafficOverhead } ))entry(namepool.gonode(typeregularcontents>package encrypted import "sync" var bytePool = sync.Pool{New: func() interface{} { return []byte(nil) }} func allocBytes(size int) []byte { bs := bytePool.Get().([]byte) if cap(bs) < size { bs = make([]byte, size) } return bs[:size] } func freeBytes(bs []byte) { bytePool.Put(bs[:0]) //nolint:staticcheck } ))entry(name session.gonode(typeregularcontents¯Apackage encrypted import ( "encoding/binary" "time" "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/types" ) /* TODO: We either need to save the private keys for sent inits (so we can make a session when we receive an ack...) *or* we need to send an ack back when we receive an ack and we create a session because of it */ const ( sessionTimeout = time.Minute sessionTrafficOverheadMin = 1 + 1 + 1 + 1 + boxOverhead + boxPubSize // header, seq, seq, nonce sessionTrafficOverhead = sessionTrafficOverheadMin + 9 + 9 + 9 sessionInitSize = 1 + boxPubSize + boxOverhead + edSigSize + boxPubSize + boxPubSize + 8 + 8 sessionAckSize = sessionInitSize ) const ( sessionTypeDummy = iota sessionTypeInit sessionTypeAck sessionTypeTraffic ) /****************** * sessionManager * ******************/ type sessionManager struct { phony.Inbox pc *PacketConn sessions map[edPub]*sessionInfo buffers map[edPub]*sessionBuffer } func (mgr *sessionManager) init(pc *PacketConn) { mgr.pc = pc mgr.sessions = make(map[edPub]*sessionInfo) mgr.buffers = make(map[edPub]*sessionBuffer) } func (mgr *sessionManager) _newSession(ed *edPub, recv, send boxPub, seq uint64) *sessionInfo { info := newSession(ed, recv, send, seq) info.Act(mgr, func() { info.mgr = mgr info._resetTimer() }) mgr.sessions[info.ed] = info return info } func (mgr *sessionManager) _sessionForInit(pub *edPub, init *sessionInit) (*sessionInfo, *sessionBuffer) { var info *sessionInfo var buf *sessionBuffer if info = mgr.sessions[*pub]; info == nil { info = mgr._newSession(pub, init.current, init.next, init.seq) if buf = mgr.buffers[*pub]; buf != nil { buf.timer.Stop() delete(mgr.buffers, *pub) info.sendPub, info.sendPriv = buf.init.current, buf.currentPriv info.nextPub, info.nextPriv = buf.init.next, buf.nextPriv info._fixShared(0, 0) // The caller is responsible for sending buf.data when ready } } return info, buf } func (mgr *sessionManager) handleData(from phony.Actor, pub *edPub, data []byte) { mgr.Act(from, func() { if len(data) == 0 { return } switch data[0] { case sessionTypeDummy: case sessionTypeInit: init := new(sessionInit) if init.decrypt(&mgr.pc.secretBox, pub, data) { mgr._handleInit(pub, init) } freeBytes(data) case sessionTypeAck: ack := new(sessionAck) if ack.decrypt(&mgr.pc.secretBox, pub, data) { mgr._handleAck(pub, ack) } freeBytes(data) case sessionTypeTraffic: mgr._handleTraffic(pub, data) default: } }) } func (mgr *sessionManager) _handleInit(pub *edPub, init *sessionInit) { if info, buf := mgr._sessionForInit(pub, init); info != nil { info.handleInit(mgr, init) if buf != nil && buf.data != nil { info.doSend(mgr, buf.data) } } } func (mgr *sessionManager) _handleAck(pub *edPub, ack *sessionAck) { _, isOld := mgr.sessions[*pub] if info, buf := mgr._sessionForInit(pub, &ack.sessionInit); info != nil { if isOld { info.handleAck(mgr, ack) } else { info.handleInit(mgr, &ack.sessionInit) } if buf != nil && buf.data != nil { info.doSend(mgr, buf.data) } } } func (mgr *sessionManager) _handleTraffic(pub *edPub, msg []byte) { if info := mgr.sessions[*pub]; info != nil { info.doRecv(mgr, msg) } else { // We don't know that the node really exists, it could be spoofed/replay // So we don't want to save session or a buffer based on this node // So we send an init with keys we'll forget // If they ack, we'll set up a session and let it self-heal... currentPub, _ := newBoxKeys() nextPub, _ := newBoxKeys() init := newSessionInit(¤tPub, &nextPub, 0) mgr.sendInit(pub, &init) } } func (mgr *sessionManager) writeTo(toKey edPub, msg []byte) { // WARNING: unsafe to call from within an actor, must only be exposed over the PacketConn functions (which are, themselves, unsafe for actors to call in most cases, since they may block) phony.Block(mgr, func() { if info := mgr.sessions[toKey]; info != nil { info.doSend(mgr, msg) } else { // Need to buffer the traffic mgr._bufferAndInit(toKey, msg) } }) } func (mgr *sessionManager) _bufferAndInit(toKey edPub, msg []byte) { var buf *sessionBuffer if buf = mgr.buffers[toKey]; buf == nil { // Create a new buffer (including timer) buf = new(sessionBuffer) currentPub, currentPriv := newBoxKeys() nextPub, nextPriv := newBoxKeys() buf.init = newSessionInit(¤tPub, &nextPub, 0) buf.currentPriv = currentPriv buf.nextPriv = nextPriv buf.timer = time.AfterFunc(0, func() {}) mgr.buffers[toKey] = buf } buf.data = msg buf.timer.Stop() mgr.sendInit(&toKey, &buf.init) buf.timer = time.AfterFunc(sessionTimeout, func() { mgr.Act(nil, func() { if b := mgr.buffers[toKey]; b == buf { b.timer.Stop() delete(mgr.buffers, toKey) } }) }) } func (mgr *sessionManager) sendInit(dest *edPub, init *sessionInit) { if bs, err := init.encrypt(&mgr.pc.secretEd, dest); err == nil { mgr.pc.PacketConn.WriteTo(bs, types.Addr(dest.asKey())) } } func (mgr *sessionManager) sendAck(dest *edPub, ack *sessionAck) { if bs, err := ack.encrypt(&mgr.pc.secretEd, dest); err == nil { mgr.pc.PacketConn.WriteTo(bs, types.Addr(dest.asKey())) } } /*************** * sessionInfo * ***************/ type sessionInfo struct { phony.Inbox mgr *sessionManager seq uint64 // remote seq ed edPub // remote ed key remoteKeySeq uint64 // signals rotation of current/next current boxPub // send to this, expect to receive from it next boxPub // if we receive from this, then rotate it to current localKeySeq uint64 // signals rotation of recv/send/next recvPriv boxPriv recvPub boxPub recvShared boxShared recvNonce uint64 sendPriv boxPriv // becomes recvPriv when we ratchet forward sendPub boxPub // becomes recvPub sendShared boxShared sendNonce uint64 nextPriv boxPriv // becomes sendPriv nextPub boxPub // becomes sendPub timer *time.Timer ack *sessionAck since time.Time rotated time.Time // last time we rotated keys rx uint64 tx uint64 nextSendShared boxShared nextSendNonce uint64 nextRecvShared boxShared nextRecvNonce uint64 } func newSession(ed *edPub, current, next boxPub, seq uint64) *sessionInfo { info := new(sessionInfo) info.seq = seq - 1 // so the first update works info.ed = *ed info.current, info.next = current, next info.recvPub, info.recvPriv = newBoxKeys() info.sendPub, info.sendPriv = newBoxKeys() info.nextPub, info.nextPriv = newBoxKeys() info.since = time.Now() info._fixShared(0, 0) return info } // happens at session creation or after receiving an init/ack func (info *sessionInfo) _fixShared(recvNonce, sendNonce uint64) { getShared(&info.recvShared, &info.current, &info.recvPriv) getShared(&info.sendShared, &info.current, &info.sendPriv) getShared(&info.nextSendShared, &info.next, &info.sendPriv) getShared(&info.nextRecvShared, &info.next, &info.recvPriv) info.nextSendNonce, info.nextRecvNonce = 0, 0 info.recvNonce, info.sendNonce = recvNonce, sendNonce } func (info *sessionInfo) _resetTimer() { if info.timer != nil { info.timer.Stop() } info.timer = time.AfterFunc(sessionTimeout, func() { info.mgr.Act(nil, func() { if oldInfo := info.mgr.sessions[info.ed]; oldInfo == info { delete(info.mgr.sessions, info.ed) } }) }) } func (info *sessionInfo) handleInit(from phony.Actor, init *sessionInit) { info.Act(from, func() { if init.seq <= info.seq { return } info._handleUpdate(init) // Send a sessionAck info._sendAck() }) } func (info *sessionInfo) handleAck(from phony.Actor, ack *sessionAck) { info.Act(from, func() { if ack.seq <= info.seq { return } info._handleUpdate(&ack.sessionInit) }) } // return true if everything looks OK and the session was updated func (info *sessionInfo) _handleUpdate(init *sessionInit) { info.current = init.current info.next = init.next info.seq = init.seq info.remoteKeySeq = init.keySeq // Advance our keys, since this counts as a response info.recvPub, info.recvPriv = info.sendPub, info.sendPriv info.sendPub, info.sendPriv = info.nextPub, info.nextPriv info.nextPub, info.nextPriv = newBoxKeys() info.localKeySeq++ // Don't roll back sendNonce, just to be extra safe info._fixShared(0, info.sendNonce) info._resetTimer() } func (info *sessionInfo) doSend(from phony.Actor, msg []byte) { // TODO? some worker pool to multi-thread this info.Act(from, func() { defer freeBytes(msg) info.sendNonce += 1 // Advance the nonce before anything else if info.sendNonce == 0 { // Nonce overflowed, so rotate keys info.recvPub, info.recvPriv = info.sendPub, info.sendPriv info.sendPub, info.sendPriv = info.nextPub, info.nextPriv info.nextPub, info.nextPriv = newBoxKeys() info.localKeySeq++ info._fixShared(0, 0) } bs := allocBytes(sessionTrafficOverhead + len(msg)) defer freeBytes(bs) bs[0] = sessionTypeTraffic offset := 1 offset += binary.PutUvarint(bs[offset:], info.localKeySeq) offset += binary.PutUvarint(bs[offset:], info.remoteKeySeq) offset += binary.PutUvarint(bs[offset:], info.sendNonce) bs = bs[:offset] // We need to include info.nextPub below the layer of encryption // That way the remote side knows it's us when we send from it later... tmp := allocBytes(len(info.nextPub) + len(msg))[:0] tmp = append(tmp, info.nextPub[:]...) tmp = append(tmp, msg...) bs = boxSeal(bs, tmp, info.sendNonce, &info.sendShared) freeBytes(tmp) // send info.mgr.pc.PacketConn.WriteTo(bs, types.Addr(info.ed[:])) info.tx += uint64(len(msg)) info._resetTimer() }) } func (info *sessionInfo) doRecv(from phony.Actor, msg []byte) { // TODO? some worker pool to multi-thread this info.Act(from, func() { orig := msg defer freeBytes(orig) if len(msg) < sessionTrafficOverheadMin || msg[0] != sessionTypeTraffic { return } offset := 1 remoteKeySeq, rksLen := binary.Uvarint(msg[offset:]) if rksLen <= 0 { return } offset += rksLen localKeySeq, lksLen := binary.Uvarint(msg[offset:]) if lksLen <= 0 { return } offset += lksLen nonce, nonceLen := binary.Uvarint(msg[offset:]) if nonceLen <= 0 { return } offset += nonceLen msg := msg[offset:] fromCurrent := remoteKeySeq == info.remoteKeySeq fromNext := remoteKeySeq == info.remoteKeySeq+1 toRecv := localKeySeq+1 == info.localKeySeq toSend := localKeySeq == info.localKeySeq var sharedKey *boxShared var onSuccess func(boxPub) switch { case fromCurrent && toRecv: // The boring case, nothing to ratchet, just update nonce if !(info.recvNonce < nonce) { return } sharedKey = &info.recvShared onSuccess = func(_ boxPub) { info.recvNonce = nonce } case fromNext && toSend: // The remote side appears to have ratcheted forward if !(info.nextSendNonce < nonce) { return } sharedKey = &info.nextSendShared onSuccess = func(innerKey boxPub) { info.nextSendNonce = nonce if info.rotated.IsZero() || time.Since(info.rotated) > time.Minute { // Rotate their keys info.current = info.next info.next = innerKey info.remoteKeySeq++ // = remoteKeySeq // Rotate our own keys info.recvPub, info.recvPriv = info.sendPub, info.sendPriv info.sendPub, info.sendPriv = info.nextPub, info.nextPriv info.localKeySeq++ // Generate new next keys info.nextPub, info.nextPriv = newBoxKeys() // Update nonces info._fixShared(nonce, 0) info.rotated = time.Now() } } case fromNext && toRecv: // The remote side appears to have ratcheted forward early // Technically there's no reason we can't handle this //panic("DEBUG") // TODO test this if !(info.nextRecvNonce < nonce) { return } sharedKey = &info.nextRecvShared onSuccess = func(innerKey boxPub) { info.nextRecvNonce = nonce if info.rotated.IsZero() || time.Since(info.rotated) > time.Minute { // Rotate their keys info.current = info.next info.next = innerKey info.remoteKeySeq++ // = remoteKeySeq // Rotate our own keys info.recvPub, info.recvPriv = info.sendPub, info.sendPriv info.sendPub, info.sendPriv = info.nextPub, info.nextPriv info.localKeySeq++ // Generate new next keys info.nextPub, info.nextPriv = newBoxKeys() // Update nonces info._fixShared(nonce, 0) info.rotated = time.Now() } } default: // We can't make sense of their message // Send a sessionInit and hope they ack so we can fix things info._sendInit() return } // Decrypt and handle packet unboxed, ok := allocBytes(0), false defer func() { freeBytes(unboxed) }() if unboxed, ok = boxOpen(unboxed, msg, nonce, sharedKey); ok { var key boxPub copy(key[:], unboxed) msg := append(allocBytes(0), unboxed[len(key):]...) info.mgr.pc.network.recv(info, msg) // Misc remaining followup work onSuccess(key) info.rx += uint64(len(msg)) info._resetTimer() } else { // Keys somehow became out-of-sync // This seems to happen in some edge cases if a node restarts // Fix by sending a new init info._sendInit() } }) } func (info *sessionInfo) _sendInit() { init := newSessionInit(&info.sendPub, &info.nextPub, info.localKeySeq) info.mgr.sendInit(&info.ed, &init) } func (info *sessionInfo) _sendAck() { init := newSessionInit(&info.sendPub, &info.nextPub, info.localKeySeq) ack := sessionAck{init} info.mgr.sendAck(&info.ed, &ack) } /*************** * sessionInit * ***************/ type sessionInit struct { current boxPub next boxPub keySeq uint64 seq uint64 // timestamp or similar } func newSessionInit(current, next *boxPub, keySeq uint64) sessionInit { var init sessionInit init.current = *current init.next = *next init.keySeq = keySeq init.seq = uint64(time.Now().Unix()) return init } func (init *sessionInit) encrypt(from *edPriv, to *edPub) ([]byte, error) { fromPub, fromPriv := newBoxKeys() var toBox *boxPub var err error if toBox, err = to.toBox(); err != nil { return nil, err } // Get sig bytes var sigBytes []byte // TODO initialize to correct size sigBytes = append(sigBytes, fromPub[:]...) sigBytes = append(sigBytes, init.current[:]...) sigBytes = append(sigBytes, init.next[:]...) offset := len(sigBytes) sigBytes = sigBytes[:offset+8] binary.BigEndian.PutUint64(sigBytes[offset:offset+8], init.keySeq) offset = len(sigBytes) sigBytes = sigBytes[:offset+8] binary.BigEndian.PutUint64(sigBytes[offset:offset+8], init.seq) // Sign sig := edSign(sigBytes, from) // Prepare the payload (to be encrypted) var payload []byte // TODO initialize to correct size payload = append(payload, sig[:]...) payload = append(payload, sigBytes[boxPubSize:]...) // Encrypt var shared boxShared getShared(&shared, toBox, &fromPriv) bs := boxSeal(nil, payload, 0, &shared) // Assemble final message data := make([]byte, 1, sessionInitSize) data[0] = sessionTypeInit data = append(data, fromPub[:]...) data = append(data, bs...) if len(data) != sessionInitSize { panic("this should never happen") } return data, nil } func (init *sessionInit) decrypt(priv *boxPriv, from *edPub, data []byte) bool { if len(data) != sessionInitSize { return false } var fromBox boxPub offset := 1 offset = bytesPop(fromBox[:], data, offset) bs := data[offset:] var shared boxShared getShared(&shared, &fromBox, priv) var payload []byte var ok bool if payload, ok = boxOpen(nil, bs, 0, &shared); !ok { return false } offset = 0 var sig edSig offset = bytesPop(sig[:], payload, offset) tmp := payload[offset:] // Used in sigBytes offset = bytesPop(init.current[:], payload, offset) offset = bytesPop(init.next[:], payload, offset) init.keySeq = binary.BigEndian.Uint64(payload[offset : offset+8]) offset += 8 init.seq = binary.BigEndian.Uint64(payload[offset:]) // Check signature var sigBytes []byte sigBytes = append(sigBytes, fromBox[:]...) sigBytes = append(sigBytes, tmp...) return edCheck(sigBytes, &sig, from) } /************** * sessionAck * **************/ type sessionAck struct { sessionInit } func (ack *sessionAck) encrypt(from *edPriv, to *edPub) ([]byte, error) { data, err := ack.sessionInit.encrypt(from, to) if err == nil { data[0] = sessionTypeAck } return data, err } /***************** * sessionBuffer * *****************/ type sessionBuffer struct { data []byte init sessionInit currentPriv boxPriv // pairs with init.recv nextPriv boxPriv // pairs with init.send timer *time.Timer // time.AfterFunc to clean up } ))))entry(namego.modnode(typeregularcontentsømodule github.com/Arceliar/ironwood go 1.15 require ( github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bits-and-blooms/bloom/v3 v3.7.0 golang.org/x/crypto v0.23.0 ) ))entry(namego.sumnode(typeregularcontentssgithub.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56jeWUzvzdls= github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= 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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ))entry(namenetworknode(type directoryentry(namebloomfilter.gonode(typeregularcontents> package network import ( "encoding/binary" bfilter "github.com/bits-and-blooms/bloom/v3" "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/types" ) const ( bloomFilterF = 16 // number of bytes used for flags in the wire format, should be bloomFilterU / 8, rounded up bloomFilterU = bloomFilterF * 8 // number of uint64s in the backing array bloomFilterB = bloomFilterU * 8 // number of bytes in the backing array bloomFilterM = bloomFilterB * 8 // number of bits in teh backing array bloomFilterK = 8 // number of hashes to use per inserted key ) // bloom is bloomFilterM bits long bloom filter uses bloomFilterK hash functions. // Maybe this should be a *bfilter.BloomFilter directly, no struct? type bloom struct { filter *bfilter.BloomFilter } func newBloom() *bloom { return &bloom{ filter: bfilter.New(bloomFilterM, bloomFilterK), } } func (b *bloom) addKey(key publicKey) { b.filter.Add(key[:]) } func (b *bloom) addFilter(f *bfilter.BloomFilter) { b.filter.Merge(f) } func (b *bloom) size() int { size := bloomFilterF // Flags for chunks that are all 0 bits size += bloomFilterF // Flags for chunks that are all 1 bits us := b.filter.BitSet().Bytes() for _, u := range us { if u != 0 && u != ^uint64(0) { size += 8 } } return size } func (b *bloom) encode(out []byte) ([]byte, error) { start := len(out) var flags0, flags1 [bloomFilterF]byte keep := make([]uint64, 0, bloomFilterU) us := b.filter.BitSet().Bytes() for idx, u := range us { if u == 0 { flags0[idx/8] |= 0x80 >> (uint64(idx) % 8) continue } if u == ^uint64(0) { flags1[idx/8] |= 0x80 >> (uint64(idx) % 8) continue } keep = append(keep, u) } out = append(out, flags0[:]...) out = append(out, flags1[:]...) var buf [8]byte for _, u := range keep { binary.BigEndian.PutUint64(buf[:], u) out = append(out, buf[:]...) } end := len(out) if end-start != b.size() { panic("this should never happen") } return out, nil } func (b *bloom) decode(data []byte) error { var tmp bloom var usArray [bloomFilterU]uint64 us := usArray[:0] var flags0, flags1 [bloomFilterF]byte if !wireChopSlice(flags0[:], &data) { return types.ErrDecode } else if !wireChopSlice(flags1[:], &data) { return types.ErrDecode } for idx := 0; idx < bloomFilterU; idx++ { flag0 := flags0[idx/8] & (0x80 >> (uint64(idx) % 8)) flag1 := flags1[idx/8] & (0x80 >> (uint64(idx) % 8)) if flag0 != 0 && flag1 != 0 { return types.ErrDecode } else if flag0 != 0 { us = append(us, 0) } else if flag1 != 0 { us = append(us, ^uint64(0)) } else if len(data) >= 8 { u := binary.BigEndian.Uint64(data[:8]) us = append(us, u) data = data[8:] } else { return types.ErrDecode } } if len(data) != 0 { return types.ErrDecode } tmp.filter = bfilter.From(us, bloomFilterK) *b = tmp return nil } /***************************** * router bloom filter stuff * *****************************/ type blooms struct { router *router blooms map[publicKey]bloomInfo // TODO? add some kind of timeout and keepalive timer to force an update/send } type bloomInfo struct { send bloom recv bloom onTree bool zDirty bool } func (bs *blooms) init(r *router) { bs.router = r bs.blooms = make(map[publicKey]bloomInfo) } func (bs *blooms) _isOnTree(key publicKey) bool { return bs.blooms[key].onTree //|| key == bs.router.core.crypto.publicKey } func (bs *blooms) _fixOnTree() { selfKey := bs.router.core.crypto.publicKey if selfInfo, isIn := bs.router.infos[selfKey]; isIn { for pk, pbi := range bs.blooms { wasOn := pbi.onTree pbi.onTree = false if selfInfo.parent == pk { pbi.onTree = true } else if info, isIn := bs.router.infos[pk]; isIn { if info.parent == selfKey { pbi.onTree = true } } else { // They must not have sent us their info yet } if wasOn && !pbi.onTree { // We dropped them from the tree, so we need to send a blank update // That way, if the link returns to the tree, we don't start with false positives b := newBloom() pbi.send = *b for p := range bs.router.peers[pk] { p.sendBloom(bs.router, b) } } bs.blooms[pk] = pbi } } else { panic("this should never happen") } } func (bs *blooms) xKey(key publicKey) publicKey { k := key xfed := bs.router.core.config.bloomTransform(k.toEd()) var xform publicKey copy(xform[:], xfed) return xform } func (bs *blooms) _addInfo(key publicKey) { bs.blooms[key] = bloomInfo{ send: *newBloom(), recv: *newBloom(), } } func (bs *blooms) _removeInfo(key publicKey) { delete(bs.blooms, key) // We'll need to send updated blooms, but this can happen during regular maintenance } func (bs *blooms) handleBloom(fromPeer *peer, b *bloom) { bs.router.Act(fromPeer, func() { bs._handleBloom(fromPeer, b) }) } func (bs blooms) _handleBloom(fromPeer *peer, b *bloom) { pbi, isIn := bs.blooms[fromPeer.key] if !isIn { return } pbi.recv = *b bs.blooms[fromPeer.key] = pbi } func (bs *blooms) _doMaintenance() { bs._fixOnTree() bs._sendAllBlooms() } func (bs *blooms) _getBloomFor(key publicKey, keepOnes bool) (*bloom, bool) { // getBloomFor increments the sequence number, even if we only send it to 1 peer // this means we may sometimes unnecessarily send a bloom when we get a new peer link to an existing peer node pbi, isIn := bs.blooms[key] if !isIn { panic("this should never happen") } b := newBloom() xform := bs.xKey(bs.router.core.crypto.publicKey) b.addKey(xform) for k, pbi := range bs.blooms { if !pbi.onTree { continue } if k == key { continue } b.addFilter(bs.blooms[k].recv.filter) } if keepOnes { // Don't reset existing 1 bits, we'll set anything unnecessairy to 0 next time // Ensures that 1s travel faster than 0s, to help prevent flapping if !pbi.zDirty { c := b.filter.Copy() b.addFilter(pbi.send.filter) if !b.filter.Equal(c) { // We're keeping unnecessairy 1 bits, so set the dirty flag pbi.zDirty = true } } else { b.addFilter(pbi.send.filter) } } isNew := true if b.filter.Equal(pbi.send.filter) { *b = pbi.send isNew = false } else { pbi.send = *b bs.blooms[key] = pbi } return b, isNew } func (bs *blooms) _sendBloom(p *peer) { // Just send whatever our most recently sent bloom is // For new or off-tree nodes, this is the empty bloom filter b := bs.blooms[p.key].send p.sendBloom(bs.router, &b) } func (bs *blooms) _sendAllBlooms() { for k, pbi := range bs.blooms { if !pbi.onTree { continue } keepOnes := !pbi.zDirty if b, isNew := bs._getBloomFor(k, keepOnes); isNew { if ps, isIn := bs.router.peers[k]; isIn { for p := range ps { p.sendBloom(bs.router, b) } } else { panic("this should never happen") } } } } func (bs *blooms) sendMulticast(from phony.Actor, packet pqPacket, fromKey publicKey, toKey publicKey) { // Ideally we need a way to detect duplicate packets from multiple links to the same peer, so we can drop them // I.e. we need to sequence number all multicast packets... This can maybe be part of the framing, along side the packet length, or something // For now, we just send to 1 peer (possibly at random) bs.router.Act(from, func() { bs._sendMulticast(packet, fromKey, toKey) }) } func (bs *blooms) _sendMulticast(packet pqPacket, fromKey publicKey, toKey publicKey) { // TODO make very sure this can't loop, even temporarily due to network state changes being delayed // Does the onTree state stay safe, even when we're delaying maintenance from message updates?... xform := bs.xKey(toKey) for k, pbi := range bs.blooms { if !pbi.onTree { // This is not on the tree, so skip it continue } if k == fromKey { // From this key, so don't send it back continue } if !pbi.recv.filter.Test(xform[:]) { // The bloom filter tells us this peer definitely doesn't carea bout this xformed toKey continue } // Send this broadcast packet to the peer var bestPeer *peer for p := range bs.router.peers[k] { if bestPeer == nil || p.prio < bestPeer.prio { bestPeer = p } } if bestPeer == nil { panic("this should never happen") } bestPeer.sendQueued(bs.router, packet) } } ))entry(namebloomfilter_test.gonode(typeregularcontents/package network import "testing" func TestBloom(t *testing.T) { b := newBloom() c := newBloom() var buf []byte var err error // Zero value test if buf, err = b.encode(buf); err != nil { panic(err) } if err = c.decode(buf); err != nil { panic(err) } if !b.filter.Equal(c.filter) { panic("unequal bitsets") } // Intermedaite value test, add some keys buf = buf[:0] var k publicKey b.addKey(k) for idx := 0; idx < len(k); idx++ { k[idx] = ^k[idx] b.addKey(k) } if buf, err = b.encode(buf); err != nil { panic(err) } if err = c.decode(buf); err != nil { panic(err) } if !b.filter.Equal(c.filter) { panic("unequal bitsets") } // Max value test buf = buf[:0] bitset := b.filter.BitSet() us := bitset.Bytes() for idx := range us { us[idx] = ^uint64(0) } bitset.SetBitsetFrom(us) if !b.filter.BitSet().All() { panic("bitset should be saturated") } if buf, err = b.encode(buf); err != nil { panic(err) } if err = c.decode(buf); err != nil { panic(err) } if !b.filter.Equal(c.filter) { panic("unequal bitsets") } } ))entry(name config.gonode(typeregularcontents®package network import ( "crypto/ed25519" "time" ) type config struct { routerRefresh time.Duration routerTimeout time.Duration peerKeepAliveDelay time.Duration peerTimeout time.Duration peerMaxMessageSize uint64 bloomTransform func(ed25519.PublicKey) ed25519.PublicKey pathNotify func(ed25519.PublicKey) pathTimeout time.Duration pathThrottle time.Duration } type Option func(*config) func configDefaults() Option { return func(c *config) { c.routerRefresh = 4 * time.Minute c.routerTimeout = 5 * time.Minute c.peerKeepAliveDelay = time.Second c.peerTimeout = 3 * time.Second c.peerMaxMessageSize = 1048576 // 1 megabyte c.bloomTransform = func(key ed25519.PublicKey) ed25519.PublicKey { return key } c.pathNotify = func(key ed25519.PublicKey) {} c.pathTimeout = time.Minute c.pathThrottle = time.Second } } func WithRouterRefresh(duration time.Duration) Option { return func(c *config) { c.routerRefresh = duration } } func WithRouterTimeout(duration time.Duration) Option { return func(c *config) { c.routerTimeout = duration } } func WithPeerKeepAliveDelay(duration time.Duration) Option { return func(c *config) { c.peerKeepAliveDelay = duration } } func WithPeerTimeout(duration time.Duration) Option { return func(c *config) { c.peerTimeout = duration } } func WithPeerMaxMessageSize(size uint64) Option { return func(c *config) { c.peerMaxMessageSize = size } } func WithBloomTransform(xform func(key ed25519.PublicKey) ed25519.PublicKey) Option { return func(c *config) { c.bloomTransform = xform } } func WithPathNotify(notify func(key ed25519.PublicKey)) Option { return func(c *config) { c.pathNotify = notify } } func WithPathTimeout(duration time.Duration) Option { return func(c *config) { c.pathTimeout = duration } } func WithPathThrottle(duration time.Duration) Option { return func(c *config) { c.pathThrottle = duration } } ))entry(namecore.gonode(typeregularcontentspackage network import "crypto/ed25519" type core struct { config config // application-level configuration, must be the same on all nodes in a network crypto crypto // crypto info, e.g. pubkeys and sign/verify wrapper functions router router // logic to make next-hop decisions (plus maintain needed network state) peers peers // info about peers (from HandleConn), makes routing decisions and passes protocol traffic to relevant parts of the code pconn PacketConn // net.PacketConn-like interface } func (c *core) init(secret ed25519.PrivateKey, opts ...Option) error { opts = append([]Option{configDefaults()}, opts...) for _, opt := range opts { opt(&c.config) } c.crypto.init(secret) c.router.init(c) c.peers.init(c) c.pconn.init(c) return nil } ))entry(name core_test.gonode(typeregularcontents[!package network import ( "bytes" "crypto/ed25519" "errors" //"fmt" "net" "sync" "testing" "time" "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/types" ) func TestTwoNodes(t *testing.T) { pubA, privA, _ := ed25519.GenerateKey(nil) pubB, privB, _ := ed25519.GenerateKey(nil) a, _ := NewPacketConn(privA) b, _ := NewPacketConn(privB) cA, cB := newDummyConn(pubA, pubB) defer cA.Close() defer cB.Close() go a.HandleConn(pubB, cA, 0) go b.HandleConn(pubA, cB, 0) waitForRoot([]*PacketConn{a, b}, 30*time.Second) timer := time.NewTimer(6 * time.Second) defer func() { timer.Stop() }() addrA := a.LocalAddr() addrB := b.LocalAddr() done := make(chan struct{}) go func() { defer func() { defer func() { recover() }() close(done) }() msg := make([]byte, 2048) n, from, err := b.ReadFrom(msg) if err != nil { panic("err") } msg = msg[:n] aA := addrA.(types.Addr) fA := from.(types.Addr) if !bytes.Equal(aA, fA) { panic("wrong source address") } }() go func() { msg := []byte("test") for { select { case <-done: return default: } if _, err := a.WriteTo(msg, addrB); err != nil { panic(err) } time.Sleep(time.Second) } }() select { case <-timer.C: // This is where we would log something... t.Log("timeout") panic("timeout") case <-done: } } func TestLineNetwork(t *testing.T) { var conns []*PacketConn for idx := 0; idx < 8; idx++ { _, priv, _ := ed25519.GenerateKey(nil) conn, err := NewPacketConn(priv) if err != nil { panic(err) } conns = append(conns, conn) } wait := make(chan struct{}) for idx := range conns { if idx == 0 { continue } prev := conns[idx-1] here := conns[idx] keyA := ed25519.PublicKey(prev.LocalAddr().(types.Addr)) keyB := ed25519.PublicKey(here.LocalAddr().(types.Addr)) linkA, linkB := newDummyConn(keyA, keyB) defer linkA.Close() defer linkB.Close() go func() { <-wait prev.HandleConn(keyB, linkA, 0) }() go func() { <-wait here.HandleConn(keyA, linkB, 0) }() } close(wait) waitForRoot(conns, 30*time.Second) for aIdx := range conns { a := conns[aIdx] aAddr := a.LocalAddr() var aK publicKey copy(aK[:], aAddr.(types.Addr)) for bIdx := range conns { if bIdx == aIdx { continue } b := conns[bIdx] bAddr := b.LocalAddr() done := make(chan struct{}) msg := []byte("test") go func() { // Send from a to b for { select { case <-done: return default: } if n, err := a.WriteTo(msg, bAddr); n != len(msg) || err != nil { panic("write problem") } time.Sleep(time.Second) } }() go func() { defer func() { defer func() { recover() }() close(done) }() // Recv from a at b read := make([]byte, 2048) for { n, from, err := b.ReadFrom(read) bs := read[:n] if !bytes.Equal(bs, msg) || err != nil { if !bytes.Equal(bs, msg) { println(string(bs), string(msg)) //panic("unequal") } if err != nil { //panic(err) } //panic("read problem") } var fK publicKey copy(fK[:], from.(types.Addr)) if fK.equal(aK) { break } } }() timer := time.NewTimer(30 * time.Second) select { case <-timer.C: func() { defer func() { recover() }() close(done) }() // This is where we would log something... t.Log("timeout") panic("timeout") case <-done: timer.Stop() } } } } func TestRandomTreeNetwork(t *testing.T) { var conns []*PacketConn randIdx := func() int { return int(time.Now().UnixNano() % int64(len(conns))) } wait := make(chan struct{}) for idx := 0; idx < 8; idx++ { _, priv, _ := ed25519.GenerateKey(nil) conn, err := NewPacketConn(priv) if err != nil { panic(err) } if len(conns) > 0 { pIdx := randIdx() p := conns[pIdx] keyA := ed25519.PublicKey(conn.LocalAddr().(types.Addr)) keyB := ed25519.PublicKey(p.LocalAddr().(types.Addr)) linkA, linkB := newDummyConn(keyA, keyB) defer linkA.Close() defer linkB.Close() go func() { <-wait conn.HandleConn(keyB, linkA, 0) }() go func() { <-wait p.HandleConn(keyA, linkB, 0) }() } conns = append(conns, conn) } close(wait) waitForRoot(conns, 30*time.Second) for aIdx := range conns { a := conns[aIdx] aAddr := a.LocalAddr() var aK publicKey copy(aK[:], aAddr.(types.Addr)) for bIdx := range conns { if bIdx == aIdx { continue } b := conns[bIdx] bAddr := b.LocalAddr() done := make(chan struct{}) msg := []byte("test") go func() { // Send from a to b for { select { case <-done: return default: } if n, err := a.WriteTo(msg, bAddr); n != len(msg) || err != nil { panic("write problem") } time.Sleep(time.Second) } }() go func() { defer func() { defer func() { recover() }() close(done) }() // Recv from a at b read := make([]byte, 2048) for { n, from, err := b.ReadFrom(read) bs := read[:n] if !bytes.Equal(bs, msg) || err != nil { if !bytes.Equal(bs, msg) { println(string(bs), string(msg)) //panic("unequal") } if err != nil { //panic(err) } //panic("read problem") } var fK publicKey copy(fK[:], from.(types.Addr)) if fK.equal(aK) { break } } }() timer := time.NewTimer(30 * time.Second) select { case <-timer.C: func() { defer func() { recover() }() close(done) }() // This is where we would log something... t.Log("timeout") panic("timeout") case <-done: timer.Stop() } } } } // waitForRoot is a helper function that waits until all nodes are using the same root // that should usually mean the network has settled into a stable state, at least for static network tests func waitForRoot(conns []*PacketConn, timeout time.Duration) { begin := time.Now() for { time.Sleep(time.Second) if time.Since(begin) > timeout { panic("timeout") } var root publicKey for _, conn := range conns { phony.Block(&conn.core.router, func() { root, _ = conn.core.router._getRootAndDists(conn.core.crypto.publicKey) }) break } var bad bool for _, conn := range conns { var croot publicKey phony.Block(&conn.core.router, func() { croot, _ = conn.core.router._getRootAndDists(conn.core.crypto.publicKey) }) if !croot.equal(root) { bad = true break } } if !bad { break } } } /************* * dummyConn * *************/ type dummyConn struct { readLock sync.Mutex recv chan []byte recvBuf []byte writeLock sync.Mutex send chan []byte closeLock *sync.Mutex closed chan struct{} } func newDummyConn(keyA, keyB ed25519.PublicKey) (*dummyConn, *dummyConn) { toA := make(chan []byte) toB := make(chan []byte) cl := new(sync.Mutex) closed := make(chan struct{}) connA := dummyConn{recv: toA, send: toB, closeLock: cl, closed: closed} connB := dummyConn{recv: toB, send: toA, closeLock: cl, closed: closed} return &connA, &connB } func (d *dummyConn) Read(b []byte) (n int, err error) { d.readLock.Lock() defer d.readLock.Unlock() if len(d.recvBuf) == 0 { select { case <-d.closed: return 0, errors.New("closed") case bs := <-d.recv: d.recvBuf = append(d.recvBuf, bs...) } } n = len(b) if len(d.recvBuf) < n { n = len(d.recvBuf) } copy(b, d.recvBuf[:n]) d.recvBuf = d.recvBuf[n:] return n, nil } func (d *dummyConn) Write(b []byte) (n int, err error) { d.writeLock.Lock() defer d.writeLock.Unlock() bs := append([]byte(nil), b...) select { case <-d.closed: return 0, errors.New("closed") case d.send <- bs: return len(bs), nil } } func (d *dummyConn) Close() error { d.closeLock.Lock() defer d.closeLock.Unlock() select { case <-d.closed: return errors.New("closed") default: close(d.closed) } return nil } func (d *dummyConn) LocalAddr() net.Addr { panic("Not implemented: LocalAddr") return nil } func (d *dummyConn) RemoteAddr() net.Addr { panic("Not implemented: RemoteAddr") return nil } func (d *dummyConn) SetDeadline(t time.Time) error { //panic("Not implemented: SetDeadline") return nil } func (d *dummyConn) SetReadDeadline(t time.Time) error { //panic("Not implemented: SetReadDeadline") return nil } func (d *dummyConn) SetWriteDeadline(t time.Time) error { panic("Not implemented: SetWriteDeadline") return nil } ))entry(name crypto.gonode(typeregularcontentsfpackage network import ( "crypto/ed25519" "github.com/Arceliar/ironwood/types" ) const ( publicKeySize = ed25519.PublicKeySize privateKeySize = ed25519.PrivateKeySize signatureSize = ed25519.SignatureSize ) type publicKey [publicKeySize]byte type privateKey [privateKeySize]byte type signature [signatureSize]byte type crypto struct { privateKey privateKey publicKey publicKey } func (key *privateKey) sign(message []byte) signature { var sig signature tmp := ed25519.Sign(ed25519.PrivateKey(key[:]), message) copy(sig[:], tmp) return sig } func (key privateKey) equal(comparedKey privateKey) bool { return key == comparedKey } func (key *publicKey) verify(message []byte, sig *signature) bool { return ed25519.Verify(ed25519.PublicKey(key[:]), message, sig[:]) } func (key publicKey) equal(comparedKey publicKey) bool { return key == comparedKey } func (key publicKey) less(comparedKey publicKey) bool { for idx := range key { switch { case key[idx] < comparedKey[idx]: return true case key[idx] > comparedKey[idx]: return false } } return false } func (key publicKey) addr() types.Addr { return types.Addr(key[:]) } func (c *crypto) init(secret ed25519.PrivateKey) { copy(c.privateKey[:], secret) copy(c.publicKey[:], secret.Public().(ed25519.PublicKey)) } func (key publicKey) toEd() ed25519.PublicKey { k := key return k[:] } ))entry(namecrypto_test.gonode(typeregularcontents±package network import ( "crypto/ed25519" "testing" ) func TestSign(t *testing.T) { var c crypto _, priv, _ := ed25519.GenerateKey(nil) c.init(priv) msg := []byte("this is a test") _ = c.privateKey.sign(msg) } func TestVerify(t *testing.T) { var c crypto _, priv, _ := ed25519.GenerateKey(nil) c.init(priv) msg := []byte("this is a test") sig := c.privateKey.sign(msg) if !c.publicKey.verify(msg, &sig) { panic("verification failed") } } func BenchmarkSign(b *testing.B) { var c crypto _, priv, _ := ed25519.GenerateKey(nil) c.init(priv) msg := []byte("this is a test") for idx := 0; idx < b.N; idx++ { _ = c.privateKey.sign(msg) } } func BenchmarkVerify(b *testing.B) { var c crypto _, priv, _ := ed25519.GenerateKey(nil) c.init(priv) msg := []byte("this is a test") sig := c.privateKey.sign(msg) for idx := 0; idx < b.N; idx++ { if !c.publicKey.verify(msg, &sig) { panic("verification failed") } } } ))entry(namedebug.gonode(typeregularcontents¥ package network import ( "crypto/ed25519" "net" "time" "github.com/Arceliar/phony" ) type Debug struct { c *core } func (d *Debug) init(c *core) { d.c = c } type DebugSelfInfo struct { Key ed25519.PublicKey RoutingEntries uint64 } type DebugPeerInfo struct { Key ed25519.PublicKey Root ed25519.PublicKey Port uint64 Priority uint8 RX uint64 TX uint64 Updated time.Time Conn net.Conn Latency time.Duration } type DebugTreeInfo struct { Key ed25519.PublicKey Parent ed25519.PublicKey Sequence uint64 } type DebugPathInfo struct { Key ed25519.PublicKey Path []uint64 Sequence uint64 } type DebugBloomInfo struct { Key ed25519.PublicKey Send [bloomFilterU]uint64 Recv [bloomFilterU]uint64 } type DebugLookupInfo struct { Key ed25519.PublicKey Path []uint64 Target ed25519.PublicKey } func (d *Debug) GetSelf() (info DebugSelfInfo) { info.Key = append(info.Key[:0], d.c.crypto.publicKey[:]...) phony.Block(&d.c.router, func() { info.RoutingEntries = uint64(len(d.c.router.infos)) }) return } func (d *Debug) GetPeers() (infos []DebugPeerInfo) { phony.Block(&d.c.peers, func() { for _, peers := range d.c.peers.peers { for peer := range peers { var info DebugPeerInfo info.Port = uint64(peer.port) info.Key = append(info.Key[:0], peer.key[:]...) info.Priority = peer.prio info.Conn = peer.conn if rtt := peer.srrt.Sub(peer.srst).Round(time.Millisecond / 100); rtt > 0 { info.Latency = rtt } infos = append(infos, info) } } }) return } func (d *Debug) GetTree() (infos []DebugTreeInfo) { phony.Block(&d.c.router, func() { for key, dinfo := range d.c.router.infos { var info DebugTreeInfo info.Key = append(info.Key[:0], key[:]...) info.Parent = append(info.Parent[:0], dinfo.parent[:]...) info.Sequence = dinfo.seq infos = append(infos, info) } }) return } func (d *Debug) GetPaths() (infos []DebugPathInfo) { phony.Block(&d.c.router, func() { for key, pinfo := range d.c.router.pathfinder.paths { var info DebugPathInfo info.Key = append(info.Key[:0], key[:]...) info.Path = make([]uint64, 0, len(pinfo.path)) for _, port := range pinfo.path { info.Path = append(info.Path, uint64(port)) } info.Sequence = pinfo.seq infos = append(infos, info) } }) return } func (d *Debug) GetBlooms() (infos []DebugBloomInfo) { phony.Block(&d.c.router, func() { for key, binfo := range d.c.router.blooms.blooms { var info DebugBloomInfo info.Key = append(info.Key[:0], key[:]...) copy(info.Send[:], binfo.send.filter.BitSet().Bytes()) copy(info.Recv[:], binfo.recv.filter.BitSet().Bytes()) infos = append(infos, info) } }) return } func (d *Debug) SetDebugLookupLogger(logger func(DebugLookupInfo)) { phony.Block(&d.c.router, func() { d.c.router.pathfinder.logger = func(lookup *pathLookup) { info := DebugLookupInfo{ Key: append(ed25519.PublicKey(nil), lookup.source[:]...), Path: make([]uint64, 0, len(lookup.from)), Target: append(ed25519.PublicKey(nil), lookup.dest[:]...), } for _, p := range lookup.from { info.Path = append(info.Path, uint64(p)) } logger(info) } }) } ))entry(name packetconn.gonode(typeregularcontents¬package network import ( "crypto/ed25519" "fmt" "net" "sync" "time" "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/types" ) func _type_asserts_() { var _ types.PacketConn = new(PacketConn) } type PacketConn struct { actor phony.Inbox core *core recv chan *traffic //read buffer recvReady uint64 recvq packetQueue readDeadline *deadline closeMutex sync.Mutex closed chan struct{} Debug Debug } // NewPacketConn returns a *PacketConn struct which implements the types.PacketConn interface. func NewPacketConn(secret ed25519.PrivateKey, options ...Option) (*PacketConn, error) { c := new(core) if err := c.init(secret, options...); err != nil { return nil, err } return &c.pconn, nil } func (pc *PacketConn) init(c *core) { pc.core = c pc.recv = make(chan *traffic, 1) pc.readDeadline = newDeadline() pc.closed = make(chan struct{}) pc.Debug.init(c) } // ReadFrom fulfills the net.PacketConn interface, with a types.Addr returned as the from address. // Note that failing to call ReadFrom may cause the connection to block and/or leak memory. func (pc *PacketConn) ReadFrom(p []byte) (n int, from net.Addr, err error) { var tr *traffic pc.doPop() select { case <-pc.closed: return 0, nil, types.ErrClosed case <-pc.readDeadline.getCancel(): return 0, nil, types.ErrTimeout case tr = <-pc.recv: } copy(p, tr.payload) n = len(tr.payload) if len(p) < len(tr.payload) { n = len(p) } fromKey := tr.source // copy, since tr is going back in the pool from = fromKey.addr() freeTraffic(tr) return } // WriteTo fulfills the net.PacketConn interface, with a types.Addr expected as the destination address. func (pc *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { select { case <-pc.closed: return 0, types.ErrClosed default: } if _, ok := addr.(types.Addr); !ok { return 0, types.ErrBadAddress } dest := addr.(types.Addr) if len(dest) != publicKeySize { return 0, types.ErrBadAddress } if uint64(len(p)) > pc.MTU() { return 0, types.ErrOversizedMessage } tr := allocTraffic() tr.source = pc.core.crypto.publicKey copy(tr.dest[:], dest) tr.watermark = ^uint64(0) tr.payload = append(tr.payload, p...) pc.core.router.sendTraffic(tr) return len(p), nil } // Close shuts down the PacketConn. func (pc *PacketConn) Close() error { pc.closeMutex.Lock() defer pc.closeMutex.Unlock() select { case <-pc.closed: return types.ErrClosed default: } close(pc.closed) phony.Block(&pc.core.peers, func() { for _, ps := range pc.core.peers.peers { for p := range ps { p.conn.Close() } } }) phony.Block(&pc.core.router, pc.core.router._shutdown) return nil } // LocalAddr returns a types.Addr of the ed25519.PublicKey for this PacketConn. func (pc *PacketConn) LocalAddr() net.Addr { return pc.core.crypto.publicKey.addr() } // SetDeadline fulfills the net.PacketConn interface. Note that only read deadlines are affected. func (pc *PacketConn) SetDeadline(t time.Time) error { if err := pc.SetReadDeadline(t); err != nil { return err } else if err := pc.SetWriteDeadline(t); err != nil { return err } return nil } // SetReadDeadline fulfills the net.PacketConn interface. func (pc *PacketConn) SetReadDeadline(t time.Time) error { pc.readDeadline.set(t) return nil } // SetWriteDeadline fulfills the net.PacketConn interface. func (pc *PacketConn) SetWriteDeadline(t time.Time) error { return nil } // HandleConn expects a peer's public key as its first argument, and a net.Conn with TCP-like semantics (reliable ordered delivery) as its second argument. // This function blocks while the net.Conn is in use, and returns an error if any occurs. // This function returns (almost) immediately if PacketConn.Close() is called. // In all cases, the net.Conn is closed before returning. func (pc *PacketConn) HandleConn(key ed25519.PublicKey, conn net.Conn, prio uint8) error { defer conn.Close() if len(key) != publicKeySize { return types.ErrBadKey } var pk publicKey copy(pk[:], key) if pc.core.crypto.publicKey.equal(pk) { return fmt.Errorf("%w: Expected %s, Found %s", types.ErrBadKey, pc.core.crypto.publicKey.addr().String(), pk.addr().String(), ) } p, err := pc.core.peers.addPeer(pk, conn, prio) if err != nil { return err } err = p.handler() if e := pc.core.peers.removePeer(p); e != nil { return e } return err } // IsClosed returns true if and only if the connection is closed. // This is to check if the PacketConn is closed without potentially being stuck on a blocking operation (e.g. a read or write). func (pc *PacketConn) IsClosed() bool { select { case <-pc.closed: return true default: } return false } // PrivateKey() returns the ed25519.PrivateKey used to initialize the PacketConn. func (pc *PacketConn) PrivateKey() ed25519.PrivateKey { sk := pc.core.crypto.privateKey return ed25519.PrivateKey(sk[:]) } // MTU returns the maximum transmission unit of the PacketConn, i.e. maximum safe message size to send over the network. func (pc *PacketConn) MTU() uint64 { var tr traffic tr.watermark = ^uint64(0) overhead := uint64(tr.size()) + 1 // 1 byte type overhead // TODO extra padding for source/destination paths... but that would imply a max path length... return pc.core.config.peerMaxMessageSize - overhead } func (pc *PacketConn) handleTraffic(from phony.Actor, tr *traffic) { // Note: if there are multiple concurrent ReadFrom calls, packets can be returned out-of-order at the channel level // But concurrent reads can always do things out of order, so that probaby doesn't matter... pc.actor.Act(from, func() { if !tr.dest.equal(pc.core.crypto.publicKey) { // Wrong key, do nothing } else if pc.recvReady > 0 { // Send immediately select { case pc.recv <- tr: pc.recvReady -= 1 case <-pc.closed: } } else { if info, ok := pc.recvq.peek(); ok && time.Since(info.time) > 25*time.Millisecond { // The queue already has a significant delay // Drop the oldest packet from the larget queue to make room pc.recvq.drop() } pc.recvq.push(tr) } }) } func (pc *PacketConn) doPop() { pc.actor.Act(nil, func() { if info, ok := pc.recvq.pop(); ok { select { case pc.recv <- info.packet.(*traffic): case <-pc.closed: default: panic("this should never happen") } } else { pc.recvReady += 1 } }) } type deadline struct { m sync.Mutex timer *time.Timer once *sync.Once cancel chan struct{} } func newDeadline() *deadline { return &deadline{ once: new(sync.Once), cancel: make(chan struct{}), } } func (d *deadline) set(t time.Time) { d.m.Lock() defer d.m.Unlock() d.once.Do(func() { if d.timer != nil { d.timer.Stop() } }) select { case <-d.cancel: d.cancel = make(chan struct{}) default: } d.once = new(sync.Once) var zero time.Time if t != zero { once := d.once cancel := d.cancel d.timer = time.AfterFunc(time.Until(t), func() { once.Do(func() { close(cancel) }) }) } } func (d *deadline) getCancel() chan struct{} { d.m.Lock() defer d.m.Unlock() ch := d.cancel return ch } func (pc *PacketConn) SendLookup(key ed25519.PublicKey) { var k publicKey copy(k[:], key) pc.core.router.Act(nil, func() { pc.core.router.pathfinder._rumorSendLookup(k) }) } ))entry(namepacketqueue.gonode(typeregularcontentsÄpackage network import ( "container/heap" "time" ) type pqPacket interface { wireEncodeable wireType() wirePacketType sourceKey() publicKey destKey() publicKey } type pqPacketInfo struct { packet pqPacket size uint64 time time.Time } type pqSource struct { key publicKey infos []pqPacketInfo size uint64 } type pqDest struct { key publicKey sources []pqSource size uint64 } type packetQueue struct { dests []pqDest size uint64 } // drop will remove a packet from the queue // the packet removed will be the oldest packet from the longest stream to the largest destination queue // returns true if a packet was removed, false otherwise func (q *packetQueue) drop() bool { if q.size == 0 { return false } var dIdx int for idx := range q.dests { if q.dests[idx].size > q.dests[dIdx].size { dIdx = idx } } dest := q.dests[dIdx] var sIdx int for idx := range dest.sources { if dest.sources[idx].size > dest.sources[sIdx].size { sIdx = idx } } source := dest.sources[sIdx] info := source.infos[0] source.size -= info.size if len(source.infos) > 0 { source.infos = source.infos[1:] } dest.sources[sIdx] = source if source.size > 0 { heap.Fix(&dest, sIdx) } else { heap.Remove(&dest, sIdx) } dest.size -= info.size q.dests[dIdx] = dest if dest.size > 0 { heap.Fix(q, dIdx) } else { heap.Remove(q, dIdx) } q.size -= info.size switch p := info.packet.(type) { case *traffic: freeTraffic(p) default: // Nothing to do } return true } // push adds a packet with the provided size to a queue for the provided source and destination keys // a new queue will be created if needed func (q *packetQueue) push(packet pqPacket) { sKey := packet.sourceKey() dKey := packet.destKey() size := packet.size() info := pqPacketInfo{packet: packet, size: uint64(size), time: time.Now()} sIdx, dIdx := -1, -1 source, dest := pqSource{key: sKey}, pqDest{key: dKey} for idx, d := range q.dests { if d.key.equal(dKey) { dIdx, dest = idx, d break } } for idx, s := range dest.sources { if s.key.equal(sKey) { sIdx, source = idx, s break } } source.infos = append(source.infos, info) source.size += info.size if sIdx < 0 { dest.sources = append(dest.sources, source) } else { dest.sources[sIdx] = source } dest.size += info.size if dIdx < 0 { q.dests = append(q.dests, dest) } else { q.dests[dIdx] = dest } q.size += info.size } // pop removes and returns the oldest packet (from across all source/destination pairs) func (q *packetQueue) pop() (info pqPacketInfo, ok bool) { if q.size > 0 { dest := q.dests[0] source := dest.sources[0] info = source.infos[0] source.size -= info.size dest.size -= info.size q.size -= info.size if len(source.infos) > 1 { source.infos = source.infos[1:] dest.sources[0] = source heap.Fix(&dest, 0) } else { dest.sources[0] = source heap.Remove(&dest, 0) } if len(dest.sources) > 0 { q.dests[0] = dest heap.Fix(q, 0) } else { q.dests[0] = dest heap.Remove(q, 0) } return info, true } return } func (q *packetQueue) peek() (info pqPacketInfo, ok bool) { if len(q.dests) > 0 { return q.dests[0].sources[0].infos[0], true } return } //////////////////////////////////////////////////////////////////////////////// // Interface methods for packetQueue to satisfy heap.Interface func (q *packetQueue) Len() int { return len(q.dests) } func (q *packetQueue) Less(i, j int) bool { return q.dests[i].sources[0].infos[0].time.Before(q.dests[j].sources[0].infos[0].time) } func (q *packetQueue) Swap(i, j int) { q.dests[i], q.dests[j] = q.dests[j], q.dests[i] } func (q *packetQueue) Push(x interface{}) { dest := x.(pqDest) q.dests = append(q.dests, dest) q.size += dest.size } func (q *packetQueue) Pop() interface{} { idx := len(q.dests) - 1 dest := q.dests[idx] q.dests = q.dests[:idx] q.size -= dest.size return dest } // Interface methods for pqDest to satisfy heap.Interface func (d *pqDest) Len() int { return len(d.sources) } func (d *pqDest) Less(i, j int) bool { return d.sources[i].infos[0].time.Before(d.sources[j].infos[0].time) } func (d *pqDest) Swap(i, j int) { d.sources[i], d.sources[j] = d.sources[j], d.sources[i] } func (d *pqDest) Push(x interface{}) { source := x.(pqSource) d.sources = append(d.sources, source) d.size += source.size } func (d *pqDest) Pop() interface{} { idx := len(d.sources) - 1 source := d.sources[idx] d.sources = d.sources[:idx] d.size -= source.size return source } ))entry(name pathfinder.gonode(typeregularcontents‘6package network import ( "time" "github.com/Arceliar/ironwood/types" ) const pathfinderTrafficCache = true // WARNING The pathfinder should only be used from within the router's actor, it's not threadsafe type pathfinder struct { router *router info pathNotifyInfo paths map[publicKey]pathInfo rumors map[publicKey]pathRumor logger func(*pathLookup) } func (pf *pathfinder) init(r *router) { pf.router = r pf.info.sign(pf.router.core.crypto.privateKey) pf.paths = make(map[publicKey]pathInfo) pf.rumors = make(map[publicKey]pathRumor) } func (pf *pathfinder) _sendLookup(dest publicKey) { if info, isIn := pf.paths[dest]; isIn { if time.Since(info.reqTime) < pf.router.core.config.pathThrottle { // Don't flood with request, wait a bit return } } selfKey := pf.router.core.crypto.publicKey _, from := pf.router._getRootAndPath(selfKey) lookup := pathLookup{ source: selfKey, dest: dest, from: from, } pf._handleLookup(lookup.source, &lookup) } func (pf *pathfinder) handleLookup(p *peer, lookup *pathLookup) { pf.router.Act(p, func() { if !pf.router.blooms._isOnTree(p.key) { return } pf._handleLookup(p.key, lookup) }) } func (pf *pathfinder) _handleLookup(fromKey publicKey, lookup *pathLookup) { if pf.logger != nil { pf.logger(lookup) } // Continue the multicast pf.router.blooms._sendMulticast(lookup, fromKey, lookup.dest) // Check if we should send a response too dx := pf.router.blooms.xKey(lookup.dest) sx := pf.router.blooms.xKey(pf.router.core.crypto.publicKey) if dx == sx { // We match, send a response // TODO? throttle this per dest that we're sending a response to? _, path := pf.router._getRootAndPath(pf.router.core.crypto.publicKey) notify := pathNotify{ path: lookup.from, watermark: ^uint64(0), source: pf.router.core.crypto.publicKey, dest: lookup.source, info: pathNotifyInfo{ seq: uint64(time.Now().Unix()), //pf.info.seq, path: path, }, } if !pf.info.equal(notify.info) { //notify.info.seq++ notify.info.sign(pf.router.core.crypto.privateKey) pf.info = notify.info } else { notify.info = pf.info } pf._handleNotify(notify.source, ¬ify) } } func (pf *pathfinder) handleNotify(p *peer, notify *pathNotify) { pf.router.Act(p, func() { pf._handleNotify(p.key, notify) }) } func (pf *pathfinder) _handleNotify(fromKey publicKey, notify *pathNotify) { if p := pf.router._lookup(notify.path, ¬ify.watermark); p != nil { p.sendPathNotify(pf.router, notify) return } // Check if we should accept this response if notify.dest != pf.router.core.crypto.publicKey { return } var info pathInfo var isIn bool // Note that we need to res.check() in every case (as soon as success is otherwise inevitable) if info, isIn = pf.paths[notify.source]; isIn { if notify.info.seq <= info.seq { // This isn't newer than the last seq we received, so drop it return } nfo := notify.info nfo.path = info.path if nfo.equal(notify.info) { // This doesn't actually add anything new, so skip it return } if !notify.check() { return } info.timer.Reset(pf.router.core.config.pathTimeout) } else { xform := pf.router.blooms.xKey(notify.source) if _, isIn := pf.rumors[xform]; !isIn { return } if !notify.check() { return } key := notify.source var timer *time.Timer timer = time.AfterFunc(pf.router.core.config.pathTimeout, func() { pf.router.Act(nil, func() { if info := pf.paths[key]; info.timer == timer { timer.Stop() delete(pf.paths, key) if info.traffic != nil { freeTraffic(info.traffic) } } }) }) info = pathInfo{ reqTime: time.Now(), timer: timer, } if rumor := pf.rumors[xform]; rumor.traffic != nil && rumor.traffic.dest == notify.source { info.traffic = rumor.traffic rumor.traffic = nil pf.rumors[xform] = rumor } } info.path = notify.info.path info.seq = notify.info.seq info.broken = false if info.traffic != nil { tr := info.traffic info.traffic = nil // We defer so it happens after we've store the updated info in the map defer pf._handleTraffic(tr) } pf.paths[notify.source] = info pf.router.core.config.pathNotify(notify.source.toEd()) } func (pf *pathfinder) _rumorSendLookup(dest publicKey) { xform := pf.router.blooms.xKey(dest) if rumor, isIn := pf.rumors[xform]; isIn { if time.Since(rumor.sendTime) < pf.router.core.config.pathThrottle { return } rumor.sendTime = time.Now() rumor.timer.Reset(pf.router.core.config.pathTimeout) pf.rumors[xform] = rumor } else { var timer *time.Timer timer = time.AfterFunc(pf.router.core.config.pathTimeout, func() { pf.router.Act(nil, func() { if rumor := pf.rumors[xform]; rumor.timer == timer { delete(pf.rumors, xform) timer.Stop() if rumor.traffic != nil { freeTraffic(rumor.traffic) } } }) }) pf.rumors[xform] = pathRumor{ sendTime: time.Now(), timer: timer, } } pf._sendLookup(dest) } func (pf *pathfinder) _handleTraffic(tr *traffic) { const cache = pathfinderTrafficCache // TODO make this unconditional, this is just to easily toggle the cache on/off for now if info, isIn := pf.paths[tr.dest]; isIn { tr.path = append(tr.path[:0], info.path...) _, from := pf.router._getRootAndPath(pf.router.core.crypto.publicKey) tr.from = append(tr.from[:0], from...) if cache { if info.traffic != nil { freeTraffic(info.traffic) } info.traffic = allocTraffic() info.traffic.copyFrom(tr) pf.paths[tr.dest] = info } pf.router.handleTraffic(nil, tr) } else { pf._rumorSendLookup(tr.dest) if cache { xform := pf.router.blooms.xKey(tr.dest) if rumor, isIn := pf.rumors[xform]; isIn { if rumor.traffic != nil { freeTraffic(rumor.traffic) } rumor.traffic = tr pf.rumors[xform] = rumor } else { panic("this should never happen") } } } } func (pf *pathfinder) _doBroken(tr *traffic) { broken := pathBroken{ path: append([]peerPort(nil), tr.from...), watermark: ^uint64(0), source: tr.source, dest: tr.dest, } pf._handleBroken(&broken) } func (pf *pathfinder) _handleBroken(broken *pathBroken) { // Hack using traffic to do routing if p := pf.router._lookup(broken.path, &broken.watermark); p != nil { p.sendPathBroken(pf.router, broken) return } // Check if we should accept this pathBroken if broken.source != pf.router.core.crypto.publicKey { return } if info, isIn := pf.paths[broken.dest]; isIn { info.broken = true pf.paths[broken.dest] = info pf._sendLookup(broken.dest) // Throttled inside this function } } func (pf *pathfinder) handleBroken(p *peer, broken *pathBroken) { pf.router.Act(p, func() { pf._handleBroken(broken) }) } func (pf *pathfinder) _resetTimeout(key publicKey) { // Note: We should call this when we receive a packet from this destination // We should *not* reset just because we tried to send a packet // We need things to time out eventually if e.g. a node restarts and resets its seqs if info, isIn := pf.paths[key]; isIn && !info.broken { info.timer.Reset(pf.router.core.config.pathTimeout) } } /************ * pathInfo * ************/ type pathInfo struct { path []peerPort // *not* zero terminated (and must be free of zeros) seq uint64 reqTime time.Time // Time a request was last sent (to prevent spamming) timer *time.Timer // time.AfterFunc(cleanup...), reset whenever we receive traffic from this node traffic *traffic broken bool // Set to true if we receive a pathBroken, which prevents the timer from being reset (we must get a new notify to clear) } /************* * pathRumor * *************/ type pathRumor struct { traffic *traffic sendTime time.Time // Time we last sent a rumor (to prevnt spamming) timer *time.Timer // time.AfterFunc(cleanup...) } /************** * pathLookup * **************/ type pathLookup struct { source publicKey dest publicKey from []peerPort } func (lookup *pathLookup) size() int { size := len(lookup.source) size += len(lookup.dest) size += wireSizePath(lookup.from) return size } func (lookup *pathLookup) encode(out []byte) ([]byte, error) { start := len(out) out = append(out, lookup.source[:]...) out = append(out, lookup.dest[:]...) out = wireAppendPath(out, lookup.from) end := len(out) if end-start != lookup.size() { panic("this should never happen") } return out, nil } func (lookup *pathLookup) decode(data []byte) error { var tmp pathLookup orig := data if !wireChopSlice(tmp.source[:], &orig) { return types.ErrDecode } else if !wireChopSlice(tmp.dest[:], &orig) { return types.ErrDecode } else if !wireChopPath(&tmp.from, &orig) { return types.ErrDecode } else if len(orig) != 0 { return types.ErrDecode } *lookup = tmp return nil } // Needed for pqPacket interface func (lookup *pathLookup) wireType() wirePacketType { return wireProtoPathLookup } func (lookup *pathLookup) sourceKey() publicKey { return lookup.source } func (lookup *pathLookup) destKey() publicKey { return lookup.dest } /****************** * pathNotifyInfo * ******************/ type pathNotifyInfo struct { seq uint64 path []peerPort // Path from root to source, aka coords, zero-terminated sig signature // signature from the source key } // equal returns true if the pathResponseInfos are equal, inspecting the contents of the path and ignoring the sig func (info *pathNotifyInfo) equal(cmp pathNotifyInfo) bool { if info.seq != cmp.seq { return false } else if len(info.path) != len(cmp.path) { return false } for idx := range info.path { if info.path[idx] != cmp.path[idx] { return false } } return true } func (info *pathNotifyInfo) bytesForSig() []byte { var out []byte out = wireAppendUint(out, info.seq) out = wireAppendPath(out, info.path) return out } func (info *pathNotifyInfo) sign(key privateKey) { info.sig = key.sign(info.bytesForSig()) } func (info *pathNotifyInfo) size() int { size := wireSizeUint(info.seq) size += wireSizePath(info.path) size += len(info.sig) return size } func (info *pathNotifyInfo) encode(out []byte) ([]byte, error) { start := len(out) out = wireAppendUint(out, info.seq) out = wireAppendPath(out, info.path) out = append(out, info.sig[:]...) end := len(out) if end-start != info.size() { panic("this should never happen") } return out, nil } func (info *pathNotifyInfo) decode(data []byte) error { var tmp pathNotifyInfo orig := data if !wireChopUint(&tmp.seq, &orig) { return types.ErrDecode } else if !wireChopPath(&tmp.path, &orig) { return types.ErrDecode } else if !wireChopSlice(tmp.sig[:], &orig) { return types.ErrDecode } else if len(orig) != 0 { return types.ErrDecode } *info = tmp return nil } /************** * pathNotify * **************/ type pathNotify struct { path []peerPort watermark uint64 source publicKey // who sent the response, not who resquested it dest publicKey // exact key we are sending response to info pathNotifyInfo } func (notify *pathNotify) check() bool { return notify.source.verify(notify.info.bytesForSig(), ¬ify.info.sig) } func (notify *pathNotify) size() int { size := wireSizePath(notify.path) size += wireSizeUint(notify.watermark) size += len(notify.source) size += len(notify.dest) size += notify.info.size() return size } func (notify *pathNotify) encode(out []byte) ([]byte, error) { start := len(out) out = wireAppendPath(out, notify.path) out = wireAppendUint(out, notify.watermark) out = append(out, notify.source[:]...) out = append(out, notify.dest[:]...) var err error if out, err = notify.info.encode(out); err != nil { return nil, err } end := len(out) if end-start != notify.size() { panic("this should never happen") } return out, nil } func (notify *pathNotify) decode(data []byte) error { var tmp pathNotify orig := data if !wireChopPath(&tmp.path, &orig) { return types.ErrDecode } else if !wireChopUint(&tmp.watermark, &orig) { return types.ErrDecode } else if !wireChopSlice(tmp.source[:], &orig) { return types.ErrDecode } else if !wireChopSlice(tmp.dest[:], &orig) { return types.ErrDecode } else if err := tmp.info.decode(orig); err != nil { return err } *notify = tmp return nil } func (notify *pathNotify) wireType() wirePacketType { return wireProtoPathNotify } func (notify *pathNotify) sourceKey() publicKey { return notify.source } func (notify *pathNotify) destKey() publicKey { return notify.dest } /************** * pathBroken * **************/ type pathBroken struct { path []peerPort watermark uint64 source publicKey dest publicKey } func (broken *pathBroken) size() int { size := wireSizePath(broken.path) size += wireSizeUint(broken.watermark) size += len(broken.source) size += len(broken.dest) return size } func (broken *pathBroken) encode(out []byte) ([]byte, error) { start := len(out) out = wireAppendPath(out, broken.path) out = wireAppendUint(out, broken.watermark) out = append(out, broken.source[:]...) out = append(out, broken.dest[:]...) end := len(out) if end-start != broken.size() { panic("this should never happen") } return out, nil } func (broken *pathBroken) decode(data []byte) error { var tmp pathBroken orig := data if !wireChopPath(&tmp.path, &orig) { return types.ErrDecode } else if !wireChopUint(&tmp.watermark, &orig) { return types.ErrDecode } else if !wireChopSlice(tmp.source[:], &orig) { return types.ErrDecode } else if !wireChopSlice(tmp.dest[:], &orig) { return types.ErrDecode } else if len(orig) != 0 { return types.ErrDecode } *broken = tmp return nil } func (broken *pathBroken) wireType() wirePacketType { return wireProtoPathBroken } func (broken *pathBroken) sourceKey() publicKey { return broken.source } func (broken *pathBroken) destKey() publicKey { return broken.dest } ))entry(namepeers.gonode(typeregularcontents3*package network import ( "bufio" "encoding/binary" "io" //"math" "net" "time" "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/types" ) // TODO? copy relevant config info to structs here, to avoid needing to dereference pointers all the way back to the core type peerPort uint64 type peers struct { phony.Inbox // Used to create/remove peers core *core ports map[peerPort]struct{} peers map[publicKey]map[*peer]struct{} order uint64 // global counter for (*peer).order } func (ps *peers) init(c *core) { ps.core = c ps.ports = make(map[peerPort]struct{}) ps.peers = make(map[publicKey]map[*peer]struct{}) } func (ps *peers) addPeer(key publicKey, conn net.Conn, prio uint8) (*peer, error) { var p *peer var err error ps.core.pconn.closeMutex.Lock() defer ps.core.pconn.closeMutex.Unlock() select { case <-ps.core.pconn.closed: return nil, types.ErrClosed default: } phony.Block(ps, func() { var port peerPort if keyPeers, isIn := ps.peers[key]; isIn { for p := range keyPeers { port = p.port break } } else { // Allocate port for idx := 1; ; idx++ { // skip 0 if _, isIn := ps.ports[peerPort(idx)]; isIn { continue } port = peerPort(idx) break } ps.ports[port] = struct{}{} ps.peers[key] = make(map[*peer]struct{}) } p = new(peer) p.peers = ps p.conn = conn p.done = make(chan struct{}) p.key = key p.port = port p.prio = prio p.monitor.peer = p p.monitor.pDelay = ps.core.config.peerTimeout // It doesn't make sense to start the ping delay any shorter than this p.writer.peer = p p.writer.wbuf = bufio.NewWriter(p.conn) p.order = ps.order ps.order++ ps.peers[p.key][p] = struct{}{} }) return p, err } func (ps *peers) removePeer(p *peer) error { var err error phony.Block(ps, func() { kps := ps.peers[p.key] if _, isIn := kps[p]; !isIn { err = types.ErrPeerNotFound } else { delete(kps, p) if len(kps) == 0 { delete(ps.peers, p.key) delete(ps.ports, p.port) } } }) return err } type peer struct { phony.Inbox // Only used to process or send some protocol traffic peers *peers conn net.Conn done chan struct{} key publicKey port peerPort prio uint8 queue packetQueue order uint64 // order in which peers were connected (relative uptime) monitor peerMonitor writer peerWriter ready bool // is the writer ready for traffic? srst time.Time // sigReq send time srrt time.Time // sigRes receive time } type peerMonitor struct { phony.Inbox peer *peer keepAliveTimer *time.Timer pDelay time.Duration deadlined bool } func (m *peerMonitor) keepAlive() { m.Act(nil, func() { select { case <-m.peer.done: return default: } m.peer.writer.Act(m, func() { m.peer.writer._write([]byte{0x01, byte(wireKeepAlive)}, wireKeepAlive) }) }) } func (m *peerMonitor) sent(pType wirePacketType) { m.Act(&m.peer.writer, func() { if m.keepAliveTimer != nil { // We're sending a packet, so we definitely don't need to send a keepalive after this m.keepAliveTimer.Stop() m.keepAliveTimer = nil } switch { case m.deadlined: return case pType == wireDummy: case pType == wireKeepAlive: default: // We're sending non-keepalive traffic // This means we expect some kind of acknowledgement (at least a keepalive) // Set a read deadline for that (and make a note that we did so) m.peer.conn.SetReadDeadline(time.Now().Add(m.peer.peers.core.config.peerTimeout)) m.deadlined = true } }) } func (m *peerMonitor) recv(pType wirePacketType) { m.Act(nil, func() { m.peer.conn.SetReadDeadline(time.Time{}) m.deadlined = false switch { case m.keepAliveTimer != nil: case pType == wireDummy: case pType == wireKeepAlive: default: // We just received non-keepalive traffic // The other side is expecting some kind of response, at least a keepalive // We set a timer to trigger a response later, if we don't send any traffic in the mean time select { case <-m.peer.done: default: m.keepAliveTimer = time.AfterFunc(m.peer.peers.core.config.peerKeepAliveDelay, m.keepAlive) } } }) } type peerWriter struct { phony.Inbox peer *peer wbuf *bufio.Writer seq uint64 } func (w *peerWriter) _write(bs []byte, pType wirePacketType) { w.peer.monitor.sent(pType) // _, _ = w.peer.conn.Write(bs) _, _ = w.wbuf.Write(bs) w.seq++ seq := w.seq w.Act(nil, func() { if seq == w.seq { w.peer.pop() // Ask for more traffic to send } }) } func (w *peerWriter) sendPacket(pType wirePacketType, data wireEncodeable, done func()) { w.Act(nil, func() { bufSize := uint64(data.size() + 1) if bufSize > w.peer.peers.core.config.peerMaxMessageSize { return } writeBuf := allocBytes(0) defer freeBytes(writeBuf) // The +1 is from 1 byte for the pType writeBuf = binary.AppendUvarint(writeBuf[:], bufSize) var err error writeBuf, err = wireEncode(writeBuf, byte(pType), data) if err != nil { panic(err) } w._write(writeBuf, pType) switch tr := data.(type) { case *traffic: freeTraffic(tr) default: // Not a special case, don't free anything } if done != nil { w.peer.Act(w, done) } }) } func (p *peer) handler() error { defer func() { p.peers.core.router.removePeer(nil, p) }() defer p.monitor.Act(nil, func() { if p.monitor.keepAliveTimer != nil { p.monitor.keepAliveTimer.Stop() p.monitor.keepAliveTimer = nil } }) defer close(p.done) p.conn.SetDeadline(time.Time{}) // Add peer to the router, to kick off protocol exchanges p.peers.core.router.addPeer(p, p) // Now allocate buffers and start reading / handling packets... rbuf := bufio.NewReader(p.conn) for { var usize uint64 var err error if usize, err = binary.ReadUvarint(rbuf); err != nil { return err } if usize > p.peers.core.config.peerMaxMessageSize { return types.ErrOversizedMessage } size := int(usize) bs := allocBytes(size) if _, err = io.ReadFull(rbuf, bs); err != nil { freeBytes(bs) return err } phony.Block(p, func() { err = p._handlePacket(bs) }) freeBytes(bs) if err != nil { return err } } } func (p *peer) _handlePacket(bs []byte) error { // Note: this function should be non-blocking. // Individual handlers should send actor messages as needed. if len(bs) == 0 { return types.ErrEmptyMessage } pType := wirePacketType(bs[0]) p.monitor.recv(pType) switch pType { case wireDummy: return nil case wireKeepAlive: return nil case wireProtoSigReq: return p._handleSigReq(bs[1:]) case wireProtoSigRes: return p._handleSigRes(bs[1:]) case wireProtoAnnounce: return p._handleAnnounce(bs[1:]) case wireProtoBloomFilter: return p._handleBloom(bs[1:]) case wireProtoPathLookup: return p._handlePathLookup(bs[1:]) case wireProtoPathNotify: return p._handlePathNotify(bs[1:]) case wireProtoPathBroken: return p._handlePathBroken(bs[1:]) case wireTraffic: return p._handleTraffic(bs[1:]) default: return types.ErrUnrecognizedMessage } } func (p *peer) sendDirect(from phony.Actor, pType wirePacketType, data wireEncodeable, done func()) { p.Act(from, func() { p.writer.sendPacket(pType, data, done) }) } func (p *peer) _handleSigReq(bs []byte) error { req := new(routerSigReq) if err := req.decode(bs); err != nil { return err } p.peers.core.router.handleRequest(p, p, req) return nil } func (p *peer) sendSigReq(from phony.Actor, req *routerSigReq) { p.sendDirect(from, wireProtoSigReq, req, func() { p.srst = time.Now() }) } func (p *peer) _handleSigRes(bs []byte) error { res := new(routerSigRes) if err := res.decode(bs); err != nil { return err } if !res.check(p.peers.core.crypto.publicKey, p.key) { return types.ErrBadMessage } p.srrt = time.Now() p.peers.core.router.handleResponse(p, p, res) return nil } func (p *peer) sendSigRes(from phony.Actor, res *routerSigRes) { p.sendDirect(from, wireProtoSigRes, res, nil) } func (p *peer) _handleAnnounce(bs []byte) error { ann := new(routerAnnounce) if err := ann.decode(bs); err != nil { return err } if !ann.check() { return types.ErrBadMessage } p.peers.core.router.handleAnnounce(p, p, ann) return nil } func (p *peer) sendAnnounce(from phony.Actor, ann *routerAnnounce) { p.sendDirect(from, wireProtoAnnounce, ann, nil) } func (p *peer) _handleBloom(bs []byte) error { b := newBloom() if err := b.decode(bs); err != nil { return err } p.peers.core.router.blooms.handleBloom(p, b) return nil } func (p *peer) sendBloom(from phony.Actor, b *bloom) { p.sendDirect(from, wireProtoBloomFilter, b, nil) } func (p *peer) _handlePathLookup(bs []byte) error { lookup := new(pathLookup) if err := lookup.decode(bs); err != nil { return err } p.peers.core.router.pathfinder.handleLookup(p, lookup) return nil } func (p *peer) _handlePathNotify(bs []byte) error { notify := new(pathNotify) if err := notify.decode(bs); err != nil { return err } p.peers.core.router.pathfinder.handleNotify(p, notify) return nil } func (p *peer) sendPathNotify(from phony.Actor, notify *pathNotify) { //p.sendDirect(from, wireProtoPathNotify, notify) p.sendQueued(from, notify) } func (p *peer) _handlePathBroken(bs []byte) error { broken := new(pathBroken) if err := broken.decode(bs); err != nil { return err } p.peers.core.router.pathfinder.handleBroken(p, broken) return nil } func (p *peer) sendPathBroken(from phony.Actor, broken *pathBroken) { //p.sendDirect(from, wireProtoPathBroken, broken) p.sendQueued(from, broken) } func (p *peer) _handleTraffic(bs []byte) error { tr := allocTraffic() if err := tr.decode(bs); err != nil { return err // This is just to check that it unmarshals correctly } p.peers.core.router.handleTraffic(p, tr) return nil } func (p *peer) sendTraffic(from phony.Actor, tr *traffic) { p.sendQueued(from, tr) } func (p *peer) sendQueued(from phony.Actor, packet pqPacket) { p.Act(from, func() { p._push(packet) }) } func (p *peer) _push(packet pqPacket) { if p.ready { p.writer.sendPacket(packet.wireType(), packet, nil) p.ready = false return } // We're waiting, so queue the packet up for later if info, ok := p.queue.peek(); ok && time.Since(info.time) > 25*time.Millisecond { // The queue already has a significant delay // Drop the oldest packet from the larget queue to make room p.queue.drop() } // Add the packet to the queue p.queue.push(packet) } func (p *peer) pop() { p.Act(nil, func() { if info, ok := p.queue.pop(); ok { p.writer.sendPacket(info.packet.wireType(), info.packet, nil) } else { p.ready = true p.writer.Act(nil, func() { p.writer.wbuf.Flush() }) } }) } ))entry(namepool.gonode(typeregularcontents¡package network import "sync" var bytePool = sync.Pool{New: func() interface{} { return []byte(nil) }} func allocBytes(size int) []byte { bs := bytePool.Get().([]byte) if cap(bs) < size { bs = make([]byte, size) } return bs[:size] } func freeBytes(bs []byte) { bytePool.Put(bs[:0]) //nolint:staticcheck } var trafficPool = sync.Pool{New: func() interface{} { return new(traffic) }} func allocTraffic() *traffic { tr := trafficPool.Get().(*traffic) tr.payload = allocBytes(0) return tr } func freeTraffic(tr *traffic) { freeBytes(tr.payload) path := tr.path[:0] from := tr.from[:0] *tr = traffic{} tr.path = path tr.from = from trafficPool.Put(tr) } ))entry(name router.gonode(typeregularcontentsJ_package network import ( crand "crypto/rand" "encoding/binary" "time" //"fmt" "github.com/Arceliar/phony" "github.com/Arceliar/ironwood/types" ) /*********** * router * ***********/ /* Potential showstopping issue (long term): Greedy routing using coords is fundamentally insecure. Nothing prevents a node from advertising the same port number to two different children. Everything downstream of the attacker is at risk of random blackholes etc. This costs the attacker essentially nothing. Workaround: use full keys. That obviously won't work for normal traffic -- it's too much info. It *may* work for protocol traffic, so we can use it for pathfinding. We could then e.g. build a source route along the way, and use the source route... if we can do that securely... Added benefit, we do expect source routing to be more stable in the face of tree flapping... Obvious issues with ygg v0.4 style source routing... alternatives? Detect if we've visited the same node before so we can drop traffic? How? Bloom filter would work, except for the issue of false positives... If we store a reverse route, we could send back an error, so the sender can resize the bloom filter... Seems messy... Bloom filter to track visited nodes, and if in the filter then add to a list? If in the list already, drop traffic entirely? */ type router struct { phony.Inbox core *core pathfinder pathfinder // see pathfinder.go blooms blooms // see bloomfilter.go peers map[publicKey]map[*peer]struct{} // True if we're allowed to send a mirror to this peer (but have not done so already) sent map[publicKey]map[publicKey]struct{} // tracks which info we've sent to our peer ports map[peerPort]publicKey // used in tree lookups infos map[publicKey]routerInfo timers map[publicKey]*time.Timer ancs map[publicKey][]publicKey // Peer ancestry info cache map[publicKey][]peerPort // Cache path slice for each peer requests map[publicKey]routerSigReq responses map[publicKey]routerSigRes resSeqs map[publicKey]uint64 resSeqCtr uint64 refresh bool doRoot1 bool doRoot2 bool mainTimer *time.Timer } func (r *router) init(c *core) { r.core = c r.pathfinder.init(r) r.blooms.init(r) r.peers = make(map[publicKey]map[*peer]struct{}) r.sent = make(map[publicKey]map[publicKey]struct{}) r.ports = make(map[peerPort]publicKey) r.infos = make(map[publicKey]routerInfo) r.timers = make(map[publicKey]*time.Timer) r.ancs = make(map[publicKey][]publicKey) r.cache = make(map[publicKey][]peerPort) r.requests = make(map[publicKey]routerSigReq) r.responses = make(map[publicKey]routerSigRes) r.resSeqs = make(map[publicKey]uint64) // Kick off actor to do initial work / become root r.mainTimer = time.AfterFunc(time.Second, func() { r.Act(nil, r._doMaintenance) }) r.doRoot2 = true r.Act(nil, r._doMaintenance) } func (r *router) _doMaintenance() { if r.mainTimer == nil { return } r.doRoot2 = r.doRoot2 || r.doRoot1 r._resetCache() // Resets path caches, since that info may no longer be good, TODO? don't wait for maintenance to do this r._updateAncestries() r._fix() // Selects new parent, if needed r._sendAnnounces() // Sends announcements to peers, if needed r.blooms._doMaintenance() r.mainTimer.Reset(time.Second) } func (r *router) _shutdown() { if r.mainTimer != nil { r.mainTimer.Stop() r.mainTimer = nil } // TODO clean up pathfinder etc... // There's a lot more to do here } func (r *router) _resetCache() { for k := range r.cache { delete(r.cache, k) } } func (r *router) addPeer(from phony.Actor, p *peer) { r.Act(from, func() { //r._resetCache() if _, isIn := r.peers[p.key]; !isIn { r.peers[p.key] = make(map[*peer]struct{}) r.sent[p.key] = make(map[publicKey]struct{}) r.ports[p.port] = p.key r.blooms._addInfo(p.key) } else { // Send anything we've already sent over previous peer connections to this node for k := range r.sent[p.key] { if info, isIn := r.infos[k]; isIn { p.sendAnnounce(r, info.getAnnounce(k)) } else { panic("this should never happen") } } } r.peers[p.key][p] = struct{}{} if _, isIn := r.responses[p.key]; !isIn { if _, isIn := r.requests[p.key]; !isIn { r.requests[p.key] = *r._newReq() } req := r.requests[p.key] p.sendSigReq(r, &req) } r.blooms._sendBloom(p) }) } func (r *router) removePeer(from phony.Actor, p *peer) { r.Act(from, func() { //r._resetCache() ps := r.peers[p.key] delete(ps, p) if len(ps) == 0 { delete(r.peers, p.key) delete(r.sent, p.key) delete(r.ports, p.port) delete(r.requests, p.key) delete(r.responses, p.key) delete(r.resSeqs, p.key) delete(r.ancs, p.key) delete(r.cache, p.key) r.blooms._removeInfo(p.key) //r._fix() } else { // The bloom the remote node is tracking could be wrong due to a race // TODO? don't send it immediately, reset the "sent" state to blank so we'll resend next maintenance period for p := range ps { r.blooms._sendBloom(p) } } }) } func (r *router) _clearReqs() { for k := range r.requests { delete(r.requests, k) } for k := range r.responses { delete(r.responses, k) } for k := range r.resSeqs { delete(r.resSeqs, k) } r.resSeqCtr = 0 } func (r *router) _sendReqs() { r._clearReqs() for pk, ps := range r.peers { req := r._newReq() r.requests[pk] = *req for p := range ps { p.sendSigReq(r, req) } } } func (r *router) _updateAncestries() { for pkey := range r.peers { anc := r._getAncestry(pkey) old := r.ancs[pkey] var diff bool if len(anc) != len(old) { diff = true } else { for idx := range anc { if anc[idx] != old[idx] { diff = true break } } } if diff { r.ancs[pkey] = anc } } } func (r *router) _fix() { bestRoot := r.core.crypto.publicKey bestParent := r.core.crypto.publicKey self := r.infos[r.core.crypto.publicKey] // Check if our current parent leads to a better root than ourself if _, isIn := r.peers[self.parent]; isIn { root, _ := r._getRootAndDists(r.core.crypto.publicKey) if root.less(bestRoot) { bestRoot, bestParent = root, self.parent } } // Check if we know a better root/parent for pk := range r.responses { if _, isIn := r.infos[pk]; !isIn { // We don't know where this peer is continue } pRoot, pDists := r._getRootAndDists(pk) if _, isIn := pDists[r.core.crypto.publicKey]; isIn { // This would loop through us already continue } if pRoot.less(bestRoot) { bestRoot, bestParent = pRoot, pk } else if pRoot != bestRoot { continue // wrong root } if (r.refresh || bestParent != self.parent) && r.resSeqs[pk] < r.resSeqs[bestParent] { // It's time to refresh our self info // If we're going to change to a better parent, now seems like the time... bestRoot, bestParent = pRoot, pk } } if r.refresh || r.doRoot1 || r.doRoot2 || self.parent != bestParent { res, isIn := r.responses[bestParent] switch { case isIn && bestRoot != r.core.crypto.publicKey && r._useResponse(bestParent, &res): // Somebody else should be root // Note that it's possible our current parent hasn't sent a res for our current req // (Link failure in progress, or from bad luck with timing) r.refresh = false r.doRoot1 = false r.doRoot2 = false r._sendReqs() case r.doRoot2: // Become root if !r._becomeRoot() { panic("this should never happen") } /* self = r.infos[r.core.crypto.publicKey] ann := self.getAnnounce(r.core.crypto.publicKey) for _, ps := range r.peers { for p := range ps { p.sendAnnounce(r, ann) } } */ r.refresh = false r.doRoot1 = false r.doRoot2 = false r._sendReqs() case !r.doRoot1: r.doRoot1 = true // No need to sendReqs in this case // either we already have a req, or we've already requested one // so resetting and re-requesting is just a waste of bandwidth default: // We need to self-root, but we already started a timer to do that later // So this is a no-op } } } func (r *router) _sendAnnounces() { // This is insanely delicate, lots of correctness is implicit across how nodes behave // Change nothing here. selfAnc := r._getAncestry(r.core.crypto.publicKey) var toSend []publicKey var anns []*routerAnnounce for peerKey, sent := range r.sent { // Initial setup stuff toSend = toSend[:0] anns = anns[:0] peerAnc := r._getAncestry(peerKey) // Get whatever we haven't sent from selfAnc for _, k := range selfAnc { if _, isIn := sent[k]; !isIn { toSend = append(toSend, k) sent[k] = struct{}{} } } // Get whatever we haven't sent from peerAnc for _, k := range peerAnc { if _, isIn := sent[k]; !isIn { toSend = append(toSend, k) sent[k] = struct{}{} } } /* // Reset sent so it only contains the ancestry info for k := range sent { delete(sent, k) } for _, k := range selfAnc { sent[k] = struct{}{} } for _, k := range peerAnc { sent[k] = struct{}{} } */ // Now prepare announcements for _, k := range toSend { if info, isIn := r.infos[k]; isIn { anns = append(anns, info.getAnnounce(k)) } else { panic("this should never happen") } } // Send announcements for p := range r.peers[peerKey] { for _, ann := range anns { p.sendAnnounce(r, ann) } } } } func (r *router) _newReq() *routerSigReq { var req routerSigReq nonce := make([]byte, 8) crand.Read(nonce) // If there's an error, there's not much to do... req.nonce = binary.BigEndian.Uint64(nonce) req.seq = r.infos[r.core.crypto.publicKey].seq + 1 return &req } func (r *router) _becomeRoot() bool { req := r._newReq() res := routerSigRes{ routerSigReq: *req, port: 0, // TODO? something else? } res.psig = r.core.crypto.privateKey.sign(res.bytesForSig(r.core.crypto.publicKey, r.core.crypto.publicKey)) ann := routerAnnounce{ key: r.core.crypto.publicKey, parent: r.core.crypto.publicKey, routerSigRes: res, sig: res.psig, } if !ann.check() { panic("this should never happen") } return r._update(&ann) } func (r *router) _handleRequest(p *peer, req *routerSigReq) { res := routerSigRes{ routerSigReq: *req, port: p.port, } res.psig = r.core.crypto.privateKey.sign(res.bytesForSig(p.key, r.core.crypto.publicKey)) p.sendSigRes(r, &res) } func (r *router) handleRequest(from phony.Actor, p *peer, req *routerSigReq) { r.Act(from, func() { r._handleRequest(p, req) }) } func (r *router) _handleResponse(p *peer, res *routerSigRes) { if _, isIn := r.responses[p.key]; !isIn && r.requests[p.key] == res.routerSigReq { r.resSeqCtr++ r.resSeqs[p.key] = r.resSeqCtr r.responses[p.key] = *res //r._fix() // This could become our new parent } } func (r *router) _useResponse(peerKey publicKey, res *routerSigRes) bool { bs := res.bytesForSig(r.core.crypto.publicKey, peerKey) info := routerInfo{ parent: peerKey, routerSigRes: *res, sig: r.core.crypto.privateKey.sign(bs), } ann := info.getAnnounce(r.core.crypto.publicKey) if r._update(ann) { /* for _, ps := range r.peers { for p := range ps { p.sendAnnounce(r, ann) } } */ return true } return false } func (r *router) handleResponse(from phony.Actor, p *peer, res *routerSigRes) { r.Act(from, func() { r._handleResponse(p, res) }) } func (r *router) _update(ann *routerAnnounce) bool { if info, isIn := r.infos[ann.key]; isIn { switch { // Note: This logic *must* be the same on every node // If that's not true, then peers can infinitely spam announcements at each other for expired infos /********************************* * XXX *** DO NOT CHANGE *** XXX * *********************************/ case info.seq > ann.seq: // This is an old seq, so exit return false case info.seq < ann.seq: // This is a newer seq, so don't exit case info.parent.less(ann.parent): // same seq, worse (higher) parent return false case ann.parent.less(info.parent): // same seq, better (lower) parent, so don't exit case ann.nonce < info.nonce: // same seq and parent, lower nonce, so don't exit default: // same seq and parent, same or worse nonce, so exit return false } } // Clean up sent info and cache for _, sent := range r.sent { delete(sent, ann.key) } r._resetCache() // Save info info := routerInfo{ parent: ann.parent, routerSigRes: ann.routerSigRes, sig: ann.sig, } key := ann.key var timer *time.Timer if key == r.core.crypto.publicKey { delay := r.core.config.routerRefresh // TODO? slightly randomize timer = time.AfterFunc(delay, func() { r.Act(nil, func() { if r.timers[key] == timer { r.refresh = true //r._fix() } }) }) } else { timer = time.AfterFunc(r.core.config.routerTimeout, func() { r.Act(nil, func() { if r.timers[key] == timer { timer.Stop() // Shouldn't matter, but just to be safe... delete(r.infos, key) delete(r.timers, key) for _, sent := range r.sent { delete(sent, key) } r._resetCache() //r._fix() } }) }) } if oldTimer, isIn := r.timers[key]; isIn { oldTimer.Stop() } r.timers[ann.key] = timer r.infos[ann.key] = info return true } func (r *router) _handleAnnounce(p *peer, ann *routerAnnounce) { if r._update(ann) { if ann.key == r.core.crypto.publicKey { // We just updated our own info from a message we received by a peer // That suggests we went offline, so our seq reset when we came back // The info they sent us could have been expired (see below in this function) // So we need to set that an update is required, as if our refresh timer has passed r.refresh = true } // No point in sending this back to the original sender r.sent[p.key][ann.key] = struct{}{} //r._fix() // This could require us to change parents } else { // We didn't accept the info, because we alerady know it or something better info := routerInfo{ parent: ann.parent, routerSigRes: ann.routerSigRes, sig: ann.sig, } if oldInfo := r.infos[ann.key]; info != oldInfo { // They sent something, but it was worse // Should we tell them what we know // Only to the p that sent it, since we'll spam the rest as messages arrive... r.sent[p.key][ann.key] = struct{}{} p.sendAnnounce(r, oldInfo.getAnnounce(ann.key)) } else { // They sent us exactly the same info we already have // No point in sending it back when we do maintenance r.sent[p.key][ann.key] = struct{}{} } } } func (r *router) handleAnnounce(from phony.Actor, p *peer, ann *routerAnnounce) { r.Act(from, func() { r._handleAnnounce(p, ann) }) } func (r *router) sendTraffic(tr *traffic) { // This must be non-blocking, to prevent deadlocks between read/write paths in the encrypted package // Basically, WriteTo and ReadFrom can't be allowed to block each other, but they could if we allowed backpressure here // There may be a better way to handle this, but it practice it probably won't be an issue (we'll throw the packet in a queue somewhere, or drop it) r.Act(nil, func() { r.pathfinder._handleTraffic(tr) }) } func (r *router) handleTraffic(from phony.Actor, tr *traffic) { r.Act(from, func() { if p := r._lookup(tr.path, &tr.watermark); p != nil { p.sendTraffic(r, tr) } else if tr.dest == r.core.crypto.publicKey { r.pathfinder._resetTimeout(tr.source) r.core.pconn.handleTraffic(r, tr) } else { // Not addressed to us, and we don't know a next hop. // The path is broken, so do something about that. r.pathfinder._doBroken(tr) } }) } func (r *router) _getRootAndDists(dest publicKey) (publicKey, map[publicKey]uint64) { // This returns the distances from the destination's root for the destination and each of its ancestors // Note that we skip any expired infos dists := make(map[publicKey]uint64) next := dest var root publicKey var dist uint64 for { if _, isIn := dists[next]; isIn { break } if info, isIn := r.infos[next]; isIn { root = next dists[next] = dist dist++ next = info.parent } else { break } } return root, dists } func (r *router) _getRootAndPath(dest publicKey) (publicKey, []peerPort) { var ports []peerPort visited := make(map[publicKey]struct{}) var root publicKey next := dest for { if _, isIn := visited[next]; isIn { // We hit a loop return dest, nil } if info, isIn := r.infos[next]; isIn { root = next visited[next] = struct{}{} if next == info.parent { // We reached a root, don't append the self port (it should be zero anyway) break } ports = append(ports, info.port) next = info.parent } else { // We hit a dead end return dest, nil } } // Reverse order, since we built this from the node to the root for left, right := 0, len(ports)-1; left < right; left, right = left+1, right-1 { ports[left], ports[right] = ports[right], ports[left] } return root, ports } func (r *router) _getDist(destPath []peerPort, key publicKey) uint64 { // We cache the keyPath to avoid allocating slices for every lookup var keyPath []peerPort if cached, isIn := r.cache[key]; isIn { keyPath = cached } else { _, keyPath = r._getRootAndPath(key) r.cache[key] = keyPath } end := len(destPath) if len(keyPath) < end { end = len(keyPath) } dist := uint64(len(keyPath) + len(destPath)) for idx := 0; idx < end; idx++ { if keyPath[idx] == destPath[idx] { dist -= 2 } else { break } } return dist } func (r *router) _lookup(path []peerPort, watermark *uint64) *peer { // Look up the next hop (in treespace) towards the destination var bestPeer *peer bestDist := ^uint64(0) if watermark != nil { if dist := r._getDist(path, r.core.crypto.publicKey); dist < *watermark { bestDist = dist // Self dist, so other nodes must be strictly better by distance *watermark = dist } else { return nil } } tiebreak := func(key publicKey) bool { // If distances match, keep the peer with the lowest key, just so there's some kind of consistency return bestPeer != nil && key.less(bestPeer.key) } for k, ps := range r.peers { if dist := r._getDist(path, k); dist < bestDist || (dist == bestDist && tiebreak(k)) { for p := range ps { // Set the next hop to any peer object for this peer bestPeer = p bestDist = dist break } } } if bestPeer != nil { for p := range r.peers[bestPeer.key] { // Find the best peer object for this peer switch { case p.prio < bestPeer.prio: bestPeer = p // Better priority case p.prio == bestPeer.prio && p.order < bestPeer.order: bestPeer = p // Up for longer } } } return bestPeer } func (r *router) _getAncestry(key publicKey) []publicKey { // Returns the ancestry starting with the root side, ordering is important for how we send over the network / GC info... anc := r._backwardsAncestry(key) for left, right := 0, len(anc)-1; left < right; left, right = left+1, right-1 { anc[left], anc[right] = anc[right], anc[left] } return anc } func (r *router) _backwardsAncestry(key publicKey) []publicKey { // Return an ordered list of node ancestry, starting with the given key and ending at the root (or the end of the line) var anc []publicKey here := key for { // TODO? use a map or something to check visited nodes faster? for _, k := range anc { if k == here { return anc } } if info, isIn := r.infos[here]; isIn { anc = append(anc, here) here = info.parent continue } // Dead end return anc } } /***************** * routerSigReq * *****************/ type routerSigReq struct { seq uint64 nonce uint64 } func (req *routerSigReq) bytesForSig(node, parent publicKey) []byte { out := make([]byte, 0, publicKeySize*2+8+8) out = append(out, node[:]...) out = append(out, parent[:]...) out, _ = req.encode(out) return out } func (req *routerSigReq) size() int { size := wireSizeUint(req.seq) size += wireSizeUint(req.nonce) return size } func (req *routerSigReq) encode(out []byte) ([]byte, error) { start := len(out) out = wireAppendUint(out, req.seq) out = wireAppendUint(out, req.nonce) end := len(out) if end-start != req.size() { panic("this should never happen") } return out, nil } func (req *routerSigReq) chop(data *[]byte) error { var tmp routerSigReq orig := *data if !wireChopUint(&tmp.seq, &orig) { return types.ErrDecode } else if !wireChopUint(&tmp.nonce, &orig) { return types.ErrDecode } *req = tmp *data = orig return nil } func (req *routerSigReq) decode(data []byte) error { var tmp routerSigReq if err := tmp.chop(&data); err != nil { return err } else if len(data) != 0 { return types.ErrDecode } *req = tmp return nil } /***************** * routerSigRes * *****************/ type routerSigRes struct { routerSigReq port peerPort psig signature } func (res *routerSigRes) check(node, parent publicKey) bool { bs := res.bytesForSig(node, parent) return parent.verify(bs, &res.psig) } func (res *routerSigRes) bytesForSig(node, parent publicKey) []byte { bs := res.routerSigReq.bytesForSig(node, parent) bs = wireAppendUint(bs, uint64(res.port)) return bs } func (res *routerSigRes) size() int { size := res.routerSigReq.size() size += wireSizeUint(uint64(res.port)) size += len(res.psig) return size } func (res *routerSigRes) encode(out []byte) ([]byte, error) { start := len(out) var err error out, err = res.routerSigReq.encode(out) if err != nil { return nil, err } out = wireAppendUint(out, uint64(res.port)) out = append(out, res.psig[:]...) end := len(out) if end-start != res.size() { panic("this should never happen") } return out, nil } func (res *routerSigRes) chop(data *[]byte) error { orig := *data var tmp routerSigRes if err := tmp.routerSigReq.chop(&orig); err != nil { return err } else if !wireChopUint((*uint64)(&tmp.port), &orig) { return types.ErrDecode } else if !wireChopSlice(tmp.psig[:], &orig) { return types.ErrDecode } *res = tmp *data = orig return nil } func (res *routerSigRes) decode(data []byte) error { var tmp routerSigRes if err := tmp.chop(&data); err != nil { return err } else if len(data) != 0 { return types.ErrDecode } *res = tmp return nil } /******************* * routerAnnounce * *******************/ type routerAnnounce struct { key publicKey parent publicKey routerSigRes sig signature } func (ann *routerAnnounce) check() bool { if ann.port == 0 && ann.key != ann.parent { return false } bs := ann.bytesForSig(ann.key, ann.parent) return ann.key.verify(bs, &ann.sig) && ann.parent.verify(bs, &ann.psig) } func (ann *routerAnnounce) size() int { size := len(ann.key) size += len(ann.parent) size += ann.routerSigRes.size() size += len(ann.sig) return size } func (ann *routerAnnounce) encode(out []byte) ([]byte, error) { start := len(out) var err error out = append(out, ann.key[:]...) out = append(out, ann.parent[:]...) out, err = ann.routerSigRes.encode(out) if err != nil { return nil, err } out = append(out, ann.sig[:]...) end := len(out) if end-start != ann.size() { panic("this should never happen") } return out, nil } func (ann *routerAnnounce) decode(data []byte) error { var tmp routerAnnounce if !wireChopSlice(tmp.key[:], &data) { return types.ErrDecode } else if !wireChopSlice(tmp.parent[:], &data) { return types.ErrDecode } else if err := tmp.routerSigRes.chop(&data); err != nil { return err } else if !wireChopSlice(tmp.sig[:], &data) { return types.ErrDecode } else if len(data) != 0 { return types.ErrDecode } *ann = tmp return nil } /*************** * routerInfo * ***************/ // This is the value stored in a key,value map type routerInfo struct { parent publicKey routerSigRes sig signature } func (info *routerInfo) getAnnounce(key publicKey) *routerAnnounce { return &routerAnnounce{ key: key, parent: info.parent, routerSigRes: info.routerSigRes, sig: info.sig, } } /**************** * routerForget * ****************/ type routerForget struct { routerAnnounce } ))entry(name traffic.gonode(typeregularcontentsZpackage network import "github.com/Arceliar/ironwood/types" /*********** * traffic * ***********/ type traffic struct { path []peerPort // *not* zero terminated from []peerPort source publicKey dest publicKey watermark uint64 payload []byte } func (tr *traffic) copyFrom(original *traffic) { tmp := *tr *tr = *original tr.path = append(tmp.path[:0], tr.path...) tr.from = append(tmp.from[:0], tr.from...) tr.payload = append(tmp.payload[:0], tr.payload...) } func (tr *traffic) size() int { size := wireSizePath(tr.path) size += wireSizePath(tr.from) size += len(tr.source) size += len(tr.dest) size += wireSizeUint(tr.watermark) size += len(tr.payload) return size } func (tr *traffic) encode(out []byte) ([]byte, error) { start := len(out) out = wireAppendPath(out, tr.path) out = wireAppendPath(out, tr.from) out = append(out, tr.source[:]...) out = append(out, tr.dest[:]...) out = wireAppendUint(out, tr.watermark) out = append(out, tr.payload...) end := len(out) if end-start != tr.size() { panic("this should never happen") } return out, nil } func (tr *traffic) decode(data []byte) error { var tmp traffic tmp.path = tr.path[:0] tmp.from = tr.from[:0] if !wireChopPath(&tmp.path, &data) { return types.ErrDecode } else if !wireChopPath(&tmp.from, &data) { return types.ErrDecode } else if !wireChopSlice(tmp.source[:], &data) { return types.ErrDecode } else if !wireChopSlice(tmp.dest[:], &data) { return types.ErrDecode } else if !wireChopUint(&tmp.watermark, &data) { return types.ErrDecode } tmp.payload = append(tr.payload[:0], data...) *tr = tmp return nil } // Functions needed for pqPacket func (tr *traffic) wireType() wirePacketType { return wireTraffic } func (tr *traffic) sourceKey() publicKey { return tr.source } func (tr *traffic) destKey() publicKey { return tr.dest } ))entry(namewire.gonode(typeregularcontentsHpackage network import "encoding/binary" type wirePacketType byte const ( wireDummy wirePacketType = iota // unused wireKeepAlive wireProtoSigReq wireProtoSigRes wireProtoAnnounce wireProtoBloomFilter wireProtoPathLookup wireProtoPathNotify wireProtoPathBroken wireTraffic ) func wireChopSlice(out []byte, data *[]byte) bool { if len(*data) < len(out) { return false } copy(out, *data) *data = (*data)[len(out):] return true } func wireChopBytes(out *[]byte, data *[]byte, size int) bool { if len(*data) < size { return false } *out = append(*out, (*data)[:size]...) *data = (*data)[size:] return true } func wireChopUint(out *uint64, data *[]byte) bool { var u uint64 var l int if u, l = binary.Uvarint(*data); l <= 0 { return false } *out, *data = u, (*data)[l:] return true } func wireSizeUint(u uint64) int { var b [10]byte return binary.PutUvarint(b[:], u) } func wireAppendUint(out []byte, u uint64) []byte { return binary.AppendUvarint(out, u) } type wireEncodeable interface { size() int encode(out []byte) ([]byte, error) } func wireEncode(out []byte, pType uint8, obj wireEncodeable) ([]byte, error) { out = append(out, pType) var err error if out, err = obj.encode(out); err != nil { return nil, err } return out, nil } func wireSizePath(path []peerPort) int { var size int for _, port := range path { size += wireSizeUint(uint64(port)) } size += wireSizeUint(0) return size } func wireAppendPath(dest []byte, path []peerPort) []byte { for _, port := range path { dest = wireAppendUint(dest, uint64(port)) } dest = wireAppendUint(dest, 0) return dest } func wireDecodePath(source []byte) (path []peerPort, length int) { bs := source for { var u uint64 if !wireChopUint(&u, &bs) { return nil, -1 // TODO correct value } if u == 0 { break } path = append(path, peerPort(u)) } length = len(source) - len(bs) return } func wireChopPath(out *[]peerPort, data *[]byte) bool { path, length := wireDecodePath(*data) if length < 0 { return false } *out = append(*out, path...) *data = (*data)[length:] return true } ))))entry(namesignednode(type directoryentry(name packetconn.gonode(typeregularcontents0package signed import ( "crypto/ed25519" "net" "github.com/Arceliar/ironwood/network" "github.com/Arceliar/ironwood/types" ) type PacketConn struct { *network.PacketConn secret ed25519.PrivateKey public ed25519.PublicKey } // NewPacketConn returns a *PacketConn struct which implements the types.PacketConn interface. func NewPacketConn(secret ed25519.PrivateKey, options ...network.Option) (*PacketConn, error) { pc, err := network.NewPacketConn(secret, options...) if err != nil { return nil, err } pub := secret.Public().(ed25519.PublicKey) return &PacketConn{pc, secret, pub}, nil } func (pc *PacketConn) ReadFrom(p []byte) (n int, from net.Addr, err error) { for { if n, from, err = pc.PacketConn.ReadFrom(p); err != nil { return } fromKey := ed25519.PublicKey(from.(types.Addr)) msg, ok := pc.unpack(p[:n], fromKey) if !ok { continue // error? } n = copy(p, msg) return } } func (pc *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { switch addr.(type) { case types.Addr: default: return 0, types.ErrBadAddress } toKey := ed25519.PublicKey(addr.(types.Addr)) msg := pc.sign(nil, toKey, p) n, err = pc.PacketConn.WriteTo(msg, addr) n -= len(msg) - len(p) // subtract overhead if n < 0 { n = 0 } return } func (pc *PacketConn) sign(dest, toKey ed25519.PublicKey, msg []byte) []byte { sigBytes := make([]byte, 0, 65535) sigBytes = append(sigBytes, toKey...) sigBytes = append(sigBytes, msg...) tmp := make([]byte, 0, 65535) tmp = append(tmp, ed25519.Sign(pc.secret, sigBytes)...) tmp = append(tmp, msg...) return append(dest, tmp...) } func (pc *PacketConn) MTU() uint64 { return pc.PacketConn.MTU() - ed25519.SignatureSize } func (pc *PacketConn) unpack(bs []byte, fromKey ed25519.PublicKey) (msg []byte, ok bool) { if len(bs) < ed25519.SignatureSize { return } sig := bs[:ed25519.SignatureSize] msg = bs[ed25519.SignatureSize:] sigBytes := make([]byte, 0, 65535) sigBytes = append(sigBytes, pc.public...) sigBytes = append(sigBytes, msg...) ok = ed25519.Verify(fromKey, sigBytes, sig) return } ))))entry(nametypesnode(type directoryentry(nameaddr.gonode(typeregularcontentsÍpackage types import ( "crypto/ed25519" "encoding/hex" ) // Addr implements the `net.Addr` interface for `ed25519.PublicKey` values. type Addr ed25519.PublicKey // Network returns "ed25519.PublicKey" as a string, but is otherwise unused. func (a Addr) Network() string { return "ed25519.PublicKey" } // String returns the ed25519.PublicKey as a hexidecimal string, but is otherwise unused. func (a Addr) String() string { return hex.EncodeToString(a) } ))entry(nameerror_string.gonode(typeregularcontentsù// Code generated by "stringer -type=Error"; DO NOT EDIT. package types import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[ErrUndefined-0] _ = x[ErrEncode-1] _ = x[ErrDecode-2] _ = x[ErrClosed-3] _ = x[ErrTimeout-4] _ = x[ErrBadMessage-5] _ = x[ErrEmptyMessage-6] _ = x[ErrOversizedMessage-7] _ = x[ErrUnrecognizedMessage-8] _ = x[ErrPeerNotFound-9] _ = x[ErrBadAddress-10] _ = x[ErrBadKey-11] } const _Error_name = "ErrUndefinedErrEncodeErrDecodeErrClosedErrTimeoutErrBadMessageErrEmptyMessageErrOversizedMessageErrUnrecognizedMessageErrPeerNotFoundErrBadAddressErrBadKey" var _Error_index = [...]uint8{0, 12, 21, 30, 39, 49, 62, 77, 96, 118, 133, 146, 155} func (i Error) String() string { if i >= Error(len(_Error_index)-1) { return "Error(" + strconv.FormatInt(int64(i), 10) + ")" } return _Error_name[_Error_index[i]:_Error_index[i+1]] } ))entry(name errors.gonode(typeregularcontentspackage types //go:generate stringer -type=Error // Error is any error generated by the PacketConn. Note that other errors may still be returned, if e.g. HandleConn returns due to a network error. An Error may be wrapped to provide additional context. type Error uint const ( ErrUndefined Error = iota ErrEncode ErrDecode ErrClosed ErrTimeout ErrBadMessage ErrEmptyMessage ErrOversizedMessage ErrUnrecognizedMessage ErrPeerNotFound ErrBadAddress ErrBadKey ) func (e Error) Error() string { return e.String() } ))entry(name packetconn.gonode(typeregularcontentsdpackage types import ( "crypto/ed25519" "net" ) type PacketConn interface { net.PacketConn // HandleConn expects a peer's public key as its first argument, and a net.Conn with TCP-like semantics (reliable ordered delivery) as its second argument. // This function blocks while the net.Conn is in use, and returns an error if any occurs. // This function returns (almost) immediately if PacketConn.Close() is called. // In all cases, the net.Conn is closed before returning. HandleConn(key ed25519.PublicKey, conn net.Conn, prio uint8) error // IsClosed returns true if and only if the connection is closed. // This is to check if the PacketConn is closed without potentially being stuck on a blocking operation (e.g. a read or write). IsClosed() bool // PrivateKey returns the ed25519.PrivateKey used to initialize the PacketConn. PrivateKey() ed25519.PrivateKey // MTU returns the maximum transmission unit of the PacketConn, i.e. maximum safe message size to send over the network. MTU() uint64 // SendLookup sends a lookup for a given (possibly partial) key. SendLookup(target ed25519.PublicKey) } )))))