Compare commits

...

195 Commits

Author SHA1 Message Date
Radosław Kamiński
5f6b8e86a5 test(rendezvous): error cases (#1683) 2025-09-22 15:53:47 +01:00
richΛrd
11b98b7a3f fix: add missing import (#1707) 2025-09-22 13:38:32 +00:00
richΛrd
647f76341e feat(mix): mix_protocol and entry connection (#1703) 2025-09-22 13:09:18 +00:00
Radosław Kamiński
fbf96bb2ce chore(readme): Update chat code example link (#1709) 2025-09-22 13:28:03 +01:00
richΛrd
f0aaecb743 feat(mix): mixnode (#1702) 2025-09-21 16:46:48 +00:00
richΛrd
8d3076ea99 fix: add missing import for linux/amd64 daily job (#1706) 2025-09-20 18:39:12 -04:00
richΛrd
70b7d61436 feat(mix): SURBs and fragmentation (#1700) 2025-09-19 15:56:26 -04:00
Gabriel Cruz
37bae0986c chore: remove go daemon (#1705) 2025-09-19 15:21:23 -04:00
Gabriel Cruz
b34ddab10c chore(autonat-v2): add interop tests (#1695) 2025-09-18 14:30:56 +00:00
richΛrd
e09457da12 feat(mix): sphinx (#1691) 2025-09-18 09:04:44 -04:00
vladopajic
94ad1dcbc8 chore: nimble config tidy (#1696) 2025-09-17 18:10:07 +00:00
Gabriel Cruz
5b9f2cba6f fix: autonatV2 request addresses (#1698) 2025-09-17 13:38:03 -03:00
richΛrd
59e7069c15 feat: v1.13.0 (#1673) 2025-09-17 08:18:53 -04:00
richΛrd
18a0e9c2d1 feat(mix): message (#1690) 2025-09-16 10:49:49 -04:00
richΛrd
34a9a03b73 feat(mix): serialization (#1689) 2025-09-16 14:12:19 +00:00
Gabriel Cruz
788109b4f4 fix(autonat-v2): service setting up correctly (#1694) 2025-09-16 09:10:12 +00:00
vladopajic
44aab92b3e chore(quic): better error handling in stream.write() (#1693) 2025-09-16 08:35:57 +00:00
richΛrd
ad0812b40b feat(mix): sequence number generator and tag manager (#1688) 2025-09-15 16:49:34 -03:00
richΛrd
0751f240a2 feat(mix): crypto (#1687) 2025-09-15 16:46:28 +00:00
richΛrd
d8ecf8a135 chore: add missing import (#1692) 2025-09-15 10:04:07 -04:00
Gabriel Cruz
bab863859c chore: add autonatv2 service to builder (#1686) 2025-09-12 21:23:03 +02:00
Gabriel Cruz
73d04def6f feat: add autonat v2 service (#1684) 2025-09-12 08:26:33 -04:00
richΛrd
4509ade75c feat: add skipIDontWant and skipPreamble (#1681) 2025-09-10 16:27:54 -04:00
Gabriel Cruz
b64c0f6d85 chore: rename withAutonatV2 to withAutonatV2Server (#1680) 2025-09-10 15:20:01 -04:00
vladopajic
582ba7e650 fix(pubsub): use custom conn when message is sent as lower-priority (#1679) 2025-09-10 18:34:51 +02:00
Gabriel Cruz
31ae734aff feat(autonat-v2): add client and tests (#1659) 2025-09-10 13:11:03 +00:00
Radosław Kamiński
f26ff88e6c test(rendezvous): Simplify test setup (#1677) 2025-09-10 10:21:22 +01:00
vladopajic
4fbf59ece8 fix(builders): transport param not assigned in newStandardSwitch (#1676) 2025-09-09 11:34:41 -03:00
Radosław Kamiński
62388a7a20 refactor(rendezvous): Split Rendezvous Protobuf and add tests (#1671) 2025-09-09 09:12:43 +01:00
vladopajic
27051164db chore(tests): utilize quic transport in pubsub tests (#1667) 2025-09-08 21:30:21 +00:00
Gabriel Cruz
f41009461b fix: revert reviewdog workaround (#1672) 2025-09-08 16:35:47 -04:00
vladopajic
c3faabf522 chore(quic): add tests from common interop (#1662) 2025-09-08 16:06:22 +00:00
Gabriel Cruz
10f7f5c68a chore(autonat-v2): add server config (#1669) 2025-09-08 15:23:23 +00:00
Gabriel Cruz
f345026900 fix(linters): use workaround for reviewdog bug (#1668) 2025-09-08 14:48:03 +00:00
vladopajic
5d6578a06f chore: splitRPCMsg improvements (#1665) 2025-09-08 11:06:55 -03:00
Gabriel Cruz
871a5d047f feat(autonat-v2): add server (#1658) 2025-09-04 13:27:49 -04:00
Gabriel Cruz
061195195b chore(autonat-v2): add utils (#1657) 2025-09-03 19:04:46 +00:00
Radosław Kamiński
8add5aaaab fix(rendezvous): peer registration limit (#1656) 2025-09-03 18:01:23 +01:00
Miran
dbf60b74c7 chore(ci): remove macos-13 from the matrix (#1650) 2025-09-03 11:16:37 -04:00
Radosław Kamiński
d2eaf07960 test(rendezvous): Registration TTL tests (#1655) 2025-09-02 15:43:48 +01:00
Gabriel Cruz
6e5274487e chore: pass localAddr in noise, mplex and yamux (#1654) 2025-09-01 23:38:23 +02:00
Gabriel Cruz
7ed62461d7 chore: add localAddr to Connection (#1651) 2025-09-01 20:39:08 +02:00
Radosław Kamiński
6059ee8332 test(performance): upload plots as artifacts (#1648) 2025-09-01 16:12:49 +00:00
Radosław Kamiński
4f7e232a9e fix(rendezvous): pagination offset (#1646) 2025-08-29 18:27:03 +01:00
richΛrd
5eaa43b860 fix: dont send GoAway for unknown streams and mark streams as closed on conn close (#1645) 2025-08-28 09:34:45 -04:00
richΛrd
17ed2d88df chore: temporarily disable performance plots from being published (#1647) 2025-08-28 08:20:12 -04:00
Radosław Kamiński
c7f29ed5db test(rendezvous): Refactor Rendezvous tests (#1644) 2025-08-28 09:35:04 +01:00
vladopajic
9865cc39b5 chore(perf): follow up for PR#1600 (#1620) 2025-08-26 10:00:25 -04:00
Gabriel Cruz
601f56b786 chore(autonat-v2): add message types (#1637) 2025-08-25 15:18:43 +00:00
Ben
25a8ed4d07 refactor(kad): Refine, and reduce, exception scope (#1627) 2025-08-25 11:33:26 +00:00
Radosław Kamiński
955e28ff70 test(yamux): Add unit tests - frame handling and stream initiation (#1634) 2025-08-22 12:02:54 +01:00
Radosław Kamiński
f952e6d436 test(performance): do not run publish steps on forks and fix cleanup (#1630) 2025-08-19 13:25:52 +01:00
MorganaFuture
bed83880bf fix(test): Race condition on Windows-specific daemon close (#1628)
Co-authored-by: Ben <benph@vac.dev>
Co-authored-by: vladopajic <vladopajic@users.noreply.github.com>
2025-08-18 17:09:31 -04:00
richΛrd
9bd4b7393f feat(kad-dht): findPeer (#1624) 2025-08-18 13:45:31 +00:00
Radosław Kamiński
12d1fae404 test(yamux): Add header unit tests (#1625) 2025-08-18 13:50:54 +01:00
MorganaFuture
17073dc9e0 fix(tests): prevent race condition in testgossipsubcontrolmessages (#1626) 2025-08-15 18:46:39 +00:00
vladopajic
b1649b3566 chore(quic): add length prefixed test (#1599) 2025-08-15 15:57:56 +02:00
Ben
ef20f46b47 refactor: rm dhttypes.nim (#1612) 2025-08-15 12:23:27 +00:00
Gabriel Cruz
9161529c84 fix: pubsub signature verification (#1618) 2025-08-14 20:15:02 +00:00
Ben
8b70384b6a refactor: Removal of "Unhashed" key variant (#1623)
Internal keydata is _always_ unhashed. The parts that require its data in hashed form hash it themselves using the provided hasher (with default fallback)
2025-08-14 11:22:09 +00:00
MorganaFuture
f25814a890 feat(perf): implement proper half-close semantics (#1600)
Co-authored-by: vladopajic <vladopajic@users.noreply.github.com>
2025-08-13 10:08:17 -04:00
Radosław Kamiński
3d5ea1fa3c test(performance): fetch before push and improve latency history (#1617) 2025-08-13 14:22:42 +01:00
richΛrd
2114008704 fix: compilation warning on yamux due to using CatchableErr (#1616) 2025-08-12 22:11:33 +00:00
richΛrd
04796b210b fix: don't check for errors as close() will only contain futures that raise [] (#1615) 2025-08-12 21:26:22 +00:00
Ben
59faa023aa feat(kad): Initial unstable putval api (#1582) 2025-08-12 12:25:21 +02:00
vladopajic
fdebea4e14 chore(quic): fix flaky test when eof is expected (#1611) 2025-08-11 17:02:13 +00:00
vladopajic
0c188df806 fix(quic): race errors when stopping transport (#1614) 2025-08-11 15:48:37 +00:00
Radosław Kamiński
abee5326dc test(gossipsub): Performance tests - plot latency history (#1608) 2025-08-11 16:11:29 +01:00
Radosław Kamiński
71f04d1bb3 test(gossipsub): Performance tests - plot docker stats (#1597) 2025-08-11 15:45:50 +01:00
Radosław Kamiński
41ae43ae80 test(gossipsub): Performance tests - collect docker stats (#1593) 2025-08-11 14:01:38 +00:00
vladopajic
5dbf077d9e chore(pubsub): simplify prune backoff test (#1596) 2025-08-09 17:49:14 +00:00
vladopajic
b5fc7582ff fix(quic): setting shortAgent (#1609) 2025-08-08 17:21:58 +00:00
vladopajic
7f83ebb198 chore(quic): readOnce better exception handling (#1610) 2025-08-08 16:02:33 +00:00
vladopajic
ceb89986c1 chore(quic): exception msg fix (#1607) 2025-08-08 10:24:55 -03:00
vladopajic
f4ff27ca6b fix(quic): test improvement (#1595) 2025-08-06 14:34:07 -03:00
richΛrd
b517b692df chore: v1.12.0 (#1581) 2025-08-05 13:59:43 +00:00
Ben
7cfd26035a fix(kad): Skip self when iterating through findNode dialouts (#1594) 2025-08-05 12:00:09 +02:00
Radosław Kamiński
cd5fea53e3 test(gossipsub): Performance tests - more scenarios (#1585) 2025-08-01 08:33:39 +01:00
Radosław Kamiński
d9aa393761 test(gossipsub): Performance tests - aggregation script and workflow (#1577) 2025-07-31 17:59:09 +01:00
Gabriel Cruz
a4a0d9e375 ci: add nimbus compilation daily test (#1571) 2025-07-31 15:01:10 +00:00
richΛrd
c8b406d6ed feat(kad-dht): find nodes (#1324)
Co-authored-by: Ben-PH <benphawke@gmail.com>
2025-07-31 12:30:02 +02:00
Radosław Kamiński
f0125a62df test(gossipsub): Performance tests - base scenario and runner (#1573) 2025-07-31 09:22:14 +00:00
Gabriel Cruz
9bf2636186 ci(docs): fix generation (#1590) 2025-07-31 00:42:49 +02:00
Ben
01a33ebe5c docs: Document nimble 0.20.1 dev req and instructions (#1586) 2025-07-30 16:15:48 +02:00
Gabriel Cruz
c1cd31079b fix(interop): redis not installed (#1584) 2025-07-25 19:25:48 +00:00
vladopajic
9f9f38e314 chore(quic): add close session test (#1583) 2025-07-25 13:46:15 -04:00
Gabriel Cruz
f83638eb82 chore: remove support for nim 1.6 (#1572) 2025-07-24 15:51:44 -04:00
Ivan FB
882cb5dfe3 fix: more secure send loop in yamux and exception-handling-code cleanup in pubsubpeer (#1579) 2025-07-24 17:15:59 +00:00
vladopajic
81310df2a2 chore(switch): remove unnecesery debug log entry (#1575) 2025-07-24 15:00:21 +00:00
Ivan FB
34110a37d7 chore: revert excp type in yamux write (#1578) 2025-07-24 14:30:29 +00:00
vladopajic
1035e4f314 fix(quic): close all streams when closing session (#1576) 2025-07-24 11:04:05 -03:00
Ivan FB
d08bad5893 fix: memory consumption in yamux and pubsubpeer (#1570) 2025-07-24 09:27:45 -04:00
vladopajic
7bdba4909f chore(PeerTable): add stringification operator (#1567) 2025-07-23 10:53:26 +00:00
vladopajic
e71c7caf82 chore(perf): remove timeout when reading from quic stream (#1569) 2025-07-22 17:16:09 -04:00
vladopajic
45476bdd6b chore(deps): bump quic to v0.2.9 (#1568) 2025-07-22 10:24:01 -03:00
Gabriel Cruz
c7ee7b950d chore(autotls): only import dnsclient when autotls is required (#1565) 2025-07-22 11:53:09 +00:00
Vedran Mendelski
87b3d2c864 feat(ci): add CI failure notifications
Add Discord webhook notifications for failed daily workflows to improve visibility
of branch build issues.

- https://github.com/vacp2p/nim-libp2p/issues/1403
2025-07-22 12:00:55 +02:00
Gabriel Cruz
19b4c20e2f chore: add TransportConfig (#1561) 2025-07-21 20:47:01 +00:00
Gabriel Cruz
514bd4b5f5 fix(autotls): integration tests (#1560) 2025-07-21 19:57:37 +00:00
Ben-PH
46d936b80c chore(ci): initial flake.nix file with dev shell
Signed-off-by: Jakub Sokołowski <jakub@status.im>
2025-07-21 19:14:26 +02:00
Gabriel Cruz
80bf27c6bb fix: typo on startAt (#1562) 2025-07-21 16:56:12 +02:00
Farooq
6576c5c3bf feat: GossipSub v1.4 (#1448)
Co-authored-by: Richard Ramos <info@richardramos.me>
2025-07-20 18:47:13 +00:00
Gabriel Cruz
2e6b1d2738 feat(wstransport): add autotls support (#1535) 2025-07-18 11:58:27 -04:00
MorganaFuture
9e6c4cb4d2 fix: properly handle CancelledError in switch accept loop (#1537)
Signed-off-by: MorganaFuture <andrewmochalskyi@gmail.com>
2025-07-17 21:29:37 +00:00
MorganaFuture
5f256049ab fix: build failures caused by missing import (#1541) 2025-07-17 08:00:45 -04:00
richΛrd
e29ca73386 fix: autotls related imports (#1550) 2025-07-16 21:43:34 +00:00
vladopajic
577809750a fix(quic): add getWrapped method for QuicStream (#1546) 2025-07-16 17:16:18 +00:00
Gabriel Cruz
46a5430cc2 chore(dialer): expand dns4 and dns6 (#1543) 2025-07-15 18:28:55 +00:00
vladopajic
d8b9f59c5e chore(readme): add link to community channel (#1542) 2025-07-15 15:46:59 +02:00
MorganaFuture
2951356c9d fix(perf): add QUIC transport compatibility (#1524) 2025-07-15 08:20:26 -04:00
richΛrd
7ae21d0cbd fix: also hide autotls/utils methods (#1538) 2025-07-14 17:19:19 -04:00
richΛrd
eee8341ad2 chore: hide autotls under compile flag (#1533) 2025-07-14 13:52:33 -04:00
richΛrd
e83bd2d582 feat(gossipsub1.4): adding new attributes and protobuffers (#1515) 2025-07-12 16:12:06 +00:00
richΛrd
998bb58aef feat(gossipsub1_4): preamble store (#1513) 2025-07-11 16:48:37 -04:00
Gabriel Cruz
c1f6dec7d3 chore(peerinfo): enable calling update multiple times (#1525) 2025-07-11 13:28:34 -04:00
vladopajic
13c613c26c chore: add template newSeqUninit (#1518) 2025-07-11 13:19:24 +00:00
vladopajic
45f0f9f47a chore: removing unused type StreamSeq (#1507) 2025-07-11 12:24:01 +00:00
Gabriel Cruz
b1dd0a2ec6 chore: fix broken README links (#1517) 2025-07-10 22:35:37 +00:00
Gabriel Cruz
beecfdfadb chore(autotls): prevent nil access on stop (#1514) 2025-07-10 14:02:58 -03:00
vladopajic
e4faec5570 chore: utilizing newSeqUninitialized (#1510) 2025-07-10 08:31:53 -03:00
Radosław Kamiński
41c9bf8e8c test(gossipsub): gossipsub unit tests 2 (#1511) 2025-07-10 09:52:51 +01:00
Miran
7ae366d979 chore(test): reduce sleepAsync (#1481) 2025-07-09 09:47:31 +02:00
Radosław Kamiński
9b33cea225 test(gossipsub): gossipsub unit tests 1 (#1508)
Co-authored-by: vladopajic <vladopajic@users.noreply.github.com>
2025-07-08 07:46:28 +01:00
Radosław Kamiński
f8077f7432 test(gossipsub): behavior unit tests 2 (#1503) 2025-07-07 19:35:02 +00:00
Radosław Kamiński
773fc67865 test(gossipsub): behavior unit tests 1 (#1501) 2025-07-07 18:45:23 +00:00
vladopajic
7e07ffc5a8 chore: add info to error (#1505)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-07 14:32:52 -03:00
vladopajic
aa1c33ffe9 refactor(noise): reduce memory usage (#1497) 2025-07-07 14:27:49 +00:00
Gabriel Cruz
f1e220fba4 chore(deps): remove bio (#1506) 2025-07-04 17:13:24 -04:00
Gabriel Cruz
5ad656bf26 fix(ci): move testintegration to new job (#1499) 2025-07-02 15:43:50 -04:00
vladopajic
cfd631457a ci(daily): fix latest deps install (#1498) 2025-07-02 14:24:05 +01:00
Radosław Kamiński
4f8597609b test(gossipsub): Signature flags tests (#1496) 2025-07-02 13:09:39 +01:00
Gabriel Cruz
4ed72a753c chore(autotls): make autotls manager into service (#1486) 2025-07-01 17:51:45 +00:00
Gabriel Cruz
2a9abbe925 fix(daily): testintegration only used on amd64 (#1494) 2025-07-01 16:53:00 +00:00
Radosław Kamiński
ee61e234ac test(gossipsub): scoring integration tests (#1487) 2025-07-01 15:41:25 +00:00
Radosław Kamiński
8f54367e3a test(gossipsub): refactor compatibility related tests (#1495) 2025-07-01 15:19:44 +00:00
vladopajic
61826a20e4 refactor(bufferstream): utilize ZeroQueue (#1491) 2025-07-01 09:51:52 +00:00
vladopajic
32951e1a68 refactor(secure): utilize ZeroQueue (#1492) 2025-07-01 09:00:52 +00:00
vladopajic
1d13e405e4 chore(ci): disable hole punching tests (#1493)
Co-authored-by: Gabriel Cruz <8129788+gmelodie@users.noreply.github.com>
2025-07-01 10:40:15 +02:00
vladopajic
729e879c1c chore: remove unused import (#1490) 2025-06-30 15:53:01 +02:00
AkshayaMani
64c9cf1b9e feat(gossipsub): Add support for skipping message insertion into the message cache (Mix protocol integration) (#1485) 2025-06-27 17:37:37 -04:00
vladopajic
4d94892eb0 chore(yamux): improve performance with zero allocation queue (#1488) 2025-06-27 16:49:51 +00:00
Gabriel Cruz
3ecb1744ce fix(autotls): private checkedGetPrimaryIPAddr (#1489) 2025-06-27 08:42:22 -04:00
vladopajic
2f9c3fb3e2 chore(perf): add quic test (#1483) 2025-06-25 11:15:07 -04:00
Gabriel Cruz
2609c270b8 feat(autotls): add AutoTLSManager (#1472) 2025-06-25 14:19:59 +00:00
Radosław Kamiński
48b3e34cd3 test(gossipsub): updateScores tests (#1471) 2025-06-24 16:01:49 +00:00
Radosław Kamiński
abb2c43667 test(gossipsub): behaviourPenalty tests (#1469) 2025-06-24 15:07:14 +00:00
Radosław Kamiński
d1cfbb35d3 test(gossipsub): organise Behavior tests (#1468) 2025-06-24 14:18:54 +00:00
Radosław Kamiński
38a630eee0 test(gossipsub): rpcHandler - rateLimit and punishInvalidMessage tests (#1467) 2025-06-24 14:43:44 +01:00
richΛrd
be1a2023ce fix: mark channel as reset to not read after closed (#1479) 2025-06-23 20:05:41 -04:00
Gabriel Cruz
021d0c1700 chore(acme): add ACMEClient to hold information about an ACME account (#1470) 2025-06-23 19:24:33 +00:00
Gabriel Cruz
f49cd377ce fix(peeridauth): fix peeridauth_integration import (#1478) 2025-06-23 15:13:22 +00:00
richΛrd
fc80840784 feat(kad-dht): handler (#1455) 2025-06-20 21:08:06 +00:00
richΛrd
7742d06a58 feat(kad-dht): routing table (#1454) 2025-06-20 16:47:48 -04:00
richΛrd
e0ea1d48a4 fix: make quic test optional (#1475) 2025-06-20 15:39:40 -04:00
richΛrd
f028ad8c12 fix: force close of streams instead of reset when closing connection (#1466) 2025-06-20 12:57:20 +00:00
richΛrd
9c153c822b chore(version): update libp2p.nimble to 1.11.0 (#1433) 2025-06-18 16:39:45 -04:00
Radosław Kamiński
d803352bd6 test(gossipsub): split unit and integration tests (#1465) 2025-06-16 15:18:18 +00:00
Radosław Kamiński
2eafac47e8 test(gossipsub): GossipThreshold and PublishThreshold tests (#1464) 2025-06-16 14:46:25 +00:00
vladopajic
848fdde0a8 feat(perf): add stats (#1452) 2025-06-13 10:16:45 +00:00
Gabriel Cruz
31e7dc68e2 chore(peeridauth): add mocked client (#1458) 2025-06-12 21:11:36 +00:00
Ivan FB
08299a2059 chore: Add some more context when an exception is caught (#1432)
Co-authored-by: richΛrd <info@richardramos.me>
2025-06-12 14:38:25 +00:00
Gabriel Cruz
2f3156eafb fix(daily): fix typo in testintegration (#1463) 2025-06-12 09:26:46 -03:00
Radosław Kamiński
72e85101b0 test(gossipsub): refactor and unify scoring tests (#1461) 2025-06-12 08:18:01 +00:00
Gabriel Cruz
d205260a3e chore(acme): add MockACMEApi for testing (#1457) 2025-06-11 18:59:29 +00:00
Radosław Kamiński
97e576d146 test: increase timeout (#1460) 2025-06-11 14:19:33 +00:00
richΛrd
888cb78331 feat(kad-dht): protobuffers (#1453) 2025-06-11 12:56:02 +00:00
richΛrd
1d4c261d2a feat: withWsTransport (#1398) 2025-06-10 22:32:55 +00:00
Gabriel Cruz
83de0c0abd feat(peeridauth): add peeridauth (#1445) 2025-06-10 10:25:34 -03:00
AkshayaMani
c501adc9ab feat(gossipsub): Add support for custom connection handling (Mix protocol integration) (#1420)
Co-authored-by: Ben-PH <benphawke@gmail.com>
2025-06-09 13:36:06 -04:00
Radosław Kamiński
f9fc24cc08 test(gossipsub): flaky tests (#1451) 2025-06-09 17:20:49 +01:00
richΛrd
cd26244ccc chore(quic): add libp2p_network_bytes metric (#1439)
Co-authored-by: vladopajic <vladopajic@users.noreply.github.com>
2025-06-09 09:42:52 -03:00
vladopajic
cabab6aafe chore(gossipsub): add consts (#1447)
Co-authored-by: Radoslaw Kaminski <radoslaw@status.im>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-06 14:33:38 +00:00
Radosław Kamiński
fb42a9b4aa test(gossipsub): parameters (#1442)
Co-authored-by: vladopajic <vladopajic@users.noreply.github.com>
2025-06-06 14:09:55 +00:00
Radosław Kamiński
141f4d9116 fix(GossipSub): save sent iHave in first element (#1405) 2025-06-06 10:27:59 +00:00
Gabriel Cruz
cb31152b53 feat(autotls): add acme client (#1436) 2025-06-05 17:47:02 +00:00
Radosław Kamiński
3a7745f920 test(gossipsub): message cache (#1431) 2025-06-03 15:18:29 +01:00
Radosław Kamiński
a89916fb1a test: checkUntilTimeout refactor (#1437) 2025-06-03 13:31:34 +01:00
vladopajic
c6cf46c904 fix(ci-daily): delete cache action will continue on error (#1435) 2025-06-02 17:08:31 +02:00
Gabriel Cruz
b28a71ab13 chore(readme): improve README's development section (#1427) 2025-05-29 17:51:29 +00:00
vladopajic
95b9859bcd chore(interop): move interop code to separate folder (#1413) 2025-05-29 16:14:12 +00:00
vladopajic
9e599753af ci(daily): add pinned dependencies variant (#1418) 2025-05-29 15:27:06 +00:00
richΛrd
2e924906bb chore: bump quic (#1428) 2025-05-29 14:25:02 +00:00
Radosław Kamiński
e811c1ad32 fix(gossipsub): save iDontWants messages in the first element of history (#1393) 2025-05-29 13:33:51 +01:00
Radosław Kamiński
86695b55bb test(gossipsub): include missing test files and handle flaky tests (#1416)
Co-authored-by: vladopajic <vladopajic@users.noreply.github.com>
2025-05-29 12:44:21 +01:00
vladopajic
8c3a4d882a ci(dependencies): fix access to tokens (#1421) 2025-05-29 00:27:36 +00:00
richΛrd
4bad343ddc fix: limit chronicles version to < 0.11.0 (#1423) 2025-05-28 21:00:41 -03:00
vladopajic
47b8a05c32 ci(daily): improvements (#1404) 2025-05-27 14:41:53 +00:00
Radosław Kamiński
4e6f4af601 test(gossipsub): heartbeat tests (#1391) 2025-05-27 10:28:12 +01:00
Miran
7275f6f9c3 chore: unused imports are now errors (#1399) 2025-05-26 21:36:08 +02:00
richΛrd
c3dae6a7d4 fix(quic): reset and mm for interop tests (#1397) 2025-05-26 12:16:17 -04:00
vladopajic
bb404eda4a fix(ci-daily): remove --solver flag (#1400) 2025-05-26 16:48:51 +02:00
richΛrd
584710bd80 chore: move -d:libp2p_quic_support flag to .nimble (#1392) 2025-05-26 08:57:26 -04:00
Radosław Kamiński
ad5eae9adf test(gossipsub): move and refactor control messages tests (#1380) 2025-05-22 15:10:37 +00:00
richΛrd
26fae7cd2d chore: bump quic (#1387) 2025-05-21 22:30:35 +00:00
Miran
87d6655368 chore: update more dependencies (#1374) 2025-05-21 21:46:09 +00:00
282 changed files with 22996 additions and 7352 deletions

34
.github/actions/add_comment/action.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Add Comment
description: "Add or update comment in the PR"
runs:
using: "composite"
steps:
- name: Add/Update Comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const marker = "${{ env.MARKER }}";
const body = fs.readFileSync("${{ env.COMMENT_SUMMARY_PATH }}", 'utf8');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body && c.body.startsWith(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}

View File

@@ -0,0 +1,49 @@
name: Discord Failure Notification
description: "Send Discord notification when CI jobs fail"
inputs:
webhook_url:
description: "Discord webhook URL"
required: true
workflow_name:
description: "Name of the workflow that failed"
required: false
default: ${{ github.workflow }}
branch:
description: "Branch name"
required: false
default: ${{ github.ref_name }}
repository:
description: "Repository name"
required: false
default: ${{ github.repository }}
run_id:
description: "GitHub run ID"
required: false
default: ${{ github.run_id }}
server_url:
description: "GitHub server URL"
required: false
default: ${{ github.server_url }}
runs:
using: "composite"
steps:
- name: Send Discord notification
shell: bash
run: |
curl -H "Content-Type: application/json" \
-X POST \
-d "{
\"embeds\": [{
\"title\": \"${{ inputs.workflow_name }} Job Failed\",
\"url\": \"${{ inputs.server_url }}/${{ inputs.repository }}/actions/runs/${{ inputs.run_id }}\",
\"description\": \"The workflow has failed on branch \`${{ inputs.branch }}\`\",
\"color\": 15158332,
\"fields\": [
{\"name\": \"Repository\", \"value\": \"${{ inputs.repository }}\", \"inline\": true},
{\"name\": \"Branch\", \"value\": \"${{ inputs.branch }}\", \"inline\": true}
],
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"
}]
}" \
"${{ inputs.webhook_url }}"

View File

@@ -0,0 +1,24 @@
name: Generate Plots
description: "Set up Python and run script to generate plots with Docker Stats"
runs:
using: "composite"
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Python dependencies
shell: bash
run: |
python -m pip install --upgrade pip
pip install matplotlib
- name: Plot Docker Stats
shell: bash
run: python performance/scripts/plot_docker_stats.py
- name: Plot Latency History
shell: bash
run: python performance/scripts/plot_latency_history.py

View File

@@ -8,7 +8,7 @@ inputs:
default: "amd64"
nim_ref:
description: "Nim version"
default: "version-1-6"
default: "version-2-0"
shell:
description: "Shell to run commands in"
default: "bash --noprofile --norc -e -o pipefail"

View File

@@ -0,0 +1,21 @@
name: Process Stats
description: "Set up Nim and run scripts to aggregate latency and process raw docker stats"
runs:
using: "composite"
steps:
- name: Set up Nim
uses: jiro4989/setup-nim-action@v2
with:
nim-version: "2.x"
repo-token: ${{ env.GITHUB_TOKEN }}
- name: Aggregate latency stats and prepare markdown for comment and summary
shell: bash
run: |
nim c -r -d:release -o:/tmp/process_latency_stats ./performance/scripts/process_latency_stats.nim
- name: Process raw docker stats to csv files
shell: bash
run: |
nim c -r -d:release -o:/tmp/process_docker_stats ./performance/scripts/process_docker_stats.nim

View File

@@ -0,0 +1,36 @@
name: Publish Latency History
description: "Publish latency history CSVs in a configurable branch and folder"
runs:
using: "composite"
steps:
- name: Clone the branch
uses: actions/checkout@v4
with:
repository: ${{ github.repository }}
ref: ${{ env.PUBLISH_BRANCH_NAME }}
path: ${{ env.CHECKOUT_SUBFOLDER_HISTORY }}
fetch-depth: 0
- name: Commit & push latency history CSVs
shell: bash
run: |
cd "$CHECKOUT_SUBFOLDER_HISTORY"
git fetch origin "$PUBLISH_BRANCH_NAME"
git reset --hard "origin/$PUBLISH_BRANCH_NAME"
mkdir -p "$PUBLISH_DIR_LATENCY_HISTORY"
cp ../$SHARED_VOLUME_PATH/$LATENCY_HISTORY_PREFIX*.csv "$PUBLISH_DIR_LATENCY_HISTORY/"
git add "$PUBLISH_DIR_LATENCY_HISTORY"
if git diff-index --quiet HEAD --; then
echo "No changes to commit"
else
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git commit -m "Update latency history CSVs"
git push origin "$PUBLISH_BRANCH_NAME"
fi
cd ..

View File

@@ -0,0 +1,56 @@
name: Publish Plots
description: "Publish plots in performance_plots branch and add to the workflow summary"
runs:
using: "composite"
steps:
- name: Clone the performance_plots branch
uses: actions/checkout@v4
with:
repository: ${{ github.repository }}
ref: ${{ env.PUBLISH_BRANCH_NAME }}
path: ${{ env.CHECKOUT_SUBFOLDER_SUBPLOTS }}
fetch-depth: 0
- name: Commit & push plots
shell: bash
run: |
cd $CHECKOUT_SUBFOLDER_SUBPLOTS
git fetch origin "$PUBLISH_BRANCH_NAME"
git reset --hard "origin/$PUBLISH_BRANCH_NAME"
# Remove any branch folder older than 7 days
DAYS=7
cutoff=$(( $(date +%s) - DAYS*24*3600 ))
scan_dir="${PUBLISH_DIR_PLOTS%/}"
find "$scan_dir" -mindepth 1 -maxdepth 1 -type d -print0 \
| while IFS= read -r -d $'\0' d; do \
ts=$(git log -1 --format=%ct -- "$d" 2>/dev/null || true); \
if [ -n "$ts" ] && [ "$ts" -le "$cutoff" ]; then \
echo "[cleanup] Deleting: $d"; \
rm -rf -- "$d"; \
fi; \
done
rm -rf $PUBLISH_DIR_PLOTS/$BRANCH_NAME
mkdir -p $PUBLISH_DIR_PLOTS/$BRANCH_NAME
cp ../$SHARED_VOLUME_PATH/*.png $PUBLISH_DIR_PLOTS/$BRANCH_NAME/ 2>/dev/null || true
cp ../$LATENCY_HISTORY_PATH/*.png $PUBLISH_DIR_PLOTS/ 2>/dev/null || true
git add -A "$PUBLISH_DIR_PLOTS/"
git status
if git diff-index --quiet HEAD --; then
echo "No changes to commit"
else
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git commit -m "Update performance plots for $BRANCH_NAME"
git push origin $PUBLISH_BRANCH_NAME
fi
- name: Add plots to GitHub Actions summary
shell: bash
run: |
nim c -r -d:release -o:/tmp/add_plots_to_summary ./performance/scripts/add_plots_to_summary.nim

View File

@@ -25,15 +25,11 @@ jobs:
cpu: i386
- os: linux-gcc-14
cpu: amd64
- os: macos
cpu: amd64
- os: macos-14
cpu: arm64
- os: windows
cpu: amd64
nim:
- ref: version-1-6
memory_management: refc
- ref: version-2-0
memory_management: refc
- ref: version-2-2
@@ -47,10 +43,6 @@ jobs:
os: linux-gcc-14
builder: ubuntu-24.04
shell: bash
- platform:
os: macos
builder: macos-13
shell: bash
- platform:
os: macos-14
builder: macos-14
@@ -80,15 +72,6 @@ jobs:
shell: ${{ matrix.shell }}
nim_ref: ${{ matrix.nim.ref }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '~1.16.0' # That's the minimum Go version that works with arm.
- name: Install p2pd
run: |
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
- name: Restore deps from cache
id: deps-cache
uses: actions/cache@v3
@@ -118,5 +101,5 @@ jobs:
nimble --version
gcc --version
export NIMFLAGS="${NIMFLAGS} -d:libp2p_quic_support --mm:${{ matrix.nim.memory_management }}"
export NIMFLAGS="${NIMFLAGS} --mm:${{ matrix.nim.memory_management }}"
nimble test

View File

@@ -51,7 +51,7 @@ jobs:
- name: Run test suite with coverage flags
run: |
export NIMFLAGS="-d:libp2p_quic_support --lineDir:on --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage"
export NIMFLAGS="--lineDir:on --passC:-fprofile-arcs --passC:-ftest-coverage --passL:-fprofile-arcs --passL:-ftest-coverage"
nimble testnative
nimble testpubsub
nimble testfilter

View File

@@ -6,9 +6,37 @@ on:
workflow_dispatch:
jobs:
test_amd64:
name: Daily amd64
test_amd64_latest:
name: Daily test amd64 (latest dependencies)
uses: ./.github/workflows/daily_common.yml
with:
nim: "[{'ref': 'version-1-6', 'memory_management': 'refc'}, {'ref': 'version-2-0', 'memory_management': 'refc'}]"
nim: "[
{'ref': 'version-2-0', 'memory_management': 'refc'},
{'ref': 'version-2-2', 'memory_management': 'refc'},
{'ref': 'devel', 'memory_management': 'refc'},
]"
cpu: "['amd64']"
test_amd64_pinned:
name: Daily test amd64 (pinned dependencies)
uses: ./.github/workflows/daily_common.yml
with:
pinned_deps: true
nim: "[
{'ref': 'version-2-0', 'memory_management': 'refc'},
{'ref': 'version-2-2', 'memory_management': 'refc'},
{'ref': 'devel', 'memory_management': 'refc'},
]"
cpu: "['amd64']"
notify-on-failure:
name: Notify Discord on Failure
needs: [test_amd64_latest, test_amd64_pinned]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
uses: ./.github/actions/discord_notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}

View File

@@ -4,6 +4,11 @@ name: Daily Common
on:
workflow_call:
inputs:
pinned_deps:
description: 'Should dependencies be installed from pinned file or use latest versions'
required: false
type: boolean
default: false
nim:
description: 'Nim Configuration'
required: true
@@ -17,20 +22,12 @@ on:
required: false
type: string
default: "[]"
use_sat_solver:
description: 'Install dependencies with SAT Solver'
required: false
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
delete_cache:
name: Delete github action's branch cache
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: snnaplab/delete-branch-cache-action@v1
@@ -72,17 +69,13 @@ jobs:
nim_ref: ${{ matrix.nim.ref }}
cpu: ${{ matrix.cpu }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '~1.16.0'
cache: false
- name: Install p2pd
- name: Install dependencies (pinned)
if: ${{ inputs.pinned_deps }}
run: |
V=1 bash scripts/build_p2pd.sh p2pdCache 124530a3
- name: Install dependencies
nimble install_pinned
- name: Install dependencies (latest)
if: ${{ inputs.pinned_deps == false }}
run: |
nimble install -y --depsOnly
@@ -91,11 +84,14 @@ jobs:
nim --version
nimble --version
if [[ "${{ inputs.use_sat_solver }}" == "true" ]]; then
dependency_solver="sat"
else
dependency_solver="legacy"
fi
export NIMFLAGS="${NIMFLAGS} -d:libp2p_quic_support --mm:${{ matrix.nim.memory_management }} --solver:${dependency_solver}"
export NIMFLAGS="${NIMFLAGS} --mm:${{ matrix.nim.memory_management }}"
nimble test
- name: Run integration tests
if: ${{ matrix.platform.os == 'linux' && matrix.cpu == 'amd64' }}
run: |
nim --version
nimble --version
export NIMFLAGS="${NIMFLAGS} --mm:${{ matrix.nim.memory_management }}"
nimble testintegration

View File

@@ -1,14 +0,0 @@
name: Daily Nim Devel
on:
schedule:
- cron: "30 6 * * *"
workflow_dispatch:
jobs:
test_nim_devel:
name: Daily Nim Devel
uses: ./.github/workflows/daily_common.yml
with:
nim: "[{'ref': 'devel', 'memory_management': 'orc'}]"
cpu: "['amd64']"

View File

@@ -6,10 +6,45 @@ on:
workflow_dispatch:
jobs:
test_i386:
name: Daily i386 (Linux)
test_i386_latest:
name: Daily i386 (latest dependencies)
uses: ./.github/workflows/daily_common.yml
with:
nim: "[{'ref': 'version-1-6', 'memory_management': 'refc'}, {'ref': 'version-2-0', 'memory_management': 'refc'}, {'ref': 'devel', 'memory_management': 'orc'}]"
nim: "[
{'ref': 'version-2-0', 'memory_management': 'refc'},
{'ref': 'version-2-2', 'memory_management': 'refc'},
{'ref': 'devel', 'memory_management': 'refc'},
]"
cpu: "['i386']"
exclude: "[{'platform': {'os':'macos'}}, {'platform': {'os':'windows'}}]"
exclude: "[
{'platform': {'os':'macos'}},
{'platform': {'os':'windows'}},
]"
test_i386_pinned:
name: Daily i386 (pinned dependencies)
uses: ./.github/workflows/daily_common.yml
with:
pinned_deps: true
nim: "[
{'ref': 'version-2-0', 'memory_management': 'refc'},
{'ref': 'version-2-2', 'memory_management': 'refc'},
{'ref': 'devel', 'memory_management': 'refc'},
]"
cpu: "['i386']"
exclude: "[
{'platform': {'os':'macos'}},
{'platform': {'os':'windows'}},
]"
notify-on-failure:
name: Notify Discord on Failure
needs: [test_i386_latest, test_i386_pinned]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
uses: ./.github/actions/discord_notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}

39
.github/workflows/daily_nimbus.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Daily Nimbus
on:
schedule:
- cron: "30 6 * * *"
workflow_dispatch:
jobs:
compile_nimbus:
timeout-minutes: 80
name: 'Compile Nimbus (linux-amd64)'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Compile nimbus using nim-libp2p
run: |
git clone --branch unstable --single-branch https://github.com/status-im/nimbus-eth2.git
cd nimbus-eth2
git submodule set-branch --branch ${{ github.sha }} vendor/nim-libp2p
make -j"$(nproc)"
make -j"$(nproc)" nimbus_beacon_node
notify-on-failure:
name: Notify Discord on Failure
needs: compile_nimbus
if: failure()
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
uses: ./.github/actions/discord_notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}

View File

@@ -1,15 +0,0 @@
name: Daily SAT
on:
schedule:
- cron: "30 6 * * *"
workflow_dispatch:
jobs:
test_amd64:
name: Daily SAT
uses: ./.github/workflows/daily_common.yml
with:
nim: "[{'ref': 'version-2-0', 'memory_management': 'refc'}]"
cpu: "['amd64']"
use_sat_solver: true

View File

@@ -17,13 +17,13 @@ jobs:
target:
- repository: status-im/nimbus-eth2
ref: unstable
token: ${{ secrets.ACTIONS_GITHUB_TOKEN_NIMBUS_ETH2 }}
secret: ACTIONS_GITHUB_TOKEN_NIMBUS_ETH2
- repository: waku-org/nwaku
ref: master
token: ${{ secrets.ACTIONS_GITHUB_TOKEN_NWAKU }}
secret: ACTIONS_GITHUB_TOKEN_NWAKU
- repository: codex-storage/nim-codex
ref: master
token: ${{ secrets.ACTIONS_GITHUB_TOKEN_NIM_CODEX }}
secret: ACTIONS_GITHUB_TOKEN_NIM_CODEX
steps:
- name: Clone target repository
uses: actions/checkout@v4
@@ -32,7 +32,7 @@ jobs:
ref: ${{ matrix.target.ref}}
path: nbc
fetch-depth: 0
token: ${{ matrix.target.token }}
token: ${{ secrets[matrix.target.secret] }}
- name: Checkout this ref in target repository
run: |
@@ -50,4 +50,16 @@ jobs:
git branch -D nim-libp2p-auto-bump-${{ matrix.target.ref }} || true
git switch -c nim-libp2p-auto-bump-${{ matrix.target.ref }}
git push -f origin nim-libp2p-auto-bump-${{ matrix.target.ref }}
notify-on-failure:
name: Notify Discord on Failure
needs: [bumper]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
uses: ./.github/actions/discord_notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}

View File

@@ -21,7 +21,7 @@ jobs:
- uses: jiro4989/setup-nim-action@v1
with:
nim-version: '1.6.x'
nim-version: '2.2.x'
- name: Generate doc
run: |

View File

@@ -36,7 +36,7 @@ jobs:
shell: bash
os: linux
cpu: amd64
nim_ref: version-1-6
nim_ref: version-2-2
- name: Restore deps from cache
id: deps-cache

View File

@@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build image
run: docker buildx build --load -t nim-libp2p-head -f tests/transport-interop/Dockerfile .
run: docker buildx build --load -t nim-libp2p-head -f interop/transport/Dockerfile .
- name: Run tests
uses: libp2p/test-plans/.github/actions/run-transport-interop-test@master
with:
@@ -35,26 +35,55 @@ jobs:
# without suffix action fails because "hole-punching-interop" artifacts have
# the same name as "transport-interop" artifacts
test-results-suffix: transport-interop
extra-versions: ${{ github.workspace }}/tests/transport-interop/version.json
extra-versions: ${{ github.workspace }}/interop/transport/version.json
s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }}
s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }}
s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }}
run-hole-punching-interop:
name: Run hole-punching interoperability tests
# nim-libp2p#1367: hole punching tests are temporary disabled as they keep failing
# and issue does not seem to be on nim-libp2p side
# run-hole-punching-interop:
# name: Run hole-punching interoperability tests
# runs-on: ubuntu-22.04
# steps:
# - uses: actions/checkout@v4
# - uses: docker/setup-buildx-action@v3
# - name: Build image
# run: docker buildx build --load -t nim-libp2p-head -f interop/hole-punching/Dockerfile .
# - name: Run tests
# uses: libp2p/test-plans/.github/actions/run-interop-hole-punch-test@master
# with:
# test-filter: nim-libp2p-head
# extra-versions: ${{ github.workspace }}/interop/hole-punching/version.json
# s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }}
# s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }}
# s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }}
# aws-region: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }}
run-autonatv2-interop:
name: Run AutoNATv2 interoperability tests
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build image
run: docker buildx build --load -t nim-libp2p-head -f tests/hole-punching-interop/Dockerfile .
- name: Run tests
uses: libp2p/test-plans/.github/actions/run-interop-hole-punch-test@master
- name: Set up Go
uses: actions/setup-go@v5
with:
test-filter: nim-libp2p-head
extra-versions: ${{ github.workspace }}/tests/hole-punching-interop/version.json
s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }}
s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }}
s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }}
go-version: "1.25"
- name: Set up Nim
uses: jiro4989/setup-nim-action@v1
with:
nim-version: "stable"
- name: Run Go and Nim together
run: |
nimble install
cd interop/autonatv2/go-peer
git clone https://github.com/libp2p/go-libp2p
cd go-libp2p
git apply ../disable-filtering-of-private-ip-addresses.patch
cd ..
go run testautonatv2.go &
cd ../nim-peer
nim r src/nim_peer.nim $(cat ../go-peer/peer.id)

View File

@@ -22,6 +22,6 @@ jobs:
uses: arnetheduck/nph-action@v1
with:
version: 0.6.1
options: "examples libp2p tests tools *.nim*"
options: "examples libp2p tests interop tools *.nim*"
fail: true
suggest: true

94
.github/workflows/performance.yml vendored Normal file
View File

@@ -0,0 +1,94 @@
name: Performance
on:
push:
branches:
- master
pull_request:
merge_group:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
performance:
timeout-minutes: 20
strategy:
fail-fast: false
defaults:
run:
shell: bash
env:
VACP2P: "vacp2p"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.number }}
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
MARKER: "<!-- perf-summary-marker -->"
COMMENT_SUMMARY_PATH: "/tmp/perf-summary.md"
SHARED_VOLUME_PATH: "performance/output"
DOCKER_STATS_PREFIX: "docker_stats_"
PUBLISH_BRANCH_NAME: "performance_plots"
CHECKOUT_SUBFOLDER_SUBPLOTS: "subplots"
PUBLISH_DIR_PLOTS: "plots"
CHECKOUT_SUBFOLDER_HISTORY: "history"
PUBLISH_DIR_LATENCY_HISTORY: "latency_history"
LATENCY_HISTORY_PATH: "history/latency_history"
LATENCY_HISTORY_PREFIX: "pr"
LATENCY_HISTORY_PLOT_FILENAME: "latency_history_all_scenarios.png"
name: "Performance"
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker Image with cache
uses: docker/build-push-action@v6
with:
context: .
file: performance/Dockerfile
tags: test-node:latest
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run
run: |
./performance/runner.sh
- name: Process latency and docker stats
uses: ./.github/actions/process_stats
- name: Publish history
if: github.repository_owner == env.VACP2P
uses: ./.github/actions/publish_history
- name: Generate plots
if: github.repository_owner == env.VACP2P
uses: ./.github/actions/generate_plots
- name: Post/Update PR comment
if: github.event_name == 'pull_request'
uses: ./.github/actions/add_comment
- name: Upload performance artifacts
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: performance-artifacts
path: |
performance/output/pr*_latency.csv
performance/output/*.png
history/latency_history/*.png
if-no-files-found: ignore
retention-days: 7

10
.pinned
View File

@@ -1,6 +1,6 @@
bearssl;https://github.com/status-im/nim-bearssl@#34d712933a4e0f91f5e66bc848594a581504a215
chronicles;https://github.com/status-im/nim-chronicles@#32ac8679680ea699f7dbc046e8e0131cac97d41a
chronos;https://github.com/status-im/nim-chronos@#c04576d829b8a0a1b12baaa8bc92037501b3a4a0
chronicles;https://github.com/status-im/nim-chronicles@#61759a5e8df8f4d68bcd1b4b8c1adab3e72bbd8d
chronos;https://github.com/status-im/nim-chronos@#b55e2816eb45f698ddaca8d8473e401502562db2
dnsclient;https://github.com/ba0f3/dnsclient.nim@#23214235d4784d24aceed99bbfe153379ea557c8
faststreams;https://github.com/status-im/nim-faststreams@#c51315d0ae5eb2594d0bf41181d0e1aca1b3c01d
httputils;https://github.com/status-im/nim-http-utils@#79cbab1460f4c0cdde2084589d017c43a3d7b4f1
@@ -8,12 +8,14 @@ json_serialization;https://github.com/status-im/nim-json-serialization@#2b1c5eb1
metrics;https://github.com/status-im/nim-metrics@#6142e433fc8ea9b73379770a788017ac528d46ff
ngtcp2;https://github.com/status-im/nim-ngtcp2@#9456daa178c655bccd4a3c78ad3b8cce1f0add73
nimcrypto;https://github.com/cheatfate/nimcrypto@#19c41d6be4c00b4a2c8000583bd30cf8ceb5f4b1
quic;https://github.com/status-im/nim-quic.git@#a6c30263c95fc5ddb2ef4d197c09b282555c06b0
quic;https://github.com/vacp2p/nim-quic@#9370190ded18d78a5a9990f57aa8cbbf947f3891
results;https://github.com/arnetheduck/nim-results@#df8113dda4c2d74d460a8fa98252b0b771bf1f27
secp256k1;https://github.com/status-im/nim-secp256k1@#f808ed5e7a7bfc42204ec7830f14b7a42b63c284
serialization;https://github.com/status-im/nim-serialization@#548d0adc9797a10b2db7f788b804330306293088
stew;https://github.com/status-im/nim-stew@#0db179256cf98eb9ce9ee7b9bc939f219e621f77
testutils;https://github.com/status-im/nim-testutils@#9e842bd58420d23044bc55e16088e8abbe93ce51
unittest2;https://github.com/status-im/nim-unittest2@#8b51e99b4a57fcfb31689230e75595f024543024
websock;https://github.com/status-im/nim-websock@#f8ed9b40a5ff27ad02a3c237c4905b0924e3f982
websock;https://github.com/status-im/nim-websock@#d5cd89062cd2d168ef35193c7d29d2102921d97e
zlib;https://github.com/status-im/nim-zlib@#daa8723fd32299d4ca621c837430c29a5a11e19a
jwt;https://github.com/vacp2p/nim-jwt@#18f8378de52b241f321c1f9ea905456e89b95c6f
bearssl_pkey_decoder;https://github.com/vacp2p/bearssl_pkey_decoder@#21dd3710df9345ed2ad8bf8f882761e07863b8e0

176
README.md
View File

@@ -20,14 +20,13 @@
- [Background](#background)
- [Install](#install)
- [Getting Started](#getting-started)
- [Testing](#testing)
- [Modules](#modules)
- [Users](#users)
- [Stability](#stability)
- [Development](#development)
- [Contribute](#contribute)
- [Contributors](#contributors)
- [Core Maintainers](#core-maintainers)
- [Modules](#modules)
- [Users](#users)
- [Stability](#stability)
- [License](#license)
## Background
@@ -39,20 +38,106 @@ This is a native Nim implementation, using [chronos](https://github.com/status-i
Learn more about libp2p at [**libp2p.io**](https://libp2p.io) and follow libp2p's documentation [**docs.libp2p.io**](https://docs.libp2p.io).
## Install
**Prerequisite**
- [Nim](https://nim-lang.org/install.html)
> The currently supported Nim versions are 1.6, 2.0 and 2.2.
> The currently supported Nim versions are 2.0 and 2.2.
```
nimble install libp2p
```
## Getting Started
You'll find the nim-libp2p documentation [here](https://vacp2p.github.io/nim-libp2p/docs/). See [examples](./examples) for simple usage patterns.
## Getting Started
Try out the chat example. Full code can be found [here](https://github.com/vacp2p/nim-libp2p/blob/master/examples/directchat.nim):
```bash
nim c -r --threads:on examples/directchat.nim
```
This will output a peer ID such as `QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu` which you can use in another instance to connect to it.
```bash
./examples/directchat
/connect QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu # change this hash by the hash you were given
```
You can now chat between the instances!
![Chat example](https://imgur.com/caYRu8K.gif)
## Development
Clone the repository and install the dependencies:
```sh
git clone https://github.com/vacp2p/nim-libp2p
cd nim-libp2p
nimble install -dy
```
You can use `nix develop` to start a shell with Nim and Nimble.
nimble 0.20.1 is required for running `testnative`. At time of writing, this is not available in nixpkgs: If using `nix develop`, follow up with `nimble install nimble`, and use that (typically `~/.nimble/bin/nimble`).
### Testing
Remember you'll need to build the `go-libp2p-daemon` binary to run the `nim-libp2p` tests.
To do so, please follow the installation instructions in [daemonapi.md](examples/go-daemon/daemonapi.md).
Run unit tests:
```sh
# run all the unit tests
nimble test
```
For a list of all available test suites, use:
```
nimble tasks
```
### Contribute
The libp2p implementation in Nim is a work in progress. We welcome contributors to help out! Specifically, you can:
- Go through the modules and **check out existing issues**. This would be especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it.
- **Perform code reviews**. Feel free to let us know if you found anything that can a) speed up the project development b) ensure better quality and c) reduce possible future bugs.
- **Add tests**. Help nim-libp2p to be more robust by adding more tests to the [tests folder](tests/).
- **Small PRs**. Try to keep PRs atomic and digestible. This makes the review process and pinpointing bugs easier.
- **Code format**. Code should be formatted with [nph](https://github.com/arnetheduck/nph) and follow the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
- **Join the Conversation**. Connect with other contributors in our [community channel](https://discord.com/channels/1204447718093750272/1351621032263417946). Ask questions, share ideas, get support, and stay informed about the latest updates from the maintainers.
### Contributors
<a href="https://github.com/vacp2p/nim-libp2p/graphs/contributors"><img src="https://contrib.rocks/image?repo=vacp2p/nim-libp2p" alt="nim-libp2p contributors"></a>
### Core Maintainers
<table>
<tbody>
<tr>
<td align="center"><a href="https://github.com/richard-ramos"><img src="https://avatars.githubusercontent.com/u/1106587?v=4?s=100" width="100px;" alt="Richard"/><br /><sub><b>Richard</b></sub></a></td>
<td align="center"><a href="https://github.com/vladopajic"><img src="https://avatars.githubusercontent.com/u/4353513?v=4?s=100" width="100px;" alt="Vlado"/><br /><sub><b>Vlado</b></sub></a></td>
<td align="center"><a href="https://github.com/gmelodie"><img src="https://avatars.githubusercontent.com/u/8129788?v=4?s=100" width="100px;" alt="Gabe"/><br /><sub><b>Gabe</b></sub></a></td>
</tr>
</tbody>
</table>
### Compile time flags
Enable quic transport support
```bash
nim c -d:libp2p_quic_support some_file.nim
```
Enable autotls support
```bash
nim c -d:libp2p_autotls_support some_file.nim
```
Enable expensive metrics (ie, metrics with per-peer cardinality):
```bash
nim c -d:libp2p_expensive_metrics some_file.nim
```
Set list of known libp2p agents for metrics:
```bash
nim c -d:libp2p_agents_metrics -d:KnownLibP2PAgents=nimbus,lighthouse,lodestar,prysm,teku some_file.nim
```
Specify gossipsub specific topics to measure in the metrics:
```bash
nim c -d:KnownLibP2PTopics=topic1,topic2,topic3 some_file.nim
```
## Modules
List of packages modules implemented in nim-libp2p:
@@ -64,8 +149,6 @@ List of packages modules implemented in nim-libp2p:
| [connmanager](libp2p/connmanager.nim) | Connection manager |
| [identify / push identify](libp2p/protocols/identify.nim) | [Identify](https://docs.libp2p.io/concepts/fundamentals/protocols/#identify) protocol |
| [ping](libp2p/protocols/ping.nim) | [Ping](https://docs.libp2p.io/concepts/fundamentals/protocols/#ping) protocol |
| [libp2p-daemon-client](libp2p/daemon/daemonapi.nim) | [go-daemon](https://github.com/libp2p/go-libp2p-daemon) nim wrapper |
| [interop-libp2p](tests/testinterop.nim) | Interop tests |
| **Transports** | |
| [libp2p-tcp](libp2p/transports/tcptransport.nim) | TCP transport |
| [libp2p-ws](libp2p/transports/wstransport.nim) | WebSocket & WebSocket Secure transport |
@@ -109,72 +192,7 @@ The versioning follows [semver](https://semver.org/), with some additions:
- Some of libp2p procedures are marked as `.public.`, they will remain compatible during each `MAJOR` version
- The rest of the procedures are considered internal, and can change at any `MINOR` version (but remain compatible for each new `PATCH`)
We aim to be compatible at all time with at least 2 Nim `MINOR` versions, currently `1.6 & 2.0`
## Development
Clone and Install dependencies:
```sh
git clone https://github.com/vacp2p/nim-libp2p
cd nim-libp2p
# to use dependencies computed by nimble
nimble install -dy
# OR to install the dependencies versions used in CI
nimble install_pinned
```
Run unit tests:
```sh
# run all the unit tests
nimble test
```
This requires the go daemon to be available. To only run native tests, use `nimble testnative`.
Or use `nimble tasks` to show all available tasks.
### Contribute
The libp2p implementation in Nim is a work in progress. We welcome contributors to help out! Specifically, you can:
- Go through the modules and **check out existing issues**. This would be especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it.
- **Perform code reviews**. Feel free to let us know if you found anything that can a) speed up the project development b) ensure better quality and c) reduce possible future bugs.
- **Add tests**. Help nim-libp2p to be more robust by adding more tests to the [tests folder](tests/).
- **Small PRs**. Try to keep PRs atomic and digestible. This makes the review process and pinpointing bugs easier.
- **Code format**. Please format code using [nph](https://github.com/arnetheduck/nph) v0.5.1. This will ensure a consistent codebase and make PRs easier to review. A CI rule has been added to ensure that future commits are all formatted using the same nph version.
The code follows the [Status Nim Style Guide](https://status-im.github.io/nim-style-guide/).
### Contributors
<a href="https://github.com/vacp2p/nim-libp2p/graphs/contributors"><img src="https://contrib.rocks/image?repo=vacp2p/nim-libp2p" alt="nim-libp2p contributors"></a>
### Core Maintainers
<table>
<tbody>
<tr>
<td align="center"><a href="https://github.com/richard-ramos"><img src="https://avatars.githubusercontent.com/u/1106587?v=4?s=100" width="100px;" alt="Richard"/><br /><sub><b>Richard</b></sub></a></td>
<td align="center"><a href="https://github.com/vladopajic"><img src="https://avatars.githubusercontent.com/u/4353513?v=4?s=100" width="100px;" alt="Vlado"/><br /><sub><b>Vlado</b></sub></a></td>
<td align="center"><a href="https://github.com/gmelodie"><img src="https://avatars.githubusercontent.com/u/8129788?v=4?s=100" width="100px;" alt="Gabe"/><br /><sub><b>Gabe</b></sub></a></td>
</tr>
</tbody>
</table>
### Compile time flags
Enable quic transport support
```bash
nim c -d:libp2p_quic_support some_file.nim
```
Enable expensive metrics (ie, metrics with per-peer cardinality):
```bash
nim c -d:libp2p_expensive_metrics some_file.nim
```
Set list of known libp2p agents for metrics:
```bash
nim c -d:libp2p_agents_metrics -d:KnownLibP2PAgents=nimbus,lighthouse,lodestar,prysm,teku some_file.nim
```
Specify gossipsub specific topics to measure in the metrics:
```bash
nim c -d:KnownLibP2PTopics=topic1,topic2,topic3 some_file.nim
```
We aim to be compatible at all time with at least 2 Nim `MINOR` versions, currently `2.0 & 2.2`
## License

View File

@@ -4,12 +4,14 @@ if dirExists("nimbledeps/pkgs"):
if dirExists("nimbledeps/pkgs2"):
switch("NimblePath", "nimbledeps/pkgs2")
switch("warningAsError", "UnusedImport:on")
switch("warningAsError", "UseBase:on")
switch("warning", "CaseTransition:off")
switch("warning", "ObservableStores:off")
switch("warning", "LockLevel:off")
--styleCheck:
usages
switch("warningAsError", "UseBase:on")
--styleCheck:
error
--mm:
@@ -22,7 +24,7 @@ if defined(windows) and not defined(vcc):
--define:
nimRawSetjmp
# begin Nimble config (version 1)
when fileExists("nimble.paths"):
# begin Nimble config (version 2)
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config

View File

@@ -1,5 +1,5 @@
# nim-libp2p examples
In this folder, you'll find the sources of the [nim-libp2p website](https://status-im.github.io/nim-libp2p/docs/)
In this folder, you'll find the sources of the [nim-libp2p website](https://vacp2p.github.io/nim-libp2p/docs/)
We recommand to follow the tutorials on the website, but feel free to grok the sources here!

View File

@@ -1,3 +1,4 @@
{.used.}
## # Circuit Relay example
##
## Circuit Relay can be used when a node cannot reach another node

View File

@@ -1,3 +1,4 @@
{.used.}
when not (compileOption("threads")):
{.fatal: "Please, compile this program with the --threads:on option!".}

View File

@@ -1,54 +0,0 @@
import chronos, nimcrypto, strutils
import ../../libp2p/daemon/daemonapi
import ../hexdump
const PubSubTopic = "test-net"
proc dumpSubscribedPeers(api: DaemonAPI) {.async.} =
var peers = await api.pubsubListPeers(PubSubTopic)
echo "= List of connected and subscribed peers:"
for item in peers:
echo item.pretty()
proc dumpAllPeers(api: DaemonAPI) {.async.} =
var peers = await api.listPeers()
echo "Current connected peers count = ", len(peers)
for item in peers:
echo item.peer.pretty()
proc monitor(api: DaemonAPI) {.async.} =
while true:
echo "Dumping all peers"
await dumpAllPeers(api)
await sleepAsync(5000)
proc main() {.async.} =
echo "= Starting P2P bootnode"
var api = await newDaemonApi({DHTFull, PSGossipSub})
var id = await api.identity()
echo "= P2P bootnode ", id.peer.pretty(), " started."
let mcip4 = multiCodec("ip4")
let mcip6 = multiCodec("ip6")
echo "= You can use one of this addresses to bootstrap your nodes:"
for item in id.addresses:
if item.protoCode() == mcip4 or item.protoCode() == mcip6:
echo $item & "/ipfs/" & id.peer.pretty()
asyncSpawn monitor(api)
proc pubsubLogger(
api: DaemonAPI, ticket: PubsubTicket, message: PubSubMessage
): Future[bool] {.async.} =
let msglen = len(message.data)
echo "= Recieved pubsub message with length ",
msglen, " bytes from peer ", message.peer.pretty()
echo dumpHex(message.data)
await api.dumpSubscribedPeers()
result = true
var ticket = await api.pubsubSubscribe(PubSubTopic, pubsubLogger)
when isMainModule:
waitFor(main())
while true:
poll()

View File

@@ -1,132 +0,0 @@
import chronos, nimcrypto, strutils
import ../../libp2p/daemon/daemonapi
## nim c -r --threads:on chat.nim
when not (compileOption("threads")):
{.fatal: "Please, compile this program with the --threads:on option!".}
const ServerProtocols = @["/test-chat-stream"]
type CustomData = ref object
api: DaemonAPI
remotes: seq[StreamTransport]
consoleFd: AsyncFD
serveFut: Future[void]
proc threadMain(wfd: AsyncFD) {.thread.} =
## This procedure performs reading from `stdin` and sends data over
## pipe to main thread.
var transp = fromPipe(wfd)
while true:
var line = stdin.readLine()
let res = waitFor transp.write(line & "\r\n")
proc serveThread(udata: CustomData) {.async.} =
## This procedure perform reading on pipe and sends data to remote clients.
var transp = fromPipe(udata.consoleFd)
proc remoteReader(transp: StreamTransport) {.async.} =
while true:
var line = await transp.readLine()
if len(line) == 0:
break
echo ">> ", line
while true:
try:
var line = await transp.readLine()
if line.startsWith("/connect"):
var parts = line.split(" ")
if len(parts) == 2:
var peerId = PeerId.init(parts[1])
var address = MultiAddress.init(multiCodec("p2p-circuit"))
address &= MultiAddress.init(multiCodec("p2p"), peerId)
echo "= Searching for peer ", peerId.pretty()
var id = await udata.api.dhtFindPeer(peerId)
echo "= Peer " & parts[1] & " found at addresses:"
for item in id.addresses:
echo $item
echo "= Connecting to peer ", $address
await udata.api.connect(peerId, @[address], 30)
echo "= Opening stream to peer chat ", parts[1]
var stream = await udata.api.openStream(peerId, ServerProtocols)
udata.remotes.add(stream.transp)
echo "= Connected to peer chat ", parts[1]
asyncSpawn remoteReader(stream.transp)
elif line.startsWith("/search"):
var parts = line.split(" ")
if len(parts) == 2:
var peerId = PeerId.init(parts[1])
echo "= Searching for peer ", peerId.pretty()
var id = await udata.api.dhtFindPeer(peerId)
echo "= Peer " & parts[1] & " found at addresses:"
for item in id.addresses:
echo $item
elif line.startsWith("/consearch"):
var parts = line.split(" ")
if len(parts) == 2:
var peerId = PeerId.init(parts[1])
echo "= Searching for peers connected to peer ", parts[1]
var peers = await udata.api.dhtFindPeersConnectedToPeer(peerId)
echo "= Found ", len(peers), " connected to peer ", parts[1]
for item in peers:
var peer = item.peer
var addresses = newSeq[string]()
var relay = false
for a in item.addresses:
addresses.add($a)
if a.protoName() == "/p2p-circuit":
relay = true
break
if relay:
echo peer.pretty(), " * ", " [", addresses.join(", "), "]"
else:
echo peer.pretty(), " [", addresses.join(", "), "]"
elif line.startsWith("/exit"):
break
else:
var msg = line & "\r\n"
echo "<< ", line
var pending = newSeq[Future[int]]()
for item in udata.remotes:
pending.add(item.write(msg))
if len(pending) > 0:
var results = await all(pending)
except CatchableError as err:
echo err.msg
proc main() {.async.} =
var data = new CustomData
data.remotes = newSeq[StreamTransport]()
var (rfd, wfd) = createAsyncPipe()
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
raise newException(ValueError, "Could not initialize pipe!")
data.consoleFd = rfd
data.serveFut = serveThread(data)
var thread: Thread[AsyncFD]
thread.createThread(threadMain, wfd)
echo "= Starting P2P node"
data.api = await newDaemonApi({DHTFull, Bootstrap})
await sleepAsync(3000)
var id = await data.api.identity()
proc streamHandler(api: DaemonAPI, stream: P2PStream) {.async.} =
echo "= Peer ", stream.peer.pretty(), " joined chat"
data.remotes.add(stream.transp)
while true:
var line = await stream.transp.readLine()
if len(line) == 0:
break
echo ">> ", line
await data.api.addHandler(ServerProtocols, streamHandler)
echo "= Your PeerId is ", id.peer.pretty()
await data.serveFut
when isMainModule:
waitFor(main())

View File

@@ -1,61 +0,0 @@
# Table of Contents
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Script](#script)
- [Usage](#usage)
- [Example](#example)
- [Getting Started](#getting-started)
# Introduction
This is a libp2p-backed daemon wrapping the functionalities of go-libp2p for use in Nim. <br>
For more information about the go daemon, check out [this repository](https://github.com/libp2p/go-libp2p-daemon).
> **Required only** for running the tests.
# Prerequisites
Go with version `1.16.0`.
> You will *likely* be able to build `go-libp2p-daemon` with different Go versions, but **they haven't been tested**.
# Installation
Follow one of the methods below:
## Script
Run the build script while having the `go` command pointing to the correct Go version.
We recommend using `1.16.0`, as previously stated.
```sh
./scripts/build_p2pd.sh
```
If everything goes correctly, the binary (`p2pd`) should be built and placed in the correct directory.
If you find any issues, please head into our discord and ask for our assistance.
After successfully building the binary, remember to add it to your path so it can be found. You can do that by running:
```sh
export PATH="$PATH:$HOME/go/bin"
```
> **Tip:** To make this change permanent, add the command above to your `.bashrc` file.
# Usage
## Example
Examples can be found in the [examples folder](https://github.com/status-im/nim-libp2p/tree/readme/examples/go-daemon)
## Getting Started
Try out the chat example. Full code can be found [here](https://github.com/status-im/nim-libp2p/blob/master/examples/chat.nim):
```bash
nim c -r --threads:on examples/directchat.nim
```
This will output a peer ID such as `QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu` which you can use in another instance to connect to it.
```bash
./examples/directchat
/connect QmbmHfVvouKammmQDJck4hz33WvVktNEe7pasxz2HgseRu
```
You can now chat between the instances!
![Chat example](https://imgur.com/caYRu8K.gif)

View File

@@ -1,46 +0,0 @@
import chronos, nimcrypto, strutils, os
import ../../libp2p/daemon/daemonapi
const PubSubTopic = "test-net"
proc main(bn: string) {.async.} =
echo "= Starting P2P node"
var bootnodes = bn.split(",")
var api = await newDaemonApi(
{DHTFull, PSGossipSub, WaitBootstrap}, bootstrapNodes = bootnodes, peersRequired = 1
)
var id = await api.identity()
echo "= P2P node ", id.peer.pretty(), " started:"
for item in id.addresses:
echo item
proc pubsubLogger(
api: DaemonAPI, ticket: PubsubTicket, message: PubSubMessage
): Future[bool] {.async.} =
let msglen = len(message.data)
echo "= Recieved pubsub message with length ",
msglen, " bytes from peer ", message.peer.pretty(), ": "
var strdata = cast[string](message.data)
echo strdata
result = true
var ticket = await api.pubsubSubscribe(PubSubTopic, pubsubLogger)
# Waiting for gossipsub interval
while true:
var peers = await api.pubsubListPeers(PubSubTopic)
if len(peers) > 0:
break
await sleepAsync(1000)
var data = "HELLO\r\n"
var msgData = cast[seq[byte]](data)
await api.pubsubPublish(PubSubTopic, msgData)
when isMainModule:
if paramCount() != 1:
echo "Please supply bootnodes!"
else:
waitFor(main(paramStr(1)))
while true:
poll()

View File

@@ -1,3 +1,5 @@
{.used.}
import chronos # an efficient library for async
import stew/byteutils # various utils
import libp2p

View File

@@ -3,4 +3,4 @@
Welcome to the nim-libp2p documentation!
Here, you'll find [tutorials](tutorial_1_connect.md) to help you get started, as well as
the [full reference](https://status-im.github.io/nim-libp2p/master/libp2p.html).
the [full reference](https://vacp2p.github.io/nim-libp2p/master/libp2p.html).

View File

@@ -1,3 +1,4 @@
{.used.}
## # Simple ping tutorial
##
## Hi all, welcome to the first nim-libp2p tutorial!

View File

@@ -1,3 +1,4 @@
{.used.}
## # Custom protocol in libp2p
##
## In the [previous tutorial](tutorial_1_connect.md), we've looked at how to create a simple ping program using the `nim-libp2p`.

View File

@@ -1,3 +1,4 @@
{.used.}
## # Protobuf usage
##
## In the [previous tutorial](tutorial_2_customproto.md), we created a simple "ping" protocol.

View File

@@ -1,3 +1,4 @@
{.used.}
## # GossipSub
##
## In this tutorial, we'll build a simple GossipSub network

View File

@@ -1,3 +1,4 @@
{.used.}
## # Discovery Manager
##
## In the [previous tutorial](tutorial_4_gossipsub.md), we built a custom protocol using [protobuf](https://developers.google.com/protocol-buffers) and

View File

@@ -1,3 +1,4 @@
{.used.}
## # Tron example
##
## In this tutorial, we will create a video game based on libp2p, using

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1752620740,
"narHash": "sha256-f3pO+9lg66mV7IMmmIqG4PL3223TYMlnlw+pnpelbss=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "32a4e87942101f1c9f9865e04dc3ddb175f5f32e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

34
flake.nix Normal file
View File

@@ -0,0 +1,34 @@
{
description = "nim-libp2p dev shell flake";
nixConfig = {
extra-substituters = [ "https://nix-cache.status.im/" ];
extra-trusted-public-keys = [ "nix-cache.status.im-1:x/93lOfLU+duPplwMSBR+OlY4+mo+dCN7n0mr4oPwgY=" ];
};
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
};
outputs = { self, nixpkgs }:
let
stableSystems = [
"x86_64-linux" "aarch64-linux" "armv7a-linux"
"x86_64-darwin" "aarch64-darwin"
"x86_64-windows"
];
forEach = nixpkgs.lib.genAttrs;
forAllSystems = forEach stableSystems;
pkgsFor = forEach stableSystems (
system: import nixpkgs { inherit system; }
);
in rec {
devShells = forAllSystems (system: {
default = pkgsFor.${system}.mkShell {
nativeBuildInputs = with pkgsFor.${system}; [
nim-2_2 nimble openssl.dev
];
};
});
};
}

View File

@@ -0,0 +1,76 @@
From 29bac4cd8f28abfb9efb481d800b7c2e855d9b03 Mon Sep 17 00:00:00 2001
From: Gabriel Cruz <gabe@gmelodie.com>
Date: Wed, 17 Sep 2025 10:42:14 -0300
Subject: [PATCH] disable filtering of private ip addresses
---
p2p/protocol/autonatv2/autonat.go | 24 +-----------------------
p2p/protocol/autonatv2/server.go | 9 ++++++---
2 files changed, 7 insertions(+), 26 deletions(-)
diff --git a/p2p/protocol/autonatv2/autonat.go b/p2p/protocol/autonatv2/autonat.go
index 24883052..00a6211f 100644
--- a/p2p/protocol/autonatv2/autonat.go
+++ b/p2p/protocol/autonatv2/autonat.go
@@ -16,7 +16,6 @@ import (
"github.com/libp2p/go-libp2p/core/peer"
logging "github.com/libp2p/go-libp2p/gologshim"
ma "github.com/multiformats/go-multiaddr"
- manet "github.com/multiformats/go-multiaddr/net"
)
const (
@@ -180,21 +179,7 @@ func (an *AutoNAT) Close() {
// GetReachability makes a single dial request for checking reachability for requested addresses
func (an *AutoNAT) GetReachability(ctx context.Context, reqs []Request) (Result, error) {
var filteredReqs []Request
- if !an.allowPrivateAddrs {
- filteredReqs = make([]Request, 0, len(reqs))
- for _, r := range reqs {
- if manet.IsPublicAddr(r.Addr) {
- filteredReqs = append(filteredReqs, r)
- } else {
- log.Error("private address in reachability check", "address", r.Addr)
- }
- }
- if len(filteredReqs) == 0 {
- return Result{}, ErrPrivateAddrs
- }
- } else {
- filteredReqs = reqs
- }
+ filteredReqs = reqs
an.mx.Lock()
now := time.Now()
var p peer.ID
@@ -215,13 +200,6 @@ func (an *AutoNAT) GetReachability(ctx context.Context, reqs []Request) (Result,
log.Debug("reachability check failed", "peer", p, "err", err)
return res, fmt.Errorf("reachability check with %s failed: %w", p, err)
}
- // restore the correct index in case we'd filtered private addresses
- for i, r := range reqs {
- if r.Addr.Equal(res.Addr) {
- res.Idx = i
- break
- }
- }
log.Debug("reachability check successful", "peer", p)
return res, nil
}
diff --git a/p2p/protocol/autonatv2/server.go b/p2p/protocol/autonatv2/server.go
index 167d3d8e..e6d1e492 100644
--- a/p2p/protocol/autonatv2/server.go
+++ b/p2p/protocol/autonatv2/server.go
@@ -196,9 +197,6 @@ func (as *server) serveDialRequest(s network.Stream) EventDialRequestCompleted {
if err != nil {
continue
}
- if !as.allowPrivateAddrs && !manet.IsPublicAddr(a) {
- continue
- }
if !as.dialerHost.Network().CanDial(p, a) {
continue
}
--
2.51.0

View File

@@ -0,0 +1,97 @@
module go-peer
go 1.25.1
require github.com/libp2p/go-libp2p v0.43.0
replace github.com/libp2p/go-libp2p => ./go-libp2p
require (
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/flynn/noise v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/ipfs/go-cid v0.5.0 // indirect
github.com/ipfs/go-log/v2 v2.6.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/koron/go-ssdp v0.0.6 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.2.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect
github.com/libp2p/go-netroute v0.2.2 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/libp2p/go-yamux/v5 v5.0.1 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.66 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.16.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.1 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.6.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v2 v2.2.12 // indirect
github.com/pion/dtls/v3 v3.0.6 // indirect
github.com/pion/ice/v4 v4.0.10 // indirect
github.com/pion/interceptor v0.1.40 // indirect
github.com/pion/logging v0.2.3 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.19 // indirect
github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.13 // indirect
github.com/pion/srtp/v3 v3.0.6 // indirect
github.com/pion/stun v0.6.1 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v2 v2.2.10 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.0.2 // indirect
github.com/pion/webrtc/v4 v4.1.2 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/quic-go/webtransport-go v0.9.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
go.uber.org/dig v1.19.0 // indirect
go.uber.org/fx v1.24.0 // indirect
go.uber.org/mock v0.5.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
lukechampine.com/blake3 v1.4.1 // indirect
)

View File

@@ -0,0 +1,441 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
github.com/ipfs/go-log/v2 v2.6.0 h1:2Nu1KKQQ2ayonKp4MPo6pXCjqw1ULc9iohRqWV5EYqg=
github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU=
github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw=
github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc=
github.com/libp2p/go-libp2p v0.43.0 h1:b2bg2cRNmY4HpLK8VHYQXLX2d3iND95OjodLFymvqXU=
github.com/libp2p/go-libp2p v0.43.0/go.mod h1:IiSqAXDyP2sWH+J2gs43pNmB/y4FOi2XQPbsb+8qvzc=
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8=
github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE=
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg=
github.com/libp2p/go-yamux/v5 v5.0.1/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc=
github.com/multiformats/go-multiaddr v0.16.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M=
github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo=
github.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ=
github.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c=
github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4=
github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54=
github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70=
github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -0,0 +1 @@
12D3KooWSnUDxXeeEnerD1Wf35R5b8bjTMzdAz838aDUUY8GJPGa

View File

@@ -0,0 +1,2 @@
@i
(>%ËÁø‡®PM”ܘXE~§|# õ“ýºØ®ü\íÇØ¬åsqzïÔDSݺvöLË(± Úð…•(×

View File

@@ -0,0 +1,97 @@
package main
import (
"crypto/rand"
"fmt"
"io/ioutil"
"log"
"os"
libp2p "github.com/libp2p/go-libp2p"
crypto "github.com/libp2p/go-libp2p/core/crypto"
peer "github.com/libp2p/go-libp2p/core/peer"
)
const (
privKeyFile = "peer.key"
peerIDFile = "peer.id"
)
func loadOrCreateIdentity() (crypto.PrivKey, peer.ID, error) {
if _, err := os.Stat(privKeyFile); err == nil {
// Load private key
data, err := ioutil.ReadFile(privKeyFile)
if err != nil {
return nil, "", fmt.Errorf("failed to read private key: %w", err)
}
priv, err := crypto.UnmarshalPrivateKey(data)
if err != nil {
return nil, "", fmt.Errorf("failed to unmarshal private key: %w", err)
}
// Load peer ID as string
peerData, err := ioutil.ReadFile(peerIDFile)
if err != nil {
return nil, "", fmt.Errorf("failed to read peer ID: %w", err)
}
pid, err := peer.Decode(string(peerData))
if err != nil {
return nil, "", fmt.Errorf("failed to decode peer ID: %w", err)
}
return priv, pid, nil
}
// Create new keypair
priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
return nil, "", fmt.Errorf("failed to generate keypair: %w", err)
}
pid, err := peer.IDFromPublicKey(pub)
if err != nil {
return nil, "", fmt.Errorf("failed to derive peer ID: %w", err)
}
// Save private key
privBytes, err := crypto.MarshalPrivateKey(priv)
if err != nil {
return nil, "", fmt.Errorf("failed to marshal private key: %w", err)
}
if err := ioutil.WriteFile(privKeyFile, privBytes, 0600); err != nil {
return nil, "", fmt.Errorf("failed to write private key: %w", err)
}
// Save peer ID in canonical string form
if err := ioutil.WriteFile(peerIDFile, []byte(pid.String()), 0644); err != nil {
return nil, "", fmt.Errorf("failed to write peer ID: %w", err)
}
return priv, pid, nil
}
func main() {
priv, pid, err := loadOrCreateIdentity()
if err != nil {
log.Fatalf("Identity setup failed: %v", err)
}
h, err := libp2p.New(
libp2p.Identity(priv),
libp2p.EnableAutoNATv2(),
libp2p.ListenAddrStrings(
"/ip4/0.0.0.0/tcp/4040",
"/ip6/::/tcp/4040",
),
)
if err != nil {
log.Fatalf("Failed to create host: %v", err)
}
defer h.Close()
fmt.Println("Peer ID:", pid.String())
fmt.Println("Listen addresses:", h.Addrs())
fmt.Println("AutoNATv2 client started.")
select {}
}

View File

@@ -0,0 +1,4 @@
# begin Nimble config (version 2)
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config

View File

@@ -0,0 +1,10 @@
version = "0.1.0"
author = "Status Research & Development Gmb"
description = "AutoNATv2 peer for interop testing"
license = "MIT"
srcDir = "src"
bin = @["nim_peer"]
# Dependencies
requires "nim >= 2.3.1", "libp2p"

View File

@@ -0,0 +1,64 @@
import net, os, chronos, libp2p
import libp2p/protocols/connectivity/autonatv2/service
import libp2p/protocols/connectivity/autonatv2/types
proc waitForService(
host: string, port: Port, retries: int = 20, delay: Duration = 500.milliseconds
): Future[bool] {.async.} =
for i in 0 ..< retries:
try:
var s = newSocket()
s.connect(host, port)
s.close()
return true
except OSError:
discard
await sleepAsync(delay)
return false
proc main() {.async.} =
if paramCount() != 1:
quit("Usage: nim r src/nim_peer.nim <peerid>", 1)
# ensure go peer is started
await sleepAsync(3.seconds)
let dstPeerId = PeerId.init(paramStr(1)).get()
var src = SwitchBuilder
.new()
.withRng(newRng())
.withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/3030").tryGet()])
.withAutonatV2Server()
.withAutonatV2(
serviceConfig = AutonatV2ServiceConfig.new(scheduleInterval = Opt.some(1.seconds))
)
.withTcpTransport()
.withYamux()
.withNoise()
.build()
let awaiter = newFuture[void]()
proc statusAndConfidenceHandler(
networkReachability: NetworkReachability, confidence: Opt[float]
) {.async: (raises: [CancelledError]).} =
if networkReachability != NetworkReachability.Unknown and confidence.isSome() and
confidence.get() >= 0.3:
if not awaiter.finished:
awaiter.complete()
let service = cast[AutonatV2Service](src.services[1])
service.setStatusAndConfidenceHandler(statusAndConfidenceHandler)
await src.start()
await src.connect(dstPeerId, @[MultiAddress.init("/ip4/127.0.0.1/tcp/4040").get()])
await awaiter
echo service.networkReachability
when isMainModule:
if waitFor(waitForService("127.0.0.1", Port(4040))):
waitFor(main())
else:
quit("timeout waiting for service", 1)

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1.5-labs
FROM nimlang/nim:1.6.16 as builder
FROM nimlang/nim:latest as builder
WORKDIR /workspace
@@ -7,11 +7,11 @@ COPY .pinned libp2p.nimble nim-libp2p/
RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y libssl-dev
RUN cd nim-libp2p && nimble install_pinned && nimble install "redis@#b341fe240dbf11c544011dd0e033d3c3acca56af" -y
RUN cd nim-libp2p && nimble install_pinned && nimble install redis -y
COPY . nim-libp2p/
RUN cd nim-libp2p && nim c --skipParentCfg --NimblePath:./nimbledeps/pkgs -d:chronicles_log_level=DEBUG -d:chronicles_default_output_device=stderr -d:release --threads:off --skipProjCfg -o:hole-punching-tests ./tests/hole-punching-interop/hole_punching.nim
RUN cd nim-libp2p && nim c --skipParentCfg --NimblePath:./nimbledeps/pkgs2 --mm:refc -d:chronicles_log_level=DEBUG -d:chronicles_default_output_device=stderr -d:release --threads:off --skipProjCfg -o:hole-punching-tests ./interop/hole-punching/hole_punching.nim
FROM --platform=linux/amd64 debian:bullseye-slim
RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y dnsutils jq curl tcpdump iproute2 libssl-dev

View File

@@ -15,8 +15,7 @@ import
protocols/connectivity/autonat/service,
protocols/ping,
]
import ../stubs/autonatclientstub
import ../errorhelpers
import ../../tests/[stubs/autonatclientstub, errorhelpers]
logScope:
topics = "hp interop node"
@@ -85,8 +84,8 @@ proc main() {.async.} =
debug "Dialing relay...", relayMA
let relayId = await switch.connect(relayMA).wait(30.seconds)
debug "Connected to relay", relayId
except AsyncTimeoutError:
raise newException(CatchableError, "Connection to relay timed out")
except AsyncTimeoutError as e:
raise newException(CatchableError, "Connection to relay timed out: " & e.msg, e)
# Wait for our relay address to be published
while not switch.peerInfo.addrs.anyIt(it.contains(multiCodec("p2p-circuit")).tryGet()):
@@ -104,7 +103,7 @@ proc main() {.async.} =
try:
PeerId.init(redisClient.bLPop(@["LISTEN_CLIENT_PEER_ID"], 0)[1]).tryGet()
except Exception as e:
raise newException(CatchableError, e.msg)
raise newException(CatchableError, "Exception init peer: " & e.msg, e)
debug "Got listener peer id", listenerId
let listenerRelayAddr = MultiAddress.init($relayMA & "/p2p-circuit").tryGet()
@@ -131,8 +130,8 @@ try:
return "done"
discard waitFor(mainAsync().wait(4.minutes))
except AsyncTimeoutError:
error "Program execution timed out."
except AsyncTimeoutError as e:
error "Program execution timed out", description = e.msg
quit(-1)
except CatchableError as e:
error "Unexpected error", description = e.msg

View File

@@ -0,0 +1,18 @@
# syntax=docker/dockerfile:1.5-labs
FROM nimlang/nim:latest as builder
WORKDIR /app
COPY .pinned libp2p.nimble nim-libp2p/
RUN --mount=type=cache,target=/var/cache/apt apt-get update && apt-get install -y libssl-dev
RUN cd nim-libp2p && nimble install_pinned && nimble install redis -y
COPY . nim-libp2p/
RUN \
cd nim-libp2p && \
nim c --skipProjCfg --skipParentCfg --NimblePath:./nimbledeps/pkgs2 -p:nim-libp2p --mm:refc -d:libp2p_quic_support -d:chronicles_log_level=WARN -d:chronicles_default_output_device=stderr --threads:off ./interop/transport/main.nim
ENTRYPOINT ["/app/nim-libp2p/interop/transport/main"]

View File

@@ -47,12 +47,9 @@ proc main() {.async.} =
MultiAddress.init("/ip4/" & ip & "/udp/0/quic-v1").tryGet()
)
of "ws":
discard switchBuilder
.withTransport(
proc(upgr: Upgrade, privateKey: PrivateKey): Transport =
WsTransport.new(upgr)
discard switchBuilder.withWsTransport().withAddress(
MultiAddress.init("/ip4/" & ip & "/tcp/0/ws").tryGet()
)
.withAddress(MultiAddress.init("/ip4/" & ip & "/tcp/0/ws").tryGet())
else:
doAssert false
@@ -83,7 +80,7 @@ proc main() {.async.} =
try:
redisClient.bLPop(@["listenerAddr"], testTimeout.seconds.int)[1]
except Exception as e:
raise newException(CatchableError, e.msg)
raise newException(CatchableError, "Exception calling bLPop: " & e.msg, e)
let
remoteAddr = MultiAddress.init(listenerAddr).tryGet()
dialingStart = Moment.now()
@@ -108,8 +105,8 @@ try:
return "done"
discard waitFor(mainAsync().wait(testTimeout))
except AsyncTimeoutError:
error "Program execution timed out."
except AsyncTimeoutError as e:
error "Program execution timed out", description = e.msg
quit(-1)
except CatchableError as e:
error "Unexpected error", description = e.msg

View File

@@ -1,17 +1,17 @@
mode = ScriptMode.Verbose
packageName = "libp2p"
version = "1.10.1"
version = "1.13.0"
author = "Status Research & Development GmbH"
description = "LibP2P implementation"
license = "MIT"
skipDirs = @["tests", "examples", "Nim", "tools", "scripts", "docs"]
requires "nim >= 1.6.0",
requires "nim >= 2.0.0",
"nimcrypto >= 0.6.0 & < 0.7.0", "dnsclient >= 0.3.0 & < 0.4.0", "bearssl >= 0.2.5",
"chronicles >= 0.10.2", "chronos >= 4.0.3", "metrics", "secp256k1", "stew >= 0.4.0",
"websock", "unittest2", "results",
"https://github.com/status-im/nim-quic.git#a6c30263c95fc5ddb2ef4d197c09b282555c06b0"
"chronicles >= 0.11.0 & < 0.12.0", "chronos >= 4.0.4", "metrics", "secp256k1",
"stew >= 0.4.0", "websock >= 0.2.0", "unittest2", "results", "quic >= 0.2.16",
"https://github.com/vacp2p/nim-jwt.git#18f8378de52b241f321c1f9ea905456e89b95c6f"
let nimc = getEnv("NIMC", "nim") # Which nim compiler to use
let lang = getEnv("NIMLANG", "c") # Which backend (c/cpp/js)
@@ -30,7 +30,7 @@ proc runTest(filename: string, moreoptions: string = "") =
excstr.add(" " & moreoptions & " ")
if getEnv("CICOV").len > 0:
excstr &= " --nimcache:nimcache/" & filename & "-" & $excstr.hash
exec excstr & " -r " & " tests/" & filename
exec excstr & " -r -d:libp2p_quic_support -d:libp2p_autotls_support tests/" & filename
rmFile "tests/" & filename.toExe
proc buildSample(filename: string, run = false, extraFlags = "") =
@@ -49,19 +49,16 @@ proc tutorialToMd(filename: string) =
task testnative, "Runs libp2p native tests":
runTest("testnative")
task testdaemon, "Runs daemon tests":
runTest("testdaemon")
task testinterop, "Runs interop tests":
runTest("testinterop")
task testpubsub, "Runs pubsub tests":
runTest("pubsub/testpubsub")
runTest("pubsub/testpubsub", "-d:libp2p_gossipsub_1_4")
task testfilter, "Run PKI filter test":
runTest("testpkifilter")
runTest("testpkifilter", moreoptions = "-d:libp2p_pki_schemes=")
task testintegration, "Runs integraion tests":
runTest("testintegration")
task test, "Runs the test suite":
runTest("testall")
exec "nimble testfilter"

533
libp2p/autotls/acme/api.nim Normal file
View File

@@ -0,0 +1,533 @@
import json, uri
from times import DateTime, parse
import chronos/apps/http/httpclient, results, chronicles
import ./utils
import ../../crypto/crypto
import ../../crypto/rsa
export ACMEError
logScope:
topics = "libp2p acme api"
const
LetsEncryptURL* = "https://acme-v02.api.letsencrypt.org"
LetsEncryptURLStaging* = "https://acme-staging-v02.api.letsencrypt.org"
Alg = "RS256"
DefaultChalCompletedRetries = 10
DefaultChalCompletedRetryTime = 1.seconds
DefaultFinalizeRetries = 10
DefaultFinalizeRetryTime = 1.seconds
DefaultRandStringSize = 256
ACMEHttpHeaders = [("Content-Type", "application/jose+json")]
type Authorization* = string
type Domain* = string
type Kid* = string
type Nonce* = string
type ACMEDirectory* = object
newNonce*: string
newOrder*: string
newAccount*: string
type ACMEApi* = ref object of RootObj
directory: Opt[ACMEDirectory]
session: HttpSessionRef
acmeServerURL*: Uri
type HTTPResponse* = object
body*: JsonNode
headers*: HttpTable
type JWK = object
kty: string
n: string
e: string
# whether the request uses Kid or not
type ACMERequestType = enum
ACMEJwkRequest
ACMEKidRequest
type ACMERequestHeader = object
alg: string
typ: string
nonce: Nonce
url: string
case kind: ACMERequestType
of ACMEJwkRequest:
jwk: JWK
of ACMEKidRequest:
kid: Kid
type Email = string
type ACMERegisterRequest* = object
termsOfServiceAgreed: bool
contact: seq[Email]
type ACMEAccountStatus = enum
valid = "valid"
deactivated = "deactivated"
revoked = "revoked"
type ACMERegisterResponseBody = object
status*: ACMEAccountStatus
type ACMERegisterResponse* = object
kid*: Kid
status*: ACMEAccountStatus
type ACMEChallengeStatus* {.pure.} = enum
PENDING = "pending"
PROCESSING = "processing"
VALID = "valid"
INVALID = "invalid"
type ACMEOrderStatus* {.pure.} = enum
PENDING = "pending"
READY = "ready"
PROCESSING = "processing"
VALID = "valid"
INVALID = "invalid"
type ACMEChallengeType* {.pure.} = enum
DNS01 = "dns-01"
HTTP01 = "http-01"
TLSALPN01 = "tls-alpn-01"
type ACMEChallengeToken* = string
type ACMEChallenge* = object
url*: string
`type`*: ACMEChallengeType
status*: ACMEChallengeStatus
token*: ACMEChallengeToken
type ACMEChallengeIdentifier = object
`type`: string
value: string
type ACMEChallengeRequest = object
identifiers: seq[ACMEChallengeIdentifier]
type ACMEChallengeResponseBody = object
status: ACMEOrderStatus
authorizations: seq[Authorization]
finalize: string
type ACMEChallengeResponse* = object
status*: ACMEOrderStatus
authorizations*: seq[Authorization]
finalize*: string
order*: string
type ACMEChallengeResponseWrapper* = object
finalize*: string
order*: string
dns01*: ACMEChallenge
type ACMEAuthorizationsResponse* = object
challenges*: seq[ACMEChallenge]
type ACMECompletedResponse* = object
url: string
type ACMECheckKind* = enum
ACMEOrderCheck
ACMEChallengeCheck
type ACMECheckResponse* = object
case kind: ACMECheckKind
of ACMEOrderCheck:
orderStatus: ACMEOrderStatus
of ACMEChallengeCheck:
chalStatus: ACMEChallengeStatus
retryAfter: Duration
type ACMEFinalizeResponse* = object
status: ACMEOrderStatus
type ACMEOrderResponse* = object
certificate: string
expires: string
type ACMECertificateResponse* = object
rawCertificate*: string
certificateExpiry*: DateTime
type ACMECertificate* = object
rawCertificate*: string
certificateExpiry*: DateTime
certKeyPair*: KeyPair
when defined(libp2p_autotls_support):
import options, sequtils, strutils, jwt, bearssl/pem
template handleError*(msg: string, body: untyped): untyped =
try:
body
except ACMEError as exc:
raise exc
except CancelledError as exc:
raise exc
except JsonKindError as exc:
raise newException(ACMEError, msg & ": Failed to decode JSON", exc)
except ValueError as exc:
raise newException(ACMEError, msg & ": Failed to decode JSON", exc)
except HttpError as exc:
raise newException(ACMEError, msg & ": Failed to connect to ACME server", exc)
except CatchableError as exc:
raise newException(ACMEError, msg & ": Unexpected error", exc)
method post*(
self: ACMEApi, uri: Uri, payload: string
): Future[HTTPResponse] {.
async: (raises: [ACMEError, HttpError, CancelledError]), base
.}
method get*(
self: ACMEApi, uri: Uri
): Future[HTTPResponse] {.
async: (raises: [ACMEError, HttpError, CancelledError]), base
.}
proc new*(
T: typedesc[ACMEApi], acmeServerURL: Uri = parseUri(LetsEncryptURL)
): ACMEApi =
let session = HttpSessionRef.new()
ACMEApi(
session: session, directory: Opt.none(ACMEDirectory), acmeServerURL: acmeServerURL
)
proc getDirectory(
self: ACMEApi
): Future[ACMEDirectory] {.async: (raises: [ACMEError, CancelledError]).} =
handleError("getDirectory"):
self.directory.valueOr:
let acmeResponse = await self.get(self.acmeServerURL / "directory")
let directory = acmeResponse.body.to(ACMEDirectory)
self.directory = Opt.some(directory)
directory
method requestNonce*(
self: ACMEApi
): Future[Nonce] {.async: (raises: [ACMEError, CancelledError]), base.} =
handleError("requestNonce"):
let acmeResponse = await self.get(parseUri((await self.getDirectory()).newNonce))
Nonce(acmeResponse.headers.keyOrError("Replay-Nonce"))
# TODO: save n and e in account so we don't have to recalculate every time
proc acmeHeader(
self: ACMEApi, uri: Uri, key: KeyPair, needsJwk: bool, kid: Opt[Kid]
): Future[ACMERequestHeader] {.async: (raises: [ACMEError, CancelledError]).} =
if not needsJwk and kid.isNone():
raise newException(ACMEError, "kid not set")
if key.pubkey.scheme != PKScheme.RSA or key.seckey.scheme != PKScheme.RSA:
raise newException(ACMEError, "Unsupported signing key type")
let newNonce = await self.requestNonce()
if needsJwk:
let pubkey = key.pubkey.rsakey
let nArray = @(getArray(pubkey.buffer, pubkey.key.n, pubkey.key.nlen))
let eArray = @(getArray(pubkey.buffer, pubkey.key.e, pubkey.key.elen))
ACMERequestHeader(
kind: ACMEJwkRequest,
alg: Alg,
typ: "JWT",
nonce: newNonce,
url: $uri,
jwk: JWK(kty: "RSA", n: base64UrlEncode(nArray), e: base64UrlEncode(eArray)),
)
else:
ACMERequestHeader(
kind: ACMEKidRequest,
alg: Alg,
typ: "JWT",
nonce: newNonce,
url: $uri,
kid: kid.get(),
)
method post*(
self: ACMEApi, uri: Uri, payload: string
): Future[HTTPResponse] {.
async: (raises: [ACMEError, HttpError, CancelledError]), base
.} =
let rawResponse = await HttpClientRequestRef
.post(self.session, $uri, body = payload, headers = ACMEHttpHeaders)
.get()
.send()
let body = await rawResponse.getResponseBody()
HTTPResponse(body: body, headers: rawResponse.headers)
method get*(
self: ACMEApi, uri: Uri
): Future[HTTPResponse] {.
async: (raises: [ACMEError, HttpError, CancelledError]), base
.} =
let rawResponse = await HttpClientRequestRef.get(self.session, $uri).get().send()
let body = await rawResponse.getResponseBody()
HTTPResponse(body: body, headers: rawResponse.headers)
proc createSignedAcmeRequest(
self: ACMEApi,
uri: Uri,
payload: auto,
key: KeyPair,
needsJwk: bool = false,
kid: Opt[Kid] = Opt.none(Kid),
): Future[string] {.async: (raises: [ACMEError, CancelledError]).} =
if key.pubkey.scheme != PKScheme.RSA or key.seckey.scheme != PKScheme.RSA:
raise newException(ACMEError, "Unsupported signing key type")
let acmeHeader = await self.acmeHeader(uri, key, needsJwk, kid)
handleError("createSignedAcmeRequest"):
var token = toJWT(%*{"header": acmeHeader, "claims": payload})
let derPrivKey = key.seckey.rsakey.getBytes.get
let pemPrivKey: string = pemEncode(derPrivKey, "PRIVATE KEY")
token.sign(pemPrivKey)
$token.toFlattenedJson()
proc requestRegister*(
self: ACMEApi, key: KeyPair
): Future[ACMERegisterResponse] {.async: (raises: [ACMEError, CancelledError]).} =
let registerRequest = ACMERegisterRequest(termsOfServiceAgreed: true)
handleError("acmeRegister"):
let payload = await self.createSignedAcmeRequest(
parseUri((await self.getDirectory()).newAccount),
registerRequest,
key,
needsJwk = true,
)
let acmeResponse =
await self.post(parseUri((await self.getDirectory()).newAccount), payload)
let acmeResponseBody = acmeResponse.body.to(ACMERegisterResponseBody)
ACMERegisterResponse(
status: acmeResponseBody.status,
kid: acmeResponse.headers.keyOrError("location"),
)
proc requestNewOrder*(
self: ACMEApi, domains: seq[Domain], key: KeyPair, kid: Kid
): Future[ACMEChallengeResponse] {.async: (raises: [ACMEError, CancelledError]).} =
# request challenge from ACME server
let orderRequest = ACMEChallengeRequest(
identifiers: domains.mapIt(ACMEChallengeIdentifier(`type`: "dns", value: it))
)
handleError("requestNewOrder"):
let payload = await self.createSignedAcmeRequest(
parseUri((await self.getDirectory()).newOrder),
orderRequest,
key,
kid = Opt.some(kid),
)
let acmeResponse =
await self.post(parseUri((await self.getDirectory()).newOrder), payload)
let challengeResponseBody = acmeResponse.body.to(ACMEChallengeResponseBody)
if challengeResponseBody.authorizations.len == 0:
raise newException(ACMEError, "Authorizations field is empty")
ACMEChallengeResponse(
status: challengeResponseBody.status,
authorizations: challengeResponseBody.authorizations,
finalize: challengeResponseBody.finalize,
order: acmeResponse.headers.keyOrError("location"),
)
proc requestAuthorizations*(
self: ACMEApi, authorizations: seq[Authorization], key: KeyPair, kid: Kid
): Future[ACMEAuthorizationsResponse] {.async: (raises: [ACMEError, CancelledError]).} =
handleError("requestAuthorizations"):
doAssert authorizations.len > 0
let acmeResponse = await self.get(parseUri(authorizations[0]))
acmeResponse.body.to(ACMEAuthorizationsResponse)
proc requestChallenge*(
self: ACMEApi, domains: seq[Domain], key: KeyPair, kid: Kid
): Future[ACMEChallengeResponseWrapper] {.
async: (raises: [ACMEError, CancelledError])
.} =
let orderResponse = await self.requestNewOrder(domains, key, kid)
if orderResponse.status != ACMEOrderStatus.PENDING and
orderResponse.status != ACMEOrderStatus.READY:
# ready is a valid status when renewing certs before expiry
raise
newException(ACMEError, "Invalid new order status: " & $orderResponse.status)
let authorizationsResponse =
await self.requestAuthorizations(orderResponse.authorizations, key, kid)
if authorizationsResponse.challenges.len == 0:
raise newException(ACMEError, "No challenges received")
return ACMEChallengeResponseWrapper(
finalize: orderResponse.finalize,
order: orderResponse.order,
dns01: authorizationsResponse.challenges.filterIt(
it.`type` == ACMEChallengeType.DNS01
)[0],
# getting the first element is safe since we checked that authorizationsResponse.challenges.len != 0
)
proc requestCheck*(
self: ACMEApi, checkURL: Uri, checkKind: ACMECheckKind, key: KeyPair, kid: Kid
): Future[ACMECheckResponse] {.async: (raises: [ACMEError, CancelledError]).} =
handleError("requestCheck"):
let acmeResponse = await self.get(checkURL)
let retryAfter =
try:
parseInt(acmeResponse.headers.keyOrError("Retry-After")).seconds
except ValueError:
DefaultChalCompletedRetryTime
case checkKind
of ACMEOrderCheck:
try:
ACMECheckResponse(
kind: checkKind,
orderStatus: parseEnum[ACMEOrderStatus](acmeResponse.body["status"].getStr),
retryAfter: retryAfter,
)
except ValueError:
raise newException(
ACMEError, "Invalid order status: " & acmeResponse.body["status"].getStr
)
of ACMEChallengeCheck:
try:
ACMECheckResponse(
kind: checkKind,
chalStatus:
parseEnum[ACMEChallengeStatus](acmeResponse.body["status"].getStr),
retryAfter: retryAfter,
)
except ValueError:
raise newException(
ACMEError, "Invalid order status: " & acmeResponse.body["status"].getStr
)
proc sendChallengeCompleted*(
self: ACMEApi, chalURL: Uri, key: KeyPair, kid: Kid
): Future[ACMECompletedResponse] {.async: (raises: [ACMEError, CancelledError]).} =
handleError("sendChallengeCompleted"):
let payload =
await self.createSignedAcmeRequest(chalURL, %*{}, key, kid = Opt.some(kid))
let acmeResponse = await self.post(chalURL, payload)
acmeResponse.body.to(ACMECompletedResponse)
proc checkChallengeCompleted*(
self: ACMEApi,
checkURL: Uri,
key: KeyPair,
kid: Kid,
retries: int = DefaultChalCompletedRetries,
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
for i in 0 .. retries:
let checkResponse =
await self.requestCheck(checkURL, ACMEChallengeCheck, key, kid)
case checkResponse.chalStatus
of ACMEChallengeStatus.PENDING:
await sleepAsync(checkResponse.retryAfter) # try again after some delay
of ACMEChallengeStatus.VALID:
return true
else:
raise newException(
ACMEError,
"Failed challenge completion: expected 'valid', got '" &
$checkResponse.chalStatus & "'",
)
return false
proc completeChallenge*(
self: ACMEApi,
chalURL: Uri,
key: KeyPair,
kid: Kid,
retries: int = DefaultChalCompletedRetries,
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
let completedResponse = await self.sendChallengeCompleted(chalURL, key, kid)
# check until acme server is done (poll validation)
return await self.checkChallengeCompleted(chalURL, key, kid, retries = retries)
proc requestFinalize*(
self: ACMEApi,
domain: Domain,
finalize: Uri,
certKeyPair: KeyPair,
key: KeyPair,
kid: Kid,
): Future[ACMEFinalizeResponse] {.async: (raises: [ACMEError, CancelledError]).} =
handleError("requestFinalize"):
let payload = await self.createSignedAcmeRequest(
finalize, %*{"csr": createCSR(domain, certKeyPair)}, key, kid = Opt.some(kid)
)
let acmeResponse = await self.post(finalize, payload)
# server responds with updated order response
acmeResponse.body.to(ACMEFinalizeResponse)
proc checkCertFinalized*(
self: ACMEApi,
order: Uri,
key: KeyPair,
kid: Kid,
retries: int = DefaultChalCompletedRetries,
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
for i in 0 .. retries:
let checkResponse = await self.requestCheck(order, ACMEOrderCheck, key, kid)
case checkResponse.orderStatus
of ACMEOrderStatus.VALID:
return true
of ACMEOrderStatus.PROCESSING:
await sleepAsync(checkResponse.retryAfter) # try again after some delay
else:
error "Failed certificate finalization",
description = "expected 'valid', got '" & $checkResponse.orderStatus & "'"
return false # do not try again
return false
proc certificateFinalized*(
self: ACMEApi,
domain: Domain,
finalize: Uri,
order: Uri,
certKeyPair: KeyPair,
key: KeyPair,
kid: Kid,
retries: int = DefaultFinalizeRetries,
): Future[bool] {.async: (raises: [ACMEError, CancelledError]).} =
let finalizeResponse =
await self.requestFinalize(domain, finalize, certKeyPair, key, kid)
# keep checking order until cert is valid (done)
return await self.checkCertFinalized(order, key, kid, retries = retries)
proc requestGetOrder*(
self: ACMEApi, order: Uri
): Future[ACMEOrderResponse] {.async: (raises: [ACMEError, CancelledError]).} =
handleError("requestGetOrder"):
let acmeResponse = await self.get(order)
acmeResponse.body.to(ACMEOrderResponse)
proc downloadCertificate*(
self: ACMEApi, order: Uri
): Future[ACMECertificateResponse] {.async: (raises: [ACMEError, CancelledError]).} =
let orderResponse = await self.requestGetOrder(order)
handleError("downloadCertificate"):
let rawResponse = await HttpClientRequestRef
.get(self.session, orderResponse.certificate)
.get()
.send()
ACMECertificateResponse(
rawCertificate: bytesToString(await rawResponse.getBodyBytes()),
certificateExpiry: parse(orderResponse.expires, "yyyy-MM-dd'T'HH:mm:ss'Z'"),
)
proc close*(self: ACMEApi) {.async: (raises: [CancelledError]).} =
await self.session.closeWait()
else:
{.hint: "autotls disabled. Use -d:libp2p_autotls_support".}

View File

@@ -0,0 +1,98 @@
# Nim-Libp2p
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
import chronicles
import ../../crypto/crypto
import ./api
export api
type KeyAuthorization* = string
type ACMEClient* = ref object
api: ACMEApi
key*: KeyPair
kid*: Kid
logScope:
topics = "libp2p acme client"
when defined(libp2p_autotls_support):
import uri
import chronos, results, stew/byteutils
import ../../crypto/rsa
import ./utils
proc new*(
T: typedesc[ACMEClient],
rng: ref HmacDrbgContext = newRng(),
api: ACMEApi = ACMEApi.new(acmeServerURL = parseUri(LetsEncryptURL)),
key: Opt[KeyPair] = Opt.none(KeyPair),
kid: Kid = Kid(""),
): T {.raises: [].} =
let key = key.valueOr:
KeyPair.random(PKScheme.RSA, rng[]).get()
T(api: api, key: key, kid: kid)
proc getOrInitKid*(
self: ACMEClient
): Future[Kid] {.async: (raises: [ACMEError, CancelledError]).} =
if self.kid.len == 0:
let registerResponse = await self.api.requestRegister(self.key)
self.kid = registerResponse.kid
return self.kid
proc genKeyAuthorization*(self: ACMEClient, token: string): KeyAuthorization =
base64UrlEncode(@(sha256.digest((token & "." & thumbprint(self.key)).toBytes).data))
proc getChallenge*(
self: ACMEClient, domains: seq[api.Domain]
): Future[ACMEChallengeResponseWrapper] {.
async: (raises: [ACMEError, CancelledError])
.} =
await self.api.requestChallenge(domains, self.key, await self.getOrInitKid())
proc getCertificate*(
self: ACMEClient,
domain: api.Domain,
certKeyPair: KeyPair,
challenge: ACMEChallengeResponseWrapper,
): Future[ACMECertificateResponse] {.async: (raises: [ACMEError, CancelledError]).} =
let chalURL = parseUri(challenge.dns01.url)
let orderURL = parseUri(challenge.order)
let finalizeURL = parseUri(challenge.finalize)
trace "Sending challenge completed notification"
discard await self.api.sendChallengeCompleted(
chalURL, self.key, await self.getOrInitKid()
)
trace "Checking for completed challenge"
let completed = await self.api.checkChallengeCompleted(
chalURL, self.key, await self.getOrInitKid()
)
if not completed:
raise newException(
ACMEError, "Failed to signal ACME server about challenge completion"
)
trace "Waiting for certificate to be finalized"
let finalized = await self.api.certificateFinalized(
domain, finalizeURL, orderURL, certKeyPair, self.key, await self.getOrInitKid()
)
if not finalized:
raise
newException(ACMEError, "Failed to finalize certificate for domain " & domain)
trace "Downloading certificate"
await self.api.downloadCertificate(orderURL)
proc close*(self: ACMEClient) {.async: (raises: [CancelledError]).} =
await self.api.close()

View File

@@ -0,0 +1,40 @@
import uri
import chronos, chronos/apps/http/httpclient, json
import ./api, ./utils
export api
type MockACMEApi* = ref object of ACMEApi
mockedResponses*: seq[HTTPResponse]
proc new*(
T: typedesc[MockACMEApi]
): Future[T] {.async: (raises: [ACMEError, CancelledError]).} =
let directory = ACMEDirectory(
newNonce: LetsEncryptURL & "/new-nonce",
newOrder: LetsEncryptURL & "/new-order",
newAccount: LetsEncryptURL & "/new-account",
)
MockACMEApi(
session: HttpSessionRef.new(),
directory: Opt.some(directory),
acmeServerURL: parseUri(LetsEncryptURL),
)
when defined(libp2p_autotls_support):
method requestNonce*(
self: MockACMEApi
): Future[Nonce] {.async: (raises: [ACMEError, CancelledError]).} =
return $self.acmeServerURL & "/acme/1234"
method post*(
self: MockACMEApi, uri: Uri, payload: string
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
result = self.mockedResponses[0]
self.mockedResponses.delete(0)
method get*(
self: MockACMEApi, uri: Uri
): Future[HTTPResponse] {.async: (raises: [ACMEError, HttpError, CancelledError]).} =
result = self.mockedResponses[0]
self.mockedResponses.delete(0)

View File

@@ -0,0 +1,73 @@
import ../../errors
type ACMEError* = object of LPError
when defined(libp2p_autotls_support):
import base64, strutils, chronos/apps/http/httpclient, json
import ../../transports/tls/certificate_ffi
import ../../transports/tls/certificate
import ../../crypto/crypto
import ../../crypto/rsa
proc keyOrError*(table: HttpTable, key: string): string {.raises: [ValueError].} =
if not table.contains(key):
raise newException(ValueError, "key " & key & " not present in headers")
table.getString(key)
proc base64UrlEncode*(data: seq[byte]): string =
## Encodes data using base64url (RFC 4648 §5) — no padding, URL-safe
var encoded = base64.encode(data, safe = true)
encoded.removeSuffix("=")
encoded.removeSuffix("=")
return encoded
proc thumbprint*(key: KeyPair): string =
doAssert key.seckey.scheme == PKScheme.RSA, "unsupported keytype"
let pubkey = key.pubkey.rsakey
let nArray = @(getArray(pubkey.buffer, pubkey.key.n, pubkey.key.nlen))
let eArray = @(getArray(pubkey.buffer, pubkey.key.e, pubkey.key.elen))
let n = base64UrlEncode(nArray)
let e = base64UrlEncode(eArray)
let keyJson = %*{"e": e, "kty": "RSA", "n": n}
let digest = sha256.digest($keyJson)
return base64UrlEncode(@(digest.data))
proc getResponseBody*(
response: HttpClientResponseRef
): Future[JsonNode] {.async: (raises: [ACMEError, CancelledError]).} =
try:
let bodyBytes = await response.getBodyBytes()
if bodyBytes.len > 0:
return bytesToString(bodyBytes).parseJson()
return %*{} # empty body
except CancelledError as exc:
raise exc
except CatchableError as exc:
raise newException(
ACMEError, "Unexpected error occurred while getting body bytes", exc
)
except Exception as exc: # this is required for nim 1.6
raise newException(
ACMEError, "Unexpected error occurred while getting body bytes", exc
)
proc createCSR*(
domain: string, certKeyPair: KeyPair
): string {.raises: [ACMEError].} =
var certKey: cert_key_t
var certCtx: cert_context_t
var derCSR: ptr cert_buffer = nil
# convert KeyPair to cert_key_t
let rawSeckey: seq[byte] = certKeyPair.seckey.getRawBytes.valueOr:
raise newException(ACMEError, "Failed to get seckey raw bytes (DER)")
let seckeyBuffer = rawSeckey.toCertBuffer()
if cert_new_key_t(seckeyBuffer.unsafeAddr, certKey.addr) != CERT_SUCCESS:
raise newException(ACMEError, "Failed to convert key pair to cert_key_t")
# create CSR
if cert_signing_req(domain.cstring, certKey, derCSR.addr) != CERT_SUCCESS:
raise newException(ACMEError, "Failed to create CSR")
base64.encode(derCSR.toSeq, safe = true)

View File

@@ -0,0 +1,33 @@
when defined(libp2p_autotls_support):
import ./service, ./acme/client, ../peeridauth/client
import ../crypto/crypto, ../crypto/rsa, websock/websock
type MockAutotlsService* = ref object of AutotlsService
mockedCert*: TLSCertificate
mockedKey*: TLSPrivateKey
proc new*(
T: typedesc[MockAutotlsService],
rng: ref HmacDrbgContext = newRng(),
config: AutotlsConfig = AutotlsConfig.new(),
): T =
T(
acmeClient:
ACMEClient.new(api = ACMEApi.new(acmeServerURL = config.acmeServerURL)),
brokerClient: PeerIDAuthClient.new(),
bearer: Opt.none(BearerToken),
cert: Opt.none(AutotlsCert),
certReady: newAsyncEvent(),
running: newAsyncEvent(),
config: config,
rng: rng,
)
method getCertWhenReady*(
self: MockAutotlsService
): Future[AutotlsCert] {.async: (raises: [AutoTLSError, CancelledError]).} =
AutotlsCert.new(self.mockedCert, self.mockedKey, Moment.now)
method setup*(self: MockAutotlsService) {.base, async.} =
self.running.fire()

295
libp2p/autotls/service.nim Normal file
View File

@@ -0,0 +1,295 @@
# Nim-Libp2p
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
{.push public.}
import chronos, chronicles, net, results
import chronos/apps/http/httpclient, bearssl/rand
import
./acme/client,
./utils,
../crypto/crypto,
../nameresolving/nameresolver,
../peeridauth/client,
../switch,
../peerinfo,
../wire
logScope:
topics = "libp2p autotls"
export LetsEncryptURL, AutoTLSError
const
DefaultDnsServers* =
@[
initTAddress("1.1.1.1:53"),
initTAddress("1.0.0.1:53"),
initTAddress("[2606:4700:4700::1111]:53"),
]
DefaultRenewCheckTime* = 1.hours
DefaultRenewBufferTime = 1.hours
DefaultIssueRetries = 3
DefaultIssueRetryTime = 1.seconds
AutoTLSBroker* = "registration.libp2p.direct"
AutoTLSDNSServer* = "libp2p.direct"
HttpOk* = 200
HttpCreated* = 201
# NoneIp is needed because nim 1.6.16 can't do proper generic inference
NoneIp = Opt.none(IpAddress)
type SigParam = object
k: string
v: seq[byte]
type AutotlsCert* = ref object
cert*: TLSCertificate
privkey*: TLSPrivateKey
expiry*: Moment
type AutotlsConfig* = ref object
acmeServerURL*: Uri
nameResolver*: NameResolver
ipAddress: Opt[IpAddress]
renewCheckTime*: Duration
renewBufferTime*: Duration
issueRetries*: int
issueRetryTime*: Duration
type AutotlsService* = ref object of Service
acmeClient*: ACMEClient
brokerClient*: PeerIDAuthClient
bearer*: Opt[BearerToken]
cert*: Opt[AutotlsCert]
certReady*: AsyncEvent
running*: AsyncEvent
config*: AutotlsConfig
managerFut: Future[void]
peerInfo: PeerInfo
rng*: ref HmacDrbgContext
when defined(libp2p_autotls_support):
import json, sequtils, bearssl/pem
import
../crypto/rsa,
../utils/heartbeat,
../transports/transport,
../utils/ipaddr,
../transports/tcptransport,
../nameresolving/dnsresolver
proc new*(
T: typedesc[AutotlsCert],
cert: TLSCertificate,
privkey: TLSPrivateKey,
expiry: Moment,
): T =
T(cert: cert, privkey: privkey, expiry: expiry)
method getCertWhenReady*(
self: AutotlsService
): Future[AutotlsCert] {.base, async: (raises: [AutoTLSError, CancelledError]).} =
await self.certReady.wait()
return self.cert.get
proc new*(
T: typedesc[AutotlsConfig],
ipAddress: Opt[IpAddress] = NoneIp,
nameServers: seq[TransportAddress] = DefaultDnsServers,
acmeServerURL: Uri = parseUri(LetsEncryptURL),
renewCheckTime: Duration = DefaultRenewCheckTime,
renewBufferTime: Duration = DefaultRenewBufferTime,
issueRetries: int = DefaultIssueRetries,
issueRetryTime: Duration = DefaultIssueRetryTime,
): T =
T(
nameResolver: DnsResolver.new(nameServers),
acmeServerURL: acmeServerURL,
ipAddress: ipAddress,
renewCheckTime: renewCheckTime,
renewBufferTime: renewBufferTime,
issueRetries: issueRetries,
issueRetryTime: issueRetryTime,
)
proc new*(
T: typedesc[AutotlsService],
rng: ref HmacDrbgContext = newRng(),
config: AutotlsConfig = AutotlsConfig.new(),
): T =
T(
acmeClient:
ACMEClient.new(api = ACMEApi.new(acmeServerURL = config.acmeServerURL)),
brokerClient: PeerIDAuthClient.new(),
bearer: Opt.none(BearerToken),
cert: Opt.none(AutotlsCert),
certReady: newAsyncEvent(),
running: newAsyncEvent(),
config: config,
managerFut: nil,
peerInfo: nil,
rng: rng,
)
method setup*(
self: AutotlsService, switch: Switch
): Future[bool] {.async: (raises: [CancelledError]).} =
trace "Setting up AutotlsService"
let hasBeenSetup = await procCall Service(self).setup(switch)
if hasBeenSetup:
if self.config.ipAddress.isNone():
try:
self.config.ipAddress = Opt.some(getPublicIPAddress())
except ValueError as exc:
error "Failed to get public IP address", err = exc.msg
return false
except OSError as exc:
error "Failed to get public IP address", err = exc.msg
return false
self.managerFut = self.run(switch)
return hasBeenSetup
method issueCertificate(
self: AutotlsService
): Future[bool] {.
base, async: (raises: [AutoTLSError, ACMEError, PeerIDAuthError, CancelledError])
.} =
trace "Issuing certificate"
if self.peerInfo.isNil():
error "Cannot issue new certificate: peerInfo not set"
return false
# generate autotls domain string: "*.{peerID}.libp2p.direct"
let baseDomain =
api.Domain(encodePeerId(self.peerInfo.peerId) & "." & AutoTLSDNSServer)
let domain = api.Domain("*." & baseDomain)
let acmeClient = self.acmeClient
trace "Requesting ACME challenge"
let dns01Challenge = await acmeClient.getChallenge(@[domain])
trace "Generating key authorization"
let keyAuth = acmeClient.genKeyAuthorization(dns01Challenge.dns01.token)
let addrs = await self.peerInfo.expandAddrs()
if addrs.len == 0:
error "Unable to authenticate with broker: no addresses"
return false
let strMultiaddresses: seq[string] = addrs.mapIt($it)
let payload = %*{"value": keyAuth, "addresses": strMultiaddresses}
let registrationURL = parseUri("https://" & AutoTLSBroker & "/v1/_acme-challenge")
trace "Sending challenge to AutoTLS broker"
let (bearer, response) =
await self.brokerClient.send(registrationURL, self.peerInfo, payload, self.bearer)
if self.bearer.isNone():
# save bearer token for future
self.bearer = Opt.some(bearer)
if response.status != HttpOk:
error "Failed to authenticate with AutoTLS Broker at " & AutoTLSBroker
debug "Broker message",
body = bytesToString(response.body), peerinfo = self.peerInfo
return false
let dashedIpAddr = ($self.config.ipAddress.get()).replace(".", "-")
let acmeChalDomain = api.Domain("_acme-challenge." & baseDomain)
let ip4Domain = api.Domain(dashedIpAddr & "." & baseDomain)
debug "Waiting for DNS record to be set", ip = ip4Domain, acme = acmeChalDomain
let dnsSet = await checkDNSRecords(
self.config.nameResolver, self.config.ipAddress.get(), baseDomain, keyAuth
)
if not dnsSet:
error "DNS records not set"
return false
trace "Notifying challenge completion to ACME and downloading cert"
let certKeyPair = KeyPair.random(PKScheme.RSA, self.rng[]).get()
let certificate =
await acmeClient.getCertificate(domain, certKeyPair, dns01Challenge)
let derPrivKey = certKeyPair.seckey.rsakey.getBytes.valueOr:
raise newException(AutoTLSError, "Unable to get TLS private key")
let pemPrivKey: string = derPrivKey.pemEncode("PRIVATE KEY")
debug "autotls cert", pemPrivKey = pemPrivKey, cert = certificate.rawCertificate
trace "Installing certificate"
let newCert =
try:
AutotlsCert.new(
TLSCertificate.init(certificate.rawCertificate),
TLSPrivateKey.init(pemPrivKey),
asMoment(certificate.certificateExpiry),
)
except TLSStreamProtocolError:
error "Could not parse downloaded certificates"
return false
self.cert = Opt.some(newCert)
self.certReady.fire()
trace "Certificate installed"
true
proc hasTcpStarted(switch: Switch): bool =
switch.transports.filterIt(it of TcpTransport and it.running).len == 0
proc tryIssueCertificate(self: AutotlsService) {.async: (raises: [CancelledError]).} =
for _ in 0 ..< self.config.issueRetries:
try:
if await self.issueCertificate():
return
except CancelledError as exc:
raise exc
except CatchableError as exc:
error "Failed to issue certificate", err = exc.msg
await sleepAsync(self.config.issueRetryTime)
error "Failed to issue certificate"
method run*(
self: AutotlsService, switch: Switch
) {.async: (raises: [CancelledError]).} =
trace "Starting Autotls management"
self.running.fire()
self.peerInfo = switch.peerInfo
# ensure that there's at least one TcpTransport running
# for communicating with autotls broker
if switch.hasTcpStarted():
error "Could not find a running TcpTransport in switch"
return
heartbeat "Certificate Management", self.config.renewCheckTime:
if self.cert.isNone():
await self.tryIssueCertificate()
# AutotlsService will renew the cert 1h before it expires
let cert = self.cert.get
let waitTime = cert.expiry - Moment.now - self.config.renewBufferTime
if waitTime <= self.config.renewBufferTime:
await self.tryIssueCertificate()
method stop*(
self: AutotlsService, switch: Switch
): Future[bool] {.async: (raises: [CancelledError]).} =
let hasBeenStopped = await procCall Service(self).stop(switch)
if hasBeenStopped:
if not self.acmeClient.isNil():
await self.acmeClient.close()
if not self.brokerClient.isNil():
await self.brokerClient.close()
if not self.managerFut.isNil():
await self.managerFut.cancelAndWait()
self.managerFut = nil
return hasBeenStopped

82
libp2p/autotls/utils.nim Normal file
View File

@@ -0,0 +1,82 @@
# Nim-Libp2p
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
{.push public.}
import chronos, chronicles
import ../errors
logScope:
topics = "libp2p utils"
const
DefaultDnsRetries = 3
DefaultDnsRetryTime = 1.seconds
type AutoTLSError* = object of LPError
when defined(libp2p_autotls_support):
import strutils
from times import DateTime, toTime, toUnix
import stew/base36
import
../peerid,
../multihash,
../cid,
../multicodec,
../nameresolving/nameresolver,
./acme/client
proc asMoment*(dt: DateTime): Moment =
let unixTime: int64 = dt.toTime.toUnix
return Moment.init(unixTime, Second)
proc encodePeerId*(peerId: PeerId): string {.raises: [AutoTLSError].} =
var mh: MultiHash
let decodeResult = MultiHash.decode(peerId.data, mh)
if decodeResult.isErr() or decodeResult.get() == -1:
raise
newException(AutoTLSError, "Failed to decode PeerId: invalid multihash format")
let cidResult = Cid.init(CIDv1, multiCodec("libp2p-key"), mh)
if cidResult.isErr():
raise newException(AutoTLSError, "Failed to initialize CID from multihash")
return Base36.encode(cidResult.get().data.buffer)
proc checkDNSRecords*(
nameResolver: NameResolver,
ipAddress: IpAddress,
baseDomain: api.Domain,
keyAuth: KeyAuthorization,
retries: int = DefaultDnsRetries,
): Future[bool] {.async: (raises: [AutoTLSError, CancelledError]).} =
# if my ip address is 100.10.10.3 then the ip4Domain will be:
# 100-10-10-3.{peerIdBase36}.libp2p.direct
# and acme challenge TXT domain will be:
# _acme-challenge.{peerIdBase36}.libp2p.direct
let dashedIpAddr = ($ipAddress).replace(".", "-")
let acmeChalDomain = api.Domain("_acme-challenge." & baseDomain)
let ip4Domain = api.Domain(dashedIpAddr & "." & baseDomain)
var txt: seq[string]
var ip4: seq[TransportAddress]
for _ in 0 .. retries:
txt = await nameResolver.resolveTxt(acmeChalDomain)
try:
ip4 = await nameResolver.resolveIp(ip4Domain, 0.Port)
except CancelledError as exc:
raise exc
except CatchableError as exc:
error "Failed to resolve IP", description = exc.msg # retry
if txt.len > 0 and txt[0] == keyAuth and ip4.len > 0:
return true
await sleepAsync(DefaultDnsRetryTime)
return false

View File

@@ -23,26 +23,45 @@ import
stream/connection,
multiaddress,
crypto/crypto,
transports/[transport, tcptransport, memorytransport],
transports/[transport, tcptransport, wstransport, memorytransport],
muxers/[muxer, mplex/mplex, yamux/yamux],
protocols/[identify, secure/secure, secure/noise, rendezvous],
protocols/connectivity/[autonat/server, relay/relay, relay/client, relay/rtransport],
protocols/connectivity/[
autonat/server,
autonatv2/server,
autonatv2/service,
autonatv2/client,
relay/relay,
relay/client,
relay/rtransport,
],
connmanager,
upgrademngrs/muxedupgrade,
observedaddrmanager,
autotls/service,
nameresolving/nameresolver,
errors,
utility
import services/wildcardresolverservice
export switch, peerid, peerinfo, connection, multiaddress, crypto, errors
export
switch, peerid, peerinfo, connection, multiaddress, crypto, errors, TLSPrivateKey,
TLSCertificate, TLSFlags, ServerFlags
const MemoryAutoAddress* = memorytransport.MemoryAutoAddress
type
TransportProvider* {.public.} =
TransportProvider* {.deprecated: "Use TransportBuilder instead".} =
proc(upgr: Upgrade, privateKey: PrivateKey): Transport {.gcsafe, raises: [].}
TransportBuilder* {.public.} =
proc(config: TransportConfig): Transport {.gcsafe, raises: [].}
TransportConfig* = ref object
upgr*: Upgrade
privateKey*: PrivateKey
autotls*: AutotlsService
SecureProtocol* {.pure.} = enum
Noise
@@ -51,7 +70,7 @@ type
addresses: seq[MultiAddress]
secureManagers: seq[SecureProtocol]
muxers: seq[MuxerProvider]
transports: seq[TransportProvider]
transports: seq[TransportBuilder]
rng: ref HmacDrbgContext
maxConnections: int
maxIn: int
@@ -63,6 +82,10 @@ type
nameResolver: NameResolver
peerStoreCapacity: Opt[int]
autonat: bool
autonatV2ServerConfig: Opt[AutonatV2Config]
autonatV2Client: AutonatV2Client
autonatV2ServiceConfig: AutonatV2ServiceConfig
autotls: AutotlsService
circuitRelay: Relay
rdv: RendezVous
services: seq[Service]
@@ -147,8 +170,23 @@ proc withNoise*(b: SwitchBuilder): SwitchBuilder {.public.} =
b
proc withTransport*(
b: SwitchBuilder, prov: TransportProvider
b: SwitchBuilder, prov: TransportBuilder
): SwitchBuilder {.public.} =
## Use a custom transport
runnableExamples:
let switch = SwitchBuilder
.new()
.withTransport(
proc(config: TransportConfig): Transport =
TcpTransport.new(flags, config.upgr)
)
.build()
b.transports.add(prov)
b
proc withTransport*(
b: SwitchBuilder, prov: TransportProvider
): SwitchBuilder {.deprecated: "Use TransportBuilder instead".} =
## Use a custom transport
runnableExamples:
let switch = SwitchBuilder
@@ -158,15 +196,30 @@ proc withTransport*(
TcpTransport.new(flags, upgr)
)
.build()
b.transports.add(prov)
b
let tBuilder: TransportBuilder = proc(config: TransportConfig): Transport =
prov(config.upgr, config.privateKey)
b.withTransport(tBuilder)
proc withTcpTransport*(
b: SwitchBuilder, flags: set[ServerFlags] = {}
): SwitchBuilder {.public.} =
b.withTransport(
proc(upgr: Upgrade, privateKey: PrivateKey): Transport =
TcpTransport.new(flags, upgr)
proc(config: TransportConfig): Transport =
TcpTransport.new(flags, config.upgr)
)
proc withWsTransport*(
b: SwitchBuilder,
tlsPrivateKey: TLSPrivateKey = nil,
tlsCertificate: TLSCertificate = nil,
tlsFlags: set[TLSFlags] = {},
flags: set[ServerFlags] = {},
): SwitchBuilder =
b.withTransport(
proc(config: TransportConfig): Transport =
WsTransport.new(
config.upgr, tlsPrivateKey, tlsCertificate, config.autotls, tlsFlags, flags
)
)
when defined(libp2p_quic_support):
@@ -174,14 +227,14 @@ when defined(libp2p_quic_support):
proc withQuicTransport*(b: SwitchBuilder): SwitchBuilder {.public.} =
b.withTransport(
proc(upgr: Upgrade, privateKey: PrivateKey): Transport =
QuicTransport.new(upgr, privateKey)
proc(config: TransportConfig): Transport =
QuicTransport.new(config.upgr, config.privateKey)
)
proc withMemoryTransport*(b: SwitchBuilder): SwitchBuilder {.public.} =
b.withTransport(
proc(upgr: Upgrade, privateKey: PrivateKey): Transport =
MemoryTransport.new(upgr)
proc(config: TransportConfig): Transport =
MemoryTransport.new(config.upgr)
)
proc withRng*(b: SwitchBuilder, rng: ref HmacDrbgContext): SwitchBuilder {.public.} =
@@ -238,6 +291,26 @@ proc withAutonat*(b: SwitchBuilder): SwitchBuilder =
b.autonat = true
b
proc withAutonatV2Server*(
b: SwitchBuilder, config: AutonatV2Config = AutonatV2Config.new()
): SwitchBuilder =
b.autonatV2ServerConfig = Opt.some(config)
b
proc withAutonatV2*(
b: SwitchBuilder, serviceConfig = AutonatV2ServiceConfig.new()
): SwitchBuilder =
b.autonatV2Client = AutonatV2Client.new(b.rng)
b.autonatV2ServiceConfig = serviceConfig
b
when defined(libp2p_autotls_support):
proc withAutotls*(
b: SwitchBuilder, config: AutotlsConfig = AutotlsConfig.new()
): SwitchBuilder {.public.} =
b.autotls = AutotlsService.new(config = config)
b
proc withCircuitRelay*(b: SwitchBuilder, r: Relay = Relay.new()): SwitchBuilder =
b.circuitRelay = r
b
@@ -289,10 +362,17 @@ proc build*(b: SwitchBuilder): Switch {.raises: [LPError], public.} =
ms = MultistreamSelect.new()
muxedUpgrade = MuxedUpgrade.new(b.muxers, secureManagerInstances, ms)
if not b.autotls.isNil():
b.services.insert(b.autotls, 0)
let transports = block:
var transports: seq[Transport]
for tProvider in b.transports:
transports.add(tProvider(muxedUpgrade, seckey))
transports.add(
tProvider(
TransportConfig(upgr: muxedUpgrade, privateKey: seckey, autotls: b.autotls)
)
)
transports
if b.secureManagers.len == 0:
@@ -310,6 +390,13 @@ proc build*(b: SwitchBuilder): Switch {.raises: [LPError], public.} =
if b.enableWildcardResolver:
b.services.insert(WildcardAddressResolverService.new(), 0)
if not isNil(b.autonatV2Client):
b.services.add(
AutonatV2Service.new(
b.rng, client = b.autonatV2Client, config = b.autonatV2ServiceConfig
)
)
let switch = newSwitch(
peerInfo = peerInfo,
transports = transports,
@@ -323,9 +410,15 @@ proc build*(b: SwitchBuilder): Switch {.raises: [LPError], public.} =
switch.mount(identify)
if not isNil(b.autonatV2Client):
b.autonatV2Client.setup(switch)
switch.mount(b.autonatV2Client)
b.autonatV2ServerConfig.withValue(config):
switch.mount(AutonatV2.new(switch, config = config))
if b.autonat:
let autonat = Autonat.new(switch)
switch.mount(autonat)
switch.mount(Autonat.new(switch))
if not isNil(b.circuitRelay):
if b.circuitRelay of RelayClient:
@@ -339,13 +432,78 @@ proc build*(b: SwitchBuilder): Switch {.raises: [LPError], public.} =
return switch
proc newStandardSwitch*(
type TransportType* {.pure.} = enum
QUIC
TCP
Memory
proc newStandardSwitchBuilder*(
privKey = none(PrivateKey),
addrs: MultiAddress | seq[MultiAddress] =
MultiAddress.init("/ip4/127.0.0.1/tcp/0").expect("valid address"),
secureManagers: openArray[SecureProtocol] = [SecureProtocol.Noise],
addrs: MultiAddress | seq[MultiAddress] = newSeq[MultiAddress](),
transport: TransportType = TransportType.TCP,
transportFlags: set[ServerFlags] = {},
rng = newRng(),
secureManagers: openArray[SecureProtocol] = [SecureProtocol.Noise],
inTimeout: Duration = 5.minutes,
outTimeout: Duration = 5.minutes,
maxConnections = MaxConnections,
maxIn = -1,
maxOut = -1,
maxConnsPerPeer = MaxConnectionsPerPeer,
nameResolver: NameResolver = nil,
sendSignedPeerRecord = false,
peerStoreCapacity = 1000,
): SwitchBuilder {.raises: [LPError], public.} =
## Helper for common switch configurations.
var b = SwitchBuilder
.new()
.withRng(rng)
.withSignedPeerRecord(sendSignedPeerRecord)
.withMaxConnections(maxConnections)
.withMaxIn(maxIn)
.withMaxOut(maxOut)
.withMaxConnsPerPeer(maxConnsPerPeer)
.withPeerStore(capacity = peerStoreCapacity)
.withNameResolver(nameResolver)
.withNoise()
var addrs =
when addrs is MultiAddress:
@[addrs]
else:
addrs
case transport
of TransportType.QUIC:
when defined(libp2p_quic_support):
if addrs.len == 0:
addrs = @[MultiAddress.init("/ip4/0.0.0.0/udp/0/quic-v1").tryGet()]
b = b.withQuicTransport().withAddresses(addrs)
else:
raiseAssert "QUIC not supported in this build"
of TransportType.TCP:
if addrs.len == 0:
addrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/0").tryGet()]
b = b.withTcpTransport(transportFlags).withAddresses(addrs).withMplex(
inTimeout, outTimeout
)
of TransportType.Memory:
if addrs.len == 0:
addrs = @[MultiAddress.init(MemoryAutoAddress).tryGet()]
b = b.withMemoryTransport().withAddresses(addrs).withMplex(inTimeout, outTimeout)
privKey.withValue(pkey):
b = b.withPrivateKey(pkey)
b
proc newStandardSwitch*(
privKey = none(PrivateKey),
addrs: MultiAddress | seq[MultiAddress] = newSeq[MultiAddress](),
transport: TransportType = TransportType.TCP,
transportFlags: set[ServerFlags] = {},
rng = newRng(),
secureManagers: openArray[SecureProtocol] = [SecureProtocol.Noise],
inTimeout: Duration = 5.minutes,
outTimeout: Duration = 5.minutes,
maxConnections = MaxConnections,
@@ -356,28 +514,21 @@ proc newStandardSwitch*(
sendSignedPeerRecord = false,
peerStoreCapacity = 1000,
): Switch {.raises: [LPError], public.} =
## Helper for common switch configurations.
let addrs =
when addrs is MultiAddress:
@[addrs]
else:
addrs
var b = SwitchBuilder
.new()
.withAddresses(addrs)
.withRng(rng)
.withSignedPeerRecord(sendSignedPeerRecord)
.withMaxConnections(maxConnections)
.withMaxIn(maxIn)
.withMaxOut(maxOut)
.withMaxConnsPerPeer(maxConnsPerPeer)
.withPeerStore(capacity = peerStoreCapacity)
.withMplex(inTimeout, outTimeout)
.withTcpTransport(transportFlags)
.withNameResolver(nameResolver)
.withNoise()
privKey.withValue(pkey):
b = b.withPrivateKey(pkey)
b.build()
newStandardSwitchBuilder(
privKey = privKey,
addrs = addrs,
transport = transport,
transportFlags = transportFlags,
rng = rng,
secureManagers = secureManagers,
inTimeout = inTimeout,
outTimeout = outTimeout,
maxConnections = maxConnections,
maxIn = maxIn,
maxOut = maxOut,
maxConnsPerPeer = maxConnsPerPeer,
nameResolver = nameResolver,
sendSignedPeerRecord = sendSignedPeerRecord,
peerStoreCapacity = peerStoreCapacity,
)
.build()

View File

@@ -10,10 +10,12 @@
## This module implementes CID (Content IDentifier).
{.push raises: [].}
{.used.}
import tables, hashes
import multibase, multicodec, multihash, vbuffer, varint, results
import stew/base58
import ./utils/sequninit
export results
@@ -122,7 +124,7 @@ proc decode(data: openArray[char]): Result[Cid, CidError] =
return err(CidError.Incorrect)
if len(data) == 46:
if data[0] == 'Q' and data[1] == 'm':
buffer = newSeq[byte](BTCBase58.decodedLength(len(data)))
buffer = newSeqUninit[byte](BTCBase58.decodedLength(len(data)))
if BTCBase58.decode(data, buffer, plen) != Base58Status.Success:
return err(CidError.Incorrect)
buffer.setLen(plen)
@@ -130,7 +132,7 @@ proc decode(data: openArray[char]): Result[Cid, CidError] =
let length = MultiBase.decodedLength(data[0], len(data))
if length == -1:
return err(CidError.Incorrect)
buffer = newSeq[byte](length)
buffer = newSeqUninit[byte](length)
if MultiBase.decode(data, buffer, plen) != MultiBaseStatus.Success:
return err(CidError.Incorrect)
buffer.setLen(plen)

View File

@@ -140,7 +140,7 @@ proc triggerConnEvent*(
except CancelledError as exc:
raise exc
except CatchableError as exc:
warn "Exception in triggerConnEvents",
warn "Exception in triggerConnEvent",
description = exc.msg, peer = peerId, event = $event
proc addPeerEventHandler*(
@@ -186,7 +186,7 @@ proc expectConnection*(
if key in c.expectedConnectionsOverLimit:
raise newException(
AlreadyExpectingConnectionError,
"Already expecting an incoming connection from that peer",
"Already expecting an incoming connection from that peer: " & shortLog(p),
)
let future = Future[Muxer].Raising([CancelledError]).init()

View File

@@ -11,6 +11,7 @@
{.push raises: [].}
from strutils import split, strip, cmpIgnoreCase
import ../utils/sequninit
const libp2p_pki_schemes* {.strdefine.} = "rsa,ed25519,secp256k1,ecnist"
@@ -176,7 +177,7 @@ proc shuffle*[T](rng: ref HmacDrbgContext, x: var openArray[T]) =
if x.len == 0:
return
var randValues = newSeqUninitialized[byte](len(x) * 2)
var randValues = newSeqUninit[byte](len(x) * 2)
hmacDrbgGenerate(rng[], randValues)
for i in countdown(x.high, 1):
@@ -873,7 +874,7 @@ proc stretchKeys*(
var seed = "key expansion"
result.macsize = 20
let length = result.ivsize + result.keysize + result.macsize
result.data = newSeq[byte](2 * length)
result.data = newSeqUninit[byte](2 * length)
if hashType == "SHA256":
makeSecret(result.data, HMAC[sha256], sharedSecret, seed)
@@ -904,7 +905,7 @@ template macOpenArray*(secret: Secret, id: int): untyped =
proc iv*(secret: Secret, id: int): seq[byte] {.inline.} =
## Get array of bytes with with initial vector.
result = newSeq[byte](secret.ivsize)
result = newSeqUninit[byte](secret.ivsize)
var offset =
if id == 0:
0
@@ -913,7 +914,7 @@ proc iv*(secret: Secret, id: int): seq[byte] {.inline.} =
copyMem(addr result[0], unsafeAddr secret.data[offset], secret.ivsize)
proc key*(secret: Secret, id: int): seq[byte] {.inline.} =
result = newSeq[byte](secret.keysize)
result = newSeqUninit[byte](secret.keysize)
var offset =
if id == 0:
0
@@ -923,7 +924,7 @@ proc key*(secret: Secret, id: int): seq[byte] {.inline.} =
copyMem(addr result[0], unsafeAddr secret.data[offset], secret.keysize)
proc mac*(secret: Secret, id: int): seq[byte] {.inline.} =
result = newSeq[byte](secret.macsize)
result = newSeqUninit[byte](secret.macsize)
var offset =
if id == 0:
0

View File

@@ -23,6 +23,7 @@ import minasn1
export minasn1.Asn1Error
import stew/ctops
import results
import ../utils/sequninit
import ../utility
@@ -458,7 +459,7 @@ proc getBytes*(seckey: EcPrivateKey): EcResult[seq[byte]] =
if isNil(seckey):
return err(EcKeyIncorrectError)
if seckey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
var res = newSeqUninit[byte](0)
let length = ?seckey.toBytes(res)
res.setLen(length)
discard ?seckey.toBytes(res)
@@ -471,7 +472,7 @@ proc getBytes*(pubkey: EcPublicKey): EcResult[seq[byte]] =
if isNil(pubkey):
return err(EcKeyIncorrectError)
if pubkey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
var res = newSeqUninit[byte](0)
let length = ?pubkey.toBytes(res)
res.setLen(length)
discard ?pubkey.toBytes(res)
@@ -483,7 +484,7 @@ proc getBytes*(sig: EcSignature): EcResult[seq[byte]] =
## Serialize EC signature ``sig`` to ASN.1 DER binary form and return it.
if isNil(sig):
return err(EcSignatureError)
var res = newSeq[byte]()
var res = newSeqUninit[byte](0)
let length = ?sig.toBytes(res)
res.setLen(length)
discard ?sig.toBytes(res)
@@ -494,7 +495,7 @@ proc getRawBytes*(seckey: EcPrivateKey): EcResult[seq[byte]] =
if isNil(seckey):
return err(EcKeyIncorrectError)
if seckey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
var res = newSeqUninit[byte](0)
let length = ?seckey.toRawBytes(res)
res.setLen(length)
discard ?seckey.toRawBytes(res)
@@ -507,7 +508,7 @@ proc getRawBytes*(pubkey: EcPublicKey): EcResult[seq[byte]] =
if isNil(pubkey):
return err(EcKeyIncorrectError)
if pubkey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
var res = newSeqUninit[byte](0)
let length = ?pubkey.toRawBytes(res)
res.setLen(length)
discard ?pubkey.toRawBytes(res)
@@ -519,7 +520,7 @@ proc getRawBytes*(sig: EcSignature): EcResult[seq[byte]] =
## Serialize EC signature ``sig`` to raw binary form and return it.
if isNil(sig):
return err(EcSignatureError)
var res = newSeq[byte]()
var res = newSeqUninit[byte](0)
let length = ?sig.toBytes(res)
res.setLen(length)
discard ?sig.toBytes(res)
@@ -929,7 +930,7 @@ proc getSecret*(pubkey: EcPublicKey, seckey: EcPrivateKey): seq[byte] =
var data: array[Secret521Length, byte]
let res = toSecret(pubkey, seckey, data)
if res > 0:
result = newSeq[byte](res)
result = newSeqUninit[byte](res)
copyMem(addr result[0], addr data[0], res)
proc sign*[T: byte | char](
@@ -943,7 +944,7 @@ proc sign*[T: byte | char](
var impl = ecGetDefault()
if seckey.key.curve in EcSupportedCurvesCint:
var sig = new EcSignature
sig.buffer = newSeq[byte](256)
sig.buffer = newSeqUninit[byte](256)
var kv = addr sha256Vtable
kv.init(addr hc.vtable)
if len(message) > 0:

View File

@@ -17,6 +17,7 @@ export results
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
import nimcrypto/utils as ncrutils
import ../utility
import ../utils/sequninit
type
Asn1Error* {.pure.} = enum
@@ -679,15 +680,15 @@ proc init*(t: typedesc[Asn1Buffer], data: string): Asn1Buffer =
proc init*(t: typedesc[Asn1Buffer]): Asn1Buffer =
## Initialize empty ``Asn1Buffer``.
Asn1Buffer(buffer: newSeq[byte]())
Asn1Buffer(buffer: newSeqUninit[byte](0))
proc init*(t: typedesc[Asn1Composite], tag: Asn1Tag): Asn1Composite =
## Initialize ``Asn1Composite`` with tag ``tag``.
Asn1Composite(tag: tag, buffer: newSeq[byte]())
Asn1Composite(tag: tag, buffer: newSeqUninit[byte](0))
proc init*(t: typedesc[Asn1Composite], idx: int): Asn1Composite =
## Initialize ``Asn1Composite`` with tag context-specific id ``id``.
Asn1Composite(tag: Asn1Tag.Context, idx: idx, buffer: newSeq[byte]())
Asn1Composite(tag: Asn1Tag.Context, idx: idx, buffer: newSeqUninit[byte](0))
proc `$`*(buffer: Asn1Buffer): string =
## Return string representation of ``buffer``.

View File

@@ -21,6 +21,7 @@ import results
import stew/ctops
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
import nimcrypto/utils as ncrutils
import ../utils/sequninit
export Asn1Error, results
@@ -124,7 +125,7 @@ proc random*[T: RsaKP](
length = eko + ((bits + 7) shr 3)
let res = new T
res.buffer = newSeq[byte](length)
res.buffer = newSeqUninit[byte](length)
var keygen = rsaKeygenGetDefault()
@@ -169,7 +170,7 @@ proc copy*[T: RsaPKI](key: T): T =
key.seck.dqlen.uint + key.seck.iqlen.uint + key.pubk.nlen.uint +
key.pubk.elen.uint + key.pexplen.uint
result = new RsaPrivateKey
result.buffer = newSeq[byte](length)
result.buffer = newSeqUninit[byte](length)
let po: uint = 0
let qo = po + key.seck.plen
let dpo = qo + key.seck.qlen
@@ -207,7 +208,7 @@ proc copy*[T: RsaPKI](key: T): T =
if len(key.buffer) > 0:
let length = key.key.nlen + key.key.elen
result = new RsaPublicKey
result.buffer = newSeq[byte](length)
result.buffer = newSeqUninit[byte](length)
let no = 0
let eo = no + key.key.nlen
copyMem(addr result.buffer[no], key.key.n, key.key.nlen)
@@ -226,7 +227,7 @@ proc getPublicKey*(key: RsaPrivateKey): RsaPublicKey =
doAssert(not isNil(key))
let length = key.pubk.nlen + key.pubk.elen
result = new RsaPublicKey
result.buffer = newSeq[byte](length)
result.buffer = newSeqUninit[byte](length)
result.key.n = addr result.buffer[0]
result.key.e = addr result.buffer[key.pubk.nlen]
copyMem(addr result.buffer[0], cast[pointer](key.pubk.n), key.pubk.nlen)
@@ -357,7 +358,7 @@ proc getBytes*(key: RsaPrivateKey): RsaResult[seq[byte]] =
## return it.
if isNil(key):
return err(RsaKeyIncorrectError)
var res = newSeq[byte](4096)
var res = newSeqUninit[byte](4096)
let length = ?key.toBytes(res)
if length > 0:
res.setLen(length)
@@ -370,7 +371,7 @@ proc getBytes*(key: RsaPublicKey): RsaResult[seq[byte]] =
## return it.
if isNil(key):
return err(RsaKeyIncorrectError)
var res = newSeq[byte](4096)
var res = newSeqUninit[byte](4096)
let length = ?key.toBytes(res)
if length > 0:
res.setLen(length)
@@ -382,7 +383,7 @@ proc getBytes*(sig: RsaSignature): RsaResult[seq[byte]] =
## Serialize RSA signature ``sig`` to raw binary form and return it.
if isNil(sig):
return err(RsaSignatureError)
var res = newSeq[byte](4096)
var res = newSeqUninit[byte](4096)
let length = ?sig.toBytes(res)
if length > 0:
res.setLen(length)
@@ -753,7 +754,7 @@ proc sign*[T: byte | char](
var hash: array[32, byte]
let impl = rsaPkcs1SignGetDefault()
var res = new RsaSignature
res.buffer = newSeq[byte]((key.seck.nBitlen + 7) shr 3)
res.buffer = newSeqUninit[byte]((key.seck.nBitlen + 7) shr 3)
var kv = addr sha256Vtable
kv.init(addr hc.vtable)
if len(message) > 0:

View File

@@ -11,6 +11,7 @@
import bearssl/rand
import secp256k1, results, stew/byteutils, nimcrypto/[hash, sha2]
import ../utils/sequninit
export sha2, results, rand
@@ -85,8 +86,9 @@ proc init*(sig: var SkSignature, data: string): SkResult[void] =
var buffer: seq[byte]
try:
buffer = hexToSeqByte(data)
except ValueError:
return err("secp: Hex to bytes failed")
except ValueError as e:
let errMsg = "secp: Hex to bytes failed: " & e.msg
return err(errMsg.cstring)
init(sig, buffer)
proc init*(t: typedesc[SkPrivateKey], data: openArray[byte]): SkResult[SkPrivateKey] =
@@ -181,7 +183,7 @@ proc getBytes*(key: SkPublicKey): seq[byte] {.inline.} =
proc getBytes*(sig: SkSignature): seq[byte] {.inline.} =
## Serialize Secp256k1 `signature` and return it.
result = newSeq[byte](72)
result = newSeqUninit[byte](72)
let length = toBytes(sig, result)
result.setLen(length)

File diff suppressed because it is too large Load Diff

View File

@@ -1,156 +0,0 @@
# Nim-Libp2p
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
## This module implements Pool of StreamTransport.
import chronos
const DefaultPoolSize* = 8 ## Default pool size
type
ConnectionFlags = enum
None
Busy
PoolItem = object
transp*: StreamTransport
flags*: set[ConnectionFlags]
PoolState = enum
Connecting
Connected
Closing
Closed
TransportPool* = ref object ## Transports pool object
transports: seq[PoolItem]
busyCount: int
state: PoolState
bufferSize: int
event: AsyncEvent
TransportPoolError* = object of AsyncError
proc waitAll[T](futs: seq[Future[T]]): Future[void] =
## Performs waiting for all Future[T].
var counter = len(futs)
var retFuture = newFuture[void]("connpool.waitAllConnections")
proc cb(udata: pointer) =
dec(counter)
if counter == 0:
retFuture.complete()
for fut in futs:
fut.addCallback(cb)
return retFuture
proc newPool*(
address: TransportAddress,
poolsize: int = DefaultPoolSize,
bufferSize = DefaultStreamBufferSize,
): Future[TransportPool] {.async: (raises: [CancelledError]).} =
## Establish pool of connections to address ``address`` with size
## ``poolsize``.
var pool = new TransportPool
pool.bufferSize = bufferSize
pool.transports = newSeq[PoolItem](poolsize)
var conns = newSeq[Future[StreamTransport]](poolsize)
pool.state = Connecting
for i in 0 ..< poolsize:
conns[i] = connect(address, bufferSize)
# Waiting for all connections to be established.
await waitAll(conns)
# Checking connections and preparing pool.
for i in 0 ..< poolsize:
if conns[i].failed:
raise conns[i].error
else:
let transp = conns[i].read()
let item = PoolItem(transp: transp)
pool.transports[i] = item
# Setup available connections event
pool.event = newAsyncEvent()
pool.state = Connected
result = pool
proc acquire*(
pool: TransportPool
): Future[StreamTransport] {.async: (raises: [CancelledError, TransportPoolError]).} =
## Acquire non-busy connection from pool ``pool``.
var transp: StreamTransport
if pool.state in {Connected}:
while true:
if pool.busyCount < len(pool.transports):
for conn in pool.transports.mitems():
if Busy notin conn.flags:
conn.flags.incl(Busy)
inc(pool.busyCount)
transp = conn.transp
break
else:
await pool.event.wait()
pool.event.clear()
if not isNil(transp):
break
else:
raise newException(TransportPoolError, "Pool is not ready!")
result = transp
proc release*(
pool: TransportPool, transp: StreamTransport
) {.async: (raises: [TransportPoolError]).} =
## Release connection ``transp`` back to pool ``pool``.
if pool.state in {Connected, Closing}:
var found = false
for conn in pool.transports.mitems():
if conn.transp == transp:
conn.flags.excl(Busy)
dec(pool.busyCount)
pool.event.fire()
found = true
break
if not found:
raise newException(TransportPoolError, "Transport not bound to pool!")
else:
raise newException(TransportPoolError, "Pool is not ready!")
proc join*(
pool: TransportPool
) {.async: (raises: [TransportPoolError, CancelledError]).} =
## Waiting for all connection to become available.
if pool.state in {Connected, Closing}:
while true:
if pool.busyCount == 0:
break
else:
await pool.event.wait()
pool.event.clear()
elif pool.state == Connecting:
raise newException(TransportPoolError, "Pool is not ready!")
proc close*(
pool: TransportPool
) {.async: (raises: [TransportPoolError, CancelledError]).} =
## Closes transports pool ``pool`` and release all resources.
if pool.state == Connected:
pool.state = Closing
# Waiting for all transports to become available.
await pool.join()
# Closing all transports
var pending = newSeq[Future[void]](len(pool.transports))
for i in 0 ..< len(pool.transports):
let transp = pool.transports[i].transp
transp.close()
pending[i] = transp.join()
# Waiting for all transports to be closed
await waitAll(pending)
# Mark pool as `Closed`.
pool.state = Closed

View File

@@ -11,7 +11,7 @@
import chronos
import results
import peerid, stream/connection, transports/transport
import peerid, stream/connection, transports/transport, muxers/muxer
export results
@@ -49,6 +49,22 @@ method dial*(
doAssert(false, "[Dial.dial] abstract method not implemented!")
method dialAndUpgrade*(
self: Dial,
peerId: Opt[PeerId],
hostname: string,
addrs: MultiAddress,
dir = Direction.Out,
): Future[Muxer] {.base, async: (raises: [CancelledError]).} =
doAssert(false, "[Dial.dialAndUpgrade] abstract method not implemented!")
method dialAndUpgrade*(
self: Dial, peerId: Opt[PeerId], addrs: seq[MultiAddress], dir = Direction.Out
): Future[Muxer] {.
base, async: (raises: [CancelledError, MaError, TransportAddressError, LPError])
.} =
doAssert(false, "[Dial.dialAndUpgrade] abstract method not implemented!")
method dial*(
self: Dial,
peerId: PeerId,
@@ -65,6 +81,11 @@ method dial*(
method addTransport*(self: Dial, transport: Transport) {.base.} =
doAssert(false, "[Dial.addTransport] abstract method not implemented!")
method negotiateStream*(
self: Dial, conn: Connection, protos: seq[string]
): Future[Connection] {.base, async: (raises: [CatchableError]).} =
doAssert(false, "[Dial.negotiateStream] abstract method not implemented!")
method tryDial*(
self: Dial, peerId: PeerId, addrs: seq[MultiAddress]
): Future[Opt[MultiAddress]] {.

View File

@@ -43,20 +43,20 @@ type Dialer* = ref object of Dial
peerStore: PeerStore
nameResolver: NameResolver
proc dialAndUpgrade(
method dialAndUpgrade*(
self: Dialer,
peerId: Opt[PeerId],
hostname: string,
address: MultiAddress,
addrs: MultiAddress,
dir = Direction.Out,
): Future[Muxer] {.async: (raises: [CancelledError]).} =
for transport in self.transports: # for each transport
if transport.handles(address): # check if it can dial it
trace "Dialing address", address, peerId = peerId.get(default(PeerId)), hostname
if transport.handles(addrs): # check if it can dial it
trace "Dialing address", addrs, peerId = peerId.get(default(PeerId)), hostname
let dialed =
try:
libp2p_total_dial_attempts.inc()
await transport.dial(hostname, address, peerId)
await transport.dial(hostname, addrs, peerId)
except CancelledError as exc:
trace "Dialing canceled",
description = exc.msg, peerId = peerId.get(default(PeerId))
@@ -104,12 +104,13 @@ proc expandDnsAddr(
): Future[seq[(MultiAddress, Opt[PeerId])]] {.
async: (raises: [CancelledError, MaError, TransportAddressError, LPError])
.} =
if not DNSADDR.matchPartial(address):
if not DNS.matchPartial(address):
return @[(address, peerId)]
if isNil(self.nameResolver):
info "Can't resolve DNSADDR without NameResolver", ma = address
return @[]
trace "Start trying to resolve addresses"
let
toResolve =
if peerId.isSome:
@@ -121,21 +122,24 @@ proc expandDnsAddr(
address
resolved = await self.nameResolver.resolveDnsAddr(toResolve)
debug "resolved addresses",
originalAddresses = toResolve, resolvedAddresses = resolved
for resolvedAddress in resolved:
let lastPart = resolvedAddress[^1].tryGet()
if lastPart.protoCode == Result[MultiCodec, string].ok(multiCodec("p2p")):
var peerIdBytes: seq[byte]
try:
peerIdBytes = lastPart.protoArgument().tryGet()
except ResultError[string]:
raiseAssert "expandDnsAddr failed in protoArgument: " & getCurrentExceptionMsg()
except ResultError[string] as e:
raiseAssert "expandDnsAddr failed in expandDnsAddr protoArgument: " & e.msg
let addrPeerId = PeerId.init(peerIdBytes).tryGet()
result.add((resolvedAddress[0 ..^ 2].tryGet(), Opt.some(addrPeerId)))
else:
result.add((resolvedAddress, peerId))
proc dialAndUpgrade(
method dialAndUpgrade*(
self: Dialer, peerId: Opt[PeerId], addrs: seq[MultiAddress], dir = Direction.Out
): Future[Muxer] {.
async: (raises: [CancelledError, MaError, TransportAddressError, LPError])
@@ -145,7 +149,6 @@ proc dialAndUpgrade(
for rawAddress in addrs:
# resolve potential dnsaddr
let addresses = await self.expandDnsAddr(peerId, rawAddress)
for (expandedAddress, addrPeerId) in addresses:
# DNS resolution
let
@@ -156,6 +159,11 @@ proc dialAndUpgrade(
else:
await self.nameResolver.resolveMAddress(expandedAddress)
debug "Expanded address and hostname",
expandedAddress = expandedAddress,
hostname = hostname,
resolvedAddresses = resolvedAddresses
for resolvedAddress in resolvedAddresses:
result = await self.dialAndUpgrade(addrPeerId, hostname, resolvedAddress, dir)
if not isNil(result):
@@ -178,7 +186,7 @@ proc internalConnect(
dir = Direction.Out,
): Future[Muxer] {.async: (raises: [DialFailedError, CancelledError]).} =
if Opt.some(self.localPeerId) == peerId:
raise newException(DialFailedError, "can't dial self!")
raise newException(DialFailedError, "internalConnect can't dial self!")
# Ensure there's only one in-flight attempt per peer
let lock = self.dialLock.mgetOrPut(peerId.get(default(PeerId)), newAsyncLock())
@@ -186,8 +194,8 @@ proc internalConnect(
defer:
try:
lock.release()
except AsyncLockError:
raiseAssert "lock must have been acquired in line above"
except AsyncLockError as e:
raiseAssert "lock must have been acquired in line above: " & e.msg
if reuseConnection:
peerId.withValue(peerId):
@@ -198,7 +206,9 @@ proc internalConnect(
try:
self.connManager.getOutgoingSlot(forceDial)
except TooManyConnectionsError as exc:
raise newException(DialFailedError, exc.msg)
raise newException(
DialFailedError, "failed getOutgoingSlot in internalConnect: " & exc.msg, exc
)
let muxed =
try:
@@ -208,11 +218,15 @@ proc internalConnect(
raise exc
except CatchableError as exc:
slot.release()
raise newException(DialFailedError, exc.msg)
raise newException(
DialFailedError, "failed dialAndUpgrade in internalConnect: " & exc.msg, exc
)
slot.trackMuxer(muxed)
if isNil(muxed): # None of the addresses connected
raise newException(DialFailedError, "Unable to establish outgoing link")
raise newException(
DialFailedError, "Unable to establish outgoing link in internalConnect"
)
try:
self.connManager.storeMuxer(muxed)
@@ -228,7 +242,11 @@ proc internalConnect(
except CatchableError as exc:
trace "Failed to finish outgoing upgrade", description = exc.msg
await muxed.close()
raise newException(DialFailedError, "Failed to finish outgoing upgrade")
raise newException(
DialFailedError,
"Failed to finish outgoing upgrade in internalConnect: " & exc.msg,
exc,
)
method connect*(
self: Dialer,
@@ -260,21 +278,20 @@ method connect*(
if allowUnknownPeerId == false:
raise newException(
DialFailedError, "Address without PeerID and unknown peer id disabled!"
DialFailedError, "Address without PeerID and unknown peer id disabled in connect"
)
return
(await self.internalConnect(Opt.none(PeerId), @[address], false)).connection.peerId
proc negotiateStream(
method negotiateStream*(
self: Dialer, conn: Connection, protos: seq[string]
): Future[Connection] {.async: (raises: [CatchableError]).} =
trace "Negotiating stream", conn, protos
let selected = await MultistreamSelect.select(conn, protos)
if not protos.contains(selected):
await conn.closeWithEOF()
raise newException(DialFailedError, "Unable to select sub-protocol " & $protos)
raise newException(DialFailedError, "Unable to select sub-protocol: " & $protos)
return conn
method tryDial*(
@@ -289,13 +306,13 @@ method tryDial*(
try:
let mux = await self.dialAndUpgrade(Opt.some(peerId), addrs)
if mux.isNil():
raise newException(DialFailedError, "No valid multiaddress")
raise newException(DialFailedError, "No valid multiaddress in tryDial")
await mux.close()
return mux.connection.observedAddr
except CancelledError as exc:
raise exc
except CatchableError as exc:
raise newException(DialFailedError, exc.msg)
raise newException(DialFailedError, "tryDial failed: " & exc.msg, exc)
method dial*(
self: Dialer, peerId: PeerId, protos: seq[string]
@@ -309,14 +326,17 @@ method dial*(
try:
let stream = await self.connManager.getStream(peerId)
if stream.isNil:
raise newException(DialFailedError, "Couldn't get muxed stream")
raise newException(
DialFailedError,
"Couldn't get muxed stream in dial for peer_id: " & shortLog(peerId),
)
return await self.negotiateStream(stream, protos)
except CancelledError as exc:
trace "Dial canceled"
trace "Dial canceled", description = exc.msg
raise exc
except CatchableError as exc:
trace "Error dialing", description = exc.msg
raise newException(DialFailedError, exc.msg)
raise newException(DialFailedError, "failed dial existing: " & exc.msg)
method dial*(
self: Dialer,
@@ -347,17 +367,20 @@ method dial*(
stream = await self.connManager.getStream(conn)
if isNil(stream):
raise newException(DialFailedError, "Couldn't get muxed stream")
raise newException(
DialFailedError,
"Couldn't get muxed stream in new dial for remote_peer_id: " & shortLog(peerId),
)
return await self.negotiateStream(stream, protos)
except CancelledError as exc:
trace "Dial canceled", conn
trace "Dial canceled", conn, description = exc.msg
await cleanup()
raise exc
except CatchableError as exc:
debug "Error dialing", conn, description = exc.msg
await cleanup()
raise newException(DialFailedError, exc.msg)
raise newException(DialFailedError, "failed new dial: " & exc.msg, exc)
method addTransport*(self: Dialer, t: Transport) =
self.transports &= t

View File

@@ -113,7 +113,7 @@ proc add*(dm: DiscoveryManager, di: DiscoveryInterface) =
try:
query.peers.putNoWait(pa)
except AsyncQueueFullError as exc:
debug "Cannot push discovered peer to queue"
debug "Cannot push discovered peer to queue", description = exc.msg
proc request*(dm: DiscoveryManager, pa: PeerAttributes): DiscoveryQuery =
var query = DiscoveryQuery(attr: pa, peers: newAsyncQueue[PeerAttributes]())
@@ -159,7 +159,7 @@ proc stop*(query: DiscoveryQuery) =
query.finished = true
for r in query.futs:
if not r.finished():
r.cancel()
r.cancelSoon()
proc stop*(dm: DiscoveryManager) =
for q in dm.queries:
@@ -167,7 +167,7 @@ proc stop*(dm: DiscoveryManager) =
for i in dm.interfaces:
if isNil(i.advertiseLoop):
continue
i.advertiseLoop.cancel()
i.advertiseLoop.cancelSoon()
proc getPeer*(
query: DiscoveryQuery
@@ -179,7 +179,7 @@ proc getPeer*(
try:
await getter or allFinished(query.futs)
except CancelledError as exc:
getter.cancel()
getter.cancelSoon()
raise exc
if not finished(getter):

View File

@@ -27,7 +27,7 @@ macro checkFutures*[F](futs: seq[F], exclude: untyped = []): untyped =
quote:
for res in `futs`:
if res.failed:
let exc = res.readError()
let exc = res.error
# We still don't abort but warn
debug "A future has failed, enable trace logging for details",
error = exc.name
@@ -37,7 +37,7 @@ macro checkFutures*[F](futs: seq[F], exclude: untyped = []): untyped =
for res in `futs`:
block check:
if res.failed:
let exc = res.readError()
let exc = res.error
for i in 0 ..< `nexclude`:
if exc of `exclude`[i]:
trace "A future has failed", error = exc.name, description = exc.msg

View File

@@ -27,6 +27,7 @@ import
utility
import stew/[base58, base32, endians2]
export results, vbuffer, errors, utility
import ./utils/sequninit
logScope:
topics = "libp2p multiaddress"
@@ -223,7 +224,7 @@ proc p2pStB(s: string, vb: var VBuffer): bool =
proc p2pBtS(vb: var VBuffer, s: var string): bool =
## P2P address bufferToString() implementation.
var address = newSeq[byte]()
var address = newSeqUninit[byte](0)
if vb.readSeq(address) > 0:
var mh: MultiHash
if MultiHash.decode(address, mh).isOk:
@@ -232,7 +233,7 @@ proc p2pBtS(vb: var VBuffer, s: var string): bool =
proc p2pVB(vb: var VBuffer): bool =
## P2P address validateBuffer() implementation.
var address = newSeq[byte]()
var address = newSeqUninit[byte](0)
if vb.readSeq(address) > 0:
var mh: MultiHash
if MultiHash.decode(address, mh).isOk:
@@ -555,7 +556,7 @@ proc protoAddress*(ma: MultiAddress): MaResult[seq[byte]] =
##
## If current MultiAddress do not have argument value, then result array will
## be empty.
var buffer = newSeq[byte](len(ma.data.buffer))
var buffer = newSeqUninit[byte](len(ma.data.buffer))
let res = ?protoArgument(ma, buffer)
buffer.setLen(res)
ok(buffer)
@@ -569,7 +570,7 @@ proc protoArgument*(ma: MultiAddress): MaResult[seq[byte]] =
proc getPart(ma: MultiAddress, index: int): MaResult[MultiAddress] =
var header: uint64
var data = newSeq[byte]()
var data = newSeqUninit[byte](0)
var offset = 0
var vb = ma
var res: MultiAddress
@@ -643,7 +644,7 @@ proc `[]`*(ma: MultiAddress, slice: HSlice): MaResult[MultiAddress] {.inline.} =
iterator items*(ma: MultiAddress): MaResult[MultiAddress] =
## Iterates over all addresses inside of MultiAddress ``ma``.
var header: uint64
var data = newSeq[byte]()
var data = newSeqUninit[byte](0)
var vb = ma
while true:
if vb.data.isEmpty():
@@ -842,6 +843,14 @@ proc init*(
res.data.finish()
ok(res)
proc getPart*(ma: MultiAddress, codec: MultiCodec): MaResult[MultiAddress] =
## Returns the first multiaddress in ``value`` with codec ``codec``
for part in ma:
let part = ?part
if codec == ?part.protoCode:
return ok(part)
err("no such codec in multiaddress")
proc getProtocol(name: string): MAProtocol {.inline.} =
let mc = MultiCodec.codec(name)
if mc != InvalidMultiCodec:
@@ -1118,3 +1127,32 @@ proc getRepeatedField*(
err(ProtoError.IncorrectBlob)
else:
ok(true)
proc areAddrsConsistent*(a, b: MultiAddress): bool =
## Checks if two multiaddresses have the same protocol stack.
let protosA = a.protocols().get()
let protosB = b.protocols().get()
if protosA.len != protosB.len:
return false
for idx in 0 ..< protosA.len:
let protoA = protosA[idx]
let protoB = protosB[idx]
if protoA != protoB:
if idx == 0:
# allow DNS ↔ IP at the first component
if protoB == multiCodec("dns") or protoB == multiCodec("dnsaddr"):
if not (protoA == multiCodec("ip4") or protoA == multiCodec("ip6")):
return false
elif protoB == multiCodec("dns4"):
if protoA != multiCodec("ip4"):
return false
elif protoB == multiCodec("dns6"):
if protoA != multiCodec("ip6"):
return false
else:
return false
else:
return false
true

View File

@@ -18,6 +18,7 @@
import tables
import results
import stew/[base32, base58, base64]
import ./utils/sequninit
type
MultiBaseStatus* {.pure.} = enum
@@ -533,7 +534,7 @@ proc decode*(
let empty: seq[byte] = @[]
ok(empty) # empty
else:
var buffer = newSeq[byte](mb.decl(length - 1))
var buffer = newSeqUninit[byte](mb.decl(length - 1))
var outlen = 0
let res = mb.decr(inbytes.toOpenArray(1, length - 1), buffer, outlen)
if res != MultiBaseStatus.Success:

View File

@@ -10,6 +10,7 @@
## This module implements MultiCodec.
{.push raises: [].}
{.used.}
import tables, hashes
import vbuffer

View File

@@ -22,6 +22,7 @@
## 2. MURMUR
{.push raises: [].}
{.used.}
import tables
import nimcrypto/[sha, sha2, keccak, blake2, hash, utils]
@@ -566,7 +567,7 @@ proc init*(mhtype: typedesc[MultiHash], data: string): MhResult[MultiHash] {.inl
proc init58*(mhtype: typedesc[MultiHash], data: string): MultiHash {.inline.} =
## Create MultiHash from BASE58 encoded string representation ``data``.
if MultiHash.decode(Base58.decode(data), result) == -1:
raise newException(MultihashError, "Incorrect MultiHash binary format")
raise newException(MultihashError, "Incorrect MultiHash binary format in init58")
proc cmp(a: openArray[byte], b: openArray[byte]): bool {.inline.} =
if len(a) != len(b):

View File

@@ -249,11 +249,7 @@ proc addHandler*[E](
m.handlers.add(HandlerHolder(protos: @[codec], protocol: protocol, match: matcher))
proc start*(m: MultistreamSelect) {.async: (raises: [CancelledError]).} =
# Nim 1.6.18: Using `mapIt` results in a seq of `.Raising([])`
# TODO https://github.com/nim-lang/Nim/issues/23445
var futs = newSeqOfCap[Future[void].Raising([CancelledError])](m.handlers.len)
for it in m.handlers:
futs.add it.protocol.start()
let futs = m.handlers.mapIt(it.protocol.start())
try:
await allFutures(futs)
for fut in futs:
@@ -273,10 +269,7 @@ proc start*(m: MultistreamSelect) {.async: (raises: [CancelledError]).} =
raise exc
proc stop*(m: MultistreamSelect) {.async: (raises: []).} =
# Nim 1.6.18: Using `mapIt` results in a seq of `.Raising([CancelledError])`
var futs = newSeqOfCap[Future[void].Raising([])](m.handlers.len)
for it in m.handlers:
futs.add it.protocol.stop()
let futs = m.handlers.mapIt(it.protocol.stop())
await noCancel allFutures(futs)
for fut in futs:
await fut

View File

@@ -11,8 +11,7 @@
import std/[oids, strformat]
import pkg/[chronos, chronicles, metrics]
import
./coder, ../muxer, ../../stream/[bufferstream, connection, streamseq], ../../peerinfo
import ./coder, ../muxer, ../../stream/[bufferstream, connection], ../../peerinfo
export connection
@@ -87,7 +86,7 @@ proc open*(s: LPChannel) {.async: (raises: [CancelledError, LPStreamError]).} =
raise exc
except LPStreamError as exc:
await s.conn.close()
raise exc
raise newException(LPStreamError, "Opening LPChannel failed: " & exc.msg, exc)
method closed*(s: LPChannel): bool =
s.closedLocal
@@ -151,6 +150,10 @@ method close*(s: LPChannel) {.async: (raises: []).} =
trace "Closed channel", s, len = s.len
method closeWrite*(s: LPChannel) {.async: (raises: []).} =
## For mplex, closeWrite is the same as close - it implements half-close
await s.close()
method initStream*(s: LPChannel) =
if s.objName.len == 0:
s.objName = LPChannelTrackerName

View File

@@ -95,6 +95,7 @@ proc newStreamInternal*(
result.peerId = m.connection.peerId
result.observedAddr = m.connection.observedAddr
result.localAddr = m.connection.localAddr
result.transportDir = m.connection.transportDir
when defined(libp2p_agents_metrics):
result.shortAgent = m.connection.shortAgent

View File

@@ -54,6 +54,10 @@ method newStream*(
.} =
raiseAssert("[Muxer.newStream] abstract method not implemented!")
when defined(libp2p_agents_metrics):
method setShortAgent*(m: Muxer, shortAgent: string) {.base, gcsafe.} =
m.connection.shortAgent = shortAgent
method close*(m: Muxer) {.base, async: (raises: []).} =
if m.connection != nil:
await m.connection.close()

View File

@@ -12,6 +12,7 @@
import sequtils, std/[tables]
import chronos, chronicles, metrics, stew/[endians2, byteutils, objects]
import ../muxer, ../../stream/connection
import ../../utils/[zeroqueue, sequninit]
export muxer
@@ -134,12 +135,11 @@ proc windowUpdate(
)
type
ToSend =
tuple[
data: seq[byte],
sent: int,
fut: Future[void].Raising([CancelledError, LPStreamError]),
]
ToSend = ref object
data: seq[byte]
sent: int
fut: Future[void].Raising([CancelledError, LPStreamError])
YamuxChannel* = ref object of Connection
id: uint32
recvWindow: int
@@ -151,7 +151,7 @@ type
opened: bool
isSending: bool
sendQueue: seq[ToSend]
recvQueue: seq[byte]
recvQueue: ZeroQueue
isReset: bool
remoteReset: bool
closedRemotely: AsyncEvent
@@ -217,6 +217,19 @@ method closeImpl*(channel: YamuxChannel) {.async: (raises: []).} =
discard
await channel.actuallyClose()
method closeWrite*(channel: YamuxChannel) {.async: (raises: []).} =
## For yamux, closeWrite is the same as close - it implements half-close
await channel.close()
proc clearQueues(channel: YamuxChannel, error: ref LPStreamEOFError = nil) =
for toSend in channel.sendQueue:
if error.isNil():
toSend.fut.complete()
else:
toSend.fut.fail(error)
channel.sendQueue = @[]
channel.recvQueue.clear()
proc reset(channel: YamuxChannel, isLocal: bool = false) {.async: (raises: []).} =
# If we reset locally, we want to flush up to a maximum of recvWindow
# bytes. It's because the peer we're connected to can send us data before
@@ -226,10 +239,8 @@ proc reset(channel: YamuxChannel, isLocal: bool = false) {.async: (raises: []).}
trace "Reset channel"
channel.isReset = true
channel.remoteReset = not isLocal
for (d, s, fut) in channel.sendQueue:
fut.fail(newLPStreamEOFError())
channel.sendQueue = @[]
channel.recvQueue = @[]
channel.clearQueues(newLPStreamEOFError())
channel.sendWindow = 0
if not channel.closedLocally:
if isLocal and not channel.isSending:
@@ -257,7 +268,7 @@ proc updateRecvWindow(
return
let delta = channel.maxRecvWindow - inWindow
channel.recvWindow.inc(delta)
channel.recvWindow.inc(delta.int)
await channel.conn.write(YamuxHeader.windowUpdate(channel.id, delta.uint32))
trace "increasing the recvWindow", delta
@@ -278,8 +289,9 @@ method readOnce*(
trace "stream is down when readOnce", channel = $channel
newLPStreamConnDownError()
if channel.isEof:
channel.clearQueues()
raise newLPStreamRemoteClosedError()
if channel.recvQueue.len == 0:
if channel.recvQueue.isEmpty():
channel.receivedData.clear()
let
closedRemotelyFut = channel.closedRemotely.wait()
@@ -290,28 +302,24 @@ method readOnce*(
if not receivedDataFut.finished():
await receivedDataFut.cancelAndWait()
await closedRemotelyFut or receivedDataFut
if channel.closedRemotely.isSet() and channel.recvQueue.len == 0:
if channel.closedRemotely.isSet() and channel.recvQueue.isEmpty():
channel.isEof = true
channel.clearQueues()
return
0 # we return 0 to indicate that the channel is closed for reading from now on
let toRead = min(channel.recvQueue.len, nbytes)
var p = cast[ptr UncheckedArray[byte]](pbytes)
toOpenArray(p, 0, nbytes - 1)[0 ..< toRead] =
channel.recvQueue.toOpenArray(0, toRead - 1)
channel.recvQueue = channel.recvQueue[toRead ..^ 1]
let consumed = channel.recvQueue.consumeTo(pbytes, nbytes)
# We made some room in the recv buffer let the peer know
await channel.updateRecvWindow()
channel.activity = true
return toRead
return consumed
proc gotDataFromRemote(
channel: YamuxChannel, b: seq[byte]
) {.async: (raises: [CancelledError, LPStreamError]).} =
channel.recvWindow -= b.len
channel.recvQueue = channel.recvQueue.concat(b)
channel.recvQueue.push(b)
channel.receivedData.fire()
when defined(libp2p_yamux_metrics):
libp2p_yamux_recv_queue.observe(channel.recvQueue.len.int64)
@@ -320,17 +328,18 @@ proc gotDataFromRemote(
proc setMaxRecvWindow*(channel: YamuxChannel, maxRecvWindow: int) =
channel.maxRecvWindow = maxRecvWindow
proc trySend(
channel: YamuxChannel
) {.async: (raises: [CancelledError, LPStreamError]).} =
proc sendLoop(channel: YamuxChannel) {.async: (raises: []).} =
if channel.isSending:
return
channel.isSending = true
defer:
channel.isSending = false
while channel.sendQueue.len != 0:
channel.sendQueue.keepItIf(not (it.fut.cancelled() and it.sent == 0))
const NumBytesHeader = 12
while channel.sendQueue.len > 0:
channel.sendQueue.keepItIf(not it.fut.finished())
if channel.sendWindow == 0:
trace "trying to send while the sendWindow is empty"
if channel.lengthSendQueueWithLimit() > channel.maxSendQueueSize:
@@ -342,54 +351,57 @@ proc trySend(
let
bytesAvailable = channel.lengthSendQueue()
toSend = min(channel.sendWindow, bytesAvailable)
numBytesToSend = min(channel.sendWindow, bytesAvailable)
var
sendBuffer = newSeqUninitialized[byte](toSend + 12)
header = YamuxHeader.data(channel.id, toSend.uint32)
sendBuffer = newSeqUninit[byte](NumBytesHeader + numBytesToSend)
header = YamuxHeader.data(channel.id, numBytesToSend.uint32)
inBuffer = 0
if toSend >= bytesAvailable and channel.closedLocally:
trace "last buffer we'll sent on this channel", toSend, bytesAvailable
if numBytesToSend >= bytesAvailable and channel.closedLocally:
trace "last buffer we will send on this channel", numBytesToSend, bytesAvailable
header.flags.incl({Fin})
sendBuffer[0 ..< 12] = header.encode()
sendBuffer[0 ..< NumBytesHeader] = header.encode()
var futures: seq[Future[void].Raising([CancelledError, LPStreamError])]
while inBuffer < toSend:
while inBuffer < numBytesToSend:
var toSend = channel.sendQueue[0]
# concatenate the different message we try to send into one buffer
let (data, sent, fut) = channel.sendQueue[0]
let bufferToSend = min(data.len - sent, toSend - inBuffer)
let bufferToSend = min(toSend.data.len - toSend.sent, numBytesToSend - inBuffer)
sendBuffer.toOpenArray(12, 12 + toSend - 1)[
sendBuffer.toOpenArray(NumBytesHeader, NumBytesHeader + numBytesToSend - 1)[
inBuffer ..< (inBuffer + bufferToSend)
] = channel.sendQueue[0].data.toOpenArray(sent, sent + bufferToSend - 1)
] = toSend.data.toOpenArray(toSend.sent, toSend.sent + bufferToSend - 1)
channel.sendQueue[0].sent.inc(bufferToSend)
if channel.sendQueue[0].sent >= data.len:
if toSend.sent >= toSend.data.len:
# if every byte of the message is in the buffer, add the write future to the
# sequence of futures to be completed (or failed) when the buffer is sent
futures.add(fut)
futures.add(toSend.fut)
channel.sendQueue.delete(0)
inBuffer.inc(bufferToSend)
trace "try to send the buffer", h = $header
channel.sendWindow.dec(toSend)
try:
await channel.conn.write(sendBuffer)
channel.sendWindow.dec(inBuffer)
except CancelledError:
trace "cancelled sending the buffer"
for fut in futures.items():
fut.cancelSoon()
await channel.reset()
break
## Just for compiler. This should never happen as sendLoop is started by asyncSpawn.
## Therefore, no one owns that sendLoop's future and no one can cancel it.
discard
except LPStreamError as exc:
trace "failed to send the buffer"
error "failed to send the buffer", description = exc.msg
let connDown = newLPStreamConnDownError(exc)
for fut in futures.items():
for fut in futures:
fut.fail(connDown)
await channel.reset()
break
for fut in futures.items():
for fut in futures:
fut.complete()
channel.activity = true
method write*(
@@ -397,21 +409,29 @@ method write*(
): Future[void] {.async: (raises: [CancelledError, LPStreamError], raw: true).} =
## Write to yamux channel
##
result = newFuture[void]("Yamux Send")
var resFut = newFuture[void]("Yamux Send")
if channel.remoteReset:
trace "stream is reset when write", channel = $channel
result.fail(newLPStreamResetError())
return result
resFut.fail(newLPStreamResetError())
return resFut
if channel.closedLocally or channel.isReset:
result.fail(newLPStreamClosedError())
return result
resFut.fail(newLPStreamClosedError())
return resFut
if msg.len == 0:
result.complete()
return result
channel.sendQueue.add((msg, 0, result))
resFut.complete()
return resFut
channel.sendQueue.add(ToSend(data: msg, sent: 0, fut: resFut))
when defined(libp2p_yamux_metrics):
libp2p_yamux_send_queue.observe(channel.lengthSendQueue().int64)
asyncSpawn channel.trySend()
asyncSpawn channel.sendLoop()
return resFut
proc open(channel: YamuxChannel) {.async: (raises: [CancelledError, LPStreamError]).} =
## Open a yamux channel by sending a window update with Syn or Ack flag
@@ -420,6 +440,8 @@ proc open(channel: YamuxChannel) {.async: (raises: [CancelledError, LPStreamErro
trace "Try to open channel twice"
return
channel.opened = true
channel.isReset = false
await channel.conn.write(
YamuxHeader.windowUpdate(
channel.id,
@@ -493,6 +515,7 @@ proc createStream(
stream.initStream()
stream.peerId = m.connection.peerId
stream.observedAddr = m.connection.observedAddr
stream.localAddr = m.connection.localAddr
stream.transportDir = m.connection.transportDir
when defined(libp2p_agents_metrics):
stream.shortAgent = m.connection.shortAgent
@@ -507,12 +530,19 @@ method close*(m: Yamux) {.async: (raises: []).} =
if m.isClosed == true:
trace "Already closed"
return
m.isClosed = true
trace "Closing yamux"
let channels = toSeq(m.channels.values())
for channel in channels:
await channel.reset(isLocal = true)
channel.clearQueues(newLPStreamEOFError())
channel.recvWindow = 0
channel.sendWindow = 0
channel.closedLocally = true
channel.isReset = true
channel.opened = false
channel.isClosed = true
await channel.remoteClosed()
channel.receivedData.fire()
try:
await m.connection.write(YamuxHeader.goAway(NormalTermination))
except CancelledError as exc:
@@ -520,6 +550,8 @@ method close*(m: Yamux) {.async: (raises: []).} =
except LPStreamError as exc:
trace "failed to send goAway", description = exc.msg
await m.connection.close()
m.isClosed = true
trace "Closed yamux"
proc handleStream(m: Yamux, channel: YamuxChannel) {.async: (raises: []).} =
@@ -578,31 +610,35 @@ method handle*(m: Yamux) {.async: (raises: []).} =
raise
newException(YamuxError, "Peer exhausted the recvWindow after reset")
if header.length > 0:
var buffer = newSeqUninitialized[byte](header.length)
var buffer = newSeqUninit[byte](header.length)
await m.connection.readExactly(addr buffer[0], int(header.length))
do:
raise newException(YamuxError, "Unknown stream ID: " & $header.streamId)
# If we do not have a stream, likely we sent a RST and/or closed the stream
trace "unknown stream id", id = header.streamId
continue
let channel =
try:
m.channels[header.streamId]
except KeyError:
except KeyError as e:
raise newException(
YamuxError,
"Stream was cleaned up before handling data: " & $header.streamId,
"Stream was cleaned up before handling data: " & $header.streamId & " : " &
e.msg,
e,
)
if header.msgType == WindowUpdate:
channel.sendWindow += int(header.length)
await channel.trySend()
asyncSpawn channel.sendLoop()
else:
if header.length.int > channel.recvWindow.int:
# check before allocating the buffer
raise newException(YamuxError, "Peer exhausted the recvWindow")
if header.length > 0:
var buffer = newSeqUninitialized[byte](header.length)
var buffer = newSeqUninit[byte](header.length)
await m.connection.readExactly(addr buffer[0], int(header.length))
trace "Msg Rcv", description = shortLog(buffer)
await channel.gotDataFromRemote(buffer)

View File

@@ -15,7 +15,8 @@ import
chronicles,
stew/byteutils,
dnsclientpkg/[protocol, types],
../utility
../utility,
../utils/sequninit
import nameresolver
@@ -37,18 +38,18 @@ proc questionToBuf(address: string, kind: QKind): seq[byte] =
let dataLen = requestStream.getPosition()
requestStream.setPosition(0)
var buf = newSeq[byte](dataLen)
var buf = newSeqUninit[byte](dataLen)
discard requestStream.readData(addr buf[0], dataLen)
buf
except IOError as exc:
info "Failed to created DNS buffer", description = exc.msg
newSeq[byte](0)
newSeqUninit[byte](0)
except OSError as exc:
info "Failed to created DNS buffer", description = exc.msg
newSeq[byte](0)
newSeqUninit[byte](0)
except ValueError as exc:
info "Failed to created DNS buffer", description = exc.msg
newSeq[byte](0)
newSeqUninit[byte](0)
proc getDnsResponse(
dnsServer: TransportAddress, address: string, kind: QKind
@@ -78,23 +79,23 @@ proc getDnsResponse(
try:
await receivedDataFuture.wait(5.seconds) #unix default
except AsyncTimeoutError:
raise newException(IOError, "DNS server timeout")
except AsyncTimeoutError as e:
raise newException(IOError, "DNS server timeout: " & e.msg, e)
let rawResponse = sock.getMessage()
try:
parseResponse(string.fromBytes(rawResponse))
except IOError as exc:
raise exc
raise newException(IOError, "Failed to parse DNS response: " & exc.msg, exc)
except OSError as exc:
raise exc
raise newException(OSError, "Failed to parse DNS response: " & exc.msg, exc)
except ValueError as exc:
raise exc
raise newException(ValueError, "Failed to parse DNS response: " & exc.msg, exc)
except Exception as exc:
# Nim 1.6: parseResponse can has a raises: [Exception, ..] because of
# https://github.com/nim-lang/Nim/commit/035134de429b5d99c5607c5fae912762bebb6008
# it can't actually raise though
raiseAssert exc.msg
raiseAssert "Exception parsing DN response: " & exc.msg
finally:
await sock.closeWait()

View File

@@ -11,6 +11,7 @@
{.push raises: [].}
{.push public.}
{.used.}
import
std/[hashes, strutils],
@@ -23,7 +24,8 @@ import
./multicodec,
./multihash,
./vbuffer,
./protobuf/minprotobuf
./protobuf/minprotobuf,
./utils/sequninit
export results, utility
@@ -141,7 +143,7 @@ func init*(pid: var PeerId, data: string): bool =
## Initialize peer id from base58 encoded string representation.
##
## Returns ``true`` if peer was successfully initialiazed.
var p = newSeq[byte](len(data) + 4)
var p = newSeqUninit[byte](len(data) + 4)
var length = 0
if Base58.decode(data, p, length) == Base58Status.Success:
p.setLen(length)

View File

@@ -0,0 +1,345 @@
# Nim-Libp2p
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
import base64, json, strutils, uri, times, stew/byteutils
import chronos, chronos/apps/http/httpclient, results, chronicles
import ../peerinfo, ../crypto/crypto, ../varint.nim
logScope:
topics = "libp2p peeridauth"
const
NimLibp2pUserAgent = "nim-libp2p"
PeerIDAuthPrefix* = "libp2p-PeerID"
ChallengeCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
ChallengeDefaultLen = 48
export Domain
type PeerIDAuthClient* = ref object of RootObj
session: HttpSessionRef
rng: ref HmacDrbgContext
type PeerIDAuthError* = object of LPError
type PeerIDAuthResponse* = object
status*: int
headers*: HttpTable
body*: seq[byte]
type BearerToken* = object
token*: string
expires*: Opt[DateTime]
type PeerIDAuthOpaque* = string
type PeerIDAuthSignature* = string
type PeerIDAuthChallenge* = string
type PeerIDAuthAuthenticationResponse* = object
challengeClient*: PeerIDAuthChallenge
opaque*: PeerIDAuthOpaque
serverPubkey*: PublicKey
type PeerIDAuthAuthorizationResponse* = object
sig*: PeerIDAuthSignature
bearer*: BearerToken
response*: PeerIDAuthResponse
type SigParam = object
k: string
v: seq[byte]
proc new*(
T: typedesc[PeerIDAuthClient], rng: ref HmacDrbgContext = newRng()
): PeerIDAuthClient =
PeerIDAuthClient(session: HttpSessionRef.new(), rng: rng)
proc sampleChar(
ctx: var HmacDrbgContext, choices: string
): char {.raises: [ValueError].} =
## Samples a random character from the input string using the DRBG context
if choices.len == 0:
raise newException(ValueError, "Cannot sample from an empty string")
var idx: uint32
ctx.generate(idx)
return choices[uint32(idx mod uint32(choices.len))]
proc randomChallenge(
rng: ref HmacDrbgContext, challengeLen: int = ChallengeDefaultLen
): PeerIDAuthChallenge {.raises: [PeerIDAuthError].} =
var rng = rng[]
var challenge = ""
try:
for _ in 0 ..< challengeLen:
challenge.add(rng.sampleChar(ChallengeCharset))
except ValueError as exc:
raise newException(PeerIDAuthError, "Failed to generate challenge", exc)
PeerIDAuthChallenge(challenge)
proc extractField(data, key: string): string {.raises: [PeerIDAuthError].} =
# Helper to extract quoted value from key
for segment in data.split(","):
if key in segment:
return segment.split("=", 1)[1].strip(chars = {' ', '"'})
raise newException(PeerIDAuthError, "Failed to find " & key & " in " & data)
proc genDataToSign(
parts: seq[SigParam], prefix: string = PeerIDAuthPrefix
): seq[byte] {.raises: [PeerIDAuthError].} =
var buf: seq[byte] = prefix.toBytes()
for p in parts:
let varintLen = PB.encodeVarint(hint(p.k.len + p.v.len + 1)).valueOr:
raise newException(PeerIDAuthError, "could not encode fields length to varint")
buf.add varintLen
buf.add (p.k & "=").toBytes()
buf.add p.v
return buf
proc getSigParams(
clientSender: bool, hostname: string, challenge: string, publicKey: PublicKey
): seq[SigParam] =
if clientSender:
@[
SigParam(k: "challenge-client", v: challenge.toBytes()),
SigParam(k: "hostname", v: hostname.toBytes()),
SigParam(k: "server-public-key", v: publicKey.getBytes().get()),
]
else:
@[
SigParam(k: "challenge-server", v: challenge.toBytes()),
SigParam(k: "client-public-key", v: publicKey.getBytes().get()),
SigParam(k: "hostname", v: hostname.toBytes()),
]
proc sign(
privateKey: PrivateKey,
challenge: PeerIDAuthChallenge,
publicKey: PublicKey,
hostname: string,
clientSender: bool = true,
): PeerIDAuthSignature {.raises: [PeerIDAuthError].} =
let bytesToSign =
getSigParams(clientSender, hostname, challenge, publicKey).genDataToSign()
PeerIDAuthSignature(
base64.encode(privateKey.sign(bytesToSign).get().getBytes(), safe = true)
)
proc checkSignature*(
serverSig: PeerIDAuthSignature,
serverPublicKey: PublicKey,
challengeServer: PeerIDAuthChallenge,
clientPublicKey: PublicKey,
hostname: string,
): bool {.raises: [PeerIDAuthError].} =
let bytesToSign =
getSigParams(false, hostname, challengeServer, clientPublicKey).genDataToSign()
var serverSignature: Signature
try:
if not serverSignature.init(base64.decode(serverSig).toBytes()):
raise newException(
PeerIDAuthError, "Failed to initialize Signature from base64 encoded sig"
)
except ValueError as exc:
raise newException(PeerIDAuthError, "Failed to decode server's signature", exc)
serverSignature.verify(
bytesToSign.toOpenArray(0, bytesToSign.len - 1), serverPublicKey
)
method post*(
self: PeerIDAuthClient, uri: Uri, payload: string, authHeader: string
): Future[PeerIDAuthResponse] {.async: (raises: [HttpError, CancelledError]), base.} =
let rawResponse = await HttpClientRequestRef
.post(
self.session,
$uri,
body = payload,
headers = [
("Content-Type", "application/json"),
("User-Agent", NimLibp2pUserAgent),
("Authorization", authHeader),
],
)
.get()
.send()
PeerIDAuthResponse(
status: rawResponse.status,
headers: rawResponse.headers,
body: await rawResponse.getBodyBytes(),
)
method get*(
self: PeerIDAuthClient, uri: Uri
): Future[PeerIDAuthResponse] {.
async: (raises: [PeerIDAuthError, HttpError, CancelledError]), base
.} =
if self.session.isNil():
raise newException(PeerIDAuthError, "Session is nil")
let req = HttpClientRequestRef.get(self.session, $uri).valueOr:
raise newException(PeerIDAuthError, "Could not get request obj")
let rawResponse = await req.send()
PeerIDAuthResponse(
status: rawResponse.status,
headers: rawResponse.headers,
body: await rawResponse.getBodyBytes(),
)
proc requestAuthentication*(
self: PeerIDAuthClient, uri: Uri
): Future[PeerIDAuthAuthenticationResponse] {.
async: (raises: [PeerIDAuthError, CancelledError])
.} =
let response =
try:
await self.get(uri)
except HttpError as exc:
raise newException(PeerIDAuthError, "Failed to start PeerID Auth", exc)
let wwwAuthenticate = response.headers.getString("WWW-Authenticate")
if wwwAuthenticate == "":
raise newException(PeerIDAuthError, "WWW-authenticate not present in response")
let serverPubkey: PublicKey =
try:
PublicKey.init(decode(extractField(wwwAuthenticate, "public-key")).toBytes()).valueOr:
raise newException(PeerIDAuthError, "Failed to initialize server public-key")
except ValueError as exc:
raise newException(PeerIDAuthError, "Failed to decode server public-key", exc)
PeerIDAuthAuthenticationResponse(
challengeClient: extractField(wwwAuthenticate, "challenge-client"),
opaque: extractField(wwwAuthenticate, "opaque"),
serverPubkey: serverPubkey,
)
proc pubkeyBytes*(pubkey: PublicKey): seq[byte] {.raises: [PeerIDAuthError].} =
try:
pubkey.getBytes().valueOr:
raise
newException(PeerIDAuthError, "Failed to get bytes from PeerInfo's publicKey")
except ValueError as exc:
raise newException(
PeerIDAuthError, "Failed to get bytes from PeerInfo's publicKey", exc
)
proc parse3339DateTime(
timeStr: string
): DateTime {.raises: [ValueError, TimeParseError].} =
let parts = timeStr.split('.')
let base = parse(parts[0], "yyyy-MM-dd'T'HH:mm:ss")
let millis = parseInt(parts[1].strip(chars = {'Z'}))
result = base + initDuration(milliseconds = millis)
proc requestAuthorization*(
self: PeerIDAuthClient,
peerInfo: PeerInfo,
uri: Uri,
challengeClient: PeerIDAuthChallenge,
challengeServer: PeerIDAuthChallenge,
serverPubkey: PublicKey,
opaque: PeerIDAuthOpaque,
payload: auto,
): Future[PeerIDAuthAuthorizationResponse] {.
async: (raises: [PeerIDAuthError, CancelledError])
.} =
let clientPubkeyB64 = peerInfo.publicKey.pubkeyBytes().encode(safe = true)
let sig = peerInfo.privateKey.sign(challengeClient, serverPubkey, uri.hostname)
let authHeader =
PeerIDAuthPrefix & " public-key=\"" & clientPubkeyB64 & "\"" & ", opaque=\"" & opaque &
"\"" & ", challenge-server=\"" & challengeServer & "\"" & ", sig=\"" & sig & "\""
let response =
try:
await self.post(uri, $payload, authHeader)
except HttpError as exc:
raise newException(
PeerIDAuthError, "Failed to send Authorization for PeerID Auth", exc
)
let authenticationInfo = response.headers.getString("authentication-info")
let bearerExpires =
try:
Opt.some(parse3339DateTime(extractField(authenticationInfo, "expires")))
except ValueError, PeerIDAuthError, TimeParseError:
Opt.none(DateTime)
PeerIDAuthAuthorizationResponse(
sig: PeerIDAuthSignature(extractField(authenticationInfo, "sig")),
bearer: BearerToken(
token: extractField(authenticationInfo, "bearer"), expires: bearerExpires
),
response: response,
)
proc sendWithoutBearer(
self: PeerIDAuthClient, uri: Uri, peerInfo: PeerInfo, payload: auto
): Future[(BearerToken, PeerIDAuthResponse)] {.
async: (raises: [PeerIDAuthError, CancelledError])
.} =
# Authenticate in three ways as per the PeerID Auth spec
# https://github.com/libp2p/specs/blob/master/http/peer-id-auth.md
let authenticationResponse = await self.requestAuthentication(uri)
let challengeServer = self.rng.randomChallenge()
let authorizationResponse = await self.requestAuthorization(
peerInfo, uri, authenticationResponse.challengeClient, challengeServer,
authenticationResponse.serverPubkey, authenticationResponse.opaque, payload,
)
if not checkSignature(
authorizationResponse.sig, authenticationResponse.serverPubkey, challengeServer,
peerInfo.publicKey, uri.hostname,
):
raise newException(PeerIDAuthError, "Failed to validate server's signature")
return (authorizationResponse.bearer, authorizationResponse.response)
proc sendWithBearer(
self: PeerIDAuthClient,
uri: Uri,
peerInfo: PeerInfo,
payload: auto,
bearer: BearerToken,
): Future[(BearerToken, PeerIDAuthResponse)] {.
async: (raises: [PeerIDAuthError, CancelledError])
.} =
if bearer.expires.isSome() and DateTime(bearer.expires.get) <= now():
raise newException(PeerIDAuthError, "Bearer expired")
let authHeader = PeerIDAuthPrefix & " bearer=\"" & bearer.token & "\""
let response =
try:
await self.post(uri, $payload, authHeader)
except HttpError as exc:
raise newException(
PeerIDAuthError, "Failed to send request with bearer token for PeerID Auth", exc
)
return (bearer, response)
proc send*(
self: PeerIDAuthClient,
uri: Uri,
peerInfo: PeerInfo,
payload: auto,
bearer: Opt[BearerToken] = Opt.none(BearerToken),
): Future[(BearerToken, PeerIDAuthResponse)] {.
async: (raises: [PeerIDAuthError, CancelledError])
.} =
if bearer.isSome():
await self.sendWithBearer(uri, peerInfo, payload, bearer.get)
else:
await self.sendWithoutBearer(uri, peerInfo, payload)
proc close*(
self: PeerIDAuthClient
): Future[void] {.async: (raises: [CancelledError]).} =
await self.session.closeWait()

View File

@@ -0,0 +1,40 @@
# Nim-Libp2p
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
import uri
import chronos, chronos/apps/http/httpclient
import ../crypto/crypto, ./client
export client
type MockPeerIDAuthClient* = ref object of PeerIDAuthClient
mockedStatus*: int
mockedHeaders*: HttpTable
mockedBody*: seq[byte]
proc new*(
T: typedesc[MockPeerIDAuthClient], rng: ref HmacDrbgContext
): MockPeerIDAuthClient {.raises: [PeerIDAuthError].} =
MockPeerIDAuthClient(session: HttpSessionRef.new(), rng: rng)
method post*(
self: MockPeerIDAuthClient, uri: Uri, payload: string, authHeader: string
): Future[PeerIDAuthResponse] {.async: (raises: [HttpError, CancelledError]).} =
PeerIDAuthResponse(
status: self.mockedStatus, headers: self.mockedHeaders, body: self.mockedBody
)
method get*(
self: MockPeerIDAuthClient, uri: Uri
): Future[PeerIDAuthResponse] {.async: (raises: [HttpError, CancelledError]).} =
PeerIDAuthResponse(
status: self.mockedStatus, headers: self.mockedHeaders, body: self.mockedBody
)

View File

@@ -52,12 +52,16 @@ func shortLog*(p: PeerInfo): auto =
chronicles.formatIt(PeerInfo):
shortLog(it)
proc expandAddrs*(
p: PeerInfo
): Future[seq[MultiAddress]] {.async: (raises: [CancelledError]).} =
var addrs = p.listenAddrs
for mapper in p.addressMappers:
addrs = await mapper(addrs)
addrs
proc update*(p: PeerInfo) {.async: (raises: [CancelledError]).} =
# p.addrs.len == 0 overrides addrs only if it is the first time update is being executed or if the field is empty.
# p.addressMappers.len == 0 is for when all addressMappers have been removed,
# and we wish to have addrs in its initial state, i.e., a copy of listenAddrs.
if p.addrs.len == 0 or p.addressMappers.len == 0:
p.addrs = p.listenAddrs
p.addrs = p.listenAddrs
for mapper in p.addressMappers:
p.addrs = await mapper(p.addrs)
@@ -89,6 +93,10 @@ proc parseFullAddress*(ma: MultiAddress): MaResult[(PeerId, MultiAddress)] =
proc parseFullAddress*(ma: string | seq[byte]): MaResult[(PeerId, MultiAddress)] =
parseFullAddress(?MultiAddress.init(ma))
proc toFullAddress*(peerId: PeerId, ma: MultiAddress): MaResult[MultiAddress] =
let peerIdPart = ?MultiAddress.init(multiCodec("p2p"), peerId.data)
concat(ma, peerIdPart)
proc new*(
p: typedesc[PeerInfo],
key: PrivateKey,
@@ -101,8 +109,10 @@ proc new*(
let pubkey =
try:
key.getPublicKey().tryGet()
except CatchableError:
raise newException(PeerInfoError, "invalid private key")
except CatchableError as e:
raise newException(
PeerInfoError, "invalid private key creating PeerInfo: " & e.msg, e
)
let peerId = PeerId.init(key).tryGet()

View File

@@ -214,7 +214,7 @@ proc identify*(
info.agentVersion.get("").split("/")[0].safeToLowerAscii().get("")
if KnownLibP2PAgentsSeq.contains(shortAgent):
knownAgent = shortAgent
muxer.connection.setShortAgent(knownAgent)
muxer.setShortAgent(knownAgent)
peerStore.updatePeerInfo(info, stream.observedAddr)
finally:

View File

@@ -12,6 +12,7 @@
{.push raises: [].}
import ../varint, ../utility, stew/endians2, results
import ../utils/sequninit
export results, utility
{.push public.}
@@ -142,18 +143,17 @@ proc initProtoBuffer*(
result.options = options
proc initProtoBuffer*(options: set[ProtoFlags] = {}): ProtoBuffer =
## Initialize ProtoBuffer with new sequence of capacity ``cap``.
result.buffer = newSeq[byte]()
## Initialize ProtoBuffer with new sequence of capacity ``cap``
result.options = options
if WithVarintLength in options:
# Our buffer will start from position 10, so we can store length of buffer
# in [0, 9].
result.buffer.setLen(10)
result.buffer = newSeqUninit[byte](10)
result.offset = 10
elif {WithUint32LeLength, WithUint32BeLength} * options != {}:
# Our buffer will start from position 4, so we can store length of buffer
# in [0, 3].
result.buffer.setLen(4)
result.buffer = newSeqUninit[byte](4)
result.offset = 4
proc write*[T: ProtoScalar](pb: var ProtoBuffer, field: int, value: T) =

View File

@@ -12,7 +12,7 @@
import results
import chronos, chronicles
import ../../../switch, ../../../multiaddress, ../../../peerid
import core
import types
logScope:
topics = "libp2p autonat"
@@ -87,7 +87,7 @@ method dialMe*(
except CancelledError as e:
raise e
except CatchableError as e:
raise newException(AutonatError, "read Dial response failed", e)
raise newException(AutonatError, "read Dial response failed: " & e.msg, e)
let response = getResponseOrRaise(AutonatMsg.decode(respBytes))

View File

@@ -20,9 +20,9 @@ import
../../../peerid,
../../../utils/[semaphore, future],
../../../errors
import core
import types
export core
export types
logScope:
topics = "libp2p autonat"
@@ -105,7 +105,7 @@ proc tryDial(
autonat.sem.release()
for f in futs:
if not f.finished():
f.cancel()
f.cancelSoon()
proc handleDial(autonat: Autonat, conn: Connection, msg: AutonatMsg): Future[void] =
let dial = msg.dial.valueOr:

View File

@@ -14,11 +14,11 @@ import chronos, metrics
import ../../../switch
import ../../../wire
import client
from core import NetworkReachability, AutonatUnreachableError
from types import NetworkReachability, AutonatUnreachableError
import ../../../utils/heartbeat
import ../../../crypto/crypto
export core.NetworkReachability
export NetworkReachability
logScope:
topics = "libp2p autonatservice"

View File

@@ -58,6 +58,9 @@ type
NotReachable
Reachable
proc isReachable*(self: NetworkReachability): bool =
self == NetworkReachability.Reachable
proc encode(p: AutonatPeerInfo): ProtoBuffer =
result = initProtoBuffer()
p.id.withValue(id):

View File

@@ -0,0 +1,202 @@
# Nim-LibP2P
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
import results
import chronos, chronicles, tables
import
../../protocol,
../../../switch,
../../../multiaddress,
../../../multicodec,
../../../peerid,
../../../protobuf/minprotobuf,
./types,
./utils
logScope:
topics = "libp2p autonat v2 client"
const
MaxAcceptedDialDataRequest* = 100 * 1024 # 100 KB
MaxDialDataResponsePayload* = 1024
DefaultDialBackTimeout* = 5.seconds
type AutonatV2Client* = ref object of LPProtocol
dialer*: Dial
dialBackTimeout: Duration
rng: ref HmacDrbgContext
expectedNonces: Table[Nonce, Opt[MultiAddress]]
proc handleDialBack(
self: AutonatV2Client, conn: Connection, dialBack: DialBack
) {.async: (raises: [CancelledError, AutonatV2Error, LPStreamError]).} =
debug "Handling DialBack",
conn = conn, localAddr = conn.localAddr, observedAddr = conn.observedAddr
if not self.expectedNonces.hasKey(dialBack.nonce):
error "Not expecting this nonce", nonce = dialBack.nonce
return
conn.localAddr.withValue(localAddr):
debug "Setting expectedNonces",
nonce = dialBack.nonce, localAddr = Opt.some(localAddr)
self.expectedNonces[dialBack.nonce] = Opt.some(localAddr)
else:
error "Unable to get localAddr from connection"
return
trace "Sending DialBackResponse"
await conn.writeLp(DialBackResponse(status: DialBackStatus.Ok).encode().buffer)
proc new*(
T: typedesc[AutonatV2Client],
rng: ref HmacDrbgContext,
dialBackTimeout: Duration = DefaultDialBackTimeout,
): T =
let client = T(rng: rng, dialBackTimeout: dialBackTimeout)
# handler for DialBack messages
proc handleStream(
conn: Connection, proto: string
) {.async: (raises: [CancelledError]).} =
try:
let dialBack = DialBack.decode(initProtoBuffer(await conn.readLp(DialBackLpSize))).valueOr:
trace "Unable to decode DialBack"
return
if not await client.handleDialBack(conn, dialBack).withTimeout(
client.dialBackTimeout
):
trace "Sending DialBackResponse timed out"
except CancelledError as exc:
raise exc
except LPStreamRemoteClosedError as exc:
debug "Connection closed by peer", description = exc.msg, peer = conn.peerId
except LPStreamError as exc:
debug "Connection closed by peer", description = exc.msg, peer = conn.peerId
client.handler = handleStream
client.codec = $AutonatV2Codec.DialBack
client
proc setup*(self: AutonatV2Client, switch: Switch) =
self.dialer = switch.dialer
proc handleDialDataRequest*(
conn: Connection, req: DialDataRequest
): Future[DialResponse] {.
async: (raises: [CancelledError, AutonatV2Error, LPStreamError])
.} =
debug "Received DialDataRequest",
numBytes = req.numBytes, maxAcceptedNumBytes = MaxAcceptedDialDataRequest
if req.numBytes > MaxAcceptedDialDataRequest:
raise newException(
AutonatV2Error, "Rejecting DialDataRequest: numBytes is greater than the maximum"
)
# send required data
var msg = AutonatV2Msg(
msgType: MsgType.DialDataResponse,
dialDataResp: DialDataResponse(data: newSeq[byte](MaxDialDataResponsePayload)),
)
let messagesToSend =
(req.numBytes + MaxDialDataResponsePayload - 1) div MaxDialDataResponsePayload
for i in 0 ..< messagesToSend:
await conn.writeLp(msg.encode().buffer)
debug "Sending DialDataResponse", i = i, messagesToSend = messagesToSend
# get DialResponse
msg = AutonatV2Msg.decode(initProtoBuffer(await conn.readLp(AutonatV2MsgLpSize))).valueOr:
raise newException(AutonatV2Error, "Unable to decode AutonatV2Msg")
debug "Received message", msgType = msg.msgType
if msg.msgType != MsgType.DialResponse:
raise
newException(AutonatV2Error, "Expecting DialResponse, but got " & $msg.msgType)
return msg.dialResp
proc checkAddrIdx(
self: AutonatV2Client, addrIdx: AddrIdx, testAddrs: seq[MultiAddress], nonce: Nonce
): bool {.raises: [AutonatV2Error].} =
debug "checking addrs", addrIdx = addrIdx, testAddrs = testAddrs, nonce = nonce
let dialBackAddrs = self.expectedNonces.getOrDefault(nonce).valueOr:
debug "Not expecting this nonce",
nonce = nonce, expectedNonces = self.expectedNonces
return false
if addrIdx.int >= testAddrs.len:
debug "addrIdx outside of testAddrs range",
addrIdx = addrIdx, testAddrs = testAddrs, testAddrsLen = testAddrs.len
return false
let dialRespAddrs = testAddrs[addrIdx]
if not areAddrsConsistent(dialRespAddrs, dialBackAddrs):
debug "Invalid addrIdx: got DialBack in another address",
addrIdx = addrIdx, dialBackAddrs = dialBackAddrs, dialRespAddrs = dialRespAddrs
return false
true
method sendDialRequest*(
self: AutonatV2Client, pid: PeerId, testAddrs: seq[MultiAddress]
): Future[AutonatV2Response] {.
base,
async: (raises: [AutonatV2Error, CancelledError, DialFailedError, LPStreamError])
.} =
## Dials peer with `pid` and requests that it tries connecting to `testAddrs`
let nonce = self.rng[].generate(Nonce)
self.expectedNonces[nonce] = Opt.none(MultiAddress)
var dialResp: DialResponse
try:
let conn = await self.dialer.dial(pid, @[$AutonatV2Codec.DialRequest])
defer:
await conn.close()
# send dialRequest
await conn.writeLp(
AutonatV2Msg(
msgType: MsgType.DialRequest,
dialReq: DialRequest(addrs: testAddrs, nonce: nonce),
).encode().buffer
)
let msg = AutonatV2Msg.decode(
initProtoBuffer(await conn.readLp(AutonatV2MsgLpSize))
).valueOr:
raise newException(AutonatV2Error, "Unable to decode AutonatV2Msg")
dialResp =
case msg.msgType
of MsgType.DialResponse:
msg.dialResp
of MsgType.DialDataRequest:
await conn.handleDialDataRequest(msg.dialDataReq)
else:
raise newException(
AutonatV2Error,
"Expecting DialResponse or DialDataRequest, but got " & $msg.msgType,
)
debug "Received DialResponse", dialResp = dialResp
dialResp.dialStatus.withValue(dialStatus):
if dialStatus == DialStatus.Ok:
dialResp.addrIdx.withValue(addrIdx):
if not self.checkAddrIdx(addrIdx, testAddrs, nonce):
raise newException(
AutonatV2Error, "Invalid addrIdx " & $addrIdx & " in DialResponse"
)
except LPStreamRemoteClosedError as exc:
error "Stream reset by server", description = exc.msg, peer = pid
finally:
# rollback any changes
self.expectedNonces.del(nonce)
return dialResp.asAutonatV2Response(testAddrs)

View File

@@ -0,0 +1,65 @@
# Nim-LibP2P
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
import results
import chronos, chronicles
import
../../../../libp2p/
[
switch,
muxers/muxer,
dialer,
multiaddress,
multicodec,
peerid,
protobuf/minprotobuf,
],
../../protocol,
./types,
./server
type AutonatV2Mock* = ref object of LPProtocol
config*: AutonatV2Config
response*: ProtoBuffer
proc new*(
T: typedesc[AutonatV2Mock], config: AutonatV2Config = AutonatV2Config.new()
): T =
let autonatV2 = T(config: config)
proc handleStream(
conn: Connection, proto: string
) {.async: (raises: [CancelledError]).} =
defer:
await conn.close()
try:
let msg = AutonatV2Msg.decode(
initProtoBuffer(await conn.readLp(AutonatV2MsgLpSize))
).valueOr:
return
if msg.msgType != MsgType.DialRequest:
return
except LPStreamError:
return
try:
# return mocked message
await conn.writeLp(autonatV2.response.buffer)
except CancelledError as exc:
raise exc
except LPStreamRemoteClosedError:
discard
except LPStreamError:
discard
autonatV2.handler = handleStream
autonatV2.codec = $AutonatV2Codec.DialRequest
autonatV2

Some files were not shown because too many files have changed in this diff Show More