Compare commits

..

602 Commits

Author SHA1 Message Date
Neil Alexander
562a7d1f19 Merge pull request #516 from yggdrasil-network/develop
Version 0.3.8
2019-08-21 18:19:56 +01:00
Neil Alexander
0cb99d522f Update changelog 2019-08-21 18:18:46 +01:00
Neil Alexander
1308cb37b9 Merge pull request #515 from Arceliar/tidy
Clean up go.mod / go.sum
2019-08-21 07:11:07 +01:00
Arceliar
0d5dd9c455 update crypto dependency and run go mod tidy 2019-08-20 23:44:20 -05:00
Arceliar
12ce8c6a0a Merge pull request #512 from neilalexander/cryptokey
Cryptokey routing changes
2019-08-20 20:23:00 -05:00
Arceliar
f9d28e80df Merge pull request #514 from Arceliar/bugfix
hopefully prevent a deadlock
2019-08-20 19:13:00 -05:00
Arceliar
226dd6170d hopefully prevent a deadlock 2019-08-20 18:49:53 -05:00
Arceliar
4156aa3003 move ckr checks into the tunConn code 2019-08-20 18:10:08 -05:00
Neil Alexander
b79829c43b Merge branch 'develop' into cryptokey 2019-08-20 09:43:17 +01:00
Neil Alexander
ca73cf9e98 Merge pull request #513 from Arceliar/speedup
More speedup
2019-08-20 09:43:00 +01:00
Neil Alexander
b6e67bc0ba Check CKR remotes when receiving traffic 2019-08-20 09:38:46 +01:00
Neil Alexander
2b6462c8a9 Strict checking of Yggdrasil source/destination addresses 2019-08-20 09:38:27 +01:00
Arceliar
834a6a6f1a don't allocate a new child cancellation in Conn read/write calls if no deadline is set 2019-08-19 18:06:05 -05:00
Neil Alexander
2a629880fd Rename crypto-key config options, improve control flow 2019-08-19 10:28:30 +01:00
Arceliar
c04816b4bd Merge pull request #510 from Arceliar/streamWrites
Send multiple packets from the switch at once
2019-08-18 18:19:06 -05:00
Arceliar
8af1a7086c when a link becomes idle and packet are buffered that the link could send, send at least 65535 bytes worth instead of 1 packet, this reduces syscall overhead when small packets are sent through the network 2019-08-18 12:29:07 -05:00
Arceliar
62337bcd64 allow links to send multiple packets at once, currently we still only bother to send 1 at a time from the switch level 2019-08-18 12:17:54 -05:00
Neil Alexander
009d9c9ec0 Merge pull request #505 from yggdrasil-network/develop
Version 0.3.7
2019-08-18 11:20:50 +01:00
Arceliar
039dd98f0d Update CHANGELOG.md 2019-08-17 12:46:34 -05:00
Arceliar
57e7acdda8 Update CHANGELOG.md 2019-08-17 12:42:17 -05:00
Neil Alexander
80535d402f Merge pull request #508 from Arceliar/nonce
New nonce tracking
2019-08-17 10:55:21 +01:00
Arceliar
fd5f3ca764 fix heap pop order 2019-08-16 23:07:40 -05:00
Arceliar
03b8af9f1a keep track of recent nonces with a heap and a map instead of a fixed-size bitmask 2019-08-16 18:37:16 -05:00
Neil Alexander
fdac8932a8 Update changelog 2019-08-15 13:11:54 +01:00
Neil Alexander
ae0fe93de5 Update changelog 2019-08-15 12:54:04 +01:00
Neil Alexander
adf69d0127 Merge pull request #506 from Arceliar/switchorder
Switch priority change
2019-08-15 11:00:12 +01:00
Neil Alexander
5b054766a2 Update comments in handleIn, add switch_getFlowLabelFromCoords helper (in case it is useful if we try to consider flowlabels in multi-link scenarios) 2019-08-15 10:54:04 +01:00
Arceliar
382c2e6546 even more go.sum 2019-08-14 18:14:24 -05:00
Arceliar
1a2b7a8b60 test a change to how switch hops are selected when multiple links are idle 2019-08-14 17:57:36 -05:00
Neil Alexander
2abb71682f Update changelog, readme, go.mod/go.sum 2019-08-14 22:21:30 +01:00
Neil Alexander
f26f071901 Merge pull request #497 from Slex/issues/488
Implement feature from https://github.com/yggdrasil-network/yggdrasil…
2019-08-14 20:11:15 +01:00
Neil Alexander
02bfe28399 Minor tweaks 2019-08-14 20:09:02 +01:00
Neil Alexander
2cec5bf108 Merge pull request #504 from neilalexander/netlink
Use new netlink library (fixes #493)
2019-08-14 19:59:30 +01:00
Neil Alexander
33cd10c463 Merge branch 'issues/488' of github.com:slex/yggdrasil-go into issues/488 2019-08-14 19:58:45 +01:00
Neil Alexander
4702da2bcb Use new netlink library (fixes #493) 2019-08-14 19:32:40 +01:00
Neil Alexander
d9fabad8bc Merge pull request #502 from Arceliar/linkleak
Try to fix leaks in #501
2019-08-14 07:17:39 +01:00
Arceliar
46c5df1c23 when we abandon a link because we already have a connection to that peer, only wait for the connection to close if it's an *outgoing* link, otherwise incomming connection attempts can cause us to leak links 2019-08-13 18:49:49 -05:00
Neil Alexander
5e7df5a1c4 Merge pull request #499 from yggdrasil-network/sessionfix
Prevent session leaks or blocking if the listener is busy
2019-08-13 08:27:30 +01:00
Arceliar
b2cb1d965c avoid leaking sessions when no listener exists, or blocking if it's busy 2019-08-12 18:22:30 -05:00
Arceliar
c15976e4dc go.sum 2019-08-12 18:08:02 -05:00
Neil Alexander
70a118ae98 Update go.mod/go.sum 2019-08-12 11:41:29 +01:00
Neil Alexander
16076b53b9 Merge pull request #498 from Arceliar/search
Search fixes
2019-08-11 21:13:49 +01:00
Arceliar
277da1fe60 make sure searches don't end if try to continue (in parallel) with nowhere left to send, but we just sent a search and are still waiting for a response 2019-08-11 13:11:14 -05:00
Arceliar
7a28eb787e try to fix a few edge cases with searches that could lead them to ending without the callback being run or without cleaning up the old search info 2019-08-11 13:00:19 -05:00
Slex
589ad638ea Implement feature from https://github.com/yggdrasil-network/yggdrasil-go/issues/488 2019-08-11 00:31:22 +03:00
Arceliar
ae05683c73 Merge pull request #494 from Arceliar/bufpersession
Per-session front-dropping buffers for incoming traffic
2019-08-07 18:15:20 -05:00
Arceliar
5e81a0c421 Use a separate buffer per session for incoming packets, so 1 session that floods won't block other sessions 2019-08-07 18:08:31 -05:00
Arceliar
9ab08446ff make sure the sessionInfo.recvWorker doesn't block if sinfo.recv somehow fills 2019-08-07 17:40:50 -05:00
Neil Alexander
71e9ca25f7 Merge pull request #492 from neilalexander/fixlisten
Transform Listen statement to new format if needed
2019-08-07 10:57:55 +01:00
Neil Alexander
bbb35d7209 Transform Listen statement to new format if needed 2019-08-07 10:52:19 +01:00
Neil Alexander
c99ed9fb60 Merge pull request #491 from Arceliar/flowkey
Fix the old flowkey stuff so congestion control actually works...
2019-08-07 10:33:17 +01:00
Arceliar
d795ab1b65 minor allocation fix 2019-08-06 20:51:38 -05:00
Arceliar
790524bd1c copy/paste old flowkey logic into a util function, add a struct of key and packet, make WriteNoCopy accept this instead of a slice 2019-08-06 19:25:55 -05:00
Arceliar
6cb0ed91ad Merge pull request #486 from Arceliar/bugfix
Conn/Session cleanup
2019-08-05 19:17:30 -05:00
Arceliar
679866d5ff have createSession fill the sessionInfo.cancel field, have Conn use Conn.session.cancel instead of storing its own cancellation, this should prevent any of these things from being both nil and reachable at the same time 2019-08-05 19:11:28 -05:00
Arceliar
032b86c9a3 Merge pull request #485 from Arceliar/bugfix
Bugfix
2019-08-05 18:57:14 -05:00
Arceliar
8a85149817 remove src/.DS_Store 2019-08-05 18:50:08 -05:00
Arceliar
84a4f54217 temporary fix to nil pointer, better to make sure it's never nil 2019-08-05 18:49:15 -05:00
Neil Alexander
bd3b42022b Merge pull request #480 from Arceliar/speedup
Speedup
2019-08-05 10:24:54 +01:00
Neil Alexander
f046249ac6 Merge pull request #484 from neilalexander/config
API changes
2019-08-05 10:24:43 +01:00
Neil Alexander
2ee00fcc09 Return box_pub_key as hex string in JSON (replaces #481) 2019-08-05 10:21:40 +01:00
Neil Alexander
3a2ae9d902 Update API to represent coords as []uint64 2019-08-05 10:17:19 +01:00
Neil Alexander
37533f157d Make some API changes (currently broken) 2019-08-05 00:30:12 +01:00
Arceliar
979c3d4c07 move some potentially blocking operations out of session pool workers, minor cleanup 2019-08-04 16:29:58 -05:00
Arceliar
c55d7b4705 have the switch queue drop packts to ourself when the total size of all packets is at least queueTotalMaxSize, instead of an arbitrary unconfigurable packet count 2019-08-04 16:16:49 -05:00
Arceliar
6803f209b0 have tuntap code use Conn.ReadNoCopy and Conn.WriteNoCopy to avoid copying between slices 2019-08-04 15:59:51 -05:00
Arceliar
5d5486049b add Conn.ReadNoCopy and Conn.WriteNoCopy that transfer ownership of a slice instead of copying, have Read and Write use the NoCopy versions under the hood and just manage copying as needed 2019-08-04 15:53:34 -05:00
Arceliar
07f14f92ed disable crypto and switch buffer changes from testing 2019-08-04 15:25:14 -05:00
Arceliar
0ba8c6a34f have the stream code use bufio instead of copying manually to an input buffer, slightly reduces total uses of memmove 2019-08-04 15:21:04 -05:00
Arceliar
75b931f37e eliminate some more copying between slices 2019-08-04 14:50:19 -05:00
Arceliar
f52955ee0f WARNING: CRYPTO DISABLED while speeding up stream writeMsg 2019-08-04 14:18:59 -05:00
Arceliar
1e6a6d2160 use session.cancel in the router to make blocking safe, reduce size of fromRouter buffer so the drops in the switch are closer to the intended front-drop behavior 2019-08-04 02:21:41 -05:00
Arceliar
7bf5884ac1 remove some lossy channel sends that should be safe to allow to block 2019-08-04 02:14:45 -05:00
Arceliar
6da5802ae5 don't block forever in Write if the session is cancelled, cleanup Conn.Read slightly 2019-08-04 02:08:47 -05:00
Arceliar
144c823bee just use a sync.Pool as the bytestore to not overcomplicate things, the allocations from interface{} casting don't seem to actually hurt in practice right now 2019-08-04 00:28:13 -05:00
Arceliar
cbbb61b019 fix another drain on the bytestore 2019-08-04 00:00:41 -05:00
Arceliar
00e9c3dbd9 do session crypto work using the worker pool 2019-08-03 23:27:52 -05:00
Arceliar
befd1b43a0 refactor session worker code slightly 2019-08-03 23:14:51 -05:00
Arceliar
7a9ad0c8cc add workerpool to util 2019-08-03 23:10:37 -05:00
Arceliar
b9987b4fdc reduce time spent with a mutex held in sessionInfo.recvWorker 2019-08-03 22:47:10 -05:00
Arceliar
099bd3ae1e reduce part of sendWorker that needs to keep a mutex 2019-08-03 22:35:10 -05:00
Arceliar
72ed541bf3 a little cleanup to Conn functions 2019-08-03 22:07:38 -05:00
Arceliar
5dfc71e1ee put bytes back when done 2019-08-03 22:00:47 -05:00
Arceliar
df0090e32a Add per-session read/write workers, work in progress, they still unfortunately need to take a mutex for safety 2019-08-03 21:46:18 -05:00
Neil Alexander
a2966291b9 Merge pull request #478 from yggdrasil-network/develop
Version 0.3.6
2019-08-03 12:00:00 +01:00
Neil Alexander
523f90bfc4 Merge pull request #477 from yggdrasil-network/changelog
Update CHANGELOG.md
2019-08-03 10:17:28 +01:00
Arceliar
1eabf88782 more updates to go.sum 2019-08-02 20:48:07 -05:00
Neil Alexander
68769efdc9 Update go.mod/go.sum 2019-08-02 20:05:15 +01:00
Neil Alexander
e6bca895bc Update go.mod/go.sum 2019-07-30 11:52:30 +01:00
Neil Alexander
a3099894bd Update CHANGELOG.md 2019-07-30 10:15:06 +01:00
Neil Alexander
92a611f34b Merge pull request #476 from yggdrasil-network/build
Update build script
2019-07-30 00:14:03 +01:00
Neil Alexander
b4d08f9273 Try to be more POSIX-compliant 2019-07-30 00:03:17 +01:00
Neil Alexander
cafa20074c Don't strip debug builds 2019-07-29 23:50:00 +01:00
Neil Alexander
750a79eb09 Update build script 2019-07-29 23:45:47 +01:00
Neil Alexander
853054eb62 Merge pull request #475 from Arceliar/misc
Misc
2019-07-29 20:24:49 +01:00
Arceliar
406e143f7f move some logic from TunAdapter.reader into a new function, TunAdapter.readerPacketHandler 2019-07-28 23:33:04 -05:00
Neil Alexander
7c4c1558ff Merge pull request #474 from neilalexander/gomobile
Various API changes and simplifications to fix mobile builds
2019-07-28 19:34:06 +01:00
Neil Alexander
bb4abf575b Fix build -i 2019-07-28 13:51:22 +01:00
Neil Alexander
cbc8711dd3 Remove mobile module, since it can now be moved into another repository 2019-07-28 13:39:29 +01:00
Neil Alexander
c9554f82be Formatting tweaks in api.go 2019-07-28 11:35:16 +01:00
Neil Alexander
24f4754f2b Export NodeInfoPayload type, rename some API functions 2019-07-28 11:30:24 +01:00
Arceliar
38e1503b28 split up some of the tun reader logic into a separate worker, so the main loop can be mostly just syscalls 2019-07-27 20:09:43 -05:00
Arceliar
b66bea813b rename a couple of things and move a PutBytes so it happens sooner 2019-07-27 18:23:55 -05:00
Arceliar
9e118884d4 remove some commented code 2019-07-27 18:12:06 -05:00
Arceliar
e0a3055c2f get rid of session workers, new util.PutBytes/GetBytes logic 2019-07-27 18:10:32 -05:00
Arceliar
39245f8134 Merge pull request #473 from Arceliar/macos
Possible fix for macos self-traffic
2019-07-27 11:12:03 -05:00
Neil Alexander
377f88512b Remove commented out router function 2019-07-27 15:57:19 +01:00
Neil Alexander
de1005e4fa Various API changes and simplifications to fix mobile builds 2019-07-27 15:00:09 +01:00
Neil Alexander
ad4ba6871e Use Go 1.12.7 for macOS builds 2019-07-27 14:15:07 +01:00
Neil Alexander
d813105386 Export CIBRANCH to Bash env 2019-07-27 14:11:03 +01:00
Neil Alexander
4d4fa84123 Hopefully fix CircleCI builds on PRs 2019-07-27 13:57:19 +01:00
Neil Alexander
195d577151 Add IFF_NODAD/IFF_SECURED, define consts 2019-07-27 13:30:47 +01:00
Arceliar
e5bb9bcb8d change how searches are initialized so we actually send a dhtReq to ourself and get a response, in case we're the destination 2019-07-26 17:44:40 -05:00
Neil Alexander
9b99f0b5e4 Update go.mod/go.sum references 2019-07-25 08:40:45 +01:00
Neil Alexander
6263fa287c Merge pull request #467 from neilalexander/debugtext
Send PPROF output text to stderr
2019-07-23 08:15:03 +01:00
Neil Alexander
f208b7f542 Merge pull request #469 from neilalexander/fix468
Don't send IP back twice with getPeers
2019-07-23 08:14:49 +01:00
Neil Alexander
837eb0131b Merge pull request #470 from neilalexander/conndebug
Redirect Conn session closure errors to debug channel
2019-07-23 08:14:38 +01:00
Neil Alexander
de9d0a6cf1 Redirect Conn session closure errors to debug channel 2019-07-22 22:41:55 +01:00
Neil Alexander
8669091a08 Don't send IP back twice with getPeers 2019-07-22 19:45:48 +01:00
Neil Alexander
34ac5c9197 Send PPROF output text to stderr instead of stdout so that it doesn't break -genconf 2019-07-20 21:56:53 +01:00
Neil Alexander
48ad3c5d7f Update water go.mod references, fix some bugs in TAP mode (which should hopefully fix Windows support too) 2019-07-20 16:13:54 +01:00
Neil Alexander
36201895e7 Don't mangle bs slice in TAP mode 2019-07-20 12:10:05 +01:00
Neil Alexander
2582df752d Fix resetting Windows adapter (reverting previous change) 2019-07-20 11:43:30 +01:00
Neil Alexander
2aa57750a6 Merge branch 'windows' into develop 2019-07-20 11:15:05 +01:00
Neil Alexander
f3e3e4bca1 Update go.mod/go.sum again for Windows interface selection tweaks 2019-07-20 11:14:42 +01:00
Neil Alexander
4f3d29332d Merge pull request #466 from neilalexander/windows
Fix a couple of Windows bugs
2019-07-19 22:37:27 +01:00
Neil Alexander
52080aa41e Build with Go 1.12.7 2019-07-19 22:34:18 +01:00
Neil Alexander
613468e6a7 Update go.mod/go.sum again for BSD tweaks in Water due to failed CI build 2019-07-19 22:30:59 +01:00
Neil Alexander
1a5c2a4942 Update Windows module a bit - capture TAP setup errors earlier, refer to newer version of water which should fix #456 2019-07-19 22:21:30 +01:00
Neil Alexander
85881c04fa Merge pull request #463 from Arceliar/cancellation
Cancellation
2019-07-18 11:14:11 +01:00
Arceliar
cf3ebe04a7 have Conn use Cancellation instead of manually setting up timers 2019-07-17 21:37:45 -05:00
Arceliar
6bf182e341 add util.CancellationChild() and run gofmt 2019-07-17 21:15:02 -05:00
Arceliar
06e8403aaf add cancellation code to util, like context but just the cancellation parts + some error logic 2019-07-17 21:09:22 -05:00
Arceliar
c36da7b814 Merge pull request #462 from Arceliar/fixes
fix possible unsafe memory use in Conn.Read
2019-07-17 18:33:35 -05:00
Arceliar
5301207480 fix possible unsafe memory use in Conn.Read 2019-07-17 18:25:38 -05:00
Neil Alexander
06330f503f Recover if stillAlive fails 2019-07-18 00:02:16 +01:00
Neil Alexander
311c612f2e Only flag stillAlive on successful write 2019-07-17 23:23:19 +01:00
Neil Alexander
307b24d8cb Fix Conn.Read/Conn.Write behavior after Conn.Close, get rid of second TUN/TAP conn reader goroutine, no longer use deadlines 2019-07-17 21:42:17 +01:00
Neil Alexander
1bf1c6eb36 Revert "Remove stillAlive code from TUN/TAP conn as it is no longer required with the new deadlines"
This reverts commit eec70bf2f2.
2019-07-17 19:43:29 +01:00
Neil Alexander
eec70bf2f2 Remove stillAlive code from TUN/TAP conn as it is no longer required with the new deadlines 2019-07-17 13:53:16 +01:00
Neil Alexander
7d1c03d2ac Only call stillAlive if channel read succeeds 2019-07-17 12:07:16 +01:00
Neil Alexander
747b50bb7c Try to improve handling of timeouts 2019-07-17 11:13:53 +01:00
Neil Alexander
2532cd77e4 Merge pull request #461 from yggdrasil-network/connreader
Try to fix TUN/TAP conn reader leakage
2019-07-17 10:15:44 +01:00
Neil Alexander
d34600b5f9 Try to fix TUN/TAP conn reader leakage 2019-07-17 10:12:10 +01:00
Neil Alexander
7edcab8621 Merge pull request #433 from neilalexander/circlecirpm
Try to build the new RPM using CircleCI
2019-07-17 07:11:02 +01:00
Neil Alexander
fd2c7259b1 Merge pull request #460 from neilalexander/fix413
Fix #413
2019-07-17 07:10:43 +01:00
Neil Alexander
829a24a858 Fix default case 2019-07-16 11:48:31 +01:00
Neil Alexander
f3dd4320f7 Try to set Conflicts in RPM properly 2019-07-16 11:44:58 +01:00
Neil Alexander
0c4e2cc41e Merge branch 'develop' into circlecirpm 2019-07-16 11:28:46 +01:00
Neil Alexander
145a43e5f0 Fix #413 by always generating public keys from private ones instead of trusting public keys supplied by config 2019-07-16 09:49:28 +01:00
Neil Alexander
a5152f1d44 Merge pull request #458 from reuank/patch-1
Correcting typo in headline
2019-07-09 11:33:08 +01:00
Leon Knauer
99aac19f98 Correcting typo in headline 2019-07-09 12:30:29 +02:00
Neil Alexander
f831f9d2cc Merge pull request #395 from neilalexander/fix-385
Handle admin socket error cases better
2019-07-07 19:44:17 +01:00
Neil Alexander
ea9d5db16d Make admin socket output a bit friendlier (fixes #385) 2019-07-07 19:41:53 +01:00
Neil Alexander
c9dc9507de Merge pull request #455 from yggdrasil-network/tapfix
Fix TAP mode
2019-07-06 20:32:52 +01:00
Neil Alexander
30c03369cd Try to fix CKR setup deadlock, fix some Windows output formatting 2019-07-06 20:08:32 +01:00
Neil Alexander
a10c141896 Fix data race on peermacs 2019-07-06 15:15:43 +01:00
Neil Alexander
e8272926a4 Fix TAP mode 2019-07-06 15:08:17 +01:00
Neil Alexander
912c181581 Merge pull request #453 from yggdrasil-network/stopfix
Bug fixes
2019-07-06 12:36:46 +01:00
Neil Alexander
4804ce39af Tidy up the terminate path a bit 2019-07-06 12:17:40 +01:00
Neil Alexander
618d46a7b3 Don't block on adding peers in case one is unreachable and we are forced to wait for timeout 2019-07-06 12:12:30 +01:00
Neil Alexander
02c99d3e7d More directly define a minwinsvc exit handler 2019-07-06 12:04:31 +01:00
Neil Alexander
12486b0557 Try to more gracefully handle shutdowns on Windows 2019-07-06 11:52:30 +01:00
Arceliar
b2607a7205 Merge pull request #447 from Arceliar/bugfixes
Another Conn bugfix
2019-07-01 19:09:36 -05:00
Arceliar
86c30a1fc4 fix another panic from a send on a closed session worker channel, from races between Conn.Read/Write/Close 2019-07-01 18:55:07 -05:00
Arceliar
cd29fde178 temporary workaround to concurrency bug in sessions.getSharedKey 2019-06-29 19:32:15 -05:00
Arceliar
fbe44ea973 fix bug in session api code 2019-06-29 19:25:34 -05:00
Arceliar
b8592669b8 Merge pull request #446 from Arceliar/bugfixes
Bugfixes
2019-06-29 19:00:11 -05:00
Arceliar
40553a6a44 make GetSessions use the session workers to avoid races 2019-06-29 18:56:26 -05:00
Arceliar
d39428735d recover if we try to send to a closed session worker due to a race between a Conn.Write call and a Conn.Close call 2019-06-29 18:50:21 -05:00
Arceliar
28db566b37 fix concurrency bug in iface.go 2019-06-29 18:44:24 -05:00
Arceliar
0fb1165b76 Merge pull request #445 from Arceliar/dial
more bugfixes
2019-06-29 17:48:12 -05:00
Arceliar
7d58a7ef3e fix channel multiple close bug and concurrency bug in the way sessionInfo.close was being called 2019-06-29 17:44:28 -05:00
Arceliar
43bcb9e154 Merge pull request #444 from Arceliar/dial
Dial fixes
2019-06-29 16:17:47 -05:00
Arceliar
818eca90db fix nil pointer deref if searches fail, block dial until a search exceeds or a timeout passes (todo: replace timer with context) 2019-06-29 16:10:02 -05:00
Arceliar
ca1f2bb0a2 add go-syslog to go.mod/go.sum 2019-06-29 12:33:00 -05:00
Arceliar
8ecf99d8a9 Merge pull request #443 from Arceliar/dial
Bugfix in dial code
2019-06-29 12:21:19 -05:00
Arceliar
784acba823 I think this fixes the concurrent map read/write panic 2019-06-29 12:14:44 -05:00
Neil Alexander
0d23342358 Merge pull request #441 from Arceliar/dial
Dial
2019-06-29 10:52:05 +01:00
Arceliar
e88bef35c0 get rid of old buffered session packets 2019-06-28 20:02:58 -05:00
Arceliar
e7cb76cea3 clean up unused old session maps 2019-06-28 19:21:44 -05:00
Arceliar
c808be514f make tunAdapter.wrap return the right thing 2019-06-28 19:11:28 -05:00
Arceliar
5df110ac79 make Dial block until the search finishes, and use it as such 2019-06-28 18:42:31 -05:00
Neil Alexander
ac8ff740ee Merge pull request #438 from neilalexander/multicast
Try and solidify multicast interface behavior
2019-06-29 00:38:38 +01:00
Neil Alexander
721a8b5d27 Merge pull request #440 from neilalexander/logging
Add support for logging to file or syslog instead of stdout
2019-06-29 00:37:05 +01:00
Neil Alexander
23108e268b Use go-syslog to fix builds on Windows 2019-06-29 00:32:23 +01:00
Neil Alexander
27b3b9b49b Return new copy of interfaces on each Interfaces() call 2019-06-29 00:12:56 +01:00
Neil Alexander
93a323c62c Add support for logging to file or syslog instead of stdout 2019-06-28 23:45:04 +01:00
Arceliar
29a0f8b572 some minor refactoring to dht callbacks and searches, work in progress 2019-06-25 19:31:29 -05:00
Neil Alexander
54f1804101 Try and solidify multicast interface behavior 2019-06-20 15:11:55 +01:00
Neil Alexander
2fd3ac6837 Merge pull request #432 from neilalexander/gatekeeper
Implement session gatekeeper functions
2019-06-13 23:41:42 +01:00
Neil Alexander
535ca06c07 Merge pull request #431 from neilalexander/defaults
Add multicast interfaces to platform-specific defaults
2019-06-13 23:41:28 +01:00
Neil Alexander
f545060e89 Add notes on isSessionAllowed checks 2019-06-13 23:37:53 +01:00
Neil Alexander
9a7d350884 Fix expressions 2019-06-11 23:48:00 +01:00
Neil Alexander
ec5bb84975 Try to build the new RPM using CircleCI 2019-06-11 15:30:55 +01:00
Neil Alexander
e229ad6e2b Update comments 2019-06-11 12:52:13 +01:00
Neil Alexander
907986f200 Implement session firewall as gatekeeper func in cmd/yggdrasil 2019-06-11 12:50:01 +01:00
Neil Alexander
720a078a35 Add SetSessionGatekeeper
This allows you to define a function which determines whether a session connection (either incoming or outgoing) is allowed based on the public key.
2019-06-11 10:52:21 +01:00
Neil Alexander
17175b49f2 Add multicast interfaces to platform-specific defaults (this makes it easier to avoid bringing AWDL up by default on macOS as an example, or over L2 VPNs when not expected) 2019-06-11 10:18:59 +01:00
Neil Alexander
d4a3b2bc76 Merge pull request #429 from Arceliar/sessionfix
fix issue with sessions dying and never being fixed
2019-06-11 10:07:52 +01:00
Arceliar
4b56849b08 fix issue with sessions dying and never being fixed 2019-06-10 22:09:12 -05:00
Arceliar
cab4b5f793 Merge pull request #428 from Arceliar/readerror
Conn.Read don't return useless errors
2019-05-31 17:56:53 -05:00
Arceliar
1addf08ccd don't have Conn.Read return an error for temorary crypto failures from e.g. out of order packets, just drop the packet and keep blocking until there's usable traffic 2019-05-31 17:51:01 -05:00
Neil Alexander
7e837e97e9 Merge pull request #427 from neilalexander/ckrenabled
Fix panic when determining if CKR is enabled
2019-05-31 11:33:07 +01:00
Neil Alexander
f0422dbd8b Fix panic when determining if CKR is enabled 2019-05-30 17:33:59 +01:00
Neil Alexander
e430d16018 Merge pull request #426 from neilalexander/errorhandle
Don't indefinitely block TUN/TAP reader goroutine after conn error
2019-05-30 12:52:35 +01:00
Neil Alexander
9e086e70f0 Don't indefinitely block TUN/TAP reader goroutine when a conn error happens 2019-05-30 12:44:47 +01:00
Neil Alexander
396c879d0f Merge pull request #425 from neilalexander/sessionmtu
Re-add ICMPv6 packet too big handling
2019-05-29 20:19:41 +01:00
Neil Alexander
0096d1ae3e Re-add ICMPv6 packet too big handling 2019-05-29 20:16:17 +01:00
Neil Alexander
75cc25b7f4 Merge pull request #424 from neilalexander/sessionmtu
Fix bug where MTU was ignored by sessions, resulting in default 1280
2019-05-29 19:17:10 +01:00
Neil Alexander
3b6c726a3c Fix bug where MTU was ignored by sessions, resulting in default 1280 2019-05-29 19:11:12 +01:00
Neil Alexander
6b6266bfdd Merge pull request #419 from yggdrasil-network/modular
Modular Yggdrasil
2019-05-29 18:19:13 +01:00
Neil Alexander
78eb40cbad Record session uptime (purely for the admin socket) 2019-05-29 12:59:36 +01:00
Arceliar
b2513fce56 have the tunConn close things after a 2 minute timeout 2019-05-28 18:35:52 -05:00
Arceliar
5ea864869a don't spam searches for unused connections. todo: timeout old connections somehow 2019-05-23 20:27:52 -05:00
Neil Alexander
70774fc3de Reimplement get/setTunnelRouting, add/removeSourceSubnet, add/removeRoute, getRoutes, getSourceSubnets, make CKR threadsafe 2019-05-20 21:45:33 +01:00
Neil Alexander
5b8d8a9341 Reimplement getNodeInfo, dhtPing, get/add/removeAllowedEncryptionPublicKey, add/removePeer 2019-05-20 19:51:44 +01:00
Neil Alexander
e9e2d7bc6f Remove debug println 2019-05-19 22:03:20 +01:00
Neil Alexander
d575b83ec1 Refactor admin socket somewhat, allow modules to set up their own handlers 2019-05-19 22:02:04 +01:00
Neil Alexander
8ef1978cb1 Start factoring out the admin socket into a separate module (not all functions implemented yet) 2019-05-19 17:27:48 +01:00
Neil Alexander
7ca5a2533d Implement GetDHT, GetSwitchQueues, GetSessions 2019-05-19 16:29:04 +01:00
Neil Alexander
8a6f6f3b2b Implement GetPeers and GetSwitchPeers API functions in Core, in preparation for breaking out the admin socket into a separate module 2019-05-18 17:21:02 +01:00
Neil Alexander
ce60609906 Remove wrappedConn as unnecessary 2019-05-18 16:16:32 +01:00
Neil Alexander
5b09b45572 Merge pull request #416 from fifteenthcommotion/yggdrasil-brute-simple
contribute decently fast yggdrasil address generator in C
2019-05-18 12:28:49 +01:00
fifteenthcommotion
080052ce04 remove ygg-brute gitignore 2019-05-18 04:25:57 -07:00
Neil Alexander
1b3ec0b93f Fix multicast start check so that it shouldn't give up if interfaces aren't up when Yggdrasil starts (fixes #405) 2019-05-17 22:59:29 +01:00
Neil Alexander
2df62e2b9b Remove code that translates v0.2 config options (it was commented out anyway) 2019-05-17 22:52:14 +01:00
Neil Alexander
ae2cc13d14 Fix configuration reloading support 2019-05-17 22:29:52 +01:00
Neil Alexander
71ccaf753e Add crypto-key routing into TUN/TAP 2019-05-17 22:09:20 +01:00
Arceliar
9c01947b1c reduce allocations in switch 2019-05-16 18:10:47 -05:00
Arceliar
522ed147b1 use the subnet derived ID/mask when creating a connection based on a subnet address, fix a potential blocking channel send in tuntap/conn.go, and get debug.go compiling well enough to profile things (the sim is currently still broken) 2019-05-15 18:01:26 -05:00
fifteenthcommotion
5bed78c7a7 add CC0 for good measure 2019-05-12 12:40:45 -07:00
fifteenthcommotion
db85a11194 unlicense and spacing perfectionism 2019-05-12 02:18:03 -07:00
fifteenthcommotion
5a3c730097 contribute decently fast yggdrasil address generator in C 2019-05-11 16:31:46 -07:00
Arceliar
efdaea1b5e fix some races and GetBytes/PutBytes usage, but this still seems to deadlock somewhere in iperf tests 2019-05-02 17:37:49 -05:00
Neil Alexander
5f66c4c95c Try using separate workers for each TUN/TAP connection (sometimes produces duplicate packets when communicating with both the node address and a subnet address, sometimes also can't Ctrl-C to quit) 2019-04-28 17:14:09 +01:00
Arceliar
6469e39ff1 workaround to random timeouts 2019-04-26 22:42:05 -05:00
Arceliar
5d323861f0 properly fix the memory errors, it was caused by a function returning and PutBytes-ing a buffer before a worker had a chance to decrypt the buffer, so it would GetBytes the same buffer by dumb luck and then get an illegal overlap 2019-04-26 22:21:31 -05:00
Arceliar
01ea6d3d80 somehow this doesn't seem to deadlock or crash from buffer reuse (util.PutBytes), but I have no idea why it was doing that before and not now 2019-04-26 21:49:11 -05:00
Arceliar
15051b0a3c Add deadline timers, keep searches alive until they complete (or the conn is closed) to keep Write from blocking forever 2019-04-26 19:31:47 -05:00
Arceliar
0059baf36c add a newConn function that returns a pointer to a Conn with atomics properly initialized 2019-04-26 18:07:57 -05:00
Neil Alexander
75130f7735 Fix TAP support again 2019-04-23 11:46:16 +01:00
Neil Alexander
2b44f5d2f6 Fix TAP support 2019-04-23 11:37:32 +01:00
Neil Alexander
b4513ca2e8 Re-add support for TAP mode 2019-04-23 10:43:07 +01:00
Neil Alexander
870b2b6a2e Remove CKR from src/yggdrasil (it will be moved into tuntap) 2019-04-23 10:28:40 +01:00
Neil Alexander
2bee3cd7ca Update TODOs at top of tun.go 2019-04-23 00:04:22 +01:00
Neil Alexander
d7a1c04748 It works, sort of, amazingly 2019-04-22 23:58:59 +01:00
Neil Alexander
e1a2d666bf Clean up router, tweaks 2019-04-22 23:12:13 +01:00
Neil Alexander
6e528799e9 Conn Read/Write operations will block while search completes 2019-04-22 22:38:37 +01:00
Neil Alexander
ea8948f378 TUN/TAP addr/subnet to Conn mappings, other fixes 2019-04-22 20:06:39 +01:00
Neil Alexander
9778f5d2b8 Fix search behaviour on closed Conns, various other fixes 2019-04-22 15:00:19 +01:00
Neil Alexander
bbd1246f7b Fix bug in mask generation for outbound dials, change iface reader mutexes to read-only locks unless RW is needed 2019-04-22 11:49:47 +01:00
Neil Alexander
ccf03fd3b6 Don't write huge mostly empty buffers unnecessarily 2019-04-22 11:22:40 +01:00
Neil Alexander
47eb2fc47f Break deadlock by creating session recv queue when session is created instead of repointing at search completion, also make expired atomic 2019-04-22 11:20:35 +01:00
Arceliar
5a02e2ff44 apparently it was these callbacks that were sometimes deadlocking things 2019-04-21 22:31:56 -05:00
Arceliar
9ce7fe2e3f fix tun/tap CIDR notation so things work on linux, may break other platforms for all I know 2019-04-21 20:56:12 -05:00
Arceliar
5dada3952c use a session worker to try to avoid mutex hell. compiles, but incomplete and doesn't work yet 2019-04-21 20:38:14 -05:00
Neil Alexander
0b8f5b5dda Tweaks 2019-04-21 12:28:46 +01:00
Neil Alexander
781cd7571f Fix race on tun conns, but still deadlocks if more than one connection is opened 2019-04-21 12:00:31 +01:00
Neil Alexander
79bcfbf175 Change some mutexes to atomics, change conns map to pointers, sort of works but seems to deadlock very easily 2019-04-21 11:50:41 +01:00
Neil Alexander
62621f2960 Some tweaks 2019-04-20 20:22:58 +01:00
Neil Alexander
d01662c1fb Try to convert TUN/TAP to use new yggdrasil.Conn, search masks are still broken 2019-04-20 16:32:27 +01:00
Neil Alexander
319366513c Allow building with race detector 2019-04-20 11:53:46 +01:00
Neil Alexander
f3e742a297 Squash a whole load of races (and mutex half the world) 2019-04-20 11:53:38 +01:00
Neil Alexander
24281d4049 Fix Read, update sample 2019-04-19 23:47:11 +01:00
Neil Alexander
693bcc5713 Update sample in cmd/yggdrasil 2019-04-19 23:30:57 +01:00
Neil Alexander
e31b914e38 Improve errors and handling of expired sessions 2019-04-19 23:30:43 +01:00
Neil Alexander
7e726b0afb Listener should clean up a bit more when closing 2019-04-19 23:04:09 +01:00
Neil Alexander
aac88adbed Listen-Accept-Read-Write pattern now works, amazingly 2019-04-19 22:57:52 +01:00
Neil Alexander
27b78b925d Move mutexes around 2019-04-19 21:23:15 +01:00
Neil Alexander
e3eadba4b7 Protect session nonces with mutexes, modify sent/received bytes atomically 2019-04-19 20:10:41 +01:00
Neil Alexander
ade684beff Signal when a session is closed, other tweaks 2019-04-19 10:55:15 +01:00
Neil Alexander
c593721362 Tweaks 2019-04-19 00:33:54 +01:00
Neil Alexander
b20c8b6da5 Move some things around a bit, delete session workers 2019-04-19 00:11:43 +01:00
Neil Alexander
b2f4f2e1b6 Update errors, update Write 2019-04-19 00:07:26 +01:00
Neil Alexander
160e01e84f Searches called from api.go, various other tweaks, searches now have a callback for success/failure, node ID now reported by admin socket 2019-04-18 23:38:23 +01:00
Neil Alexander
eef2a02d0a Experiment with new API 2019-04-18 16:38:24 +01:00
Neil Alexander
24fa8355f1 Merge pull request #401 from neilalexander/modular
Refactoring for Yggdrasil library
2019-04-17 18:35:10 +01:00
Neil Alexander
9bc24f8dbf Return both current and previous config when replacing 2019-04-15 22:00:38 +01:00
Arceliar
53fba06382 Merge pull request #407 from cathugger/develop
wire: cleaner and faster wire_intToUint and wire_intFromUint
2019-04-11 00:28:27 -05:00
Neil Alexander
ed4e21478f Merge pull request #409 from vpzomtrrfrt/armel
Add armel support to deb script
2019-04-07 23:30:07 +01:00
Colin Reeder
2465ad0384 Add armel to PKGARCH usage list 2019-04-07 16:14:58 -06:00
Colin Reeder
b5e3b05e77 Add armel support to deb script 2019-04-07 13:07:26 -06:00
cathugger
4488189a75 wire: cleaner and faster wire_intToUint and wire_intFromUint
Bit operations are much faster on most processors than multiplication.
Also specify that it's zigzag to ease finding additional documentation for it.
2019-04-06 21:34:47 +03:00
Neil Alexander
2e72c7c93d Fix mobile logging 2019-04-01 22:45:30 +01:00
Neil Alexander
90feae6a7d Comment out AWDL (doesn't work in iOS properly) and move out of main package 2019-04-01 20:12:39 +01:00
Neil Alexander
350b51cabb TUN/TAP now uses config, log, etc from adapter.go 2019-04-01 20:10:14 +01:00
Neil Alexander
58f5cc88d0 Fix session bug, fix dummy adapter, fix mobile framework builds 2019-04-01 19:59:50 +01:00
Neil Alexander
047717abf2 Break out mobile and dummy adapter 2019-04-01 18:02:06 +01:00
Neil Alexander
39baf7365c Unexport/modify some interfaces to revive broken iOS/Android builds 2019-03-30 00:09:35 +00:00
Neil Alexander
4c0c3a23cb Fix bugs 2019-03-29 18:24:57 +00:00
Neil Alexander
f19a4e4398 More godoc improvements 2019-03-29 18:18:31 +00:00
Neil Alexander
b5ac65cacb Rearrange public interface, godoc improvements 2019-03-29 18:05:17 +00:00
Neil Alexander
399e1a2ffe Make AddPeer remember added peer (as opposed to CallPeer which does not) 2019-03-29 08:58:30 +00:00
Neil Alexander
a830521078 Don't crash if Yggdrasil is started with no router adapter 2019-03-29 08:38:09 +00:00
Neil Alexander
dd05a7f2a8 Tweaks 2019-03-28 19:09:19 +00:00
Neil Alexander
fd0b614f9c Temporarily disable debug CircleCI builds as I don't know how badly I've broken the sim with this PR 2019-03-28 18:03:14 +00:00
Neil Alexander
7ea4e9575e Break out multicast into a separate package 2019-03-28 16:13:14 +00:00
Neil Alexander
03bc7bbcd6 Fix TUN/TAP for non-Darwin platforms 2019-03-28 15:32:01 +00:00
Neil Alexander
eb22ed44ac Add new reject channel to router so we can send back rejected packets to adapter (e.g. for ICMPv6 Packet Too Big), implement ICMPv6 PTB in TUN/TAP instead of router 2019-03-28 09:50:13 +00:00
Neil Alexander
0715e829c2 Fix adapter setup and no longer panics on packets shorter than IP header 2019-03-28 09:12:00 +00:00
Neil Alexander
0b494a8255 Refactoring: move tuntap and icmpv6 into separate package 2019-03-28 00:30:25 +00:00
Neil Alexander
67c670ab4c Merge pull request #391 from aparcar/patch-1
README: add OpenWrt as supported platform
2019-03-19 16:39:02 +00:00
Paul Spooren
3f824ee99c README: add OpenWrt as supported platform 2019-03-19 15:54:49 +01:00
Neil Alexander
c11f08f2a3 Merge pull request #384 from yggdrasil-network/develop
Version 0.3.5
2019-03-13 23:05:09 +00:00
Neil Alexander
b57030430c Update README.md 2019-03-13 20:21:01 +00:00
Neil Alexander
e582ac102b Update README.md 2019-03-13 20:12:08 +00:00
Neil Alexander
7478c8ba2b Update README.md 2019-03-13 20:08:50 +00:00
Neil Alexander
09c92698df Update README.md 2019-03-13 20:06:02 +00:00
Neil Alexander
9019ccc118 Don't install gomobile for now 2019-03-13 19:09:09 +00:00
Neil Alexander
14afb8881e Update CircleCI to use Go 1.12 on other 2019-03-13 18:51:00 +00:00
Neil Alexander
8ddadce699 Update CircleCI to use Go 1.12 on macOS 2019-03-13 18:47:03 +00:00
Neil Alexander
9f16fc47b3 Update CircleCI again 2019-03-13 18:41:47 +00:00
Neil Alexander
d6111911d4 Update CircleCI again 2019-03-13 18:36:28 +00:00
Neil Alexander
d4437afa34 Update CircleCI to 2.1 2019-03-13 18:27:20 +00:00
Neil Alexander
2892e10e3e Merge pull request #383 from neilalexander/multicastaddr
Handle cases where link-local addresses may disappear or change
2019-03-13 18:12:16 +00:00
Neil Alexander
d0aeffb5f4 Update CHANGELOG.md 2019-03-13 18:05:59 +00:00
Neil Alexander
5bacfabae7 Handle cases where link-local addresses may disappear or change 2019-03-13 17:43:33 +00:00
Neil Alexander
cc0c188dc9 Merge pull request #380 from neilalexander/listeners
Re-order config, update default Listen
2019-03-12 20:16:37 +00:00
Neil Alexander
41872820c3 Remove isAutoconf option to GenerateConfig 2019-03-12 19:18:43 +00:00
Neil Alexander
4062c93e18 Re-order config, update default Listen 2019-03-12 19:04:30 +00:00
Neil Alexander
a364aac145 Merge pull request #379 from neilalexander/allowedfix
Fix AllowedEncryptionPublicKeys
2019-03-12 16:08:16 +00:00
Neil Alexander
830be7f4db Update comments again 2019-03-12 16:06:12 +00:00
Neil Alexander
dc3a05f13a Correctly classify link-local addresses in the TCP handler, fix AllowedPublicEncryptionKeys warning 2019-03-12 16:03:02 +00:00
Neil Alexander
c388885a92 Update config comments for AllowedEncryptionPublicKeys 2019-03-12 15:29:42 +00:00
Neil Alexander
229de91a3a Fix AllowedEncryptionPublicKeys so that it works in incoming connections and not outgoing ones 2019-03-12 15:01:27 +00:00
Neil Alexander
43643e0307 Merge pull request #374 from yggdrasil-network/develop
Version 0.3.4
2019-03-12 10:02:04 +00:00
Arceliar
b3b64ddb9a Merge pull request #378 from neilalexander/linklocalport
Add LinkLocalTCPPort option
2019-03-10 14:19:28 -05:00
Neil Alexander
ec19c479dd Add comment about no reloading for LinkLocalTCPPort 2019-03-10 19:17:03 +00:00
Neil Alexander
9d5ca85424 Add LinkLocalTCPPort option 2019-03-10 19:08:56 +00:00
Neil Alexander
07822a74c7 Update CHANGELOG.md in preparation for v0.3.4 2019-03-10 18:32:10 +00:00
Neil Alexander
c48cbfa78b Merge pull request #377 from neilalexander/awdl-fixes
AWDL fixes for macOS
2019-03-10 18:08:30 +00:00
Neil Alexander
3c2cdfea1c Keep AWDL awake, or wake it up again after a minute if suspended for some reason (e.g. sleep) 2019-03-10 18:05:27 +00:00
Neil Alexander
9507117a3c Merge pull request #373 from l-n-s/apparmor-2
Add AppArmor profile
2019-03-10 08:55:19 +00:00
Viktor Villainov
f5c6c191ea AppArmor: multiarch support and allow datagram transports 2019-03-09 22:34:26 -05:00
Arceliar
f65e66ac26 Merge pull request #376 from Arceliar/fixes
use idle time in switch decisions to force it to try all links
2019-03-09 19:34:16 -06:00
Arceliar
3c696c3e55 use idle time in switch decisions to force it to try all links 2019-03-09 19:27:52 -06:00
Arceliar
7017c6d102 Merge pull request #375 from Arceliar/fixes
More switch/link fixes
2019-03-09 18:43:09 -06:00
Arceliar
c7b4bfcef5 misc fixes 2019-03-09 18:08:26 -06:00
Neil Alexander
98d66ed048 Update CHANGELOG.md 2019-03-09 23:13:06 +00:00
Viktor Villainov
00ad8e594e Add AppArmor profile 2019-03-09 08:15:14 -05:00
Neil Alexander
f38963c71b Merge pull request #371 from neilalexander/multilink-fixes
Fix bugs from multilink branch (#360)
2019-03-09 09:28:40 +00:00
Neil Alexander
03eec4b14d Don't leak interface name via multicast, ensure zone is always correct when dialling link-local 2019-03-09 09:24:52 +00:00
Arceliar
a0e6edd219 Merge pull request #360 from neilalexander/multilink
Support for multiple listeners
2019-03-08 18:56:47 -06:00
Arceliar
57fa56853d Merge pull request #370 from Arceliar/switch
Try to switch parents if a parent link is blocked
2019-03-08 18:55:16 -06:00
Arceliar
426d157025 make sure we don't replace an existing listener 2019-03-08 18:51:07 -06:00
Neil Alexander
917ca6c1c5 Make changes based on review comments 2019-03-08 10:26:46 +00:00
Arceliar
02b1892cc5 try to switch parents if a parent link is blocked 2019-03-07 21:36:12 -06:00
Neil Alexander
57eb6eaeb0 Clean up config package 2019-03-06 17:45:47 +00:00
Neil Alexander
898f7865b1 Merge pull request #369 from neilalexander/fix339
Fix getRoutes
2019-03-06 17:40:21 +00:00
Neil Alexander
3bc3002fff Add handlers for setTunnelRouting/getTunnelRouting 2019-03-06 17:37:48 +00:00
Neil Alexander
ad7e392afe Fix getRoutes (#339) 2019-03-06 17:32:25 +00:00
Neil Alexander
b8cabf3212 Support removing Listen interfaces at runtime properly 2019-03-06 16:40:48 +00:00
Neil Alexander
18ef28a477 Fix default Listen config 2019-03-06 13:00:45 +00:00
Neil Alexander
531d9f39ca Fix multicast bug, set static multicast interval 15 seconds 2019-03-06 12:15:40 +00:00
Neil Alexander
c0d5a8c0bd Clean up old listeners first 2019-03-06 12:09:57 +00:00
Neil Alexander
f4e17b9a9f Properly handle multicast interfaces going up and down 2019-03-06 12:07:33 +00:00
Neil Alexander
cc0c725e63 Merge branch 'develop' of github.com:yggdrasil-network/yggdrasil-go into multilink 2019-03-06 11:06:58 +00:00
Neil Alexander
81aed4244c Merge pull request #368 from willeponken/add-reload-busybox-init
contrib/busybox-init: add reload and use -useconffile
2019-03-06 11:06:46 +00:00
Neil Alexander
de2aff2758 Refactor multicast so that it creates a new TCP listener for each interface with LL addresses (so that it will not break if Listen is not set with a wildcard address) 2019-03-06 11:06:13 +00:00
William Wennerström
f4ccbe6c94 contrib/busybox-init: add reload and use -useconffile
+ Added reload command.
+ Use -useconffile instead, as it's required for reloading.
2019-03-06 11:30:49 +01:00
Neil Alexander
2419b61b2c Merge branch 'develop' of github.com:yggdrasil-network/yggdrasil-go into multilink 2019-03-05 22:10:36 +00:00
Neil Alexander
03b5261f1c Merge pull request #366 from willeponken/add-contrib-openrc
contrib/openrc: add init file for OpenRC
2019-03-05 22:09:35 +00:00
Neil Alexander
19267beb9e Merge branch 'develop' into multilink 2019-03-05 22:07:20 +00:00
William Wennerström
76dd1f6345 contrib/openrc: I like symmetry 2019-03-05 22:27:51 +01:00
William Wennerström
1097c1c0c9 contrib/openrc: add reload command 2019-03-05 20:50:24 +01:00
William Wennerström
26a952aa6c contrib/openrc: add init file for OpenRC 2019-03-05 20:35:02 +01:00
Neil Alexander
d5a9c39dab Merge pull request #365 from neilalexander/fix362
Add getTunnelRouting and setTunnelRouting
2019-03-05 18:24:07 +00:00
Neil Alexander
236692bdc4 Add getTunnelRouting and setTunnelRouting (fixes #362) 2019-03-05 17:55:46 +00:00
Neil Alexander
fbc0b866fc Merge pull request #364 from neilalexander/fix363
Fix getTunTap
2019-03-05 17:46:08 +00:00
Neil Alexander
a17d6d3a68 Fix getTunTap (fixes #363) 2019-03-05 17:37:26 +00:00
Neil Alexander
e71108dd26 Fix date in changelog.md 2019-03-05 09:16:44 +00:00
Neil Alexander
2ef823e69c Fix deadlock when reconfiguring multicast 2019-03-04 23:16:46 +00:00
Neil Alexander
88925d3e06 Centralise call/listen functions in link.go 2019-03-04 22:45:35 +00:00
Neil Alexander
61774aed3b Show proto in admin socket, link linkInfo from peer, other fixes 2019-03-04 20:33:08 +00:00
Neil Alexander
2b8648e2b3 Fix debug builds 2019-03-04 19:04:09 +00:00
Neil Alexander
0be0b078cb Remove unused types in link.go 2019-03-04 19:00:06 +00:00
Neil Alexander
eeede4e6d0 Fix some obvious concurrency bugs 2019-03-04 18:47:40 +00:00
Neil Alexander
82bb95b77f Some more (inelegant) multiple listener code plus some reconfigure support 2019-03-04 18:41:32 +00:00
Neil Alexander
be8db0c120 Support multiple TCP listeners 2019-03-04 17:52:57 +00:00
Neil Alexander
ae79246a66 Move TCP under link.go 2019-03-04 17:09:48 +00:00
Neil Alexander
ddd1ac4606 Fix launchd file for macOS to use -useconffile 2019-03-04 08:44:25 +00:00
Neil Alexander
1e2436f5c1 Merge pull request #359 from neilalexander/macospkg
Fix macOS package
2019-03-04 08:38:41 +00:00
Neil Alexander
7c435e6c1b Fix macOS package 2019-03-04 08:35:45 +00:00
Neil Alexander
035ace9824 Merge pull request #358 from neilalexander/unixadmin
Add a timeout on the UNIX admin socket check
2019-03-03 19:37:14 +00:00
Neil Alexander
918ce5a3fc Add a timeout on the UNIX admin socket check 2019-03-03 19:32:36 +00:00
Arceliar
be2f46ece8 Merge pull request #356 from neilalexander/unixadmin
Try to clean up UNIX admin socket
2019-03-03 13:15:14 -06:00
Neil Alexander
c940bae9e3 Update output 2019-03-03 14:15:01 +00:00
Neil Alexander
8f66d5b8dd Try to clean up UNIX admin socket 2019-03-03 14:09:54 +00:00
Neil Alexander
8342dfc6d5 Merge pull request #351 from yggdrasil-network/revert-333-develop
Revert "Simplifying Dockerfile"
2019-03-02 15:29:25 +00:00
Neil Alexander
857a33c91b Revert "Simplifying Dockerfile" 2019-03-02 15:26:55 +00:00
Neil Alexander
46a5c7cccf Merge pull request #333 from cwinfo/develop
Simplifying Dockerfile
2019-03-02 10:39:39 +00:00
Neil Alexander
3c733eadb4 Merge pull request #350 from neilalexander/awdl
Wake AWDL for multicast peering on macOS
2019-03-02 10:33:47 +00:00
Arceliar
98a84ec7e5 Merge pull request #338 from Arceliar/fixes
Fixes
2019-03-01 19:18:45 -06:00
Neil Alexander
b401b92a75 Try updating circleci workflow 2019-03-02 00:38:18 +00:00
Neil Alexander
12e088ab9e Remove unnecessary Cgo line 2019-03-01 19:34:53 +00:00
Neil Alexander
e99903bf72 Wake up AWDL on Darwin if awdl0 is an enabled multicast interface 2019-03-01 19:26:50 +00:00
Neil Alexander
a6ae159329 Give some more feedback that a configuration reload actually happens 2019-03-01 18:26:52 +00:00
Neil Alexander
58757bb955 Merge pull request #349 from willeponken/add-busybox-init
contrib/busybox-init: add init.d script for busybox init
2019-03-01 16:34:03 +00:00
William Wennerstr?m
ef77822897 contrib/busybox-init: add init.d script for busybox init 2019-03-01 17:26:07 +01:00
Arceliar
304f22dc1d re-enable session workers in a way that doesn't block and drops packets before decrypting if necessary 2019-02-28 20:05:21 -06:00
Arceliar
06df791efc buffer packets moving from the switch to the router, allow them front drop if there's too many 2019-02-28 19:08:56 -06:00
Arceliar
371b5ca6a2 Change log message about AllowedEncryptionPublicKeys from Debug to Warn 2019-02-28 18:49:34 -06:00
Arceliar
2569242050 fixes to linkInterface.handler() 2019-02-26 21:07:56 -06:00
Arceliar
def4fb3587 fix timeout and improve logging on connection close 2019-02-24 14:48:16 -06:00
Arceliar
654407dc6d close long-dead connections in link.go instead of in switch.go, this is important in case a connection opens but never bothers to send even one switch message 2019-02-24 13:24:55 -06:00
Arceliar
bb3edd5e55 add the relevant error to the default logging when a connection is closed 2019-02-24 12:59:30 -06:00
Arceliar
042adb0516 make sure the only place traffic is ever dropped is in the switch. this currently disables the dedicated crypto workers 2019-02-23 00:07:00 -06:00
Arceliar
68dce0dd74 Merge branch 'switch' into session 2019-02-22 23:16:38 -06:00
Neil Alexander
1f1ba3bab8 Merge pull request #335 from yggdrasil-network/develop
Version 0.3.3
2019-02-21 19:21:27 +00:00
Neil Alexander
24cf4b9d2b Add ExecReload for SIGHUP 2019-02-18 22:31:34 +00:00
Neil Alexander
1192ceaf68 Update CHANGELOG.md 2019-02-18 18:10:05 +00:00
Arceliar
e31962de0f Merge branch 'develop' into switch 2019-02-16 16:26:20 -06:00
Neil Alexander
45224d5f10 Merge pull request #334 from Arceliar/bugfix
Bugfix
2019-02-16 09:57:21 +00:00
Arceliar
957248b3dd add twolink test for namespaces with multiple links with different bandwidth 2019-02-15 20:23:28 -06:00
Arceliar
6f0bbbfb98 Debug some issues with the state machine that tracks idle connections in link.go 2019-02-15 19:35:10 -06:00
Arceliar
3c9c8672c9 Merge branch 'develop' of https://github.com/yggdrasil-network/yggdrasil-go into switch 2019-02-15 16:28:48 -06:00
Christer Warén
2b70f967a8 Merge pull request #14 from cwinfo/new-docker-solution
New-Docker-Solution to Branch Develop
2019-02-11 16:54:45 +02:00
Christer Warén
594a988ec6 Merge pull request #13 from yggdrasil-network/develop
Branch Develop: Base to Fork
2019-02-11 16:53:12 +02:00
Christer Warén
c2561c90c7 Update Dockerfile
Making easier to install and update, because dont have to download Github repository manually.
2019-02-11 16:50:22 +02:00
Neil Alexander
2ba427a737 Merge pull request #329 from neilalexander/readtimeout
Remove ReadTimeout configuration option
2019-02-10 18:19:20 +00:00
Neil Alexander
8ab624242a Merge pull request #330 from neilalexander/systemdresume
Remove yggdrasil-resume.service
2019-02-10 18:19:09 +00:00
Neil Alexander
3640ee7177 Merge pull request #331 from neilalexander/macosci
Don't build 32-bit builds for macOS
2019-02-10 18:18:58 +00:00
Neil Alexander
30320801d3 Don't build 32-bit builds for macOS 2019-02-10 15:23:49 +00:00
Neil Alexander
fe09c234bc Fix Debian generate.sh 2019-02-10 12:29:04 +00:00
Neil Alexander
9f7609817e Remove yggdrasil-resume.service from systemd contrib and Debian package 2019-02-10 12:22:39 +00:00
Neil Alexander
0ca64b0abe Remove ReadTimeout configuration option 2019-02-10 12:13:49 +00:00
Arceliar
21cecf4630 consistently prioritize which peer to forward to instead of letting it be partly random 2019-02-09 17:44:25 -06:00
Arceliar
f7576d6b69 Merge pull request #327 from Arceliar/bugfix
fix bug in switch time
2019-02-09 15:35:03 -06:00
Arceliar
ad43558fbb fix bug in switch time 2019-02-09 15:30:17 -06:00
Arceliar
74ac535d55 slightly faster switch logic, should be easier to have a useful tie-breaker for peers that are equally close to the destination via the tree metric 2019-02-08 19:46:11 -06:00
Arceliar
16d754bbbc Merge pull request #325 from Arceliar/alloc
Fix allocations found in profiling
2019-02-05 17:51:58 -06:00
Arceliar
41f49faaa0 get code running in the netns test again, remove unnecessary allocations that were found in profiling 2019-02-05 17:39:59 -06:00
Neil Alexander
4ed10bc528 Merge pull request #324 from mikhailnov/develop
Fail build script if building of any target fails
2019-02-05 12:25:18 +00:00
Mikhail Novosyolov
ebdd968c24 Fail build script if building of any target fails
E.g, I had a build error of yggdrasil, but ./build returned exit code 0:

+ ./build -t -l -linkmode=external
Building: yggdrasil
github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil
/home/user/go/src/github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil/multicast.go:39:9: undefined: net.ListenConfig
Building: yggdrasilctl
+ exit 0
2019-02-05 15:22:09 +03:00
Christer Warén
462af89600 Merge pull request #12 from cwinfo/develop
Branch Develop to New-Docker-Solution
2019-02-04 15:25:37 +02:00
Christer Warén
7407663a3f Merge pull request #11 from yggdrasil-network/develop
Branch Develop: Base to Fork
2019-02-04 15:24:13 +02:00
Arceliar
f747f259b3 Merge pull request #318 from yggdrasil-network/link
Implement link interface
2019-02-03 16:21:45 -06:00
Arceliar
a49a9bbf18 Merge pull request #322 from Arceliar/link
Idle link logic changes
2019-02-03 15:57:45 -06:00
Arceliar
2f8dd5dde0 remove race condition in setting peer.close by requiring it as an argument to newPeer 2019-02-03 15:50:25 -06:00
Arceliar
5ddf84f329 remove peers completely after a long switch timeout, this could use some improvement later 2019-02-03 15:22:14 -06:00
Arceliar
ebbe5f67ad don't time out a link unless we were expecting an ack and didn't get one 2019-02-02 22:41:51 -06:00
Arceliar
b44a0f29f3 send an ack if we receive a packet and don't have any return traffic, keeping a legacy 4-second keep-alive in case there's no traffic at all to send (to be removed later, after nodes have upgraded), ideally we should either remove ReadTimeout or use it for the switch idle timeout instead 2019-02-02 22:18:55 -06:00
Neil Alexander
6d83d970bb Merge pull request #320 from neilalexander/link
link.go: Connect/disconnect logging, check AllowedEncryptionKeys
2019-02-01 07:57:27 +00:00
Neil Alexander
43f798e82e Check link-local in tcp.go, track direction in link.go, fix compile error for mobile.go 2019-02-01 00:02:17 +00:00
Neil Alexander
ec5f7d9879 Enforce AllowedEncryptionPublicKeys for all peers inc. link-local 2019-01-31 23:47:20 +00:00
Neil Alexander
432f93de89 Check AllowedEncryptionPublicKeys 2019-01-31 23:29:18 +00:00
Neil Alexander
e36f88c75f Info logging when link connects/disconnects 2019-01-31 23:18:02 +00:00
Arceliar
05962b2cbd disable idle nodes in the switch instead of killing the connection entirely. this implementation is ugly, but i think it maybe works 2019-01-30 20:58:23 -06:00
Christer Warén
901333cc99 Update Dockerfile 2019-01-30 13:12:46 +02:00
Christer Warén
6e196b9f81 Merge pull request #10 from yggdrasil-network/develop
Branch Develop: Base to Fork
2019-01-30 13:02:34 +02:00
Neil Alexander
2466c54a71 Update debug lines in link.go 2019-01-27 20:56:10 +00:00
Neil Alexander
1a3a67f205 Merge remote-tracking branch 'origin/develop' into link
Fix merge conflicts from flexible logging branch
2019-01-27 20:54:21 +00:00
Neil Alexander
c5cc280556 Merge pull request #287 from neilalexander/logging
Add flexible logging support
2019-01-27 20:51:55 +00:00
Neil Alexander
22d2e0e4fe Fix debug builds 2019-01-27 13:33:32 +00:00
Neil Alexander
0838928668 Add support for flexible logging levels 2019-01-27 13:31:43 +00:00
Arceliar
6ef0e47632 fix merge conflict 2019-01-26 14:27:37 -06:00
Arceliar
5b31cbee2a Merge pull request #317 from Arceliar/deadlock
Fix a deadlock
2019-01-26 14:24:03 -06:00
Arceliar
bca69df1f6 possible workaround to a deadlock 2019-01-26 14:07:18 -06:00
Arceliar
15750a5194 Merge pull request #316 from jcgruenhage/ansible
make ansible key generator a tiny bit faster
2019-01-25 18:18:38 -06:00
Jan Christian Grünhage
2baedc9bcc make ansible key generator a tiny bit faster 2019-01-25 21:28:22 +01:00
Neil Alexander
705b914d00 Move awdl into link 2019-01-23 19:42:33 +00:00
Neil Alexander
188a9e439d Bug fixes for AWDL 2019-01-23 18:16:31 +00:00
Neil Alexander
2944be4faf Further tweaks 2019-01-23 17:05:16 +00:00
Neil Alexander
81545fd9bf Clean up 2019-01-23 15:16:22 +00:00
Neil Alexander
9c6cf50684 Adapt AWDL to link 2019-01-23 15:08:19 +00:00
Arceliar
7b2460662d close the connection before blocking a duplicate link 2019-01-22 21:53:39 -06:00
Arceliar
f936151f2d correctly clean up various things 2019-01-22 21:48:43 -06:00
Arceliar
eb8951081d fix duplicate connection bug, I think this is also in develop 2019-01-22 21:23:57 -06:00
Arceliar
12c0e019dc only create one interface, but still opens duplicate connections before it catches this, so more work is needed 2019-01-22 21:16:41 -06:00
Arceliar
f95663e923 actually finish initializing peers 2019-01-22 18:24:15 -06:00
Arceliar
137212d8cf work in progress, establishes TCP connections and gets through metadata handshake using the link code, but doesn't seem to send traffic yet (no switch peers are created) 2019-01-21 23:08:50 -06:00
Arceliar
5a4d6481dd Work in progress, add a linkInterfaceMsgIO interface type and make stream implement it, this will be used by link 2019-01-21 21:27:52 -06:00
Neil Alexander
ad6ec6ab11 Merge pull request #315 from neilalexander/flowlabel
Update switch flow separation for IPv4
2019-01-21 23:22:40 +00:00
Neil Alexander
62d4d62a77 Update comments 2019-01-21 16:24:29 +00:00
Neil Alexander
d3f67ad017 Improve command flow 2019-01-21 16:22:49 +00:00
Neil Alexander
cdfb930703 Update switch flow separation for IPv4 2019-01-21 12:27:29 +00:00
Arceliar
c8e1be0f73 link/stream refactoring bugfixes and gofmt 2019-01-19 16:37:45 -06:00
Neil Alexander
41a410f2a1 Initialise awdl.go from link.go, remove deadlock between awdl.create and link.create, other bits and pieces 2019-01-19 12:19:24 +00:00
Neil Alexander
c51a3340b1 Update awdl.go to use new link stuff (untested) 2019-01-19 00:42:53 +00:00
Neil Alexander
4ae36dfffe Merge branch 'ios' into link 2019-01-19 00:23:38 +00:00
Neil Alexander
6fe3b01e90 Rename awdl.go to link.go, add stream.go, update tcp.go 2019-01-19 00:14:10 +00:00
Neil Alexander
30df632eb2 Merge branch 'develop' into ios 2019-01-17 23:15:00 +00:00
Neil Alexander
f6cb194d5c Merge pull request #313 from neilalexander/sourcefix
Fix source address selection
2019-01-17 23:10:54 +00:00
Neil Alexander
c839012580 Fix source address selection 2019-01-17 23:06:59 +00:00
Neil Alexander
2219d96df1 Merge pull request #312 from neilalexander/nodeconfig
Further nodeconfig changes
2019-01-16 20:51:55 +00:00
Neil Alexander
9d5085492e Handle session firewall using central config 2019-01-16 20:38:51 +00:00
Neil Alexander
fdf300a1ff Handle AllowedEncryptionPublicKeys internally 2019-01-16 20:26:39 +00:00
Neil Alexander
68eb684f6d Fix mobile.go now that multicast interfaces are handled internally 2019-01-16 19:27:44 +00:00
Neil Alexander
4b6c925cb4 Merge pull request #311 from neilalexander/sintf
Fix source address selection when sintf specified
2019-01-16 14:59:20 +00:00
Neil Alexander
8baf593b62 Update source address selection when sintf specified 2019-01-16 14:52:27 +00:00
Neil Alexander
c3bd3bd140 Merge pull request #310 from neilalexander/nodeconfig
Bug fixes from nodeconfig branch
2019-01-16 13:26:19 +00:00
Neil Alexander
c85dbaea95 Fix missing nodeinfo.init 2019-01-16 13:23:26 +00:00
Neil Alexander
4fba558638 Fix concurrent map write in tcp.go 2019-01-16 13:20:12 +00:00
Neil Alexander
8fa9b84108 Merge pull request #297 from neilalexander/nodeconfig
Use central NodeConfig for components
2019-01-15 23:51:58 +00:00
Neil Alexander
9780e11ccf Merge pull request #309 from jcgruenhage/patch-1
update ansible key generator
2019-01-15 16:12:10 +00:00
Jan Christian Grünhage
21306532c1 update ansible key generator
It's probably easier to use without it having an ansible folder prefix, if people want to put it into a different dir then they should change their workdir. Apart from that, this fixes a bug where I defined yggdrasil_signing_public_key twice, but never the private key.
2019-01-15 16:48:25 +01:00
Neil Alexander
53be1b02f3 Check if accepting socket produced an error 2019-01-15 08:53:57 +00:00
Neil Alexander
2cd373fc1e Remove unnecessary selects 2019-01-15 08:51:19 +00:00
Neil Alexander
39567bed83 Address some comments 2019-01-15 08:44:33 +00:00
Neil Alexander
3bf53796a7 Merge branch 'develop' into nodeconfig 2019-01-15 08:37:10 +00:00
Arceliar
1f21903ea7 Merge pull request #308 from jcgruenhage/ansible-key-generator
add ansible key generator
2019-01-14 21:27:34 -06:00
Arceliar
248edf8319 Merge pull request #304 from deavmi/develop
Spelling fixes
2019-01-14 21:27:15 -06:00
Jan Christian Grünhage
88cf6b7684 add ansible key generator 2019-01-15 02:28:27 +01:00
Neil Alexander
d9ddf30faf Fix debug builds 2019-01-14 19:29:22 +00:00
Neil Alexander
f6b663c257 Make multicasting use config instead of ifceExpr in Core 2019-01-14 19:27:13 +00:00
Neil Alexander
9e486ed4fe Move nodeinfo into router 2019-01-14 19:05:16 +00:00
Neil Alexander
5cde3b5efc Update nodeinfo in router reconfigure 2019-01-14 18:51:49 +00:00
Neil Alexander
9e186bdd67 Remove mutexes from CKR and use router goroutine/doAdmin for update config 2019-01-14 18:34:15 +00:00
Neil Alexander
51026d762e Make session firewall thread-safe for config updates 2019-01-14 18:24:35 +00:00
Neil Alexander
bd04124e43 Reconfigure support for crypto-key routing 2019-01-14 18:06:41 +00:00
Neil Alexander
28072c9fe2 Make CKR thread-safe 2019-01-14 17:41:08 +00:00
Neil Alexander
87d393bd9f Move add peer loop into Core, refresh it from active config 2019-01-14 17:21:15 +00:00
Neil Alexander
aed3c7e784 Give nodeconfig to tun 2019-01-14 14:25:52 +00:00
Neil Alexander
738a9da796 Merge branch 'develop' into nodeconfig 2019-01-14 14:01:38 +00:00
Christer Warén
38b114a151 Merge pull request #8 from yggdrasil-network/develop
Branch Develop: Branch to Patch
2019-01-14 05:17:55 +02:00
Neil Alexander
3fa5893fd6 Merge pull request #306 from neilalexander/ios
AWDL support, Android tweaks
2019-01-13 23:14:01 +00:00
Neil Alexander
f556f3e2a8 Try to perform TCP-like key exchange 2019-01-13 22:57:37 +00:00
Neil Alexander
704e4a062f Specify source interface when responding to multicast beacon 2019-01-13 22:51:34 +00:00
Neil Alexander
c8db66b17d Remove unneeded AWDL context functions 2019-01-13 18:11:36 +00:00
Neil Alexander
4622a85c34 AWDL support for macOS/iOS 2019-01-13 18:08:41 +00:00
Neil Alexander
4f7e8856b8 Update build script for iOS/Android 2019-01-10 17:44:52 +00:00
Neil Alexander
e24e859254 Add some comments, move AWDL functions to iOS-specific build tags 2019-01-10 11:31:04 +00:00
Neil Alexander
a371e34a18 Add Android support, add addStaticPeers 2019-01-10 10:44:44 +00:00
Tristan B. Kildaire
08a71af2d8 Spelling fixes for switch.go 2019-01-09 11:49:12 +02:00
Tristan B. Kildaire
345979b502 Spelling fixes for search.go 2019-01-09 11:44:45 +02:00
Tristan B. Kildaire
ab4be3424b Spelling fixes for peer.go 2019-01-09 11:42:07 +02:00
Neil Alexander
8119db10c7 Merge pull request #302 from neilalexander/logo
Logo proposal
2019-01-08 22:17:09 +00:00
Arceliar
38209ee9b9 Merge pull request #301 from neilalexander/ios
Boilerplate for iOS support
2019-01-06 17:06:24 -06:00
Neil Alexander
6efac9a377 Add contexts 2019-01-06 14:12:10 +00:00
Neil Alexander
2034c9eab9 Fix missing pointer from awdlInterface to awdl 2019-01-05 23:00:49 +00:00
Neil Alexander
87362a21e2 Access NSLog through Cgo for iOS NetworkExtension logging 2019-01-05 21:59:07 +00:00
Neil Alexander
6bbd8c1b30 Rethink channels, more error throwing 2019-01-05 12:06:45 +00:00
Neil Alexander
90366dd853 Update handler behavior 2019-01-05 01:02:22 +00:00
Neil Alexander
1170ea9e98 Start linkloop 2019-01-05 00:52:41 +00:00
Neil Alexander
4363283a6f Notify switch idle 2019-01-05 00:32:28 +00:00
Neil Alexander
00bf71a09a Fight me Swift and your hexadecimal strings 2019-01-04 23:31:44 +00:00
Neil Alexander
5a36b4723a Add AWDL calls to exposed API, handle proto traffic first 2019-01-04 17:41:03 +00:00
Neil Alexander
3878197a59 gofmt 2019-01-04 17:23:37 +00:00
Neil Alexander
f29a098488 Add experimental dummy interface for AWDL 2019-01-04 17:14:40 +00:00
Neil Alexander
d10a0d6137 Add GenerateConfigJSON, fix StartJSON 2019-01-03 22:50:08 +00:00
Neil Alexander
f7b0a85b5e Add StartJSON 2019-01-02 23:15:36 +00:00
Neil Alexander
4ff3db2309 Add dummy tun, helper functions 2019-01-02 18:05:54 +00:00
Neil Alexander
53aeca8fa2 Add some simple functions for Swift bindings (iOS) 2019-01-01 23:25:20 +00:00
Neil Alexander
1e29465af1 Fix debug builds (hopefully) 2018-12-31 12:08:15 +00:00
Neil Alexander
4d3e90cbfe Merge branch 'develop' into nodeconfig 2018-12-31 11:55:37 +00:00
Neil Alexander
d08a3c6643 Merge pull request #299 from neilalexander/getnodeinfo
getNodeInfo: Show own info if box_pub_key/coords not specified
2018-12-31 11:51:36 +00:00
Neil Alexander
fb47c9822f getNodeInfo: Show own info if box_pub_key/coords not specified 2018-12-31 11:48:50 +00:00
Neil Alexander
cd86c33850 Try to tidy up a bit, move checks for if we are already calling/connected
Something I noticed when working on reconfigure support for the "Listen"
option is that we have some rather huge weaknesses in our multicasting
design. Right now if we change our Listen address, it's not really
possible for remote nodes to know whether they are still connected to
us, so they start connecting in response to our changed beacons. They
can't know that they already know about us until *after* the handshake
but this registers in the local client log as repeated Connect/Disconnects
even though the existing peerings never actually drop.
2018-12-30 21:11:16 +00:00
Neil Alexander
80c9a1bc12 Don't track localAddr in conns as it is irrelevant 2018-12-30 16:48:34 +00:00
Neil Alexander
cb4495902b Allow updating Listen during runtime 2018-12-30 15:21:09 +00:00
Neil Alexander
f96747181d Allow updating AdminListen during runtime 2018-12-30 12:26:55 +00:00
Neil Alexander
7fae1c993a Handle errors from reconfigure tasks 2018-12-30 12:04:42 +00:00
Neil Alexander
2925920c70 Use mutex in switch/tcp init 2018-12-29 19:53:31 +00:00
Neil Alexander
fa7c4117b4 Use Core.config in init functions 2018-12-29 19:14:26 +00:00
Neil Alexander
219fb96553 Support notifying components for config reload, listen for SIGHUP 2018-12-29 18:51:51 +00:00
Neil Alexander
34778bd49f Merge pull request #293 from neilalexander/version
Rewrite contrib/semver/version.sh
2018-12-27 22:29:36 +00:00
Neil Alexander
e6e7f9377f Move --count parameter 2018-12-27 21:45:30 +00:00
Neil Alexander
8c7b9e2f90 Add a null check to name.sh 2018-12-27 21:44:29 +00:00
Neil Alexander
6fcd8a8dbd Fix incorrect check 2018-12-27 21:36:50 +00:00
Neil Alexander
7eaee172cf Replace tests with ifs 2018-12-27 21:22:46 +00:00
Neil Alexander
57894541b7 Check string emptiness 2018-12-27 21:14:23 +00:00
Neil Alexander
e6a246f040 Chop up contrib/semver/version.sh 2018-12-27 20:03:46 +00:00
Neil Alexander
b4a7dab34d Versioning be damned 2018-12-26 23:50:17 +00:00
Neil Alexander
9a5f3a0abb Merge pull request #291 from yggdrasil-network/develop
Version 0.3.2
2018-12-26 23:42:52 +00:00
Neil Alexander
dd7c5ec52b Merge pull request #290 from neilalexander/changelog
Update changelog for v0.3.2
2018-12-26 23:39:30 +00:00
Neil Alexander
52e6461433 Update changelog for v0.3.2 2018-12-26 23:28:12 +00:00
Neil Alexander
a71c5dd887 Merge pull request #289 from neilalexander/tunicmpv6
Don't process ICMPv6 messages when in TUN mode
2018-12-26 23:18:23 +00:00
Neil Alexander
4e03bdb054 Don't process ICMPv6 messages when in TUN mode 2018-12-26 22:45:21 +00:00
Neil Alexander
cc2b6f093c Merge pull request #288 from sinbsd/develop
Use #!/bin/sh for clean script
2018-12-26 14:29:34 +00:00
sin
702317add1 Use #!/bin/sh for clean script 2018-12-26 14:26:29 +00:00
Neil Alexander
a8e6b864fd Merge pull request #286 from neilalexander/fixndp
Fix ICMPv6 behaviour when populating peermacs
2018-12-26 12:29:16 +00:00
Neil Alexander
74692b689a Fix OpenBSD (tested and working on 6.4) 2018-12-26 12:25:28 +00:00
Neil Alexander
b3d6c9a385 Print when peermacs learned 2018-12-26 11:57:08 +00:00
Neil Alexander
9eeb482587 Use ICMPv6 NDP target instead of source address when populating peermacs 2018-12-26 11:51:21 +00:00
Arceliar
4e5906bf23 Merge pull request #284 from Arceliar/dht
insert a copy when calling dht.insertPeer
2018-12-26 00:26:54 -06:00
Arceliar
50ed92d6d2 insert a copy when calling dht.insertPeer 2018-12-26 00:18:51 -06:00
Neil Alexander
13d14b67ab Merge pull request #278 from deavmi/patch-1
Typo fix
2018-12-22 10:19:50 +00:00
Tristan B. Kildaire
b66049c14f Typo fix
Typo fix in function's header comment.
2018-12-22 11:31:52 +02:00
Neil Alexander
0dfdc789d3 Merge pull request #275 from neilalexander/nodeinfomask
Allow hiding nodeinfo defaults
2018-12-22 08:54:37 +00:00
Neil Alexander
e428077a2a Merge pull request #276 from Arceliar/dht
Tune DHT a little better
2018-12-22 08:53:44 +00:00
Arceliar
59093aa43b clean up node info immediately if it reaches the timeout or if it needs refreshing but won't be pinged due to being unimportant 2018-12-21 17:45:24 -06:00
Arceliar
973f76fb76 Merge pull request #277 from deavmi/develop
Typo fix in Whitepaper.
2018-12-21 17:24:24 -06:00
Tristan B. Kildaire
39997267f7 Typo fix.
Just a typo fix.
2018-12-21 15:04:15 +02:00
Neil Alexander
f6b0075989 Case-insensitive checking of null if string, don't print the nodeinfo again 2018-12-21 10:04:32 +00:00
Neil Alexander
586deed0f9 Add NodeInfoPrivacy option for not including defaults, and also check for null/"null" instead of "hide" 2018-12-21 09:56:34 +00:00
Arceliar
f59852b1e1 adjust how dht throttle works, it should now back off faster, and back off even more if things are not in use 2018-12-20 20:16:51 -06:00
Neil Alexander
60549cfa09 Adds special keyword 'hide' for masking built-in nodeinfo defaults 2018-12-20 23:49:15 +00:00
Arceliar
dfcdafa55c move special peer/dht insert logic form router.go to dht.go 2018-12-20 17:37:59 -06:00
Christer Warén
3bbe922f5f Merge pull request #7 from yggdrasil-network/develop
Branch Develop: Branch to Patch
2018-12-19 11:32:34 +02:00
Arceliar
e65910806c Merge pull request #270 from neilalexander/circleci
Don't fail if tag exists
2018-12-18 18:47:57 -06:00
Neil Alexander
3283de17d5 Don't fail if tag exists 2018-12-19 00:15:12 +00:00
Neil Alexander
b2fcf130b9 Merge pull request #269 from darkdrgn2k/armv6
Added compile support for Arm v6
2018-12-18 23:59:51 +00:00
darkdrgn2k
f91fb1045c Downgraded all armhf builds to v6 2018-12-18 12:28:15 -05:00
darkdrgn2k
ace9568981 Added compile support for Arm v6
Support for older raspberry pis and Pi Zero
2018-12-18 11:36:15 -05:00
Neil Alexander
631f1fe907 Merge pull request #267 from neilalexander/arghsemver
CircleCI: don't recreate tags that already exist
2018-12-18 12:17:47 +00:00
Neil Alexander
09ea9a166f More tweaks to semver 2018-12-18 12:14:47 +00:00
Neil Alexander
3ea33c9fa7 Fix build tags maybe? 2018-12-18 12:03:23 +00:00
Neil Alexander
bfdb079b79 Append suffix to master releaser releases if they are not the first merge (package revisions) 2018-12-18 11:52:22 +00:00
Neil Alexander
5684279403 CircleCI: don't recreate tags that already exist 2018-12-18 11:47:46 +00:00
Neil Alexander
953ad0ef59 Update neilalexander's logo proposal 2018-11-14 19:25:35 +00:00
Neil Alexander
9e5f90d0e4 Add neilalexander's logo proposal 2018-11-04 19:15:53 +00:00
92 changed files with 7183 additions and 3710 deletions

View File

@@ -1,11 +1,148 @@
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
version: 2.1
jobs:
build:
build-linux:
docker:
- image: circleci/golang:1.11
- image: circleci/golang:1.12.7
steps:
- checkout
- run:
name: Create artifact upload directory and set variables
command: |
mkdir /tmp/upload
echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV
echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV
echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV
echo 'export CIBRANCH=$(echo $CIRCLE_BRANCH | tr -d "/")' >> $BASH_ENV
case "$CINAME" in \
"yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \
"yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \
*) (echo 'export CICONFLICTS="yggdrasil yggdrasil-develop"' >> $BASH_ENV) ;; \
esac
git config --global user.email "$(git log --format='%ae' HEAD -1)";
git config --global user.name "$(git log --format='%an' HEAD -1)";
- run:
name: Install RPM utilities
command: |
sudo apt-get install -y rpm file
mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS
- run:
name: Test debug builds
command: |
./build -d
test -f yggdrasil && test -f yggdrasilctl
- run:
name: Build for Linux (including Debian packages)
command: |
rm -f {yggdrasil,yggdrasilctl}
PKGARCH=amd64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-amd64;
PKGARCH=i386 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-i386;
PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel;
PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips;
PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf;
PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64;
mv *.deb /tmp/upload/
- run:
name: Build for Linux (RPM packages)
command: |
git clone https://github.com/yggdrasil-network/yggdrasil-package-rpm ~/rpmbuild/SPECS
cd ../ && tar -czvf ~/rpmbuild/SOURCES/v$CIVERSIONRPM --transform "s/project/yggdrasil-go-$CIBRANCH-$CIVERSIONRPM/" project
sed -i "s/yggdrasil-go/yggdrasil-go-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^Name\:.*/Name\: $CINAME/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^Version\:.*/Version\: $CIVERSIONRPM/" ~/rpmbuild/SPECS/yggdrasil.spec
sed -i "s/^Conflicts\:.*/Conflicts\: $CICONFLICTS/" ~/rpmbuild/SPECS/yggdrasil.spec
cat ~/rpmbuild/SPECS/yggdrasil.spec
GOARCH=amd64 rpmbuild -v --nodeps --target=x86_64 -ba ~/rpmbuild/SPECS/yggdrasil.spec
#GOARCH=386 rpmbuild -v --nodeps --target=i386 -bb ~/rpmbuild/SPECS/yggdrasil.spec
find ~/rpmbuild/RPMS/ -name '*.rpm' -exec mv {} /tmp/upload \;
find ~/rpmbuild/SRPMS/ -name '*.rpm' -exec mv {} /tmp/upload \;
- run:
name: Build for EdgeRouter
command: |
rm -f {yggdrasil,yggdrasilctl}
git clone https://github.com/neilalexander/vyatta-yggdrasil /tmp/vyatta-yggdrasil;
cd /tmp/vyatta-yggdrasil;
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-x $CIRCLE_BRANCH;
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-lite $CIRCLE_BRANCH;
mv *.deb /tmp/upload;
- persist_to_workspace:
root: /tmp
paths:
- upload
build-macos:
macos:
xcode: "10.0.0"
working_directory: ~/go/src/github.com/yggdrasil-network/yggdrasil-go
steps:
- checkout
- run:
name: Create artifact upload directory and set variables
command: |
mkdir /tmp/upload
echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV
echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV
echo 'export PATH=$PATH:/usr/local/go/bin:~/go/bin' >> $BASH_ENV
git config --global user.email "$(git log --format='%ae' HEAD -1)";
git config --global user.name "$(git log --format='%an' HEAD -1)";
echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- run:
name: Install Go 1.12.7
command: |
cd /tmp
curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg
sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target /
#- run:
# name: Install Gomobile
# command: |
# GO111MODULE=off go get golang.org/x/mobile/cmd/gomobile
# gomobile init
- run:
name: Build for macOS
command: |
GO111MODULE=on GOOS=darwin GOARCH=amd64 ./build
cp yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64
cp yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64;
- run:
name: Build for macOS (.pkg format)
command: |
PKGARCH=amd64 sh contrib/macos/create-pkg.sh
mv *.pkg /tmp/upload/
#- run:
# name: Build framework for iOS (.framework format)
# command: |
# sudo GO111MODULE=off go get -v github.com/yggdrasil-network/yggdrasil-go/cmd/...
# sudo GO111MODULE=off go get -v github.com/yggdrasil-network/yggdrasil-go/src/...
# GO111MODULE=off ./build -i
# mv *.framework /tmp/upload
- persist_to_workspace:
root: /tmp
paths:
- upload
build-other:
docker:
- image: circleci/golang:1.12.7
steps:
- checkout
@@ -19,44 +156,6 @@ jobs:
git config --global user.email "$(git log --format='%ae' HEAD -1)";
git config --global user.name "$(git log --format='%an' HEAD -1)";
- run:
name: Install alien
command: |
sudo apt-get install -y alien
- run:
name: Test debug builds
command: |
./build -d
test -f yggdrasil && test -f yggdrasilctl
- run:
name: Build for Linux (including Debian packages and RPMs)
command: |
rm -f {yggdrasil,yggdrasilctl}
PKGARCH=amd64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-amd64;
PKGARCH=i386 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-i386;
PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel;
PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips;
PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf;
PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64;
sudo alien --to-rpm yggdrasil*.deb --scripts --keep-version && mv *.rpm /tmp/upload/;
mv *.deb /tmp/upload/
- run:
name: Build for macOS
command: |
rm -f {yggdrasil,yggdrasilctl}
GOOS=darwin GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64;
GOOS=darwin GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-i386;
- run:
name: Build for macOS (.pkg format)
command: |
rm -rf {yggdrasil,yggdrasilctl}
GOOS=darwin GOARCH=amd64 ./build && PKGARCH=amd64 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/
GOOS=darwin GOARCH=386 ./build && PKGARCH=i386 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/
- run:
name: Build for OpenBSD
command: |
@@ -85,26 +184,31 @@ jobs:
GOOS=windows GOARCH=amd64 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-amd64.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-amd64.exe;
GOOS=windows GOARCH=386 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-i386.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-i386.exe;
- run:
name: Build for EdgeRouter
command: |
rm -f {yggdrasil,yggdrasilctl}
git clone https://github.com/neilalexander/vyatta-yggdrasil /tmp/vyatta-yggdrasil;
cd /tmp/vyatta-yggdrasil;
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-x $CIRCLE_BRANCH;
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-lite $CIRCLE_BRANCH;
mv *.deb /tmp/upload;
- persist_to_workspace:
root: /tmp
paths:
- upload
upload:
machine: true
steps:
- attach_workspace:
at: /tmp
- store_artifacts:
path: /tmp/upload
destination: /
- run:
name: Create tags (master branch only)
command: >
if [ "${CIRCLE_BRANCH}" == "master" ]; then
git tag -f -a $(sh contrib/semver/version.sh) -m "Created by CircleCI" && git push -f --tags;
else
echo "Only runs for master branch (this is ${CIRCLE_BRANCH})";
fi;
when: on_success
workflows:
version: 2.1
build:
jobs:
- build-linux
- build-macos
- build-other
- upload:
requires:
- build-linux
- build-macos
- build-other

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "doc/yggdrasil-network.github.io"]
path = doc/yggdrasil-network.github.io
url = https://github.com/yggdrasil-network/yggdrasil-network.github.io/

View File

@@ -25,6 +25,144 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities.
-->
## [0.3.8] - 2019-08-21
### Changed
- Yggdrasil can now send multiple packets from the switch at once, which results in improved throughput with smaller packets or lower MTUs
- Performance has been slightly improved by not allocating cancellations where not necessary
- Crypto-key routing options have been renamed for clarity
- `IPv4Sources` is now named `IPv4LocalSubnets`
- `IPv6Sources` is now named `IPv6LocalSubnets`
- `IPv4Destinations` is now named `IPv4RemoteSubnets`
- `IPv6Destinations` is now named `IPv6RemoteSubnets`
- The old option names will continue to be accepted by the configuration parser for now but may not be indefinitely
- When presented with multiple paths between two nodes, the switch now prefers the most recently used port when possible instead of the least recently used, helping to reduce packet reordering
- New nonce tracking should help to reduce the number of packets dropped as a result of multiple/aggregate paths or congestion control in the switch
### Fixed
- **Security vulnerability**: Address verification was not strict enough, which could result in a malicious session sending traffic with unexpected or spoofed source or destination addresses which Yggdrasil could fail to reject
- Versions `0.3.6` and `0.3.7` are vulnerable - users of these versions should upgrade as soon as possible
- Versions `0.3.5` and earlier are not affected
- A deadlock was fixed in the session code which could result in Yggdrasil failing to pass traffic after some time
## [0.3.7] - 2019-08-14
### Changed
- The switch should now forward packets along a single path more consistently in cases where congestion is low and multiple equal-length paths exist, which should improve stability and result in fewer out-of-order packets
- Sessions should now be more tolerant of out-of-order packets, by replacing a bitmask with a variable sized heap+map structure to track recently received nonces, which should reduce the number of packets dropped due to reordering when multiple paths are used or multiple independent flows are transmitted through the same session
- The admin socket can no longer return a dotfile representation of the known parts of the network, this could be rebuilt by clients using information from `getSwitchPeers`,`getDHT` and `getSessions`
### Fixed
- A number of significant performance regressions introduced in version 0.3.6 have been fixed, resulting in better performance
- Flow labels are now used to prioritise traffic flows again correctly
- In low-traffic scenarios where there are multiple peerings between a pair of nodes, Yggdrasil now prefers the most active peering instead of the least active, helping to reduce packet reordering
- The `Listen` statement, when configured as a string rather than an array, will now be parsed correctly
- The admin socket now returns `coords` as a correct array of unsigned 64-bit integers, rather than the internal representation
- The admin socket now returns `box_pub_key` in string format again
- Sessions no longer leak/block when no listener (e.g. TUN/TAP) is configured
- Incoming session connections no longer block when a session already exists, which results in less leaked goroutines
- Flooded sessions will no longer block other sessions
- Searches are now cleaned up properly and a couple of edge-cases with duplicate searches have been fixed
- A number of minor allocation and pointer fixes
## [0.3.6] - 2019-08-03
### Added
- Yggdrasil now has a public API with interfaces such as `yggdrasil.ConnDialer`, `yggdrasil.ConnListener` and `yggdrasil.Conn` for using Yggdrasil as a transport directly within applications
- Session gatekeeper functions, part of the API, which can be used to control whether to allow or reject incoming or outgoing sessions dynamically (compared to the previous fixed whitelist/blacklist approach)
- Support for logging to files or syslog (where supported)
- Platform defaults now include the ability to set sane defaults for multicast interfaces
### Changed
- Following a massive refactoring exercise, Yggdrasil's codebase has now been broken out into modules
- Core node functionality in the `yggdrasil` package with a public API
- This allows Yggdrasil to be integrated directly into other applications and used as a transport
- IP-specific code has now been moved out of the core `yggdrasil` package, making Yggdrasil effectively protocol-agnostic
- Multicast peer discovery functionality is now in the `multicast` package
- Admin socket functionality is now in the `admin` package and uses the Yggdrasil public API
- TUN/TAP, ICMPv6 and all IP-specific functionality is now in the `tuntap` package
- `PPROF` debug output is now sent to `stderr` instead of `stdout`
- Node IPv6 addresses on macOS are now configured as `secured`
- Upstream dependency references have been updated, which includes a number of fixes in the Water library
### Fixed
- Multicast discovery is no longer disabled if the nominated interfaces aren't available on the system yet, e.g. during boot
- Multicast interfaces are now re-evaluated more frequently so that Yggdrasil doesn't need to be restarted to use interfaces that have become available since startup
- Admin socket error cases are now handled better
- Various fixes in the TUN/TAP module, particularly surrounding Windows platform support
- Invalid keys will now cause the node to fail to start, rather than starting but silently not working as before
- Session MTUs are now always calculated correctly, in some cases they were incorrectly defaulting to 1280 before
- Multiple searches now don't take place for a single connection
- Concurrency bugs fixed
- Fixed a number of bugs in the ICMPv6 neighbor solicitation in the TUN/TAP code
- A case where peers weren't always added correctly if one or more peers were unreachable has been fixed
- Searches which include the local node are now handled correctly
- Lots of small bug tweaks and clean-ups throughout the codebase
## [0.3.5] - 2019-03-13
### Fixed
- The `AllowedEncryptionPublicKeys` option has now been fixed to handle incoming connections properly and no longer blocks outgoing connections (this was broken in v0.3.4)
- Multicast TCP listeners will now be stopped correctly when the link-local address on the interface changes or disappears altogether
## [0.3.4] - 2019-03-12
### Added
- Support for multiple listeners (although currently only TCP listeners are supported)
- New multicast behaviour where each multicast interface is given it's own link-local listener and does not depend on the `Listen` configuration
- Blocking detection in the switch to avoid parenting a blocked peer
- Support for adding and removing listeners and multicast interfaces when reloading configuration during runtime
- Yggdrasil will now attempt to clean up UNIX admin sockets on startup if left behind by a previous crash
- Admin socket `getTunnelRouting` and `setTunnelRouting` calls for enabling and disabling crypto-key routing during runtime
- On macOS, Yggdrasil will now try to wake up AWDL on start-up when `awdl0` is a configured multicast interface, to keep it awake after system sleep, and to stop waking it when no longer needed
- Added `LinkLocalTCPPort` option for controlling the port number that link-local TCP listeners will listen on by default when setting up `MulticastInterfaces` (a node restart is currently required for changes to `LinkLocalTCPPort` to take effect - it cannot be updated by reloading config during runtime)
### Changed
- The `Listen` configuration statement is now an array instead of a string
- The `Listen` configuration statement should now conform to the same formatting as peers with the protocol prefix, e.g. `tcp://[::]:0`
- Session workers are now non-blocking
- Multicast interval is now fixed at every 15 seconds and network interfaces are reevaluated for eligibility on each interval (where before the interval depended upon the number of configured multicast interfaces and evaluation only took place at startup)
- Dead connections are now closed in the link handler as opposed to the switch
- Peer forwarding is now prioritised instead of randomised
### Fixed
- Admin socket `getTunTap` call now returns properly instead of claiming no interface is enabled in all cases
- Handling of `getRoutes` etc in `yggdrasilctl` is now working
- Local interface names are no longer leaked in multicast packets
- Link-local TCP connections, particularly those initiated because of multicast beacons, are now always correctly scoped for the target interface
- Yggdrasil now correctly responds to multicast interfaces going up and down during runtime
## [0.3.3] - 2019-02-18
### Added
- Dynamic reconfiguration, which allows reloading the configuration file to make changes during runtime by sending a `SIGHUP` signal (note: this only works with `-useconffile` and not `-useconf` and currently reconfiguring TUN/TAP is not supported)
- Support for building Yggdrasil as an iOS or Android framework if the appropriate tools (e.g. `gomobile`/`gobind` + SDKs) are available
- Connection contexts used for TCP connections which allow more exotic socket options to be set, e.g.
- Reusing the multicast socket to allow multiple running Yggdrasil instances without having to disable multicast
- Allowing supported Macs to peer with other nearby Macs that aren't even on the same Wi-Fi network using AWDL
- Flexible logging support, which allows for logging at different levels of verbosity
### Changed
- Switch changes to improve parent selection
- Node configuration is now stored centrally, rather than having fragments/copies distributed at startup time
- Significant refactoring in various areas, including for link types (TCP, AWDL etc), generic streams and adapters
- macOS builds through CircleCI are now 64-bit only
### Fixed
- Simplified `systemd` service now in `contrib`
### Removed
- `ReadTimeout` option is now deprecated
## [0.3.2] - 2018-12-26
### Added
- The admin socket is now multithreaded, greatly improving performance of the crawler and allowing concurrent lookups to take place
- The ability to hide NodeInfo defaults through either setting the `NodeInfoPrivacy` option or through setting individual `NodeInfo` attributes to `null`
### Changed
- The `armhf` build now targets ARMv6 instead of ARMv7, adding support for Raspberry Pi Zero and other older models, amongst others
### Fixed
- DHT entries are now populated using a copy in memory to fix various potential DHT bugs
- DHT traffic should now throttle back exponentially to reduce idle traffic
- Adjust how nodes are inserted into the DHT which should help to reduce some incorrect DHT traffic
- In TAP mode, the NDP target address is now correctly used when populating the peer MAC table. This fixes serious connectivity problems when in TAP mode, particularly on BSD
- In TUN mode, ICMPv6 packets are now ignored whereas they were incorrectly processed before
## [0.3.1] - 2018-12-17
### Added
- Build name and version is now imprinted onto the binaries if available/specified during build

201
README.md
View File

@@ -3,149 +3,122 @@
[![CircleCI](https://circleci.com/gh/yggdrasil-network/yggdrasil-go.svg?style=shield&circle-token=:circle-token
)](https://circleci.com/gh/yggdrasil-network/yggdrasil-go)
## What is it?
## Introduction
This is a toy implementation of an encrypted IPv6 network, with many good ideas stolen from [cjdns](https://github.com/cjdelisle/cjdns), which was written to test a particular routing scheme that was cobbled together one random afternoon.
It's notably not a shortest path routing scheme, with the goal of scalable name-independent routing on dynamic networks with an internet-like topology.
It's named Yggdrasil after the world tree from Norse mythology, because that seemed like the obvious name given how it works.
More information is available at <https://yggdrasil-network.github.io/>.
Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6
network. It is lightweight, self-arranging, supported on multiple platforms and
allows pretty much any IPv6-capable application to communicate securely with
other Yggdrasil nodes. Yggdrasil does not require you to have IPv6 Internet
connectivity - it also works over IPv4.
This is a toy / proof-of-principle, and considered alpha quality by the developers. It's not expected to be feature complete, and future updates may not be backwards compatible, though it should warn you if it sees a connection attempt with a node running a newer version.
You're encouraged to play with it, but it is strongly advised not to use it for anything mission critical.
Although Yggdrasil shares many similarities with
[cjdns](https://github.com/cjdelisle/cjdns), it employs a different routing
algorithm based on a globally-agreed spanning tree and greedy routing in a
metric space, and aims to implement some novel local backpressure routing
techniques. In theory, Yggdrasil should scale well on networks with
internet-like topologies.
## Supported Platforms
We actively support the following platforms, and packages are available for
some of the below:
- Linux
- `.deb` and `.rpm` packages are built by CI for Debian and Red Hat-based
distributions
- Void and Arch packages also available within their respective repositories
- macOS
- `.pkg` packages are built by CI
- Ubiquiti EdgeOS
- `.deb` Vyatta packages are built by CI
- Windows
- FreeBSD
- OpenBSD
- NetBSD
- OpenWrt
Please see our [Platforms](https://yggdrasil-network.github.io/) pages for more
specific information about each of our supported platforms, including
installation steps and caveats.
You may also find other platform-specific wrappers, scripts or tools in the
`contrib` folder.
## Building
1. Install Go (requires 1.11 or later, [godeb](https://github.com/niemeyer/godeb) is recommended for Debian-based Linux distributions).
2. Clone this repository.
2. `./build`
If you want to build from source, as opposed to installing one of the pre-built
packages:
Note that you can cross-compile for other platforms and architectures by specifying the `$GOOS` and `$GOARCH` environment variables, for example, `GOOS=windows ./build` or `GOOS=linux GOARCH=mipsle ./build`.
1. Install [Go](https://golang.org) (requires Go 1.12 or later)
2. Clone this repository
2. Run `./build`
The build script sets its own `$GOPATH`, so the build environment is self-contained.
Note that you can cross-compile for other platforms and architectures by
specifying the `GOOS` and `GOARCH` environment variables, e.g. `GOOS=windows
./build` or `GOOS=linux GOARCH=mipsle ./build`.
## Running
To run the program, you'll need permission to create a `tun` device and configure it using `ip`.
If you don't want to mess with capabilities for the `tun` device, then using `sudo` should work, with the usual security caveats about running a program as root.
### Generate configuration
To run with default settings:
To generate static configuration, either generate a HJSON file (human-friendly,
complete with comments):
1. `./yggdrasil --autoconf`
That will generate a new set of keys (and an IP address) each time the program is run.
The program will bind to all addresses on a random port and listen for incoming connections.
It will send announcements over IPv6 link-local multicast, and it will attempt to start a connection if it hears an announcement from another device.
In practice, you probably want to run this instead:
1. `./yggdrasil --genconf > conf.json`
2. `./yggdrasil --useconf < conf.json`
This keeps a persistent set of keys (and by extension, IP address) and gives you the option of editing the configuration file.
If you want to use it as an overlay network on top of e.g. the internet, then you can do so by adding the remote devices domain/address and port (as a string, e.g. `"1.2.3.4:5678"`) to the list of `Peers` in the configuration file.
By default, it peers over TCP (which can be forced with `"tcp://1.2.3.4:5678"` syntax), but it's also possible to connect over a socks proxy (`"socks://socksHost:socksPort/1.2.3.4:5678"`).
The socks proxy approach is useful for e.g. [peering over tor hidden services](https://github.com/yggdrasil-network/public-peers/blob/master/other/tor.md).
UDP support was removed as part of v0.2, and may be replaced by a better implementation at a later date.
### Platforms
#### Linux
- Should work out of the box on most Linux distributions with `iproute2` installed.
- systemd service scripts are included in the `contrib/systemd/` folder so that it runs automatically in the background (using `/etc/yggdrasil.conf` for configuration), copy the service files into `/etc/systemd/system`, copy `yggdrasil` into your `$PATH`, i.e. `/usr/bin`, and then enable the service:
```
systemctl enable yggdrasil
systemctl start yggdrasil
```
- Once installed as a systemd service, you can read the `yggdrasil` output:
```
systemctl status yggdrasil
journalctl -u yggdrasil
./yggdrasil -genconf > /path/to/yggdrasil.conf
```
#### macOS
... or generate a plain JSON file (which is easy to manipulate
programmatically):
- Tested and working out of the box on macOS 10.13 High Sierra.
- May work in theory on any macOS version with `utun` support (which was added in macOS 10.7 Lion), although this is untested at present.
- TAP mode is not supported on macOS.
#### FreeBSD, NetBSD
- Works in TAP mode, but currently doesn't work in TUN mode.
- You may need to create the TAP adapter first if it doesn't already exist, i.e. `ifconfig tap0 create`.
#### OpenBSD
- Works in TAP mode, but currently doesn't work in TUN mode.
- You may need to create the TAP adapter first if it doesn't already exist, i.e. `ifconfig tap0 create`.
- OpenBSD is not capable of listening on both IPv4 and IPv6 at the same time on the same socket (unlike FreeBSD and NetBSD). This affects the `Listen` and `AdminListen` configuration options. You will need to set `Listen` and `AdminListen` to use either an IPv4 or an IPv6 address.
- You may consider using [relayd](https://man.openbsd.org/relayd.8) to allow incoming Yggdrasil connections on both IPv4 and IPv6 simultaneously.
#### Windows
- Tested and working on Windows 7 and Windows 10, and should work on any recent versions of Windows, but it depends on the [OpenVPN TAP driver](https://openvpn.net/index.php/open-source/downloads.html) being installed first.
- Has been proven to work with both the [NDIS 5](https://swupdate.openvpn.org/community/releases/tap-windows-9.9.2_3.exe) (`tap-windows-9.9.2_3`) driver and the [NDIS 6](https://swupdate.openvpn.org/community/releases/tap-windows-9.21.2.exe) (`tap-windows-9.21.2`) driver, however there are substantial performance issues with the NDIS 6 driver therefore it is recommended to use the NDIS 5 driver instead.
- Be aware that connectivity issues can occur on Windows if multiple IPv6 addresses from the `200::/7` prefix are assigned to the TAP interface. If this happens, then you may need to manually remove the old/unused addresses from the interface (though the code has a workaround in place to do this automatically in some cases).
- TUN mode is not supported on Windows.
- Yggdrasil can be installed as a Windows service so that it runs automatically in the background. From an Administrator Command Prompt:
```
sc create yggdrasil binpath= "\"C:\path\to\yggdrasil.exe\" -useconffile \"C:\path\to\yggdrasil.conf\""
sc config yggdrasil displayname= "Yggdrasil Service"
sc config yggdrasil start= "auto"
sc start yggdrasil
```
- Alternatively, if you want the service to autoconfigure instead of using an `yggdrasil.conf`, replace the `sc create` line from above with:
```
sc create yggdrasil binpath= "\"C:\path\to\yggdrasil.exe\" -autoconf"
./yggdrasil -genconf -json > /path/to/yggdrasil.conf
```
#### EdgeRouter
You will need to edit the `yggdrasil.conf` file to add or remove peers, modify
other configuration such as listen addresses or multicast addresses, etc.
- Tested and working on the EdgeRouter X, using the [vyatta-yggdrasil](https://github.com/neilalexander/vyatta-yggdrasil) wrapper package.
### Run Yggdrasil
## Optional: advertise a prefix locally
Suppose a node has generated the address: `200:1111:2222:3333:4444:5555:6666:7777`
Then the node may also use addresses from the prefix: `300:1111:2222:3333::/64` (note the `200` changed to `300`, a separate `/8` is used for prefixes, but the rest of the first 64 bits are the same).
To advertise this prefix and a route to `200::/7`, the following seems to work on the developers' networks:
1. Enable IPv6 forwarding (e.g. `sysctl -w net.ipv6.conf.all.forwarding=1` or add it to sysctl.conf).
2. `ip addr add 300:1111:2222:3333::1/64 dev eth0` or similar, to assign an address for the router to use in that prefix, where the LAN is reachable through `eth0`.
3. Install/run `radvd` with something like the following in `/etc/radvd.conf`:
To run with the generated static configuration:
```
interface eth0
{
AdvSendAdvert on;
prefix 300:1111:2222:3333::/64 {
AdvOnLink on;
AdvAutonomous on;
};
route 200::/7 {};
};
./yggdrasil -useconffile /path/to/yggdrasil.conf
```
This is enough to give unsupported devices on the LAN access to the yggdrasil network. See the [configuration](https://yggdrasil-network.github.io/configuration.html) page for more info.
To run in auto-configuration mode (which will use sane defaults and random keys
at each startup, instead of using a static configuration file):
## How does it work?
```
./yggdrasil -autoconf
```
I'd rather not try to explain in the readme, but it is described further on the [about](https://yggdrasil-network.github.io/about.html) page, so you can check there if you're interested.
Be warned that it's still not a very good explanation, but it at least gives a high-level overview and links to some relevant work by other people.
You will likely need to run Yggdrasil as a privileged user or under `sudo`,
unless you have permission to create TUN/TAP adapters. On Linux this can be done
by giving the Yggdrasil binary the `CAP_NET_ADMIN` capability.
## Obligatory performance propaganda
## Documentation
A [simplified model](misc/sim/treesim-forward.py) of this routing scheme has been tested in simulation on the 9204-node [skitter](https://www.caida.org/tools/measurement/skitter/) network topology dataset from [caida](https://www.caida.org/), and compared with results in [arxiv:0708.2309](https://arxiv.org/abs/0708.2309).
Using the routing scheme as implemented in this code, the average multiplicative stretch is observed to be about 1.08, with an average routing table size of 6 for a name-dependent scheme, and approximately 30 additional (but smaller) entries needed for the name-independent routing table.
The number of name-dependent routing table entries needed is proportional to node degree, so that 6 is the mean of a distribution with a long tail, but this may be an acceptable tradeoff(it's at least worth trying, hence this code).
The size of name-dependent routing table entries is relatively large, due to cryptographic signatures associated with routing table updates, but in the absence of cryptographic overhead, each entry should otherwise be comparable in size to the BC routing scheme described in the above paper.
A modified version of this scheme, with the same resource requirements, achieves a multiplicative stretch of 1.02, which drops to 1.01 if source routing is used.
Both of these optimizations are not present in the current implementation, as the former depends on network state information that appears difficult to cryptographically secure, and the latter optimization is both tedious to implement and would make debugging other aspects of the implementation more difficult.
Documentation is available on our [GitHub
Pages](https://yggdrasil-network.github.io) site, or in the base submodule
repository within `doc/yggdrasil-network.github.io`.
- [Configuration file options](https://yggdrasil-network.github.io/configuration.html)
- [Platform-specific documentation](https://yggdrasil-network.github.io/platforms.html)
- [Frequently asked questions](https://yggdrasil-network.github.io/faq.html)
- [Admin API documentation](https://yggdrasil-network.github.io/admin.html)
- [Version changelog](CHANGELOG.md)
## Community
Feel free to join us on our [Matrix
channel](https://matrix.to/#/#yggdrasil:matrix.org) at `#yggdrasil:matrix.org`
or in the `#yggdrasil` IRC channel on Freenode.
## License
This code is released under the terms of the LGPLv3, but with an added exception that was shamelessly taken from [godeb](https://github.com/niemeyer/godeb).
Under certain circumstances, this exception permits distribution of binaries that are (statically or dynamically) linked with this code, without requiring the distribution of Minimal Corresponding Source or Minimal Application Code.
This code is released under the terms of the LGPLv3, but with an added exception
that was shamelessly taken from [godeb](https://github.com/niemeyer/godeb).
Under certain circumstances, this exception permits distribution of binaries
that are (statically or dynamically) linked with this code, without requiring
the distribution of Minimal Corresponding Source or Minimal Application Code.
For more details, see: [LICENSE](LICENSE).

51
build
View File

@@ -1,36 +1,53 @@
#!/bin/sh
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil}
set -ef
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version}
PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
ARGS="-v"
while getopts "udtc:l:" option
while getopts "uaitc:l:dro:" option
do
case "${option}"
case "$option"
in
u) UPX=true;;
d) DEBUG=true;;
i) IOS=true;;
a) ANDROID=true;;
t) TABLES=true;;
c) GCFLAGS="$GCFLAGS $OPTARG";;
l) LDFLAGS="$LDFLAGS $OPTARG";;
d) ARGS="$ARGS -tags debug" DEBUG=true;;
r) ARGS="$ARGS -race";;
o) ARGS="$ARGS -o $OPTARG";;
esac
done
if [ -z $TABLES ]; then
STRIP="-s -w"
if [ -z $TABLES ] && [ -z $DEBUG ]; then
LDFLAGS="$LDFLAGS -s -w"
fi
for CMD in `ls cmd/` ; do
echo "Building: $CMD"
if [ $IOS ]; then
echo "Building framework for iOS"
gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \
github.com/yggdrasil-network/yggdrasil-go/src/config \
github.com/yggdrasil-network/yggdrasil-extras/src/mobile
elif [ $ANDROID ]; then
echo "Building aar for Android"
gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \
github.com/yggdrasil-network/yggdrasil-go/src/config \
github.com/yggdrasil-network/yggdrasil-extras/src/mobile
else
for CMD in `ls cmd/` ; do
echo "Building: $CMD"
go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD
if [ $DEBUG ]; then
go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD
else
go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD
fi
if [ $UPX ]; then
upx --brute $CMD
fi
done
if [ $UPX ]; then
upx --brute $CMD
fi
done
fi

2
clean
View File

@@ -1,2 +1,2 @@
#!/bin/bash
#!/bin/sh
git clean -dxf

View File

@@ -7,78 +7,115 @@ import (
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"os/signal"
"regexp"
"strings"
"syscall"
"time"
"golang.org/x/text/encoding/unicode"
"github.com/gologme/log"
gsyslog "github.com/hashicorp/go-syslog"
"github.com/hjson/hjson-go"
"github.com/kardianos/minwinsvc"
"github.com/mitchellh/mapstructure"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
type nodeConfig = config.NodeConfig
type Core = yggdrasil.Core
type node struct {
core Core
core yggdrasil.Core
state *config.NodeState
tuntap tuntap.TunAdapter
multicast multicast.Multicast
admin admin.AdminSocket
}
// Generates default configuration. This is used when outputting the -genconf
// parameter and also when using -autoconf. The isAutoconf flag is used to
// determine whether the operating system should select a free port by itself
// (which guarantees that there will not be a conflict with any other services)
// or whether to generate a random port number. The only side effect of setting
// isAutoconf is that the TCP and UDP ports will likely end up with different
// port numbers.
func generateConfig(isAutoconf bool) *nodeConfig {
// Create a new core.
core := Core{}
// Generate encryption keys.
bpub, bpriv := core.NewEncryptionKeys()
spub, spriv := core.NewSigningKeys()
// Create a node configuration and populate it.
cfg := nodeConfig{}
if isAutoconf {
cfg.Listen = "[::]:0"
func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig {
// Use a configuration file. If -useconf, the configuration will be read
// from stdin. If -useconffile, the configuration will be read from the
// filesystem.
var conf []byte
var err error
if *useconffile != "" {
// Read the file from the filesystem
conf, err = ioutil.ReadFile(*useconffile)
} else {
r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768)
// Read the file from stdin.
conf, err = ioutil.ReadAll(os.Stdin)
}
cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen
cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
cfg.SigningPublicKey = hex.EncodeToString(spub[:])
cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedEncryptionPublicKeys = []string{}
cfg.MulticastInterfaces = []string{".*"}
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
cfg.SessionFirewall.Enable = false
cfg.SessionFirewall.AllowFromDirect = true
cfg.SessionFirewall.AllowFromRemote = true
cfg.SwitchOptions.MaxTotalQueueSize = yggdrasil.SwitchQueueTotalMinSize
return &cfg
if err != nil {
panic(err)
}
// If there's a byte order mark - which Windows 10 is now incredibly fond of
// throwing everywhere when it's converting things into UTF-16 for the hell
// of it - remove it and decode back down into UTF-8. This is necessary
// because hjson doesn't know what to do with UTF-16 and will panic
if bytes.Compare(conf[0:2], []byte{0xFF, 0xFE}) == 0 ||
bytes.Compare(conf[0:2], []byte{0xFE, 0xFF}) == 0 {
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
decoder := utf.NewDecoder()
conf, err = decoder.Bytes(conf)
if err != nil {
panic(err)
}
}
// Generate a new configuration - this gives us a set of sane defaults -
// then parse the configuration we loaded above on top of it. The effect
// of this is that any configuration item that is missing from the provided
// configuration will use a sane default.
cfg := config.GenerateConfig()
var dat map[string]interface{}
if err := hjson.Unmarshal(conf, &dat); err != nil {
panic(err)
}
// Check for fields that have changed type recently, e.g. the Listen config
// option is now a []string rather than a string
if listen, ok := dat["Listen"].(string); ok {
dat["Listen"] = []string{listen}
}
if tunnelrouting, ok := dat["TunnelRouting"].(map[string]interface{}); ok {
if c, ok := tunnelrouting["IPv4Sources"]; ok {
delete(tunnelrouting, "IPv4Sources")
tunnelrouting["IPv4LocalSubnets"] = c
}
if c, ok := tunnelrouting["IPv6Sources"]; ok {
delete(tunnelrouting, "IPv6Sources")
tunnelrouting["IPv6LocalSubnets"] = c
}
if c, ok := tunnelrouting["IPv4Destinations"]; ok {
delete(tunnelrouting, "IPv4Destinations")
tunnelrouting["IPv4RemoteSubnets"] = c
}
if c, ok := tunnelrouting["IPv6Destinations"]; ok {
delete(tunnelrouting, "IPv6Destinations")
tunnelrouting["IPv6RemoteSubnets"] = c
}
}
// Sanitise the config
confJson, err := json.Marshal(dat)
if err != nil {
panic(err)
}
json.Unmarshal(confJson, &cfg)
// Overlay our newly mapped configuration onto the autoconf node config that
// we generated above.
if err = mapstructure.Decode(dat, &cfg); err != nil {
panic(err)
}
return cfg
}
// Generates a new configuration and returns it in HJSON format. This is used
// with -genconf.
func doGenconf(isjson bool) string {
cfg := generateConfig(false)
cfg := config.GenerateConfig()
var bs []byte
var err error
if isjson {
@@ -101,128 +138,25 @@ func main() {
normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
version := flag.Bool("version", false, "prints the version of this build")
ver := flag.Bool("version", false, "prints the version of this build")
logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable")
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
flag.Parse()
var cfg *nodeConfig
var cfg *config.NodeConfig
var err error
switch {
case *version:
fmt.Println("Build name:", yggdrasil.GetBuildName())
fmt.Println("Build version:", yggdrasil.GetBuildVersion())
os.Exit(0)
case *ver:
fmt.Println("Build name:", version.BuildName())
fmt.Println("Build version:", version.BuildVersion())
return
case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
// port numbers, and will use an automatically selected TUN/TAP interface.
cfg = generateConfig(true)
cfg = config.GenerateConfig()
case *useconffile != "" || *useconf:
// Use a configuration file. If -useconf, the configuration will be read
// from stdin. If -useconffile, the configuration will be read from the
// filesystem.
var config []byte
var err error
if *useconffile != "" {
// Read the file from the filesystem
config, err = ioutil.ReadFile(*useconffile)
} else {
// Read the file from stdin.
config, err = ioutil.ReadAll(os.Stdin)
}
if err != nil {
panic(err)
}
// If there's a byte order mark - which Windows 10 is now incredibly fond of
// throwing everywhere when it's converting things into UTF-16 for the hell
// of it - remove it and decode back down into UTF-8. This is necessary
// because hjson doesn't know what to do with UTF-16 and will panic
if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 ||
bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 {
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
decoder := utf.NewDecoder()
config, err = decoder.Bytes(config)
if err != nil {
panic(err)
}
}
// Generate a new configuration - this gives us a set of sane defaults -
// then parse the configuration we loaded above on top of it. The effect
// of this is that any configuration item that is missing from the provided
// configuration will use a sane default.
cfg = generateConfig(false)
var dat map[string]interface{}
if err := hjson.Unmarshal(config, &dat); err != nil {
panic(err)
}
confJson, err := json.Marshal(dat)
if err != nil {
panic(err)
}
json.Unmarshal(confJson, &cfg)
// For now we will do a little bit to help the user adjust their
// configuration to match the new configuration format, as some of the key
// names have changed recently.
changes := map[string]string{
"Multicast": "",
"LinkLocal": "MulticastInterfaces",
"BoxPub": "EncryptionPublicKey",
"BoxPriv": "EncryptionPrivateKey",
"SigPub": "SigningPublicKey",
"SigPriv": "SigningPrivateKey",
"AllowedBoxPubs": "AllowedEncryptionPublicKeys",
}
// Loop over the mappings aove and see if we have anything to fix.
for from, to := range changes {
if _, ok := dat[from]; ok {
if to == "" {
if !*normaliseconf {
log.Println("Warning: Deprecated config option", from, "- please remove")
}
} else {
if !*normaliseconf {
log.Println("Warning: Deprecated config option", from, "- please rename to", to)
}
// If the configuration file doesn't already contain a line with the
// new name then set it to the old value. This makes sure that we
// don't overwrite something that was put there intentionally.
if _, ok := dat[to]; !ok {
dat[to] = dat[from]
}
}
}
}
// Check to see if the peers are in a parsable format, if not then default
// them to the TCP scheme
if peers, ok := dat["Peers"].([]interface{}); ok {
for index, peer := range peers {
uri := peer.(string)
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
continue
}
if strings.HasPrefix(uri, "tcp:") {
uri = uri[4:]
}
(dat["Peers"].([]interface{}))[index] = "tcp://" + uri
}
}
// Now do the same with the interface peers
if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok {
for intf, peers := range interfacepeers {
for index, peer := range peers.([]interface{}) {
uri := peer.(string)
if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
continue
}
if strings.HasPrefix(uri, "tcp:") {
uri = uri[4:]
}
((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri
}
}
}
// Overlay our newly mapped configuration onto the autoconf node config that
// we generated above.
if err = mapstructure.Decode(dat, &cfg); err != nil {
panic(err)
}
// Read the configuration from either stdin or from the filesystem
cfg = readConfig(useconf, useconffile, normaliseconf)
// If the -normaliseconf option was specified then remarshal the above
// configuration and print it back to stdout. This lets the user update
// their configuration file with newly mapped names (like above) or to
@@ -254,72 +188,175 @@ func main() {
return
}
// Create a new logger that logs output to stdout.
logger := log.New(os.Stdout, "", log.Flags())
var logger *log.Logger
switch *logto {
case "stdout":
logger = log.New(os.Stdout, "", log.Flags())
case "syslog":
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
logger = log.New(syslogger, "", log.Flags())
}
default:
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
logger = log.New(logfd, "", log.Flags())
}
}
if logger == nil {
logger = log.New(os.Stdout, "", log.Flags())
logger.Warnln("Logging defaulting to stdout")
}
//logger.EnableLevel("error")
//logger.EnableLevel("warn")
//logger.EnableLevel("info")
if levels := strings.Split(*logging, ","); len(levels) > 0 {
for _, level := range levels {
l := strings.TrimSpace(level)
switch l {
case "error", "warn", "info", "trace", "debug":
logger.EnableLevel(l)
default:
continue
}
}
}
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
// don't need to create this manually.
n := node{}
// Check to see if any multicast interface expressions were provided in the
// config. If they were then set them now.
for _, ll := range cfg.MulticastInterfaces {
ifceExpr, err := regexp.Compile(ll)
if err != nil {
panic(err)
}
n.core.AddMulticastInterfaceExpr(ifceExpr)
}
// Now that we have a working configuration, we can now actually start
// Yggdrasil. This will start the router, switch, DHT node, TCP and UDP
// sockets, TUN/TAP adapter and multicast discovery port.
if err := n.core.Start(cfg, logger); err != nil {
logger.Println("An error occurred during startup")
// Now start Yggdrasil - this starts the DHT, router, switch and other core
// components needed for Yggdrasil to operate
n.state, err = n.core.Start(cfg, logger)
if err != nil {
logger.Errorln("An error occurred during startup")
panic(err)
}
// Check to see if any allowed encryption keys were provided in the config.
// If they were then set them now.
for _, pBoxStr := range cfg.AllowedEncryptionPublicKeys {
n.core.AddAllowedEncryptionPublicKey(pBoxStr)
// Register the session firewall gatekeeper function
n.core.SetSessionGatekeeper(n.sessionFirewall)
// Start the admin socket
n.admin.Init(&n.core, n.state, logger, nil)
if err := n.admin.Start(); err != nil {
logger.Errorln("An error occurred starting admin socket:", err)
}
// If any static peers were provided in the configuration above then we should
// configure them. The loop ensures that disconnected peers will eventually
// be reconnected with.
go func() {
if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 {
return
}
for {
for _, peer := range cfg.Peers {
n.core.AddPeer(peer, "")
time.Sleep(time.Second)
// Start the multicast interface
n.multicast.Init(&n.core, n.state, logger, nil)
if err := n.multicast.Start(); err != nil {
logger.Errorln("An error occurred starting multicast:", err)
}
n.multicast.SetupAdminHandlers(&n.admin)
// Start the TUN/TAP interface
if listener, err := n.core.ConnListen(); err == nil {
if dialer, err := n.core.ConnDialer(); err == nil {
n.tuntap.Init(n.state, logger, listener, dialer)
if err := n.tuntap.Start(); err != nil {
logger.Errorln("An error occurred starting TUN/TAP:", err)
}
for intf, intfpeers := range cfg.InterfacePeers {
for _, peer := range intfpeers {
n.core.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
time.Sleep(time.Minute)
n.tuntap.SetupAdminHandlers(&n.admin)
} else {
logger.Errorln("Unable to get Dialer:", err)
}
}()
// The Stop function ensures that the TUN/TAP adapter is correctly shut down
// before the program exits.
defer func() {
n.core.Stop()
}()
} else {
logger.Errorln("Unable to get Listener:", err)
}
// Make some nice output that tells us what our IPv6 address and subnet are.
// This is just logged to stdout for the user.
address := n.core.GetAddress()
subnet := n.core.GetSubnet()
logger.Printf("Your IPv6 address is %s", address.String())
logger.Printf("Your IPv6 subnet is %s", subnet.String())
address := n.core.Address()
subnet := n.core.Subnet()
logger.Infof("Your IPv6 address is %s", address.String())
logger.Infof("Your IPv6 subnet is %s", subnet.String())
// Catch interrupts from the operating system to exit gracefully.
c := make(chan os.Signal, 1)
r := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
// Create a function to capture the service being stopped on Windows.
winTerminate := func() {
c <- os.Interrupt
}
minwinsvc.SetOnExit(winTerminate)
signal.Notify(r, os.Interrupt, syscall.SIGHUP)
// Capture the service being stopped on Windows.
minwinsvc.SetOnExit(n.shutdown)
defer n.shutdown()
// Wait for the terminate/interrupt signal. Once a signal is received, the
// deferred Stop function above will run which will shut down TUN/TAP.
<-c
for {
select {
case _ = <-c:
goto exit
case _ = <-r:
if *useconffile != "" {
cfg = readConfig(useconf, useconffile, normaliseconf)
n.core.UpdateConfig(cfg)
n.tuntap.UpdateConfig(cfg)
n.multicast.UpdateConfig(cfg)
} else {
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
}
}
}
exit:
}
func (n *node) shutdown() {
n.core.Stop()
n.admin.Stop()
n.multicast.Stop()
n.tuntap.Stop()
os.Exit(0)
}
func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool {
n.state.Mutex.RLock()
defer n.state.Mutex.RUnlock()
// Allow by default if the session firewall is disabled
if !n.state.Current.SessionFirewall.Enable {
return true
}
// Prepare for checking whitelist/blacklist
var box crypto.BoxPubKey
// Reject blacklisted nodes
for _, b := range n.state.Current.SessionFirewall.BlacklistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return false
}
}
}
// Allow whitelisted nodes
for _, b := range n.state.Current.SessionFirewall.WhitelistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return true
}
}
}
// Allow outbound sessions if appropriate
if n.state.Current.SessionFirewall.AlwaysAllowOutbound {
if initiator {
return true
}
}
// Look and see if the pubkey is that of a direct peer
var isDirectPeer bool
for _, peer := range n.core.GetPeers() {
if peer.PublicKey == *pubkey {
isDirectPeer = true
break
}
}
// Allow direct peers if appropriate
if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer {
return true
}
// Allow remote nodes if appropriate
if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer {
return true
}
// Finally, default-deny if not matching any of the above rules
return false
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/hjson/hjson-go"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
)
type admin_info map[string]interface{}
@@ -53,9 +54,17 @@ func main() {
server := flag.String("endpoint", endpoint, "Admin socket endpoint")
injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)")
verbose := flag.Bool("v", false, "Verbose output (includes public keys)")
ver := flag.Bool("version", false, "Prints the version of this build")
flag.Parse()
args := flag.Args()
if *ver {
fmt.Println("Build name:", version.BuildName())
fmt.Println("Build version:", version.BuildVersion())
fmt.Println("To get the version number of the running Yggdrasil node, run", os.Args[0], "getSelf")
return
}
if len(args) == 0 {
flag.Usage()
return
@@ -200,7 +209,7 @@ func main() {
if !keysOrdered {
for k := range slv.(map[string]interface{}) {
if !*verbose {
if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" {
if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" {
continue
}
}
@@ -277,6 +286,9 @@ func main() {
fmt.Println("Coords:", coords)
}
if *verbose {
if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok {
fmt.Println("Node ID:", nodeID)
}
if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok {
fmt.Println("Public encryption key:", boxPubKey)
}
@@ -388,16 +400,30 @@ func main() {
}
}
case "getroutes":
if _, ok := res["routes"]; !ok {
fmt.Println("No routes found")
} else if res["routes"] == nil {
if routes, ok := res["routes"].(map[string]interface{}); !ok {
fmt.Println("No routes found")
} else {
fmt.Println("Routes:")
for _, v := range res["routes"].([]interface{}) {
fmt.Println("-", v)
if res["routes"] == nil || len(routes) == 0 {
fmt.Println("No routes found")
} else {
fmt.Println("Routes:")
for k, v := range routes {
if pv, ok := v.(string); ok {
fmt.Println("-", k, " via ", pv)
}
}
}
}
case "settunnelrouting":
fallthrough
case "gettunnelrouting":
if enabled, ok := res["enabled"].(bool); !ok {
fmt.Println("Tunnel routing is disabled")
} else if !enabled {
fmt.Println("Tunnel routing is disabled")
} else {
fmt.Println("Tunnel routing is enabled")
}
default:
if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil {
fmt.Println(string(json))

125
contrib/ansible/genkeys.go Normal file
View File

@@ -0,0 +1,125 @@
/*
This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/)
*/
package main
import (
"encoding/hex"
"flag"
"fmt"
"net"
"os"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
var numHosts = flag.Int("hosts", 1, "number of host vars to generate")
var keyTries = flag.Int("tries", 1000, "number of tries before taking the best keys")
type keySet struct {
priv []byte
pub []byte
id []byte
ip string
}
func main() {
flag.Parse()
if *numHosts > *keyTries {
println("Can't generate less keys than hosts.")
return
}
var encryptionKeys []keySet
for i := 0; i < *numHosts+1; i++ {
encryptionKeys = append(encryptionKeys, newBoxKey())
}
encryptionKeys = sortKeySetArray(encryptionKeys)
for i := 0; i < *keyTries-*numHosts-1; i++ {
encryptionKeys[0] = newBoxKey()
encryptionKeys = bubbleUpTo(encryptionKeys, 0)
}
var signatureKeys []keySet
for i := 0; i < *numHosts+1; i++ {
signatureKeys = append(signatureKeys, newSigKey())
}
signatureKeys = sortKeySetArray(signatureKeys)
for i := 0; i < *keyTries-*numHosts-1; i++ {
signatureKeys[0] = newSigKey()
signatureKeys = bubbleUpTo(signatureKeys, 0)
}
os.MkdirAll("host_vars", 0755)
for i := 1; i <= *numHosts; i++ {
os.MkdirAll(fmt.Sprintf("host_vars/%x", i), 0755)
file, err := os.Create(fmt.Sprintf("host_vars/%x/vars", i))
if err != nil {
return
}
defer file.Close()
file.WriteString(fmt.Sprintf("yggdrasil_encryption_public_key: %v\n", hex.EncodeToString(encryptionKeys[i].pub)))
file.WriteString("yggdrasil_encryption_private_key: \"{{ vault_yggdrasil_encryption_private_key }}\"\n")
file.WriteString(fmt.Sprintf("yggdrasil_signing_public_key: %v\n", hex.EncodeToString(signatureKeys[i].pub)))
file.WriteString("yggdrasil_signing_private_key: \"{{ vault_yggdrasil_signing_private_key }}\"\n")
file.WriteString(fmt.Sprintf("ansible_host: %v\n", encryptionKeys[i].ip))
file, err = os.Create(fmt.Sprintf("host_vars/%x/vault", i))
if err != nil {
return
}
defer file.Close()
file.WriteString(fmt.Sprintf("vault_yggdrasil_encryption_private_key: %v\n", hex.EncodeToString(encryptionKeys[i].priv)))
file.WriteString(fmt.Sprintf("vault_yggdrasil_signing_private_key: %v\n", hex.EncodeToString(signatureKeys[i].priv)))
}
}
func newBoxKey() keySet {
pub, priv := crypto.NewBoxKeys()
id := crypto.GetNodeID(pub)
ip := net.IP(address.AddrForNodeID(id)[:]).String()
return keySet{priv[:], pub[:], id[:], ip}
}
func newSigKey() keySet {
pub, priv := crypto.NewSigKeys()
id := crypto.GetTreeID(pub)
return keySet{priv[:], pub[:], id[:], ""}
}
func isBetter(oldID, newID []byte) bool {
for idx := range oldID {
if newID[idx] > oldID[idx] {
return true
}
if newID[idx] < oldID[idx] {
return false
}
}
return false
}
func sortKeySetArray(sets []keySet) []keySet {
for i := 0; i < len(sets); i++ {
sets = bubbleUpTo(sets, i)
}
return sets
}
func bubbleUpTo(sets []keySet, num int) []keySet {
for i := 0; i < len(sets)-num-1; i++ {
if isBetter(sets[i+1].id, sets[i].id) {
var tmp = sets[i]
sets[i] = sets[i+1]
sets[i+1] = tmp
} else {
break
}
}
return sets
}

View File

@@ -0,0 +1,23 @@
# Last Modified: Sat Mar 9 06:08:02 2019
#include <tunables/global>
/usr/bin/yggdrasil {
#include <abstractions/base>
capability net_admin,
network inet stream,
network inet dgram,
network inet6 dgram,
network inet6 stream,
network netlink raw,
/lib/@{multiarch}/ld-*.so mr,
/proc/sys/net/core/somaxconn r,
/dev/net/tun rw,
/usr/bin/yggdrasil mr,
/etc/yggdrasil.conf rw,
/run/yggdrasil.sock rw,
}

View File

@@ -0,0 +1,77 @@
#!/bin/sh
CONFFILE="/etc/yggdrasil.conf"
genconf() {
/usr/bin/yggdrasil -genconf > "$1"
return $?
}
probetun() {
modprobe tun
return $?
}
start() {
if [ ! -f "$CONFFILE" ]; then
printf 'Generating configuration file: '
if genconf "$CONFFILE"; then
echo "OK"
else
echo "FAIL"
return 1
fi
fi
if [ ! -e /dev/net/tun ]; then
printf 'Inserting TUN module: '
if probetun; then
echo "OK"
else
echo "FAIL"
return 1
fi
fi
printf 'Starting yggdrasil: '
if start-stop-daemon -S -q -b -x /usr/bin/yggdrasil \
-- -useconffile "$CONFFILE"; then
echo "OK"
else
echo "FAIL"
fi
}
stop() {
printf "Stopping yggdrasil: "
if start-stop-daemon -K -q -x /usr/bin/yggdrasil; then
echo "OK"
else
echo "FAIL"
fi
}
reload() {
printf "Reloading yggdrasil: "
if start-stop-daemon -K -q -s HUP -x /usr/bin/yggdrasil; then
echo "OK"
else
echo "FAIL"
start
fi
}
restart() {
stop
start
}
case "$1" in
start|stop|restart|reload)
"$1";;
*)
echo "Usage: $0 {start|stop|restart|reload}"
exit 1
esac
exit 0

View File

@@ -25,10 +25,11 @@ if [ $PKGARCH = "amd64" ]; then GOARCH=amd64 GOOS=linux ./build
elif [ $PKGARCH = "i386" ]; then GOARCH=386 GOOS=linux ./build
elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build
elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build
elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=7 ./build
elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build
elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build
elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build
else
echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64"
echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel"
exit 1
fi
@@ -110,12 +111,10 @@ EOF
cp yggdrasil /tmp/$PKGNAME/usr/bin/
cp yggdrasilctl /tmp/$PKGNAME/usr/bin/
cp contrib/systemd/yggdrasil.service /tmp/$PKGNAME/etc/systemd/system/
cp contrib/systemd/yggdrasil-resume.service /tmp/$PKGNAME/etc/systemd/system/
tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \
usr/bin/yggdrasil usr/bin/yggdrasilctl \
etc/systemd/system/yggdrasil.service \
etc/systemd/system/yggdrasil-resume.service
etc/systemd/system/yggdrasil.service
tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
echo 2.0 > /tmp/$PKGNAME/debian-binary

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="drawing.svg"
inkscape:version="0.91 r13725"
version="1.1"
id="svg4240"
viewBox="0 0 981.96461 321.60015"
height="90.762711mm"
width="277.13223mm">
<defs
id="defs4242" />
<sodipodi:namedview
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="0"
inkscape:window-height="1021"
inkscape:window-width="2048"
showgrid="false"
inkscape:current-layer="layer2"
inkscape:document-units="px"
inkscape:cy="13.914395"
inkscape:cx="751.6295"
inkscape:zoom="0.66468037"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base" />
<metadata
id="metadata4245">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(383.92494,-160.49328)" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
transform="translate(383.92494,-160.49328)">
<path
style="fill:#000000"
d="m 352.74397,478.24119 c 0.92103,-3.76903 11.87131,-30.48993 21.5083,-52.48465 9.86344,-22.51152 9.67726,-21.6278 6.92943,-32.89221 -3.42997,-14.06075 -3.22164,-36.95243 0.44688,-49.10642 13.24423,-43.87864 47.63362,-73.61698 122.30718,-105.76556 24.32504,-10.47245 37.67777,-17.18807 47.80968,-24.04538 17.86083,-12.08828 36.4402,-33.06424 42.38057,-47.84736 1.25285,-3.11781 2.66096,-5.64051 3.12912,-5.60598 1.46014,0.10767 0.73701,44.30167 -0.9768,59.69719 -10.61597,95.36545 -42.95689,157.39345 -96.20598,184.51751 -30.73114,15.65385 -79.17559,21.45357 -101.74118,12.18037 -3.19081,-1.31125 -6.5492,-2.38408 -7.46311,-2.38408 -3.43636,0 -15.75824,32.89925 -19.29523,51.51802 -1.09802,5.78003 -2.76237,13.70787 -3.00898,14.91667 -5.50064,-0.0422 -0.35371,-0.0119 -8.18026,-0.0119 l -8.29605,0 z"
id="path4918"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssssssscssssscscs" />
<g
style="font-style:normal;font-weight:normal;font-size:150px;line-height:86.00000143%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text4920"
transform="translate(-2,0)">
<path
d="m -345.34994,319.70594 -36.575,-55.6875 21.725,0 23.925,38.775 24.2,-38.775 20.625,0 -36.575,55.6875 0,41.6625 -17.325,0 0,-41.6625 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5638"
inkscape:connector-curvature="0" />
<path
d="m -202.55678,354.21844 q -18.0125,9.625 -40.2875,9.625 -11.275,0 -20.7625,-3.575 -9.35,-3.7125 -16.225,-10.3125 -6.7375,-6.7375 -10.5875,-16.0875 -3.85,-9.35 -3.85,-20.7625 0,-11.6875 3.85,-21.175 3.85,-9.625 10.5875,-16.3625 6.875,-6.7375 16.225,-10.3125 9.4875,-3.7125 20.7625,-3.7125 11.1375,0 20.9,2.75 9.7625,2.6125 17.4625,9.4875 l -12.7875,12.925 q -4.675,-4.5375 -11.4125,-7.0125 -6.6,-2.475 -14.025,-2.475 -7.5625,0 -13.75,2.75 -6.05,2.6125 -10.45,7.425 -4.4,4.675 -6.875,11 -2.3375,6.325 -2.3375,13.6125 0,7.8375 2.3375,14.4375 2.475,6.6 6.875,11.4125 4.4,4.8125 10.45,7.5625 6.1875,2.75 13.75,2.75 6.6,0 12.375,-1.2375 5.9125,-1.2375 10.45,-3.85 l 0,-22.9625 -19.9375,0 0,-15.675 37.2625,0 0,49.775 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5640"
inkscape:connector-curvature="0" />
<path
d="m -102.05346,354.21844 q -18.0125,9.625 -40.2875,9.625 -11.275,0 -20.7625,-3.575 -9.35,-3.7125 -16.225,-10.3125 -6.7375,-6.7375 -10.5875,-16.0875 -3.85,-9.35 -3.85,-20.7625 0,-11.6875 3.85,-21.175 3.85,-9.625 10.5875,-16.3625 6.875,-6.7375 16.225,-10.3125 9.4875,-3.7125 20.7625,-3.7125 11.1375,0 20.9,2.75 9.7625,2.6125 17.4625,9.4875 l -12.7875,12.925 q -4.675,-4.5375 -11.4125,-7.0125 -6.6,-2.475 -14.025,-2.475 -7.5625,0 -13.75,2.75 -6.05,2.6125 -10.45,7.425 -4.4,4.675 -6.875,11 -2.3375,6.325 -2.3375,13.6125 0,7.8375 2.3375,14.4375 2.475,6.6 6.875,11.4125 4.4,4.8125 10.45,7.5625 6.1875,2.75 13.75,2.75 6.6,0 12.375,-1.2375 5.9125,-1.2375 10.45,-3.85 l 0,-22.9625 -19.9375,0 0,-15.675 37.2625,0 0,49.775 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5642"
inkscape:connector-curvature="0" />
<path
d="m -90.037646,264.01844 38.3625,0 q 9.625,0 18.5625,3.025 8.9375,2.8875 15.8125,8.9375 6.875,6.05 11,15.2625 4.125,9.075 4.125,21.45 0,12.5125 -4.8125,21.725 -4.675,9.075 -12.2375,15.125 -7.425,5.9125 -16.6375,8.9375 -9.075,2.8875 -17.875,2.8875 l -36.3,0 0,-97.35 z m 30.25,81.675 q 8.1125,0 15.2625,-1.7875 7.2875,-1.925 12.65,-5.775 5.3625,-3.9875 8.3875,-10.175 3.1625,-6.325 3.1625,-15.2625 0,-8.8 -2.75,-15.125 -2.75,-6.325 -7.7,-10.175 -4.8125,-3.9875 -11.55,-5.775 -6.6,-1.925 -14.575,-1.925 l -15.8125,0 0,66 12.925,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5644"
inkscape:connector-curvature="0" />
<path
d="m 7.511578,264.01844 33.825,0 q 7.0125,0 13.475,1.375 6.6,1.2375 11.687502,4.4 5.0875,3.1625 8.1125,8.525 3.025,5.3625 3.025,13.6125 0,10.5875 -5.9125,17.7375 -5.775002,7.15 -16.637502,8.6625 l 25.850002,43.0375 -20.900002,0 -22.55,-41.25 -12.65,0 0,41.25 -17.325,0 0,-97.35 z m 30.8,41.25 q 3.7125,0 7.425,-0.275 3.7125,-0.4125 6.7375,-1.65 3.1625,-1.375 5.0875,-3.9875 1.925,-2.75 1.925,-7.5625 0,-4.2625 -1.7875,-6.875 -1.7875,-2.6125 -4.675,-3.85 -2.8875,-1.375 -6.4625,-1.7875 -3.4375,-0.4125 -6.7375,-0.4125 l -14.9875,0 0,26.4 13.475,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5646"
inkscape:connector-curvature="0" />
<path
d="m 126.82369,264.01844 14.9875,0 41.9375,97.35 -19.8,0 -9.075,-22.275 -42.2125,0 -8.8,22.275 -19.3875,0 42.35,-97.35 z m 22,60.225 -14.9875,-39.6 -15.2625,39.6 30.25,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5648"
inkscape:connector-curvature="0" />
<path
d="m 239.7639,284.91844 q -2.75,-3.9875 -7.425,-5.775 -4.5375,-1.925 -9.625,-1.925 -3.025,0 -5.9125,0.6875 -2.75,0.6875 -5.0875,2.2 -2.2,1.5125 -3.575,3.9875 -1.375,2.3375 -1.375,5.6375 0,4.95 3.4375,7.5625 3.4375,2.6125 8.525,4.5375 5.0875,1.925 11.1375,3.7125 6.05,1.7875 11.1375,4.95 5.0875,3.1625 8.525,8.3875 3.4375,5.225 3.4375,13.8875 0,7.8375 -2.8875,13.75 -2.8875,5.775 -7.8375,9.625 -4.8125,3.85 -11.275,5.775 -6.4625,1.925 -13.6125,1.925 -9.075,0 -17.4625,-3.025 -8.3875,-3.025 -14.4375,-10.175 l 13.0625,-12.65 q 3.1625,4.8125 8.25,7.5625 5.225,2.6125 11,2.6125 3.025,0 6.05,-0.825 3.025,-0.825 5.5,-2.475 2.475,-1.65 3.9875,-4.125 1.5125,-2.6125 1.5125,-5.9125 0,-5.3625 -3.4375,-8.25 -3.4375,-2.8875 -8.525,-4.8125 -5.0875,-2.0625 -11.1375,-3.85 -6.05,-1.7875 -11.1375,-4.8125 -5.0875,-3.1625 -8.525,-8.25 -3.4375,-5.225 -3.4375,-13.8875 0,-7.5625 3.025,-13.0625 3.1625,-5.5 8.1125,-9.075 5.0875,-3.7125 11.55,-5.5 6.4625,-1.7875 13.2,-1.7875 7.7,0 14.85,2.3375 7.2875,2.3375 13.0625,7.7 l -12.65,13.3375 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5650"
inkscape:connector-curvature="0" />
<path
d="m 264.21257,264.01844 17.325,0 0,97.35 -17.325,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5652"
inkscape:connector-curvature="0" />
<path
d="m 296.37837,264.01844 17.325,0 0,81.675 41.3875,0 0,15.675 -58.7125,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5654"
inkscape:connector-curvature="0" />
<path
d="m -369.13744,382.26844 22.9625,0 47.1625,72.325 0.275,0 0,-72.325 17.325,0 0,97.35 -22,0 -48.125,-74.6625 -0.275,0 0,74.6625 -17.325,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5656"
inkscape:connector-curvature="0" />
<path
d="m -270.48568,382.26844 64.4875,0 0,15.675 -47.1625,0 0,23.925 44.6875,0 0,15.675 -44.6875,0 0,26.4 49.6375,0 0,15.675 -66.9625,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5658"
inkscape:connector-curvature="0" />
<path
d="m -165.14057,397.94344 -29.8375,0 0,-15.675 77,0 0,15.675 -29.8375,0 0,81.675 -17.325,0 0,-81.675 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5660"
inkscape:connector-curvature="0" />
<path
d="m -111.36694,382.26844 18.974997,0 18.2875,70.125 0.275,0 21.8625,-70.125 17.05,0 21.45,70.125 0.275,0 19.1125,-70.125 17.6,0 -28.3250004,97.35 -16.4999996,0 -22.55,-74.1125 -0.275,0 -22.55,74.1125 -15.95,0 -28.737497,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5662"
inkscape:connector-curvature="0" />
<path
d="m 24.435016,431.35594 q 0,-11.6875 3.85,-21.175 3.85,-9.625 10.5875,-16.3625 6.875,-6.7375 16.225,-10.3125 9.4875,-3.7125 20.7625,-3.7125 11.412504,-0.1375 20.900004,3.4375 9.4875,3.4375 16.3625,10.175 6.875,6.7375 10.725,16.225 3.85,9.4875 3.85,21.175 0,11.4125 -3.85,20.7625 -3.85,9.35 -10.725,16.0875 -6.875,6.7375 -16.3625,10.5875 -9.4875,3.7125 -20.900004,3.85 -11.275,0 -20.7625,-3.575 -9.35,-3.7125 -16.225,-10.3125 -6.7375,-6.7375 -10.5875,-16.0875 -3.85,-9.35 -3.85,-20.7625 z m 18.15,-1.1 q 0,7.8375 2.3375,14.4375 2.475,6.6 6.875,11.4125 4.4,4.8125 10.45,7.5625 6.1875,2.75 13.75,2.75 7.5625,0 13.750004,-2.75 6.1875,-2.75 10.5875,-7.5625 4.4,-4.8125 6.7375,-11.4125 2.475,-6.6 2.475,-14.4375 0,-7.2875 -2.475,-13.6125 -2.3375,-6.325 -6.7375,-11 -4.4,-4.8125 -10.5875,-7.425 -6.187504,-2.75 -13.750004,-2.75 -7.5625,0 -13.75,2.75 -6.05,2.6125 -10.45,7.425 -4.4,4.675 -6.875,11 -2.3375,6.325 -2.3375,13.6125 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5664"
inkscape:connector-curvature="0" />
<path
d="m 137.68287,382.26844 33.825,0 q 7.0125,0 13.475,1.375 6.6,1.2375 11.6875,4.4 5.0875,3.1625 8.1125,8.525 3.025,5.3625 3.025,13.6125 0,10.5875 -5.9125,17.7375 -5.775,7.15 -16.6375,8.6625 l 25.85,43.0375 -20.9,0 -22.55,-41.25 -12.65,0 0,41.25 -17.325,0 0,-97.35 z m 30.8,41.25 q 3.7125,0 7.425,-0.275 3.7125,-0.4125 6.7375,-1.65 3.1625,-1.375 5.0875,-3.9875 1.925,-2.75 1.925,-7.5625 0,-4.2625 -1.7875,-6.875 -1.7875,-2.6125 -4.675,-3.85 -2.8875,-1.375 -6.4625,-1.7875 -3.4375,-0.4125 -6.7375,-0.4125 l -14.9875,0 0,26.4 13.475,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5666"
inkscape:connector-curvature="0" />
<path
d="m 221.50746,382.26844 17.325,0 0,41.25 0.825,0 40.2875,-41.25 23.375,0 -45.5125,44.9625 48.5375,52.3875 -24.3375,0 -42.2125,-47.85 -0.9625,0 0,47.85 -17.325,0 0,-97.35 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:137.5px;line-height:86.00000143%;font-family:Avenir;-inkscape-font-specification:'Avenir Heavy';letter-spacing:1.35000002px"
id="path5668"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -8,7 +8,7 @@
<array>
<string>sh</string>
<string>-c</string>
<string>/usr/local/bin/yggdrasil -useconf &lt; /etc/yggdrasil.conf</string>
<string>/usr/local/bin/yggdrasil -useconffile /etc/yggdrasil.conf</string>
</array>
<key>KeepAlive</key>
<true/>

55
contrib/openrc/yggdrasil Executable file
View File

@@ -0,0 +1,55 @@
#!/sbin/openrc-run
description="An experiment in scalable routing as an encrypted IPv6 overlay network."
CONFFILE="/etc/yggdrasil.conf"
pidfile="/run/${RC_SVCNAME}.pid"
command="/usr/bin/yggdrasil"
extra_started_commands="reload"
depend() {
use net dns logger
}
start_pre() {
if [ ! -f "${CONFFILE}" ]; then
ebegin "Generating new configuration file into ${CONFFILE}"
if ! eval ${command} -genconf > ${CONFFILE}; then
eerror "Failed to generate configuration file"
exit 1
fi
fi
if [ ! -e /dev/net/tun ]; then
ebegin "Inserting TUN module"
if ! modprobe tun; then
eerror "Failed to insert TUN kernel module"
exit 1
fi
fi
}
start() {
ebegin "Starting ${RC_SVCNAME}"
start-stop-daemon --start --quiet \
--pidfile "${pidfile}" \
--make-pidfile \
--background \
--stdout /var/log/yggdrasil.stdout.log \
--stderr /var/log/yggdrasil.stderr.log \
--exec "${command}" -- -useconffile "${CONFFILE}"
eend $?
}
reload() {
ebegin "Reloading ${RC_SVCNAME}"
start-stop-daemon --signal HUP --pidfile "${pidfile}"
eend $?
}
stop() {
ebegin "Stopping ${RC_SVCNAME}"
start-stop-daemon --stop --pidfile "${pidfile}" --exec "${command}"
eend $?
}

View File

@@ -4,8 +4,8 @@
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
# Complain if the git history is not available
if [ $? != 0 ]; then
printf "unknown"
if [ $? != 0 ] || [ -z "$BRANCH" ]; then
printf "yggdrasil"
exit 1
fi

View File

@@ -1,62 +1,46 @@
#!/bin/sh
# Merge commits from this branch are counted
DEVELOPBRANCH="yggdrasil-network/develop"
# Get the last tag
TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.0" 2>/dev/null)
TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null)
# Get last merge to master
MERGE=$(git rev-list $TAG..master --grep "from $DEVELOPBRANCH" 2>/dev/null | head -n 1)
# Get the number of merges since the last merge to master
PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" --first-parent master 2>/dev/null)
# Decide whether we should prepend the version with "v" - the default is that
# we do because we use it in git tags, but we might not always need it
PREPEND="v"
if [ "$1" = "--bare" ]; then
PREPEND=""
fi
# If it fails then there's no last tag - go from the first commit
if [ $? != 0 ]; then
PATCH=$(git rev-list HEAD --count 2>/dev/null)
# Complain if the git history is not available
if [ $? != 0 ]; then
printf 'unknown'
exit 1
fi
printf '%s0.0.%d' "$PREPEND" "$PATCH"
# Did getting the tag succeed?
if [ $? != 0 ] || [ -z "$TAG" ]; then
printf -- "unknown"
exit 1
fi
# Get the number of merges on the current branch since the last tag
BUILD=$(git rev-list $TAG..HEAD --count --merges)
# Get the current branch
BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null)
# Did getting the branch succeed?
if [ $? != 0 ] || [ -z "$BRANCH" ]; then
BRANCH="master"
fi
# Split out into major, minor and patch numbers
MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1)
MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2)
# Get the current checked out branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3)
# Output in the desired format
if [ $PATCH = 0 ]; then
if [ ! -z $FULL ]; then
printf '%s%d.%d.0' "$PREPEND" "$MAJOR" "$MINOR"
else
printf '%s%d.%d' "$PREPEND" "$MAJOR" "$MINOR"
fi
if [ $((PATCH)) -eq 0 ]; then
printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))"
else
printf '%s%d.%d.%d' "$PREPEND" "$MAJOR" "$MINOR" "$PATCH"
printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))"
fi
# Add the build tag on non-master branches
if [ $BRANCH != "master" ]; then
if [ $BUILD != 0 ]; then
printf -- "-%04d" "$BUILD"
if [ "$BRANCH" != "master" ]; then
BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null)
# Did getting the count of commits since the tag succeed?
if [ $? != 0 ] || [ -z "$BUILD" ]; then
printf -- "-unknown"
exit 1
fi
# Is the build greater than zero?
if [ $((BUILD)) -gt 0 ]; then
printf -- "-%04d" "$((BUILD))"
fi
fi

View File

@@ -1,10 +0,0 @@
[Unit]
Description=Restart yggdrasil on resume from sleep
After=sleep.target
[Service]
Type=oneshot
ExecStart=/bin/systemctl restart yggdrasil
[Install]
WantedBy=sleep.target

View File

@@ -14,8 +14,8 @@ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \
echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \
fi"
ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
[Install]
WantedBy=multi-user.target
Also=yggdrasil-resume.service

View File

@@ -0,0 +1,150 @@
This software is released into the public domain. As such, it can be
used under the Unlicense or CC0 public domain dedications.
The Unlicense
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

View File

@@ -0,0 +1,12 @@
.PHONY: all
all: util yggdrasil-brute-multi-curve25519 yggdrasil-brute-multi-ed25519
util: util.c
gcc -Wall -std=c89 -O3 -c -o util.o util.c
yggdrasil-brute-multi-ed25519: yggdrasil-brute-multi-ed25519.c util.o
gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-ed25519 -lsodium yggdrasil-brute-multi-ed25519.c util.o
yggdrasil-brute-multi-curve25519: yggdrasil-brute-multi-curve25519.c util.o
gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-curve25519 -lsodium yggdrasil-brute-multi-curve25519.c util.o

View File

@@ -0,0 +1,8 @@
# yggdrasil-brute-simple
Simple program for finding curve25519 and ed25519 public keys whose sha512 hash has many leading ones.
Because ed25519 private keys consist of a seed that is hashed to find the secret part of the keypair,
this program is near optimal for finding ed25519 keypairs. Curve25519 key generation, on the other hand,
could be further optimized with elliptic curve magic.
Depends on libsodium.

View File

@@ -0,0 +1,62 @@
#include "yggdrasil-brute.h"
int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]) {
/* Where to insert hash into sorted hashlist */
int j;
int where = -1;
for (j = 0; j < NUMKEYS; ++j) {
if (memcmp(hash, besthashlist[j], 64) > 0) ++where;
else break;
}
return where;
}
void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where) {
int j;
for (j = 0; j < where; ++j) {
memcpy(itemlist[j], itemlist[j+1], 64);
}
memcpy(itemlist[where], item, 64);
}
void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where) {
int j;
for (j = 0; j < where; ++j) {
memcpy(itemlist[j], itemlist[j+1], 32);
}
memcpy(itemlist[where], item, 32);
}
void make_addr(unsigned char addr[32], unsigned char hash[64]) {
/* Public key hash to yggdrasil ipv6 address */
int i;
int offset;
unsigned char mask;
unsigned char c;
int ones = 0;
unsigned char br = 0; /* false */
for (i = 0; i < 64 && !br; ++i) {
mask = 128;
c = hash[i];
while (mask) {
if (c & mask) {
++ones;
} else {
br = 1; /* true */
break;
}
mask >>= 1;
}
}
addr[0] = 2;
addr[1] = ones;
offset = ones + 1;
for (i = 0; i < 14; ++i) {
c = hash[offset/8] << (offset%8);
c |= hash[offset/8 + 1] >> (8 - offset%8);
addr[i + 2] = c;
offset += 8;
}
}

View File

@@ -0,0 +1,105 @@
/*
sk: 32 random bytes
sk[0] &= 248;
sk[31] &= 127;
sk[31] |= 64;
increment sk
pk = curve25519_scalarmult_base(mysecret)
hash = sha512(pk)
if besthash:
bestsk = sk
besthash = hash
*/
#include "yggdrasil-brute.h"
void seed(unsigned char sk[32]) {
randombytes_buf(sk, 32);
sk[0] &= 248;
sk[31] &= 127;
sk[31] |= 64;
}
int main(int argc, char **argv) {
int i;
int j;
unsigned char addr[16];
time_t starttime;
time_t requestedtime;
unsigned char bestsklist[NUMKEYS][32];
unsigned char bestpklist[NUMKEYS][32];
unsigned char besthashlist[NUMKEYS][64];
unsigned char sk[32];
unsigned char pk[32];
unsigned char hash[64];
unsigned int runs = 0;
int where;
if (argc != 2) {
fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 <seconds>\n");
return 1;
}
if (sodium_init() < 0) {
/* panic! the library couldn't be initialized, it is not safe to use */
printf("sodium init failed!\n");
return 1;
}
starttime = time(NULL);
requestedtime = atoi(argv[1]);
if (requestedtime < 0) requestedtime = 0;
fprintf(stderr, "Searching for yggdrasil curve25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime);
sodium_memzero(bestsklist, NUMKEYS * 32);
sodium_memzero(bestpklist, NUMKEYS * 32);
sodium_memzero(besthashlist, NUMKEYS * 64);
seed(sk);
do {
/* generate pubkey, hash, compare, increment secret.
* this loop should take 4 seconds on modern hardware */
for (i = 0; i < (1 << 16); ++i) {
++runs;
if (crypto_scalarmult_curve25519_base(pk, sk) != 0) {
printf("scalarmult to create pub failed!\n");
return 1;
}
crypto_hash_sha512(hash, pk, 32);
where = find_where(hash, besthashlist);
if (where >= 0) {
insert_32(bestsklist, sk, where);
insert_32(bestpklist, pk, where);
insert_64(besthashlist, hash, where);
seed(sk);
}
for (j = 1; j < 31; ++j) if (++sk[j]) break;
}
} while (time(NULL) - starttime < requestedtime || runs < NUMKEYS);
fprintf(stderr, "--------------addr-------------- -----------------------------secret----------------------------- -----------------------------public-----------------------------\n");
for (i = 0; i < NUMKEYS; ++i) {
make_addr(addr, besthashlist[i]);
for (j = 0; j < 16; ++j) printf("%02x", addr[j]);
printf(" ");
for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]);
printf(" ");
for (j = 0; j < 32; ++j) printf("%02x", bestpklist[i][j]);
printf("\n");
}
sodium_memzero(bestsklist, NUMKEYS * 32);
sodium_memzero(sk, 32);
return 0;
}

View File

@@ -0,0 +1,106 @@
/*
seed: 32 random bytes
sk: sha512(seed)
sk[0] &= 248
sk[31] &= 127
sk[31] |= 64
pk: scalarmult_ed25519_base(sk)
increment seed
generate sk
generate pk
hash = sha512(mypub)
if besthash:
bestseed = seed
bestseckey = sk
bestpubkey = pk
besthash = hash
*/
#include "yggdrasil-brute.h"
int main(int argc, char **argv) {
int i;
int j;
time_t starttime;
time_t requestedtime;
unsigned char bestsklist[NUMKEYS][64]; /* sk contains pk */
unsigned char besthashlist[NUMKEYS][64];
unsigned char seed[32];
unsigned char sk[64];
unsigned char pk[32];
unsigned char hash[64];
unsigned int runs = 0;
int where;
if (argc != 2) {
fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 <seconds>\n");
return 1;
}
if (sodium_init() < 0) {
/* panic! the library couldn't be initialized, it is not safe to use */
printf("sodium init failed!\n");
return 1;
}
starttime = time(NULL);
requestedtime = atoi(argv[1]);
if (requestedtime < 0) requestedtime = 0;
fprintf(stderr, "Searching for yggdrasil ed25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime);
sodium_memzero(bestsklist, NUMKEYS * 64);
sodium_memzero(besthashlist, NUMKEYS * 64);
randombytes_buf(seed, 32);
do {
/* generate pubkey, hash, compare, increment secret.
* this loop should take 4 seconds on modern hardware */
for (i = 0; i < (1 << 17); ++i) {
++runs;
crypto_hash_sha512(sk, seed, 32);
if (crypto_scalarmult_ed25519_base(pk, sk) != 0) {
printf("scalarmult to create pub failed!\n");
return 1;
}
memcpy(sk + 32, pk, 32);
crypto_hash_sha512(hash, pk, 32);
/* insert into local list of good key */
where = find_where(hash, besthashlist);
if (where >= 0) {
insert_64(bestsklist, sk, where);
insert_64(besthashlist, hash, where);
randombytes_buf(seed, 32);
}
for (j = 1; j < 31; ++j) if (++seed[j]) break;
}
} while (time(NULL) - starttime < requestedtime || runs < NUMKEYS);
fprintf(stderr, "!! Secret key is seed concatenated with public !!\n");
fprintf(stderr, "---hash--- ------------------------------seed------------------------------ -----------------------------public-----------------------------\n");
for (i = 0; i < NUMKEYS; ++i) {
for (j = 0; j < 5; ++j) printf("%02x", besthashlist[i][j]);
printf(" ");
for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]);
printf(" ");
for (j = 32; j < 64; ++j) printf("%02x", bestsklist[i][j]);
printf("\n");
}
sodium_memzero(bestsklist, NUMKEYS * 64);
sodium_memzero(sk, 64);
sodium_memzero(seed, 32);
return 0;
}

View File

@@ -0,0 +1,12 @@
#include <sodium.h>
#include <stdio.h> /* printf */
#include <string.h> /* memcpy */
#include <stdlib.h> /* atoi */
#include <time.h> /* time */
#define NUMKEYS 10
void make_addr(unsigned char addr[32], unsigned char hash[64]);
int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]);
void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where);
void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where);

View File

@@ -1,4 +1,4 @@
# Yggdasil
# Yggdrasil
Note: This is a very rough early draft.
@@ -63,7 +63,7 @@ These coordinates are used as a distance label.
Given the coordinates of any two nodes, it is possible to calculate the length of some real path through the network between the two nodes.
Traffic is forwarded using a [greedy routing](https://en.wikipedia.org/wiki/Small-world_routing#Greedy_routing) scheme, where each node forwards the packet to a one-hop neighbor that is closer to the destination (according to this distance metric) than the current node.
In particular, when a packet needs to be forward, a node will forward it to whatever peer is closest to the destination in the greedy [metric space](https://en.wikipedia.org/wiki/Metric_space) used by the network, provided that the peer is closer to the destination than the current node.
In particular, when a packet needs to be forwarded, a node will forward it to whatever peer is closest to the destination in the greedy [metric space](https://en.wikipedia.org/wiki/Metric_space) used by the network, provided that the peer is closer to the destination than the current node.
If no closer peers are idle, then the packet is queued in FIFO order, with separate queues per destination coords (currently, as a bit of a hack, IPv6 flow labels are embedeed after the end of the significant part of the coords, so queues distinguish between different traffic streams with the same destination).
Whenever the node finishes forwarding a packet to a peer, it checks the queues, and will forward the first packet from the queue with the maximum `<age of first packet>/<queue size in bytes>`, i.e. the bandwidth the queue is attempting to use, subject to the constraint that the peer is a valid next hop (i.e. closer to the destination than the current node).

16
go.mod
View File

@@ -1,14 +1,18 @@
module github.com/yggdrasil-network/yggdrasil-go
require (
github.com/docker/libcontainer v2.2.1+incompatible
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0
github.com/mitchellh/mapstructure v1.1.2
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20181207154023-610586996380
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e
golang.org/x/text v0.3.0
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b // indirect
github.com/vishvananda/netlink v1.0.0
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
golang.org/x/text v0.3.2
)

32
go.sum
View File

@@ -1,5 +1,7 @@
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU=
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g=
@@ -8,13 +10,23 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY=
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc=
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0=
golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM=
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I=
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8 h1:YY9Pg2BEp0jeUVU60svTOaDr+fs1ySC9RbdC1Qc6wOw=
github.com/yggdrasil-network/water v0.0.0-20190812103929-c83fe40250f8/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -51,12 +51,12 @@ ip netns exec node4 ip link set lo up
ip netns exec node5 ip link set lo up
ip netns exec node6 ip link set lo up
ip netns exec node1 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
ip netns exec node2 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
ip netns exec node3 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
ip netns exec node4 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
ip netns exec node5 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
ip netns exec node6 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
echo '{AdminListen: "none"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo '{AdminListen: "none"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo '{AdminListen: "none"}' | ip netns exec node3 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo '{AdminListen: "none"}' | ip netns exec node4 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo '{AdminListen: "none"}' | ip netns exec node5 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo '{AdminListen: "none"}' | ip netns exec node6 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo "Started, to continue you should (possibly w/ sudo):"
echo "kill" $(jobs -p)

33
misc/run-twolink-test Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
# Connects nodes in two namespaces by two links with different bandwidth (10mbit and 100mbit)
ip netns add node1
ip netns add node2
ip link add veth11 type veth peer name veth21
ip link set veth11 netns node1 up
ip link set veth21 netns node2 up
ip link add veth12 type veth peer name veth22
ip link set veth12 netns node1 up
ip link set veth22 netns node2 up
ip netns exec node1 tc qdisc add dev veth11 root tbf rate 10mbit burst 8192 latency 1ms
ip netns exec node2 tc qdisc add dev veth21 root tbf rate 10mbit burst 8192 latency 1ms
ip netns exec node1 tc qdisc add dev veth12 root tbf rate 100mbit burst 8192 latency 1ms
ip netns exec node2 tc qdisc add dev veth22 root tbf rate 100mbit burst 8192 latency 1ms
echo '{AdminListen: "unix://node1.sock"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node1.log &
echo '{AdminListen: "unix://node2.sock"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node2.log &
echo "Started, to continue you should (possibly w/ sudo):"
echo "kill" $(jobs -p)
wait
ip netns delete node1
ip netns delete node2
ip link delete veth11
ip link delete veth12

View File

@@ -6,13 +6,15 @@ import "os"
import "strings"
import "strconv"
import "time"
import "log"
import "runtime"
import "runtime/pprof"
import "flag"
import "github.com/gologme/log"
import . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
import . "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
////////////////////////////////////////////////////////////////////////////////

490
src/admin/admin.go Normal file
View File

@@ -0,0 +1,490 @@
package admin
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
// TODO: Add authentication
type AdminSocket struct {
core *yggdrasil.Core
log *log.Logger
reconfigure chan chan error
listenaddr string
listener net.Listener
handlers map[string]handler
}
// Info refers to information that is returned to the admin socket handler.
type Info map[string]interface{}
type handler struct {
args []string // List of human-readable argument names
handler func(Info) (Info, error) // First is input map, second is output
}
// AddHandler is called for each admin function to add the handler and help documentation to the API.
func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(Info) (Info, error)) error {
if _, ok := a.handlers[strings.ToLower(name)]; ok {
return errors.New("handler already exists")
}
a.handlers[strings.ToLower(name)] = handler{
args: args,
handler: handlerfunc,
}
return nil
}
// init runs the initial admin setup.
func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) {
a.core = c
a.log = log
a.reconfigure = make(chan chan error, 1)
a.handlers = make(map[string]handler)
go func() {
for {
e := <-a.reconfigure
current, previous := state.GetCurrent(), state.GetPrevious()
if current.AdminListen != previous.AdminListen {
a.listenaddr = current.AdminListen
a.Stop()
a.Start()
}
e <- nil
}
}()
current := state.GetCurrent()
a.listenaddr = current.AdminListen
a.AddHandler("list", []string{}, func(in Info) (Info, error) {
handlers := make(map[string]interface{})
for handlername, handler := range a.handlers {
handlers[handlername] = Info{"fields": handler.args}
}
return Info{"list": handlers}, nil
})
a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
ip := c.Address().String()
subnet := c.Subnet()
return Info{
"self": Info{
ip: Info{
"box_pub_key": c.EncryptionPublicKey(),
"build_name": version.BuildName(),
"build_version": version.BuildVersion(),
"coords": fmt.Sprintf("%v", c.Coords()),
"subnet": subnet.String(),
},
},
}, nil
})
a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) {
peers := make(Info)
for _, p := range a.core.GetPeers() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey))
so := net.IP(addr[:]).String()
peers[so] = Info{
"port": p.Port,
"uptime": p.Uptime.Seconds(),
"bytes_sent": p.BytesSent,
"bytes_recvd": p.BytesRecvd,
"proto": p.Protocol,
"endpoint": p.Endpoint,
"box_pub_key": hex.EncodeToString(p.PublicKey[:]),
}
}
return Info{"peers": peers}, nil
})
a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) {
switchpeers := make(Info)
for _, s := range a.core.GetSwitchPeers() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
so := fmt.Sprint(s.Port)
switchpeers[so] = Info{
"ip": net.IP(addr[:]).String(),
"coords": fmt.Sprintf("%v", s.Coords),
"port": s.Port,
"bytes_sent": s.BytesSent,
"bytes_recvd": s.BytesRecvd,
"proto": s.Protocol,
"endpoint": s.Endpoint,
"box_pub_key": hex.EncodeToString(s.PublicKey[:]),
}
}
return Info{"switchpeers": switchpeers}, nil
})
/*
a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) {
queues := a.core.GetSwitchQueues()
return Info{"switchqueues": queues.asMap()}, nil
})
*/
a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) {
dht := make(Info)
for _, d := range a.core.GetDHT() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey))
so := net.IP(addr[:]).String()
dht[so] = Info{
"coords": fmt.Sprintf("%v", d.Coords),
"last_seen": d.LastSeen.Seconds(),
"box_pub_key": hex.EncodeToString(d.PublicKey[:]),
}
}
return Info{"dht": dht}, nil
})
a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) {
sessions := make(Info)
for _, s := range a.core.GetSessions() {
addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
so := net.IP(addr[:]).String()
sessions[so] = Info{
"coords": fmt.Sprintf("%v", s.Coords),
"bytes_sent": s.BytesSent,
"bytes_recvd": s.BytesRecvd,
"mtu": s.MTU,
"uptime": s.Uptime.Seconds(),
"was_mtu_fixed": s.WasMTUFixed,
"box_pub_key": hex.EncodeToString(s.PublicKey[:]),
}
}
return Info{"sessions": sessions}, nil
})
a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) {
// Set sane defaults
intf := ""
// Has interface been specified?
if itf, ok := in["interface"]; ok {
intf = itf.(string)
}
if a.core.AddPeer(in["uri"].(string), intf) == nil {
return Info{
"added": []string{
in["uri"].(string),
},
}, nil
} else {
return Info{
"not_added": []string{
in["uri"].(string),
},
}, errors.New("Failed to add peer")
}
})
a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) {
port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64)
if err != nil {
return Info{}, err
}
if a.core.DisconnectPeer(uint64(port)) == nil {
return Info{
"removed": []string{
fmt.Sprint(port),
},
}, nil
} else {
return Info{
"not_removed": []string{
fmt.Sprint(port),
},
}, errors.New("Failed to remove peer")
}
})
a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) {
return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil
})
a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
return Info{
"added": []string{
in["box_pub_key"].(string),
},
}, nil
} else {
return Info{
"not_added": []string{
in["box_pub_key"].(string),
},
}, errors.New("Failed to add allowed key")
}
})
a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
return Info{
"removed": []string{
in["box_pub_key"].(string),
},
}, nil
} else {
return Info{
"not_removed": []string{
in["box_pub_key"].(string),
},
}, errors.New("Failed to remove allowed key")
}
})
a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) {
var reserr error
var result yggdrasil.DHTRes
if in["target"] == nil {
in["target"] = "none"
}
coords := util.DecodeCoordString(in["coords"].(string))
var boxPubKey crypto.BoxPubKey
if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil {
copy(boxPubKey[:], b[:])
if n, err := hex.DecodeString(in["target"].(string)); err == nil {
var targetNodeID crypto.NodeID
copy(targetNodeID[:], n[:])
result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID)
} else {
result, reserr = a.core.DHTPing(boxPubKey, coords, nil)
}
} else {
return Info{}, err
}
if reserr != nil {
return Info{}, reserr
}
infos := make(map[string]map[string]string, len(result.Infos))
for _, dinfo := range result.Infos {
info := map[string]string{
"box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]),
"coords": fmt.Sprintf("%v", dinfo.Coords),
}
addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String()
infos[addr] = info
}
return Info{"nodes": infos}, nil
})
a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) {
var nocache bool
if in["nocache"] != nil {
nocache = in["nocache"].(string) == "true"
}
var boxPubKey crypto.BoxPubKey
var coords []uint64
if in["box_pub_key"] == nil && in["coords"] == nil {
nodeinfo := a.core.MyNodeInfo()
var jsoninfo interface{}
if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil {
return Info{}, err
} else {
return Info{"nodeinfo": jsoninfo}, nil
}
} else if in["box_pub_key"] == nil || in["coords"] == nil {
return Info{}, errors.New("Expecting both box_pub_key and coords")
} else {
if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil {
copy(boxPubKey[:], b[:])
} else {
return Info{}, err
}
coords = util.DecodeCoordString(in["coords"].(string))
}
result, err := a.core.GetNodeInfo(boxPubKey, coords, nocache)
if err == nil {
var m map[string]interface{}
if err = json.Unmarshal(result, &m); err == nil {
return Info{"nodeinfo": m}, nil
} else {
return Info{}, err
}
} else {
return Info{}, err
}
})
}
// start runs the admin API socket to listen for / respond to admin API calls.
func (a *AdminSocket) Start() error {
if a.listenaddr != "none" && a.listenaddr != "" {
go a.listen()
}
return nil
}
// cleans up when stopping
func (a *AdminSocket) Stop() error {
if a.listener != nil {
return a.listener.Close()
} else {
return nil
}
}
// listen is run by start and manages API connections.
func (a *AdminSocket) listen() {
u, err := url.Parse(a.listenaddr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up")
if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process")
os.Exit(1)
} else {
if err := os.Remove(a.listenaddr[7:]); err == nil {
a.log.Debugln(a.listenaddr[7:], "was cleaned up")
} else {
a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err)
os.Exit(1)
}
}
}
a.listener, err = net.Listen("unix", a.listenaddr[7:])
if err == nil {
switch a.listenaddr[7:8] {
case "@": // maybe abstract namespace
default:
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
a.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
}
}
}
case "tcp":
a.listener, err = net.Listen("tcp", u.Host)
default:
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
a.listener, err = net.Listen("tcp", a.listenaddr)
}
} else {
a.listener, err = net.Listen("tcp", a.listenaddr)
}
if err != nil {
a.log.Errorf("Admin socket failed to listen: %v", err)
os.Exit(1)
}
a.log.Infof("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String())
defer a.listener.Close()
for {
conn, err := a.listener.Accept()
if err == nil {
go a.handleRequest(conn)
}
}
}
// handleRequest calls the request handler for each request sent to the admin API.
func (a *AdminSocket) handleRequest(conn net.Conn) {
decoder := json.NewDecoder(conn)
encoder := json.NewEncoder(conn)
encoder.SetIndent("", " ")
recv := make(Info)
send := make(Info)
defer func() {
r := recover()
if r != nil {
send = Info{
"status": "error",
"error": "Check your syntax and input types",
}
a.log.Debugln("Admin socket error:", r)
if err := encoder.Encode(&send); err != nil {
a.log.Debugln("Admin socket JSON encode error:", err)
}
conn.Close()
}
}()
for {
// Start with a clean slate on each request
recv = Info{}
send = Info{}
// Decode the input
if err := decoder.Decode(&recv); err != nil {
a.log.Debugln("Admin socket JSON decode error:", err)
return
}
// Send the request back with the response, and default to "error"
// unless the status is changed below by one of the handlers
send["request"] = recv
send["status"] = "error"
n := strings.ToLower(recv["request"].(string))
if _, ok := recv["request"]; !ok {
send["error"] = "No request sent"
goto respond
}
if h, ok := a.handlers[n]; ok {
// Check that we have all the required arguments
for _, arg := range h.args {
// An argument in [square brackets] is optional and not required,
// so we can safely ignore those
if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
continue
}
// Check if the field is missing
if _, ok := recv[arg]; !ok {
send = Info{
"status": "error",
"error": "Expected field missing: " + arg,
"expecting": arg,
}
goto respond
}
}
// By this point we should have all the fields we need, so call
// the handler
response, err := h.handler(recv)
if err != nil {
send["error"] = err.Error()
if response != nil {
send["response"] = response
goto respond
}
} else {
send["status"] = "success"
if response != nil {
send["response"] = response
goto respond
}
}
} else {
// Start with a clean response on each request, which defaults to an error
// state. If a handler is found below then this will be overwritten
send = Info{
"request": recv,
"status": "error",
"error": fmt.Sprintf("Unknown action '%s', try 'list' for help", recv["request"].(string)),
}
goto respond
}
// Send the response back
respond:
if err := encoder.Encode(&send); err != nil {
return
}
// If "keepalive" isn't true then close the connection
if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
conn.Close()
}
}
}

View File

@@ -1,32 +1,65 @@
package config
import (
"encoding/hex"
"sync"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
// NodeState represents the active and previous configuration of the node and
// protects it with a mutex
type NodeState struct {
Current NodeConfig
Previous NodeConfig
Mutex sync.RWMutex
}
// Current returns the current node config
func (s *NodeState) GetCurrent() NodeConfig {
s.Mutex.RLock()
defer s.Mutex.RUnlock()
return s.Current
}
// Previous returns the previous node config
func (s *NodeState) GetPrevious() NodeConfig {
s.Mutex.RLock()
defer s.Mutex.RUnlock()
return s.Previous
}
// Replace the node configuration with new configuration. This method returns
// both the new and the previous node configs
func (s *NodeState) Replace(n NodeConfig) {
s.Mutex.Lock()
defer s.Mutex.Unlock()
s.Previous = s.Current
s.Current = n
}
// NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct {
Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."`
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."`
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."`
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."`
InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."`
ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."`
AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."`
MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."`
AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."`
EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."`
EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"`
SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."`
SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"`
MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."`
LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."`
IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."`
IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."`
IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."`
TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."`
SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."`
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
//Net NetConfig `comment:"Extended options for connecting to peers over other networks."`
}
// NetConfig defines network/proxy related configuration values
type NetConfig struct {
Tor TorConfig `comment:"Experimental options for configuring peerings over Tor."`
I2P I2PConfig `comment:"Experimental options for configuring peerings over I2P."`
}
// SessionFirewall controls the session firewall configuration
@@ -41,14 +74,66 @@ type SessionFirewall struct {
// TunnelRouting contains the crypto-key routing tables for tunneling
type TunnelRouting struct {
Enable bool `comment:"Enable or disable tunnel routing."`
IPv6Destinations map[string]string `comment:"IPv6 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"`
IPv6Sources []string `comment:"Optional IPv6 source subnets which are allowed to be tunnelled in\naddition to this node's Yggdrasil address/subnet. If not\nspecified, only traffic originating from this node's Yggdrasil\naddress or subnet will be tunnelled."`
IPv4Destinations map[string]string `comment:"IPv4 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"`
IPv4Sources []string `comment:"IPv4 source subnets which are allowed to be tunnelled. Unlike for\nIPv6, this option is required for bridging IPv4 traffic. Only\ntraffic with a source matching these subnets will be tunnelled."`
Enable bool `comment:"Enable or disable tunnel routing."`
IPv6RemoteSubnets map[string]string `comment:"IPv6 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"`
IPv6LocalSubnets []string `comment:"IPv6 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges (or the Yggdrasil node's IPv6 address/subnet)\nwill be tunnelled."`
IPv4RemoteSubnets map[string]string `comment:"IPv4 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"`
IPv4LocalSubnets []string `comment:"IPv4 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges will be tunnelled."`
}
// SwitchOptions contains tuning options for the switch
type SwitchOptions struct {
MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."`
}
// Generates default configuration. This is used when outputting the -genconf
// parameter and also when using -autoconf. The isAutoconf flag is used to
// determine whether the operating system should select a free port by itself
// (which guarantees that there will not be a conflict with any other services)
// or whether to generate a random port number. The only side effect of setting
// isAutoconf is that the TCP and UDP ports will likely end up with different
// port numbers.
func GenerateConfig() *NodeConfig {
// Generate encryption keys.
bpub, bpriv := crypto.NewBoxKeys()
spub, spriv := crypto.NewSigKeys()
// Create a node configuration and populate it.
cfg := NodeConfig{}
cfg.Listen = []string{}
cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen
cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
cfg.SigningPublicKey = hex.EncodeToString(spub[:])
cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedEncryptionPublicKeys = []string{}
cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
cfg.SessionFirewall.Enable = false
cfg.SessionFirewall.AllowFromDirect = true
cfg.SessionFirewall.AllowFromRemote = true
cfg.SessionFirewall.AlwaysAllowOutbound = true
cfg.SwitchOptions.MaxTotalQueueSize = 4 * 1024 * 1024
cfg.NodeInfoPrivacy = false
return &cfg
}
// NewEncryptionKeys generates a new encryption keypair. The encryption keys are
// used to encrypt traffic and to derive the IPv6 address/subnet of the node.
func (cfg *NodeConfig) NewEncryptionKeys() {
bpub, bpriv := crypto.NewBoxKeys()
cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
}
// NewSigningKeys generates a new signing keypair. The signing keys are used to
// derive the structure of the spanning tree.
func (cfg *NodeConfig) NewSigningKeys() {
spub, spriv := crypto.NewSigKeys()
cfg.SigningPublicKey = hex.EncodeToString(spub[:])
cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
}

View File

@@ -1,8 +0,0 @@
package config
// I2PConfig is the configuration structure for i2p related configuration
type I2PConfig struct {
Keyfile string // private key file or empty string for ephemeral keys
Addr string // address of i2p api connector
Enabled bool
}

View File

@@ -1,8 +0,0 @@
package config
// TorConfig is the configuration structure for Tor Proxy related values
type TorConfig struct {
OnionKeyfile string // hidden service private key for ADD_ONION (currently unimplemented)
ControlAddr string // tor control port address
Enabled bool
}

View File

@@ -13,7 +13,9 @@ It also defines NodeID and TreeID as hashes of keys, and wraps hash functions
import (
"crypto/rand"
"crypto/sha512"
"encoding/hex"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
@@ -32,6 +34,41 @@ type NodeID [NodeIDLen]byte
type TreeID [TreeIDLen]byte
type Handle [handleLen]byte
func (n *NodeID) String() string {
return hex.EncodeToString(n[:])
}
// Network returns "nodeid" nearly always right now.
func (n *NodeID) Network() string {
return "nodeid"
}
// PrefixLength returns the number of bits set in a masked NodeID.
func (n *NodeID) PrefixLength() int {
var len int
for i, v := range *n {
_, _ = i, v
if v == 0xff {
len += 8
continue
}
for v&0x80 != 0 {
len++
v <<= 1
}
if v != 0 {
return -1
}
for i++; i < NodeIDLen; i++ {
if n[i] != 0 {
return -1
}
}
break
}
return len
}
func GetNodeID(pub *BoxPubKey) *NodeID {
h := sha512.Sum512(pub[:])
return (*NodeID)(&h)
@@ -88,6 +125,15 @@ func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool {
return ed25519.Verify(pub[:], msg, sig[:])
}
func (p SigPrivKey) Public() SigPubKey {
priv := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
copy(priv[:], p[:])
pub := priv.Public().(ed25519.PublicKey)
var sigPub SigPubKey
copy(sigPub[:], pub[:])
return sigPub
}
////////////////////////////////////////////////////////////////////////////////
// NaCl-like crypto "box" (curve25519+xsalsa20+poly1305)
@@ -168,6 +214,14 @@ func (n *BoxNonce) Increment() {
}
}
func (p BoxPrivKey) Public() BoxPubKey {
var boxPub [BoxPubKeyLen]byte
var boxPriv [BoxPrivKeyLen]byte
copy(boxPriv[:BoxPrivKeyLen], p[:BoxPrivKeyLen])
curve25519.ScalarBaseMult(&boxPub, &boxPriv)
return boxPub
}
// Used to subtract one nonce from another, staying in the range +- 64.
// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask.
// It's basically part of the machinery that prevents replays and duplicate packets.

View File

@@ -10,6 +10,9 @@ type platformDefaultParameters struct {
// Configuration (used for yggdrasilctl)
DefaultConfigFile string
// Multicast interfaces
DefaultMulticastInterfaces []string
// TUN/TAP
MaximumIfMTU int
DefaultIfMTU int

View File

@@ -12,6 +12,12 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
"en.*",
"bridge.*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

View File

@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 32767,
DefaultIfMTU: 32767,

View File

@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

View File

@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 9000,
DefaultIfMTU: 9000,

View File

@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 16384,
DefaultIfMTU: 16384,

View File

@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

View File

@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []string{
".*",
},
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,

13
src/multicast/admin.go Normal file
View File

@@ -0,0 +1,13 @@
package multicast
import "github.com/yggdrasil-network/yggdrasil-go/src/admin"
func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) {
var intfs []string
for _, v := range m.Interfaces() {
intfs = append(intfs, v.Name)
}
return admin.Info{"multicast_interfaces": intfs}, nil
})
}

285
src/multicast/multicast.go Normal file
View File

@@ -0,0 +1,285 @@
package multicast
import (
"context"
"fmt"
"net"
"regexp"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
"golang.org/x/net/ipv6"
)
// Multicast represents the multicast advertisement and discovery mechanism used
// by Yggdrasil to find peers on the same subnet. When a beacon is received on a
// configured multicast interface, Yggdrasil will attempt to peer with that node
// automatically.
type Multicast struct {
core *yggdrasil.Core
config *config.NodeState
log *log.Logger
sock *ipv6.PacketConn
groupAddr string
listeners map[string]*yggdrasil.TcpListener
listenPort uint16
isOpen bool
}
// Init prepares the multicast interface for use.
func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
m.core = core
m.config = state
m.log = log
m.listeners = make(map[string]*yggdrasil.TcpListener)
current := m.config.GetCurrent()
m.listenPort = current.LinkLocalTCPPort
m.groupAddr = "[ff02::114]:9001"
return nil
}
// Start starts the multicast interface. This launches goroutines which will
// listen for multicast beacons from other hosts and will advertise multicast
// beacons out to the network.
func (m *Multicast) Start() error {
addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil {
return err
}
listenString := fmt.Sprintf("[::]:%v", addr.Port)
lc := net.ListenConfig{
Control: m.multicastReuse,
}
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
if err != nil {
return err
}
m.sock = ipv6.NewPacketConn(conn)
if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil {
// Windows can't set this flag, so we need to handle it in other ways
}
m.isOpen = true
go m.multicastStarted()
go m.listen()
go m.announce()
return nil
}
// Stop is not implemented for multicast yet.
func (m *Multicast) Stop() error {
m.isOpen = false
m.sock.Close()
return nil
}
// UpdateConfig updates the multicast module with the provided config.NodeConfig
// and then signals the various module goroutines to reconfigure themselves if
// needed.
func (m *Multicast) UpdateConfig(config *config.NodeConfig) {
m.log.Debugln("Reloading multicast configuration...")
m.config.Replace(*config)
m.log.Infoln("Multicast configuration reloaded successfully")
}
// GetInterfaces returns the currently known/enabled multicast interfaces. It is
// expected that UpdateInterfaces has been called at least once before calling
// this method.
func (m *Multicast) Interfaces() map[string]net.Interface {
interfaces := make(map[string]net.Interface)
// Get interface expressions from config
current := m.config.GetCurrent()
exprs := current.MulticastInterfaces
// Ask the system for network interfaces
allifaces, err := net.Interfaces()
if err != nil {
panic(err)
}
// Work out which interfaces to announce on
for _, iface := range allifaces {
if iface.Flags&net.FlagUp == 0 {
// Ignore interfaces that are down
continue
}
if iface.Flags&net.FlagMulticast == 0 {
// Ignore non-multicast interfaces
continue
}
if iface.Flags&net.FlagPointToPoint != 0 {
// Ignore point-to-point interfaces
continue
}
for _, expr := range exprs {
// Compile each regular expression
e, err := regexp.Compile(expr)
if err != nil {
panic(err)
}
// Does the interface match the regular expression? Store it if so
if e.MatchString(iface.Name) {
interfaces[iface.Name] = iface
}
}
}
return interfaces
}
func (m *Multicast) announce() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
}
destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
}
for {
interfaces := m.Interfaces()
// There might be interfaces that we configured listeners for but are no
// longer up - if that's the case then we should stop the listeners
for name, listener := range m.listeners {
// Prepare our stop function!
stop := func() {
listener.Stop <- true
delete(m.listeners, name)
m.log.Debugln("No longer multicasting on", name)
}
// If the interface is no longer visible on the system then stop the
// listener, as another one will be started further down
if _, ok := interfaces[name]; !ok {
stop()
continue
}
// It's possible that the link-local listener address has changed so if
// that is the case then we should clean up the interface listener
found := false
listenaddr, err := net.ResolveTCPAddr("tcp6", listener.Listener.Addr().String())
if err != nil {
stop()
continue
}
// Find the interface that matches the listener
if intf, err := net.InterfaceByName(name); err == nil {
if addrs, err := intf.Addrs(); err == nil {
// Loop through the addresses attached to that listener and see if any
// of them match the current address of the listener
for _, addr := range addrs {
if ip, _, err := net.ParseCIDR(addr.String()); err == nil {
// Does the interface address match our listener address?
if ip.Equal(listenaddr.IP) {
found = true
break
}
}
}
}
}
// If the address has not been found on the adapter then we should stop
// and clean up the TCP listener. A new one will be created below if a
// suitable link-local address is found
if !found {
stop()
}
}
// Now that we have a list of valid interfaces from the operating system,
// we can start checking if we can send multicasts on them
for _, iface := range interfaces {
// Find interface addresses
addrs, err := iface.Addrs()
if err != nil {
panic(err)
}
for _, addr := range addrs {
addrIP, _, _ := net.ParseCIDR(addr.String())
// Ignore IPv4 addresses
if addrIP.To4() != nil {
continue
}
// Ignore non-link-local addresses
if !addrIP.IsLinkLocalUnicast() {
continue
}
// Join the multicast group
m.sock.JoinGroup(&iface, groupAddr)
// Try and see if we already have a TCP listener for this interface
var listener *yggdrasil.TcpListener
if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil {
// No listener was found - let's create one
listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort)
if li, err := m.core.ListenTCP(listenaddr); err == nil {
m.log.Debugln("Started multicasting on", iface.Name)
// Store the listener so that we can stop it later if needed
m.listeners[iface.Name] = li
listener = li
} else {
m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
}
} else {
// An existing listener was found
listener = m.listeners[iface.Name]
}
// Make sure nothing above failed for some reason
if listener == nil {
continue
}
// Get the listener details and construct the multicast beacon
lladdr := listener.Listener.Addr().String()
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
a.Zone = ""
destAddr.Zone = iface.Name
msg := []byte(a.String())
m.sock.WriteTo(msg, nil, destAddr)
}
break
}
}
time.Sleep(time.Second * 15)
}
}
func (m *Multicast) listen() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
}
bs := make([]byte, 2048)
for {
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil {
if !m.isOpen {
return
}
panic(err)
}
if rcm != nil {
// Windows can't set the flag needed to return a non-nil value here
// So only make these checks if we get something useful back
// TODO? Skip them always, I'm not sure if they're really needed...
if !rcm.Dst.IsLinkLocalMulticast() {
continue
}
if !rcm.Dst.Equal(groupAddr.IP) {
continue
}
}
anAddr := string(bs[:nBytes])
addr, err := net.ResolveTCPAddr("tcp6", anAddr)
if err != nil {
continue
}
from := fromAddr.(*net.UDPAddr)
if addr.IP.String() != from.IP.String() {
continue
}
if _, ok := m.Interfaces()[from.Zone]; ok {
addr.Zone = ""
if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil {
m.log.Debugln("Call from multicast failed:", err)
}
}
}
}

View File

@@ -0,0 +1,72 @@
// +build darwin
package multicast
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#import <Foundation/Foundation.h>
NSNetServiceBrowser *serviceBrowser;
void StartAWDLBrowsing() {
if (serviceBrowser == nil) {
serviceBrowser = [[NSNetServiceBrowser alloc] init];
serviceBrowser.includesPeerToPeer = YES;
}
[serviceBrowser searchForServicesOfType:@"_yggdrasil._tcp" inDomain:@""];
}
void StopAWDLBrowsing() {
if (serviceBrowser == nil) {
return;
}
[serviceBrowser stop];
}
*/
import "C"
import (
"syscall"
"time"
"golang.org/x/sys/unix"
)
var awdlGoroutineStarted bool
func (m *Multicast) multicastStarted() {
if awdlGoroutineStarted {
return
}
awdlGoroutineStarted = true
for {
C.StopAWDLBrowsing()
for intf := range m.Interfaces() {
if intf == "awdl0" {
m.log.Infoln("Multicast discovery is using AWDL discovery")
C.StartAWDLBrowsing()
break
}
}
time.Sleep(time.Minute)
}
}
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseport error
var recvanyif error
control = c.Control(func(fd uintptr) {
reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
// sys/socket.h: #define SO_RECV_ANYIF 0x1104
recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
})
switch {
case reuseport != nil:
return reuseport
case recvanyif != nil:
return recvanyif
default:
return control
}
}

View File

@@ -0,0 +1,13 @@
// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows
package multicast
import "syscall"
func (m *Multicast) multicastStarted() {
}
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
return nil
}

View File

@@ -1,11 +1,15 @@
// +build linux darwin netbsd freebsd openbsd dragonflybsd
// +build linux netbsd freebsd openbsd dragonflybsd
package yggdrasil
package multicast
import "syscall"
import "golang.org/x/sys/unix"
func multicastReuse(network string, address string, c syscall.RawConn) error {
func (m *Multicast) multicastStarted() {
}
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseport error

View File

@@ -1,11 +1,15 @@
// +build windows
package yggdrasil
package multicast
import "syscall"
import "golang.org/x/sys/windows"
func multicastReuse(network string, address string, c syscall.RawConn) error {
func (m *Multicast) multicastStarted() {
}
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseaddr error

119
src/tuntap/admin.go Normal file
View File

@@ -0,0 +1,119 @@
package tuntap
import (
"encoding/hex"
"errors"
"fmt"
"net"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
)
func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) {
defer func() {
if err := recover(); err != nil {
r = admin.Info{"none": admin.Info{}}
e = nil
}
}()
return admin.Info{
t.iface.Name(): admin.Info{
"tap_mode": t.iface.IsTAP(),
"mtu": t.mtu,
},
}, nil
})
/*
// TODO: rewrite this as I'm fairly sure it doesn't work right on many
// platforms anyway, but it may require changes to Water
a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) {
// Set sane defaults
iftapmode := defaults.GetDefaults().DefaultIfTAPMode
ifmtu := defaults.GetDefaults().DefaultIfMTU
// Has TAP mode been specified?
if tap, ok := in["tap_mode"]; ok {
iftapmode = tap.(bool)
}
// Check we have enough params for MTU
if mtu, ok := in["mtu"]; ok {
if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
ifmtu = int(in["mtu"].(float64))
}
}
// Start the TUN adapter
if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil {
return Info{}, errors.New("Failed to configure adapter")
} else {
return Info{
a.core.router.tun.iface.Name(): Info{
"tap_mode": a.core.router.tun.iface.IsTAP(),
"mtu": ifmtu,
},
}, nil
}
})
*/
a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) {
return admin.Info{"enabled": t.ckr.isEnabled()}, nil
})
a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) {
enabled := false
if e, ok := in["enabled"].(bool); ok {
enabled = e
}
t.ckr.setEnabled(enabled)
return admin.Info{"enabled": enabled}, nil
})
a.AddHandler("addLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.addLocalSubnet(in["subnet"].(string)); err == nil {
return admin.Info{"added": []string{in["subnet"].(string)}}, nil
} else {
return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet")
}
})
a.AddHandler("addRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.addRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
} else {
return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route")
}
})
a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) {
var subnets []string
getSourceSubnets := func(snets []net.IPNet) {
for _, subnet := range snets {
subnets = append(subnets, subnet.String())
}
}
getSourceSubnets(t.ckr.ipv4locals)
getSourceSubnets(t.ckr.ipv6locals)
return admin.Info{"source_subnets": subnets}, nil
})
a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) {
routes := make(admin.Info)
getRoutes := func(ckrs []cryptokey_route) {
for _, ckr := range ckrs {
routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:])
}
}
getRoutes(t.ckr.ipv4remotes)
getRoutes(t.ckr.ipv6remotes)
return admin.Info{"routes": routes}, nil
})
a.AddHandler("removeLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.removeLocalSubnet(in["subnet"].(string)); err == nil {
return admin.Info{"removed": []string{in["subnet"].(string)}}, nil
} else {
return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet")
}
})
a.AddHandler("removeRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
if err := t.ckr.removeRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
} else {
return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route")
}
})
}

View File

@@ -1,4 +1,4 @@
package yggdrasil
package tuntap
import (
"bytes"
@@ -7,6 +7,8 @@ import (
"fmt"
"net"
"sort"
"sync"
"sync/atomic"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
@@ -16,14 +18,18 @@ import (
// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil.
type cryptokey struct {
core *Core
enabled bool
ipv4routes []cryptokey_route
ipv6routes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route
ipv6cache map[address.Address]cryptokey_route
ipv4sources []net.IPNet
ipv6sources []net.IPNet
tun *TunAdapter
enabled atomic.Value // bool
reconfigure chan chan error
ipv4remotes []cryptokey_route
ipv6remotes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route
ipv6cache map[address.Address]cryptokey_route
ipv4locals []net.IPNet
ipv6locals []net.IPNet
mutexremotes sync.RWMutex
mutexcaches sync.RWMutex
mutexlocals sync.RWMutex
}
type cryptokey_route struct {
@@ -32,54 +38,111 @@ type cryptokey_route struct {
}
// Initialise crypto-key routing. This must be done before any other CKR calls.
func (c *cryptokey) init(core *Core) {
c.core = core
c.ipv4routes = make([]cryptokey_route, 0)
c.ipv6routes = make([]cryptokey_route, 0)
func (c *cryptokey) init(tun *TunAdapter) {
c.tun = tun
c.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-c.reconfigure
e <- nil
}
}()
c.tun.log.Debugln("Configuring CKR...")
if err := c.configure(); err != nil {
c.tun.log.Errorln("CKR configuration failed:", err)
} else {
c.tun.log.Debugln("CKR configured")
}
}
// Configure the CKR routes - this must only ever be called from the router
// goroutine, e.g. through router.doAdmin
func (c *cryptokey) configure() error {
current := c.tun.config.GetCurrent()
// Set enabled/disabled state
c.setEnabled(current.TunnelRouting.Enable)
// Clear out existing routes
c.mutexremotes.Lock()
c.ipv6remotes = make([]cryptokey_route, 0)
c.ipv4remotes = make([]cryptokey_route, 0)
c.mutexremotes.Unlock()
// Add IPv6 routes
for ipv6, pubkey := range current.TunnelRouting.IPv6RemoteSubnets {
if err := c.addRemoteSubnet(ipv6, pubkey); err != nil {
return err
}
}
// Add IPv4 routes
for ipv4, pubkey := range current.TunnelRouting.IPv4RemoteSubnets {
if err := c.addRemoteSubnet(ipv4, pubkey); err != nil {
return err
}
}
// Clear out existing sources
c.mutexlocals.Lock()
c.ipv6locals = make([]net.IPNet, 0)
c.ipv4locals = make([]net.IPNet, 0)
c.mutexlocals.Unlock()
// Add IPv6 sources
c.ipv6locals = make([]net.IPNet, 0)
for _, source := range current.TunnelRouting.IPv6LocalSubnets {
if err := c.addLocalSubnet(source); err != nil {
return err
}
}
// Add IPv4 sources
c.ipv4locals = make([]net.IPNet, 0)
for _, source := range current.TunnelRouting.IPv4LocalSubnets {
if err := c.addLocalSubnet(source); err != nil {
return err
}
}
// Wipe the caches
c.mutexcaches.Lock()
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
c.ipv4sources = make([]net.IPNet, 0)
c.ipv6sources = make([]net.IPNet, 0)
c.mutexcaches.Unlock()
return nil
}
// Enable or disable crypto-key routing.
func (c *cryptokey) setEnabled(enabled bool) {
c.enabled = enabled
c.enabled.Store(enabled)
}
// Check if crypto-key routing is enabled.
func (c *cryptokey) isEnabled() bool {
return c.enabled
enabled, ok := c.enabled.Load().(bool)
return ok && enabled
}
// Check whether the given address (with the address length specified in bytes)
// matches either the current node's address, the node's routed subnet or the
// list of subnets specified in IPv4Sources/IPv6Sources.
func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
ip := net.IP(addr[:addrlen])
if addrlen == net.IPv6len {
// Does this match our node's address?
if bytes.Equal(addr[:16], c.core.router.addr[:16]) {
return true
}
// Does this match our node's subnet?
if bytes.Equal(addr[:8], c.core.router.subnet[:8]) {
return true
}
}
// list of subnets specified in ipv4locals/ipv6locals.
func (c *cryptokey) isValidLocalAddress(addr address.Address, addrlen int) bool {
c.mutexlocals.RLock()
defer c.mutexlocals.RUnlock()
// Does it match a configured CKR source?
if c.isEnabled() {
ip := net.IP(addr[:addrlen])
// Build our references to the routing sources
var routingsources *[]net.IPNet
// Check if the prefix is IPv4 or IPv6
if addrlen == net.IPv6len {
routingsources = &c.ipv6sources
routingsources = &c.ipv6locals
} else if addrlen == net.IPv4len {
routingsources = &c.ipv4sources
routingsources = &c.ipv4locals
} else {
return false
}
@@ -97,7 +160,10 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
// Adds a source subnet, which allows traffic with these source addresses to
// be tunnelled using crypto-key routing.
func (c *cryptokey) addSourceSubnet(cidr string) error {
func (c *cryptokey) addLocalSubnet(cidr string) error {
c.mutexlocals.Lock()
defer c.mutexlocals.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -112,9 +178,9 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
// Check if the prefix is IPv4 or IPv6
if prefixsize == net.IPv6len*8 {
routingsources = &c.ipv6sources
routingsources = &c.ipv6locals
} else if prefixsize == net.IPv4len*8 {
routingsources = &c.ipv4sources
routingsources = &c.ipv4locals
} else {
return errors.New("Unexpected prefix size")
}
@@ -128,13 +194,18 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
// Add the source subnet
*routingsources = append(*routingsources, *ipnet)
c.core.log.Println("Added CKR source subnet", cidr)
c.tun.log.Infoln("Added CKR source subnet", cidr)
return nil
}
// Adds a destination route for the given CIDR to be tunnelled to the node
// with the given BoxPubKey.
func (c *cryptokey) addRoute(cidr string, dest string) error {
func (c *cryptokey) addRemoteSubnet(cidr string, dest string) error {
c.mutexremotes.Lock()
c.mutexcaches.Lock()
defer c.mutexremotes.Unlock()
defer c.mutexcaches.Unlock()
// Is the CIDR we've been given valid?
ipaddr, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -150,10 +221,10 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
// Check if the prefix is IPv4 or IPv6
if prefixsize == net.IPv6len*8 {
routingtable = &c.ipv6routes
routingtable = &c.ipv6remotes
routingcache = &c.ipv6cache
} else if prefixsize == net.IPv4len*8 {
routingtable = &c.ipv4routes
routingtable = &c.ipv4remotes
routingcache = &c.ipv4cache
} else {
return errors.New("Unexpected prefix size")
@@ -200,7 +271,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
delete(*routingcache, k)
}
c.core.log.Println("Added CKR destination subnet", cidr)
c.tun.log.Infoln("Added CKR destination subnet", cidr)
return nil
}
}
@@ -209,6 +280,8 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
// length specified in bytes) from the crypto-key routing table. An error is
// returned if the address is not suitable or no route was found.
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) {
c.mutexcaches.RLock()
// Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking
if addr.IsValid() {
@@ -221,10 +294,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Check if the prefix is IPv4 or IPv6
if addrlen == net.IPv6len {
routingtable = &c.ipv6routes
routingcache = &c.ipv6cache
} else if addrlen == net.IPv4len {
routingtable = &c.ipv4routes
routingcache = &c.ipv4cache
} else {
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
@@ -232,18 +303,36 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Check if there's a cache entry for this addr
if route, ok := (*routingcache)[addr]; ok {
c.mutexcaches.RUnlock()
return route.destination, nil
}
c.mutexcaches.RUnlock()
c.mutexremotes.RLock()
defer c.mutexremotes.RUnlock()
// Check if the prefix is IPv4 or IPv6
if addrlen == net.IPv6len {
routingtable = &c.ipv6remotes
} else if addrlen == net.IPv4len {
routingtable = &c.ipv4remotes
} else {
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
}
// No cache was found - start by converting the address into a net.IP
ip := make(net.IP, addrlen)
copy(ip[:addrlen], addr[:])
// Check if we have a route. At this point c.ipv6routes should be
// Check if we have a route. At this point c.ipv6remotes should be
// pre-sorted so that the most specific routes are first
for _, route := range *routingtable {
// Does this subnet match the given IP?
if route.subnet.Contains(ip) {
c.mutexcaches.Lock()
defer c.mutexcaches.Unlock()
// Check if the routing cache is above a certain size, if it is evict
// a random entry so we can make room for this one. We take advantage
// of the fact that the iteration order is random here
@@ -263,12 +352,15 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
}
// No route was found if we got to this point
return crypto.BoxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String()))
return crypto.BoxPubKey{}, fmt.Errorf("no route to %s", ip.String())
}
// Removes a source subnet, which allows traffic with these source addresses to
// be tunnelled using crypto-key routing.
func (c *cryptokey) removeSourceSubnet(cidr string) error {
func (c *cryptokey) removeLocalSubnet(cidr string) error {
c.mutexlocals.Lock()
defer c.mutexlocals.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -283,9 +375,9 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
// Check if the prefix is IPv4 or IPv6
if prefixsize == net.IPv6len*8 {
routingsources = &c.ipv6sources
routingsources = &c.ipv6locals
} else if prefixsize == net.IPv4len*8 {
routingsources = &c.ipv4sources
routingsources = &c.ipv4locals
} else {
return errors.New("Unexpected prefix size")
}
@@ -294,7 +386,7 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
for idx, subnet := range *routingsources {
if subnet.String() == ipnet.String() {
*routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...)
c.core.log.Println("Removed CKR source subnet", cidr)
c.tun.log.Infoln("Removed CKR source subnet", cidr)
return nil
}
}
@@ -303,7 +395,12 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
// Removes a destination route for the given CIDR to be tunnelled to the node
// with the given BoxPubKey.
func (c *cryptokey) removeRoute(cidr string, dest string) error {
func (c *cryptokey) removeRemoteSubnet(cidr string, dest string) error {
c.mutexremotes.Lock()
c.mutexcaches.Lock()
defer c.mutexremotes.Unlock()
defer c.mutexcaches.Unlock()
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -319,10 +416,10 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
// Check if the prefix is IPv4 or IPv6
if prefixsize == net.IPv6len*8 {
routingtable = &c.ipv6routes
routingtable = &c.ipv6remotes
routingcache = &c.ipv6cache
} else if prefixsize == net.IPv4len*8 {
routingtable = &c.ipv4routes
routingtable = &c.ipv4remotes
routingcache = &c.ipv4cache
} else {
return errors.New("Unexpected prefix size")
@@ -343,7 +440,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
for k := range *routingcache {
delete(*routingcache, k)
}
c.core.log.Printf("Removed CKR destination subnet %s via %s\n", cidr, dest)
c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest)
return nil
}
}

273
src/tuntap/conn.go Normal file
View File

@@ -0,0 +1,273 @@
package tuntap
import (
"bytes"
"errors"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
)
const tunConnTimeout = 2 * time.Minute
type tunConn struct {
tun *TunAdapter
conn *yggdrasil.Conn
addr address.Address
snet address.Subnet
send chan []byte
stop chan struct{}
alive chan struct{}
}
func (s *tunConn) close() {
s.tun.mutex.Lock()
defer s.tun.mutex.Unlock()
s._close_nomutex()
}
func (s *tunConn) _close_nomutex() {
s.conn.Close()
delete(s.tun.addrToConn, s.addr)
delete(s.tun.subnetToConn, s.snet)
func() {
defer func() { recover() }()
close(s.stop) // Closes reader/writer goroutines
}()
func() {
defer func() { recover() }()
close(s.alive) // Closes timeout goroutine
}()
}
func (s *tunConn) reader() (err error) {
select {
case _, ok := <-s.stop:
if !ok {
return errors.New("session was already closed")
}
default:
}
s.tun.log.Debugln("Starting conn reader for", s.conn.String())
defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String())
for {
select {
case <-s.stop:
return nil
default:
}
var bs []byte
if bs, err = s.conn.ReadNoCopy(); err != nil {
if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() {
if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn read debug:", err)
} else {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err)
}
return e
}
} else if len(bs) > 0 {
ipv4 := len(bs) > 20 && bs[0]&0xf0 == 0x40
ipv6 := len(bs) > 40 && bs[0]&0xf0 == 0x60
isCGA := true
// Check source addresses
switch {
case ipv6 && bs[8] == 0x02 && bytes.Equal(s.addr[:16], bs[8:24]): // source
case ipv6 && bs[8] == 0x03 && bytes.Equal(s.snet[:8], bs[8:16]): // source
default:
isCGA = false
}
// Check destiantion addresses
switch {
case ipv6 && bs[24] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[24:40]): // destination
case ipv6 && bs[24] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[24:32]): // destination
default:
isCGA = false
}
// Decide how to handle the packet
var skip bool
switch {
case isCGA: // Allowed
case s.tun.ckr.isEnabled() && (ipv4 || ipv6):
var srcAddr address.Address
var dstAddr address.Address
var addrlen int
if ipv4 {
copy(srcAddr[:], bs[12:16])
copy(dstAddr[:], bs[16:20])
addrlen = 4
}
if ipv6 {
copy(srcAddr[:], bs[8:24])
copy(dstAddr[:], bs[24:40])
addrlen = 16
}
if !s.tun.ckr.isValidLocalAddress(dstAddr, addrlen) {
// The destination address isn't in our CKR allowed range
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(srcAddr, addrlen); err == nil {
srcNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *srcNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID
skip = true
}
} else {
// We have no CKR route for this source address
skip = true
}
default:
skip = true
}
if skip {
util.PutBytes(bs)
continue
}
s.tun.send <- bs
s.stillAlive()
} else {
util.PutBytes(bs)
}
}
}
func (s *tunConn) writer() error {
select {
case _, ok := <-s.stop:
if !ok {
return errors.New("session was already closed")
}
default:
}
s.tun.log.Debugln("Starting conn writer for", s.conn.String())
defer s.tun.log.Debugln("Stopping conn writer for", s.conn.String())
for {
select {
case <-s.stop:
return nil
case bs, ok := <-s.send:
if !ok {
return errors.New("send closed")
}
v4 := len(bs) > 20 && bs[0]&0xf0 == 0x40
v6 := len(bs) > 40 && bs[0]&0xf0 == 0x60
isCGA := true
// Check source addresses
switch {
case v6 && bs[8] == 0x02 && bytes.Equal(s.tun.addr[:16], bs[8:24]): // source
case v6 && bs[8] == 0x03 && bytes.Equal(s.tun.subnet[:8], bs[8:16]): // source
default:
isCGA = false
}
// Check destiantion addresses
switch {
case v6 && bs[24] == 0x02 && bytes.Equal(s.addr[:16], bs[24:40]): // destination
case v6 && bs[24] == 0x03 && bytes.Equal(s.snet[:8], bs[24:32]): // destination
default:
isCGA = false
}
// Decide how to handle the packet
var skip bool
switch {
case isCGA: // Allowed
case s.tun.ckr.isEnabled() && (v4 || v6):
var srcAddr address.Address
var dstAddr address.Address
var addrlen int
if v4 {
copy(srcAddr[:], bs[12:16])
copy(dstAddr[:], bs[16:20])
addrlen = 4
}
if v6 {
copy(srcAddr[:], bs[8:24])
copy(dstAddr[:], bs[24:40])
addrlen = 16
}
if !s.tun.ckr.isValidLocalAddress(srcAddr, addrlen) {
// The source address isn't in our CKR allowed range
skip = true
} else if key, err := s.tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
dstNodeID := crypto.GetNodeID(&key)
if s.conn.RemoteAddr() == *dstNodeID {
// This is the one allowed CKR case, where source and destination addresses are both good
} else {
// The CKR key associated with this address doesn't match the sender's NodeID
skip = true
}
} else {
// We have no CKR route for this destination address... why do we have the packet in the first place?
skip = true
}
default:
skip = true
}
if skip {
util.PutBytes(bs)
continue
}
msg := yggdrasil.FlowKeyMessage{
FlowKey: util.GetFlowKey(bs),
Message: bs,
}
if err := s.conn.WriteNoCopy(msg); err != nil {
if e, eok := err.(yggdrasil.ConnError); !eok {
if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err)
} else {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err)
}
} else if e.PacketTooBig() {
// TODO: This currently isn't aware of IPv4 for CKR
ptb := &icmp.PacketTooBig{
MTU: int(e.PacketMaximumSize()),
Data: bs[:900],
}
if packet, err := CreateICMPv6(bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil {
s.tun.send <- packet
}
} else {
if e.Closed() {
s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err)
} else {
s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err)
}
}
} else {
s.stillAlive()
}
}
}
}
func (s *tunConn) stillAlive() {
defer func() { recover() }()
select {
case s.alive <- struct{}{}:
default:
}
}
func (s *tunConn) checkForTimeouts() error {
timer := time.NewTimer(tunConnTimeout)
defer util.TimerStop(timer)
defer s.close()
for {
select {
case _, ok := <-s.alive:
if !ok {
return errors.New("connection closed")
}
util.TimerStop(timer)
timer.Reset(tunConnTimeout)
case <-timer.C:
return errors.New("timed out")
}
}
}

View File

@@ -1,4 +1,4 @@
package yggdrasil
package tuntap
// The ICMPv6 module implements functions to easily create ICMPv6
// packets. These functions, when mixed with the built-in Go IPv6
@@ -13,6 +13,7 @@ import (
"encoding/binary"
"errors"
"net"
"sync"
"time"
"golang.org/x/net/icmp"
@@ -21,19 +22,18 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type macAddress [6]byte
const len_ETHER = 14
type icmpv6 struct {
tun *tunAdapter
mylladdr net.IP
mymac macAddress
peermacs map[address.Address]neighbor
type ICMPv6 struct {
tun *TunAdapter
mylladdr net.IP
mymac net.HardwareAddr
peermacs map[address.Address]neighbor
peermacsmutex sync.RWMutex
}
type neighbor struct {
mac macAddress
mac net.HardwareAddr
learned bool
lastadvertisement time.Time
lastsolicitation time.Time
@@ -59,56 +59,60 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) {
// Initialises the ICMPv6 module by assigning our link-local IPv6 address and
// our MAC address. ICMPv6 messages will always appear to originate from these
// addresses.
func (i *icmpv6) init(t *tunAdapter) {
func (i *ICMPv6) Init(t *TunAdapter) {
i.tun = t
i.peermacsmutex.Lock()
i.peermacs = make(map[address.Address]neighbor)
i.peermacsmutex.Unlock()
// Our MAC address and link-local address
i.mymac = macAddress{
i.mymac = net.HardwareAddr{
0x02, 0x00, 0x00, 0x00, 0x00, 0x02}
i.mylladdr = net.IP{
0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}
copy(i.mymac[:], i.tun.core.router.addr[:])
copy(i.mylladdr[9:], i.tun.core.router.addr[1:])
copy(i.mymac[:], i.tun.addr[:])
copy(i.mylladdr[9:], i.tun.addr[1:])
}
// Parses an incoming ICMPv6 packet. The packet provided may be either an
// ethernet frame containing an IP packet, or the IP packet alone. This is
// determined by whether the TUN/TAP adapter is running in TUN (layer 3) or
// TAP (layer 2) mode.
func (i *icmpv6) parse_packet(datain []byte) {
// TAP (layer 2) mode. Returns an error condition which is nil if the ICMPv6
// module handled the packet or contains the error if not.
func (i *ICMPv6) ParsePacket(datain []byte) error {
var response []byte
var err error
// Parse the frame/packet
if i.tun.iface.IsTAP() {
response, err = i.parse_packet_tap(datain)
if i.tun.IsTAP() {
response, err = i.UnmarshalPacketL2(datain)
} else {
response, err = i.parse_packet_tun(datain, nil)
response, err = i.UnmarshalPacket(datain, nil)
}
if err != nil {
return
return err
}
// Write the packet to TUN/TAP
i.tun.iface.Write(response)
return nil
}
// Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off
// the IP packet to the parse_packet_tun function for further processing.
// the IP packet to the ParsePacket function for further processing.
// A response buffer is also created for the response message, also complete
// with ethernet headers.
func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) {
// Ignore non-IPv6 frames
if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) {
return nil, nil
return nil, errors.New("Ignoring non-IPv6 frame")
}
// Hand over to parse_packet_tun to interpret the IPv6 packet
// Hand over to ParsePacket to interpret the IPv6 packet
mac := datain[6:12]
ipv6packet, err := i.parse_packet_tun(datain[len_ETHER:], &mac)
ipv6packet, err := i.UnmarshalPacket(datain[len_ETHER:], &mac)
if err != nil {
return nil, err
}
@@ -130,7 +134,7 @@ func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
// sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the
// ICMPv6 message match a known expected type. The relevant handler function
// is then called and a response packet may be returned.
func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error) {
func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) {
// Parse the IPv6 packet headers
ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen])
if err != nil {
@@ -139,12 +143,12 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Check if the packet is IPv6
if ipv6Header.Version != ipv6.Version {
return nil, err
return nil, errors.New("Ignoring non-IPv6 packet")
}
// Check if the packet is ICMPv6
if ipv6Header.NextHeader != 58 {
return nil, err
return nil, errors.New("Ignoring non-ICMPv6 packet")
}
// Parse the ICMPv6 message contents
@@ -156,33 +160,55 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Check for a supported message type
switch icmpv6Header.Type {
case ipv6.ICMPTypeNeighborSolicitation:
response, err := i.handle_ndp(datain[ipv6.HeaderLen:])
if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Solicitation in TUN mode")
}
response, err := i.HandleNDP(datain[ipv6.HeaderLen:])
if err == nil {
// Create our ICMPv6 response
responsePacket, err := i.create_icmpv6_tun(
responsePacket, err := CreateICMPv6(
ipv6Header.Src, i.mylladdr,
ipv6.ICMPTypeNeighborAdvertisement, 0,
&icmp.DefaultMessageBody{Data: response})
if err != nil {
return nil, err
}
// Send it back
return responsePacket, nil
} else {
return nil, err
}
case ipv6.ICMPTypeNeighborAdvertisement:
if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Advertisement in TUN mode")
}
if datamac != nil {
var addr address.Address
var mac macAddress
var target address.Address
mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(addr[:], ipv6Header.Src[:])
copy(target[:], datain[48:64])
copy(mac[:], (*datamac)[:])
neighbor := i.peermacs[addr]
i.peermacsmutex.Lock()
neighbor := i.peermacs[target]
neighbor.mac = mac
neighbor.learned = true
neighbor.lastadvertisement = time.Now()
i.peermacs[addr] = neighbor
i.peermacs[target] = neighbor
i.peermacsmutex.Unlock()
i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String())
/*
i.tun.log.Debugln("Peer MAC table:")
i.peermacsmutex.RLock()
for t, n := range i.peermacs {
if n.learned {
i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String())
} else {
i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet")
}
}
i.peermacsmutex.RUnlock()
*/
}
return nil, errors.New("No response needed")
}
@@ -193,9 +219,9 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with ethernet and IP headers, which can be written
// directly to a TAP adapter.
func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Pass through to create_icmpv6_tun
ipv6packet, err := i.create_icmpv6_tun(dst, src, mtype, mcode, mbody)
func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Pass through to CreateICMPv6
ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody)
if err != nil {
return nil, err
}
@@ -215,9 +241,9 @@ func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mt
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with IP headers only, which can be written directly to
// a TUN adapter, or called directly by the create_icmpv6_tap function when
// a TUN adapter, or called directly by the CreateICMPv6L2 function when
// generating a message for TAP adapters.
func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
// Create the ICMPv6 message
icmpMessage := icmp.Message{
Type: mtype,
@@ -256,13 +282,54 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType,
return responsePacket, nil
}
func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
func (i *ICMPv6) Solicit(addr address.Address) {
retries := 5
for retries > 0 {
retries--
i.peermacsmutex.RLock()
if n, ok := i.peermacs[addr]; ok && n.learned {
i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String())
i.peermacsmutex.RUnlock()
return
}
i.peermacsmutex.RUnlock()
i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String())
i.peermacsmutex.Lock()
if n, ok := i.peermacs[addr]; !ok {
i.peermacs[addr] = neighbor{
lastsolicitation: time.Now(),
}
} else {
n.lastsolicitation = time.Now()
}
i.peermacsmutex.Unlock()
request, err := i.createNDPL2(addr)
if err != nil {
panic(err)
}
if _, err := i.tun.iface.Write(request); err != nil {
panic(err)
}
i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String())
time.Sleep(time.Second)
}
}
func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) {
i.peermacsmutex.RLock()
defer i.peermacsmutex.RUnlock()
n, ok := i.peermacs[addr]
return n, ok
}
func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) {
// Create the ND payload
var payload [28]byte
copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00})
copy(payload[4:20], dst[:])
copy(payload[20:22], []byte{0x01, 0x01})
copy(payload[22:28], i.mymac[:6])
copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags
copy(payload[4:20], dst[:]) // Destination
copy(payload[20:22], []byte{0x01, 0x01}) // Type & length
copy(payload[22:28], i.mymac[:6]) // Link layer address
// Create the ICMPv6 solicited-node address
var dstaddr address.Address
@@ -273,21 +340,18 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
copy(dstaddr[13:], dst[13:16])
// Create the multicast MAC
var dstmac macAddress
dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(dstmac[:2], []byte{0x33, 0x33})
copy(dstmac[2:6], dstaddr[12:16])
// Create the ND request
requestPacket, err := i.create_icmpv6_tap(
requestPacket, err := i.CreateICMPv6L2(
dstmac, dstaddr[:], i.mylladdr,
ipv6.ICMPTypeNeighborSolicitation, 0,
&icmp.DefaultMessageBody{Data: payload[:]})
if err != nil {
return nil, err
}
neighbor := i.peermacs[dstaddr]
neighbor.lastsolicitation = time.Now()
i.peermacs[dstaddr] = neighbor
return requestPacket, nil
}
@@ -296,7 +360,7 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
// when the host operating system generates an NDP request for any address in
// the fd00::/8 range, so that the operating system knows to route that traffic
// to the Yggdrasil TAP adapter.
func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) {
// Ignore NDP requests for anything outside of fd00::/8
var source address.Address
copy(source[:], in[8:])
@@ -311,10 +375,10 @@ func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
// Create our NDP message body response
body := make([]byte, 28)
binary.BigEndian.PutUint32(body[:4], uint32(0x20000000))
copy(body[4:20], in[8:24]) // Target address
body[20] = uint8(2)
body[21] = uint8(1)
binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags
copy(body[4:20], in[8:24]) // Target address
body[20] = uint8(2) // Type: Target link-layer address
body[21] = uint8(1) // Length: 1x address (8 bytes)
copy(body[22:28], i.mymac[:6])
// Send it back

272
src/tuntap/iface.go Normal file
View File

@@ -0,0 +1,272 @@
package tuntap
import (
"bytes"
"net"
"time"
"github.com/songgao/packets/ethernet"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
func (tun *TunAdapter) writer() error {
var w int
var err error
for {
b := <-tun.send
n := len(b)
if n == 0 {
continue
}
if tun.iface.IsTAP() {
sendndp := func(dstAddr address.Address) {
neigh, known := tun.icmpv6.getNeighbor(dstAddr)
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
if !known {
tun.icmpv6.Solicit(dstAddr)
}
}
peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
var dstAddr address.Address
var peerknown bool
if b[0]&0xf0 == 0x40 {
dstAddr = tun.addr
} else if b[0]&0xf0 == 0x60 {
if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) {
dstAddr = tun.addr
}
}
if neighbor, ok := tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned {
// If we've learned the MAC of a 300::/7 address, for example, or a CKR
// address, use the MAC address of that
peermac = neighbor.mac
peerknown = true
} else if neighbor, ok := tun.icmpv6.getNeighbor(tun.addr); ok && neighbor.learned {
// Otherwise send directly to the MAC address of the host if that's
// known instead
peermac = neighbor.mac
peerknown = true
} else {
// Nothing has been discovered, try to discover the destination
sendndp(tun.addr)
}
if peerknown {
var proto ethernet.Ethertype
switch {
case b[0]&0xf0 == 0x60:
proto = ethernet.IPv6
case b[0]&0xf0 == 0x40:
proto = ethernet.IPv4
}
var frame ethernet.Frame
frame.Prepare(
peermac[:6], // Destination MAC address
tun.icmpv6.mymac[:6], // Source MAC address
ethernet.NotTagged, // VLAN tagging
proto, // Ethertype
len(b)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n])
n += tun_ETHER_HEADER_LENGTH
w, err = tun.iface.Write(frame[:n])
} else {
tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet")
}
} else {
w, err = tun.iface.Write(b[:n])
util.PutBytes(b)
}
if err != nil {
if !tun.isOpen {
return err
}
tun.log.Errorln("TUN/TAP iface write error:", err)
continue
}
if w != n {
tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given")
continue
}
}
}
// Run in a separate goroutine by the reader
// Does all of the per-packet ICMP checks, passes packets to the right Conn worker
func (tun *TunAdapter) readerPacketHandler(ch chan []byte) {
for recvd := range ch {
// If it's a TAP adapter, update the buffer slice so that we no longer
// include the ethernet headers
offset := 0
if tun.iface.IsTAP() {
// Set our offset to beyond the ethernet headers
offset = tun_ETHER_HEADER_LENGTH
// Check first of all that we can go beyond the ethernet headers
if len(recvd) <= offset {
continue
}
}
// Offset the buffer from now on so that we can ignore ethernet frames if
// they are present
bs := recvd[offset:]
// If we detect an ICMP packet then hand it to the ICMPv6 module
if bs[6] == 58 {
// Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full
// Ethernet frame rather than just the IPv6 packet as this is needed for
// NDP to work correctly
if err := tun.icmpv6.ParsePacket(recvd); err == nil {
// We acted on the packet in the ICMPv6 module so don't forward or do
// anything else with it
continue
}
}
if offset != 0 {
// Shift forward to avoid leaking bytes off the front of the slice when we eventually store it
bs = append(recvd[:0], bs...)
}
// From the IP header, work out what our source and destination addresses
// and node IDs are. We will need these in order to work out where to send
// the packet
var dstAddr address.Address
var dstSnet address.Subnet
var addrlen int
n := len(bs)
// Check the IP protocol - if it doesn't match then we drop the packet and
// do nothing with it
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized IPv6 header
if len(bs) < 40 {
continue
}
// Check the packet size
if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) {
continue
}
// IPv6 address
addrlen = 16
copy(dstAddr[:addrlen], bs[24:])
copy(dstSnet[:addrlen/2], bs[24:])
} else if bs[0]&0xf0 == 0x40 {
// Check if we have a fully-sized IPv4 header
if len(bs) < 20 {
continue
}
// Check the packet size
if n != 256*int(bs[2])+int(bs[3]) {
continue
}
// IPv4 address
addrlen = 4
copy(dstAddr[:addrlen], bs[16:])
} else {
// Unknown address length or protocol, so drop the packet and ignore it
tun.log.Traceln("Unknown packet type, dropping")
continue
}
if tun.ckr.isEnabled() {
if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) {
if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
// A public key was found, get the node ID for the search
dstNodeID := crypto.GetNodeID(&key)
dstAddr = *address.AddrForNodeID(dstNodeID)
dstSnet = *address.SubnetForNodeID(dstNodeID)
addrlen = 16
}
}
}
if addrlen != 16 || (!dstAddr.IsValid() && !dstSnet.IsValid()) {
// Couldn't find this node's ygg IP
continue
}
// Do we have an active connection for this node address?
var dstNodeID, dstNodeIDMask *crypto.NodeID
tun.mutex.RLock()
session, isIn := tun.addrToConn[dstAddr]
if !isIn || session == nil {
session, isIn = tun.subnetToConn[dstSnet]
if !isIn || session == nil {
// Neither an address nor a subnet mapping matched, therefore populate
// the node ID and mask to commence a search
if dstAddr.IsValid() {
dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask()
} else {
dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask()
}
}
}
tun.mutex.RUnlock()
// If we don't have a connection then we should open one
if !isIn || session == nil {
// Check we haven't been given empty node ID, really this shouldn't ever
// happen but just to be sure...
if dstNodeID == nil || dstNodeIDMask == nil {
panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen")
}
// Dial to the remote node
go func() {
// FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes
tun.mutex.Lock()
_, known := tun.dials[*dstNodeID]
tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs)
for len(tun.dials[*dstNodeID]) > 32 {
util.PutBytes(tun.dials[*dstNodeID][0])
tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:]
}
tun.mutex.Unlock()
if known {
return
}
var tc *tunConn
if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil {
// We've been given a connection so prepare the session wrapper
if tc, err = tun.wrap(conn); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP iface wrap:", err)
}
}
tun.mutex.Lock()
packets := tun.dials[*dstNodeID]
delete(tun.dials, *dstNodeID)
tun.mutex.Unlock()
if tc != nil {
for _, packet := range packets {
p := packet // Possibly required because of how range
tc.send <- p
}
}
}()
// While the dial is going on we can't do much else
// continuing this iteration - skip to the next one
continue
}
// If we have a connection now, try writing to it
if isIn && session != nil {
session.send <- bs
}
}
}
func (tun *TunAdapter) reader() error {
toWorker := make(chan []byte, 32)
defer close(toWorker)
go tun.readerPacketHandler(toWorker)
for {
// Get a slice to store the packet in
recvd := util.ResizeBytes(util.GetBytes(), 65535+tun_ETHER_HEADER_LENGTH)
// Wait for a packet to be delivered to us through the TUN/TAP adapter
n, err := tun.iface.Read(recvd)
if err != nil {
if !tun.isOpen {
return err
}
panic(err)
}
if n == 0 {
util.PutBytes(recvd)
continue
}
// Send the packet to the worker
toWorker <- recvd[:n]
}
}

265
src/tuntap/tun.go Normal file
View File

@@ -0,0 +1,265 @@
package tuntap
// This manages the tun driver to send/recv packets to/from applications
// TODO: Crypto-key routing support
// TODO: Set MTU of session properly
// TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery
// TODO: Connection timeouts (call Conn.Close() when we want to time out)
// TODO: Don't block in reader on writes that are pending searches
import (
"encoding/hex"
"errors"
"fmt"
"net"
"sync"
"github.com/gologme/log"
"github.com/yggdrasil-network/water"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
const tun_IPv6_HEADER_LENGTH = 40
const tun_ETHER_HEADER_LENGTH = 14
// TunAdapter represents a running TUN/TAP interface and extends the
// yggdrasil.Adapter type. In order to use the TUN/TAP adapter with Yggdrasil,
// you should pass this object to the yggdrasil.SetRouterAdapter() function
// before calling yggdrasil.Start().
type TunAdapter struct {
config *config.NodeState
log *log.Logger
reconfigure chan chan error
listener *yggdrasil.Listener
dialer *yggdrasil.Dialer
addr address.Address
subnet address.Subnet
ckr cryptokey
icmpv6 ICMPv6
mtu int
iface *water.Interface
send chan []byte
mutex sync.RWMutex // Protects the below
addrToConn map[address.Address]*tunConn
subnetToConn map[address.Subnet]*tunConn
dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes
isOpen bool
}
// Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults().
func getSupportedMTU(mtu int) int {
if mtu > defaults.GetDefaults().MaximumIfMTU {
return defaults.GetDefaults().MaximumIfMTU
}
return mtu
}
// Name returns the name of the adapter, e.g. "tun0". On Windows, this may
// return a canonical adapter name instead.
func (tun *TunAdapter) Name() string {
return tun.iface.Name()
}
// MTU gets the adapter's MTU. This can range between 1280 and 65535, although
// the maximum value is determined by your platform. The returned value will
// never exceed that of MaximumMTU().
func (tun *TunAdapter) MTU() int {
return getSupportedMTU(tun.mtu)
}
// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it
// is a TUN adapter (Layer 3).
func (tun *TunAdapter) IsTAP() bool {
return tun.iface.IsTAP()
}
// DefaultName gets the default TUN/TAP interface name for your platform.
func DefaultName() string {
return defaults.GetDefaults().DefaultIfName
}
// DefaultMTU gets the default TUN/TAP interface MTU for your platform. This can
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
func DefaultMTU() int {
return defaults.GetDefaults().DefaultIfMTU
}
// DefaultIsTAP returns true if the default adapter mode for the current
// platform is TAP (Layer 2) and returns false for TUN (Layer 3).
func DefaultIsTAP() bool {
return defaults.GetDefaults().DefaultIfTAPMode
}
// MaximumMTU returns the maximum supported TUN/TAP interface MTU for your
// platform. This can be as high as 65535, depending on platform, but is never
// lower than 1280.
func MaximumMTU() int {
return defaults.GetDefaults().MaximumIfMTU
}
// Init initialises the TUN/TAP module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere.
func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) {
tun.config = config
tun.log = log
tun.listener = listener
tun.dialer = dialer
tun.addrToConn = make(map[address.Address]*tunConn)
tun.subnetToConn = make(map[address.Subnet]*tunConn)
tun.dials = make(map[crypto.NodeID][][]byte)
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Start() error {
current := tun.config.GetCurrent()
if tun.config == nil || tun.listener == nil || tun.dialer == nil {
return errors.New("No configuration available to TUN/TAP")
}
var boxPub crypto.BoxPubKey
boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey)
if err != nil {
return err
}
copy(boxPub[:], boxPubHex)
nodeID := crypto.GetNodeID(&boxPub)
tun.addr = *address.AddrForNodeID(nodeID)
tun.subnet = *address.SubnetForNodeID(nodeID)
tun.mtu = current.IfMTU
ifname := current.IfName
iftapmode := current.IfTAPMode
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
if ifname != "none" {
if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil {
return err
}
}
if ifname == "none" || ifname == "dummy" {
tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy")
return nil
}
tun.mutex.Lock()
tun.isOpen = true
tun.send = make(chan []byte, 32) // TODO: is this a sensible value?
tun.reconfigure = make(chan chan error)
tun.mutex.Unlock()
go func() {
for {
e := <-tun.reconfigure
e <- nil
}
}()
go tun.handler()
go tun.reader()
go tun.writer()
tun.icmpv6.Init(tun)
if iftapmode {
go tun.icmpv6.Solicit(tun.addr)
}
tun.ckr.init(tun)
return nil
}
// Start the setup process for the TUN/TAP adapter. If successful, starts the
// read/write goroutines to handle packets on that interface.
func (tun *TunAdapter) Stop() error {
tun.isOpen = false
// TODO: we have nothing that cleanly stops all the various goroutines opened
// by TUN/TAP, e.g. readers/writers, sessions
tun.iface.Close()
return nil
}
// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig
// and then signals the various module goroutines to reconfigure themselves if
// needed.
func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) {
tun.log.Debugln("Reloading TUN/TAP configuration...")
tun.config.Replace(*config)
errors := 0
components := []chan chan error{
tun.reconfigure,
tun.ckr.reconfigure,
}
for _, component := range components {
response := make(chan error)
component <- response
if err := <-response; err != nil {
tun.log.Errorln(err)
errors++
}
}
if errors > 0 {
tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload")
} else {
tun.log.Infoln("TUN/TAP configuration reloaded successfully")
}
}
func (tun *TunAdapter) handler() error {
for {
// Accept the incoming connection
conn, err := tun.listener.Accept()
if err != nil {
tun.log.Errorln("TUN/TAP connection accept error:", err)
return err
}
if _, err := tun.wrap(conn); err != nil {
// Something went wrong when storing the connection, typically that
// something already exists for this address or subnet
tun.log.Debugln("TUN/TAP handler wrap:", err)
}
}
}
func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
// Prepare a session wrapper for the given connection
s := tunConn{
tun: tun,
conn: conn,
send: make(chan []byte, 32), // TODO: is this a sensible value?
stop: make(chan struct{}),
alive: make(chan struct{}, 1),
}
c = &s
// Get the remote address and subnet of the other side
remoteNodeID := conn.RemoteAddr()
s.addr = *address.AddrForNodeID(&remoteNodeID)
s.snet = *address.SubnetForNodeID(&remoteNodeID)
// Work out if this is already a destination we already know about
tun.mutex.Lock()
defer tun.mutex.Unlock()
atc, aok := tun.addrToConn[s.addr]
stc, sok := tun.subnetToConn[s.snet]
// If we know about a connection for this destination already then assume it
// is no longer valid and close it
if aok {
atc._close_nomutex()
err = errors.New("replaced connection for address")
} else if sok {
stc._close_nomutex()
err = errors.New("replaced connection for subnet")
}
// Save the session wrapper so that we can look it up quickly next time
// we receive a packet through the interface for this address
tun.addrToConn[s.addr] = &s
tun.subnetToConn[s.snet] = &s
// Start the connection goroutines
go s.reader()
go s.writer()
go s.checkForTimeouts()
// Return
return c, err
}

View File

@@ -1,6 +1,6 @@
// +build openbsd freebsd netbsd
package yggdrasil
package tuntap
import (
"encoding/binary"
@@ -77,7 +77,7 @@ type in6_ifreq_lifetime struct {
// a system socket and making syscalls to the kernel. This is not refined though
// and often doesn't work (if at all), therefore if a call fails, it resorts
// to calling "ifconfig" instead.
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if ifname[:4] == "auto" {
ifname = "/dev/tap0"
@@ -103,20 +103,20 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
return tun.setupAddress(addr)
}
func (tun *tunAdapter) setupAddress(addr string) error {
func (tun *TunAdapter) setupAddress(addr string) error {
var sfd int
var err error
// Create system socket
if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil {
tun.core.log.Printf("Create AF_INET socket failed: %v.", err)
tun.log.Printf("Create AF_INET socket failed: %v.", err)
return err
}
// Friendly output
tun.core.log.Printf("Interface name: %s", tun.iface.Name())
tun.core.log.Printf("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
// Create the MTU request
var ir in6_ifreq_mtu
@@ -126,15 +126,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// Set the MTU
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
tun.core.log.Printf("Error in SIOCSIFMTU: %v", errno)
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
// Fall back to ifconfig to set the MTU
cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu))
tun.core.log.Printf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Printf("SIOCSIFMTU fallback failed: %v.", err)
tun.core.log.Println(string(output))
tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}
@@ -155,15 +155,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// Set the interface address
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
err = errno
tun.core.log.Printf("Error in SIOCSIFADDR_IN6: %v", errno)
tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
// Fall back to ifconfig to set the address
cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr)
tun.core.log.Printf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Printf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
tun.core.log.Println(string(output))
tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}

View File

@@ -1,4 +1,6 @@
package yggdrasil
// +build !mobile
package tuntap
// The darwin platform specific tun parts
@@ -14,9 +16,9 @@ import (
)
// Configures the "utun" adapter with the correct IPv6 address and MTU.
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if iftapmode {
tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN")
tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
}
config := water.Config{DeviceType: water.TUN}
iface, err := water.New(config)
@@ -28,7 +30,12 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
return tun.setupAddress(addr)
}
const darwin_SIOCAIFADDR_IN6 = 2155899162
const (
darwin_SIOCAIFADDR_IN6 = 2155899162 // netinet6/in6_var.h
darwin_IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h
darwin_IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h
darwin_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
)
type in6_addrlifetime struct {
ia6t_expire float64
@@ -62,12 +69,12 @@ type ifreq struct {
// Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using
// a system socket and making direct syscalls to the kernel.
func (tun *tunAdapter) setupAddress(addr string) error {
func (tun *TunAdapter) setupAddress(addr string) error {
var fd int
var err error
if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
tun.core.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
return err
}
@@ -89,26 +96,29 @@ func (tun *tunAdapter) setupAddress(addr string) error {
ar.ifra_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b))
}
ar.ifra_lifetime.ia6t_vltime = 0xFFFFFFFF
ar.ifra_lifetime.ia6t_pltime = 0xFFFFFFFF
ar.ifra_flags |= darwin_IN6_IFF_NODAD
ar.ifra_flags |= darwin_IN6_IFF_SECURED
ar.ifra_lifetime.ia6t_vltime = darwin_ND6_INFINITE_LIFETIME
ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME
var ir ifreq
copy(ir.ifr_name[:], tun.iface.Name())
ir.ifru_mtu = uint32(tun.mtu)
tun.core.log.Printf("Interface name: %s", ar.ifra_name)
tun.core.log.Printf("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", ir.ifru_mtu)
tun.log.Infof("Interface name: %s", ar.ifra_name)
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", ir.ifru_mtu)
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
err = errno
tun.core.log.Printf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
return err
}
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
tun.core.log.Printf("Error in SIOCSIFMTU: %v", errno)
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
return err
}

View File

@@ -1,19 +1,17 @@
package yggdrasil
// +build !mobile
package tuntap
// The linux platform specific tun parts
import (
"errors"
"fmt"
"net"
"github.com/docker/libcontainer/netlink"
"github.com/vishvananda/netlink"
water "github.com/yggdrasil-network/water"
)
// Configures the TAP adapter with the correct IPv6 address and MTU.
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
@@ -38,9 +36,9 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
}
// Friendly output
tun.core.log.Printf("Interface name: %s", tun.iface.Name())
tun.core.log.Printf("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}
@@ -48,36 +46,22 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
// is used to do this, so there is not a hard requirement on "ip" or "ifconfig"
// to exist on the system, but this will fail if Netlink is not present in the
// kernel (it nearly always is).
func (tun *tunAdapter) setupAddress(addr string) error {
// Set address
var netIF *net.Interface
ifces, err := net.Interfaces()
func (tun *TunAdapter) setupAddress(addr string) error {
nladdr, err := netlink.ParseAddr(addr)
if err != nil {
return err
}
for _, ifce := range ifces {
if ifce.Name == tun.iface.Name() {
var newIF = ifce
netIF = &newIF // Don't point inside ifces, it's apparently unsafe?...
}
}
if netIF == nil {
return errors.New(fmt.Sprintf("Failed to find interface: %s", tun.iface.Name()))
}
ip, ipNet, err := net.ParseCIDR(addr)
nlintf, err := netlink.LinkByName(tun.iface.Name())
if err != nil {
return err
}
err = netlink.NetworkLinkAddIp(netIF, ip, ipNet)
if err != nil {
if err := netlink.AddrAdd(nlintf, nladdr); err != nil {
return err
}
err = netlink.NetworkSetMTU(netIF, tun.mtu)
if err != nil {
if err := netlink.LinkSetMTU(nlintf, tun.mtu); err != nil {
return err
}
netlink.NetworkLinkUp(netIF)
if err != nil {
if err := netlink.LinkSetUp(nlintf); err != nil {
return err
}
return nil

View File

@@ -1,6 +1,6 @@
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile
package yggdrasil
package tuntap
import water "github.com/yggdrasil-network/water"
@@ -9,7 +9,7 @@ import water "github.com/yggdrasil-network/water"
// Creates the TUN/TAP adapter, if supported by the Water library. Note that
// no guarantees are made at this point on an unsupported platform.
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
@@ -27,7 +27,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
// We don't know how to set the IPv6 address on an unknown platform, therefore
// write about it to stdout and don't try to do anything further.
func (tun *tunAdapter) setupAddress(addr string) error {
tun.core.log.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
return nil
}

View File

@@ -1,6 +1,7 @@
package yggdrasil
package tuntap
import (
"errors"
"fmt"
"os/exec"
"strings"
@@ -13,9 +14,9 @@ import (
// Configures the TAP adapter with the correct IPv6 address and MTU. On Windows
// we don't make use of a direct operating system API to do this - we instead
// delegate the hard work to "netsh".
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if !iftapmode {
tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP")
tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
}
config := water.Config{DeviceType: water.TAP}
config.PlatformSpecificParams.ComponentID = "tap0901"
@@ -27,23 +28,27 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
iface, err := water.New(config)
if err != nil {
panic(err)
}
// Disable/enable the interface to resets its configuration (invalidating iface)
cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output))
return err
}
if iface.Name() == "" {
return errors.New("unable to find TAP adapter with component ID " + config.PlatformSpecificParams.ComponentID)
}
// Reset the adapter - this invalidates iface so we'll need to get a new one
cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
// Bring the interface back up
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err = cmd.CombinedOutput()
if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output))
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
// Get a new iface
@@ -58,41 +63,47 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
panic(err)
}
// Friendly output
tun.core.log.Printf("Interface name: %s", tun.iface.Name())
tun.core.log.Printf("Interface IPv6: %s", addr)
tun.core.log.Printf("Interface MTU: %d", tun.mtu)
tun.log.Infof("Interface name: %s", tun.iface.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}
// Sets the MTU of the TAP adapter.
func (tun *tunAdapter) setupMTU(mtu int) error {
func (tun *TunAdapter) setupMTU(mtu int) error {
if tun.iface == nil || tun.iface.Name() == "" {
return errors.New("Can't configure MTU as TAP adapter is not present")
}
// Set MTU
cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("mtu=%d", mtu),
"store=active")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output))
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
return nil
}
// Sets the IPv6 address of the TAP adapter.
func (tun *tunAdapter) setupAddress(addr string) error {
func (tun *TunAdapter) setupAddress(addr string) error {
if tun.iface == nil || tun.iface.Name() == "" {
return errors.New("Can't configure IPv6 address as TAP adapter is not present")
}
// Set address
cmd := exec.Command("netsh", "interface", "ipv6", "add", "address",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("addr=%s", addr),
"store=active")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.core.log.Printf("Windows netsh failed: %v.", err)
tun.core.log.Println(string(output))
tun.log.Errorln("Windows netsh failed:", err)
tun.log.Traceln(string(output))
return err
}
return nil

90
src/util/cancellation.go Normal file
View File

@@ -0,0 +1,90 @@
package util
import (
"errors"
"runtime"
"sync"
"time"
)
type Cancellation interface {
Finished() <-chan struct{}
Cancel(error) error
Error() error
}
var CancellationFinalized = errors.New("finalizer called")
var CancellationTimeoutError = errors.New("timeout")
func CancellationFinalizer(c Cancellation) {
c.Cancel(CancellationFinalized)
}
type cancellation struct {
cancel chan struct{}
mutex sync.RWMutex
err error
done bool
}
func NewCancellation() Cancellation {
c := cancellation{
cancel: make(chan struct{}),
}
runtime.SetFinalizer(&c, CancellationFinalizer)
return &c
}
func (c *cancellation) Finished() <-chan struct{} {
return c.cancel
}
func (c *cancellation) Cancel(err error) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.done {
return c.err
} else {
c.err = err
c.done = true
close(c.cancel)
return nil
}
}
func (c *cancellation) Error() error {
c.mutex.RLock()
err := c.err
c.mutex.RUnlock()
return err
}
func CancellationChild(parent Cancellation) Cancellation {
child := NewCancellation()
go func() {
select {
case <-child.Finished():
case <-parent.Finished():
child.Cancel(parent.Error())
}
}()
return child
}
func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation {
child := CancellationChild(parent)
go func() {
timer := time.NewTimer(timeout)
defer TimerStop(timer)
select {
case <-child.Finished():
case <-timer.C:
child.Cancel(CancellationTimeoutError)
}
}()
return child
}
func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation {
return CancellationWithTimeout(parent, deadline.Sub(time.Now()))
}

View File

@@ -2,7 +2,13 @@ package util
// These are misc. utility functions that didn't really fit anywhere else
import "runtime"
import (
"runtime"
"strconv"
"strings"
"sync"
"time"
)
// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere.
func Yield() {
@@ -20,27 +26,121 @@ func UnlockThread() {
}
// This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops.
// It's used like a sync.Pool, but with a fixed size and typechecked without type casts to/from interface{} (which were making the profiles look ugly).
var byteStore chan []byte
var byteStore = sync.Pool{New: func() interface{} { return []byte(nil) }}
func init() {
byteStore = make(chan []byte, 32)
}
// Gets an empty slice from the byte store, if one is available, or else returns a new nil slice.
// Gets an empty slice from the byte store.
func GetBytes() []byte {
select {
case bs := <-byteStore:
return bs[:0]
default:
return nil
return byteStore.Get().([]byte)[:0]
}
// Puts a slice in the store.
func PutBytes(bs []byte) {
byteStore.Put(bs)
}
// Gets a slice of the appropriate length, reusing existing slice capacity when possible
func ResizeBytes(bs []byte, length int) []byte {
if cap(bs) >= length {
return bs[:length]
} else {
return make([]byte, length)
}
}
// Puts a slice in the store, if there's room, or else returns and lets the slice get collected.
func PutBytes(bs []byte) {
// This is a workaround to go's broken timer implementation
func TimerStop(t *time.Timer) bool {
stopped := t.Stop()
select {
case byteStore <- bs:
case <-t.C:
default:
}
return stopped
}
// Run a blocking function with a timeout.
// Returns true if the function returns.
// Returns false if the timer fires.
// The blocked function remains blocked--the caller is responsible for somehow killing it.
func FuncTimeout(f func(), timeout time.Duration) bool {
success := make(chan struct{})
go func() {
defer close(success)
f()
}()
timer := time.NewTimer(timeout)
defer TimerStop(timer)
select {
case <-success:
return true
case <-timer.C:
return false
}
}
// This calculates the difference between two arrays and returns items
// that appear in A but not in B - useful somewhat when reconfiguring
// and working out what configuration items changed
func Difference(a, b []string) []string {
ab := []string{}
mb := map[string]bool{}
for _, x := range b {
mb[x] = true
}
for _, x := range a {
if !mb[x] {
ab = append(ab, x)
}
}
return ab
}
// DecodeCoordString decodes a string representing coordinates in [1 2 3] format
// and returns a []uint64.
func DecodeCoordString(in string) (out []uint64) {
s := strings.Trim(in, "[]")
t := strings.Split(s, " ")
for _, a := range t {
if u, err := strconv.ParseUint(a, 0, 64); err == nil {
out = append(out, u)
}
}
return out
}
// GetFlowLabel takes an IP packet as an argument and returns some information about the traffic flow.
// For IPv4 packets, this is derived from the source and destination protocol and port numbers.
// For IPv6 packets, this is derived from the FlowLabel field of the packet if this was set, otherwise it's handled like IPv4.
// The FlowKey is then used internally by Yggdrasil for congestion control.
func GetFlowKey(bs []byte) uint64 {
// Work out the flowkey - this is used to determine which switch queue
// traffic will be pushed to in the event of congestion
var flowkey uint64
// Get the IP protocol version from the packet
switch bs[0] & 0xf0 {
case 0x40: // IPv4 packet
// Check the packet meets minimum UDP packet length
if len(bs) >= 24 {
// Is the protocol TCP, UDP or SCTP?
if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 {
ihl := bs[0] & 0x0f * 4 // Header length
flowkey = uint64(bs[9])<<32 /* proto */ |
uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ |
uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */
}
}
case 0x60: // IPv6 packet
// Check if the flowlabel was specified in the packet header
flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3])
// If the flowlabel isn't present, make protokey from proto | sport | dport
// if the packet meets minimum UDP packet length
if flowkey == 0 && len(bs) >= 48 {
// Is the protocol TCP, UDP or SCTP?
if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 {
flowkey = uint64(bs[6])<<32 /* proto */ |
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
}
}
}
return flowkey
}

29
src/util/workerpool.go Normal file
View File

@@ -0,0 +1,29 @@
package util
import "runtime"
var workerPool chan func()
func init() {
maxProcs := runtime.GOMAXPROCS(0)
if maxProcs < 1 {
maxProcs = 1
}
workerPool = make(chan func(), maxProcs)
for idx := 0; idx < maxProcs; idx++ {
go func() {
for f := range workerPool {
f()
}
}()
}
}
// WorkerGo submits a job to a pool of GOMAXPROCS worker goroutines.
// This is meant for short non-blocking functions f() where you could just go f(),
// but you want some kind of backpressure to prevent spawning endless goroutines.
// WorkerGo returns as soon as the function is queued to run, not when it finishes.
// In Yggdrasil, these workers are used for certain cryptographic operations.
func WorkerGo(f func()) {
workerPool <- f
}

22
src/version/version.go Normal file
View File

@@ -0,0 +1,22 @@
package version
var buildName string
var buildVersion string
// BuildName gets the current build name. This is usually injected if built
// from git, or returns "unknown" otherwise.
func BuildName() string {
if buildName == "" {
return "yggdrasilctl"
}
return buildName
}
// BuildVersion gets the current build version. This is usually injected if
// built from git, or returns "unknown" otherwise.
func BuildVersion() string {
if buildVersion == "" {
return "unknown"
}
return buildVersion
}

View File

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

View File

@@ -1,992 +0,0 @@
package yggdrasil
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"os"
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
// TODO: Add authentication
type admin struct {
core *Core
listenaddr string
listener net.Listener
handlers []admin_handlerInfo
}
type admin_info map[string]interface{}
type admin_handlerInfo struct {
name string // Checked against the first word of the api call
args []string // List of human-readable argument names
handler func(admin_info) (admin_info, error) // First is input map, second is output
}
// admin_pair maps things like "IP", "port", "bucket", or "coords" onto values.
type admin_pair struct {
key string
val interface{}
}
// admin_nodeInfo represents the information we know about a node for an admin response.
type admin_nodeInfo []admin_pair
// addHandler is called for each admin function to add the handler and help documentation to the API.
func (a *admin) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) {
a.handlers = append(a.handlers, admin_handlerInfo{name, args, handler})
}
// init runs the initial admin setup.
func (a *admin) init(c *Core, listenaddr string) {
a.core = c
a.listenaddr = listenaddr
a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) {
handlers := make(map[string]interface{})
for _, handler := range a.handlers {
handlers[handler.name] = admin_info{"fields": handler.args}
}
return admin_info{"list": handlers}, nil
})
a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) {
return admin_info{"dot": string(a.getResponse_dot())}, nil
})
a.addHandler("getSelf", []string{}, func(in admin_info) (admin_info, error) {
self := a.getData_getSelf().asMap()
ip := fmt.Sprint(self["ip"])
delete(self, "ip")
return admin_info{"self": admin_info{ip: self}}, nil
})
a.addHandler("getPeers", []string{}, func(in admin_info) (admin_info, error) {
sort := "ip"
peers := make(admin_info)
for _, peerdata := range a.getData_getPeers() {
p := peerdata.asMap()
so := fmt.Sprint(p[sort])
peers[so] = p
delete(peers[so].(map[string]interface{}), sort)
}
return admin_info{"peers": peers}, nil
})
a.addHandler("getSwitchPeers", []string{}, func(in admin_info) (admin_info, error) {
sort := "port"
switchpeers := make(admin_info)
for _, s := range a.getData_getSwitchPeers() {
p := s.asMap()
so := fmt.Sprint(p[sort])
switchpeers[so] = p
delete(switchpeers[so].(map[string]interface{}), sort)
}
return admin_info{"switchpeers": switchpeers}, nil
})
a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) {
queues := a.getData_getSwitchQueues()
return admin_info{"switchqueues": queues.asMap()}, nil
})
a.addHandler("getDHT", []string{}, func(in admin_info) (admin_info, error) {
sort := "ip"
dht := make(admin_info)
for _, d := range a.getData_getDHT() {
p := d.asMap()
so := fmt.Sprint(p[sort])
dht[so] = p
delete(dht[so].(map[string]interface{}), sort)
}
return admin_info{"dht": dht}, nil
})
a.addHandler("getSessions", []string{}, func(in admin_info) (admin_info, error) {
sort := "ip"
sessions := make(admin_info)
for _, s := range a.getData_getSessions() {
p := s.asMap()
so := fmt.Sprint(p[sort])
sessions[so] = p
delete(sessions[so].(map[string]interface{}), sort)
}
return admin_info{"sessions": sessions}, nil
})
a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) {
// Set sane defaults
intf := ""
// Has interface been specified?
if itf, ok := in["interface"]; ok {
intf = itf.(string)
}
if a.addPeer(in["uri"].(string), intf) == nil {
return admin_info{
"added": []string{
in["uri"].(string),
},
}, nil
} else {
return admin_info{
"not_added": []string{
in["uri"].(string),
},
}, errors.New("Failed to add peer")
}
})
a.addHandler("removePeer", []string{"port"}, func(in admin_info) (admin_info, error) {
if a.removePeer(fmt.Sprint(in["port"])) == nil {
return admin_info{
"removed": []string{
fmt.Sprint(in["port"]),
},
}, nil
} else {
return admin_info{
"not_removed": []string{
fmt.Sprint(in["port"]),
},
}, errors.New("Failed to remove peer")
}
})
a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) {
defer func() {
recover()
r = admin_info{"none": admin_info{}}
e = nil
}()
return admin_info{
a.core.router.tun.iface.Name(): admin_info{
"tap_mode": a.core.router.tun.iface.IsTAP(),
"mtu": a.core.router.tun.mtu,
},
}, nil
})
a.addHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in admin_info) (admin_info, error) {
// Set sane defaults
iftapmode := defaults.GetDefaults().DefaultIfTAPMode
ifmtu := defaults.GetDefaults().DefaultIfMTU
// Has TAP mode been specified?
if tap, ok := in["tap_mode"]; ok {
iftapmode = tap.(bool)
}
// Check we have enough params for MTU
if mtu, ok := in["mtu"]; ok {
if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
ifmtu = int(in["mtu"].(float64))
}
}
// Start the TUN adapter
if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil {
return admin_info{}, errors.New("Failed to configure adapter")
} else {
return admin_info{
a.core.router.tun.iface.Name(): admin_info{
"tap_mode": a.core.router.tun.iface.IsTAP(),
"mtu": ifmtu,
},
}, nil
}
})
a.addHandler("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) {
var intfs []string
for _, v := range a.core.multicast.interfaces() {
intfs = append(intfs, v.Name)
}
return admin_info{"multicast_interfaces": intfs}, nil
})
a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) {
return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil
})
a.addHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) {
if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
return admin_info{
"added": []string{
in["box_pub_key"].(string),
},
}, nil
} else {
return admin_info{
"not_added": []string{
in["box_pub_key"].(string),
},
}, errors.New("Failed to add allowed key")
}
})
a.addHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) {
if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
return admin_info{
"removed": []string{
in["box_pub_key"].(string),
},
}, nil
} else {
return admin_info{
"not_removed": []string{
in["box_pub_key"].(string),
},
}, errors.New("Failed to remove allowed key")
}
})
a.addHandler("addSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) {
var err error
a.core.router.doAdmin(func() {
err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string))
})
if err == nil {
return admin_info{"added": []string{in["subnet"].(string)}}, nil
} else {
return admin_info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet")
}
})
a.addHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) {
var err error
a.core.router.doAdmin(func() {
err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string))
})
if err == nil {
return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
} else {
return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route")
}
})
a.addHandler("getSourceSubnets", []string{}, func(in admin_info) (admin_info, error) {
var subnets []string
a.core.router.doAdmin(func() {
getSourceSubnets := func(snets []net.IPNet) {
for _, subnet := range snets {
subnets = append(subnets, subnet.String())
}
}
getSourceSubnets(a.core.router.cryptokey.ipv4sources)
getSourceSubnets(a.core.router.cryptokey.ipv6sources)
})
return admin_info{"source_subnets": subnets}, nil
})
a.addHandler("getRoutes", []string{}, func(in admin_info) (admin_info, error) {
routes := make(admin_info)
a.core.router.doAdmin(func() {
getRoutes := func(ckrs []cryptokey_route) {
for _, ckr := range ckrs {
routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:])
}
}
getRoutes(a.core.router.cryptokey.ipv4routes)
getRoutes(a.core.router.cryptokey.ipv6routes)
})
return admin_info{"routes": routes}, nil
})
a.addHandler("removeSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) {
var err error
a.core.router.doAdmin(func() {
err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string))
})
if err == nil {
return admin_info{"removed": []string{in["subnet"].(string)}}, nil
} else {
return admin_info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet")
}
})
a.addHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) {
var err error
a.core.router.doAdmin(func() {
err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string))
})
if err == nil {
return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
} else {
return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route")
}
})
a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) {
if in["target"] == nil {
in["target"] = "none"
}
result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string))
if err == nil {
infos := make(map[string]map[string]string, len(result.Infos))
for _, dinfo := range result.Infos {
info := map[string]string{
"box_pub_key": hex.EncodeToString(dinfo.key[:]),
"coords": fmt.Sprintf("%v", dinfo.coords),
}
addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String()
infos[addr] = info
}
return admin_info{"nodes": infos}, nil
} else {
return admin_info{}, err
}
})
a.addHandler("getNodeInfo", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) {
var nocache bool
if in["nocache"] != nil {
nocache = in["nocache"].(string) == "true"
}
result, err := a.admin_getNodeInfo(in["box_pub_key"].(string), in["coords"].(string), nocache)
if err == nil {
var m map[string]interface{}
if err = json.Unmarshal(result, &m); err == nil {
return admin_info{"nodeinfo": m}, nil
} else {
return admin_info{}, err
}
} else {
return admin_info{}, err
}
})
}
// start runs the admin API socket to listen for / respond to admin API calls.
func (a *admin) start() error {
if a.listenaddr != "none" && a.listenaddr != "" {
go a.listen()
}
return nil
}
// cleans up when stopping
func (a *admin) close() error {
return a.listener.Close()
}
// listen is run by start and manages API connections.
func (a *admin) listen() {
u, err := url.Parse(a.listenaddr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
a.core.log.Println("WARNING:", a.listenaddr[7:], "already exists and may be in use by another process")
}
a.listener, err = net.Listen("unix", a.listenaddr[7:])
if err == nil {
switch a.listenaddr[7:8] {
case "@": // maybe abstract namespace
default:
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
a.core.log.Println("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
}
}
}
case "tcp":
a.listener, err = net.Listen("tcp", u.Host)
default:
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
a.listener, err = net.Listen("tcp", a.listenaddr)
}
} else {
a.listener, err = net.Listen("tcp", a.listenaddr)
}
if err != nil {
a.core.log.Printf("Admin socket failed to listen: %v", err)
os.Exit(1)
}
a.core.log.Printf("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String())
defer a.listener.Close()
for {
conn, err := a.listener.Accept()
if err == nil {
go a.handleRequest(conn)
}
}
}
// handleRequest calls the request handler for each request sent to the admin API.
func (a *admin) handleRequest(conn net.Conn) {
decoder := json.NewDecoder(conn)
encoder := json.NewEncoder(conn)
encoder.SetIndent("", " ")
recv := make(admin_info)
send := make(admin_info)
defer func() {
r := recover()
if r != nil {
send = admin_info{
"status": "error",
"error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax",
}
fmt.Println("Admin socket error:", r)
if err := encoder.Encode(&send); err != nil {
fmt.Println("Admin socket JSON encode error:", err)
}
conn.Close()
}
}()
for {
// Start with a clean slate on each request
recv = admin_info{}
send = admin_info{}
// Decode the input
if err := decoder.Decode(&recv); err != nil {
// fmt.Println("Admin socket JSON decode error:", err)
return
}
// Send the request back with the response, and default to "error"
// unless the status is changed below by one of the handlers
send["request"] = recv
send["status"] = "error"
handlers:
for _, handler := range a.handlers {
// We've found the handler that matches the request
if strings.ToLower(recv["request"].(string)) == strings.ToLower(handler.name) {
// Check that we have all the required arguments
for _, arg := range handler.args {
// An argument in [square brackets] is optional and not required,
// so we can safely ignore those
if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
continue
}
// Check if the field is missing
if _, ok := recv[arg]; !ok {
send = admin_info{
"status": "error",
"error": "Expected field missing: " + arg,
"expecting": arg,
}
break handlers
}
}
// By this point we should have all the fields we need, so call
// the handler
response, err := handler.handler(recv)
if err != nil {
send["error"] = err.Error()
if response != nil {
send["response"] = response
}
} else {
send["status"] = "success"
if response != nil {
send["response"] = response
}
}
break
}
}
// Send the response back
if err := encoder.Encode(&send); err != nil {
return
}
// If "keepalive" isn't true then close the connection
if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
conn.Close()
}
}
}
// asMap converts an admin_nodeInfo into a map of key/value pairs.
func (n *admin_nodeInfo) asMap() map[string]interface{} {
m := make(map[string]interface{}, len(*n))
for _, p := range *n {
m[p.key] = p.val
}
return m
}
// toString creates a printable string representation of an admin_nodeInfo.
func (n *admin_nodeInfo) toString() string {
// TODO return something nicer looking than this
var out []string
for _, p := range *n {
out = append(out, fmt.Sprintf("%v: %v", p.key, p.val))
}
return strings.Join(out, ", ")
}
// printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers.
func (a *admin) printInfos(infos []admin_nodeInfo) string {
var out []string
for _, info := range infos {
out = append(out, info.toString())
}
out = append(out, "") // To add a trailing "\n" in the join
return strings.Join(out, "\n")
}
// addPeer triggers a connection attempt to a node.
func (a *admin) addPeer(addr string, sintf string) error {
u, err := url.Parse(addr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "tcp":
a.core.tcp.connect(u.Host, sintf)
case "socks":
a.core.tcp.connectSOCKS(u.Host, u.Path[1:])
default:
return errors.New("invalid peer: " + addr)
}
} else {
return errors.New("invalid peer: " + addr)
}
return nil
}
// removePeer disconnects an existing node (given by the node's port number).
func (a *admin) removePeer(p string) error {
iport, err := strconv.Atoi(p)
if err != nil {
return err
}
a.core.peers.removePeer(switchPort(iport))
return nil
}
// startTunWithMTU creates the tun/tap device, sets its address, and sets the MTU to the provided value.
func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error {
// Close the TUN first if open
_ = a.core.router.tun.close()
// Then reconfigure and start it
addr := a.core.router.addr
straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix())-1)
if ifname != "none" {
err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu)
if err != nil {
return err
}
// If we have open sessions then we need to notify them
// that our MTU has now changed
for _, sinfo := range a.core.sessions.sinfos {
if ifname == "none" {
sinfo.myMTU = 0
} else {
sinfo.myMTU = uint16(ifmtu)
}
a.core.sessions.sendPingPong(sinfo, false)
}
// Aaaaand... go!
go a.core.router.tun.read()
}
go a.core.router.tun.write()
return nil
}
// getData_getSelf returns the self node's info for admin responses.
func (a *admin) getData_getSelf() *admin_nodeInfo {
table := a.core.switchTable.table.Load().(lookupTable)
coords := table.self.getCoords()
self := admin_nodeInfo{
{"box_pub_key", hex.EncodeToString(a.core.boxPub[:])},
{"ip", a.core.GetAddress().String()},
{"subnet", a.core.GetSubnet().String()},
{"coords", fmt.Sprint(coords)},
}
if name := GetBuildName(); name != "unknown" {
self = append(self, admin_pair{"build_name", name})
}
if version := GetBuildVersion(); version != "unknown" {
self = append(self, admin_pair{"build_version", version})
}
return &self
}
// getData_getPeers returns info from Core.peers for an admin response.
func (a *admin) getData_getPeers() []admin_nodeInfo {
ports := a.core.peers.ports.Load().(map[switchPort]*peer)
var peerInfos []admin_nodeInfo
var ps []switchPort
for port := range ports {
ps = append(ps, port)
}
sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
for _, port := range ps {
p := ports[port]
addr := *address.AddrForNodeID(crypto.GetNodeID(&p.box))
info := admin_nodeInfo{
{"ip", net.IP(addr[:]).String()},
{"port", port},
{"uptime", int(time.Since(p.firstSeen).Seconds())},
{"bytes_sent", atomic.LoadUint64(&p.bytesSent)},
{"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)},
{"endpoint", p.endpoint},
{"box_pub_key", hex.EncodeToString(p.box[:])},
}
peerInfos = append(peerInfos, info)
}
return peerInfos
}
// getData_getSwitchPeers returns info from Core.switchTable for an admin response.
func (a *admin) getData_getSwitchPeers() []admin_nodeInfo {
var peerInfos []admin_nodeInfo
table := a.core.switchTable.table.Load().(lookupTable)
peers := a.core.peers.ports.Load().(map[switchPort]*peer)
for _, elem := range table.elems {
peer, isIn := peers[elem.port]
if !isIn {
continue
}
addr := *address.AddrForNodeID(crypto.GetNodeID(&peer.box))
coords := elem.locator.getCoords()
info := admin_nodeInfo{
{"ip", net.IP(addr[:]).String()},
{"coords", fmt.Sprint(coords)},
{"port", elem.port},
{"bytes_sent", atomic.LoadUint64(&peer.bytesSent)},
{"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)},
{"endpoint", peer.endpoint},
{"box_pub_key", hex.EncodeToString(peer.box[:])},
}
peerInfos = append(peerInfos, info)
}
return peerInfos
}
// getData_getSwitchQueues returns info from Core.switchTable for an queue data.
func (a *admin) getData_getSwitchQueues() admin_nodeInfo {
var peerInfos admin_nodeInfo
switchTable := &a.core.switchTable
getSwitchQueues := func() {
queues := make([]map[string]interface{}, 0)
for k, v := range switchTable.queues.bufs {
nexthop := switchTable.bestPortForCoords([]byte(k))
queue := map[string]interface{}{
"queue_id": k,
"queue_size": v.size,
"queue_packets": len(v.packets),
"queue_port": nexthop,
}
queues = append(queues, queue)
}
peerInfos = admin_nodeInfo{
{"queues", queues},
{"queues_count", len(switchTable.queues.bufs)},
{"queues_size", switchTable.queues.size},
{"highest_queues_count", switchTable.queues.maxbufs},
{"highest_queues_size", switchTable.queues.maxsize},
{"maximum_queues_size", switchTable.queueTotalMaxSize},
}
}
a.core.switchTable.doAdmin(getSwitchQueues)
return peerInfos
}
// getData_getDHT returns info from Core.dht for an admin response.
func (a *admin) getData_getDHT() []admin_nodeInfo {
var infos []admin_nodeInfo
getDHT := func() {
now := time.Now()
var dhtInfos []*dhtInfo
for _, v := range a.core.dht.table {
dhtInfos = append(dhtInfos, v)
}
sort.SliceStable(dhtInfos, func(i, j int) bool {
return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID())
})
for _, v := range dhtInfos {
addr := *address.AddrForNodeID(v.getNodeID())
info := admin_nodeInfo{
{"ip", net.IP(addr[:]).String()},
{"coords", fmt.Sprint(v.coords)},
{"last_seen", int(now.Sub(v.recv).Seconds())},
{"box_pub_key", hex.EncodeToString(v.key[:])},
}
infos = append(infos, info)
}
}
a.core.router.doAdmin(getDHT)
return infos
}
// getData_getSessions returns info from Core.sessions for an admin response.
func (a *admin) getData_getSessions() []admin_nodeInfo {
var infos []admin_nodeInfo
getSessions := func() {
for _, sinfo := range a.core.sessions.sinfos {
// TODO? skipped known but timed out sessions?
info := admin_nodeInfo{
{"ip", net.IP(sinfo.theirAddr[:]).String()},
{"coords", fmt.Sprint(sinfo.coords)},
{"mtu", sinfo.getMTU()},
{"was_mtu_fixed", sinfo.wasMTUFixed},
{"bytes_sent", sinfo.bytesSent},
{"bytes_recvd", sinfo.bytesRecvd},
{"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])},
}
infos = append(infos, info)
}
}
a.core.router.doAdmin(getSessions)
return infos
}
// getAllowedEncryptionPublicKeys returns the public keys permitted for incoming peer connections.
func (a *admin) getAllowedEncryptionPublicKeys() []string {
pubs := a.core.peers.getAllowedEncryptionPublicKeys()
var out []string
for _, pub := range pubs {
out = append(out, hex.EncodeToString(pub[:]))
}
return out
}
// addAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) {
boxBytes, err := hex.DecodeString(bstr)
if err == nil {
var box crypto.BoxPubKey
copy(box[:], boxBytes)
a.core.peers.addAllowedEncryptionPublicKey(&box)
}
return
}
// removeAllowedEncryptionPublicKey removes a key from the whitelist for incoming peer connections.
// If none are set, an empty list permits all incoming connections.
func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) {
boxBytes, err := hex.DecodeString(bstr)
if err == nil {
var box crypto.BoxPubKey
copy(box[:], boxBytes)
a.core.peers.removeAllowedEncryptionPublicKey(&box)
}
return
}
// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID.
func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) {
var key crypto.BoxPubKey
if keyBytes, err := hex.DecodeString(keyString); err != nil {
return dhtRes{}, err
} else {
copy(key[:], keyBytes)
}
var coords []byte
for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
if cstr == "" {
// Special case, happens if trimmed is the empty string, e.g. this is the root
continue
}
if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
return dhtRes{}, err
} else {
coords = append(coords, uint8(u64))
}
}
resCh := make(chan *dhtRes, 1)
info := dhtInfo{
key: key,
coords: coords,
}
target := *info.getNodeID()
if targetString == "none" {
// Leave the default target in place
} else if targetBytes, err := hex.DecodeString(targetString); err != nil {
return dhtRes{}, err
} else if len(targetBytes) != len(target) {
return dhtRes{}, errors.New("Incorrect target NodeID length")
} else {
var target crypto.NodeID
copy(target[:], targetBytes)
}
rq := dhtReqKey{info.key, target}
sendPing := func() {
a.core.dht.addCallback(&rq, func(res *dhtRes) {
defer func() { recover() }()
select {
case resCh <- res:
default:
}
})
a.core.dht.ping(&info, &target)
}
a.core.router.doAdmin(sendPing)
go func() {
time.Sleep(6 * time.Second)
close(resCh)
}()
for res := range resCh {
return *res, nil
}
return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString))
}
func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) {
var key crypto.BoxPubKey
if keyBytes, err := hex.DecodeString(keyString); err != nil {
return nodeinfoPayload{}, err
} else {
copy(key[:], keyBytes)
}
if !nocache {
if response, err := a.core.nodeinfo.getCachedNodeInfo(key); err == nil {
return response, nil
}
}
var coords []byte
for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
if cstr == "" {
// Special case, happens if trimmed is the empty string, e.g. this is the root
continue
}
if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
return nodeinfoPayload{}, err
} else {
coords = append(coords, uint8(u64))
}
}
response := make(chan *nodeinfoPayload, 1)
sendNodeInfoRequest := func() {
a.core.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) {
defer func() { recover() }()
select {
case response <- nodeinfo:
default:
}
})
a.core.nodeinfo.sendNodeInfo(key, coords, false)
}
a.core.router.doAdmin(sendNodeInfoRequest)
go func() {
time.Sleep(6 * time.Second)
close(response)
}()
for res := range response {
return *res, nil
}
return nodeinfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString))
}
// getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network.
// This is color-coded and labeled, and includes the self node, switch peers, nodes known to the DHT, and nodes with open sessions.
// The graph is structured as a tree with directed links leading away from the root.
func (a *admin) getResponse_dot() []byte {
self := a.getData_getSelf()
peers := a.getData_getSwitchPeers()
dht := a.getData_getDHT()
sessions := a.getData_getSessions()
// Start building a tree from all known nodes
type nodeInfo struct {
name string
key string
parent string
port switchPort
options string
}
infos := make(map[string]nodeInfo)
// Get coords as a slice of strings, FIXME? this looks very fragile
coordSlice := func(coords string) []string {
tmp := strings.Replace(coords, "[", "", -1)
tmp = strings.Replace(tmp, "]", "", -1)
return strings.Split(tmp, " ")
}
// First fill the tree with all known nodes, no parents
addInfo := func(nodes []admin_nodeInfo, options string, tag string) {
for _, node := range nodes {
n := node.asMap()
info := nodeInfo{
key: n["coords"].(string),
options: options,
}
if len(tag) > 0 {
info.name = fmt.Sprintf("%s\n%s", n["ip"].(string), tag)
} else {
info.name = n["ip"].(string)
}
coordsSplit := coordSlice(info.key)
if len(coordsSplit) != 0 {
portStr := coordsSplit[len(coordsSplit)-1]
portUint, err := strconv.ParseUint(portStr, 10, 64)
if err == nil {
info.port = switchPort(portUint)
}
}
infos[info.key] = info
}
}
addInfo(dht, "fillcolor=\"#ffffff\" style=filled fontname=\"sans serif\"", "Known in DHT") // white
addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue
addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow
addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green
// Now go through and create placeholders for any missing nodes
for _, info := range infos {
// This is ugly string manipulation
coordsSplit := coordSlice(info.key)
for idx := range coordsSplit {
key := fmt.Sprintf("[%v]", strings.Join(coordsSplit[:idx], " "))
newInfo, isIn := infos[key]
if isIn {
continue
}
newInfo.name = "?"
newInfo.key = key
newInfo.options = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
coordsSplit := coordSlice(newInfo.key)
if len(coordsSplit) != 0 {
portStr := coordsSplit[len(coordsSplit)-1]
portUint, err := strconv.ParseUint(portStr, 10, 64)
if err == nil {
newInfo.port = switchPort(portUint)
}
}
infos[key] = newInfo
}
}
// Now go through and attach parents
for _, info := range infos {
pSplit := coordSlice(info.key)
if len(pSplit) > 0 {
pSplit = pSplit[:len(pSplit)-1]
}
info.parent = fmt.Sprintf("[%v]", strings.Join(pSplit, " "))
infos[info.key] = info
}
// Finally, get a sorted list of keys, which we use to organize the output
var keys []string
for _, info := range infos {
keys = append(keys, info.key)
}
// sort
sort.SliceStable(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
sort.SliceStable(keys, func(i, j int) bool {
return infos[keys[i]].port < infos[keys[j]].port
})
// Now print it all out
var out []byte
put := func(s string) {
out = append(out, []byte(s)...)
}
put("digraph {\n")
// First set the labels
for _, key := range keys {
info := infos[key]
put(fmt.Sprintf("\"%v\" [ label = \"%v\" %v ];\n", info.key, info.name, info.options))
}
// Then print the tree structure
for _, key := range keys {
info := infos[key]
if info.key == info.parent {
continue
} // happens for the root, skip it
port := fmt.Sprint(info.port)
style := "fontname=\"sans serif\""
if infos[info.parent].name == "?" || infos[info.key].name == "?" {
style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
}
put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" %s ];\n", info.parent, info.key, port, style))
}
put("}\n")
return out
}

471
src/yggdrasil/api.go Normal file
View File

@@ -0,0 +1,471 @@
package yggdrasil
import (
"encoding/hex"
"errors"
"fmt"
"net"
"sort"
"sync/atomic"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// Peer represents a single peer object. This contains information from the
// preferred switch port for this peer, although there may be more than one in
// reality.
type Peer struct {
PublicKey crypto.BoxPubKey
Endpoint string
BytesSent uint64
BytesRecvd uint64
Protocol string
Port uint64
Uptime time.Duration
}
// SwitchPeer represents a switch connection to a peer. Note that there may be
// multiple switch peers per actual peer, e.g. if there are multiple connections
// to a given node.
type SwitchPeer struct {
PublicKey crypto.BoxPubKey
Coords []uint64
BytesSent uint64
BytesRecvd uint64
Port uint64
Protocol string
Endpoint string
}
// DHTEntry represents a single DHT entry that has been learned or cached from
// DHT searches.
type DHTEntry struct {
PublicKey crypto.BoxPubKey
Coords []uint64
LastSeen time.Duration
}
// DHTRes represents a DHT response, as returned by DHTPing.
type DHTRes struct {
PublicKey crypto.BoxPubKey // key of the sender
Coords []uint64 // coords of the sender
Dest crypto.NodeID // the destination node ID
Infos []DHTEntry // response
}
// NodeInfoPayload represents a RequestNodeInfo response, in bytes.
type NodeInfoPayload []byte
// SwitchQueues represents information from the switch related to link
// congestion and a list of switch queues created in response to congestion on a
// given link.
type SwitchQueues struct {
Queues []SwitchQueue
Count uint64
Size uint64
HighestCount uint64
HighestSize uint64
MaximumSize uint64
}
// SwitchQueue represents a single switch queue, which is created in response
// to congestion on a given link.
type SwitchQueue struct {
ID string
Size uint64
Packets uint64
Port uint64
}
// Session represents an open session with another node.
type Session struct {
PublicKey crypto.BoxPubKey
Coords []uint64
BytesSent uint64
BytesRecvd uint64
MTU uint16
Uptime time.Duration
WasMTUFixed bool
}
// GetPeers returns one or more Peer objects containing information about active
// peerings with other Yggdrasil nodes, where one of the responses always
// includes information about the current node (with a port number of 0). If
// there is exactly one entry then this node is not connected to any other nodes
// and is therefore isolated.
func (c *Core) GetPeers() []Peer {
ports := c.peers.ports.Load().(map[switchPort]*peer)
var peers []Peer
var ps []switchPort
for port := range ports {
ps = append(ps, port)
}
sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
for _, port := range ps {
p := ports[port]
info := Peer{
Endpoint: p.intf.name,
BytesSent: atomic.LoadUint64(&p.bytesSent),
BytesRecvd: atomic.LoadUint64(&p.bytesRecvd),
Protocol: p.intf.info.linkType,
Port: uint64(port),
Uptime: time.Since(p.firstSeen),
}
copy(info.PublicKey[:], p.box[:])
peers = append(peers, info)
}
return peers
}
// GetSwitchPeers returns zero or more SwitchPeer objects containing information
// about switch port connections with other Yggdrasil nodes. Note that, unlike
// GetPeers, GetSwitchPeers does not include information about the current node,
// therefore it is possible for this to return zero elements if the node is
// isolated or not connected to any peers.
func (c *Core) GetSwitchPeers() []SwitchPeer {
var switchpeers []SwitchPeer
table := c.switchTable.table.Load().(lookupTable)
peers := c.peers.ports.Load().(map[switchPort]*peer)
for _, elem := range table.elems {
peer, isIn := peers[elem.port]
if !isIn {
continue
}
coords := elem.locator.getCoords()
info := SwitchPeer{
Coords: append([]uint64{}, wire_coordsBytestoUint64s(coords)...),
BytesSent: atomic.LoadUint64(&peer.bytesSent),
BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd),
Port: uint64(elem.port),
Protocol: peer.intf.info.linkType,
Endpoint: peer.intf.info.remote,
}
copy(info.PublicKey[:], peer.box[:])
switchpeers = append(switchpeers, info)
}
return switchpeers
}
// GetDHT returns zero or more entries as stored in the DHT, cached primarily
// from searches that have already taken place.
func (c *Core) GetDHT() []DHTEntry {
var dhtentries []DHTEntry
getDHT := func() {
now := time.Now()
var dhtentry []*dhtInfo
for _, v := range c.dht.table {
dhtentry = append(dhtentry, v)
}
sort.SliceStable(dhtentry, func(i, j int) bool {
return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID())
})
for _, v := range dhtentry {
info := DHTEntry{
Coords: append([]uint64{}, wire_coordsBytestoUint64s(v.coords)...),
LastSeen: now.Sub(v.recv),
}
copy(info.PublicKey[:], v.key[:])
dhtentries = append(dhtentries, info)
}
}
c.router.doAdmin(getDHT)
return dhtentries
}
// GetSwitchQueues returns information about the switch queues that are
// currently in effect. These values can change within an instant.
func (c *Core) GetSwitchQueues() SwitchQueues {
var switchqueues SwitchQueues
switchTable := &c.switchTable
getSwitchQueues := func() {
switchqueues = SwitchQueues{
Count: uint64(len(switchTable.queues.bufs)),
Size: switchTable.queues.size,
HighestCount: uint64(switchTable.queues.maxbufs),
HighestSize: switchTable.queues.maxsize,
MaximumSize: switchTable.queueTotalMaxSize,
}
for k, v := range switchTable.queues.bufs {
nexthop := switchTable.bestPortForCoords([]byte(k))
queue := SwitchQueue{
ID: k,
Size: v.size,
Packets: uint64(len(v.packets)),
Port: uint64(nexthop),
}
switchqueues.Queues = append(switchqueues.Queues, queue)
}
}
c.switchTable.doAdmin(getSwitchQueues)
return switchqueues
}
// GetSessions returns a list of open sessions from this node to other nodes.
func (c *Core) GetSessions() []Session {
var sessions []Session
getSessions := func() {
for _, sinfo := range c.sessions.sinfos {
var session Session
workerFunc := func() {
session = Session{
Coords: append([]uint64{}, wire_coordsBytestoUint64s(sinfo.coords)...),
MTU: sinfo.getMTU(),
BytesSent: sinfo.bytesSent,
BytesRecvd: sinfo.bytesRecvd,
Uptime: time.Now().Sub(sinfo.timeOpened),
WasMTUFixed: sinfo.wasMTUFixed,
}
copy(session.PublicKey[:], sinfo.theirPermPub[:])
}
var skip bool
func() {
defer func() {
if recover() != nil {
skip = true
}
}()
sinfo.doFunc(workerFunc)
}()
if skip {
continue
}
// TODO? skipped known but timed out sessions?
sessions = append(sessions, session)
}
}
c.router.doAdmin(getSessions)
return sessions
}
// ConnListen returns a listener for Yggdrasil session connections.
func (c *Core) ConnListen() (*Listener, error) {
c.sessions.listenerMutex.Lock()
defer c.sessions.listenerMutex.Unlock()
if c.sessions.listener != nil {
return nil, errors.New("a listener already exists")
}
c.sessions.listener = &Listener{
core: c,
conn: make(chan *Conn),
close: make(chan interface{}),
}
return c.sessions.listener, nil
}
// ConnDialer returns a dialer for Yggdrasil session connections.
func (c *Core) ConnDialer() (*Dialer, error) {
return &Dialer{
core: c,
}, nil
}
// ListenTCP starts a new TCP listener. The input URI should match that of the
// "Listen" configuration item, e.g.
// tcp://a.b.c.d:e
func (c *Core) ListenTCP(uri string) (*TcpListener, error) {
return c.link.tcp.listen(uri)
}
// NodeID gets the node ID.
func (c *Core) NodeID() *crypto.NodeID {
return crypto.GetNodeID(&c.boxPub)
}
// TreeID gets the tree ID.
func (c *Core) TreeID() *crypto.TreeID {
return crypto.GetTreeID(&c.sigPub)
}
// SigningPublicKey gets the node's signing public key.
func (c *Core) SigningPublicKey() string {
return hex.EncodeToString(c.sigPub[:])
}
// EncryptionPublicKey gets the node's encryption public key.
func (c *Core) EncryptionPublicKey() string {
return hex.EncodeToString(c.boxPub[:])
}
// Coords returns the current coordinates of the node.
func (c *Core) Coords() []uint64 {
table := c.switchTable.table.Load().(lookupTable)
return wire_coordsBytestoUint64s(table.self.getCoords())
}
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
// address.
func (c *Core) Address() net.IP {
address := net.IP(address.AddrForNodeID(c.NodeID())[:])
return address
}
// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a
// /64 subnet.
func (c *Core) Subnet() net.IPNet {
subnet := address.SubnetForNodeID(c.NodeID())[:]
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
}
// MyNodeInfo gets the currently configured nodeinfo.
func (c *Core) MyNodeInfo() NodeInfoPayload {
return c.router.nodeinfo.getNodeInfo()
}
// SetNodeInfo sets the local nodeinfo. Note that nodeinfo can be any value or
// struct, it will be serialised into JSON automatically.
func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
}
// GetNodeInfo requests nodeinfo from a remote node, as specified by the public
// key and coordinates specified. The third parameter specifies whether a cached
// result is acceptable - this results in less traffic being generated than is
// necessary when, e.g. crawling the network.
func (c *Core) GetNodeInfo(key crypto.BoxPubKey, coords []uint64, nocache bool) (NodeInfoPayload, error) {
response := make(chan *NodeInfoPayload, 1)
sendNodeInfoRequest := func() {
c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) {
defer func() { recover() }()
select {
case response <- nodeinfo:
default:
}
})
c.router.nodeinfo.sendNodeInfo(key, wire_coordsUint64stoBytes(coords), false)
}
c.router.doAdmin(sendNodeInfoRequest)
go func() {
time.Sleep(6 * time.Second)
close(response)
}()
for res := range response {
return *res, nil
}
return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", hex.EncodeToString(key[:]))
}
// SetSessionGatekeeper allows you to configure a handler function for deciding
// whether a session should be allowed or not. The default session firewall is
// implemented in this way. The function receives the public key of the remote
// side and a boolean which is true if we initiated the session or false if we
// received an incoming session request. The function should return true to
// allow the session or false to reject it.
func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) {
c.sessions.isAllowedMutex.Lock()
defer c.sessions.isAllowedMutex.Unlock()
c.sessions.isAllowedHandler = f
}
// SetLogger sets the output logger of the Yggdrasil node after startup. This
// may be useful if you want to redirect the output later.
func (c *Core) SetLogger(log *log.Logger) {
c.log = log
}
// AddPeer adds a peer. This should be specified in the peer URI format, e.g.:
// tcp://a.b.c.d:e
// socks://a.b.c.d:e/f.g.h.i:j
// This adds the peer to the peer list, so that they will be called again if the
// connection drops.
func (c *Core) AddPeer(addr string, sintf string) error {
if err := c.CallPeer(addr, sintf); err != nil {
return err
}
c.config.Mutex.Lock()
if sintf == "" {
c.config.Current.Peers = append(c.config.Current.Peers, addr)
} else {
c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr)
}
c.config.Mutex.Unlock()
return nil
}
// RemovePeer is not implemented yet.
func (c *Core) RemovePeer(addr string, sintf string) error {
// TODO: Implement a reverse of AddPeer, where we look up the port number
// based on the addr and sintf, disconnect it and then remove it from the
// peers list so we don't reconnect to it later
return errors.New("not implemented")
}
// CallPeer calls a peer once. This should be specified in the peer URI format,
// e.g.:
// tcp://a.b.c.d:e
// socks://a.b.c.d:e/f.g.h.i:j
// This does not add the peer to the peer list, so if the connection drops, the
// peer will not be called again automatically.
func (c *Core) CallPeer(addr string, sintf string) error {
return c.link.call(addr, sintf)
}
// DisconnectPeer disconnects a peer once. This should be specified as a port
// number.
func (c *Core) DisconnectPeer(port uint64) error {
c.peers.removePeer(switchPort(port))
return nil
}
// GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming
// peer connections.
func (c *Core) GetAllowedEncryptionPublicKeys() []string {
return c.peers.getAllowedEncryptionPublicKeys()
}
// AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) {
c.peers.addAllowedEncryptionPublicKey(bstr)
return nil
}
// RemoveAllowedEncryptionPublicKey removes a key from the whitelist for
// incoming peer connections. If none are set, an empty list permits all
// incoming connections.
func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) {
c.peers.removeAllowedEncryptionPublicKey(bstr)
return nil
}
// DHTPing sends a DHT ping to the node with the provided key and coords,
// optionally looking up the specified target NodeID.
func (c *Core) DHTPing(key crypto.BoxPubKey, coords []uint64, target *crypto.NodeID) (DHTRes, error) {
resCh := make(chan *dhtRes, 1)
info := dhtInfo{
key: key,
coords: wire_coordsUint64stoBytes(coords),
}
if target == nil {
target = info.getNodeID()
}
rq := dhtReqKey{info.key, *target}
sendPing := func() {
c.dht.addCallback(&rq, func(res *dhtRes) {
resCh <- res
})
c.dht.ping(&info, &rq.dest)
}
c.router.doAdmin(sendPing)
// TODO: do something better than the below...
res := <-resCh
if res != nil {
r := DHTRes{
Coords: append([]uint64{}, wire_coordsBytestoUint64s(res.Coords)...),
}
copy(r.PublicKey[:], res.Key[:])
for _, i := range res.Infos {
e := DHTEntry{
Coords: append([]uint64{}, wire_coordsBytestoUint64s(i.coords)...),
}
copy(e.PublicKey[:], i.key[:])
r.Infos = append(r.Infos, e)
}
return r, nil
}
return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", hex.EncodeToString(key[:]))
}

278
src/yggdrasil/conn.go Normal file
View File

@@ -0,0 +1,278 @@
package yggdrasil
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// ConnError implements the net.Error interface
type ConnError struct {
error
timeout bool
temporary bool
closed bool
maxsize int
}
// Timeout returns true if the error relates to a timeout condition on the
// connection.
func (e *ConnError) Timeout() bool {
return e.timeout
}
// Temporary return true if the error is temporary or false if it is a permanent
// error condition.
func (e *ConnError) Temporary() bool {
return e.temporary
}
// PacketTooBig returns in response to sending a packet that is too large, and
// if so, the maximum supported packet size that should be used for the
// connection.
func (e *ConnError) PacketTooBig() bool {
return e.maxsize > 0
}
// PacketMaximumSize returns the maximum supported packet size. This will only
// return a non-zero value if ConnError.PacketTooBig() returns true.
func (e *ConnError) PacketMaximumSize() int {
if !e.PacketTooBig() {
return 0
}
return e.maxsize
}
// Closed returns if the session is already closed and is now unusable.
func (e *ConnError) Closed() bool {
return e.closed
}
type Conn struct {
core *Core
readDeadline atomic.Value // time.Time // TODO timer
writeDeadline atomic.Value // time.Time // TODO timer
mutex sync.RWMutex // protects the below
nodeID *crypto.NodeID
nodeMask *crypto.NodeID
session *sessionInfo
}
// TODO func NewConn() that initializes additional fields as needed
func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn {
conn := Conn{
core: core,
nodeID: nodeID,
nodeMask: nodeMask,
session: session,
}
return &conn
}
func (c *Conn) String() string {
c.mutex.RLock()
defer c.mutex.RUnlock()
return fmt.Sprintf("conn=%p", c)
}
// This should never be called from the router goroutine, used in the dial functions
func (c *Conn) search() error {
var sinfo *searchInfo
var isIn bool
c.core.router.doAdmin(func() { sinfo, isIn = c.core.searches.searches[*c.nodeID] })
if !isIn {
done := make(chan struct{}, 1)
var sess *sessionInfo
var err error
searchCompleted := func(sinfo *sessionInfo, e error) {
sess = sinfo
err = e
// FIXME close can be called multiple times, do a non-blocking send instead
select {
case done <- struct{}{}:
default:
}
}
c.core.router.doAdmin(func() {
sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
sinfo.continueSearch()
})
<-done
c.session = sess
if c.session == nil && err == nil {
panic("search failed but returned no error")
}
if c.session != nil {
c.nodeID = crypto.GetNodeID(&c.session.theirPermPub)
for i := range c.nodeMask {
c.nodeMask[i] = 0xFF
}
}
return err
} else {
return errors.New("search already exists")
}
return nil
}
// Used in session keep-alive traffic in Conn.Write
func (c *Conn) doSearch() {
routerWork := func() {
// Check to see if there is a search already matching the destination
sinfo, isIn := c.core.searches.searches[*c.nodeID]
if !isIn {
// Nothing was found, so create a new search
searchCompleted := func(sinfo *sessionInfo, e error) {}
sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo)
// Start the search
sinfo.continueSearch()
}
}
go func() { c.core.router.admin <- routerWork }()
}
func (c *Conn) getDeadlineCancellation(value *atomic.Value) (util.Cancellation, bool) {
if deadline, ok := value.Load().(time.Time); ok {
// A deadline is set, so return a Cancellation that uses it
c := util.CancellationWithDeadline(c.session.cancel, deadline)
return c, true
} else {
// No deadline was set, so just return the existinc cancellation and a dummy value
return c.session.cancel, false
}
}
// Used internally by Read, the caller is responsible for util.PutBytes when they're done.
func (c *Conn) ReadNoCopy() ([]byte, error) {
cancel, doCancel := c.getDeadlineCancellation(&c.readDeadline)
if doCancel {
defer cancel.Cancel(nil)
}
// Wait for some traffic to come through from the session
select {
case <-cancel.Finished():
if cancel.Error() == util.CancellationTimeoutError {
return nil, ConnError{errors.New("read timeout"), true, false, false, 0}
} else {
return nil, ConnError{errors.New("session closed"), false, false, true, 0}
}
case bs := <-c.session.recv:
return bs, nil
}
}
// Implements net.Conn.Read
func (c *Conn) Read(b []byte) (int, error) {
bs, err := c.ReadNoCopy()
if err != nil {
return 0, err
}
n := len(bs)
if len(bs) > len(b) {
n = len(b)
err = ConnError{errors.New("read buffer too small for entire packet"), false, true, false, 0}
}
// Copy results to the output slice and clean up
copy(b, bs)
util.PutBytes(bs)
// Return the number of bytes copied to the slice, along with any error
return n, err
}
// Used internally by Write, the caller must not reuse the argument bytes when no error occurs
func (c *Conn) WriteNoCopy(msg FlowKeyMessage) error {
var err error
sessionFunc := func() {
// Does the packet exceed the permitted size for the session?
if uint16(len(msg.Message)) > c.session.getMTU() {
err = ConnError{errors.New("packet too big"), true, false, false, int(c.session.getMTU())}
return
}
// The rest of this work is session keep-alive traffic
switch {
case time.Since(c.session.time) > 6*time.Second:
if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second {
// TODO double check that the above condition is correct
c.doSearch()
} else {
c.core.sessions.ping(c.session)
}
case c.session.reset && c.session.pingTime.Before(c.session.time):
c.core.sessions.ping(c.session)
default: // Don't do anything, to keep traffic throttled
}
}
c.session.doFunc(sessionFunc)
if err == nil {
cancel, doCancel := c.getDeadlineCancellation(&c.writeDeadline)
if doCancel {
defer cancel.Cancel(nil)
}
select {
case <-cancel.Finished():
if cancel.Error() == util.CancellationTimeoutError {
err = ConnError{errors.New("write timeout"), true, false, false, 0}
} else {
err = ConnError{errors.New("session closed"), false, false, true, 0}
}
case c.session.send <- msg:
}
}
return err
}
// Implements net.Conn.Write
func (c *Conn) Write(b []byte) (int, error) {
written := len(b)
msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)}
err := c.WriteNoCopy(msg)
if err != nil {
util.PutBytes(msg.Message)
written = 0
}
return written, err
}
func (c *Conn) Close() (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.session != nil {
// Close the session, if it hasn't been closed already
if e := c.session.cancel.Cancel(errors.New("connection closed")); e != nil {
err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0}
}
}
return
}
func (c *Conn) LocalAddr() crypto.NodeID {
return *crypto.GetNodeID(&c.session.core.boxPub)
}
func (c *Conn) RemoteAddr() crypto.NodeID {
c.mutex.RLock()
defer c.mutex.RUnlock()
return *c.nodeID
}
func (c *Conn) SetDeadline(t time.Time) error {
c.SetReadDeadline(t)
c.SetWriteDeadline(t)
return nil
}
func (c *Conn) SetReadDeadline(t time.Time) error {
c.readDeadline.Store(t)
return nil
}
func (c *Conn) SetWriteDeadline(t time.Time) error {
c.writeDeadline.Store(t)
return nil
}

View File

@@ -2,25 +2,24 @@ package yggdrasil
import (
"encoding/hex"
"fmt"
"errors"
"io/ioutil"
"log"
"net"
"regexp"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
)
var buildName string
var buildVersion string
// The Core object represents the Yggdrasil node. You should create a Core
// object for each Yggdrasil node you plan to run.
type Core struct {
// This is the main data structure that holds everything else for a node
// We're going to keep our own copy of the provided config - that way we can
// guarantee that it will be covered by the mutex
config config.NodeState // Config
boxPub crypto.BoxPubKey
boxPriv crypto.BoxPrivKey
sigPub crypto.SigPubKey
@@ -30,19 +29,12 @@ type Core struct {
sessions sessions
router router
dht dht
admin admin
searches searches
multicast multicast
nodeinfo nodeinfo
tcp tcpInterface
link link
log *log.Logger
ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this
}
func (c *Core) init(bpub *crypto.BoxPubKey,
bpriv *crypto.BoxPrivKey,
spub *crypto.SigPubKey,
spriv *crypto.SigPrivKey) {
func (c *Core) init() error {
// TODO separate init and start functions
// Init sets up structs
// Start launches goroutines that depend on structs being set up
@@ -50,266 +42,165 @@ func (c *Core) init(bpub *crypto.BoxPubKey,
if c.log == nil {
c.log = log.New(ioutil.Discard, "", 0)
}
c.boxPub, c.boxPriv = *bpub, *bpriv
c.sigPub, c.sigPriv = *spub, *spriv
c.admin.core = c
current := c.config.GetCurrent()
boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey)
if err != nil {
return err
}
if len(boxPrivHex) < crypto.BoxPrivKeyLen {
return errors.New("EncryptionPrivateKey is incorrect length")
}
sigPrivHex, err := hex.DecodeString(current.SigningPrivateKey)
if err != nil {
return err
}
if len(sigPrivHex) < crypto.SigPrivKeyLen {
return errors.New("SigningPrivateKey is incorrect length")
}
copy(c.boxPriv[:], boxPrivHex)
copy(c.sigPriv[:], sigPrivHex)
boxPub, sigPub := c.boxPriv.Public(), c.sigPriv.Public()
copy(c.boxPub[:], boxPub[:])
copy(c.sigPub[:], sigPub[:])
if bp := hex.EncodeToString(c.boxPub[:]); current.EncryptionPublicKey != bp {
c.log.Warnln("EncryptionPublicKey in config is incorrect, should be", bp)
}
if sp := hex.EncodeToString(c.sigPub[:]); current.SigningPublicKey != sp {
c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp)
}
c.searches.init(c)
c.dht.init(c)
c.sessions.init(c)
c.multicast.init(c)
c.peers.init(c)
c.router.init(c)
c.switchTable.init(c, c.sigPub) // TODO move before peers? before router?
}
c.switchTable.init(c) // TODO move before peers? before router?
// Get the current build name. This is usually injected if built from git,
// or returns "unknown" otherwise.
func GetBuildName() string {
if buildName == "" {
return "unknown"
}
return buildName
}
// Get the current build version. This is usually injected if built from git,
// or returns "unknown" otherwise.
func GetBuildVersion() string {
if buildVersion == "" {
return "unknown"
}
return buildVersion
}
// Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging
// through the provided log.Logger. The started stack will include TCP and UDP
// sockets, a multicast discovery socket, an admin socket, router, switch and
// DHT node.
func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
c.log = log
if name := GetBuildName(); name != "unknown" {
c.log.Println("Build name:", name)
}
if version := GetBuildVersion(); version != "unknown" {
c.log.Println("Build version:", version)
}
c.log.Println("Starting up...")
var boxPub crypto.BoxPubKey
var boxPriv crypto.BoxPrivKey
var sigPub crypto.SigPubKey
var sigPriv crypto.SigPrivKey
boxPubHex, err := hex.DecodeString(nc.EncryptionPublicKey)
if err != nil {
return err
}
boxPrivHex, err := hex.DecodeString(nc.EncryptionPrivateKey)
if err != nil {
return err
}
sigPubHex, err := hex.DecodeString(nc.SigningPublicKey)
if err != nil {
return err
}
sigPrivHex, err := hex.DecodeString(nc.SigningPrivateKey)
if err != nil {
return err
}
copy(boxPub[:], boxPubHex)
copy(boxPriv[:], boxPrivHex)
copy(sigPub[:], sigPubHex)
copy(sigPriv[:], sigPrivHex)
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)
c.admin.init(c, nc.AdminListen)
c.nodeinfo.init(c)
c.nodeinfo.setNodeInfo(nc.NodeInfo)
if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil {
c.log.Println("Failed to start TCP interface")
return err
}
if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize {
c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize
}
if err := c.switchTable.start(); err != nil {
c.log.Println("Failed to start switch")
return err
}
c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable)
c.sessions.setSessionFirewallDefaults(
nc.SessionFirewall.AllowFromDirect,
nc.SessionFirewall.AllowFromRemote,
nc.SessionFirewall.AlwaysAllowOutbound,
)
c.sessions.setSessionFirewallWhitelist(nc.SessionFirewall.WhitelistEncryptionPublicKeys)
c.sessions.setSessionFirewallBlacklist(nc.SessionFirewall.BlacklistEncryptionPublicKeys)
if err := c.router.start(); err != nil {
c.log.Println("Failed to start router")
return err
}
c.router.cryptokey.setEnabled(nc.TunnelRouting.Enable)
if c.router.cryptokey.isEnabled() {
c.log.Println("Crypto-key routing enabled")
for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations {
if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil {
panic(err)
}
}
for _, source := range nc.TunnelRouting.IPv6Sources {
if c.router.cryptokey.addSourceSubnet(source); err != nil {
panic(err)
}
}
for ipv4, pubkey := range nc.TunnelRouting.IPv4Destinations {
if err := c.router.cryptokey.addRoute(ipv4, pubkey); err != nil {
panic(err)
}
}
for _, source := range nc.TunnelRouting.IPv4Sources {
if c.router.cryptokey.addSourceSubnet(source); err != nil {
panic(err)
}
}
}
if err := c.admin.start(); err != nil {
c.log.Println("Failed to start admin socket")
return err
}
if err := c.multicast.start(); err != nil {
c.log.Println("Failed to start multicast interface")
return err
}
ip := net.IP(c.router.addr[:]).String()
if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address.GetPrefix())-1), nc.IfMTU); err != nil {
c.log.Println("Failed to start TUN/TAP")
return err
}
c.log.Println("Startup complete")
return nil
}
// Stops the Yggdrasil node.
func (c *Core) Stop() {
c.log.Println("Stopping...")
c.router.tun.close()
c.admin.close()
// If any static peers were provided in the configuration above then we should
// configure them. The loop ensures that disconnected peers will eventually
// be reconnected with.
func (c *Core) addPeerLoop() {
for {
// the peers from the config - these could change!
current := c.config.GetCurrent()
// Add peers from the Peers section
for _, peer := range current.Peers {
go c.AddPeer(peer, "")
time.Sleep(time.Second)
}
// Add peers from the InterfacePeers section
for intf, intfpeers := range current.InterfacePeers {
for _, peer := range intfpeers {
go c.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
// Sit for a while
time.Sleep(time.Minute)
}
}
// Generates a new encryption keypair. The encryption keys are used to
// encrypt traffic and to derive the IPv6 address/subnet of the node.
func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
return crypto.NewBoxKeys()
// UpdateConfig updates the configuration in Core with the provided
// config.NodeConfig and then signals the various module goroutines to
// reconfigure themselves if needed.
func (c *Core) UpdateConfig(config *config.NodeConfig) {
c.log.Debugln("Reloading node configuration...")
c.config.Replace(*config)
errors := 0
components := []chan chan error{
c.searches.reconfigure,
c.dht.reconfigure,
c.sessions.reconfigure,
c.peers.reconfigure,
c.router.reconfigure,
c.switchTable.reconfigure,
c.link.reconfigure,
}
for _, component := range components {
response := make(chan error)
component <- response
if err := <-response; err != nil {
c.log.Errorln(err)
errors++
}
}
if errors > 0 {
c.log.Warnln(errors, "node module(s) reported errors during configuration reload")
} else {
c.log.Infoln("Node configuration reloaded successfully")
}
}
// Generates a new signing keypair. The signing keys are used to derive the
// structure of the spanning tree.
func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
return crypto.NewSigKeys()
}
// Gets the node ID.
func (c *Core) GetNodeID() *crypto.NodeID {
return crypto.GetNodeID(&c.boxPub)
}
// Gets the tree ID.
func (c *Core) GetTreeID() *crypto.TreeID {
return crypto.GetTreeID(&c.sigPub)
}
// Gets the IPv6 address of the Yggdrasil node. This is always a /128.
func (c *Core) GetAddress() *net.IP {
address := net.IP(address.AddrForNodeID(c.GetNodeID())[:])
return &address
}
// Gets the routed IPv6 subnet of the Yggdrasil node. This is always a /64.
func (c *Core) GetSubnet() *net.IPNet {
subnet := address.SubnetForNodeID(c.GetNodeID())[:]
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
}
// Gets the nodeinfo.
func (c *Core) GetNodeInfo() nodeinfoPayload {
return c.nodeinfo.getNodeInfo()
}
// Sets the nodeinfo.
func (c *Core) SetNodeInfo(nodeinfo interface{}) {
c.nodeinfo.setNodeInfo(nodeinfo)
}
// Sets the output logger of the Yggdrasil node after startup. This may be
// useful if you want to redirect the output later.
func (c *Core) SetLogger(log *log.Logger) {
// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs
// debug logging through the provided log.Logger. The started stack will include
// TCP and UDP sockets, a multicast discovery socket, an admin socket, router,
// switch and DHT node. A config.NodeState is returned which contains both the
// current and previous configurations (from reconfigures).
func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) {
c.log = log
c.config = config.NodeState{
Current: *nc,
Previous: *nc,
}
if name := version.BuildName(); name != "unknown" {
c.log.Infoln("Build name:", name)
}
if version := version.BuildVersion(); version != "unknown" {
c.log.Infoln("Build version:", version)
}
c.log.Infoln("Starting up...")
c.init()
if err := c.link.init(c); err != nil {
c.log.Errorln("Failed to start link interfaces")
return nil, err
}
c.config.Mutex.RLock()
if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize {
c.switchTable.queueTotalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize
}
c.config.Mutex.RUnlock()
if err := c.switchTable.start(); err != nil {
c.log.Errorln("Failed to start switch")
return nil, err
}
if err := c.router.start(); err != nil {
c.log.Errorln("Failed to start router")
return nil, err
}
go c.addPeerLoop()
c.log.Infoln("Startup complete")
return &c.config, nil
}
// Adds a peer. This should be specified in the peer URI format, i.e.
// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j
func (c *Core) AddPeer(addr string, sintf string) error {
return c.admin.addPeer(addr, sintf)
}
// Adds an expression to select multicast interfaces for peer discovery. This
// should be done before calling Start. This function can be called multiple
// times to add multiple search expressions.
func (c *Core) AddMulticastInterfaceExpr(expr *regexp.Regexp) {
c.ifceExpr = append(c.ifceExpr, expr)
}
// Adds an allowed public key. This allow peerings to be restricted only to
// keys that you have selected.
func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error {
return c.admin.addAllowedEncryptionPublicKey(boxStr)
}
// Gets the default admin listen address for your platform.
func (c *Core) GetAdminDefaultListen() string {
return defaults.GetDefaults().DefaultAdminListen
}
// Gets the default TUN/TAP interface name for your platform.
func (c *Core) GetTUNDefaultIfName() string {
return defaults.GetDefaults().DefaultIfName
}
// Gets the default TUN/TAP interface MTU for your platform. This can be as high
// as 65535, depending on platform, but is never lower than 1280.
func (c *Core) GetTUNDefaultIfMTU() int {
return defaults.GetDefaults().DefaultIfMTU
}
// Gets the maximum supported TUN/TAP interface MTU for your platform. This
// can be as high as 65535, depending on platform, but is never lower than 1280.
func (c *Core) GetTUNMaximumIfMTU() int {
return defaults.GetDefaults().MaximumIfMTU
}
// Gets the default TUN/TAP interface mode for your platform.
func (c *Core) GetTUNDefaultIfTAPMode() bool {
return defaults.GetDefaults().DefaultIfTAPMode
}
// Gets the current TUN/TAP interface name.
func (c *Core) GetTUNIfName() string {
return c.router.tun.iface.Name()
}
// Gets the current TUN/TAP interface MTU.
func (c *Core) GetTUNIfMTU() int {
return c.router.tun.mtu
// Stop shuts down the Yggdrasil node.
func (c *Core) Stop() {
c.log.Infoln("Stopping...")
}

View File

@@ -14,15 +14,18 @@ import _ "golang.org/x/net/ipv6" // TODO put this somewhere better
import "fmt"
import "net"
import "log"
import "regexp"
import "encoding/hex"
import _ "net/http/pprof"
import "net/http"
import "runtime"
import "os"
import "github.com/gologme/log"
import "github.com/yggdrasil-network/yggdrasil-go/src/address"
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
import "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
@@ -32,9 +35,9 @@ func init() {
hostPort := os.Getenv(envVarName)
switch {
case hostPort == "":
fmt.Printf("DEBUG: %s not set, profiler not started.\n", envVarName)
fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName)
default:
fmt.Printf("DEBUG: Starting pprof on %s\n", hostPort)
fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort)
go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }()
}
}
@@ -52,7 +55,21 @@ func StartProfiler(log *log.Logger) error {
func (c *Core) Init() {
bpub, bpriv := crypto.NewBoxKeys()
spub, spriv := crypto.NewSigKeys()
c.init(bpub, bpriv, spub, spriv)
hbpub := hex.EncodeToString(bpub[:])
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.config = config.NodeState{
Current: cfg,
Previous: cfg,
}
c.init()
c.switchTable.start()
c.router.start()
}
@@ -69,6 +86,7 @@ func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey {
return (crypto.BoxPubKey)(c.boxPub)
}
/*
func (c *Core) DEBUG_getSend() chan<- []byte {
return c.router.tun.send
}
@@ -76,6 +94,7 @@ func (c *Core) DEBUG_getSend() chan<- []byte {
func (c *Core) DEBUG_getRecv() <-chan []byte {
return c.router.tun.recv
}
*/
// Peer
@@ -84,9 +103,15 @@ func (c *Core) DEBUG_getPeers() *peers {
}
func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer {
//in <-chan []byte,
//out chan<- []byte) *peer {
return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out)
sim := linkInterface{
name: "(simulator)",
info: linkInfo{
local: "(simulator)",
remote: "(simulator)",
linkType: "sim",
},
}
return ps.newPeer(&box, &sig, &link, &sim, nil)
}
/*
@@ -298,6 +323,7 @@ func (c *Core) DEBUG_getAddr() *address.Address {
return address.AddrForNodeID(&c.dht.nodeID)
}
/*
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280)
}
@@ -319,6 +345,7 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) {
func (c *Core) DEBUG_stopTun() {
c.router.tun.close()
}
*/
////////////////////////////////////////////////////////////////////////////////
@@ -350,7 +377,7 @@ func (c *Core) DEBUG_init(bpub []byte,
bpriv []byte,
spub []byte,
spriv []byte) {
var boxPub crypto.BoxPubKey
/*var boxPub crypto.BoxPubKey
var boxPriv crypto.BoxPrivKey
var sigPub crypto.SigPubKey
var sigPriv crypto.SigPrivKey
@@ -358,7 +385,22 @@ func (c *Core) DEBUG_init(bpub []byte,
copy(boxPriv[:], bpriv)
copy(sigPub[:], spub)
copy(sigPriv[:], spriv)
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)
c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)*/
hbpub := hex.EncodeToString(bpub[:])
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
c.config = config.NodeState{
Current: cfg,
Previous: cfg,
}
c.init()
if err := c.router.start(); err != nil {
panic(err)
@@ -397,14 +439,14 @@ func (c *Core) DEBUG_maybeSendUDPKeys(saddr string) {
*/
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_addPeer(addr string) {
err := c.admin.addPeer(addr, "")
if err != nil {
panic(err)
}
}
*/
/*
func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
go func() {
@@ -425,20 +467,21 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
}
*/
//*
/*
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
if err := c.tcp.init(c, addrport, 0); err != nil {
c.log.Println("Failed to start TCP interface:", err)
c.config.Listen = []string{addrport}
if err := c.link.init(c); err != nil {
c.log.Println("Failed to start interfaces:", err)
panic(err)
}
}
func (c *Core) DEBUG_getGlobalTCPAddr() *net.TCPAddr {
return c.tcp.serv.Addr().(*net.TCPAddr)
return c.link.tcp.getAddr()
}
func (c *Core) DEBUG_addTCPConn(saddr string) {
c.tcp.call(saddr, nil, "")
c.link.tcp.call(saddr, nil, "")
}
//*/
@@ -472,9 +515,11 @@ func (c *Core) DEBUG_addKCPConn(saddr string) {
////////////////////////////////////////////////////////////////////////////////
/*
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
a := admin{}
a.init(c, addrport)
c.config.AdminListen = addrport
a.init()
c.admin = a
}
@@ -484,6 +529,7 @@ func (c *Core) DEBUG_setupAndStartMulticastInterface() {
c.multicast = m
m.start()
}
*/
////////////////////////////////////////////////////////////////////////////////
@@ -492,16 +538,17 @@ func (c *Core) DEBUG_setLogger(log *log.Logger) {
}
func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) {
c.ifceExpr = append(c.ifceExpr, expr)
c.log.Println("DEBUG_setIfceExpr no longer implemented")
}
/*
func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) {
err := c.admin.addAllowedEncryptionPublicKey(boxStr)
if err != nil {
panic(err)
}
}
*/
////////////////////////////////////////////////////////////////////////////////
func DEBUG_simLinkPeers(p, q *peer) {
@@ -509,8 +556,10 @@ func DEBUG_simLinkPeers(p, q *peer) {
goWorkers := func(source, dest *peer) {
source.linkOut = make(chan []byte, 1)
send := make(chan []byte, 1)
source.out = func(bs []byte) {
send <- bs
source.out = func(bss [][]byte) {
for _, bs := range bss {
send <- bs
}
}
go source.linkLoop()
go func() {
@@ -547,9 +596,11 @@ func DEBUG_simLinkPeers(p, q *peer) {
q.core.switchTable.idleIn <- q.port
}
/*
func (c *Core) DEBUG_simFixMTU() {
c.router.tun.mtu = 65535
}
*/
////////////////////////////////////////////////////////////////////////////////

View File

@@ -12,7 +12,12 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
const dht_lookup_size = 16
const (
dht_lookup_size = 16
dht_timeout = 6 * time.Minute
dht_max_delay = 5 * time.Minute
dht_max_delay_dirty = 30 * time.Second
)
// dhtInfo represents everything we know about a node in the DHT.
// This includes its key, a cache of it's NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance.
@@ -23,6 +28,7 @@ type dhtInfo struct {
recv time.Time // When we last received a message
pings int // Time out if at least 3 consecutive maintenance pings drop
throttle time.Duration
dirty bool // Set to true if we've used this node in ping responses (for queries about someone other than the person doing the asking, i.e. real searches) since the last time we heard from the node
}
// Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times.
@@ -59,11 +65,12 @@ type dhtReqKey struct {
// The main DHT struct.
type dht struct {
core *Core
nodeID crypto.NodeID
peers chan *dhtInfo // other goroutines put incoming dht updates here
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks
core *Core
reconfigure chan chan error
nodeID crypto.NodeID
peers chan *dhtInfo // other goroutines put incoming dht updates here
reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks
// These next two could be replaced by a single linked list or similar...
table map[crypto.NodeID]*dhtInfo
imp []*dhtInfo
@@ -72,9 +79,16 @@ type dht struct {
// Initializes the DHT.
func (t *dht) init(c *Core) {
t.core = c
t.nodeID = *t.core.GetNodeID()
t.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-t.reconfigure
e <- nil
}
}()
t.nodeID = *t.core.NodeID()
t.peers = make(chan *dhtInfo, 1024)
t.callbacks = make(map[dhtReqKey]dht_callbackInfo)
t.callbacks = make(map[dhtReqKey][]dht_callbackInfo)
t.reset()
}
@@ -134,6 +148,16 @@ func (t *dht) insert(info *dhtInfo) {
t.table[*info.getNodeID()] = info
}
// Insert a peer into the table if it hasn't been pinged lately, to keep peers from dropping
func (t *dht) insertPeer(info *dhtInfo) {
oldInfo, isIn := t.table[*info.getNodeID()]
if !isIn || time.Since(oldInfo.recv) > dht_max_delay+30*time.Second {
// TODO? also check coords?
newInfo := *info // Insert a copy
t.insert(&newInfo)
}
}
// Return true if first/second/third are (partially) ordered correctly.
func dht_ordered(first, second, third *crypto.NodeID) bool {
lessOrEqual := func(first, second *crypto.NodeID) bool {
@@ -185,6 +209,14 @@ func (t *dht) handleReq(req *dhtReq) {
if _, isIn := t.table[*info.getNodeID()]; !isIn && t.isImportant(&info) {
t.ping(&info, nil)
}
// Maybe mark nodes from lookup as dirty
if req.Dest != *info.getNodeID() {
// This node asked about someone other than themself, so this wasn't just idle traffic.
for _, info := range res.Infos {
// Mark nodes dirty so we're sure to check up on them again later
info.dirty = true
}
}
}
// Sends a lookup response to the specified node.
@@ -212,15 +244,17 @@ type dht_callbackInfo struct {
// Adds a callback and removes it after some timeout.
func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) {
info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)}
t.callbacks[*rq] = info
t.callbacks[*rq] = append(t.callbacks[*rq], info)
}
// Reads a lookup response, checks that we had sent a matching request, and processes the response info.
// This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses
func (t *dht) handleRes(res *dhtRes) {
rq := dhtReqKey{res.Key, res.Dest}
if callback, isIn := t.callbacks[rq]; isIn {
callback.f(res)
if callbacks, isIn := t.callbacks[rq]; isIn {
for _, callback := range callbacks {
callback.f(res)
}
delete(t.callbacks, rq)
}
_, isIn := t.reqs[rq]
@@ -294,27 +328,45 @@ func (t *dht) doMaintenance() {
}
}
t.reqs = newReqs
newCallbacks := make(map[dhtReqKey]dht_callbackInfo, len(t.callbacks))
for key, callback := range t.callbacks {
if now.Before(callback.time) {
newCallbacks[key] = callback
newCallbacks := make(map[dhtReqKey][]dht_callbackInfo, len(t.callbacks))
for key, cs := range t.callbacks {
for _, c := range cs {
if now.Before(c.time) {
newCallbacks[key] = append(newCallbacks[key], c)
} else {
// Signal failure
c.f(nil)
}
}
}
t.callbacks = newCallbacks
for infoID, info := range t.table {
if now.Sub(info.recv) > time.Minute || info.pings > 3 {
switch {
case info.pings > 6:
// It failed to respond to too many pings
fallthrough
case now.Sub(info.recv) > dht_timeout:
// It's too old
fallthrough
case info.dirty && now.Sub(info.recv) > dht_max_delay_dirty && !t.isImportant(info):
// We won't ping it to refresh it, so just drop it
delete(t.table, infoID)
t.imp = nil
}
}
for _, info := range t.getImportant() {
if now.Sub(info.recv) > info.throttle {
switch {
case now.Sub(info.recv) > info.throttle:
info.throttle *= 2
if info.throttle < time.Second {
info.throttle = time.Second
} else if info.throttle > dht_max_delay {
info.throttle = dht_max_delay
}
fallthrough
case info.dirty && now.Sub(info.recv) > dht_max_delay_dirty:
t.ping(info, nil)
info.pings++
info.throttle += time.Second
if info.throttle > 30*time.Second {
info.throttle = 30 * time.Second
}
}
}
}

View File

@@ -1,58 +0,0 @@
package yggdrasil
import (
"net"
"time"
)
// wrappedConn implements net.Conn
type wrappedConn struct {
c net.Conn
raddr net.Addr
}
// wrappedAddr implements net.Addr
type wrappedAddr struct {
network string
addr string
}
func (a *wrappedAddr) Network() string {
return a.network
}
func (a *wrappedAddr) String() string {
return a.addr
}
func (c *wrappedConn) Write(data []byte) (int, error) {
return c.c.Write(data)
}
func (c *wrappedConn) Read(data []byte) (int, error) {
return c.c.Read(data)
}
func (c *wrappedConn) SetDeadline(t time.Time) error {
return c.c.SetDeadline(t)
}
func (c *wrappedConn) SetReadDeadline(t time.Time) error {
return c.c.SetReadDeadline(t)
}
func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
return c.c.SetWriteDeadline(t)
}
func (c *wrappedConn) Close() error {
return c.c.Close()
}
func (c *wrappedConn) LocalAddr() net.Addr {
return c.c.LocalAddr()
}
func (c *wrappedConn) RemoteAddr() net.Addr {
return c.raddr
}

77
src/yggdrasil/dialer.go Normal file
View File

@@ -0,0 +1,77 @@
package yggdrasil
import (
"encoding/hex"
"errors"
"strconv"
"strings"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
)
// Dialer represents an Yggdrasil connection dialer.
type Dialer struct {
core *Core
}
// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context
// Dial opens a session to the given node. The first paramter should be "nodeid"
// and the second parameter should contain a hexadecimal representation of the
// target node ID.
func (d *Dialer) Dial(network, address string) (*Conn, error) {
var nodeID crypto.NodeID
var nodeMask crypto.NodeID
// Process
switch network {
case "nodeid":
// A node ID was provided - we don't need to do anything special with it
if tokens := strings.Split(address, "/"); len(tokens) == 2 {
len, err := strconv.Atoi(tokens[1])
if err != nil {
return nil, err
}
dest, err := hex.DecodeString(tokens[0])
if err != nil {
return nil, err
}
copy(nodeID[:], dest)
for idx := 0; idx < len; idx++ {
nodeMask[idx/8] |= 0x80 >> byte(idx%8)
}
} else {
dest, err := hex.DecodeString(tokens[0])
if err != nil {
return nil, err
}
copy(nodeID[:], dest)
for i := range nodeMask {
nodeMask[i] = 0xFF
}
}
return d.DialByNodeIDandMask(&nodeID, &nodeMask)
default:
// An unexpected address type was given, so give up
return nil, errors.New("unexpected address type")
}
}
// DialByNodeIDandMask opens a session to the given node based on raw
// NodeID parameters.
func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) {
conn := newConn(d.core, nodeID, nodeMask, nil)
if err := conn.search(); err != nil {
conn.Close()
return nil, err
}
t := time.NewTimer(6 * time.Second) // TODO use a context instead
defer t.Stop()
select {
case <-conn.session.init:
return conn, nil
case <-t.C:
conn.Close()
return nil, errors.New("session handshake timeout")
}
}

418
src/yggdrasil/link.go Normal file
View File

@@ -0,0 +1,418 @@
package yggdrasil
import (
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"net/url"
"strings"
"sync"
//"sync/atomic"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
type link struct {
core *Core
reconfigure chan chan error
mutex sync.RWMutex // protects interfaces below
interfaces map[linkInfo]*linkInterface
tcp tcp // TCP interface support
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
type linkInfo struct {
box crypto.BoxPubKey // Their encryption key
sig crypto.SigPubKey // Their signing key
linkType string // Type of link, e.g. TCP, AWDL
local string // Local name or address
remote string // Remote name or address
}
type linkInterfaceMsgIO interface {
readMsg() ([]byte, error)
writeMsgs([][]byte) (int, error)
close() error
// These are temporary workarounds to stream semantics
_sendMetaBytes([]byte) error
_recvMetaBytes() ([]byte, error)
}
type linkInterface struct {
name string
link *link
peer *peer
msgIO linkInterfaceMsgIO
info linkInfo
incoming bool
force bool
closed chan struct{}
}
func (l *link) init(c *Core) error {
l.core = c
l.mutex.Lock()
l.interfaces = make(map[linkInfo]*linkInterface)
l.reconfigure = make(chan chan error)
l.mutex.Unlock()
if err := l.tcp.init(l); err != nil {
c.log.Errorln("Failed to start TCP interface")
return err
}
go func() {
for {
e := <-l.reconfigure
tcpresponse := make(chan error)
l.tcp.reconfigure <- tcpresponse
if err := <-tcpresponse; err != nil {
e <- err
continue
}
e <- nil
}
}()
return nil
}
func (l *link) call(uri string, sintf string) error {
u, err := url.Parse(uri)
if err != nil {
return err
}
pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/")
switch u.Scheme {
case "tcp":
l.tcp.call(u.Host, nil, sintf)
case "socks":
l.tcp.call(pathtokens[0], u.Host, sintf)
default:
return errors.New("unknown call scheme: " + u.Scheme)
}
return nil
}
func (l *link) listen(uri string) error {
u, err := url.Parse(uri)
if err != nil {
return err
}
switch u.Scheme {
case "tcp":
_, err := l.tcp.listen(u.Host)
return err
default:
return errors.New("unknown listen scheme: " + u.Scheme)
}
}
func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) {
// Technically anything unique would work for names, but lets pick something human readable, just for debugging
intf := linkInterface{
name: name,
link: l,
msgIO: msgIO,
info: linkInfo{
linkType: linkType,
local: local,
remote: remote,
},
incoming: incoming,
force: force,
}
return &intf, nil
}
func (intf *linkInterface) handler() error {
// TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later
myLinkPub, myLinkPriv := crypto.NewBoxKeys()
meta := version_getBaseMetadata()
meta.box = intf.link.core.boxPub
meta.sig = intf.link.core.sigPub
meta.link = *myLinkPub
metaBytes := meta.encode()
// TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer)
var err error
if !util.FuncTimeout(func() { err = intf.msgIO._sendMetaBytes(metaBytes) }, 30*time.Second) {
return errors.New("timeout on metadata send")
}
if err != nil {
return err
}
if !util.FuncTimeout(func() { metaBytes, err = intf.msgIO._recvMetaBytes() }, 30*time.Second) {
return errors.New("timeout on metadata recv")
}
if err != nil {
return err
}
meta = version_metadata{}
if !meta.decode(metaBytes) || !meta.check() {
return errors.New("failed to decode metadata")
}
base := version_getBaseMetadata()
if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer {
intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
return errors.New("failed to connect: wrong version")
}
// Check if we're authorized to connect to this key / IP
if intf.incoming && !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) {
intf.link.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:]))
intf.msgIO.close()
return nil
}
// Check if we already have a link to this node
intf.info.box = meta.box
intf.info.sig = meta.sig
intf.link.mutex.Lock()
if oldIntf, isIn := intf.link.interfaces[intf.info]; isIn {
intf.link.mutex.Unlock()
// FIXME we should really return an error and let the caller block instead
// That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc.
intf.link.core.log.Debugln("DEBUG: found existing interface for", intf.name)
intf.msgIO.close()
if !intf.incoming {
// Block outgoing connection attempts until the existing connection closes
<-oldIntf.closed
}
return nil
} else {
intf.closed = make(chan struct{})
intf.link.interfaces[intf.info] = intf
defer func() {
intf.link.mutex.Lock()
delete(intf.link.interfaces, intf.info)
intf.link.mutex.Unlock()
close(intf.closed)
}()
intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name)
}
intf.link.mutex.Unlock()
// Create peer
shared := crypto.GetSharedKey(myLinkPriv, &meta.link)
intf.peer = intf.link.core.peers.newPeer(&meta.box, &meta.sig, shared, intf, func() { intf.msgIO.close() })
if intf.peer == nil {
return errors.New("failed to create peer")
}
defer func() {
// More cleanup can go here
intf.link.core.peers.removePeer(intf.peer.port)
}()
// Finish setting up the peer struct
out := make(chan [][]byte, 1)
defer close(out)
intf.peer.out = func(msgs [][]byte) {
defer func() { recover() }()
out <- msgs
}
intf.peer.linkOut = make(chan []byte, 1)
themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box))
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote)
intf.link.core.log.Infof("Connected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
// Start the link loop
go intf.peer.linkLoop()
// Start the writer
signalReady := make(chan struct{}, 1)
signalSent := make(chan bool, 1)
sendAck := make(chan struct{}, 1)
sendBlocked := time.NewTimer(time.Second)
defer util.TimerStop(sendBlocked)
util.TimerStop(sendBlocked)
go func() {
defer close(signalReady)
defer close(signalSent)
interval := 4 * time.Second
tcpTimer := time.NewTimer(interval) // used for backwards compat with old tcp
defer util.TimerStop(tcpTimer)
send := func(bss [][]byte) {
sendBlocked.Reset(time.Second)
size, _ := intf.msgIO.writeMsgs(bss)
util.TimerStop(sendBlocked)
select {
case signalSent <- size > 0:
default:
}
}
for {
// First try to send any link protocol traffic
select {
case msg := <-intf.peer.linkOut:
send([][]byte{msg})
continue
default:
}
// No protocol traffic to send, so reset the timer
util.TimerStop(tcpTimer)
tcpTimer.Reset(interval)
// Now block until something is ready or the timer triggers keepalive traffic
select {
case <-tcpTimer.C:
intf.link.core.log.Tracef("Sending (legacy) keep-alive to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send([][]byte{nil})
case <-sendAck:
intf.link.core.log.Tracef("Sending ack to %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
send([][]byte{nil})
case msg := <-intf.peer.linkOut:
send([][]byte{msg})
case msgs, ok := <-out:
if !ok {
return
}
send(msgs)
for _, msg := range msgs {
util.PutBytes(msg)
}
select {
case signalReady <- struct{}{}:
default:
}
//intf.link.core.log.Tracef("Sending packet to %s: %s, source %s",
// strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
}
}()
//intf.link.core.switchTable.idleIn <- intf.peer.port // notify switch that we're idle
// Used to enable/disable activity in the switch
signalAlive := make(chan bool, 1) // True = real packet, false = keep-alive
defer close(signalAlive)
ret := make(chan error, 1) // How we signal the return value when multiple goroutines are involved
go func() {
var isAlive bool
var isReady bool
var sendTimerRunning bool
var recvTimerRunning bool
recvTime := 6 * time.Second // TODO set to ReadTimeout from the config, reset if it gets changed
closeTime := 2 * switch_timeout // TODO or maybe this makes more sense for ReadTimeout?...
sendTime := time.Second
sendTimer := time.NewTimer(sendTime)
defer util.TimerStop(sendTimer)
recvTimer := time.NewTimer(recvTime)
defer util.TimerStop(recvTimer)
closeTimer := time.NewTimer(closeTime)
defer util.TimerStop(closeTimer)
for {
//intf.link.core.log.Debugf("State of %s: %s, source %s :: isAlive %t isReady %t sendTimerRunning %t recvTimerRunning %t",
// strings.ToUpper(intf.info.linkType), themString, intf.info.local,
// isAlive, isReady, sendTimerRunning, recvTimerRunning)
select {
case gotMsg, ok := <-signalAlive:
if !ok {
return
}
util.TimerStop(closeTimer)
closeTimer.Reset(closeTime)
util.TimerStop(recvTimer)
recvTimerRunning = false
isAlive = true
if !isReady {
// (Re-)enable in the switch
intf.link.core.switchTable.idleIn <- intf.peer.port
isReady = true
}
if gotMsg && !sendTimerRunning {
// We got a message
// Start a timer, if it expires then send a 0-sized ack to let them know we're alive
util.TimerStop(sendTimer)
sendTimer.Reset(sendTime)
sendTimerRunning = true
}
if !gotMsg {
intf.link.core.log.Tracef("Received ack from %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
case sentMsg, ok := <-signalSent:
// Stop any running ack timer
if !ok {
return
}
util.TimerStop(sendTimer)
sendTimerRunning = false
if sentMsg && !recvTimerRunning {
// We sent a message
// Start a timer, if it expires and we haven't gotten any return traffic (including a 0-sized ack), then assume there's a problem
util.TimerStop(recvTimer)
recvTimer.Reset(recvTime)
recvTimerRunning = true
}
case _, ok := <-signalReady:
if !ok {
return
}
if !isAlive {
// Disable in the switch
isReady = false
} else {
// Keep enabled in the switch
intf.link.core.switchTable.idleIn <- intf.peer.port
isReady = true
}
case <-sendBlocked.C:
// We blocked while trying to send something
isReady = false
intf.link.core.switchTable.blockPeer(intf.peer.port)
case <-sendTimer.C:
// We haven't sent anything, so signal a send of a 0 packet to let them know we're alive
select {
case sendAck <- struct{}{}:
default:
}
case <-recvTimer.C:
// We haven't received anything, so assume there's a problem and don't return this node to the switch until they start responding
isAlive = false
intf.link.core.switchTable.blockPeer(intf.peer.port)
case <-closeTimer.C:
// We haven't received anything in a really long time, so things have died at the switch level and then some...
// Just close the connection at this point...
select {
case ret <- errors.New("timeout"):
default:
}
intf.msgIO.close()
}
}
}()
// Run reader loop
for {
msg, err := intf.msgIO.readMsg()
if len(msg) > 0 {
intf.peer.handlePacket(msg)
}
if err != nil {
if err != io.EOF {
select {
case ret <- err:
default:
}
}
break
}
select {
case signalAlive <- len(msg) > 0:
default:
}
}
////////////////////////////////////////////////////////////////////////////////
// Remember to set `err` to something useful before returning
select {
case err = <-ret:
intf.link.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local, err)
default:
err = nil
intf.link.core.log.Infof("Disconnected %s: %s, source %s",
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
}
return err
}

45
src/yggdrasil/listener.go Normal file
View File

@@ -0,0 +1,45 @@
package yggdrasil
import (
"errors"
"net"
)
// Listener waits for incoming sessions
type Listener struct {
core *Core
conn chan *Conn
close chan interface{}
}
// Accept blocks until a new incoming session is received
func (l *Listener) Accept() (*Conn, error) {
select {
case c, ok := <-l.conn:
if !ok {
return nil, errors.New("listener closed")
}
return c, nil
case <-l.close:
return nil, errors.New("listener closed")
}
}
// Close will stop the listener
func (l *Listener) Close() (err error) {
defer func() {
recover()
err = errors.New("already closed")
}()
if l.core.sessions.listener == l {
l.core.sessions.listener = nil
}
close(l.close)
close(l.conn)
return nil
}
// Addr is not implemented for this type yet
func (l *Listener) Addr() net.Addr {
return nil
}

View File

@@ -1,162 +0,0 @@
package yggdrasil
import (
"context"
"fmt"
"net"
"time"
"golang.org/x/net/ipv6"
)
type multicast struct {
core *Core
sock *ipv6.PacketConn
groupAddr string
}
func (m *multicast) init(core *Core) {
m.core = core
m.groupAddr = "[ff02::114]:9001"
// Check if we've been given any expressions
if len(m.core.ifceExpr) == 0 {
return
}
// Ask the system for network interfaces
m.core.log.Println("Found", len(m.interfaces()), "multicast interface(s)")
}
func (m *multicast) start() error {
if len(m.core.ifceExpr) == 0 {
m.core.log.Println("Multicast discovery is disabled")
} else {
m.core.log.Println("Multicast discovery is enabled")
addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil {
return err
}
listenString := fmt.Sprintf("[::]:%v", addr.Port)
lc := net.ListenConfig{
Control: multicastReuse,
}
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
if err != nil {
return err
}
m.sock = ipv6.NewPacketConn(conn)
if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil {
// Windows can't set this flag, so we need to handle it in other ways
}
go m.listen()
go m.announce()
}
return nil
}
func (m *multicast) interfaces() []net.Interface {
// Ask the system for network interfaces
var interfaces []net.Interface
allifaces, err := net.Interfaces()
if err != nil {
panic(err)
}
// Work out which interfaces to announce on
for _, iface := range allifaces {
if iface.Flags&net.FlagUp == 0 {
// Ignore interfaces that are down
continue
}
if iface.Flags&net.FlagMulticast == 0 {
// Ignore non-multicast interfaces
continue
}
if iface.Flags&net.FlagPointToPoint != 0 {
// Ignore point-to-point interfaces
continue
}
for _, expr := range m.core.ifceExpr {
if expr.MatchString(iface.Name) {
interfaces = append(interfaces, iface)
}
}
}
return interfaces
}
func (m *multicast) announce() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
}
var anAddr net.TCPAddr
myAddr := m.core.tcp.getAddr()
anAddr.Port = myAddr.Port
destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
}
for {
for _, iface := range m.interfaces() {
m.sock.JoinGroup(&iface, groupAddr)
addrs, err := iface.Addrs()
if err != nil {
panic(err)
}
for _, addr := range addrs {
addrIP, _, _ := net.ParseCIDR(addr.String())
if addrIP.To4() != nil {
continue
} // IPv6 only
if !addrIP.IsLinkLocalUnicast() {
continue
}
anAddr.IP = addrIP
anAddr.Zone = iface.Name
destAddr.Zone = iface.Name
msg := []byte(anAddr.String())
m.sock.WriteTo(msg, nil, destAddr)
break
}
time.Sleep(time.Second)
}
time.Sleep(time.Second)
}
}
func (m *multicast) listen() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
}
bs := make([]byte, 2048)
for {
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil {
panic(err)
}
if rcm != nil {
// Windows can't set the flag needed to return a non-nil value here
// So only make these checks if we get something useful back
// TODO? Skip them always, I'm not sure if they're really needed...
if !rcm.Dst.IsLinkLocalMulticast() {
continue
}
if !rcm.Dst.Equal(groupAddr.IP) {
continue
}
}
anAddr := string(bs[:nBytes])
addr, err := net.ResolveTCPAddr("tcp6", anAddr)
if err != nil {
continue
}
from := fromAddr.(*net.UDPAddr)
if addr.IP.String() != from.IP.String() {
continue
}
addr.Zone = from.Zone
saddr := addr.String()
m.core.tcp.connect(saddr, "")
}
}

View File

@@ -1,9 +0,0 @@
// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows
package yggdrasil
import "syscall"
func multicastReuse(network string, address string, c syscall.RawConn) error {
return nil
}

View File

@@ -4,15 +4,17 @@ import (
"encoding/json"
"errors"
"runtime"
"strings"
"sync"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
)
type nodeinfo struct {
core *Core
myNodeInfo nodeinfoPayload
myNodeInfo NodeInfoPayload
myNodeInfoMutex sync.RWMutex
callbacks map[crypto.BoxPubKey]nodeinfoCallback
callbacksMutex sync.Mutex
@@ -20,15 +22,13 @@ type nodeinfo struct {
cacheMutex sync.RWMutex
}
type nodeinfoPayload []byte
type nodeinfoCached struct {
payload nodeinfoPayload
payload NodeInfoPayload
created time.Time
}
type nodeinfoCallback struct {
call func(nodeinfo *nodeinfoPayload)
call func(nodeinfo *NodeInfoPayload)
created time.Time
}
@@ -37,7 +37,7 @@ type nodeinfoReqRes struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
SendCoords []byte // Sender's coords
IsResponse bool
NodeInfo nodeinfoPayload
NodeInfo NodeInfoPayload
}
// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep
@@ -69,7 +69,7 @@ func (m *nodeinfo) init(core *Core) {
}
// Add a callback for a nodeinfo lookup
func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *nodeinfoPayload)) {
func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
m.callbacks[sender] = nodeinfoCallback{
@@ -79,7 +79,7 @@ func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *node
}
// Handles the callback, if there is one
func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) {
func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
if callback, ok := m.callbacks[sender]; ok {
@@ -89,25 +89,34 @@ func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) {
}
// Get the current node's nodeinfo
func (m *nodeinfo) getNodeInfo() nodeinfoPayload {
func (m *nodeinfo) getNodeInfo() NodeInfoPayload {
m.myNodeInfoMutex.RLock()
defer m.myNodeInfoMutex.RUnlock()
return m.myNodeInfo
}
// Set the current node's nodeinfo
func (m *nodeinfo) setNodeInfo(given interface{}) error {
func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error {
m.myNodeInfoMutex.Lock()
defer m.myNodeInfoMutex.Unlock()
newnodeinfo := map[string]interface{}{
"buildname": GetBuildName(),
"buildversion": GetBuildVersion(),
defaults := map[string]interface{}{
"buildname": version.BuildName(),
"buildversion": version.BuildVersion(),
"buildplatform": runtime.GOOS,
"buildarch": runtime.GOARCH,
}
newnodeinfo := make(map[string]interface{})
if !privacy {
for k, v := range defaults {
newnodeinfo[k] = v
}
}
if nodeinfomap, ok := given.(map[string]interface{}); ok {
for key, value := range nodeinfomap {
if _, ok := newnodeinfo[key]; ok {
if _, ok := defaults[key]; ok {
if strvalue, strok := value.(string); strok && strings.EqualFold(strvalue, "null") || value == nil {
delete(newnodeinfo, key)
}
continue
}
newnodeinfo[key] = value
@@ -125,7 +134,7 @@ func (m *nodeinfo) setNodeInfo(given interface{}) error {
}
// Add nodeinfo into the cache for a node
func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPayload) {
func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) {
m.cacheMutex.Lock()
defer m.cacheMutex.Unlock()
m.cache[key] = nodeinfoCached{
@@ -135,13 +144,13 @@ func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPaylo
}
// Get a nodeinfo entry from the cache
func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (nodeinfoPayload, error) {
func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) {
m.cacheMutex.RLock()
defer m.cacheMutex.RUnlock()
if nodeinfo, ok := m.cache[key]; ok {
return nodeinfo.payload, nil
}
return nodeinfoPayload{}, errors.New("No cache entry found")
return NodeInfoPayload{}, errors.New("No cache entry found")
}
// Handles a nodeinfo request/response - called from the router
@@ -160,7 +169,7 @@ func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse
nodeinfo := nodeinfoReqRes{
SendCoords: table.self.getCoords(),
IsResponse: isResponse,
NodeInfo: m.core.nodeinfo.getNodeInfo(),
NodeInfo: m.getNodeInfo(),
}
bs := nodeinfo.encode()
shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key)

View File

@@ -5,6 +5,7 @@ package yggdrasil
// Live code should be better commented
import (
"encoding/hex"
"sync"
"sync/atomic"
"time"
@@ -14,15 +15,14 @@ import (
)
// The peers struct represents peers with an active connection.
// Incomping packets are passed to the corresponding peer, which handles them somehow.
// Incoming packets are passed to the corresponding peer, which handles them somehow.
// In most cases, this involves passing the packet to the handler for outgoing traffic to another peer.
// In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch.
type peers struct {
core *Core
mutex sync.Mutex // Synchronize writes to atomic
ports atomic.Value //map[switchPort]*peer, use CoW semantics
authMutex sync.RWMutex
allowedEncryptionPublicKeys map[crypto.BoxPubKey]struct{}
core *Core
reconfigure chan chan error
mutex sync.Mutex // Synchronize writes to atomic
ports atomic.Value //map[switchPort]*peer, use CoW semantics
}
// Initializes the peers struct.
@@ -31,40 +31,55 @@ func (ps *peers) init(c *Core) {
defer ps.mutex.Unlock()
ps.putPorts(make(map[switchPort]*peer))
ps.core = c
ps.allowedEncryptionPublicKeys = make(map[crypto.BoxPubKey]struct{})
ps.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-ps.reconfigure
e <- nil
}
}()
}
// Returns true if an incoming peer connection to a key is allowed, either because the key is in the whitelist or because the whitelist is empty.
// Returns true if an incoming peer connection to a key is allowed, either
// because the key is in the whitelist or because the whitelist is empty.
func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool {
ps.authMutex.RLock()
defer ps.authMutex.RUnlock()
_, isIn := ps.allowedEncryptionPublicKeys[*box]
return isIn || len(ps.allowedEncryptionPublicKeys) == 0
boxstr := hex.EncodeToString(box[:])
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
if v == boxstr {
return true
}
}
return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0
}
// Adds a key to the whitelist.
func (ps *peers) addAllowedEncryptionPublicKey(box *crypto.BoxPubKey) {
ps.authMutex.Lock()
defer ps.authMutex.Unlock()
ps.allowedEncryptionPublicKeys[*box] = struct{}{}
func (ps *peers) addAllowedEncryptionPublicKey(box string) {
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
ps.core.config.Current.AllowedEncryptionPublicKeys =
append(ps.core.config.Current.AllowedEncryptionPublicKeys, box)
}
// Removes a key from the whitelist.
func (ps *peers) removeAllowedEncryptionPublicKey(box *crypto.BoxPubKey) {
ps.authMutex.Lock()
defer ps.authMutex.Unlock()
delete(ps.allowedEncryptionPublicKeys, *box)
func (ps *peers) removeAllowedEncryptionPublicKey(box string) {
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
if v == box {
ps.core.config.Current.AllowedEncryptionPublicKeys =
append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k],
ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...)
}
}
}
// Gets the whitelist of allowed keys for incoming connections.
func (ps *peers) getAllowedEncryptionPublicKeys() []crypto.BoxPubKey {
ps.authMutex.RLock()
defer ps.authMutex.RUnlock()
keys := make([]crypto.BoxPubKey, 0, len(ps.allowedEncryptionPublicKeys))
for key := range ps.allowedEncryptionPublicKeys {
keys = append(keys, key)
}
return keys
func (ps *peers) getAllowedEncryptionPublicKeys() []string {
ps.core.config.Mutex.RLock()
defer ps.core.config.Mutex.RUnlock()
return ps.core.config.Current.AllowedEncryptionPublicKeys
}
// Atomically gets a map[switchPort]*peer of known peers.
@@ -83,6 +98,7 @@ type peer struct {
bytesRecvd uint64 // To track bandwidth usage for getPeers
// BUG: sync/atomic, 32 bit platforms need the above to be the first element
core *Core
intf *linkInterface
port switchPort
box crypto.BoxPubKey
sig crypto.SigPubKey
@@ -93,22 +109,24 @@ type peer struct {
linkOut (chan []byte) // used for protocol traffic (to bypass queues)
doSend (chan struct{}) // tell the linkLoop to send a switchMsg
dinfo (chan *dhtInfo) // used to keep the DHT working
out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
out func([][]byte) // Set up by whatever created the peers struct, used to send packets to other nodes
close func() // Called when a peer is removed, to close the underlying connection, or via admin api
}
// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number.
func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string) *peer {
// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number.
func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, intf *linkInterface, closer func()) *peer {
now := time.Now()
p := peer{box: *box,
sig: *sig,
shared: *crypto.GetSharedKey(&ps.core.boxPriv, box),
linkShared: *linkShared,
endpoint: endpoint,
firstSeen: now,
doSend: make(chan struct{}, 1),
dinfo: make(chan *dhtInfo, 1),
core: ps.core}
close: closer,
core: ps.core,
intf: intf,
}
ps.mutex.Lock()
defer ps.mutex.Unlock()
oldPorts := ps.getPorts()
@@ -217,6 +235,7 @@ func (p *peer) handlePacket(packet []byte) {
default:
util.PutBytes(packet)
}
return
}
// Called to handle traffic or protocolTraffic packets.
@@ -231,10 +250,15 @@ func (p *peer) handleTraffic(packet []byte, pTypeLen int) {
}
// This just calls p.out(packet) for now.
func (p *peer) sendPacket(packet []byte) {
func (p *peer) sendPackets(packets [][]byte) {
// Is there ever a case where something more complicated is needed?
// What if p.out blocks?
p.out(packet)
var size int
for _, packet := range packets {
size += len(packet)
}
atomic.AddUint64(&p.bytesSent, uint64(size))
p.out(packets)
}
// This wraps the packet in the inner (ephemeral) and outer (permanent) crypto layers.
@@ -341,7 +365,7 @@ func (p *peer) handleSwitchMsg(packet []byte) {
}
// This generates the bytes that we sign or check the signature of for a switchMsg.
// It begins with the next node's key, followed by the root and the timetsamp, followed by coords being advertised to the next node.
// It begins with the next node's key, followed by the root and the timestamp, followed by coords being advertised to the next node.
func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte {
var loc switchLocator
for _, hop := range msg.Hops {

View File

@@ -5,7 +5,7 @@ package yggdrasil
// TODO clean up old/unused code, maybe improve comments on whatever is left
// Send:
// Receive a packet from the tun
// Receive a packet from the adapter
// Look up session (if none exists, trigger a search)
// Hand off to session (which encrypts, etc)
// Session will pass it back to router.out, which hands it off to the self peer
@@ -20,81 +20,92 @@ package yggdrasil
// If it's dht/seach/etc. traffic, the router passes it to that part
// If it's an encapsulated IPv6 packet, the router looks up the session for it
// The packet is passed to the session, which decrypts it, router.recvPacket
// The router then runs some sanity checks before passing it to the tun
// The router then runs some sanity checks before passing it to the adapter
import (
"bytes"
"time"
//"bytes"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
type router struct {
core *Core
addr address.Address
subnet address.Subnet
in <-chan []byte // packets we received from the network, link to peer's "out"
out func([]byte) // packets we're sending to the network, link to peer's "in"
toRecv chan router_recvPacket // packets to handle via recvPacket()
tun tunAdapter // TUN/TAP adapter
adapters []Adapter // Other adapters
recv chan<- []byte // place where the tun pulls received packets from
send <-chan []byte // place where the tun puts outgoing packets
reset chan struct{} // signal that coords changed (re-init sessions/dht)
admin chan func() // pass a lambda for the admin socket to query stuff
cryptokey cryptokey
core *Core
reconfigure chan chan error
addr address.Address
subnet address.Subnet
in <-chan [][]byte // packets we received from the network, link to peer's "out"
out func([]byte) // packets we're sending to the network, link to peer's "in"
reset chan struct{} // signal that coords changed (re-init sessions/dht)
admin chan func() // pass a lambda for the admin socket to query stuff
nodeinfo nodeinfo
}
// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the tun.
type router_recvPacket struct {
bs []byte
sinfo *sessionInfo
}
// Initializes the router struct, which includes setting up channels to/from the tun/tap.
// Initializes the router struct, which includes setting up channels to/from the adapter.
func (r *router) init(core *Core) {
r.core = core
r.reconfigure = make(chan chan error, 1)
r.addr = *address.AddrForNodeID(&r.core.dht.nodeID)
r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID)
in := make(chan []byte, 32) // TODO something better than this...
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)")
p.out = func(packet []byte) {
// This is to make very sure it never blocks
select {
case in <- packet:
return
default:
util.PutBytes(packet)
}
in := make(chan [][]byte, 1) // TODO something better than this...
self := linkInterface{
name: "(self)",
info: linkInfo{
local: "(self)",
remote: "(self)",
linkType: "self",
},
}
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, &self, nil)
p.out = func(packets [][]byte) { in <- packets }
r.in = in
r.out = func(packet []byte) { p.handlePacket(packet) } // The caller is responsible for go-ing if it needs to not block
r.toRecv = make(chan router_recvPacket, 32)
recv := make(chan []byte, 32)
send := make(chan []byte, 32)
r.recv = recv
r.send = send
out := make(chan []byte, 32)
go func() {
for packet := range out {
p.handlePacket(packet)
}
}()
out2 := make(chan []byte, 32)
go func() {
// This worker makes sure r.out never blocks
// It will buffer traffic long enough for the switch worker to take it
// If (somehow) you can send faster than the switch can receive, then this would use unbounded memory
// But crypto slows sends enough that the switch should always be able to take the packets...
var buf [][]byte
for {
buf = append(buf, <-out2)
for len(buf) > 0 {
select {
case bs := <-out2:
buf = append(buf, bs)
case out <- buf[0]:
buf = buf[1:]
}
}
}
}()
r.out = func(packet []byte) { out2 <- packet }
r.reset = make(chan struct{}, 1)
r.admin = make(chan func(), 32)
r.cryptokey.init(r.core)
r.tun.init(r.core, send, recv)
r.nodeinfo.init(r.core)
r.core.config.Mutex.RLock()
r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy)
r.core.config.Mutex.RUnlock()
}
// Starts the mainLoop goroutine.
func (r *router) start() error {
r.core.log.Println("Starting router")
r.core.log.Infoln("Starting router")
go r.mainLoop()
return nil
}
// Takes traffic from the tun/tap and passes it to router.send, or from r.in and handles incoming traffic.
// Takes traffic from the adapter and passes it to router.send, or from r.in and handles incoming traffic.
// Also adds new peer info to the DHT.
// Also resets the DHT and sesssions in the event of a coord change.
// Also does periodic maintenance stuff.
@@ -103,21 +114,14 @@ func (r *router) mainLoop() {
defer ticker.Stop()
for {
select {
case rp := <-r.toRecv:
r.recvPacket(rp.bs, rp.sinfo)
case p := <-r.in:
r.handleIn(p)
case p := <-r.send:
r.sendPacket(p)
case info := <-r.core.dht.peers:
now := time.Now()
oldInfo, isIn := r.core.dht.table[*info.getNodeID()]
r.core.dht.insert(info)
if isIn && now.Sub(oldInfo.recv) < 45*time.Second {
info.recv = oldInfo.recv
case ps := <-r.in:
for _, p := range ps {
r.handleIn(p)
}
case info := <-r.core.dht.peers:
r.core.dht.insertPeer(info)
case <-r.reset:
r.core.sessions.resetInits()
r.core.sessions.reset()
r.core.dht.reset()
case <-ticker.C:
{
@@ -125,233 +129,16 @@ func (r *router) mainLoop() {
r.core.switchTable.doMaintenance()
r.core.dht.doMaintenance()
r.core.sessions.cleanup()
util.GetBytes() // To slowly drain things
}
case f := <-r.admin:
f()
case e := <-r.reconfigure:
current := r.core.config.GetCurrent()
e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy)
}
}
}
// Checks a packet's to/from address to make sure it's in the allowed range.
// If a session to the destination exists, gets the session and passes the packet to it.
// If no session exists, it triggers (or continues) a search.
// If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly.
// It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled.
func (r *router) sendPacket(bs []byte) {
var sourceAddr address.Address
var destAddr address.Address
var destSnet address.Subnet
var destPubKey *crypto.BoxPubKey
var destNodeID *crypto.NodeID
var addrlen int
if bs[0]&0xf0 == 0x60 {
// Check if we have a fully-sized header
if len(bs) < 40 {
panic("Tried to send a packet shorter than an IPv6 header...")
}
// IPv6 address
addrlen = 16
copy(sourceAddr[:addrlen], bs[8:])
copy(destAddr[:addrlen], bs[24:])
copy(destSnet[:addrlen/2], bs[24:])
} else if bs[0]&0xf0 == 0x40 {
// Check if we have a fully-sized header
if len(bs) < 20 {
panic("Tried to send a packet shorter than an IPv4 header...")
}
// IPv4 address
addrlen = 4
copy(sourceAddr[:addrlen], bs[12:])
copy(destAddr[:addrlen], bs[16:])
} else {
// Unknown address length
return
}
if !r.cryptokey.isValidSource(sourceAddr, addrlen) {
// The packet had a source address that doesn't belong to us or our
// configured crypto-key routing source subnets
return
}
if !destAddr.IsValid() && !destSnet.IsValid() {
// The addresses didn't match valid Yggdrasil node addresses so let's see
// whether it matches a crypto-key routing range instead
if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil {
// A public key was found, get the node ID for the search
destPubKey = &key
destNodeID = crypto.GetNodeID(destPubKey)
// Do a quick check to ensure that the node ID refers to a vaild Yggdrasil
// address or subnet - this might be superfluous
addr := *address.AddrForNodeID(destNodeID)
copy(destAddr[:], addr[:])
copy(destSnet[:], addr[:])
if !destAddr.IsValid() && !destSnet.IsValid() {
return
}
} else {
// No public key was found in the CKR table so we've exhausted our options
return
}
}
doSearch := func(packet []byte) {
var nodeID, mask *crypto.NodeID
switch {
case destNodeID != nil:
// We already know the full node ID, probably because it's from a CKR
// route in which the public key is known ahead of time
nodeID = destNodeID
var m crypto.NodeID
for i := range m {
m[i] = 0xFF
}
mask = &m
case destAddr.IsValid():
// We don't know the full node ID - try and use the address to generate
// a truncated node ID
nodeID, mask = destAddr.GetNodeIDandMask()
case destSnet.IsValid():
// We don't know the full node ID - try and use the subnet to generate
// a truncated node ID
nodeID, mask = destSnet.GetNodeIDandMask()
default:
return
}
sinfo, isIn := r.core.searches.searches[*nodeID]
if !isIn {
sinfo = r.core.searches.newIterSearch(nodeID, mask)
}
if packet != nil {
sinfo.packet = packet
}
r.core.searches.continueSearch(sinfo)
}
var sinfo *sessionInfo
var isIn bool
if destAddr.IsValid() {
sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr)
}
if destSnet.IsValid() {
sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet)
}
switch {
case !isIn || !sinfo.init:
// No or unintiialized session, so we need to search first
doSearch(bs)
case time.Since(sinfo.time) > 6*time.Second:
if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
// We haven't heard from the dest in a while
// We tried pinging but didn't get a response
// They may have changed coords
// Try searching to discover new coords
// Note that search spam is throttled internally
doSearch(nil)
} else {
// We haven't heard about the dest in a while
now := time.Now()
if !sinfo.time.Before(sinfo.pingTime) {
// Update pingTime to start the clock for searches (above)
sinfo.pingTime = now
}
if time.Since(sinfo.pingSend) > time.Second {
// Send at most 1 ping per second
sinfo.pingSend = now
r.core.sessions.sendPingPong(sinfo, false)
}
}
fallthrough // Also send the packet
default:
// If we know the public key ahead of time (i.e. a CKR route) then check
// if the session perm pub key matches before we send the packet to it
if destPubKey != nil {
if !bytes.Equal((*destPubKey)[:], sinfo.theirPermPub[:]) {
return
}
}
// Drop packets if the session MTU is 0 - this means that one or other
// side probably has their TUN adapter disabled
if sinfo.getMTU() == 0 {
// Don't continue - drop the packet
return
}
// Generate an ICMPv6 Packet Too Big for packets larger than session MTU
if len(bs) > int(sinfo.getMTU()) {
// Get the size of the oversized payload, up to a max of 900 bytes
window := 900
if int(sinfo.getMTU()) < window {
window = int(sinfo.getMTU())
}
// Create the Packet Too Big response
ptb := &icmp.PacketTooBig{
MTU: int(sinfo.getMTU()),
Data: bs[:window],
}
// Create the ICMPv6 response from it
icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun(
bs[8:24], bs[24:40],
ipv6.ICMPTypePacketTooBig, 0, ptb)
if err == nil {
r.recv <- icmpv6Buf
}
// Don't continue - drop the packet
return
}
sinfo.send <- bs
}
}
// Called for incoming traffic by the session worker for that connection.
// Checks that the IP address is correct (matches the session) and passes the packet to the tun/tap.
func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) {
// Note: called directly by the session worker, not the router goroutine
if len(bs) < 24 {
util.PutBytes(bs)
return
}
var sourceAddr address.Address
var dest address.Address
var snet address.Subnet
var addrlen int
if bs[0]&0xf0 == 0x60 {
// IPv6 address
addrlen = 16
copy(sourceAddr[:addrlen], bs[8:])
copy(dest[:addrlen], bs[24:])
copy(snet[:addrlen/2], bs[8:])
} else if bs[0]&0xf0 == 0x40 {
// IPv4 address
addrlen = 4
copy(sourceAddr[:addrlen], bs[12:])
copy(dest[:addrlen], bs[16:])
} else {
// Unknown address length
return
}
// Check that the packet is destined for either our Yggdrasil address or
// subnet, or that it matches one of the crypto-key routing source routes
if !r.cryptokey.isValidSource(dest, addrlen) {
util.PutBytes(bs)
return
}
// See whether the packet they sent should have originated from this session
switch {
case sourceAddr.IsValid() && sourceAddr == sinfo.theirAddr:
case snet.IsValid() && snet == sinfo.theirSubnet:
default:
key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen)
if err != nil || key != sinfo.theirPermPub {
util.PutBytes(bs)
return
}
}
//go func() { r.recv<-bs }()
r.recv <- bs
}
// Checks incoming traffic type and passes it to the appropriate handler.
func (r *router) handleIn(packet []byte) {
pType, pTypeLen := wire_decode_uint64(packet)
@@ -368,7 +155,7 @@ func (r *router) handleIn(packet []byte) {
}
// Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets.
// Passes them to the crypto session worker to be decrypted and sent to the tun/tap.
// Passes them to the crypto session worker to be decrypted and sent to the adapter.
func (r *router) handleTraffic(packet []byte) {
defer util.PutBytes(packet)
p := wire_trafficPacket{}
@@ -377,9 +164,14 @@ func (r *router) handleTraffic(packet []byte) {
}
sinfo, isIn := r.core.sessions.getSessionForHandle(&p.Handle)
if !isIn {
util.PutBytes(p.Payload)
return
}
sinfo.recv <- &p
select {
case sinfo.fromRouter <- p:
case <-sinfo.cancel.Finished():
util.PutBytes(p.Payload)
}
}
// Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type.
@@ -402,7 +194,7 @@ func (r *router) handleProto(packet []byte) {
return
}
// Now do something with the bytes in bs...
// send dht messages to dht, sessionRefresh to sessions, data to tun...
// send dht messages to dht, sessionRefresh to sessions, data to adapter...
// For data, should check that key and IP match...
bsType, bsTypeLen := wire_decode_uint64(bs)
if bsTypeLen == 0 {
@@ -468,7 +260,7 @@ func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) {
return
}
req.SendPermPub = *fromKey
r.core.nodeinfo.handleNodeInfo(&req)
r.nodeinfo.handleNodeInfo(&req)
}
// Passed a function to call.

View File

@@ -15,6 +15,7 @@ package yggdrasil
// Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there?
import (
"errors"
"sort"
"time"
@@ -30,40 +31,46 @@ const search_MAX_SEARCH_SIZE = 16
const search_RETRY_TIME = time.Second
// Information about an ongoing search.
// Includes the targed NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
type searchInfo struct {
dest crypto.NodeID
mask crypto.NodeID
time time.Time
packet []byte
toVisit []*dhtInfo
visited map[crypto.NodeID]bool
core *Core
dest crypto.NodeID
mask crypto.NodeID
time time.Time
toVisit []*dhtInfo
visited map[crypto.NodeID]bool
callback func(*sessionInfo, error)
// TODO context.Context for timeout and cancellation
}
// This stores a map of active searches.
type searches struct {
core *Core
searches map[crypto.NodeID]*searchInfo
core *Core
reconfigure chan chan error
searches map[crypto.NodeID]*searchInfo
}
// Intializes the searches struct.
// Initializes the searches struct.
func (s *searches) init(core *Core) {
s.core = core
s.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-s.reconfigure
e <- nil
}
}()
s.searches = make(map[crypto.NodeID]*searchInfo)
}
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
now := time.Now()
for dest, sinfo := range s.searches {
if now.Sub(sinfo.time) > time.Minute {
delete(s.searches, dest)
}
}
func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
info := searchInfo{
dest: *dest,
mask: *mask,
time: now.Add(-time.Second),
core: s.core,
dest: *dest,
mask: *mask,
time: time.Now(),
callback: callback,
}
s.searches[*dest] = &info
return &info
@@ -71,31 +78,29 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searc
////////////////////////////////////////////////////////////////////////////////
// Checks if there's an ongoing search relaed to a dhtRes.
// Checks if there's an ongoing search related to a dhtRes.
// If there is, it adds the response info to the search and triggers a new search step.
// If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more.
func (s *searches) handleDHTRes(res *dhtRes) {
sinfo, isIn := s.searches[res.Dest]
if !isIn || s.checkDHTRes(sinfo, res) {
func (sinfo *searchInfo) handleDHTRes(res *dhtRes) {
if res == nil || sinfo.checkDHTRes(res) {
// Either we don't recognize this search, or we just finished it
return
} else {
// Add to the search and continue
s.addToSearch(sinfo, res)
s.doSearchStep(sinfo)
}
// Add to the search and continue
sinfo.addToSearch(res)
sinfo.doSearchStep()
}
// Adds the information from a dhtRes to an ongoing search.
// Info about a node that has already been visited is not re-added to the search.
// Duplicate information about nodes toVisit is deduplicated (the newest information is kept).
// The toVisit list is sorted in ascending order of keyspace distance from the destination.
func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
func (sinfo *searchInfo) addToSearch(res *dhtRes) {
// Add responses to toVisit if closer to dest than the res node
from := dhtInfo{key: res.Key, coords: res.Coords}
sinfo.visited[*from.getNodeID()] = true
for _, info := range res.Infos {
if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] {
if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] {
continue
}
if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) {
@@ -125,85 +130,92 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
// If there are no nodes left toVisit, then this cleans up the search.
// Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping.
func (s *searches) doSearchStep(sinfo *searchInfo) {
func (sinfo *searchInfo) doSearchStep() {
if len(sinfo.toVisit) == 0 {
// Dead end, do cleanup
delete(s.searches, sinfo.dest)
if time.Since(sinfo.time) > search_RETRY_TIME {
// Dead end and no response in too long, do cleanup
delete(sinfo.core.searches.searches, sinfo.dest)
sinfo.callback(nil, errors.New("search reached dead end"))
}
return
} else {
// Send to the next search target
var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
rq := dhtReqKey{next.key, sinfo.dest}
s.core.dht.addCallback(&rq, s.handleDHTRes)
s.core.dht.ping(next, &sinfo.dest)
}
// Send to the next search target
var next *dhtInfo
next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
rq := dhtReqKey{next.key, sinfo.dest}
sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes)
sinfo.core.dht.ping(next, &sinfo.dest)
sinfo.time = time.Now()
}
// If we've recenty sent a ping for this search, do nothing.
// Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME.
func (s *searches) continueSearch(sinfo *searchInfo) {
if time.Since(sinfo.time) < search_RETRY_TIME {
return
}
sinfo.time = time.Now()
s.doSearchStep(sinfo)
func (sinfo *searchInfo) continueSearch() {
sinfo.doSearchStep()
// In case the search dies, try to spawn another thread later
// Note that this will spawn multiple parallel searches as time passes
// Any that die aren't restarted, but a new one will start later
retryLater := func() {
newSearchInfo := s.searches[sinfo.dest]
// FIXME this keeps the search alive forever if not for the searches map, fix that
newSearchInfo := sinfo.core.searches.searches[sinfo.dest]
if newSearchInfo != sinfo {
return
}
s.continueSearch(sinfo)
sinfo.continueSearch()
}
go func() {
time.Sleep(search_RETRY_TIME)
s.core.router.admin <- retryLater
sinfo.core.router.admin <- retryLater
}()
}
// Calls create search, and initializes the iterative search parts of the struct before returning it.
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
sinfo := s.createSearch(dest, mask)
sinfo.toVisit = s.core.dht.lookup(dest, true)
func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
sinfo := s.createSearch(dest, mask, callback)
sinfo.visited = make(map[crypto.NodeID]bool)
loc := s.core.switchTable.getLocator()
sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{
key: s.core.boxPub,
coords: loc.getCoords(),
}) // Start the search by asking ourself, useful if we're the destination
return sinfo
}
// Checks if a dhtRes is good (called by handleDHTRes).
// If the response is from the target, get/create a session, trigger a session ping, and return true.
// Otherwise return false.
func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool {
func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
them := crypto.GetNodeID(&res.Key)
var destMasked crypto.NodeID
var themMasked crypto.NodeID
for idx := 0; idx < crypto.NodeIDLen; idx++ {
destMasked[idx] = info.dest[idx] & info.mask[idx]
themMasked[idx] = them[idx] & info.mask[idx]
destMasked[idx] = sinfo.dest[idx] & sinfo.mask[idx]
themMasked[idx] = them[idx] & sinfo.mask[idx]
}
if themMasked != destMasked {
return false
}
// They match, so create a session and send a sessionRequest
sinfo, isIn := s.core.sessions.getByTheirPerm(&res.Key)
sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn {
sinfo = s.core.sessions.createSession(&res.Key)
if sinfo == nil {
sess = sinfo.core.sessions.createSession(&res.Key)
if sess == nil {
// nil if the DHT search finished but the session wasn't allowed
sinfo.callback(nil, errors.New("session not allowed"))
// Cleanup
delete(sinfo.core.searches.searches, res.Dest)
return true
}
_, isIn := s.core.sessions.getByTheirPerm(&res.Key)
_, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn {
panic("This should never happen")
}
}
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
sinfo.coords = res.Coords
sinfo.packet = info.packet
s.core.sessions.ping(sinfo)
sess.coords = res.Coords
sinfo.core.sessions.ping(sess)
sinfo.callback(sess, nil)
// Cleanup
delete(s.searches, res.Dest)
delete(sinfo.core.searches.searches, res.Dest)
return true
}

View File

@@ -6,7 +6,9 @@ package yggdrasil
import (
"bytes"
"encoding/hex"
"container/heap"
"errors"
"sync"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
@@ -14,37 +16,67 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// Duration that we keep track of old nonces per session, to allow some out-of-order packet delivery
const nonceWindow = time.Second
// A heap of nonces, used with a map[nonce]time to allow out-of-order packets a little time to arrive without rejecting them
type nonceHeap []crypto.BoxNonce
func (h nonceHeap) Len() int { return len(h) }
func (h nonceHeap) Less(i, j int) bool { return h[i].Minus(&h[j]) < 0 }
func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *nonceHeap) Push(x interface{}) { *h = append(*h, x.(crypto.BoxNonce)) }
func (h *nonceHeap) Pop() interface{} {
l := len(*h)
var n crypto.BoxNonce
n, *h = (*h)[l-1], (*h)[:l-1]
return n
}
func (h nonceHeap) peek() *crypto.BoxNonce { return &h[len(h)-1] }
// All the information we know about an active session.
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
type sessionInfo struct {
core *Core
theirAddr address.Address
theirSubnet address.Subnet
theirPermPub crypto.BoxPubKey
theirSesPub crypto.BoxPubKey
mySesPub crypto.BoxPubKey
mySesPriv crypto.BoxPrivKey
sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle
myHandle crypto.Handle
theirNonce crypto.BoxNonce
myNonce crypto.BoxNonce
theirMTU uint16
myMTU uint16
wasMTUFixed bool // Was the MTU fixed by a receive error?
time time.Time // Time we last received a packet
coords []byte // coords of destination
packet []byte // a buffered packet, sent immediately on ping/pong
init bool // Reset if coords change
send chan []byte
recv chan *wire_trafficPacket
nonceMask uint64
tstamp int64 // tstamp from their last session ping, replay attack mitigation
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session
core *Core //
reconfigure chan chan error //
theirAddr address.Address //
theirSubnet address.Subnet //
theirPermPub crypto.BoxPubKey //
theirSesPub crypto.BoxPubKey //
mySesPub crypto.BoxPubKey //
mySesPriv crypto.BoxPrivKey //
sharedSesKey crypto.BoxSharedKey // derived from session keys
theirHandle crypto.Handle //
myHandle crypto.Handle //
theirNonce crypto.BoxNonce //
theirNonceHeap nonceHeap // priority queue to keep track of the lowest nonce we recently accepted
theirNonceMap map[crypto.BoxNonce]time.Time // time we added each nonce to the heap
myNonce crypto.BoxNonce //
theirMTU uint16 //
myMTU uint16 //
wasMTUFixed bool // Was the MTU fixed by a receive error?
timeOpened time.Time // Time the sessino was opened
time time.Time // Time we last received a packet
mtuTime time.Time // time myMTU was last changed
pingTime time.Time // time the first ping was sent since the last received packet
pingSend time.Time // time the last ping was sent
coords []byte // coords of destination
reset bool // reset if coords change
tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation
bytesSent uint64 // Bytes of real traffic sent in this session
bytesRecvd uint64 // Bytes of real traffic received in this session
init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use
cancel util.Cancellation // Used to terminate workers
fromRouter chan wire_trafficPacket // Received packets go here, to be decrypted by the session
recv chan []byte // Decrypted packets go here, picked up by the associated Conn
send chan FlowKeyMessage // Packets with optional flow key go here, to be encrypted and sent
}
func (sinfo *sessionInfo) doFunc(f func()) {
sinfo.mutex.Lock()
defer sinfo.mutex.Unlock()
f()
}
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
@@ -52,10 +84,10 @@ type sessionPing struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
Handle crypto.Handle // Random number to ID session
SendSesPub crypto.BoxPubKey // Session key to use
Coords []byte
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool
MTU uint16
Coords []byte //
Tstamp int64 // unix time, but the only real requirement is that it increases
IsPong bool //
MTU uint16 //
}
// Updates session info in response to a ping, after checking that the ping is OK.
@@ -75,7 +107,8 @@ func (s *sessionInfo) update(p *sessionPing) bool {
s.theirHandle = p.Handle
s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
s.theirNonce = crypto.BoxNonce{}
s.nonceMask = 0
s.theirNonceHeap = nil
s.theirNonceMap = make(map[crypto.BoxNonce]time.Time)
}
if p.MTU >= 1280 || p.MTU == 0 {
s.theirMTU = p.MTU
@@ -84,150 +117,78 @@ func (s *sessionInfo) update(p *sessionPing) bool {
// allocate enough space for additional coords
s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...)
}
now := time.Now()
s.time = now
s.time = time.Now()
s.tstamp = p.Tstamp
s.init = true
s.reset = false
defer func() { recover() }() // Recover if the below panics
select {
case <-s.init:
default:
// Unblock anything waiting for the session to initialize
close(s.init)
}
return true
}
// Returns true if the session has been idle for longer than the allowed timeout.
func (s *sessionInfo) timedout() bool {
return time.Since(s.time) > time.Minute
}
// Struct of all active sessions.
// Sessions are indexed by handle.
// Additionally, stores maps of address/subnet onto keys, and keys onto handles.
type sessions struct {
core *Core
lastCleanup time.Time
// Maps known permanent keys to their shared key, used by DHT a lot
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey
// Maps (secret) handle onto session info
sinfos map[crypto.Handle]*sessionInfo
// Maps mySesPub onto handle
byMySes map[crypto.BoxPubKey]*crypto.Handle
// Maps theirPermPub onto handle
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
addrToPerm map[address.Address]*crypto.BoxPubKey
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
// Options from the session firewall
sessionFirewallEnabled bool
sessionFirewallAllowsDirect bool
sessionFirewallAllowsRemote bool
sessionFirewallAlwaysAllowsOutbound bool
sessionFirewallWhitelist []string
sessionFirewallBlacklist []string
core *Core
listener *Listener
listenerMutex sync.Mutex
reconfigure chan chan error
lastCleanup time.Time
isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed
isAllowedMutex sync.RWMutex // Protects the above
permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot
sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle
}
// Initializes the session struct.
func (ss *sessions) init(core *Core) {
ss.core = core
ss.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-ss.reconfigure
responses := make(map[crypto.Handle]chan error)
for index, session := range ss.sinfos {
responses[index] = make(chan error)
session.reconfigure <- responses[index]
}
for _, response := range responses {
if err := <-response; err != nil {
e <- err
continue
}
}
e <- nil
}
}()
ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey)
ss.sinfos = make(map[crypto.Handle]*sessionInfo)
ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.addrToPerm = make(map[address.Address]*crypto.BoxPubKey)
ss.subnetToPerm = make(map[address.Subnet]*crypto.BoxPubKey)
ss.lastCleanup = time.Now()
}
// Enable or disable the session firewall
func (ss *sessions) setSessionFirewallState(enabled bool) {
ss.sessionFirewallEnabled = enabled
}
// Set the session firewall defaults (first parameter is whether to allow
// sessions from direct peers, second is whether to allow from remote nodes).
func (ss *sessions) setSessionFirewallDefaults(allowsDirect bool, allowsRemote bool, alwaysAllowsOutbound bool) {
ss.sessionFirewallAllowsDirect = allowsDirect
ss.sessionFirewallAllowsRemote = allowsRemote
ss.sessionFirewallAlwaysAllowsOutbound = alwaysAllowsOutbound
}
// Set the session firewall whitelist - nodes always allowed to open sessions.
func (ss *sessions) setSessionFirewallWhitelist(whitelist []string) {
ss.sessionFirewallWhitelist = whitelist
}
// Set the session firewall blacklist - nodes never allowed to open sessions.
func (ss *sessions) setSessionFirewallBlacklist(blacklist []string) {
ss.sessionFirewallBlacklist = blacklist
}
// Determines whether the session with a given publickey is allowed based on
// session firewall rules.
func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool {
// Allow by default if the session firewall is disabled
if !ss.sessionFirewallEnabled {
ss.isAllowedMutex.RLock()
defer ss.isAllowedMutex.RUnlock()
if ss.isAllowedHandler == nil {
return true
}
// Prepare for checking whitelist/blacklist
var box crypto.BoxPubKey
// Reject blacklisted nodes
for _, b := range ss.sessionFirewallBlacklist {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return false
}
}
}
// Allow whitelisted nodes
for _, b := range ss.sessionFirewallWhitelist {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
if box == *pubkey {
return true
}
}
}
// Allow outbound sessions if appropriate
if ss.sessionFirewallAlwaysAllowsOutbound {
if initiator {
return true
}
}
// Look and see if the pubkey is that of a direct peer
var isDirectPeer bool
for _, peer := range ss.core.peers.ports.Load().(map[switchPort]*peer) {
if peer.box == *pubkey {
isDirectPeer = true
break
}
}
// Allow direct peers if appropriate
if ss.sessionFirewallAllowsDirect && isDirectPeer {
return true
}
// Allow remote nodes if appropriate
if ss.sessionFirewallAllowsRemote && !isDirectPeer {
return true
}
// Finally, default-deny if not matching any of the above rules
return false
return ss.isAllowedHandler(pubkey, initiator)
}
// Gets the session corresponding to a given handle.
func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) {
sinfo, isIn := ss.sinfos[*handle]
if isIn && sinfo.timedout() {
// We have a session, but it has timed out
return nil, false
}
return sinfo, isIn
}
// Gets a session corresponding to an ephemeral session key used by this node.
func (ss *sessions) getByMySes(key *crypto.BoxPubKey) (*sessionInfo, bool) {
h, isIn := ss.byMySes[*key]
if !isIn {
return nil, false
}
sinfo, isIn := ss.getSessionForHandle(h)
return sinfo, isIn
}
@@ -241,48 +202,34 @@ func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) {
return sinfo, isIn
}
// Gets a session corresponding to an IPv6 address used by the remote node.
func (ss *sessions) getByTheirAddr(addr *address.Address) (*sessionInfo, bool) {
p, isIn := ss.addrToPerm[*addr]
if !isIn {
return nil, false
}
sinfo, isIn := ss.getByTheirPerm(p)
return sinfo, isIn
}
// Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network.
func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) {
p, isIn := ss.subnetToPerm[*snet]
if !isIn {
return nil, false
}
sinfo, isIn := ss.getByTheirPerm(p)
return sinfo, isIn
}
// Creates a new session and lazily cleans up old/timedout existing sessions.
// This includse initializing session info to sane defaults (e.g. lowest supported MTU).
// Creates a new session and lazily cleans up old existing sessions. This
// includse initializing session info to sane defaults (e.g. lowest supported
// MTU).
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
if ss.sessionFirewallEnabled {
if !ss.isSessionAllowed(theirPermKey, true) {
return nil
}
// TODO: this check definitely needs to be moved
if !ss.isSessionAllowed(theirPermKey, true) {
return nil
}
sinfo := sessionInfo{}
sinfo.core = ss.core
sinfo.reconfigure = make(chan chan error, 1)
sinfo.theirPermPub = *theirPermKey
pub, priv := crypto.NewBoxKeys()
sinfo.mySesPub = *pub
sinfo.mySesPriv = *priv
sinfo.myNonce = *crypto.NewBoxNonce()
sinfo.theirMTU = 1280
sinfo.myMTU = uint16(ss.core.router.tun.mtu)
ss.core.config.Mutex.RLock()
sinfo.myMTU = uint16(ss.core.config.Current.IfMTU)
ss.core.config.Mutex.RUnlock()
now := time.Now()
sinfo.timeOpened = now
sinfo.time = now
sinfo.mtuTime = now
sinfo.pingTime = now
sinfo.pingSend = now
sinfo.init = make(chan struct{})
sinfo.cancel = util.NewCancellation()
higher := false
for idx := range ss.core.boxPub {
if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] {
@@ -302,14 +249,17 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.myHandle = *crypto.NewHandle()
sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.send = make(chan []byte, 32)
sinfo.recv = make(chan *wire_trafficPacket, 32)
go sinfo.doWorker()
sinfo.fromRouter = make(chan wire_trafficPacket, 1)
sinfo.recv = make(chan []byte, 32)
sinfo.send = make(chan FlowKeyMessage, 32)
ss.sinfos[sinfo.myHandle] = &sinfo
ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle
ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle
ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub
ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub
go func() {
// Run cleanup when the session is canceled
<-sinfo.cancel.Finished()
sinfo.core.router.doAdmin(sinfo.close)
}()
go sinfo.startWorkers()
return &sinfo
}
@@ -323,11 +273,6 @@ func (ss *sessions) cleanup() {
if time.Since(ss.lastCleanup) < time.Minute {
return
}
for _, s := range ss.sinfos {
if s.timedout() {
s.close()
}
}
permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared))
for k, v := range ss.permShared {
permShared[k] = v
@@ -338,38 +283,20 @@ func (ss *sessions) cleanup() {
sinfos[k] = v
}
ss.sinfos = sinfos
byMySes := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byMySes))
for k, v := range ss.byMySes {
byMySes[k] = v
}
ss.byMySes = byMySes
byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm))
for k, v := range ss.byTheirPerm {
byTheirPerm[k] = v
}
ss.byTheirPerm = byTheirPerm
addrToPerm := make(map[address.Address]*crypto.BoxPubKey, len(ss.addrToPerm))
for k, v := range ss.addrToPerm {
addrToPerm[k] = v
}
ss.addrToPerm = addrToPerm
subnetToPerm := make(map[address.Subnet]*crypto.BoxPubKey, len(ss.subnetToPerm))
for k, v := range ss.subnetToPerm {
subnetToPerm[k] = v
}
ss.subnetToPerm = subnetToPerm
ss.lastCleanup = time.Now()
}
// Closes a session, removing it from sessions maps and killing the worker goroutine.
// Closes a session, removing it from sessions maps.
func (sinfo *sessionInfo) close() {
delete(sinfo.core.sessions.sinfos, sinfo.myHandle)
delete(sinfo.core.sessions.byMySes, sinfo.mySesPub)
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr)
delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet)
close(sinfo.send)
close(sinfo.recv)
if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo {
delete(sinfo.core.sessions.sinfos, sinfo.myHandle)
delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
}
}
// Returns a session ping appropriate for the given session info.
@@ -393,6 +320,8 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing {
// This comes up with dht req/res and session ping/pong traffic.
func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey,
theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey {
return crypto.GetSharedKey(myPriv, theirPub)
// FIXME concurrency issues with the below, so for now we just burn the CPU every time
if skey, isIn := ss.permShared[*theirPub]; isIn {
return skey
}
@@ -431,8 +360,8 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
}
packet := p.encode()
ss.core.router.out(packet)
if !isPong {
sinfo.pingSend = time.Now()
if sinfo.pingTime.Before(sinfo.time) {
sinfo.pingTime = time.Now()
}
}
@@ -441,34 +370,38 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
func (ss *sessions) handlePing(ping *sessionPing) {
// Get the corresponding session (or create a new session)
sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub)
// Check the session firewall
if !isIn && ss.sessionFirewallEnabled {
if !ss.isSessionAllowed(&ping.SendPermPub, false) {
return
switch {
case isIn: // Session already exists
case !ss.isSessionAllowed(&ping.SendPermPub, false): // Session is not allowed
case ping.IsPong: // This is a response, not an initial ping, so ignore it.
default:
ss.listenerMutex.Lock()
if ss.listener != nil {
// This is a ping from an allowed node for which no session exists, and we have a listener ready to handle sessions.
// We need to create a session and pass it to the listener.
sinfo = ss.createSession(&ping.SendPermPub)
if s, _ := ss.getByTheirPerm(&ping.SendPermPub); s != sinfo {
panic("This should not happen")
}
conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo)
for i := range conn.nodeMask {
conn.nodeMask[i] = 0xFF
}
c := ss.listener.conn
go func() { c <- conn }()
}
ss.listenerMutex.Unlock()
}
if !isIn || sinfo.timedout() {
if isIn {
sinfo.close()
}
ss.createSession(&ping.SendPermPub)
sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub)
if !isIn {
panic("This should not happen")
}
}
// Update the session
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
return
}
if !ping.IsPong {
ss.sendPingPong(sinfo, true)
}
if sinfo.packet != nil {
// send
var bs []byte
bs, sinfo.packet = sinfo.packet, nil
ss.core.router.sendPacket(bs)
if sinfo != nil {
sinfo.doFunc(func() {
// Update the session
if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
return
}
if !ping.IsPong {
ss.sendPingPong(sinfo, true)
}
})
}
}
@@ -488,127 +421,248 @@ func (sinfo *sessionInfo) getMTU() uint16 {
// Checks if a packet's nonce is recent enough to fall within the window of allowed packets, and not already received.
func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
// The bitmask is to allow for some non-duplicate out-of-order packets
diff := theirNonce.Minus(&sinfo.theirNonce)
if diff > 0 {
if theirNonce.Minus(&sinfo.theirNonce) > 0 {
// This is newer than the newest nonce we've seen
return true
}
return ^sinfo.nonceMask&(0x01<<uint64(-diff)) != 0
if len(sinfo.theirNonceHeap) > 0 {
if theirNonce.Minus(sinfo.theirNonceHeap.peek()) > 0 {
if _, isIn := sinfo.theirNonceMap[*theirNonce]; !isIn {
// This nonce is recent enough that we keep track of older nonces, but it's not one we've seen yet
return true
}
}
}
return false
}
// Updates the nonce mask by (possibly) shifting the bitmask and setting the bit corresponding to this nonce to 1, and then updating the most recent nonce
func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) {
// Shift nonce mask if needed
// Set bit
diff := theirNonce.Minus(&sinfo.theirNonce)
if diff > 0 {
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
sinfo.nonceMask <<= uint64(diff)
sinfo.nonceMask &= 0x01
sinfo.theirNonce = *theirNonce
} else {
// This nonce is older, so set the bit but do not shift the window.
sinfo.nonceMask &= 0x01 << uint64(-diff)
// Start with some cleanup
for len(sinfo.theirNonceHeap) > 64 {
if time.Since(sinfo.theirNonceMap[*sinfo.theirNonceHeap.peek()]) < nonceWindow {
// This nonce is still fairly new, so keep it around
break
}
// TODO? reallocate the map in some cases, to free unused map space?
delete(sinfo.theirNonceMap, *sinfo.theirNonceHeap.peek())
heap.Pop(&sinfo.theirNonceHeap)
}
if theirNonce.Minus(&sinfo.theirNonce) > 0 {
// This nonce is the newest we've seen, so make a note of that
sinfo.theirNonce = *theirNonce
}
// Add it to the heap/map so we know not to allow it again
heap.Push(&sinfo.theirNonceHeap, *theirNonce)
sinfo.theirNonceMap[*theirNonce] = time.Now()
}
// Resets all sessions to an uninitialized state.
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
func (ss *sessions) resetInits() {
func (ss *sessions) reset() {
for _, sinfo := range ss.sinfos {
sinfo.init = false
sinfo.doFunc(func() {
sinfo.reset = true
})
}
}
////////////////////////////////////////////////////////////////////////////////
//////////////////////////// Worker Functions Below ////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// This is for a per-session worker.
// It handles calling the relatively expensive crypto operations.
// It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK.
func (sinfo *sessionInfo) doWorker() {
func (sinfo *sessionInfo) startWorkers() {
go sinfo.recvWorker()
go sinfo.sendWorker()
}
type FlowKeyMessage struct {
FlowKey uint64
Message []byte
}
func (sinfo *sessionInfo) recvWorker() {
// TODO move theirNonce etc into a struct that gets stored here, passed in over a channel
// Since there's no reason for anywhere else in the session code to need to *read* it...
// Only needs to be updated from the outside if a ping resets it...
// That would get rid of the need to take a mutex for the sessionFunc
var callbacks []chan func()
doRecv := func(p wire_trafficPacket) {
var bs []byte
var err error
var k crypto.BoxSharedKey
sessionFunc := func() {
if !sinfo.nonceIsOK(&p.Nonce) {
err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0}
return
}
k = sinfo.sharedSesKey
}
sinfo.doFunc(sessionFunc)
if err != nil {
util.PutBytes(p.Payload)
return
}
var isOK bool
ch := make(chan func(), 1)
poolFunc := func() {
bs, isOK = crypto.BoxOpen(&k, p.Payload, &p.Nonce)
callback := func() {
util.PutBytes(p.Payload)
if !isOK {
util.PutBytes(bs)
return
}
sessionFunc = func() {
if k != sinfo.sharedSesKey || !sinfo.nonceIsOK(&p.Nonce) {
// The session updated in the mean time, so return an error
err = ConnError{errors.New("session updated during crypto operation"), false, true, false, 0}
return
}
sinfo.updateNonce(&p.Nonce)
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
}
sinfo.doFunc(sessionFunc)
if err != nil {
// Not sure what else to do with this packet, I guess just drop it
util.PutBytes(bs)
} else {
// Pass the packet to the buffer for Conn.Read
select {
case <-sinfo.cancel.Finished():
util.PutBytes(bs)
case sinfo.recv <- bs:
}
}
}
ch <- callback
}
// Send to the worker and wait for it to finish
util.WorkerGo(poolFunc)
callbacks = append(callbacks, ch)
}
fromHelper := make(chan wire_trafficPacket, 1)
go func() {
var buf []wire_trafficPacket
for {
for len(buf) > 0 {
select {
case <-sinfo.cancel.Finished():
return
case p := <-sinfo.fromRouter:
buf = append(buf, p)
for len(buf) > 64 { // Based on nonce window size
util.PutBytes(buf[0].Payload)
buf = buf[1:]
}
case fromHelper <- buf[0]:
buf = buf[1:]
}
}
select {
case <-sinfo.cancel.Finished():
return
case p := <-sinfo.fromRouter:
buf = append(buf, p)
}
}
}()
select {
case <-sinfo.cancel.Finished():
return
case <-sinfo.init:
// Wait until the session has finished initializing before processing any packets
}
for {
for len(callbacks) > 0 {
select {
case f := <-callbacks[0]:
callbacks = callbacks[1:]
f()
case <-sinfo.cancel.Finished():
return
case p := <-fromHelper:
doRecv(p)
}
}
select {
case p, ok := <-sinfo.recv:
if ok {
sinfo.doRecv(p)
} else {
return
}
case bs, ok := <-sinfo.send:
if ok {
sinfo.doSend(bs)
} else {
return
}
case <-sinfo.cancel.Finished():
return
case p := <-fromHelper:
doRecv(p)
}
}
}
// This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer.
func (sinfo *sessionInfo) doSend(bs []byte) {
defer util.PutBytes(bs)
if !sinfo.init {
// To prevent using empty session keys
return
func (sinfo *sessionInfo) sendWorker() {
// TODO move info that this worker needs here, send updates via a channel
// Otherwise we need to take a mutex to avoid races with update()
var callbacks []chan func()
doSend := func(msg FlowKeyMessage) {
var p wire_trafficPacket
var k crypto.BoxSharedKey
sessionFunc := func() {
sinfo.bytesSent += uint64(len(msg.Message))
p = wire_trafficPacket{
Coords: append([]byte(nil), sinfo.coords...),
Handle: sinfo.theirHandle,
Nonce: sinfo.myNonce,
}
if msg.FlowKey != 0 {
// Helps ensure that traffic from this flow ends up in a separate queue from other flows
// The zero padding relies on the fact that the self-peer is always on port 0
p.Coords = append(p.Coords, 0)
p.Coords = wire_put_uint64(msg.FlowKey, p.Coords)
}
sinfo.myNonce.Increment()
k = sinfo.sharedSesKey
}
// Get the mutex-protected info needed to encrypt the packet
sinfo.doFunc(sessionFunc)
ch := make(chan func(), 1)
poolFunc := func() {
// Encrypt the packet
p.Payload, _ = crypto.BoxSeal(&k, msg.Message, &p.Nonce)
// The callback will send the packet
callback := func() {
// Encoding may block on a util.GetBytes(), so kept out of the worker pool
packet := p.encode()
// Cleanup
util.PutBytes(msg.Message)
util.PutBytes(p.Payload)
// Send the packet
sinfo.core.router.out(packet)
}
ch <- callback
}
// Send to the worker and wait for it to finish
util.WorkerGo(poolFunc)
callbacks = append(callbacks, ch)
}
// code isn't multithreaded so appending to this is safe
coords := sinfo.coords
// Read IPv6 flowlabel field (20 bits).
// Assumes packet at least contains IPv6 header.
flowkey := uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3])
// Check if the flowlabel was specified
if flowkey == 0 {
// Does the packet meet the minimum UDP packet size? (others are bigger)
if len(bs) >= 48 {
// Is the protocol TCP, UDP, SCTP?
if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 {
// if flowlabel was unspecified (0), try to use known protocols' ports
// protokey: proto | sport | dport
flowkey = uint64(bs[6])<<32 /* proto */ |
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
select {
case <-sinfo.cancel.Finished():
return
case <-sinfo.init:
// Wait until the session has finished initializing before processing any packets
}
for {
for len(callbacks) > 0 {
select {
case f := <-callbacks[0]:
callbacks = callbacks[1:]
f()
case <-sinfo.cancel.Finished():
return
case msg := <-sinfo.send:
doSend(msg)
}
}
select {
case <-sinfo.cancel.Finished():
return
case bs := <-sinfo.send:
doSend(bs)
}
}
// If we have a flowkey, either through the IPv6 flowlabel field or through
// known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords.
// Appending extra coords after a 0 ensures that we still target the local router
// but lets us send extra data (which is otherwise ignored) to help separate
// traffic streams into independent queues
if flowkey != 0 {
coords = append(coords, 0) // First target the local switchport
coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey
}
// Prepare the payload
payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce)
defer util.PutBytes(payload)
p := wire_trafficPacket{
Coords: coords,
Handle: sinfo.theirHandle,
Nonce: *nonce,
Payload: payload,
}
packet := p.encode()
sinfo.bytesSent += uint64(len(bs))
sinfo.core.router.out(packet)
}
// This takes a trafficPacket and checks the nonce.
// If the nonce is OK, it decrypts the packet.
// If the decrypted packet is OK, it calls router.recvPacket to pass the packet to the tun/tap.
// If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly.
// TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size).
func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
defer util.PutBytes(p.Payload)
if !sinfo.nonceIsOK(&p.Nonce) {
return
}
bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
if !isOK {
util.PutBytes(bs)
return
}
sinfo.updateNonce(&p.Nonce)
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo}
}

118
src/yggdrasil/stream.go Normal file
View File

@@ -0,0 +1,118 @@
package yggdrasil
import (
"bufio"
"errors"
"fmt"
"io"
"net"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// Test that this matches the interface we expect
var _ = linkInterfaceMsgIO(&stream{})
type stream struct {
rwc io.ReadWriteCloser
inputBuffer *bufio.Reader
outputBuffer net.Buffers
}
func (s *stream) close() error {
return s.rwc.Close()
}
const streamMsgSize = 2048 + 65535
var streamMsg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
func (s *stream) init(rwc io.ReadWriteCloser) {
// TODO have this also do the metadata handshake and create the peer struct
s.rwc = rwc
// TODO call something to do the metadata exchange
s.inputBuffer = bufio.NewReaderSize(s.rwc, 2*streamMsgSize)
}
// writeMsg writes a message with stream padding, and is *not* thread safe.
func (s *stream) writeMsgs(bss [][]byte) (int, error) {
buf := s.outputBuffer[:0]
var written int
for _, bs := range bss {
buf = append(buf, streamMsg[:])
buf = append(buf, wire_encode_uint64(uint64(len(bs))))
buf = append(buf, bs)
written += len(bs)
}
s.outputBuffer = buf[:0] // So we can reuse the same underlying array later
_, err := buf.WriteTo(s.rwc)
// TODO only include number of bytes from bs *successfully* written?
return written, err
}
// readMsg reads a message from the stream, accounting for stream padding, and is *not* thread safe.
func (s *stream) readMsg() ([]byte, error) {
for {
bs, err := s.readMsgFromBuffer()
if err != nil {
return nil, fmt.Errorf("message error: %v", err)
}
return bs, err
}
}
// Writes metadata bytes without stream padding, meant to be temporary
func (s *stream) _sendMetaBytes(metaBytes []byte) error {
var written int
for written < len(metaBytes) {
n, err := s.rwc.Write(metaBytes)
written += n
if err != nil {
return err
}
}
return nil
}
// Reads metadata bytes without stream padding, meant to be temporary
func (s *stream) _recvMetaBytes() ([]byte, error) {
var meta version_metadata
frag := meta.encode()
metaBytes := make([]byte, 0, len(frag))
for len(metaBytes) < len(frag) {
n, err := s.rwc.Read(frag)
if err != nil {
return nil, err
}
metaBytes = append(metaBytes, frag[:n]...)
}
return metaBytes, nil
}
// Reads bytes from the underlying rwc and returns 1 full message
func (s *stream) readMsgFromBuffer() ([]byte, error) {
pad := streamMsg // Copy
_, err := io.ReadFull(s.inputBuffer, pad[:])
if err != nil {
return nil, err
} else if pad != streamMsg {
return nil, errors.New("bad message")
}
lenSlice := make([]byte, 0, 10)
// FIXME this nextByte stuff depends on wire.go format, kind of ugly to have it here
nextByte := byte(0xff)
for nextByte > 127 {
nextByte, err = s.inputBuffer.ReadByte()
if err != nil {
return nil, err
}
lenSlice = append(lenSlice, nextByte)
}
msgLen, _ := wire_decode_uint64(lenSlice)
if msgLen > streamMsgSize {
return nil, errors.New("oversized message")
}
msg := util.ResizeBytes(util.GetBytes(), int(msgLen))
_, err = io.ReadFull(s.inputBuffer, msg)
return msg, err
}

View File

@@ -4,7 +4,7 @@ package yggdrasil
// It routes packets based on distance on the spanning tree
// In general, this is *not* equivalent to routing on the tree
// It falls back to the tree in the worst case, but it can take shortcuts too
// This is the part that makse routing reasonably efficient on scale-free graphs
// This is the part that makes routing reasonably efficient on scale-free graphs
// TODO document/comment everything in a lot more detail
@@ -131,6 +131,7 @@ type peerInfo struct {
faster map[switchPort]uint64 // Counter of how often a node is faster than the current parent, penalized extra if slower
port switchPort // Interface number of this peer
msg switchMsg // The wire switchMsg used
blocked bool // True if the link is blocked, used to avoid parenting a blocked link
}
// This is just a uint64 with a named type for clarity reasons.
@@ -162,6 +163,7 @@ type switchData struct {
// All the information stored by the switch.
type switchTable struct {
core *Core
reconfigure chan chan error
key crypto.SigPubKey // Our own key
time time.Time // Time when locator.tstamp was last updated
drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root
@@ -175,17 +177,19 @@ type switchTable struct {
admin chan func() // Pass a lambda for the admin socket to query stuff
queues switch_buffers // Queues - not atomic so ONLY use through admin chan
queueTotalMaxSize uint64 // Maximum combined size of queues
toRouter chan []byte // Packets to be sent to the router
}
// Minimum allowed total size of switch queues.
const SwitchQueueTotalMinSize = 4 * 1024 * 1024
// Initializes the switchTable struct.
func (t *switchTable) init(core *Core, key crypto.SigPubKey) {
func (t *switchTable) init(core *Core) {
now := time.Now()
t.core = core
t.key = key
locator := switchLocator{root: key, tstamp: now.Unix()}
t.reconfigure = make(chan chan error, 1)
t.key = t.core.sigPub
locator := switchLocator{root: t.key, tstamp: now.Unix()}
peers := make(map[switchPort]peerInfo)
t.data = switchData{locator: locator, peers: peers}
t.updater.Store(&sync.Once{})
@@ -195,6 +199,7 @@ func (t *switchTable) init(core *Core, key crypto.SigPubKey) {
t.idleIn = make(chan switchPort, 1024)
t.admin = make(chan func())
t.queueTotalMaxSize = SwitchQueueTotalMinSize
t.toRouter = make(chan []byte, 1)
}
// Safely gets a copy of this node's locator.
@@ -211,7 +216,6 @@ func (t *switchTable) doMaintenance() {
defer t.mutex.Unlock() // Release lock when we're done
t.cleanRoot()
t.cleanDropped()
t.cleanPeers()
}
// Updates the root periodically if it is ourself, or promotes ourself to root if we're better than the current root or if the current root has timed out.
@@ -251,6 +255,29 @@ func (t *switchTable) cleanRoot() {
}
}
// Blocks and, if possible, unparents a peer
func (t *switchTable) blockPeer(port switchPort) {
t.mutex.Lock()
defer t.mutex.Unlock()
peer, isIn := t.data.peers[port]
if !isIn {
return
}
peer.blocked = true
t.data.peers[port] = peer
if port != t.parent {
return
}
t.parent = 0
for _, info := range t.data.peers {
if info.port == port {
continue
}
t.unlockedHandleMsg(&info.msg, info.port, true)
}
t.unlockedHandleMsg(&peer.msg, peer.port, true)
}
// Removes a peer.
// Must be called by the router mainLoop goroutine, e.g. call router.doAdmin with a lambda that calls this.
// If the removed peer was this node's parent, it immediately tries to find a new parent.
@@ -268,27 +295,6 @@ func (t *switchTable) forgetPeer(port switchPort) {
}
}
// Clean all unresponsive peers from the table, needed in case a peer stops updating.
// Needed in case a non-parent peer keeps the connection open but stops sending updates.
// Also reclaims space from deleted peers by copying the map.
func (t *switchTable) cleanPeers() {
now := time.Now()
for port, peer := range t.data.peers {
if now.Sub(peer.time) > switch_timeout+switch_throttle {
// Longer than switch_timeout to make sure we don't remove a working peer because the root stopped responding.
delete(t.data.peers, port)
}
}
if _, isIn := t.data.peers[t.parent]; !isIn {
// The root timestamp would probably time out before this happens, but better safe than sorry.
// We removed the current parent, so find a new one.
t.parent = 0
for _, peer := range t.data.peers {
t.unlockedHandleMsg(&peer.msg, peer.port, true)
}
}
}
// Dropped is a list of roots that are better than the current root, but stopped sending new timestamps.
// If we switch to a new root, and that root is better than an old root that previously timed out, then we can clean up the old dropped root infos.
// This function is called periodically to do that cleanup.
@@ -410,6 +416,8 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep
// Update the matrix of peer "faster" thresholds
if reprocessing {
sender.faster = oldSender.faster
sender.time = oldSender.time
sender.blocked = oldSender.blocked
} else {
sender.faster = make(map[switchPort]uint64, len(oldSender.faster))
for port, peer := range t.data.peers {
@@ -469,6 +477,11 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep
case sender.faster[t.parent] >= switch_faster_threshold:
// The is reliably faster than the current parent.
updateRoot = true
case !sender.blocked && oldParent.blocked:
// Replace a blocked parent
updateRoot = true
case reprocessing && sender.blocked && !oldParent.blocked:
// Don't replace an unblocked parent when reprocessing
case reprocessing && sender.faster[t.parent] > oldParent.faster[sender.port]:
// The sender seems to be reliably faster than the current parent, so switch to them instead.
updateRoot = true
@@ -559,28 +572,33 @@ func (t *switchTable) getTable() lookupTable {
// Starts the switch worker
func (t *switchTable) start() error {
t.core.log.Println("Starting switch")
t.core.log.Infoln("Starting switch")
go t.doWorker()
return nil
}
// Check if a packet should go to the self node
// This means there's no node closer to the destination than us
// This is mainly used to identify packets addressed to us, or that hit a blackhole
func (t *switchTable) selfIsClosest(dest []byte) bool {
type closerInfo struct {
port switchPort
dist int
}
// Return a map of ports onto distance, keeping only ports closer to the destination than this node
// If the map is empty (or nil), then no peer is closer
func (t *switchTable) getCloser(dest []byte) []closerInfo {
table := t.getTable()
myDist := table.self.dist(dest)
if myDist == 0 {
// Skip the iteration step if it's impossible to be closer
return true
return nil
}
t.queues.closer = t.queues.closer[:0]
for _, info := range table.elems {
dist := info.locator.dist(dest)
if dist < myDist {
return false
t.queues.closer = append(t.queues.closer, closerInfo{info.port, dist})
}
}
return true
return t.queues.closer
}
// Returns true if the peer is closer to the destination than ourself
@@ -612,6 +630,16 @@ func switch_getPacketStreamID(packet []byte) string {
return string(switch_getPacketCoords(packet))
}
// Returns the flowlabel from a given set of coords
func switch_getFlowLabelFromCoords(in []byte) []byte {
for i, v := range in {
if v == 0 {
return in[i+1:]
}
}
return []byte{}
}
// Find the best port for a given set of coords
func (t *switchTable) bestPortForCoords(coords []byte) switchPort {
table := t.getTable()
@@ -631,39 +659,61 @@ func (t *switchTable) bestPortForCoords(coords []byte) switchPort {
// Handle an incoming packet
// Either send it to ourself, or to the first idle peer that's free
// Returns true if the packet has been handled somehow, false if it should be queued
func (t *switchTable) handleIn(packet []byte, idle map[switchPort]struct{}) bool {
func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) bool {
coords := switch_getPacketCoords(packet)
ports := t.core.peers.getPorts()
if t.selfIsClosest(coords) {
closer := t.getCloser(coords)
if len(closer) == 0 {
// TODO? call the router directly, and remove the whole concept of a self peer?
ports[0].sendPacket(packet)
t.toRouter <- packet
return true
}
table := t.getTable()
myDist := table.self.dist(coords)
var best *peer
bestDist := myDist
for port := range idle {
if to := ports[port]; to != nil {
if info, isIn := table.elems[to.port]; isIn {
dist := info.locator.dist(coords)
if !(dist < bestDist) {
continue
}
best = to
bestDist = dist
}
var bestDist int
var bestTime time.Time
ports := t.core.peers.getPorts()
for _, cinfo := range closer {
to := ports[cinfo.port]
thisTime, isIdle := idle[cinfo.port]
var update bool
switch {
case to == nil:
// no port was found, ignore it
case !isIdle:
// the port is busy, ignore it
case best == nil:
// this is the first idle port we've found, so select it until we find a
// better candidate port to use instead
update = true
case cinfo.dist < bestDist:
// the port takes a shorter path/is more direct than our current
// candidate, so select that instead
update = true
case cinfo.dist > bestDist:
// the port takes a longer path/is less direct than our current candidate,
// ignore it
case thisTime.After(bestTime):
// all else equal, this port was used more recently than our current
// candidate, so choose that instead. this should mean that, in low
// traffic scenarios, we consistently pick the same link which helps with
// packet ordering
update = true
default:
// the search for a port has finished
}
if update {
best = to
bestDist = cinfo.dist
bestTime = thisTime
}
}
if best != nil {
// Send to the best idle next hop
delete(idle, best.port)
best.sendPacket(packet)
best.sendPackets([][]byte{packet})
return true
} else {
// Didn't find anyone idle to send it to
return false
}
// Didn't find anyone idle to send it to
return false
}
// Info about a buffered packet
@@ -684,6 +734,7 @@ type switch_buffers struct {
size uint64 // Total size of all buffers, in bytes
maxbufs int
maxsize uint64
closer []closerInfo // Scratch space
}
func (b *switch_buffers) cleanup(t *switchTable) {
@@ -691,7 +742,7 @@ func (b *switch_buffers) cleanup(t *switchTable) {
// Remove queues for which we have no next hop
packet := buf.packets[0]
coords := switch_getPacketCoords(packet.bytes)
if t.selfIsClosest(coords) {
if len(t.getCloser(coords)) == 0 {
for _, packet := range buf.packets {
util.PutBytes(packet.bytes)
}
@@ -733,47 +784,91 @@ func (t *switchTable) handleIdle(port switchPort) bool {
if to == nil {
return true
}
var best string
var bestPriority float64
var packets [][]byte
var psize int
t.queues.cleanup(t)
now := time.Now()
for streamID, buf := range t.queues.bufs {
// Filter over the streams that this node is closer to
// Keep the one with the smallest queue
packet := buf.packets[0]
coords := switch_getPacketCoords(packet.bytes)
priority := float64(now.Sub(packet.time)) / float64(buf.size)
if priority > bestPriority && t.portIsCloser(coords, port) {
best = streamID
bestPriority = priority
for psize < 65535 {
var best string
var bestPriority float64
for streamID, buf := range t.queues.bufs {
// Filter over the streams that this node is closer to
// Keep the one with the smallest queue
packet := buf.packets[0]
coords := switch_getPacketCoords(packet.bytes)
priority := float64(now.Sub(packet.time)) / float64(buf.size)
if priority > bestPriority && t.portIsCloser(coords, port) {
best = streamID
bestPriority = priority
}
}
}
if bestPriority != 0 {
buf := t.queues.bufs[best]
var packet switch_packetInfo
// TODO decide if this should be LIFO or FIFO
packet, buf.packets = buf.packets[0], buf.packets[1:]
buf.size -= uint64(len(packet.bytes))
t.queues.size -= uint64(len(packet.bytes))
if len(buf.packets) == 0 {
delete(t.queues.bufs, best)
if bestPriority != 0 {
buf := t.queues.bufs[best]
var packet switch_packetInfo
// TODO decide if this should be LIFO or FIFO
packet, buf.packets = buf.packets[0], buf.packets[1:]
buf.size -= uint64(len(packet.bytes))
t.queues.size -= uint64(len(packet.bytes))
if len(buf.packets) == 0 {
delete(t.queues.bufs, best)
} else {
// Need to update the map, since buf was retrieved by value
t.queues.bufs[best] = buf
}
packets = append(packets, packet.bytes)
psize += len(packet.bytes)
} else {
// Need to update the map, since buf was retrieved by value
t.queues.bufs[best] = buf
// Finished finding packets
break
}
to.sendPacket(packet.bytes)
return true
} else {
return false
}
if len(packets) > 0 {
to.sendPackets(packets)
return true
}
return false
}
// The switch worker does routing lookups and sends packets to where they need to be
func (t *switchTable) doWorker() {
sendingToRouter := make(chan []byte, 1)
go func() {
// Keep sending packets to the router
self := t.core.peers.getPorts()[0]
for bs := range sendingToRouter {
self.sendPackets([][]byte{bs})
}
}()
go func() {
// Keep taking packets from the idle worker and sending them to the above whenever it's idle, keeping anything extra in a (fifo, head-drop) buffer
var buf [][]byte
var size int
for {
bs := <-t.toRouter
size += len(bs)
buf = append(buf, bs)
for len(buf) > 0 {
select {
case bs := <-t.toRouter:
size += len(bs)
buf = append(buf, bs)
for size > int(t.queueTotalMaxSize) {
size -= len(buf[0])
util.PutBytes(buf[0])
buf = buf[1:]
}
case sendingToRouter <- buf[0]:
size -= len(buf[0])
buf = buf[1:]
}
}
}
}()
t.queues.switchTable = t
t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string)
idle := make(map[switchPort]struct{}) // this is to deduplicate things
idle := make(map[switchPort]time.Time) // this is to deduplicate things
for {
//t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs))
select {
case bytes := <-t.packetIn:
// Try to send it somewhere (or drop it if it's corrupt or at a dead end)
@@ -804,10 +899,12 @@ func (t *switchTable) doWorker() {
// Try to find something to send to this peer
if !t.handleIdle(port) {
// Didn't find anything ready to send yet, so stay idle
idle[port] = struct{}{}
idle[port] = time.Now()
}
case f := <-t.admin:
f()
case e := <-t.reconfigure:
e <- nil
}
}
}

View File

@@ -15,47 +15,43 @@ package yggdrasil
// See version.go for version metadata format
import (
"errors"
"context"
"fmt"
"io"
"math/rand"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/proxy"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense
const default_tcp_timeout = 6 * time.Second
const tcp_ping_interval = (default_tcp_timeout * 2 / 3)
const default_timeout = 6 * time.Second
const tcp_ping_interval = (default_timeout * 2 / 3)
// The TCP listener and information about active TCP connections, to avoid duplication.
type tcpInterface struct {
core *Core
serv net.Listener
tcp_timeout time.Duration
type tcp struct {
link *link
reconfigure chan chan error
mutex sync.Mutex // Protecting the below
listeners map[string]*TcpListener
calls map[string]struct{}
conns map[tcpInfo](chan struct{})
conns map[linkInfo](chan struct{})
}
// This is used as the key to a map that tracks existing connections, to prevent multiple connections to the same keys and local/remote address pair from occuring.
// Different address combinations are allowed, so multi-homing is still technically possible (but not necessarily advisable).
type tcpInfo struct {
box crypto.BoxPubKey
sig crypto.SigPubKey
localAddr string
remoteAddr string
// TcpListener is a stoppable TCP listener interface. These are typically
// returned from calls to the ListenTCP() function and are also used internally
// to represent listeners created by the "Listen" configuration option and for
// multicast interfaces.
type TcpListener struct {
Listener net.Listener
Stop chan bool
}
// Wrapper function to set additional options for specific connection types.
func (iface *tcpInterface) setExtraOptions(c net.Conn) {
func (t *tcp) setExtraOptions(c net.Conn) {
switch sock := c.(type) {
case *net.TCPConn:
sock.SetNoDelay(true)
@@ -65,50 +61,153 @@ func (iface *tcpInterface) setExtraOptions(c net.Conn) {
}
// Returns the address of the listener.
func (iface *tcpInterface) getAddr() *net.TCPAddr {
return iface.serv.Addr().(*net.TCPAddr)
}
// Attempts to initiate a connection to the provided address.
func (iface *tcpInterface) connect(addr string, intf string) {
iface.call(addr, nil, intf)
}
// Attempst to initiate a connection to the provided address, viathe provided socks proxy address.
func (iface *tcpInterface) connectSOCKS(socksaddr, peeraddr string) {
iface.call(peeraddr, &socksaddr, "")
func (t *tcp) getAddr() *net.TCPAddr {
// TODO: Fix this, because this will currently only give a single address
// to multicast.go, which obviously is not great, but right now multicast.go
// doesn't have the ability to send more than one address in a packet either
t.mutex.Lock()
defer t.mutex.Unlock()
for _, l := range t.listeners {
return l.Listener.Addr().(*net.TCPAddr)
}
return nil
}
// Initializes the struct.
func (iface *tcpInterface) init(core *Core, addr string, readTimeout int32) (err error) {
iface.core = core
func (t *tcp) init(l *link) error {
t.link = l
t.reconfigure = make(chan chan error, 1)
t.mutex.Lock()
t.calls = make(map[string]struct{})
t.conns = make(map[linkInfo](chan struct{}))
t.listeners = make(map[string]*TcpListener)
t.mutex.Unlock()
iface.tcp_timeout = time.Duration(readTimeout) * time.Millisecond
if iface.tcp_timeout >= 0 && iface.tcp_timeout < default_tcp_timeout {
iface.tcp_timeout = default_tcp_timeout
go func() {
for {
e := <-t.reconfigure
t.link.core.config.Mutex.RLock()
added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen)
deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen)
t.link.core.config.Mutex.RUnlock()
if len(added) > 0 || len(deleted) > 0 {
for _, a := range added {
if a[:6] != "tcp://" {
continue
}
if _, err := t.listen(a[6:]); err != nil {
e <- err
continue
}
}
for _, d := range deleted {
if d[:6] != "tcp://" {
continue
}
t.mutex.Lock()
if listener, ok := t.listeners[d[6:]]; ok {
t.mutex.Unlock()
listener.Stop <- true
} else {
t.mutex.Unlock()
}
}
e <- nil
} else {
e <- nil
}
}
}()
t.link.core.config.Mutex.RLock()
defer t.link.core.config.Mutex.RUnlock()
for _, listenaddr := range t.link.core.config.Current.Listen {
if listenaddr[:6] != "tcp://" {
continue
}
if _, err := t.listen(listenaddr[6:]); err != nil {
return err
}
}
iface.serv, err = net.Listen("tcp", addr)
return nil
}
func (t *tcp) listen(listenaddr string) (*TcpListener, error) {
var err error
ctx := context.Background()
lc := net.ListenConfig{
Control: t.tcpContext,
}
listener, err := lc.Listen(ctx, "tcp", listenaddr)
if err == nil {
iface.calls = make(map[string]struct{})
iface.conns = make(map[tcpInfo](chan struct{}))
go iface.listener()
l := TcpListener{
Listener: listener,
Stop: make(chan bool),
}
go t.listener(&l, listenaddr)
return &l, nil
}
return err
return nil, err
}
// Runs the listener, which spawns off goroutines for incoming connections.
func (iface *tcpInterface) listener() {
defer iface.serv.Close()
iface.core.log.Println("Listening for TCP on:", iface.serv.Addr().String())
for {
sock, err := iface.serv.Accept()
if err != nil {
panic(err)
}
go iface.handler(sock, true)
func (t *tcp) listener(l *TcpListener, listenaddr string) {
if l == nil {
return
}
// Track the listener so that we can find it again in future
t.mutex.Lock()
if _, isIn := t.listeners[listenaddr]; isIn {
t.mutex.Unlock()
l.Listener.Close()
return
} else {
t.listeners[listenaddr] = l
t.mutex.Unlock()
}
// And here we go!
accepted := make(chan bool)
defer func() {
t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String())
l.Listener.Close()
t.mutex.Lock()
delete(t.listeners, listenaddr)
t.mutex.Unlock()
}()
t.link.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String())
for {
var sock net.Conn
var err error
// Listen in a separate goroutine, as that way it does not block us from
// receiving "stop" events
go func() {
sock, err = l.Listener.Accept()
accepted <- true
}()
// Wait for either an accepted connection, or a message telling us to stop
// the TCP listener
select {
case <-accepted:
if err != nil {
t.link.core.log.Errorln("Failed to accept connection:", err)
return
}
go t.handler(sock, true, nil)
case <-l.Stop:
return
}
}
}
// Checks if we already are calling this address
func (t *tcp) isAlreadyCalling(saddr string) bool {
t.mutex.Lock()
defer t.mutex.Unlock()
_, isIn := t.calls[saddr]
return isIn
}
// Checks if a connection already exists.
@@ -116,39 +215,39 @@ func (iface *tcpInterface) listener() {
// If the dial is successful, it launches the handler.
// When finished, it removes the outgoing call, so reconnection attempts can be made later.
// This all happens in a separate goroutine that it spawns.
func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
func (t *tcp) call(saddr string, options interface{}, sintf string) {
go func() {
callname := saddr
if sintf != "" {
callname = fmt.Sprintf("%s/%s", saddr, sintf)
}
quit := false
iface.mutex.Lock()
if _, isIn := iface.calls[callname]; isIn {
quit = true
} else {
iface.calls[callname] = struct{}{}
defer func() {
// Block new calls for a little while, to mitigate livelock scenarios
time.Sleep(default_tcp_timeout)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
iface.mutex.Lock()
delete(iface.calls, callname)
iface.mutex.Unlock()
}()
}
iface.mutex.Unlock()
if quit {
if t.isAlreadyCalling(callname) {
return
}
t.mutex.Lock()
t.calls[callname] = struct{}{}
t.mutex.Unlock()
defer func() {
// Block new calls for a little while, to mitigate livelock scenarios
time.Sleep(default_timeout)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
t.mutex.Lock()
delete(t.calls, callname)
t.mutex.Unlock()
}()
var conn net.Conn
var err error
if socksaddr != nil {
socksaddr, issocks := options.(string)
if issocks {
if sintf != "" {
return
}
dialerdst, er := net.ResolveTCPAddr("tcp", socksaddr)
if er != nil {
return
}
var dialer proxy.Dialer
dialer, err = proxy.SOCKS5("tcp", *socksaddr, nil, proxy.Direct)
dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), nil, proxy.Direct)
if err != nil {
return
}
@@ -156,288 +255,97 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
if err != nil {
return
}
conn = &wrappedConn{
c: conn,
raddr: &wrappedAddr{
network: "tcp",
addr: saddr,
},
}
t.handler(conn, false, dialerdst.String())
} else {
dialer := net.Dialer{}
dst, err := net.ResolveTCPAddr("tcp", saddr)
if err != nil {
return
}
if dst.IP.IsLinkLocalUnicast() {
dst.Zone = sintf
if dst.Zone == "" {
return
}
}
dialer := net.Dialer{
Control: t.tcpContext,
}
if sintf != "" {
ief, err := net.InterfaceByName(sintf)
if err != nil {
return
} else {
if ief.Flags&net.FlagUp == 0 {
return
}
addrs, err := ief.Addrs()
if err == nil {
dst, err := net.ResolveTCPAddr("tcp", saddr)
if err != nil {
return
}
for _, addr := range addrs {
src, _, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
if (src.To4() != nil) == (dst.IP.To4() != nil) && src.IsGlobalUnicast() {
dialer.LocalAddr = &net.TCPAddr{
IP: src,
Port: 0,
}
break
}
}
if dialer.LocalAddr == nil {
return
}
}
}
}
conn, err = dialer.Dial("tcp", saddr)
if err != nil {
return
}
}
iface.handler(conn, false)
}()
}
// This exchanges/checks connection metadata, sets up the peer struct, sets up the writer goroutine, and then runs the reader within the current goroutine.
// It defers a bunch of cleanup stuff to tear down all of these things when the reader exists (e.g. due to a closed connection or a timeout).
func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
defer sock.Close()
iface.setExtraOptions(sock)
// Get our keys
myLinkPub, myLinkPriv := crypto.NewBoxKeys() // ephemeral link keys
meta := version_getBaseMetadata()
meta.box = iface.core.boxPub
meta.sig = iface.core.sigPub
meta.link = *myLinkPub
metaBytes := meta.encode()
_, err := sock.Write(metaBytes)
if err != nil {
return
}
if iface.tcp_timeout > 0 {
sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout))
}
_, err = sock.Read(metaBytes)
if err != nil {
return
}
meta = version_metadata{} // Reset to zero value
if !meta.decode(metaBytes) || !meta.check() {
// Failed to decode and check the metadata
// If it's a version mismatch issue, then print an error message
base := version_getBaseMetadata()
if meta.meta == base.meta {
if meta.ver > base.ver {
iface.core.log.Println("Failed to connect to node:", sock.RemoteAddr().String(), "version:", meta.ver)
} else if meta.ver == base.ver && meta.minorVer > base.minorVer {
iface.core.log.Println("Failed to connect to node:", sock.RemoteAddr().String(), "version:", fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
}
}
// TODO? Block forever to prevent future connection attempts? suppress future messages about the same node?
return
}
info := tcpInfo{ // used as a map key, so don't include ephemeral link key
box: meta.box,
sig: meta.sig,
}
// Quit the parent call if this is a connection to ourself
equiv := func(k1, k2 []byte) bool {
for idx := range k1 {
if k1[idx] != k2[idx] {
return false
}
}
return true
}
if equiv(info.box[:], iface.core.boxPub[:]) {
return
}
if equiv(info.sig[:], iface.core.sigPub[:]) {
return
}
// Check if we're authorized to connect to this key / IP
if incoming && !iface.core.peers.isAllowedEncryptionPublicKey(&info.box) {
// Allow unauthorized peers if they're link-local
raddrStr, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
raddr := net.ParseIP(raddrStr)
if !raddr.IsLinkLocalUnicast() {
return
}
}
// Check if we already have a connection to this node, close and block if yes
info.localAddr, _, _ = net.SplitHostPort(sock.LocalAddr().String())
info.remoteAddr, _, _ = net.SplitHostPort(sock.RemoteAddr().String())
iface.mutex.Lock()
if blockChan, isIn := iface.conns[info]; isIn {
iface.mutex.Unlock()
sock.Close()
<-blockChan
return
}
blockChan := make(chan struct{})
iface.conns[info] = blockChan
iface.mutex.Unlock()
defer func() {
iface.mutex.Lock()
delete(iface.conns, info)
iface.mutex.Unlock()
close(blockChan)
}()
// Note that multiple connections to the same node are allowed
// E.g. over different interfaces
p := iface.core.peers.newPeer(&info.box, &info.sig, crypto.GetSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String())
p.linkOut = make(chan []byte, 1)
in := func(bs []byte) {
p.handlePacket(bs)
}
out := make(chan []byte, 1)
defer close(out)
go func() {
// This goroutine waits for outgoing packets, link protocol traffic, or sends idle keep-alive traffic
send := func(msg []byte) {
msgLen := wire_encode_uint64(uint64(len(msg)))
buf := net.Buffers{tcp_msg[:], msgLen, msg}
buf.WriteTo(sock)
atomic.AddUint64(&p.bytesSent, uint64(len(tcp_msg)+len(msgLen)+len(msg)))
util.PutBytes(msg)
}
timerInterval := tcp_ping_interval
timer := time.NewTimer(timerInterval)
defer timer.Stop()
for {
select {
case msg := <-p.linkOut:
// Always send outgoing link traffic first, if needed
send(msg)
continue
default:
}
// Otherwise wait reset the timer and wait for something to do
timer.Stop()
select {
case <-timer.C:
default:
}
timer.Reset(timerInterval)
select {
case _ = <-timer.C:
send(nil) // TCP keep-alive traffic
case msg := <-p.linkOut:
send(msg)
case msg, ok := <-out:
if !ok {
if ief.Flags&net.FlagUp == 0 {
return
}
send(msg) // Block until the socket write has finished
// Now inform the switch that we're ready for more traffic
p.core.switchTable.idleIn <- p.port
addrs, err := ief.Addrs()
if err == nil {
for addrindex, addr := range addrs {
src, _, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
if src.Equal(dst.IP) {
continue
}
if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() {
continue
}
bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast()
bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast()
if !bothglobal && !bothlinklocal {
continue
}
if (src.To4() != nil) != (dst.IP.To4() != nil) {
continue
}
if bothglobal || bothlinklocal || addrindex == len(addrs)-1 {
dialer.LocalAddr = &net.TCPAddr{
IP: src,
Port: 0,
Zone: sintf,
}
break
}
}
if dialer.LocalAddr == nil {
return
}
}
}
conn, err = dialer.Dial("tcp", dst.String())
if err != nil {
t.link.core.log.Debugln("Failed to dial TCP:", err)
return
}
t.handler(conn, false, nil)
}
}()
p.core.switchTable.idleIn <- p.port // Start in the idle state
p.out = func(msg []byte) {
defer func() { recover() }()
out <- msg
}
p.close = func() { sock.Close() }
go p.linkLoop()
defer func() {
// Put all of our cleanup here...
p.core.peers.removePeer(p.port)
}()
us, _, _ := net.SplitHostPort(sock.LocalAddr().String())
them, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
themNodeID := crypto.GetNodeID(&info.box)
themAddr := address.AddrForNodeID(themNodeID)
themAddrString := net.IP(themAddr[:]).String()
themString := fmt.Sprintf("%s@%s", themAddrString, them)
iface.core.log.Println("Connected:", themString, "source", us)
err = iface.reader(sock, in) // In this goroutine, because of defers
if err == nil {
iface.core.log.Println("Disconnected:", themString, "source", us)
}
func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}) {
defer sock.Close()
t.setExtraOptions(sock)
stream := stream{}
stream.init(sock)
local, _, _ := net.SplitHostPort(sock.LocalAddr().String())
remote, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast()
var name string
var proto string
if socksaddr, issocks := options.(string); issocks {
name = "socks://" + socksaddr + "/" + sock.RemoteAddr().String()
proto = "socks"
} else {
iface.core.log.Println("Disconnected:", themString, "source", us, "with error:", err)
name = "tcp://" + sock.RemoteAddr().String()
proto = "tcp"
}
return
}
// This reads from the socket into a []byte buffer for incomping messages.
// It copies completed messages out of the cache into a new slice, and passes them to the peer struct via the provided `in func([]byte)` argument.
// Then it shifts the incomplete fragments of data forward so future reads won't overwrite it.
func (iface *tcpInterface) reader(sock net.Conn, in func([]byte)) error {
bs := make([]byte, 2*tcp_msgSize)
frag := bs[:0]
for {
if iface.tcp_timeout > 0 {
sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout))
}
n, err := sock.Read(bs[len(frag):])
if n > 0 {
frag = bs[:len(frag)+n]
for {
msg, ok, err2 := tcp_chop_msg(&frag)
if err2 != nil {
return fmt.Errorf("Message error: %v", err2)
}
if !ok {
// We didn't get the whole message yet
break
}
newMsg := append(util.GetBytes(), msg...)
in(newMsg)
util.Yield()
}
frag = append(bs[:0], frag...)
}
if err != nil || n == 0 {
if err != io.EOF {
return err
}
return nil
}
}
}
////////////////////////////////////////////////////////////////////////////////
// These are 4 bytes of padding used to catch if something went horribly wrong with the tcp connection.
var tcp_msg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
// This takes a pointer to a slice as an argument.
// It checks if there's a complete message and, if so, slices out those parts and returns the message, true, and nil.
// If there's no error, but also no complete message, it returns nil, false, and nil.
// If there's an error, it returns nil, false, and the error, which the reader then handles (currently, by returning from the reader, which causes the connection to close).
func tcp_chop_msg(bs *[]byte) ([]byte, bool, error) {
// Returns msg, ok, err
if len(*bs) < len(tcp_msg) {
return nil, false, nil
}
for idx := range tcp_msg {
if (*bs)[idx] != tcp_msg[idx] {
return nil, false, errors.New("Bad message!")
}
}
msgLen, msgLenLen := wire_decode_uint64((*bs)[len(tcp_msg):])
if msgLen > tcp_msgSize {
return nil, false, errors.New("Oversized message!")
}
msgBegin := len(tcp_msg) + msgLenLen
msgEnd := msgBegin + int(msgLen)
if msgLenLen == 0 || len(*bs) < msgEnd {
// We don't have the full message
// Need to buffer this and wait for the rest to come in
return nil, false, nil
}
msg := (*bs)[msgBegin:msgEnd]
(*bs) = (*bs)[msgEnd:]
return msg, true, nil
link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force)
if err != nil {
t.link.core.log.Println(err)
panic(err)
}
t.link.core.log.Debugln("DEBUG: starting handler for", name)
err = link.handler()
t.link.core.log.Debugln("DEBUG: stopped handler for", name, err)
}

View File

@@ -0,0 +1,28 @@
// +build darwin
package yggdrasil
import (
"syscall"
"golang.org/x/sys/unix"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error {
var control error
var recvanyif error
control = c.Control(func(fd uintptr) {
// sys/socket.h: #define SO_RECV_ANYIF 0x1104
recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
})
switch {
case recvanyif != nil:
return recvanyif
default:
return control
}
}

View File

@@ -0,0 +1,13 @@
// +build !darwin
package yggdrasil
import (
"syscall"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error {
return nil
}

View File

@@ -1,239 +0,0 @@
package yggdrasil
// This manages the tun driver to send/recv packets to/from applications
import (
"bytes"
"errors"
"sync"
"time"
"github.com/songgao/packets/ethernet"
"github.com/yggdrasil-network/water"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
const tun_IPv6_HEADER_LENGTH = 40
const tun_ETHER_HEADER_LENGTH = 14
// Represents a running TUN/TAP interface.
type tunAdapter struct {
Adapter
icmpv6 icmpv6
mtu int
iface *water.Interface
mutex sync.RWMutex // Protects the below
isOpen bool
}
// Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults().
func getSupportedMTU(mtu int) int {
if mtu > defaults.GetDefaults().MaximumIfMTU {
return defaults.GetDefaults().MaximumIfMTU
}
return mtu
}
// Initialises the TUN/TAP adapter.
func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
tun.Adapter.init(core, send, recv)
tun.icmpv6.init(tun)
}
// Starts the setup process for the TUN/TAP adapter, and if successful, starts
// the read/write goroutines to handle packets on that interface.
func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int) error {
if ifname == "none" {
return nil
}
if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil {
return err
}
tun.mutex.Lock()
tun.isOpen = true
tun.mutex.Unlock()
go func() { tun.core.log.Println("WARNING: tun.read() exited with error:", tun.read()) }()
go func() { tun.core.log.Println("WARNING: tun.write() exited with error:", tun.write()) }()
if iftapmode {
go func() {
for {
if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok {
break
}
request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr)
if err != nil {
panic(err)
}
if _, err := tun.iface.Write(request); err != nil {
panic(err)
}
time.Sleep(time.Second)
}
}()
}
return nil
}
// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP
// mode then additional ethernet encapsulation is added for the benefit of the
// host operating system.
func (tun *tunAdapter) write() error {
for {
data := <-tun.recv
if tun.iface == nil {
continue
}
if tun.iface.IsTAP() {
var destAddr address.Address
if data[0]&0xf0 == 0x60 {
if len(data) < 40 {
panic("Tried to send a packet shorter than an IPv6 header...")
}
copy(destAddr[:16], data[24:])
} else if data[0]&0xf0 == 0x40 {
if len(data) < 20 {
panic("Tried to send a packet shorter than an IPv4 header...")
}
copy(destAddr[:4], data[16:])
} else {
return errors.New("Invalid address family")
}
sendndp := func(destAddr address.Address) {
neigh, known := tun.icmpv6.peermacs[destAddr]
known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
if !known {
request, err := tun.icmpv6.create_ndp_tap(destAddr)
if err != nil {
panic(err)
}
if _, err := tun.iface.Write(request); err != nil {
panic(err)
}
tun.icmpv6.peermacs[destAddr] = neighbor{
lastsolicitation: time.Now(),
}
}
}
var peermac macAddress
var peerknown bool
if data[0]&0xf0 == 0x40 {
destAddr = tun.core.router.addr
} else if data[0]&0xf0 == 0x60 {
if !bytes.Equal(tun.core.router.addr[:16], destAddr[:16]) && !bytes.Equal(tun.core.router.subnet[:8], destAddr[:8]) {
destAddr = tun.core.router.addr
}
}
if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned {
peermac = neighbor.mac
peerknown = true
} else if neighbor, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok && neighbor.learned {
peermac = neighbor.mac
peerknown = true
sendndp(destAddr)
} else {
sendndp(tun.core.router.addr)
}
if peerknown {
var proto ethernet.Ethertype
switch {
case data[0]&0xf0 == 0x60:
proto = ethernet.IPv6
case data[0]&0xf0 == 0x40:
proto = ethernet.IPv4
}
var frame ethernet.Frame
frame.Prepare(
peermac[:6], // Destination MAC address
tun.icmpv6.mymac[:6], // Source MAC address
ethernet.NotTagged, // VLAN tagging
proto, // Ethertype
len(data)) // Payload length
copy(frame[tun_ETHER_HEADER_LENGTH:], data[:])
if _, err := tun.iface.Write(frame); err != nil {
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
panic(err)
}
}
}
} else {
if _, err := tun.iface.Write(data); err != nil {
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
panic(err)
}
}
}
util.PutBytes(data)
}
}
// Reads any packets that are waiting on the TUN/TAP adapter. If the adapter
// is running in TAP mode then the ethernet headers will automatically be
// processed and stripped if necessary. If an ICMPv6 packet is found, then
// the relevant helper functions in icmpv6.go are called.
func (tun *tunAdapter) read() error {
mtu := tun.mtu
if tun.iface.IsTAP() {
mtu += tun_ETHER_HEADER_LENGTH
}
buf := make([]byte, mtu)
for {
n, err := tun.iface.Read(buf)
if err != nil {
tun.mutex.RLock()
open := tun.isOpen
tun.mutex.RUnlock()
if !open {
return nil
} else {
// panic(err)
return err
}
}
o := 0
if tun.iface.IsTAP() {
o = tun_ETHER_HEADER_LENGTH
}
switch {
case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o:
case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o:
default:
continue
}
if buf[o+6] == 58 {
// Found an ICMPv6 packet
b := make([]byte, n)
copy(b, buf)
// tun.icmpv6.recv <- b
go tun.icmpv6.parse_packet(b)
}
packet := append(util.GetBytes(), buf[o:n]...)
tun.send <- packet
}
}
// Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil
// process stops. Typically this operation will happen quickly, but on macOS
// it can block until a read operation is completed.
func (tun *tunAdapter) close() error {
tun.mutex.Lock()
tun.isOpen = false
tun.mutex.Unlock()
if tun.iface == nil {
return nil
}
return tun.iface.Close()
}

View File

@@ -70,15 +70,18 @@ func wire_decode_uint64(bs []byte) (uint64, int) {
// Converts an int64 into uint64 so it can be written to the wire.
// Non-negative integers are mapped to even integers: 0 -> 0, 1 -> 2, etc.
// Negative integres are mapped to odd integes: -1 -> 1, -2 -> 3, etc.
// Negative integers are mapped to odd integers: -1 -> 1, -2 -> 3, etc.
// This means the least significant bit is a sign bit.
// This is known as zigzag encoding.
func wire_intToUint(i int64) uint64 {
return ((uint64(-(i+1))<<1)|0x01)*(uint64(i)>>63) + (uint64(i)<<1)*(^uint64(i)>>63)
// signed arithmetic shift
return uint64((i >> 63) ^ (i << 1))
}
// Converts uint64 back to int64, genreally when being read from the wire.
func wire_intFromUint(u uint64) int64 {
return int64(u&0x01)*(-int64(u>>1)-1) + int64(^u&0x01)*int64(u>>1)
// non-arithmetic shift
return int64((u >> 1) ^ -(u & 1))
}
////////////////////////////////////////////////////////////////////////////////
@@ -112,6 +115,29 @@ func wire_decode_coords(packet []byte) ([]byte, int) {
return packet[coordBegin:coordEnd], coordEnd
}
// Converts a []uint64 set of coords to a []byte set of coords.
func wire_coordsUint64stoBytes(in []uint64) (out []byte) {
for _, coord := range in {
c := wire_encode_uint64(coord)
out = append(out, c...)
}
return out
}
// Converts a []byte set of coords to a []uint64 set of coords.
func wire_coordsBytestoUint64s(in []byte) (out []uint64) {
offset := 0
for {
coord, length := wire_decode_uint64(in[offset:])
if length == 0 {
break
}
out = append(out, coord)
offset += length
}
return out
}
////////////////////////////////////////////////////////////////////////////////
// Encodes a swtichMsg into its wire format.
@@ -391,7 +417,7 @@ func (p *nodeinfoReqRes) decode(bs []byte) bool {
if len(bs) == 0 {
return false
}
p.NodeInfo = make(nodeinfoPayload, len(bs))
p.NodeInfo = make(NodeInfoPayload, len(bs))
if !wire_chop_slice(p.NodeInfo[:], &bs) {
return false
}