225 Commits

Author SHA1 Message Date
eyedeekay
7fc3116088 Bump version 2024-11-13 14:41:50 -05:00
eyedeekay
d3fb670d66 Fix go mods 2024-11-13 14:40:27 -05:00
eyedeekay
25751504b9 Fix up import paths 2024-11-09 11:54:54 -05:00
eyedeekay
a745742ee1 setup auto-assign workflow 2024-11-08 15:01:19 -05:00
eyedeekay
def28bbf7c Merge branch 'master' of github.com:eyedeekay/sam3 2024-11-08 12:55:50 -05:00
eyedeekay
e6c161ed99 change module path 2024-11-08 12:54:40 -05:00
idk
3ebfb85f8a Merge pull request #13 from hkh4n/refactor
Refactor
2024-11-03 21:42:55 +00:00
Haris Khan
14d0b22a28 add Read() and Write() methods 2024-11-02 23:26:10 -04:00
Haris Khan
67554060fb minor typo fix 2024-11-02 22:20:52 -04:00
Haris Khan
8b09ca7502 more uniform naming 2024-11-02 21:57:11 -04:00
idk
97d1c812d3 Merge pull request #12 from hkh4n/logging
logging naming convention hotfix
2024-10-23 17:34:38 +00:00
Haris Khan
5be3e27599 Attempt to fix naming collision with other libs 2024-10-23 00:03:16 -04:00
Haris Khan
ecba767d91 grammar 2024-10-17 14:51:19 -04:00
idk
5149b7e504 Merge pull request #11 from hkh4n/logging
Added logging
2024-10-17 18:51:15 +00:00
Haris Khan
67c0c9288a Updated README.md to reflect logging 2024-10-17 14:42:19 -04:00
Haris Khan
10f42af061 .Info -> .Debug 2024-10-16 17:21:44 -04:00
Haris Khan
aa63210c3c WORKING FIX 2024-10-16 11:23:49 -04:00
Haris Khan
4e1b426230 name collision 2024-10-16 10:51:21 -04:00
Haris Khan
a372049be9 added logging to suggestedOptions.go
-fixed name collision in stream_test.go
2024-10-16 10:35:43 -04:00
Haris Khan
6e2cc71a92 added logging to streamListener.go
-added dest in ExtractDest()
2024-10-16 10:24:30 -04:00
Haris Khan
988769ed5a fixed name collision in primary_stream_test.go 2024-10-16 10:18:06 -04:00
Haris Khan
504b7fb48b added logging to stream.go
-added better error handling to Lookup()
2024-10-16 10:14:57 -04:00
Haris Khan
c10b6b284c finished up sam3.go 2024-10-16 10:02:47 -04:00
Haris Khan
c2ad35a952 added logging to sam3.go & bumped i2pkeys v0.33.7 -> v0.33.8
-removed name collisions with "log"
2024-10-15 23:01:00 -04:00
Haris Khan
9e6d0489cf added logging to resolver.go 2024-10-15 22:26:33 -04:00
Haris Khan
d05428754b added logging to raw.go 2024-10-15 22:20:33 -04:00
Haris Khan
41d9bd0150 Merge branch 'master' into logging
# Conflicts:
#	go.mod
#	go.sum
2024-10-15 22:10:49 -04:00
Haris Khan
fd22f227b1 finished up primary.go 2024-10-15 22:09:18 -04:00
Haris Khan
0415adf35d added logging to primary.go
-added fromPort, toPort in NewUniqueStreamSubsession
2024-10-15 21:01:36 -04:00
Haris Khan
3655462ca4 added logging to emit-options.go 2024-10-15 17:01:43 -04:00
Haris Khan
4bb6f81c40 added logging to emit.go 2024-10-15 12:54:17 -04:00
Haris Khan
21e7b5a177 added logging to datagram.go 2024-10-15 12:39:20 -04:00
Haris Khan
4166a2c827 added log.go & logging for config.go 2024-10-15 12:23:24 -04:00
eyedeekay
cd2a4f072e update i2pkeys library to new version 2024-09-17 19:40:10 -04:00
eyedeekay
818b5249bc Add credit for contribution to release info 2024-09-17 19:35:15 -04:00
idk
83f9866de4 Merge pull request #10 from hkh4n/refactor
Refactored error handling in sam3.go
2024-09-12 12:26:56 -04:00
Haris Khan
b4293f755e refactored error handling in NewKeys() and newGenericSessionWithSignatureAndPorts() 2024-09-10 19:55:28 -04:00
Haris Khan
2bde2687b3 refactored error handling in NewSAM(), included "fmt" 2024-09-10 19:21:06 -04:00
eyedeekay
19d8d8e4a8 Update module to i2pkeys@v0.33.7 2024-01-09 14:45:40 -05:00
eyedeekay
1cec982a61 bump version 0.33.7 2024-01-09 14:22:29 -05:00
eyedeekay
b2df466212 Don't leave errors unchecked. gofmt again. 2024-01-09 14:20:35 -05:00
eyedeekay
bf2a12d78d fix misspellings 2024-01-09 13:40:42 -05:00
eyedeekay
760e0b44b2 gofmt -s 2024-01-09 13:37:49 -05:00
eyedeekay
9b7a798782 Add a space when specifying the port to a datagram session 2024-01-09 13:34:49 -05:00
eyedeekay
3dc49e391d force an enctype if one is not present 2024-01-07 12:09:13 -05:00
eyedeekay
b08d519a17 bump version 2023-07-21 15:10:09 -04:00
idk
256aaa7430 update go modules 2023-03-07 02:19:24 +00:00
idk
197aca0ece Never pass Sig_NONE again, if sig is unset, use the correct default sig 2023-01-16 04:18:11 +00:00
idk
fff37dbffa Never pass Sig_NONE again, if sig is unset, use the correct default sig 2023-01-16 04:17:51 +00:00
idk
dfd7cd886f update index.html 2022-08-28 13:40:47 -04:00
idk
c998e57a89 update index.html 2022-08-28 13:38:27 -04:00
idk
41317685c5 update index.html 2022-08-28 13:36:46 -04:00
idk
56cca2e537 bump version 2022-08-08 18:09:36 -04:00
idk
9ca67baa32 don't let the primary session thing be a guess, detect first, then attempt one and cache the result 2022-08-08 17:43:43 -04:00
idk
9083650fd0 switch to github-release 2022-08-05 11:57:22 -04:00
idk
34eafde2e2 bump version 2022-08-05 11:56:51 -04:00
idk
1780908fb8 add logging detail 2022-08-05 01:48:06 -04:00
idk
615604699a update index.html 2022-08-04 18:07:18 -04:00
idk
fb14beb87f update index.html 2022-07-31 18:03:00 -04:00
idk
6fb498e01e update index.html 2022-07-31 17:40:28 -04:00
idk
c80e6e7d95 update index.html 2022-07-31 17:39:38 -04:00
idk
ee9930813d fixes primary tunnel test 2022-06-01 18:25:24 -04:00
idk
3d289d99c6 fix some primary sessions, grab some defaults from environment 2022-05-31 23:59:37 -04:00
idk
089f97b99f update version again so go modules finds it. 2022-05-20 16:08:40 -04:00
idk
eea4aa60a7 fix import 2022-05-03 00:21:09 -04:00
idk
00fb462ecd add announcement. remove i2pkeys 2022-03-10 01:02:13 -05:00
idk
c167f8e26d add announcement. 2022-03-10 01:01:31 -05:00
idk
7d38382735 add a NetAddr interface which slightly extends net.Addr to use a port. This should make it easier to use fake ports in some apps. 2022-03-03 18:54:48 -05:00
idk
3c910e4b33 Network on Desthash should probably not have a pointer for a reciever 2022-03-03 18:18:56 -05:00
idk
ab0064e92e add helper to generate a DestHash from bytes, makes Bittorrent a little easier to do because of CompactIP's 2022-03-03 16:53:08 -05:00
idk
b41ee5a01f add helper to generate a DestHash from bytes, makes Bittorrent a little easier to do because of CompactIP's 2022-03-03 16:51:19 -05:00
idk
ba5ad234ee add helper to generate a DestHash from bytes, makes Bittorrent a little easier to do because of CompactIP's 2022-03-03 16:49:48 -05:00
idk
eb723e9492 add helper to generate a DestHash from bytes, makes Bittorrent a little easier to do because of CompactIP's 2022-03-03 16:45:52 -05:00
idk
45106d2b70 fix semver. Check router type to determine whether to send PRIMARY or MASTER to SAM session 2022-01-10 11:19:39 -05:00
idk
0e87ddfa4b Bump and create a tag for @allhailjarjar's checkin. 2021-10-29 16:25:10 -04:00
idk
9e3532c81b Merge pull request #8 from allhailjarjar/master
Readme update and code cleanup from @allhailjarjar approved @eyedeekay
2021-10-29 16:19:21 -04:00
idk
ca8d8688cb Split key generation out in helper 2021-10-27 22:38:52 -04:00
idk
ef67dc1e44 Split key generation out in helper 2021-10-27 22:38:21 -04:00
allhailjarjar666
9a162e9502 Readme update and code cleanup 2021-10-04 21:00:03 -04:00
idk
31b8d62f04 Allow passing either pointer or instance to DialI2PRemote in DatagramSession 2021-07-18 15:06:49 -04:00
idk
d191b3404e Allow passing either pointer or instance to DialI2PRemote in DatagramSession 2021-07-18 15:05:36 -04:00
idk
1de06ec9b9 Allow passing either pointer or instance to DialI2PRemote in DatagramSession 2021-07-18 15:05:02 -04:00
idk
ed814a2fc6 Add SetReadDeadline and SetWriteDeadline to StreamSession 2021-04-15 21:11:56 -04:00
idk
746084e65f Add localaddr to streamsession 2021-04-15 20:51:07 -04:00
idk
1a55eb6e90 Merge branch 'easy-keys' into 'master'
Support SAMv3.3 PRIMARY sessions and all 3 types of SubSessions

See merge request idk/sam3!1
2021-04-15 00:50:41 +00:00
idk
d307d85458 Actually, use SubSessions 2021-02-28 23:22:42 -05:00
idk
0be404e00f Actually, use SubSessions 2021-02-28 23:11:33 -05:00
idk
c37b1a099e Append a random string to dialed addrs 2021-02-28 22:54:54 -05:00
idk
9c93a9e934 Add a resolver 2021-02-28 18:47:37 -05:00
idk
a4c6ef983d implement x/dialer 2021-02-28 18:32:45 -05:00
idk
5629074653 implement x/dialer 2021-02-28 18:13:39 -05:00
idk
4e4d930c06 implement x/dialer 2021-02-28 17:00:17 -05:00
idk
18b8c77c79 Make it so that PrimarySession.Dial can be used like net.Dial 2021-02-27 00:02:04 -05:00
idk
aa8028230e Make it so that PrimarySession.Dial can be used like net.Dial 2021-02-26 23:52:54 -05:00
idk
6c5a389c9b Make it so that PrimarySession.Dial can be used like net.Dial 2021-02-26 23:50:56 -05:00
idk
818f18648a Primary Session should return LocalAddr 2021-02-26 16:04:18 -05:00
idk
ddbe66bfe9 Primary Session should return LocalAddr 2021-02-26 16:02:14 -05:00
idk
f46a953bc8 Primary Session should return LocalAddr 2021-02-26 16:00:43 -05:00
idk
e5f304f552 Primary Session should return LocalAddr 2021-02-26 15:55:22 -05:00
idk
27810c197c experiment: try using base32 as String in address since most things are going to expect it anyway, I think 2021-02-26 15:25:59 -05:00
idk
6c76e83617 experiment: try using base32 as String in address since most things are going to expect it anyway, I think 2021-02-26 13:09:39 -05:00
idk
1748849c87 experiment: try using base32 as String in address since most things are going to expect it anyway, I think 2021-02-26 13:07:22 -05:00
idk
0f604e88b2 Try directly creating a listener on top of a PrimarySession 2021-02-26 10:52:19 -05:00
idk
3ffa61bf70 Neat that was easy, SAM Primary sessions appear to work. 2021-02-22 12:54:52 -05:00
idk
e9ab98c71b Also port the datagram example to the Primary session 2021-02-21 00:28:26 -05:00
idk
6b90389d7f Add a basic test of a Primary session by making the datagramm session test fire off a datagram subsession 2021-02-21 00:07:06 -05:00
idk
0b4982e1fe Throw in some other useful defaults while I'm at it. TODO, switch everything over to the option emitter. Thought I already did that. 2021-02-19 23:38:05 -05:00
idk
06ef087039 That... might actually do it, but there's probably something important I missed so far. I'll write up primary_test.go tomorrow. 2021-02-19 23:32:05 -05:00
idk
2cb9b8f30c Add DatagramSession to helper library, stub out samv3.3 primary sessions 2021-02-19 21:47:37 -05:00
idk
bdc1edb224 Add DatagramSession to helper library, stub out samv3.3 primary sessions 2021-02-19 21:44:59 -05:00
idk
de74967cd2 Add DatagramSession to helper library, stub out samv3.3 primary sessions 2021-02-19 21:28:28 -05:00
idk
e8b472bed9 Ask for keys from the SAM API as a one-off event in i2pkeys. Set up for implementing crypto.Public/Private keys, crypto.Signer 2021-01-22 16:26:07 -05:00
idk
e45d6f7cb0 Make it so i2pkeys also implements net.Addr 2020-12-30 20:53:27 -05:00
idk
f3baac1fbe update mods and fmt 2020-12-14 21:43:28 -05:00
idk
692064fd13 simplify listener helper a bit 2020-12-14 21:43:04 -05:00
idk
109830a970 gofmt 2020-11-23 20:38:35 -05:00
idk
d8c42f6a0a Prep to cut a new release 2020-11-22 12:50:23 -05:00
idk
9f0d7f348c Prep to cut a new release 2020-11-22 12:48:28 -05:00
idk
f464873c93 add I2PStreamSession to helper 2020-11-22 00:08:55 -05:00
idk
a2f2e6786a add I2PStreamSession to helper 2020-11-22 00:05:04 -05:00
idk
704f2971b5 add a "helper" function which just magically creates a listener 2020-11-21 17:44:50 -05:00
idk
aef996d60a update deb changelog 2020-10-18 01:04:40 -04:00
idk
529cdd6063 update deb changelog 2020-10-18 01:03:01 -04:00
idk
33f3caa3f9 Add fmt make target, incorporate address length fix from qiwenmin, add a shortcut for saving your keys 2020-10-18 00:58:45 -04:00
idk
5a43ad260d Merge pull request #3 from qiwenmin/fix-wrong-addr
Fix wrong address.
2020-10-18 04:34:41 +00:00
Qi Wenmin
cd744bfe49 Fix wrong address.
With some addresses, ToBytes() calculates the wrong buffer lenngth
and returns the larger buffer for next step (sha256). So the b32
address will wrong.

For example, when the b64 key is the following:

tr2kQArAdxb7rt3Hr6itisY1Pi7ncWVUYDan2rjB45F1lIFvDPJih5dBa~WfWTvfyt
QKdtFoGHLuZdC8X71R5GXGvh8N0ECBtjhYPG8chFSEO2Cab2SZKYskEqGeOrlOvAQ-
yYaH0xCkarxFdvEbXoV~cfDhHDJcZI3Uc522sLWlB72-uOH9nlzyt-41byGc5GWn5c
CxJZx8UfszUIG9B2QoptrN0xquWvkLxescZ~0JYePTEJTeQFWlfPAiHcuAloAq3yKt
DCY4xcSracQr9ieCmnFJoUsLtzsjSrn7L~WwlI4pYYXxkm2dtzR8cPHJCBUBNMbK~K
TIxLtH0Tt9wI3edHgRMdxNm3EoozpPMXA05Ouni~ehWMhaywiJKTDZcGKDRqXJVm~y
xJGcXiXT2UzdwM~D20KTTUlLmeRqsU5hqpk2kgBIWKFfb8Gunx2hr0wEHsJypUwDQ-
FSGT0Fw0MRGq0QWvwXxroPbJdjsNnYOKCEogHxC4VrmzaLhHlEBQAEAAcAAA==

the right raw key length is 391, but ToBytes() returns a 393 bytes
raw key.

Using DecodeString() can fix this issue.
2020-09-27 08:33:52 +08:00
idk
1ef2d90f46 fix some debian stuff 2020-06-22 18:15:05 -04:00
idk
f90e3b2755 remove it from go mod 2020-06-22 14:09:44 -04:00
idk
a959ac58ba Merge pull request #2 from eyedeekay/fancy-config
Fancy config
2019-12-08 03:14:32 +00:00
idk
eecb73010c merge config parts of ramp 2019-12-07 22:10:55 -05:00
idk
442114200c purge ramp 2019-12-07 22:09:40 -05:00
idk
d4ac38fc97 purge ramp 2019-12-07 22:06:50 -05:00
idk
0a2d58418f Merge pull request #1 from eyedeekay/desthash44
add 44-char dest hashes
2019-12-07 22:31:43 +00:00
idk
ca1a4f7f54 update deb changelog 2019-12-07 17:31:04 -05:00
idk
6406efd277 add 44-char dest hashes 2019-12-07 17:26:28 -05:00
idk
a12f9ba498 add makefile to ease releases 2019-10-22 02:26:57 -04:00
idk
4a24538cd5 windows compatibility fixes 2019-10-22 02:24:38 -04:00
idk
f617388473 windows compatibility fixes 2019-10-22 02:19:01 -04:00
idk
c2b2b39c74 gofmt 2019-10-22 02:06:08 -04:00
idk
0067d37ca3 windows compatibility fixes 2019-10-22 02:05:58 -04:00
idk
f8d54526ea update go mod 2019-07-30 14:51:40 -04:00
idk
e9868b791e enable setting the write buffer size 2019-07-30 14:50:14 -04:00
idk
a810f009bd update modules 2019-07-29 20:02:43 -04:00
idk
99ad6522eb implement UDPConn as well 2019-06-12 23:41:17 -04:00
idk
6f6be01fb2 implement UDPConn too 2019-06-12 23:33:06 -04:00
idk
7438f855cd implement UDPConn too 2019-06-12 23:32:35 -04:00
idk
eabe42f33f completely remove I2PAddr.go to its own thing so it can be used separately 2019-05-25 14:36:22 -04:00
idk
0e9665027e completely remove I2PAddr.go to its own thing so it can be used separately 2019-05-25 14:36:16 -04:00
idk
79b526d69c test, fmt, update changelog and version number 2019-05-18 18:34:57 -04:00
idk
5b1dcc54aa Create stable branch 2019-05-18 18:32:21 -04:00
idk
6b863d7514 Create stable branch 2019-05-18 18:32:12 -04:00
idk
9ee74a89d3 Create stable branch 2019-05-18 18:31:44 -04:00
idk
6029d7dd6e Create stable branch 2019-05-18 18:30:22 -04:00
idk
265e8b21b4 update changelog 2019-05-18 18:21:01 -04:00
idk
9d24d56c28 thought that might have happened 2019-04-29 16:21:40 -04:00
idk
d622765b79 forgot to add moved i2pkeys dir 2019-04-20 14:50:55 -04:00
idk
57c0d7fc54 Experimental branch 2019-04-10 00:47:41 -04:00
idk
777c148eee move i2pkeys to it's own standalone directory. 2019-04-09 14:31:59 -04:00
idk
2b8b539e44 move i2pkeys to it's own standalone directory. 2019-04-09 14:26:27 -04:00
idk
c30f54fc7b expose from and to in listener 2019-03-26 23:23:37 -04:00
idk
d09fa462ed export sig constants 2019-03-26 23:19:10 -04:00
idk
4a6b7966f2 export sig constants 2019-03-26 23:17:57 -04:00
idk
d1d27c55aa add FROM_PORT and TO_PORT options to streaming 2019-03-26 23:09:53 -04:00
idk
d422604759 add FROM_PORT and TO_PORT options to streaming 2019-03-26 23:04:25 -04:00
idk
1e3684b26e Better setup for signatures 2019-03-26 22:22:00 -04:00
idk
917c6a7c49 only run offline tests 2019-02-28 21:45:25 -05:00
idk
a42f47931b update package 2019-02-28 21:40:30 -05:00
idk
8ed385c394 switch it to being debian-native 2019-02-28 21:28:49 -05:00
idk
3ae3281168 fix changelog 2019-02-25 11:14:20 -05:00
idk
ee0fb2c871 re-add in debian folder, may require editing 2019-02-25 11:13:37 -05:00
idk
a6914d7d9b re-add in debian folder, may require editing 2019-02-25 11:13:03 -05:00
idk
af5a3f3239 add unlicense 2019-02-23 13:50:58 -05:00
idk
2e872c6b20 Add signature support 2019-02-13 23:58:24 -05:00
idk
337bae0956 revert back to pointers 2019-02-09 17:08:20 -05:00
idk
ab5663c157 revert back to pointers 2019-02-09 17:01:29 -05:00
idk
554202709f update the DestHash structure 2018-12-25 21:20:46 -05:00
idk
d1a21d339f always something new to know 2018-12-25 04:21:03 -05:00
idk
07ebb0f823 always something new to know 2018-12-25 04:19:40 -05:00
idk
21b597c509 Make a standalone resolver 2018-12-24 23:50:26 -05:00
idk
93ee7a66e9 Start creating a standalone 'resolver' 2018-12-18 16:59:13 -05:00
idk
b81d625bea Actually add some context-awareness to DialContext* 2018-12-18 14:38:53 -05:00
idk
313d32ccf3 gofmt 2018-11-27 21:56:58 -05:00
idk
4065aba3ee context-aware dialer, well faking it 2018-11-27 21:56:34 -05:00
idk
5df47060c9 context-aware dialer, well faking it 2018-11-27 21:46:02 -05:00
idk
aebf5a4245 context-aware dialer 2018-11-27 21:36:33 -05:00
idk
acc809d485 I... don't think any of these should have pointer recievers, right? Switching them hasn't broken anything yet, that's why this is a fork, YOLO. 2018-11-27 12:25:30 -05:00
idk
0152ed864d I think I have some more work to do in here... 2018-11-27 12:10:38 -05:00
idk
c77a5d980c fixed typo 2018-09-26 11:29:22 -04:00
idk
d8ee0fca12 added an alias for ToBytes as Bytes because I need it to implement MultiAddr. 2018-09-26 11:27:12 -04:00
idk
4babdcd9f7 reverse that change. 2018-09-23 02:05:17 -04:00
idk
4949b11b57 updates 2018-09-19 14:35:28 -04:00
idk
c1321ac144 updates 2018-09-19 14:29:47 -04:00
idk
f7d729017d fix some tests 2018-09-09 01:34:48 -04:00
idk
f3a993315a Change to implement updated net.Conn 2018-09-09 01:33:24 -04:00
16e9417d2e merging from i2ptools/master subfolder 2016-09-12 01:11:35 -04:00
Jeff Becker
d0e367fbab fix sam3 2016-05-09 06:52:54 -04:00
Jeff Becker
090712478f in sam3 stream try many times to accept inbound connection 2016-05-07 08:51:59 -04:00
Jeff Becker
5233589f4a add i2p.PacketSession that implements net.PacketConn 2016-02-17 08:47:01 -05:00
Jeff Becker
700e096281 addr -> host 2016-02-10 19:16:00 -05:00
Jeff Becker
7edd91a383 go fmt + try fixing leak 2016-02-10 18:23:41 -05:00
Jeff Becker
fcc4565138 try fixing leaking sockets 2016-02-10 18:11:05 -05:00
Jeff Becker
60182b22f8 try fixing leaking connections 2016-02-10 17:58:03 -05:00
Jeff Becker
59f815f2e7 go fmt 2016-02-10 17:54:17 -05:00
Jeff Becker
e6936eed2f api fixups 2016-02-10 17:36:15 -05:00
Jeff Becker
99b4ecf533 don't leak 2016-02-05 16:34:03 -05:00
Jeff Becker
1d0d324d13 fix up sam3 2016-01-31 10:38:14 -05:00
jeff
9bbc5249e1 add go bindings for i2p that implement net.Listener etc 2016-01-15 08:49:14 -05:00
jeff
5a0f4e39dc add i2maild initial commit 2015-12-24 12:59:57 -05:00
jeff
3247769471 only dial out when name resolution succeedes 2015-12-22 22:48:10 -05:00
jeff
87e20ae658 fixups in sam3 2015-12-22 12:10:29 -05:00
jeff
0143d1df57 have stream listener close parent session on close 2015-12-17 11:05:16 -05:00
jeff
b8a2f40bf1 don't close nil sam session 2015-12-17 11:02:19 -05:00
jeff
b51a8977cf update sam3 libs to try fixing socket leaking in libi2ptorrent seeder 2015-12-17 11:01:04 -05:00
jeff
298b55c427 add streamsession.close 2015-12-14 17:13:45 -05:00
jeff
92493aa92a fix more leaking connections 2015-12-14 17:08:14 -05:00
jeff
6f95da72ea close leaking connections in stream 2015-12-14 17:01:06 -05:00
jeff
c6eba7d0a0 add transient keys and dialer 2015-12-14 13:58:45 -05:00
jeff
b500865ffe add config stubs 2015-12-14 09:00:11 -05:00
jeff
20095ae901 use sam STREAM ACCEPT instead of STREAM FORWARD for streams 2015-12-13 11:47:25 -05:00
jeff
13894c0a14 fix sam stream options, it's not OPTION=i2cp.option=value it's i2cp.option=value 2015-12-07 17:37:52 -05:00
jeff
d32071ba2e create keyfile 2015-11-30 12:48:45 -05:00
jeff
a30f0eded7 have sam3 streams actually implement the interfaces they say they do 2015-11-30 12:38:18 -05:00
jeff
937d862d90 have ensurekeyfile return the i2p keys 2015-11-30 11:36:49 -05:00
jeff
a1d2ac25d6 update sam3 libs to have more 2015-11-30 11:35:25 -05:00
jeff
4108184369 bundle sam3 2015-10-15 17:21:11 -04:00
42 changed files with 4188 additions and 531 deletions

20
.github/workflows/auto-assign.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Auto Assign
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@v1
with:
repo-token:${{ secrets.GITHUB_TOKEN }}
assignees: eyedeekay
numOfAssignee: 1

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*~
*.swp
README.md.asc

0
.nojekyll Normal file
View File

View File

@@ -1,119 +0,0 @@
package sam3
import (
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"errors"
)
var (
i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
)
// The public and private keys associated with an I2P destination. I2P hides the
// details of exactly what this is, so treat them as blobs, but generally: One
// pair of DSA keys, one pair of ElGamal keys, and sometimes (almost never) also
// a certificate. String() returns you the full content of I2PKeys and Addr()
// returns the public keys.
type I2PKeys struct {
addr I2PAddr // only the public key
both string // both public and private keys
}
// Creates I2PKeys from an I2PAddr and a public/private keypair string (as
// generated by String().)
func NewKeys(addr I2PAddr, both string) I2PKeys {
return I2PKeys{addr, both}
}
// Returns the public keys of the I2PKeys.
func (k I2PKeys) Addr() I2PAddr {
return k.addr
}
// Returns the keys (both public and private), in I2Ps base64 format. Use this
// when you create sessions.
func (k I2PKeys) String() string {
return k.both
}
// I2PAddr represents an I2P destination, almost equivalent to an IP address.
// This is the humongously huge base64 representation of such an address, which
// really is just a pair of public keys and also maybe a certificate. (I2P hides
// the details of exactly what it is. Read the I2P specifications for more info.)
type I2PAddr string
// Returns the base64 representation of the I2PAddr
func (a I2PAddr) Base64() string {
return string(a)
}
// Returns the I2P destination (base64-encoded)
func (a I2PAddr) String() string {
return string(a)
}
// Returns "I2P"
func (a I2PAddr) Network() string {
return "I2P"
}
// Creates a new I2P address from a base64-encoded string. Checks if the address
// addr is in correct format. (If you know for sure it is, use I2PAddr(addr).)
func NewI2PAddrFromString(addr string) (I2PAddr, error) {
// very basic check
if len(addr) > 4096 || len(addr) < 516 {
return I2PAddr(""), errors.New("Not an I2P address")
}
buf := make([]byte, i2pB64enc.DecodedLen(len(addr)))
if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil {
return I2PAddr(""), errors.New("Address is not base64-encoded")
}
return I2PAddr(addr), nil
}
// Creates a new I2P address from a byte array. The inverse of ToBytes().
func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) {
if len(addr) > 4096 || len(addr) < 384 {
return I2PAddr(""), errors.New("Not an I2P address")
}
buf := make([]byte, i2pB64enc.EncodedLen(len(addr)))
i2pB64enc.Encode(buf, addr)
return I2PAddr(string(buf)), nil
}
// Turns an I2P address to a byte array. The inverse of NewI2PAddrFromBytes().
func (addr I2PAddr) ToBytes() ([]byte, error) {
buf := make([]byte, i2pB64enc.DecodedLen(len(addr)))
if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil {
return buf, errors.New("Address is not base64-encoded")
}
return buf, nil
}
// Returns the *.b32.i2p address of the I2P address. It is supposed to be a
// somewhat human-manageable 64 character long pseudo-domain name equivalent of
// the 516+ characters long default base64-address (the I2PAddr format). It is
// not possible to turn the base32-address back into a usable I2PAddr without
// performing a Lookup(). Lookup only works if you are using the I2PAddr from
// which the b32 address was generated.
func (addr I2PAddr) Base32() string {
hash := sha256.New()
b, _ := addr.ToBytes()
hash.Write(b)
digest := hash.Sum(nil)
b32addr := make([]byte, 56)
i2pB32enc.Encode(b32addr, digest)
return string(b32addr[:52]) + ".b32.i2p"
}
// Makes any string into a *.b32.i2p human-readable I2P address. This makes no
// sense, unless "anything" is an I2P destination of some sort.
func Base32(anything string) string {
return I2PAddr(anything).Base32()
}

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
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/>

30
Makefile Normal file
View File

@@ -0,0 +1,30 @@
USER_GH=eyedeekay
VERSION=0.33.9
CREDIT='contributors to this release: @hkh4n, @eyedeekay'
packagename=sam3
echo:
@echo "type make version to do release $(VERSION)"
version:
github-release release -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION) -d "version $(VERSION) $(CREDIT)"
del:
github-release delete -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION)
tar:
tar --exclude .git \
--exclude .go \
--exclude bin \
-cJvf ../$(packagename)_$(VERSION).orig.tar.xz .
copier:
echo '#! /usr/bin/env sh' > deb/copy.sh
echo 'for f in $$(ls); do scp $$f/*.deb user@192.168.99.106:~/DEBIAN_PKGS/$$f/main/; done' >> deb/copy.sh
fmt:
find . -name '*.go' -exec gofmt -w -s {} \;
upload-linux:
github-release upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)"

View File

@@ -1,5 +1,19 @@
# README #
## !!IMPORTANT!!
In the next version, I'll be moving the `i2pkeys` directory to it's own repository
so I can avoid import cycle headaches. Please migrate to the new `i2pkeys` repository
before upgrading your sam3 dependencies. You can probably do this by running:
```sh
find . -name '*.go' -exec sed -i 's|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g' {} \;
```
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
# README #
go library for the I2P [SAMv3.0](https://geti2p.net/en/docs/api/samv3) bridge, used to build anonymous/pseudonymous end-to-end encrypted sockets.
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
@@ -23,13 +37,11 @@ This library is much better than ccondom (that use BOB), much more stable and mu
**Does not work:**
* Everything works! :D
* Stream Forwarding
* Probably needs some real-world testing
## Documentation ##
* [Online cached version](http://godoc.org/bitbucket.org/kallevedin/sam3)
* Latest version-documentation:
* set your GOPATH
* Enter `godoc -http=:8081` into your terminal and hit enter.
@@ -40,16 +52,17 @@ This library is much better than ccondom (that use BOB), much more stable and mu
package main
import (
"bitbucket.org/kallevedin/sam3"
"github.com/eyedeekay/sam3"
"github.com/eyedeekay/sam3/i2pkeys"
"fmt"
)
const yoursam = "127.0.0.1:7656" // sam bridge
func client(server I2PAddr) {
sam, _ := NewSAM(yoursam)
func client(server i2pkeys.I2PAddr) {
sam, _ := sam3.NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession("clientTun", keys, Options_Small)
stream, _ := sam.NewStreamSession("clientTun", keys, sam3.Options_Small)
fmt.Println("Client: Connecting to " + server.Base32())
conn, _ := stream.DialI2P(server)
conn.Write([]byte("Hello world!"))
@@ -59,9 +72,9 @@ func client(server I2PAddr) {
func main() {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
go client(keys.Addr())
stream, _ := sam.NewStreamSession("serverTun", keys, Options_Medium)
stream, _ := sam.NewStreamSession("serverTun", keys, sam3.Options_Medium)
listener, _ := stream.Listen()
go client(keys.Addr())
conn, _ := listener.Accept()
buf := make([]byte, 4096)
n, _ := conn.Read(buf)
@@ -80,14 +93,36 @@ Error handling was omitted in the above code for readability.
## Testing ##
* `go test` runs the whole suite (takes 90+ sec to perform!)
* `go test -tags=nettest` runs the whole suite (takes 90+ sec to perform!)
* `go test -short` runs the shorter variant, does not connect to anything
## Verbosity ##
Logging can be enabled and configured using the DEBUG_I2P environment variable. By default, logging is disabled.
There are three available log levels:
- Debug
```shell
export DEBUG_I2P=debug
```
- Warn
```shell
export DEBUG_I2P=warn
```
- Error
```shell
export DEBUG_I2P=error
```
If DEBUG_I2P is set to an unrecognized variable, it will fall back to "debug".
## License ##
Public domain.
## Authors ##
## Author ##
* Kalle Vedin `kalle.vedin@fripost.org`
* Unknown Name (majestrate)
* Unknown Name (majestrate)
* idk
* qiwenmin

105
README.md.asc Normal file
View File

@@ -0,0 +1,105 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
# README #
go library for the I2P [SAMv3.0](https://geti2p.net/en/docs/api/samv3) bridge, used to build anonymous/pseudonymous end-to-end encrypted sockets.
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
## Support/TODO ##
**What works:**
* Utils
* Resolving domain names to I2P destinations
* .b32.i2p hashes
* Generating keys/i2p destinations
* Streaming
* DialI2P() - Connecting to stuff in I2P
* Listen()/Accept() - Handling incomming connections
* Implements net.Conn and net.Listener
* Datagrams
* Implements net.PacketConn
* Raw datagrams
* Like datagrams, but without addresses
**Does not work:**
* Everything works! :D
* Probably needs some real-world testing
## Documentation ##
* Latest version-documentation:
* set your GOPATH
* Enter `godoc -http=:8081` into your terminal and hit enter.
* Goto http://localhost:8081, click packages, and navigate to sam3
## Examples ##
```go
package main
import (
"github.com/majestrate/i2p-tools/sam3"
"fmt"
)
const yoursam = "127.0.0.1:7656" // sam bridge
func client(server I2PAddr) {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession("clientTun", keys, Options_Small)
fmt.Println("Client: Connecting to " + server.Base32())
conn, _ := stream.DialI2P(server)
conn.Write([]byte("Hello world!"))
return
}
func main() {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
go client(keys.Addr())
stream, _ := sam.NewStreamSession("serverTun", keys, Options_Medium)
listener, _ := stream.Listen()
conn, _ := listener.Accept()
buf := make([]byte, 4096)
n, _ := conn.Read(buf)
fmt.Println("Server received: " + string(buf[:n]))
}
```
The above will write to the terminal:
```text
Client: Connecting to zjnvfh4hs3et5vtz35ogwzrws26zvwkcad5uo5esecvg4qpk5b4a.b32.i2p
Server received: Hello world!
```
Error handling was omitted in the above code for readability.
## Testing ##
* `go test` runs the whole suite (takes 90+ sec to perform!)
* `go test -short` runs the shorter variant, does not connect to anything
## License ##
Public domain.
## Author ##
* Kalle Vedin `kalle.vedin@fripost.org`
* Unknown Name (majestrate)
-----BEGIN PGP SIGNATURE-----
iQEzBAEBCgAdFiEEcNIGBzi++AUjrK/311wDs5teFOEFAlxk9W0ACgkQ11wDs5te
FOEl8wf/VinlOV3Op5fZ6Qr9eTanj85FoH9KbjA0P6EcUJap4QWoEUIK/mkIlhl/
jLdAMHry5K3gXObFrVAiO4XvsAF++/JJQroUWPb++UR9ksd+M4b63ia3/BtFQGUu
J2w6dK7S79Z6IumhA6xhdDaIRGiJSu8Ox0M36ZKkfAR3WTYfsz+Nucp+1l8otNhI
fjQrmLCSgAiaCW0h3m208JR20FYipjHc7CZBzF/TVCjPq9qleEMXQCTJBWcuibrR
RdoUct6oMAXNS4S7k4LYa5FK0ETNIap9aUyFa8Tp6tmzhSOun7/Gle3ynH/c9SrO
lopjX4BT4mpXN1Lvwdw+v3tTZPyuWQ==
=lYsZ
-----END PGP SIGNATURE-----

View File

@@ -1,57 +1,70 @@
package sam3
import (
"time"
"net"
"time"
"github.com/go-i2p/i2pkeys"
)
/*
import (
. "github.com/go-i2p/i2pkeys"
)
*/
// Implements net.Conn
type SAMConn struct {
laddr I2PAddr
raddr I2PAddr
conn net.Conn
laddr i2pkeys.I2PAddr
raddr i2pkeys.I2PAddr
conn net.Conn
}
// Implements net.Conn
func (sc SAMConn) Read(buf []byte) (int, error) {
func (sc *SAMConn) Read(buf []byte) (int, error) {
n, err := sc.conn.Read(buf)
return n, err
}
// Implements net.Conn
func (sc SAMConn) Write(buf []byte) (int, error) {
func (sc *SAMConn) Write(buf []byte) (int, error) {
n, err := sc.conn.Write(buf)
return n, err
}
// Implements net.Conn
func (sc SAMConn) Close() error {
func (sc *SAMConn) Close() error {
return sc.conn.Close()
}
// Implements net.Conn
func (sc SAMConn) LocalAddr() I2PAddr {
return sc.laddr
func (sc *SAMConn) LocalAddr() net.Addr {
return sc.localAddr()
}
// Implements net.Conn
func (sc SAMConn) RemoteAddr() I2PAddr {
func (sc *SAMConn) localAddr() i2pkeys.I2PAddr {
return sc.laddr
}
func (sc *SAMConn) RemoteAddr() net.Addr {
return sc.remoteAddr()
}
// Implements net.Conn
func (sc *SAMConn) remoteAddr() i2pkeys.I2PAddr {
return sc.raddr
}
// Implements net.Conn
func (sc SAMConn) SetDeadline(t time.Time) error {
func (sc *SAMConn) SetDeadline(t time.Time) error {
return sc.conn.SetDeadline(t)
}
// Implements net.Conn
func (sc SAMConn) SetReadDeadline(t time.Time) error {
func (sc *SAMConn) SetReadDeadline(t time.Time) error {
return sc.conn.SetReadDeadline(t)
}
// Implements net.Conn
func (sc SAMConn) SetWriteDeadline(t time.Time) error {
func (sc *SAMConn) SetWriteDeadline(t time.Time) error {
return sc.conn.SetWriteDeadline(t)
}

437
config.go Normal file
View File

@@ -0,0 +1,437 @@
package sam3
import (
"fmt"
"github.com/sirupsen/logrus"
"math/rand"
"net"
"strconv"
"strings"
"github.com/go-i2p/i2pkeys"
)
// I2PConfig is a struct which manages I2P configuration options
type I2PConfig struct {
SamHost string
SamPort string
TunName string
SamMin string
SamMax string
Fromport string
Toport string
Style string
TunType string
DestinationKeys i2pkeys.I2PKeys
SigType string
EncryptLeaseSet string
LeaseSetKey string
LeaseSetPrivateKey string
LeaseSetPrivateSigningKey string
LeaseSetKeys i2pkeys.I2PKeys
InAllowZeroHop string
OutAllowZeroHop string
InLength string
OutLength string
InQuantity string
OutQuantity string
InVariance string
OutVariance string
InBackupQuantity string
OutBackupQuantity string
FastRecieve string
UseCompression string
MessageReliability string
CloseIdle string
CloseIdleTime string
ReduceIdle string
ReduceIdleTime string
ReduceIdleQuantity string
LeaseSetEncryption string
//Streaming Library options
AccessListType string
AccessList []string
}
func (f *I2PConfig) Sam() string {
host := "127.0.0.1"
port := "7656"
if f.SamHost != "" {
host = f.SamHost
}
if f.SamPort != "" {
port = f.SamPort
}
log.WithFields(logrus.Fields{
"host": host,
"port": port,
}).Debug("SAM address constructed")
return host + ":" + port
}
func (f *I2PConfig) SetSAMAddress(addr string) {
hp := strings.Split(addr, ":")
if len(hp) == 1 {
f.SamHost = hp[0]
} else if len(hp) == 2 {
f.SamPort = hp[1]
f.SamHost = hp[0]
}
f.SamPort = "7656"
f.SamHost = "127.0.0.1"
log.WithFields(logrus.Fields{
"host": f.SamHost,
"port": f.SamPort,
}).Debug("SAM address set")
}
func (f *I2PConfig) ID() string {
if f.TunName == "" {
b := make([]byte, 12)
for i := range b {
b[i] = "abcdefghijklmnopqrstuvwxyz"[rand.Intn(len("abcdefghijklmnopqrstuvwxyz"))]
}
f.TunName = string(b)
log.WithField("TunName", f.TunName).Debug("Generated random tunnel name")
}
return " ID=" + f.TunName + " "
}
func (f *I2PConfig) Leasesetsettings() (string, string, string) {
var r, s, t string
if f.LeaseSetKey != "" {
r = " i2cp.leaseSetKey=" + f.LeaseSetKey + " "
}
if f.LeaseSetPrivateKey != "" {
s = " i2cp.leaseSetPrivateKey=" + f.LeaseSetPrivateKey + " "
}
if f.LeaseSetPrivateSigningKey != "" {
t = " i2cp.leaseSetPrivateSigningKey=" + f.LeaseSetPrivateSigningKey + " "
}
log.WithFields(logrus.Fields{
"leaseSetKey": r,
"leaseSetPrivateKey": s,
"leaseSetPrivateSigningKey": t,
}).Debug("Lease set settings constructed")
return r, s, t
}
func (f *I2PConfig) FromPort() string {
if f.samMax() < 3.1 {
log.Debug("SAM version < 3.1, FromPort not applicable")
return ""
}
if f.Fromport != "0" {
log.WithField("fromPort", f.Fromport).Debug("FromPort set")
return " FROM_PORT=" + f.Fromport + " "
}
log.Debug("FromPort not set")
return ""
}
func (f *I2PConfig) ToPort() string {
if f.samMax() < 3.1 {
log.Debug("SAM version < 3.1, ToPort not applicable")
return ""
}
if f.Toport != "0" {
log.WithField("toPort", f.Toport).Debug("ToPort set")
return " TO_PORT=" + f.Toport + " "
}
log.Debug("ToPort not set")
return ""
}
func (f *I2PConfig) SessionStyle() string {
if f.Style != "" {
log.WithField("style", f.Style).Debug("Session style set")
return " STYLE=" + f.Style + " "
}
log.Debug("Using default STREAM style")
return " STYLE=STREAM "
}
func (f *I2PConfig) samMax() float64 {
i, err := strconv.Atoi(f.SamMax)
if err != nil {
log.WithError(err).Warn("Failed to parse SamMax, using default 3.1")
return 3.1
}
log.WithField("samMax", float64(i)).Debug("SAM max version parsed")
return float64(i)
}
func (f *I2PConfig) MinSAM() string {
if f.SamMin == "" {
log.Debug("Using default MinSAM: 3.0")
return "3.0"
}
log.WithField("minSAM", f.SamMin).Debug("MinSAM set")
return f.SamMin
}
func (f *I2PConfig) MaxSAM() string {
if f.SamMax == "" {
log.Debug("Using default MaxSAM: 3.1")
return "3.1"
}
log.WithField("maxSAM", f.SamMax).Debug("MaxSAM set")
return f.SamMax
}
func (f *I2PConfig) DestinationKey() string {
if &f.DestinationKeys != nil {
log.WithField("destinationKey", f.DestinationKeys.String()).Debug("Destination key set")
return " DESTINATION=" + f.DestinationKeys.String() + " "
}
log.Debug("Using TRANSIENT destination")
return " DESTINATION=TRANSIENT "
}
func (f *I2PConfig) SignatureType() string {
if f.samMax() < 3.1 {
log.Debug("SAM version < 3.1, SignatureType not applicable")
return ""
}
if f.SigType != "" {
log.WithField("sigType", f.SigType).Debug("Signature type set")
return " SIGNATURE_TYPE=" + f.SigType + " "
}
log.Debug("Signature type not set")
return ""
}
func (f *I2PConfig) EncryptLease() string {
if f.EncryptLeaseSet == "true" {
log.Debug("Lease set encryption enabled")
return " i2cp.encryptLeaseSet=true "
}
log.Debug("Lease set encryption not enabled")
return ""
}
func (f *I2PConfig) Reliability() string {
if f.MessageReliability != "" {
log.WithField("reliability", f.MessageReliability).Debug("Message reliability set")
return " i2cp.messageReliability=" + f.MessageReliability + " "
}
log.Debug("Message reliability not set")
return ""
}
func (f *I2PConfig) Reduce() string {
if f.ReduceIdle == "true" {
log.WithFields(logrus.Fields{
"reduceIdle": f.ReduceIdle,
"reduceIdleTime": f.ReduceIdleTime,
"reduceIdleQuantity": f.ReduceIdleQuantity,
}).Debug("Reduce idle settings applied")
return "i2cp.reduceOnIdle=" + f.ReduceIdle + "i2cp.reduceIdleTime=" + f.ReduceIdleTime + "i2cp.reduceQuantity=" + f.ReduceIdleQuantity
}
log.Debug("Reduce idle settings not applied")
return ""
}
func (f *I2PConfig) Close() string {
if f.CloseIdle == "true" {
log.WithFields(logrus.Fields{
"closeIdle": f.CloseIdle,
"closeIdleTime": f.CloseIdleTime,
}).Debug("Close idle settings applied")
return "i2cp.closeOnIdle=" + f.CloseIdle + "i2cp.closeIdleTime=" + f.CloseIdleTime
}
log.Debug("Close idle settings not applied")
return ""
}
func (f *I2PConfig) DoZero() string {
r := ""
if f.InAllowZeroHop == "true" {
r += " inbound.allowZeroHop=" + f.InAllowZeroHop + " "
}
if f.OutAllowZeroHop == "true" {
r += " outbound.allowZeroHop= " + f.OutAllowZeroHop + " "
}
if f.FastRecieve == "true" {
r += " " + f.FastRecieve + " "
}
log.WithField("zeroHopSettings", r).Debug("Zero hop settings applied")
return r
}
func (f *I2PConfig) Print() []string {
lsk, lspk, lspsk := f.Leasesetsettings()
return []string{
//f.targetForPort443(),
"inbound.length=" + f.InLength,
"outbound.length=" + f.OutLength,
"inbound.lengthVariance=" + f.InVariance,
"outbound.lengthVariance=" + f.OutVariance,
"inbound.backupQuantity=" + f.InBackupQuantity,
"outbound.backupQuantity=" + f.OutBackupQuantity,
"inbound.quantity=" + f.InQuantity,
"outbound.quantity=" + f.OutQuantity,
f.DoZero(),
//"i2cp.fastRecieve=" + f.FastRecieve,
"i2cp.gzip=" + f.UseCompression,
f.Reduce(),
f.Close(),
f.Reliability(),
f.EncryptLease(),
lsk, lspk, lspsk,
f.Accesslisttype(),
f.Accesslist(),
f.LeaseSetEncryptionType(),
}
}
func (f *I2PConfig) Accesslisttype() string {
if f.AccessListType == "whitelist" {
log.Debug("Access list type set to whitelist")
return "i2cp.enableAccessList=true"
} else if f.AccessListType == "blacklist" {
log.Debug("Access list type set to blacklist")
return "i2cp.enableBlackList=true"
} else if f.AccessListType == "none" {
log.Debug("Access list type set to none")
return ""
}
log.Debug("Access list type not set")
return ""
}
func (f *I2PConfig) Accesslist() string {
if f.AccessListType != "" && len(f.AccessList) > 0 {
r := ""
for _, s := range f.AccessList {
r += s + ","
}
log.WithField("accessList", r).Debug("Access list generated")
return "i2cp.accessList=" + strings.TrimSuffix(r, ",")
}
log.Debug("Access list not set")
return ""
}
func (f *I2PConfig) LeaseSetEncryptionType() string {
if f.LeaseSetEncryption == "" {
log.Debug("Using default lease set encryption type: 4,0")
return "i2cp.leaseSetEncType=4,0"
}
for _, s := range strings.Split(f.LeaseSetEncryption, ",") {
if _, err := strconv.Atoi(s); err != nil {
log.WithField("invalidType", s).Panic("Invalid encrypted leaseSet type")
//panic("Invalid encrypted leaseSet type: " + s)
}
}
log.WithField("leaseSetEncType", f.LeaseSetEncryption).Debug("Lease set encryption type set")
return "i2cp.leaseSetEncType=" + f.LeaseSetEncryption
}
func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
var config I2PConfig
config.SamHost = "127.0.0.1"
config.SamPort = "7656"
config.SamMin = "3.0"
config.SamMax = "3.2"
config.TunName = ""
config.TunType = "server"
config.Style = "STREAM"
config.InLength = "3"
config.OutLength = "3"
config.InQuantity = "2"
config.OutQuantity = "2"
config.InVariance = "1"
config.OutVariance = "1"
config.InBackupQuantity = "3"
config.OutBackupQuantity = "3"
config.InAllowZeroHop = "false"
config.OutAllowZeroHop = "false"
config.EncryptLeaseSet = "false"
config.LeaseSetKey = ""
config.LeaseSetPrivateKey = ""
config.LeaseSetPrivateSigningKey = ""
config.FastRecieve = "false"
config.UseCompression = "true"
config.ReduceIdle = "false"
config.ReduceIdleTime = "15"
config.ReduceIdleQuantity = "4"
config.CloseIdle = "false"
config.CloseIdleTime = "300000"
config.MessageReliability = "none"
for _, o := range opts {
if err := o(&config); err != nil {
return nil, err
}
}
return &config, nil
}
// options map
type Options map[string]string
// obtain sam options as list of strings
func (opts Options) AsList() (ls []string) {
for k, v := range opts {
ls = append(ls, fmt.Sprintf("%s=%s", k, v))
}
return
}
// Config is the config type for the sam connector api for i2p which allows applications to 'speak' with i2p
type Config struct {
Addr string
Opts Options
Session string
Keyfile string
}
// create new sam connector from config with a stream session
func (cfg *Config) StreamSession() (session *StreamSession, err error) {
// connect
var s *SAM
s, err = NewSAM(cfg.Addr)
if err == nil {
// ensure keys exist
var keys i2pkeys.I2PKeys
keys, err = s.EnsureKeyfile(cfg.Keyfile)
if err == nil {
// create session
session, err = s.NewStreamSession(cfg.Session, keys, cfg.Opts.AsList())
}
}
return
}
// create new sam datagram session from config
func (cfg *Config) DatagramSession() (session *DatagramSession, err error) {
// connect
var s *SAM
s, err = NewSAM(cfg.Addr)
if err == nil {
// ensure keys exist
var keys i2pkeys.I2PKeys
keys, err = s.EnsureKeyfile(cfg.Keyfile)
if err == nil {
// determine udp port
var portstr string
_, portstr, err = net.SplitHostPort(cfg.Addr)
if IgnorePortError(err) == nil {
var port int
port, err = strconv.Atoi(portstr)
if err == nil && port > 0 {
// udp port is 1 lower
port--
// create session
session, err = s.NewDatagramSession(cfg.Session, keys, cfg.Opts.AsList(), port)
}
}
}
}
return
}

View File

@@ -3,75 +3,154 @@ package sam3
import (
"bytes"
"errors"
"github.com/sirupsen/logrus"
"net"
"strconv"
"time"
"github.com/go-i2p/i2pkeys"
)
// The DatagramSession implements net.PacketConn. It works almost like ordinary
// UDP, except that datagrams may be at most 31kB large. These datagrams are
// also end-to-end encrypted, signed and includes replay-protection. And they
// also end-to-end encrypted, signed and includes replay-protection. And they
// are also built to be surveillance-resistant (yey!).
type DatagramSession struct {
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys i2pkeys.I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
remoteAddr *i2pkeys.I2PAddr // optional remote I2P address
}
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *SAM) NewDatagramSession(id string, keys I2PKeys, options []string, udpPort int) (*DatagramSession, error) {
func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"id": id,
"udpPort": udpPort,
}).Debug("Creating new DatagramSession")
if udpPort > 65335 || udpPort < 0 {
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split local host port")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost + ":0")
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost + ":" + strconv.Itoa(udpPort))
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close()
return nil, err
}
return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr}, nil
conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{" PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create generic session")
return nil, err
}
log.WithField("id", id).Info("DatagramSession created successfully")
return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr, nil}, nil
}
// Reads one datagram sent to the destination of the DatagramSession. Returns
func (s *DatagramSession) B32() string {
b32 := s.keys.Addr().Base32()
log.WithField("b32", b32).Debug("Generated B32 address")
return b32
}
func (s *DatagramSession) Dial(net string, addr string) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"net": net,
"addr": addr,
}).Debug("Dialing address")
netaddr, err := s.Lookup(addr)
if err != nil {
log.WithError(err).Error("Lookup failed")
return nil, err
}
return s.DialI2PRemote(net, netaddr)
}
func (s *DatagramSession) DialRemote(net, addr string) (net.PacketConn, error) {
log.WithFields(logrus.Fields{
"net": net,
"addr": addr,
}).Debug("Dialing remote address")
netaddr, err := s.Lookup(addr)
if err != nil {
log.WithError(err).Error("Lookup failed")
return nil, err
}
return s.DialI2PRemote(net, netaddr)
}
func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"net": net,
"addr": addr,
}).Debug("Dialing I2P remote address")
switch addr.(type) {
case *i2pkeys.I2PAddr:
s.remoteAddr = addr.(*i2pkeys.I2PAddr)
case i2pkeys.I2PAddr:
i2paddr := addr.(i2pkeys.I2PAddr)
s.remoteAddr = &i2paddr
}
return s, nil
}
func (s *DatagramSession) RemoteAddr() net.Addr {
log.WithField("remoteAddr", s.remoteAddr).Debug("Getting remote address")
return s.remoteAddr
}
// Reads one datagram sent to the destination of the DatagramSession. Returns
// the number of bytes read, from what address it was sent, or an error.
func (s *DatagramSession) ReadFrom(b []byte) (n int, addr I2PAddr, err error) {
// implements net.PacketConn
func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
log.Debug("Reading datagram")
// extra bytes to read the remote address of incomming datagram
buf := make([]byte, len(b) + 4096)
buf := make([]byte, len(b)+4096)
for {
// very basic protection: only accept incomming UDP messages from the IP of the SAM bridge
var saddr *net.UDPAddr
n, saddr, err = s.udpconn.ReadFromUDP(buf)
if err != nil {
return 0, I2PAddr(""), err
log.WithError(err).Error("Failed to read from UDP")
return 0, i2pkeys.I2PAddr(""), err
}
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
continue
@@ -80,62 +159,124 @@ func (s *DatagramSession) ReadFrom(b []byte) (n int, addr I2PAddr, err error) {
}
i := bytes.IndexByte(buf, byte('\n'))
if i > 4096 || i > n {
return 0, I2PAddr(""), errors.New("Could not parse incomming message remote address.")
log.Error("Could not parse incoming message remote address")
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address.")
}
raddr, err := NewI2PAddrFromString(string(buf[:i]))
raddr, err := i2pkeys.NewI2PAddrFromString(string(buf[:i]))
if err != nil {
return 0, I2PAddr(""), errors.New("Could not parse incomming message remote address: " + err.Error())
log.WithError(err).Error("Could not parse incoming message remote address")
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address: " + err.Error())
}
// shift out the incomming address to contain only the data received
if ( n - i+1 ) > len(b) {
if (n - i + 1) > len(b) {
copy(b, buf[i+1:i+1+len(b)])
return n-(i+1), raddr, errors.New("Datagram did not fit into your buffer.")
return n - (i + 1), raddr, errors.New("Datagram did not fit into your buffer.")
} else {
copy(b, buf[i+1:n])
return n-(i+1), raddr, nil
log.WithField("bytesRead", n-(i+1)).Debug("Datagram read successfully")
return n - (i + 1), raddr, nil
}
}
// Sends one signed datagram to the destination specified. At the time of
func (s *DatagramSession) Accept() (net.Conn, error) {
log.Debug("Accept called on DatagramSession")
return s, nil
}
func (s *DatagramSession) Read(b []byte) (n int, err error) {
log.Debug("Reading from DatagramSession")
rint, _, rerr := s.ReadFrom(b)
return rint, rerr
}
// Sends one signed datagram to the destination specified. At the time of
// writing, maximum size is 31 kilobyte, but this may change in the future.
// Implements net.PacketConn.
func (s *DatagramSession) WriteTo(b []byte, addr I2PAddr) (n int, err error) {
header := []byte("3.0 " + s.id + " " + addr.String() + "\n")
func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) {
log.WithFields(logrus.Fields{
"addr": addr,
"datagramLen": len(b),
}).Debug("Writing datagram")
header := []byte("3.1 " + s.id + " " + addr.String() + "\n")
msg := append(header, b...)
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to write to UDP")
} else {
log.WithField("bytesWritten", n).Debug("Datagram written successfully")
}
return n, err
}
func (s *DatagramSession) Write(b []byte) (int, error) {
log.WithField("dataLen", len(b)).Debug("Writing to DatagramSession")
return s.WriteTo(b, s.remoteAddr)
}
// Closes the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) Close() error {
log.Debug("Closing DatagramSession")
err := s.conn.Close()
err2 := s.udpconn.Close()
if err != nil {
log.WithError(err).Error("Failed to close connection")
return err
}
if err2 != nil {
log.WithError(err2).Error("Failed to close UDP connection")
}
return err2
}
// Returns the I2P destination of the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) LocalAddr() I2PAddr {
return s.keys.Addr()
// Returns the I2P destination of the DatagramSession.
func (s *DatagramSession) LocalI2PAddr() i2pkeys.I2PAddr {
addr := s.keys.Addr()
log.WithField("localI2PAddr", addr).Debug("Getting local I2P address")
return addr
}
// Sets read and write deadlines for the DatagramSession. Implements
// Implements net.PacketConn
func (s *DatagramSession) LocalAddr() net.Addr {
return s.LocalI2PAddr()
}
func (s *DatagramSession) Addr() net.Addr {
return s.LocalI2PAddr()
}
func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) {
log.WithField("name", name).Debug("Looking up address")
var sam *SAM
sam, err = NewSAM(s.samAddr)
if err == nil {
defer sam.Close()
a, err = sam.Lookup(name)
}
log.WithField("address", a).Debug("Lookup successful")
return
}
// Sets read and write deadlines for the DatagramSession. Implements
// net.PacketConn and does the same thing. Setting write deadlines for datagrams
// is seldom done.
func (s *DatagramSession) SetDeadline(t time.Time) error {
log.WithField("deadline", t).Debug("Setting deadline")
return s.udpconn.SetDeadline(t)
}
// Sets read deadline for the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) SetReadDeadline(t time.Time) error {
log.WithField("readDeadline", t).Debug("Setting read deadline")
return s.udpconn.SetReadDeadline(t)
}
// Sets the write deadline for the DatagramSession. Implements net.Packetconn.
func (s *DatagramSession) SetWriteDeadline(t time.Time) error {
log.WithField("writeDeadline", t).Debug("Setting write deadline")
return s.udpconn.SetWriteDeadline(t)
}
func (s *DatagramSession) SetWriteBuffer(bytes int) error {
log.WithField("bytes", bytes).Debug("Setting write buffer")
return s.udpconn.SetWriteBuffer(bytes)
}

View File

@@ -6,7 +6,6 @@ import (
"time"
)
func Test_DatagramServerClient(t *testing.T) {
if testing.Short() {
return
@@ -24,7 +23,7 @@ func Test_DatagramServerClient(t *testing.T) {
t.Fail()
return
}
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
fmt.Println("\tServer: Creating tunnel")
ds, err := sam.NewDatagramSession("DGserverTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
@@ -33,7 +32,7 @@ func Test_DatagramServerClient(t *testing.T) {
return
}
c, w := make(chan bool), make(chan bool)
go func(c, w chan(bool)) {
go func(c, w chan (bool)) {
sam2, err := NewSAM(yoursam)
if err != nil {
c <- false
@@ -52,22 +51,22 @@ func Test_DatagramServerClient(t *testing.T) {
return
}
defer ds2.Close()
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
fmt.Println("\tClient: Tries to send datagram to server")
for {
select {
default :
_, err = ds2.WriteTo([]byte("Hello datagram-world! <3 <3 <3 <3 <3 <3"), ds.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send datagram: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w :
fmt.Println("\tClient: Sent datagram, quitting.")
default:
_, err = ds2.WriteTo([]byte("Hello datagram-world! <3 <3 <3 <3 <3 <3"), ds.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send datagram: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w:
fmt.Println("\tClient: Sent datagram, quitting.")
return
}
}
c <- true
@@ -82,19 +81,14 @@ func Test_DatagramServerClient(t *testing.T) {
return
}
fmt.Println("\tServer: Received datagram: " + string(buf[:n]))
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
}
func ExampleDatagramSession() {
// Creates a new DatagramSession, which behaves just like a net.PacketConn.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
@@ -128,6 +122,7 @@ func ExampleDatagramSession() {
fmt.Println(err.Error())
return
}
fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
@@ -135,3 +130,53 @@ func ExampleDatagramSession() {
//Got message: Hello myself!
}
func ExampleMiniDatagramSession() {
// Creates a new DatagramSession, which behaves just like a net.PacketConn.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
keys, err := sam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
myself := keys.Addr()
// See the example Option_* variables.
dg, err := sam.NewDatagramSession("MINIDGTUN", keys, Options_Small, 0)
if err != nil {
fmt.Println(err.Error())
return
}
someone, err := sam.Lookup("zzz.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
err = dg.SetWriteBuffer(14 * 1024)
if err != nil {
fmt.Println(err.Error())
return
}
dg.WriteTo([]byte("Hello stranger!"), someone)
dg.WriteTo([]byte("Hello myself!"), myself)
buf := make([]byte, 31*1024)
n, _, err := dg.ReadFrom(buf)
if err != nil {
fmt.Println(err.Error())
return
}
log.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
//Got message: Hello myself!
}

59
debian/changelog vendored Normal file
View File

@@ -0,0 +1,59 @@
golang-github-eyedeekay-sam3 (0.3.2.32) unreleased; urgency=medium
[ idk ]
* Include "Convenience Functions" in /helper
-- idk <hankhill19580@gmail.com> Sun, 22 Nov 2020 12:49:13 -0500
golang-github-eyedeekay-sam3 (0.3.2.31) unreleased; urgency=medium
[ idk ]
* Incorporate address fix from qiwenmin
* Shortcut for saving keys to text file
-- idk <hankhill19580@gmail.com> Sun, 18 Oct 2020 1:01:03 -0500
golang-github-eyedeekay-sam3 (0.3.2.3) unreleased; urgency=medium
[ idk ]
* Purge ramp, re-release
-- idk <hankhill19580@gmail.com> Mon, 22 Jun 2020 17:55:03 -0500
golang-github-eyedeekay-sam3 (0.3.2.2) bionic; urgency=medium
[ idk ]
* Purge ramp
-- idk <hankhill19580@gmail.com> Sat, 7 Dec 2019 22:05:34 -0500
golang-github-eyedeekay-sam3 (0.3.2.1) bionic; urgency=medium
[ idk ]
* Add support for 44-character destination hashes.
-- idk <hankhill19580@gmail.com> Sat, 7 Dec 2019 17:30:30 -0500
golang-github-eyedeekay-sam3 (0.3.2.01) bionic; urgency=medium
[ idk ]
* completely remove the old i2pkeys version and replace it with the new one.
-- idk <hankhill19580@gmail.com> Sat, 25 May 2019 14:38:11 -0500
golang-github-eyedeekay-sam3 (0.3.2.0) bionic; urgency=medium
[ idk ]
* Bug fixes, create stable branch
* Move i2pkeys
-- idk <hankhill19580@gmail.com> Sat, 18 May 2019 18:32:51 -0500
golang-github-eyedeekay-sam3 (0.0~git20190223.af5a3f3) bionic; urgency=medium
[ idk ]
* Initial release (Closes: TODO)
-- idk <hankhill19580@gmail.com> Thu, 28 Feb 2019 21:40:35 -0500

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
11

25
debian/control vendored Normal file
View File

@@ -0,0 +1,25 @@
Source: golang-github-eyedeekay-sam3
Section: devel
Priority: optional
Maintainer: Debian Go Packaging Team <team+pkg-go@tracker.debian.org>
Uploaders: idk <hankhill19580@gmail.com>
Build-Depends: debhelper (>= 11),
dh-golang,
git,
golang-any | golang-go,
Standards-Version: 4.2.1
Homepage: https://github.com/eyedeekay/sam3
Vcs-Browser: https://github.com/eyedeekay/sam3
Vcs-Git: https://github.com/eyedeekay/sam3.git
XS-Go-Import-Path: github.com/eyedeekay/sam3
Testsuite: autopkgtest-pkg-go
Package: golang-github-eyedeekay-sam3-dev
Architecture: all
Depends: ${misc:Depends},
i2p | i2pd
Description: Go library for the I2P SAMv3.0 bridge,
used to build anonymous/pseudonymous end-to-end encrypted sockets.
README go library for the I2P SAMv3.0
(https://geti2p.net/en/docs/api/samv3) bridge, used to build
anonymous/pseudonymous end-to-end encrypted sockets.

17
debian/copyright vendored Normal file
View File

@@ -0,0 +1,17 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: sam3
Source: https://github.com/eyedeekay/sam3
Files-Excluded:
Godeps/_workspace
Files: *
Copyright: 2016 idk
License: TODO
Files: debian/*
Copyright: 2019 idk <hankhill19580@gmail.com>
License: TODO
Comment: Debian packaging is licensed under the same terms as upstream
License: TODO
TODO

1
debian/files vendored Normal file
View File

@@ -0,0 +1 @@
golang-github-eyedeekay-sam3_0.3.2.32_source.buildinfo devel optional

2
debian/gbp.conf vendored Normal file
View File

@@ -0,0 +1,2 @@
[DEFAULT]
pristine-tar = True

4
debian/rules vendored Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/make -f
%:
dh $@ --buildsystem=golang --with=golang

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (native)

4
debian/watch vendored Normal file
View File

@@ -0,0 +1,4 @@
version=4
opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/golang-github-eyedeekay-sam3-\$1\.tar\.gz/,\
uversionmangle=s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/\$1~\$2\$3/ \
https://github.com/eyedeekay/sam3/tags .*/v?(\d\S*)\.tar\.gz

430
emit-options.go Normal file
View File

@@ -0,0 +1,430 @@
package sam3
import (
"fmt"
"github.com/sirupsen/logrus"
"strconv"
"strings"
)
// Option is a SAMEmit Option
type Option func(*SAMEmit) error
// SetType sets the type of the forwarder server
func SetType(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if s == "STREAM" {
c.Style = s
log.WithField("style", s).Debug("Set session style")
return nil
} else if s == "DATAGRAM" {
c.Style = s
log.WithField("style", s).Debug("Set session style")
return nil
} else if s == "RAW" {
c.Style = s
log.WithField("style", s).Debug("Set session style")
return nil
}
log.WithField("style", s).Error("Invalid session style")
return fmt.Errorf("Invalid session STYLE=%s, must be STREAM, DATAGRAM, or RAW", s)
}
}
// SetSAMAddress sets the SAM address all-at-once
func SetSAMAddress(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
sp := strings.Split(s, ":")
if len(sp) > 2 {
log.WithField("address", s).Error("Invalid SAM address")
return fmt.Errorf("Invalid address string: %s", sp)
}
if len(sp) == 2 {
c.I2PConfig.SamPort = sp[1]
}
c.I2PConfig.SamHost = sp[0]
log.WithFields(logrus.Fields{
"host": c.I2PConfig.SamHost,
"port": c.I2PConfig.SamPort,
}).Debug("Set SAM address")
return nil
}
}
// SetSAMHost sets the host of the SAMEmit's SAM bridge
func SetSAMHost(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.SamHost = s
log.WithField("host", s).Debug("Set SAM host")
return nil
}
}
// SetSAMPort sets the port of the SAMEmit's SAM bridge using a string
func SetSAMPort(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
port, err := strconv.Atoi(s)
if err != nil {
log.WithField("port", s).Error("Invalid SAM port: non-number")
return fmt.Errorf("Invalid SAM Port %s; non-number", s)
}
if port < 65536 && port > -1 {
c.I2PConfig.SamPort = s
log.WithField("port", s).Debug("Set SAM port")
return nil
}
log.WithField("port", port).Error("Invalid SAM port")
return fmt.Errorf("Invalid port")
}
}
// SetName sets the host of the SAMEmit's SAM bridge
func SetName(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.TunName = s
log.WithField("name", s).Debug("Set tunnel name")
return nil
}
}
// SetInLength sets the number of hops inbound
func SetInLength(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 7 && u >= 0 {
c.I2PConfig.InLength = strconv.Itoa(u)
log.WithField("inLength", u).Debug("Set inbound tunnel length")
return nil
}
log.WithField("inLength", u).Error("Invalid inbound tunnel length")
return fmt.Errorf("Invalid inbound tunnel length")
}
}
// SetOutLength sets the number of hops outbound
func SetOutLength(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 7 && u >= 0 {
c.I2PConfig.OutLength = strconv.Itoa(u)
log.WithField("outLength", u).Debug("Set outbound tunnel length")
return nil
}
log.WithField("outLength", u).Error("Invalid outbound tunnel length")
return fmt.Errorf("Invalid outbound tunnel length")
}
}
// SetInVariance sets the variance of a number of hops inbound
func SetInVariance(i int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if i < 7 && i > -7 {
c.I2PConfig.InVariance = strconv.Itoa(i)
log.WithField("inVariance", i).Debug("Set inbound tunnel variance")
return nil
}
log.WithField("inVariance", i).Error("Invalid inbound tunnel variance")
return fmt.Errorf("Invalid inbound tunnel length")
}
}
// SetOutVariance sets the variance of a number of hops outbound
func SetOutVariance(i int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if i < 7 && i > -7 {
c.I2PConfig.OutVariance = strconv.Itoa(i)
log.WithField("outVariance", i).Debug("Set outbound tunnel variance")
return nil
}
log.WithField("outVariance", i).Error("Invalid outbound tunnel variance")
return fmt.Errorf("Invalid outbound tunnel variance")
}
}
// SetInQuantity sets the inbound tunnel quantity
func SetInQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u <= 16 && u > 0 {
c.I2PConfig.InQuantity = strconv.Itoa(u)
log.WithField("inQuantity", u).Debug("Set inbound tunnel quantity")
return nil
}
log.WithField("inQuantity", u).Error("Invalid inbound tunnel quantity")
return fmt.Errorf("Invalid inbound tunnel quantity")
}
}
// SetOutQuantity sets the outbound tunnel quantity
func SetOutQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u <= 16 && u > 0 {
c.I2PConfig.OutQuantity = strconv.Itoa(u)
log.WithField("outQuantity", u).Debug("Set outbound tunnel quantity")
return nil
}
log.WithField("outQuantity", u).Error("Invalid outbound tunnel quantity")
return fmt.Errorf("Invalid outbound tunnel quantity")
}
}
// SetInBackups sets the inbound tunnel backups
func SetInBackups(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 6 && u >= 0 {
c.I2PConfig.InBackupQuantity = strconv.Itoa(u)
log.WithField("inBackups", u).Debug("Set inbound tunnel backups")
return nil
}
log.WithField("inBackups", u).Error("Invalid inbound tunnel backup quantity")
return fmt.Errorf("Invalid inbound tunnel backup quantity")
}
}
// SetOutBackups sets the inbound tunnel backups
func SetOutBackups(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 6 && u >= 0 {
c.I2PConfig.OutBackupQuantity = strconv.Itoa(u)
log.WithField("outBackups", u).Debug("Set outbound tunnel backups")
return nil
}
log.WithField("outBackups", u).Error("Invalid outbound tunnel backup quantity")
return fmt.Errorf("Invalid outbound tunnel backup quantity")
}
}
// SetEncrypt tells the router to use an encrypted leaseset
func SetEncrypt(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.EncryptLeaseSet = "true"
return nil
}
c.I2PConfig.EncryptLeaseSet = "false"
log.WithField("encrypt", b).Debug("Set lease set encryption")
return nil
}
}
// SetLeaseSetKey sets the host of the SAMEmit's SAM bridge
func SetLeaseSetKey(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.LeaseSetKey = s
log.WithField("leaseSetKey", s).Debug("Set lease set key")
return nil
}
}
// SetLeaseSetPrivateKey sets the host of the SAMEmit's SAM bridge
func SetLeaseSetPrivateKey(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.LeaseSetPrivateKey = s
log.WithField("leaseSetPrivateKey", s).Debug("Set lease set private key")
return nil
}
}
// SetLeaseSetPrivateSigningKey sets the host of the SAMEmit's SAM bridge
func SetLeaseSetPrivateSigningKey(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.LeaseSetPrivateSigningKey = s
log.WithField("leaseSetPrivateSigningKey", s).Debug("Set lease set private signing key")
return nil
}
}
// SetMessageReliability sets the host of the SAMEmit's SAM bridge
func SetMessageReliability(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.MessageReliability = s
log.WithField("messageReliability", s).Debug("Set message reliability")
return nil
}
}
// SetAllowZeroIn tells the tunnel to accept zero-hop peers
func SetAllowZeroIn(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.InAllowZeroHop = "true"
return nil
}
c.I2PConfig.InAllowZeroHop = "false"
log.WithField("allowZeroIn", b).Debug("Set allow zero-hop inbound")
return nil
}
}
// SetAllowZeroOut tells the tunnel to accept zero-hop peers
func SetAllowZeroOut(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.OutAllowZeroHop = "true"
return nil
}
c.I2PConfig.OutAllowZeroHop = "false"
log.WithField("allowZeroOut", b).Debug("Set allow zero-hop outbound")
return nil
}
}
// SetCompress tells clients to use compression
func SetCompress(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.UseCompression = "true"
return nil
}
c.I2PConfig.UseCompression = "false"
log.WithField("compress", b).Debug("Set compression")
return nil
}
}
// SetFastRecieve tells clients to use compression
func SetFastRecieve(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.FastRecieve = "true"
return nil
}
c.I2PConfig.FastRecieve = "false"
log.WithField("fastReceive", b).Debug("Set fast receive")
return nil
}
}
// SetReduceIdle tells the connection to reduce it's tunnels during extended idle time.
func SetReduceIdle(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.ReduceIdle = "true"
return nil
}
c.I2PConfig.ReduceIdle = "false"
log.WithField("reduceIdle", b).Debug("Set reduce idle")
return nil
}
}
// SetReduceIdleTime sets the time to wait before reducing tunnels to idle levels
func SetReduceIdleTime(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.ReduceIdleTime = "300000"
if u >= 6 {
idleTime := strconv.Itoa((u * 60) * 1000)
c.I2PConfig.ReduceIdleTime = idleTime
log.WithField("reduceIdleTime", idleTime).Debug("Set reduce idle time")
return nil
}
log.WithField("minutes", u).Error("Invalid reduce idle timeout")
return fmt.Errorf("Invalid reduce idle timeout(Measured in minutes) %v", u)
}
}
// SetReduceIdleTimeMs sets the time to wait before reducing tunnels to idle levels in milliseconds
func SetReduceIdleTimeMs(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.ReduceIdleTime = "300000"
if u >= 300000 {
c.I2PConfig.ReduceIdleTime = strconv.Itoa(u)
log.WithField("reduceIdleTimeMs", u).Debug("Set reduce idle time in milliseconds")
return nil
}
log.WithField("milliseconds", u).Error("Invalid reduce idle timeout")
return fmt.Errorf("Invalid reduce idle timeout(Measured in milliseconds) %v", u)
}
}
// SetReduceIdleQuantity sets minimum number of tunnels to reduce to during idle time
func SetReduceIdleQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 5 {
c.I2PConfig.ReduceIdleQuantity = strconv.Itoa(u)
log.WithField("reduceIdleQuantity", u).Debug("Set reduce idle quantity")
return nil
}
log.WithField("quantity", u).Error("Invalid reduce tunnel quantity")
return fmt.Errorf("Invalid reduce tunnel quantity")
}
}
// SetCloseIdle tells the connection to close it's tunnels during extended idle time.
func SetCloseIdle(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.CloseIdle = "true"
return nil
}
c.I2PConfig.CloseIdle = "false"
return nil
}
}
// SetCloseIdleTime sets the time to wait before closing tunnels to idle levels
func SetCloseIdleTime(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.CloseIdleTime = "300000"
if u >= 6 {
idleTime := strconv.Itoa((u * 60) * 1000)
c.I2PConfig.CloseIdleTime = idleTime
log.WithFields(logrus.Fields{
"minutes": u,
"milliseconds": idleTime,
}).Debug("Set close idle time")
return nil
}
log.WithField("minutes", u).Error("Invalid close idle timeout")
return fmt.Errorf("Invalid close idle timeout(Measured in minutes) %v", u)
}
}
// SetCloseIdleTimeMs sets the time to wait before closing tunnels to idle levels in milliseconds
func SetCloseIdleTimeMs(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.CloseIdleTime = "300000"
if u >= 300000 {
c.I2PConfig.CloseIdleTime = strconv.Itoa(u)
log.WithField("closeIdleTimeMs", u).Debug("Set close idle time in milliseconds")
return nil
}
return fmt.Errorf("Invalid close idle timeout(Measured in milliseconds) %v", u)
}
}
// SetAccessListType tells the system to treat the AccessList as a whitelist
func SetAccessListType(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if s == "whitelist" {
c.I2PConfig.AccessListType = "whitelist"
log.Debug("Set access list type to whitelist")
return nil
} else if s == "blacklist" {
c.I2PConfig.AccessListType = "blacklist"
log.Debug("Set access list type to blacklist")
return nil
} else if s == "none" {
c.I2PConfig.AccessListType = ""
log.Debug("Set access list type to none")
return nil
} else if s == "" {
c.I2PConfig.AccessListType = ""
log.Debug("Set access list type to none")
return nil
}
return fmt.Errorf("Invalid Access list type(whitelist, blacklist, none)")
}
}
// SetAccessList tells the system to treat the AccessList as a whitelist
func SetAccessList(s []string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if len(s) > 0 {
for _, a := range s {
c.I2PConfig.AccessList = append(c.I2PConfig.AccessList, a)
}
log.WithField("accessList", s).Debug("Set access list")
return nil
}
log.Debug("No access list set (empty list provided)")
return nil
}
}

139
emit.go Normal file
View File

@@ -0,0 +1,139 @@
package sam3
import (
"fmt"
"github.com/sirupsen/logrus"
"net"
"strings"
)
type SAMEmit struct {
I2PConfig
}
func (e *SAMEmit) OptStr() string {
optStr := strings.Join(e.I2PConfig.Print(), " ")
log.WithField("optStr", optStr).Debug("Generated option string")
return optStr
}
func (e *SAMEmit) Hello() string {
hello := fmt.Sprintf("HELLO VERSION MIN=%s MAX=%s \n", e.I2PConfig.MinSAM(), e.I2PConfig.MaxSAM())
log.WithField("hello", hello).Debug("Generated HELLO command")
return hello
}
func (e *SAMEmit) HelloBytes() []byte {
return []byte(e.Hello())
}
func (e *SAMEmit) GenerateDestination() string {
dest := fmt.Sprintf("DEST GENERATE %s \n", e.I2PConfig.SignatureType())
log.WithField("destination", dest).Debug("Generated DEST GENERATE command")
return dest
}
func (e *SAMEmit) GenerateDestinationBytes() []byte {
return []byte(e.GenerateDestination())
}
func (e *SAMEmit) Lookup(name string) string {
lookup := fmt.Sprintf("NAMING LOOKUP NAME=%s \n", name)
log.WithField("lookup", lookup).Debug("Generated NAMING LOOKUP command")
return lookup
}
func (e *SAMEmit) LookupBytes(name string) []byte {
return []byte(e.Lookup(name))
}
func (e *SAMEmit) Create() string {
create := fmt.Sprintf(
// //1 2 3 4 5 6 7
"SESSION CREATE %s%s%s%s%s%s%s \n",
e.I2PConfig.SessionStyle(), //1
e.I2PConfig.FromPort(), //2
e.I2PConfig.ToPort(), //3
e.I2PConfig.ID(), //4
e.I2PConfig.DestinationKey(), //5
e.I2PConfig.SignatureType(), //6
e.OptStr(), //7
)
log.WithField("create", create).Debug("Generated SESSION CREATE command")
return create
}
func (e *SAMEmit) CreateBytes() []byte {
fmt.Println("sam command: " + e.Create())
return []byte(e.Create())
}
func (e *SAMEmit) Connect(dest string) string {
connect := fmt.Sprintf(
"STREAM CONNECT ID=%s %s %s DESTINATION=%s \n",
e.I2PConfig.ID(),
e.I2PConfig.FromPort(),
e.I2PConfig.ToPort(),
dest,
)
log.WithField("connect", connect).Debug("Generated STREAM CONNECT command")
return connect
}
func (e *SAMEmit) ConnectBytes(dest string) []byte {
return []byte(e.Connect(dest))
}
func (e *SAMEmit) Accept() string {
accept := fmt.Sprintf(
"STREAM ACCEPT ID=%s %s %s",
e.I2PConfig.ID(),
e.I2PConfig.FromPort(),
e.I2PConfig.ToPort(),
)
log.WithField("accept", accept).Debug("Generated STREAM ACCEPT command")
return accept
}
func (e *SAMEmit) AcceptBytes() []byte {
return []byte(e.Accept())
}
func NewEmit(opts ...func(*SAMEmit) error) (*SAMEmit, error) {
var emit SAMEmit
for _, o := range opts {
if err := o(&emit); err != nil {
log.WithError(err).Error("Failed to apply option")
return nil, err
}
}
log.Debug("New SAMEmit instance created")
return &emit, nil
}
func IgnorePortError(err error) error {
if err == nil {
return nil
}
if strings.Contains(err.Error(), "missing port in address") {
log.Debug("Ignoring 'missing port in address' error")
err = nil
}
return err
}
func SplitHostPort(hostport string) (string, string, error) {
host, port, err := net.SplitHostPort(hostport)
if err != nil {
if IgnorePortError(err) == nil {
log.WithField("host", hostport).Debug("Using full string as host, port set to 0")
host = hostport
port = "0"
}
}
log.WithFields(logrus.Fields{
"host": host,
"port": port,
}).Debug("Split host and port")
return host, port, nil
}

9
go.mod Normal file
View File

@@ -0,0 +1,9 @@
module github.com/go-i2p/sam3
go 1.12
require (
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4
github.com/sirupsen/logrus v1.9.3
golang.org/x/sys v0.27.0 // indirect
)

19
go.sum Normal file
View File

@@ -0,0 +1,19 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4 h1:LRjaRCzg1ieGKZjELlaIg06Fx04RHzQLsWMYp1H6PQ4=
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

130
helper/helper.go Normal file
View File

@@ -0,0 +1,130 @@
package sam
import (
"io/ioutil"
"log"
"net"
"os"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3"
)
// HEY! If you're looking at this, there's a good chance that `github.com/go-i2p/onramp`
// is a better fit! Check it out.
func NetListener(name, samaddr, keyspath string) (net.Listener, error) {
return I2PListener(name, sam3.SAMDefaultAddr(samaddr), keyspath)
}
// I2PListener is a convenience function which takes a SAM tunnel name, a SAM address and a filename.
// If the file contains I2P keys, it will create a service using that address. If the file does not
// exist, keys will be generated and stored in that file.
func I2PListener(name, samaddr, keyspath string) (*sam3.StreamListener, error) {
log.Printf("Starting and registering I2P service, please wait a couple of minutes...")
listener, err := I2PStreamSession(name, sam3.SAMDefaultAddr(samaddr), keyspath)
if err != nil {
return nil, err
}
if keyspath != "" {
err = ioutil.WriteFile(keyspath+".i2p.public.txt", []byte(listener.Keys().Addr().Base32()), 0644)
if err != nil {
log.Fatalf("error storing I2P base32 address in adjacent text file, %s", err)
}
}
log.Printf("Listening on: %s", listener.Addr().Base32())
return listener.Listen()
}
// I2PStreamSession is a convenience function which returns a sam3.StreamSession instead
// of a sam3.StreamListener. It also takes care of setting a persisitent key on behalf
// of the user.
func I2PStreamSession(name, samaddr, keyspath string) (*sam3.StreamSession, error) {
log.Printf("Starting and registering I2P session...")
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(samaddr))
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
}
stream, err := sam.NewStreamSession(name, *keys, sam3.Options_Medium)
return stream, err
}
// I2PDataGramsession is a convenience function which returns a sam3.DatagramSession.
// It also takes care of setting a persisitent key on behalf of the user.
func I2PDatagramSession(name, samaddr, keyspath string) (*sam3.DatagramSession, error) {
log.Printf("Starting and registering I2P session...")
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(samaddr))
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
}
gram, err := sam.NewDatagramSession(name, *keys, sam3.Options_Medium, 0)
return gram, err
}
// I2PPrimarySession is a convenience function which returns a sam3.PrimarySession.
// It also takes care of setting a persisitent key on behalf of the user.
func I2PPrimarySession(name, samaddr, keyspath string) (*sam3.PrimarySession, error) {
log.Printf("Starting and registering I2P session...")
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(samaddr))
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
}
gram, err := sam.NewPrimarySession(name, *keys, sam3.Options_Medium)
return gram, err
}
// GenerateOrLoadKeys is a convenience function which takes a filename and a SAM session.
// if the SAM session is nil, a new one will be created with the defaults.
// The keyspath must be the path to a place to store I2P keys. The keyspath will be suffixed with
// .i2p.private for the private keys, and public.txt for the b32 addresses.
// If the keyspath.i2p.private file does not exist, keys will be generated and stored in that file.
// if the keyspath.i2p.private does exist, keys will be loaded from that location and returned
func GenerateOrLoadKeys(keyspath string, sam *sam3.SAM) (keys *i2pkeys.I2PKeys, err error) {
if sam == nil {
sam, err = sam3.NewSAM(sam3.SAMDefaultAddr("127.0.0.1:7656"))
if err != nil {
return nil, err
}
}
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
f, err := os.Create(keyspath + ".i2p.private")
if err != nil {
log.Fatalf("unable to open I2P keyfile for writing: %s", err)
}
defer f.Close()
tkeys, err := sam.NewKeys()
if err != nil {
log.Fatalf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
err = i2pkeys.StoreKeysIncompat(*keys, f)
if err != nil {
log.Fatalf("unable to save newly generated I2P Keys, %s", err)
}
} else {
tkeys, err := i2pkeys.LoadKeys(keyspath + ".i2p.private")
if err != nil {
log.Fatalf("unable to load I2P Keys: %e", err)
}
keys = &tkeys
}
return keys, nil
}
// GenerateKeys is a shorter version of GenerateOrLoadKeys which generates keys and stores them in a file.
// it always uses a new default SAM session.
func GenerateKeys(keyspath string) (keys *i2pkeys.I2PKeys, err error) {
return GenerateOrLoadKeys(keyspath, nil)
}

BIN
i2plogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

331
index.html Normal file
View File

@@ -0,0 +1,331 @@
<html>
<head>
<title>
README
</title>
<meta name="author" content="eyedeekay" />
<meta name="description" content="sam3" />
<meta name="keywords" content="master" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" type="text/css" href="showhider.css" />
</head>
<body>
<div id="navbar">
<a href="#shownav">
Show navigation
</a>
<div id="shownav">
<div id="hidenav">
<ul>
<li>
<a href="index.html">
index
</a>
</li>
</ul>
<br>
<a href="#hidenav">
Hide Navigation
</a>
</div>
</div>
</div>
<h1>
<a href="#readme" rel="nofollow">
<span></span>
</a>
README
</h1>
<h2>
<a href="#important" rel="nofollow">
<span></span>
</a>
!!IMPORTANT!!
</h2>
<p>
In the next version, I&#39;ll be moving the
<code>
i2pkeys
</code>
directory to it&#39;s own repository
so I can avoid import cycle headaches. Please migrate to the new
<code>
i2pkeys
</code>
repository
before upgrading your sam3 dependencies. You can probably do this by running:
</p>
<div>
<pre>find . -name &#39;*.go&#39; -exec sed -i &#39;s|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g&#39; {} \;
</pre>
</div>
<p>
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
</p>
<h1>
<a href="#readme" rel="nofollow">
<span></span>
</a>
README
</h1>
<p>
go library for the I2P
<a href="https://geti2p.net/en/docs/api/samv3" rel="nofollow">
SAMv3.0
</a>
bridge, used to build anonymous/pseudonymous end-to-end encrypted sockets.
</p>
<p>
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
</p>
<h2>
<a href="#support-todo" rel="nofollow">
<span></span>
</a>
Support/TODO
</h2>
<p>
<strong>
What works:
</strong>
</p>
<ul>
<li>
Utils
<ul>
<li>
Resolving domain names to I2P destinations
</li>
<li>
.b32.i2p hashes
</li>
<li>
Generating keys/i2p destinations
</li>
</ul>
</li>
<li>
Streaming
<ul>
<li>
DialI2P() - Connecting to stuff in I2P
</li>
<li>
Listen()/Accept() - Handling incomming connections
</li>
<li>
Implements net.Conn and net.Listener
</li>
</ul>
</li>
<li>
Datagrams
<ul>
<li>
Implements net.PacketConn
</li>
</ul>
</li>
<li>
Raw datagrams
<ul>
<li>
Like datagrams, but without addresses
</li>
</ul>
</li>
</ul>
<p>
<strong>
Does not work:
</strong>
</p>
<ul>
<li>
Stream Forwarding
</li>
<li>
Probably needs some real-world testing
</li>
</ul>
<h2>
<a href="#documentation" rel="nofollow">
<span></span>
</a>
Documentation
</h2>
<ul>
<li>
Latest version-documentation:
<ul>
<li>
set your GOPATH
</li>
<li>
Enter
<code>
godoc -http=:8081
</code>
into your terminal and hit enter.
</li>
<li>
Goto
<a href="http://localhost:8081" rel="nofollow">
http://localhost:8081
</a>
, click packages, and navigate to sam3
</li>
</ul>
</li>
</ul>
<h2>
<a href="#examples" rel="nofollow">
<span></span>
</a>
Examples
</h2>
<div>
<pre>package main
import (
&#34;github.com/eyedeekay/sam3&#34;
&#34;github.com/eyedeekay/sam3/i2pkeys&#34;
&#34;fmt&#34;
)
const yoursam = &#34;127.0.0.1:7656&#34; // sam bridge
func client(server i2pkeys.I2PAddr) {
sam, _ := sam3.NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession(&#34;clientTun&#34;, keys, sam3.Options_Small)
fmt.Println(&#34;Client: Connecting to &#34; + server.Base32())
conn, _ := stream.DialI2P(server)
conn.Write([]byte(&#34;Hello world!&#34;))
return
}
func main() {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession(&#34;serverTun&#34;, keys, sam3.Options_Medium)
listener, _ := stream.Listen()
go client(keys.Addr())
conn, _ := listener.Accept()
buf := make([]byte, 4096)
n, _ := conn.Read(buf)
fmt.Println(&#34;Server received: &#34; + string(buf[:n]))
}
</pre>
</div>
<p>
The above will write to the terminal:
</p>
<div>
<pre>Client: Connecting to zjnvfh4hs3et5vtz35ogwzrws26zvwkcad5uo5esecvg4qpk5b4a.b32.i2p
Server received: Hello world!
</pre>
</div>
<p>
Error handling was omitted in the above code for readability.
</p>
<h2>
<a href="#testing" rel="nofollow">
<span></span>
</a>
Testing
</h2>
<ul>
<li>
<code>
go test -tags=nettest
</code>
runs the whole suite (takes 90+ sec to perform!)
</li>
<li>
<code>
go test -short
</code>
runs the shorter variant, does not connect to anything
</li>
</ul>
<h2>
<a href="#license" rel="nofollow">
<span></span>
</a>
License
</h2>
<p>
Public domain.
</p>
<h2>
<a href="#author" rel="nofollow">
<span></span>
</a>
Author
</h2>
<ul>
<li>
Kalle Vedin
<code>
kalle.vedin@fripost.org
</code>
</li>
<li>
Unknown Name (majestrate)
</li>
<li>
idk
</li>
<li>
qiwenmin
</li>
</ul>
<div>
<a href="#show">
Show license
</a>
<div id="show">
<div id="hide">
<pre><code>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/>
</code></pre>
<a href="#hide">
Hide license
</a>
</div>
</div>
</div>
<div>
<iframe src="https://snowflake.torproject.org/embed.html" width="320" height="240" frameborder="0" scrolling="no"></iframe>
</div>
<div>
<a href="https://geti2p.net/">
<img src="i2plogo.png"></img>
I2P
</a>
</div>
</body>
</html>

50
log.go Normal file
View File

@@ -0,0 +1,50 @@
package sam3
import (
"github.com/sirupsen/logrus"
"io/ioutil"
"os"
"strings"
"sync"
)
var (
log *logrus.Logger
once sync.Once
)
func InitializeSAM3Logger() {
once.Do(func() {
log = logrus.New()
// We do not want to log by default
log.SetOutput(ioutil.Discard)
log.SetLevel(logrus.PanicLevel)
// Check if DEBUG_I2P is set
if logLevel := os.Getenv("DEBUG_I2P"); logLevel != "" {
log.SetOutput(os.Stdout)
switch strings.ToLower(logLevel) {
case "debug":
log.SetLevel(logrus.DebugLevel)
case "warn":
log.SetLevel(logrus.WarnLevel)
case "error":
log.SetLevel(logrus.ErrorLevel)
default:
log.SetLevel(logrus.DebugLevel)
}
log.WithField("level", log.GetLevel()).Debug("Logging enabled.")
}
})
}
// GetSAM3Logger returns the initialized logger
func GetSAM3Logger() *logrus.Logger {
if log == nil {
InitializeSAM3Logger()
}
return log
}
func init() {
InitializeSAM3Logger()
}

482
primary.go Normal file
View File

@@ -0,0 +1,482 @@
package sam3
import (
"errors"
"fmt"
"github.com/sirupsen/logrus"
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/go-i2p/i2pkeys"
)
const (
session_ADDOK = "SESSION STATUS RESULT=OK"
)
func randport() string {
s := rand.NewSource(time.Now().UnixNano())
r := rand.New(s)
p := r.Intn(55534) + 10000
port := strconv.Itoa(p)
log.WithField("port", port).Debug("Generated random port")
return strconv.Itoa(p)
}
// Represents a primary session.
type PrimarySession struct {
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam
keys i2pkeys.I2PKeys // i2p destination keys
Timeout time.Duration
Deadline time.Time
sigType string
Config SAMEmit
stsess map[string]*StreamSession
dgsess map[string]*DatagramSession
// from string
// to string
}
func (ss *PrimarySession) From() string {
return "0"
}
func (ss *PrimarySession) To() string {
return "0"
}
func (ss *PrimarySession) SignatureType() string {
return ss.sigType
}
// Returns the local tunnel name of the I2P tunnel used for the stream session
func (ss *PrimarySession) ID() string {
return ss.id
}
func (ss *PrimarySession) Close() error {
return ss.conn.Close()
}
// Returns the I2P destination (the address) of the stream session
func (ss *PrimarySession) Addr() i2pkeys.I2PAddr {
return ss.keys.Addr()
}
func (ss *PrimarySession) LocalAddr() net.Addr {
aa := ss.keys.Addr()
return &aa
}
// Returns the keys associated with the stream session
func (ss *PrimarySession) Keys() i2pkeys.I2PKeys {
return ss.keys
}
func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": network, "addr": addr}).Debug("Dial() called")
if network == "udp" || network == "udp4" || network == "udp6" {
//return sam.DialUDPI2P(network, network+addr[0:4], addr)
return sam.DialUDPI2P(network, network+addr[0:4], addr)
}
if network == "tcp" || network == "tcp4" || network == "tcp6" {
//return sam.DialTCPI2P(network, network+addr[0:4], addr)
return sam.DialTCPI2P(network, network+addr[0:4], addr)
}
log.WithField("network", network).Error("Invalid network type")
return nil, fmt.Errorf("Error: Must specify a valid network type")
}
// DialTCP implements x/dialer
func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCP() called")
ts, ok := sam.stsess[network+raddr.String()[0:4]]
var err error
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + raddr.String()[0:4])
if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
return nil, err
}
sam.stsess[network+raddr.String()[0:4]] = ts
ts, _ = sam.stsess[network+raddr.String()[0:4]]
}
return ts.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCPI2P() called")
ts, ok := sam.stsess[network+raddr[0:4]]
var err error
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + laddr)
if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
return nil, err
}
sam.stsess[network+raddr[0:4]] = ts
ts, _ = sam.stsess[network+raddr[0:4]]
}
return ts.Dial(network, raddr)
}
// DialUDP implements x/dialer
func (sam *PrimarySession) DialUDP(network string, laddr, raddr net.Addr) (net.PacketConn, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialUDP() called")
ds, ok := sam.dgsess[network+raddr.String()[0:4]]
var err error
if !ok {
ds, err = sam.NewDatagramSubSession(network+raddr.String()[0:4], 0)
if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
return nil, err
}
sam.dgsess[network+raddr.String()[0:4]] = ds
ds, _ = sam.dgsess[network+raddr.String()[0:4]]
}
return ds.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*DatagramSession, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialUDPI2P() called")
ds, ok := sam.dgsess[network+raddr[0:4]]
var err error
if !ok {
ds, err = sam.NewDatagramSubSession(network+laddr, 0)
if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
return nil, err
}
sam.dgsess[network+raddr[0:4]] = ds
ds, _ = sam.dgsess[network+raddr[0:4]]
}
return ds.Dial(network, raddr)
}
func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) {
log.WithField("name", name).Debug("Lookup() called")
var sam *SAM
name = strings.Split(name, ":")[0]
sam, err = NewSAM(s.samAddr)
if err == nil {
log.WithField("addr", a).Debug("Lookup successful")
defer sam.Close()
a, err = sam.Lookup(name)
}
log.WithError(err).Error("Lookup failed")
return
}
func (sam *PrimarySession) Resolve(network, addr string) (net.Addr, error) {
log.WithFields(logrus.Fields{"network": network, "addr": addr}).Debug("Resolve() called")
return sam.Lookup(addr)
}
func (sam *PrimarySession) ResolveTCPAddr(network, dest string) (net.Addr, error) {
log.WithFields(logrus.Fields{"network": network, "dest": dest}).Debug("ResolveTCPAddr() called")
return sam.Lookup(dest)
}
func (sam *PrimarySession) ResolveUDPAddr(network, dest string) (net.Addr, error) {
log.WithFields(logrus.Fields{"network": network, "dest": dest}).Debug("ResolveUDPAddr() called")
return sam.Lookup(dest)
}
// Creates a new PrimarySession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
log.WithFields(logrus.Fields{"id": id, "options": options}).Debug("NewPrimarySession() called")
return sam.newPrimarySession(PrimarySessionSwitch, id, keys, options)
}
func (sam *SAM) newPrimarySession(primarySessionSwitch string, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
log.WithFields(logrus.Fields{
"primarySessionSwitch": primarySessionSwitch,
"id": id,
"options": options,
}).Debug("newPrimarySession() called")
conn, err := sam.newGenericSession(primarySessionSwitch, id, keys, options, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic session")
return nil, err
}
ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession)
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, sam.Config, ssesss, dsesss}, nil
}
// Creates a new PrimarySession with the I2CP- and PRIMARYinglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error) {
log.WithFields(logrus.Fields{
"id": id,
"options": options,
"sigType": sigType,
}).Debug("NewPrimarySessionWithSignature() called")
conn, err := sam.newGenericSessionWithSignature(PrimarySessionSwitch, id, keys, sigType, options, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic session with signature")
return nil, err
}
ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession)
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, sam.Config, ssesss, dsesss}, nil
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *PrimarySession) newGenericSubSession(style, id string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "extras": extras}).Debug("newGenericSubSession called")
return sam.newGenericSubSessionWithSignature(style, id, extras)
}
func (sam *PrimarySession) newGenericSubSessionWithSignature(style, id string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "extras": extras}).Debug("newGenericSubSessionWithSignature called")
return sam.newGenericSubSessionWithSignatureAndPorts(style, id, "0", "0", extras)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id, from, to string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "extras": extras}).Debug("newGenericSubSessionWithSignatureAndPorts called")
conn := sam.conn
fp := ""
tp := ""
if from != "0" && from != "" {
fp = " FROM_PORT=" + from
}
if to != "0" && to != "" {
tp = " TO_PORT=" + to
}
scmsg := []byte("SESSION ADD STYLE=" + style + " ID=" + id + fp + tp + " " + strings.Join(extras, " ") + "\n")
log.WithField("message", string(scmsg)).Debug("Sending SESSION ADD message")
for m, i := 0, 0; m != len(scmsg); i++ {
if i == 15 {
conn.Close()
log.Error("Writing to SAM failed after 15 attempts")
return nil, errors.New("writing to SAM failed")
}
n, err := conn.Write(scmsg[m:])
if err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
conn.Close()
return nil, err
}
m += n
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read from SAM connection")
conn.Close()
return nil, err
}
text := string(buf[:n])
log.WithField("response", text).Debug("Received response from SAM")
//log.Println("SAM:", text)
if strings.HasPrefix(text, session_ADDOK) {
//if sam.keys.String() != text[len(session_ADDOK):len(text)-1] {
//conn.Close()
//return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
//}
log.Debug("Session added successfully")
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
} else if text == session_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close()
return nil, errors.New("Duplicate tunnel name")
} else if text == session_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close()
return nil, errors.New("Duplicate destination")
} else if text == session_INVALID_KEY {
log.Error("Invalid key - Primary Session")
conn.Close()
return nil, errors.New("Invalid key - Primary Session")
} else if strings.HasPrefix(text, session_I2P_ERROR) {
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
conn.Close()
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])
} else {
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
conn.Close()
return nil, errors.New("Unable to parse SAMv3 reply: " + text)
}
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error) {
log.WithField("id", id).Debug("NewStreamSubSession called")
conn, err := sam.newGenericSubSession("STREAM", id, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, nil
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewUniqueStreamSubSession(id string) (*StreamSession, error) {
log.WithField("id", id).Debug("NewUniqueStreamSubSession called")
conn, err := sam.newGenericSubSession("STREAM", id, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
fromPort, toPort := randport(), randport()
log.WithFields(logrus.Fields{"fromPort": fromPort, "toPort": toPort}).Debug("Generated random ports")
//return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, randport(), randport()}, nil
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, fromPort, toPort}, nil
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewStreamSubSessionWithPorts(id, from, to string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to}).Debug("NewStreamSubSessionWithPorts called")
conn, err := sam.newGenericSubSessionWithSignatureAndPorts("STREAM", id, from, to, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session with signature and ports")
return nil, err
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, from, to}, nil
}
/*
func (s *PrimarySession) I2PListener(name string) (*StreamListener, error) {
listener, err := s.NewStreamSubSession(name)
if err != nil {
return nil, err
}
return listener.Listen()
}
*/
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*DatagramSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewDatagramSubSession called")
if udpPort > 65335 || udpPort < 0 {
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split local host port")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close()
return nil, err
}
conn, err := s.newGenericSubSession("DATAGRAM", id, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "localPort": lport}).Debug("Created new datagram sub-session")
return &DatagramSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr, nil}, nil
}
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewRawSubSession called")
if udpPort > 65335 || udpPort < 0 {
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split local host port")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close()
return nil, err
}
// conn, err := s.newGenericSubSession("RAW", id, s.keys, options, []string{"PORT=" + lport})
conn, err := s.newGenericSubSession("RAW", id, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "localPort": lport}).Debug("Created new raw sub-session")
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr}, nil
}

148
primary_datagram_test.go Normal file
View File

@@ -0,0 +1,148 @@
package sam3
import (
"fmt"
"testing"
"time"
)
func Test_PrimaryDatagramServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_PrimaryDatagramServerClient")
earlysam, err := NewSAM(yoursam)
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
fmt.Println("\tServer: Creating tunnel")
ds, err := sam.NewDatagramSubSession("PrimaryTunnel"+RandString(), 0)
if err != nil {
fmt.Println("Server: Failed to create tunnel: " + err.Error())
t.Fail()
return
}
defer ds.Close()
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
sam2, err := NewSAM(yoursam)
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Creating tunnel")
ds2, err := sam2.NewDatagramSession("PRIMARYClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
c <- false
return
}
defer ds2.Close()
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
fmt.Println("\tClient: Tries to send primary to server")
for {
select {
default:
_, err = ds2.WriteTo([]byte("Hello primary-world! <3 <3 <3 <3 <3 <3"), ds.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send primary: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w:
fmt.Println("\tClient: Sent primary, quitting.")
return
}
}
c <- true
}(c, w)
buf := make([]byte, 512)
fmt.Println("\tServer: ReadFrom() waiting...")
n, _, err := ds.ReadFrom(buf)
w <- true
if err != nil {
fmt.Println("\tServer: Failed to ReadFrom(): " + err.Error())
t.Fail()
return
}
fmt.Println("\tServer: Received primary: " + string(buf[:n]))
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
}
func ExamplePrimaryDatagramSession() {
// Creates a new PrimarySession, then creates a Datagram subsession on top of it
const samBridge = "127.0.0.1:7656"
earlysam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
myself := keys.Addr()
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
fmt.Println(err.Error())
return
}
defer sam.Close()
// See the example Option_* variables.
dg, err := sam.NewDatagramSubSession("DGTUN"+RandString(), 0)
if err != nil {
fmt.Println(err.Error())
return
}
defer dg.Close()
someone, err := earlysam.Lookup("zzz.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
dg.WriteTo([]byte("Hello stranger!"), someone)
dg.WriteTo([]byte("Hello myself!"), myself)
buf := make([]byte, 31*1024)
n, _, err := dg.ReadFrom(buf)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
//Got message: Hello myself!
}

307
primary_stream_test.go Normal file
View File

@@ -0,0 +1,307 @@
package sam3
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
)
func Test_PrimaryStreamingDial(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_PrimaryStreamingDial")
earlysam, err := NewSAM(yoursam)
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
fmt.Println("\tBuilding tunnel")
ss, err := sam.NewStreamSubSession("primaryStreamTunnel")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer ss.Close()
fmt.Println("\tNotice: This may fail if your I2P node is not well integrated in the I2P network.")
fmt.Println("\tLooking up i2p-projekt.i2p")
forumAddr, err := earlysam.Lookup("i2p-projekt.i2p")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tDialing i2p-projekt.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
conn, err := ss.DialI2P(forumAddr)
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer conn.Close()
fmt.Println("\tSending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("\tProbably failed to StreamSession.DialI2P(i2p-projekt.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("\tRead HTTP/HTML from i2p-projekt.i2p")
}
}
func Test_PrimaryStreamingServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_StreamingServerClient")
earlysam, err := NewSAM(yoursam)
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryServerClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
fmt.Println("\tServer: Creating tunnel")
ss, err := sam.NewUniqueStreamSubSession("PrimaryServerClientTunnel")
if err != nil {
return
}
defer ss.Close()
time.Sleep(time.Second * 10)
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
if !(<-w) {
return
}
/*
sam2, err := NewSAM(yoursam)
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
*/
fmt.Println("\tClient: Creating tunnel")
ss2, err := sam.NewStreamSubSession("primaryExampleClientTun")
if err != nil {
c <- false
return
}
defer ss2.Close()
fmt.Println("\tClient: Connecting to server")
conn, err := ss2.DialI2P(ss.Addr())
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Connected to tunnel")
defer conn.Close()
_, err = conn.Write([]byte("Hello world <3 <3 <3 <3 <3 <3"))
if err != nil {
c <- false
return
}
c <- true
}(c, w)
l, err := ss.Listen()
if err != nil {
fmt.Println("ss.Listen(): " + err.Error())
t.Fail()
w <- false
return
}
defer l.Close()
w <- true
fmt.Println("\tServer: Accept()ing on tunnel")
conn, err := l.Accept()
if err != nil {
t.Fail()
fmt.Println("Failed to Accept(): " + err.Error())
return
}
defer conn.Close()
buf := make([]byte, 512)
n, err := conn.Read(buf)
fmt.Printf("\tClient exited successfully: %t\n", <-c)
fmt.Println("\tServer: received from Client: " + string(buf[:n]))
}
func ExamplePrimaryStreamSession() {
// Creates a new StreamingSession, dials to idk.i2p and gets a SAMConn
// which behaves just like a normal net.Conn.
const samBridge = "127.0.0.1:7656"
earlysam, err := NewSAM(yoursam)
if err != nil {
log.Fatal(err.Error())
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
log.Fatal(err.Error())
return
}
sam, err := earlysam.NewPrimarySession("PrimaryStreamSessionTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
log.Fatal(err.Error())
return
}
defer sam.Close()
conn, err := sam.Dial("tcp", "idk.i2p") //someone.Base32())
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
fmt.Println("Sending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
log.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("Read HTTP/HTML from idk.i2p")
log.Println("Read HTTP/HTML from idk.i2p")
}
// Output:
//Sending HTTP GET /
//Read HTTP/HTML from idk.i2p
}
func ExamplePrimaryStreamListener() {
// One server Accept()ing on a StreamListener, and one client that Dials
// through I2P to the server. Server writes "Hello world!" through a SAMConn
// (which implements net.Conn) and the client prints the message.
const samBridge = "127.0.0.1:7656"
var ss *StreamSession
go func() {
earlysam, err := NewSAM(yoursam)
if err != nil {
log.Fatal(err.Error())
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
log.Fatal(err.Error())
return
}
sam, err := earlysam.NewPrimarySession("PrimaryListenerTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
log.Fatal(err.Error())
return
}
defer sam.Close()
ss, err = sam.NewStreamSubSession("PrimaryListenerServerTunnel2")
if err != nil {
fmt.Println(err.Error())
return
}
defer ss.Close()
l, err := ss.Listen()
if err != nil {
fmt.Println(err.Error())
return
}
defer l.Close()
//fmt.Println("Serving on primary listener", l.Addr().String())
if err := http.Serve(l, &exitHandler{}); err != nil {
fmt.Println(err.Error())
}
}()
time.Sleep(time.Second * 10)
latesam, err := NewSAM(yoursam)
if err != nil {
log.Fatal(err.Error())
return
}
defer latesam.Close()
keys2, err := latesam.NewKeys()
if err != nil {
log.Fatal(err.Error())
return
}
sc, err := latesam.NewStreamSession("PrimaryListenerClientTunnel2", keys2, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
fmt.Println(err.Error())
return
}
defer sc.Close()
client := http.Client{
Transport: &http.Transport{
Dial: sc.Dial,
},
}
//resp, err := client.Get("http://" + "idk.i2p") //ss.Addr().Base32())
resp, err := client.Get("http://" + ss.Addr().Base32())
if err != nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
r, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("Got response: " + string(r))
// Output:
// Got response: Hello world!
}
type exitHandler struct {
}
func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))
}

91
raw.go
View File

@@ -3,105 +3,152 @@ package sam3
import (
"bytes"
"errors"
"github.com/sirupsen/logrus"
"net"
"strconv"
"time"
"github.com/go-i2p/i2pkeys"
)
// The RawSession provides no authentication of senders, and there is no sender
// address attached to datagrams, so all communication is anonymous. The
// address attached to datagrams, so all communication is anonymous. The
// messages send are however still endpoint-to-endpoint encrypted. You
// need to figure out a way to identify and authenticate clients yourself, iff
// that is needed. Raw datagrams may be at most 32 kB in size. There is no
// that is needed. Raw datagrams may be at most 32 kB in size. There is no
// overhead of authentication, which is the reason to use this..
type RawSession struct {
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys i2pkeys.I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
}
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *SAM) NewRawSession(id string, keys I2PKeys, options []string, udpPort int) (*RawSession, error) {
func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("Creating new RawSession")
if udpPort > 65335 || udpPort < 0 {
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the interval 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
log.Debug("Using default UDP port 7655")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost + ":0")
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost + ":" + strconv.Itoa(udpPort))
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
conn, err := s.newGenericSession("RAW", id, keys, options, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to get local port")
return nil, err
}
return &RawSession{s.address, id, conn, udpconn, keys, rUDPAddr}, nil
conn, err := s.newGenericSession("RAW", id, keys, options, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create new generic session")
return nil, err
}
log.WithFields(logrus.Fields{
"id": id,
"localPort": lport,
"remoteUDPAddr": rUDPAddr,
}).Debug("Created new RawSession")
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil
}
// Reads one raw datagram sent to the destination of the DatagramSession. Returns
// Reads one raw datagram sent to the destination of the DatagramSession. Returns
// the number of bytes read. Who sent the raw message can not be determined at
// this layer - you need to do it (in a secure way!).
func (s *RawSession) Read(b []byte) (n int, err error) {
log.Debug("Attempting to read raw datagram")
for {
// very basic protection: only accept incomming UDP messages from the IP of the SAM bridge
var saddr *net.UDPAddr
n, saddr, err = s.udpconn.ReadFromUDP(b)
if err != nil {
log.WithError(err).Error("Failed to read from UDP")
return 0, err
}
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
log.WithField("senderIP", saddr.IP).Debug("Received datagram from SAM bridge IP")
continue
}
break
}
log.WithField("bytesRead", n).Debug("Successfully read raw datagram")
return n, nil
}
// Sends one raw datagram to the destination specified. At the time of writing,
// maximum size is 32 kilobyte, but this may change in the future.
func (s *RawSession) WriteTo(b []byte, addr I2PAddr) (n int, err error) {
func (s *RawSession) WriteTo(b []byte, addr i2pkeys.I2PAddr) (n int, err error) {
log.WithFields(logrus.Fields{
"destAddr": addr.String(),
"dataLen": len(b),
}).Debug("Attempting to write raw datagram")
header := []byte("3.0 " + s.id + " " + addr.String() + "\n")
msg := append(header, b...)
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to write to UDP")
}
log.WithField("bytesWritten", n).Debug("Successfully wrote raw datagram")
return n, err
}
// Closes the RawSession.
func (s *RawSession) Close() error {
log.Debug("Closing RawSession")
err := s.conn.Close()
err2 := s.udpconn.Close()
if err != nil {
log.WithError(err).Error("Failed to close connection")
return err
}
err2 := s.udpconn.Close()
if err2 != nil {
log.WithError(err2).Error("Failed to close UDP connection")
}
log.Debug("RawSession closed")
return err2
}
// Returns the local I2P destination of the RawSession.
func (s *RawSession) LocalAddr() I2PAddr {
func (s *RawSession) LocalAddr() i2pkeys.I2PAddr {
return s.keys.Addr()
}
@@ -116,5 +163,3 @@ func (s *RawSession) SetReadDeadline(t time.Time) error {
func (s *RawSession) SetWriteDeadline(t time.Time) error {
return s.udpconn.SetWriteDeadline(t)
}

86
resolver.go Normal file
View File

@@ -0,0 +1,86 @@
package sam3
import (
"bufio"
"bytes"
"errors"
"strings"
"github.com/go-i2p/i2pkeys"
)
type SAMResolver struct {
*SAM
}
func NewSAMResolver(parent *SAM) (*SAMResolver, error) {
log.Debug("Creating new SAMResolver from existing SAM instance")
var s SAMResolver
s.SAM = parent
return &s, nil
}
func NewFullSAMResolver(address string) (*SAMResolver, error) {
log.WithField("address", address).Debug("Creating new full SAMResolver")
var s SAMResolver
var err error
s.SAM, err = NewSAM(address)
if err != nil {
log.WithError(err).Error("Failed to create new SAM instance")
return nil, err
}
return &s, nil
}
// Performs a lookup, probably this order: 1) routers known addresses, cached
// addresses, 3) by asking peers in the I2P network.
func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Resolving name")
if _, err := sam.conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\r\n")); err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
sam.Close()
return i2pkeys.I2PAddr(""), err
}
buf := make([]byte, 4096)
n, err := sam.conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read from SAM connection")
sam.Close()
return i2pkeys.I2PAddr(""), err
}
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") {
log.Error("Failed to parse SAM response")
return i2pkeys.I2PAddr(""), errors.New("Failed to parse.")
}
s := bufio.NewScanner(bytes.NewReader(buf[13:n]))
s.Split(bufio.ScanWords)
errStr := ""
for s.Scan() {
text := s.Text()
log.WithField("text", text).Debug("Parsing SAM response token")
//log.Println("SAM3", text)
if text == "RESULT=OK" {
continue
} else if text == "RESULT=INVALID_KEY" {
errStr += "Invalid key - resolver."
log.Error("Invalid key in resolver")
} else if text == "RESULT=KEY_NOT_FOUND" {
errStr += "Unable to resolve " + name
log.WithField("name", name).Error("Unable to resolve name")
} else if text == "NAME="+name {
continue
} else if strings.HasPrefix(text, "VALUE=") {
addr := i2pkeys.I2PAddr(text[6:])
log.WithField("addr", addr).Debug("Name resolved successfully")
return i2pkeys.I2PAddr(text[6:]), nil
} else if strings.HasPrefix(text, "MESSAGE=") {
errStr += " " + text[8:]
log.WithField("message", text[8:]).Warn("Received message from SAM")
} else {
continue
}
}
return i2pkeys.I2PAddr(""), errors.New(errStr)
}

290
sam3.go
View File

@@ -4,17 +4,32 @@ package sam3
import (
"bufio"
"bytes"
"net"
"errors"
"fmt"
"github.com/sirupsen/logrus"
"io"
"math/rand"
"net"
"os"
"strings"
"github.com/go-i2p/i2pkeys"
. "github.com/go-i2p/i2pkeys"
)
func init() {
InitializeSAM3Logger()
}
// Used for controlling I2Ps SAMv3.
type SAM struct {
address string // ipv4:port
conn net.Conn
address string
conn net.Conn
resolver *SAMResolver
Config SAMEmit
keys *i2pkeys.I2PKeys
sigType int
}
const (
@@ -25,152 +40,275 @@ const (
session_I2P_ERROR = "SESSION STATUS RESULT=I2P_ERROR MESSAGE="
)
const (
Sig_NONE = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
Sig_DSA_SHA1 = "SIGNATURE_TYPE=DSA_SHA1"
Sig_ECDSA_SHA256_P256 = "SIGNATURE_TYPE=ECDSA_SHA256_P256"
Sig_ECDSA_SHA384_P384 = "SIGNATURE_TYPE=ECDSA_SHA384_P384"
Sig_ECDSA_SHA512_P521 = "SIGNATURE_TYPE=ECDSA_SHA512_P521"
Sig_EdDSA_SHA512_Ed25519 = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
)
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandString() string {
n := 4
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
log.WithField("randomString", string(b)).Debug("Generated random string")
return string(b)
}
// Creates a new controller for the I2P routers SAM bridge.
func NewSAM(address string) (*SAM, error) {
conn, err := net.Dial("tcp4", address)
log.WithField("address", address).Debug("Creating new SAM instance")
var s SAM
// TODO: clean this up
conn, err := net.Dial("tcp", address)
if err != nil {
return nil, err
log.WithError(err).Error("Failed to dial SAM address")
return nil, fmt.Errorf("error dialing to address '%s': %w", address, err)
}
if _, err := conn.Write([]byte("HELLO VERSION MIN=3.0 MAX=3.0\n")); err != nil {
return nil, err
if _, err := conn.Write(s.Config.HelloBytes()); err != nil {
log.WithError(err).Error("Failed to write hello message")
conn.Close()
return nil, fmt.Errorf("error writing to address '%s': %w", address, err)
}
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil {
return nil, err
log.WithError(err).Error("Failed to read SAM response")
conn.Close()
return nil, fmt.Errorf("error reading onto buffer: %w", err)
}
if string(buf[:n]) == "HELLO REPLY RESULT=OK VERSION=3.0\n" {
return &SAM{address, conn}, nil
if strings.Contains(string(buf[:n]), "HELLO REPLY RESULT=OK") {
log.Debug("SAM hello successful")
s.Config.I2PConfig.SetSAMAddress(address)
s.conn = conn
//s.Config.I2PConfig.DestinationKeys = nil
s.resolver, err = NewSAMResolver(&s)
if err != nil {
log.WithError(err).Error("Failed to create SAM resolver")
return nil, fmt.Errorf("error creating resolver: %w", err)
}
return &s, nil
} else if string(buf[:n]) == "HELLO REPLY RESULT=NOVERSION\n" {
log.Error("SAM bridge does not support SAMv3")
conn.Close()
return nil, errors.New("That SAM bridge does not support SAMv3.")
} else {
log.WithField("response", string(buf[:n])).Error("Unexpected SAM response")
conn.Close()
return nil, errors.New(string(buf[:n]))
}
}
func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) {
//TODO: copy them?
log.Debug("Retrieving SAM keys")
k = &sam.Config.I2PConfig.DestinationKeys
return
}
// read public/private keys from an io.Reader
func (sam *SAM) ReadKeys(r io.Reader) (err error) {
log.Debug("Reading keys from io.Reader")
var keys i2pkeys.I2PKeys
keys, err = i2pkeys.LoadKeysIncompat(r)
if err == nil {
log.Debug("Keys loaded successfully")
sam.Config.I2PConfig.DestinationKeys = keys
}
log.WithError(err).Error("Failed to load keys")
return
}
// if keyfile fname does not exist
func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
log.WithError(err).Error("Failed to load keys")
if fname == "" {
// transient
keys, err = sam.NewKeys()
if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys
log.WithFields(logrus.Fields{
"keys": keys,
}).Debug("Generated new transient keys")
}
} else {
// persistent
_, err = os.Stat(fname)
if os.IsNotExist(err) {
// make the keys
keys, err = sam.NewKeys()
if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys
// save keys
var f io.WriteCloser
f, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0600)
if err == nil {
err = i2pkeys.StoreKeysIncompat(keys, f)
f.Close()
log.Debug("Generated and saved new keys")
}
}
} else if err == nil {
// we haz key file
var f *os.File
f, err = os.Open(fname)
if err == nil {
keys, err = i2pkeys.LoadKeysIncompat(f)
if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys
log.Debug("Loaded existing keys from file")
}
}
}
}
if err != nil {
log.WithError(err).Error("Failed to ensure keyfile")
}
return
}
// Creates the I2P-equivalent of an IP address, that is unique and only the one
// who has the private keys can send messages from. The public keys are the I2P
// who has the private keys can send messages from. The public keys are the I2P
// desination (the address) that anyone can send messages to.
func (sam *SAM) NewKeys() (I2PKeys, error) {
if _, err := sam.conn.Write([]byte("DEST GENERATE\n")); err != nil {
return I2PKeys{}, err
func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
log.WithField("sigType", sigType).Debug("Generating new keys")
sigtmp := ""
if len(sigType) > 0 {
sigtmp = sigType[0]
}
if _, err := sam.conn.Write([]byte("DEST GENERATE " + sigtmp + "\n")); err != nil {
log.WithError(err).Error("Failed to write DEST GENERATE command")
return i2pkeys.I2PKeys{}, fmt.Errorf("error with writing in SAM: %w", err)
}
buf := make([]byte, 8192)
n, err := sam.conn.Read(buf)
if err != nil {
return I2PKeys{}, err
log.WithError(err).Error("Failed to read SAM response for key generation")
return i2pkeys.I2PKeys{}, fmt.Errorf("error with reading in SAM: %w", err)
}
s := bufio.NewScanner(bytes.NewReader(buf[:n]))
s.Split(bufio.ScanWords)
var pub, priv string
for s.Scan() {
text := s.Text()
if text == "DEST" {
continue
} else if text == "REPLY" {
continue
if text == "DEST" {
continue
} else if text == "REPLY" {
continue
} else if strings.HasPrefix(text, "PUB=") {
pub = text[4:]
} else if strings.HasPrefix(text, "PRIV=") {
priv = text[5:]
} else {
return I2PKeys{}, errors.New("Failed to parse keys.")
log.Error("Failed to parse keys from SAM response")
return i2pkeys.I2PKeys{}, errors.New("Failed to parse keys.")
}
}
return I2PKeys{I2PAddr(pub), priv}, nil
log.Debug("Successfully generated new keys")
return NewKeys(I2PAddr(pub), priv), nil
}
// Performs a lookup, probably this order: 1) routers known addresses, cached
// addresses, 3) by asking peers in the I2P network.
func (sam *SAM) Lookup(name string) (I2PAddr, error) {
if _, err := sam.conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\n")); err != nil {
return I2PAddr(""), err
}
buf := make([]byte, 4096)
n, err := sam.conn.Read(buf)
if err != nil {
return I2PAddr(""), err
}
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") {
return I2PAddr(""), errors.New("Failed to parse.")
}
s := bufio.NewScanner(bytes.NewReader(buf[13:n]))
s.Split(bufio.ScanWords)
errStr := ""
for s.Scan() {
text := s.Text()
if text == "RESULT=OK" {
continue
} else if text == "RESULT=INVALID_KEY" {
errStr += "Invalid key."
} else if text == "RESULT=KEY_NOT_FOUND" {
errStr += "Unable to resolve " + name
} else if text == "NAME=" + name {
continue
} else if strings.HasPrefix(text, "VALUE=") {
return I2PAddr(text[6:]), nil
} else if strings.HasPrefix(text, "MESSAGE=") {
errStr += " " + text[8:]
} else {
continue
}
}
return I2PAddr(""), errors.New(errStr)
func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Looking up address")
return sam.resolver.Resolve(name)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *SAM) newGenericSession(style, id string, keys I2PKeys, options []string, extras []string) (net.Conn, error) {
func (sam *SAM) newGenericSession(style, id string, keys i2pkeys.I2PKeys, options []string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id}).Debug("Creating new generic session")
return sam.newGenericSessionWithSignature(style, id, keys, Sig_NONE, options, extras)
}
func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "sigType": sigType}).Debug("Creating new generic session with signature")
return sam.newGenericSessionWithSignatureAndPorts(style, id, "0", "0", keys, sigType, options, extras)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "sigType": sigType}).Debug("Creating new generic session with signature and ports")
optStr := GenerateOptionString(options)
optStr := ""
for _, opt := range options {
optStr += "OPTION=" + opt + " "
}
conn := sam.conn
scmsg := []byte("SESSION CREATE STYLE=" + style + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + strings.Join(extras, " ") + "\n")
for m, i:=0, 0; m!=len(scmsg); i++ {
fp := ""
tp := ""
if from != "0" {
fp = " FROM_PORT=" + from
}
if to != "0" {
tp = " TO_PORT=" + to
}
scmsg := []byte("SESSION CREATE STYLE=" + style + fp + tp + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + strings.Join(extras, " ") + "\n")
log.WithField("message", string(scmsg)).Debug("Sending SESSION CREATE message")
for m, i := 0, 0; m != len(scmsg); i++ {
if i == 15 {
log.Error("Failed to write SESSION CREATE message after 15 attempts")
conn.Close()
return nil, errors.New("writing to SAM failed")
}
n, err := conn.Write(scmsg[m:])
if err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
conn.Close()
return nil, err
return nil, fmt.Errorf("writing to connection failed: %w", err)
}
m += n
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read SAM response")
conn.Close()
return nil, err
return nil, fmt.Errorf("reading from connection failed: %w", err)
}
text := string(buf[:n])
log.WithField("response", text).Debug("Received SAM response")
if strings.HasPrefix(text, session_OK) {
if keys.String() != text[len(session_OK):len(text)-1] {
log.Error("SAM created a tunnel with different keys than requested")
conn.Close()
return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
}
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
log.Debug("Successfully created new session")
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
} else if text == session_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close()
return nil, errors.New("Duplicate tunnel name")
} else if text == session_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close()
return nil, errors.New("Duplicate destination")
} else if text == session_INVALID_KEY {
log.Error("Invalid key for SAM session")
conn.Close()
return nil, errors.New("Invalid key")
return nil, errors.New("Invalid key - SAM session")
} else if strings.HasPrefix(text, session_I2P_ERROR) {
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
conn.Close()
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])
} else {
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
conn.Close()
return nil, errors.New("Unable to parse SAMv3 reply: " + text)
}
@@ -178,12 +316,6 @@ func (sam *SAM) newGenericSession(style, id string, keys I2PKeys, options []stri
// close this sam session
func (sam *SAM) Close() error {
if err := sam.conn.Close(); err != nil {
return err
}
return nil
log.Debug("Closing SAM session")
return sam.conn.Close()
}

View File

@@ -1,29 +1,23 @@
package sam3
import (
"fmt"
"testing"
"time"
)
const yoursam = "127.0.0.1:7656"
func Test_Basic(t *testing.T) {
fmt.Println("Test_Basic")
fmt.Println("\tAttaching to SAM at " + yoursam)
sam, err := NewSAM(yoursam)
if err != nil {
fmt.Println(err.Error)
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tCreating new keys...")
keys, err := sam.NewKeys()
if err != nil {
@@ -31,16 +25,16 @@ func Test_Basic(t *testing.T) {
t.Fail()
} else {
fmt.Println("\tAddress created: " + keys.Addr().Base32())
fmt.Println("\tI2PKeys: " + string(keys.both)[:50] + "(...etc)")
fmt.Println("\tI2PKeys: " + string(keys.String())[:50] + "(...etc)")
}
addr2, err := sam.Lookup("zzz.i2p")
if err != nil {
fmt.Println(err.Error())
t.Fail()
} else {
fmt.Println("\tzzz.i2p = " + addr2.Base32())
}
}
if err := sam.Close(); err != nil {
fmt.Println(err.Error())
@@ -48,7 +42,6 @@ func Test_Basic(t *testing.T) {
}
}
/*
func Test_GenericSession(t *testing.T) {
if testing.Short() {
@@ -95,13 +88,6 @@ func Test_GenericSession(t *testing.T) {
}
*/
func Test_RawServerClient(t *testing.T) {
if testing.Short() {
return
@@ -127,7 +113,7 @@ func Test_RawServerClient(t *testing.T) {
return
}
c, w := make(chan bool), make(chan bool)
go func(c, w chan(bool)) {
go func(c, w chan (bool)) {
sam2, err := NewSAM(yoursam)
if err != nil {
c <- false
@@ -149,17 +135,17 @@ func Test_RawServerClient(t *testing.T) {
fmt.Println("\tClient: Tries to send raw datagram to server")
for {
select {
default :
_, err = rs2.WriteTo([]byte("Hello raw-world! <3 <3 <3 <3 <3 <3"), rs.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send raw datagram: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w :
fmt.Println("\tClient: Sent raw datagram, quitting.")
default:
_, err = rs2.WriteTo([]byte("Hello raw-world! <3 <3 <3 <3 <3 <3"), rs.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send raw datagram: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w:
fmt.Println("\tClient: Sent raw datagram, quitting.")
return
}
}
c <- true
@@ -174,7 +160,5 @@ func Test_RawServerClient(t *testing.T) {
return
}
fmt.Println("\tServer: Received datagram: " + string(buf[:n]))
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
}

10
showhider.css Normal file
View File

@@ -0,0 +1,10 @@
/* edgar showhider CSS file */
#show {display:none; }
#hide {display:block; }
#show:target {display: block; }
#hide:target {display: none; }
#shownav {display:none; }
#hidenav {display:block; }
#shownav:target {display: block; }
#hidenav:target {display: none; }

392
stream.go
View File

@@ -3,198 +3,306 @@ package sam3
import (
"bufio"
"bytes"
"context"
"errors"
"github.com/sirupsen/logrus"
"io"
"net"
"strconv"
"strings"
"time"
"github.com/go-i2p/i2pkeys"
)
// Represents a streaming session.
type StreamSession struct {
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
keys I2PKeys // i2p destination keys
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam
keys i2pkeys.I2PKeys // i2p destination keys
Timeout time.Duration
Deadline time.Time
sigType string
from string
to string
}
// Read reads data from the stream.
func (s *StreamSession) Read(buf []byte) (int, error) {
return s.conn.Read(buf)
}
// Write sends data over the stream.
func (s *StreamSession) Write(data []byte) (int, error) {
return s.conn.Write(data)
}
func (s *StreamSession) SetDeadline(t time.Time) error {
log.WithField("deadline", t).Debug("Setting deadline for StreamSession")
return s.conn.SetDeadline(t)
}
func (s *StreamSession) SetReadDeadline(t time.Time) error {
log.WithField("readDeadline", t).Debug("Setting read deadline for StreamSession")
return s.conn.SetReadDeadline(t)
}
func (s *StreamSession) SetWriteDeadline(t time.Time) error {
log.WithField("writeDeadline", t).Debug("Setting write deadline for StreamSession")
return s.conn.SetWriteDeadline(t)
}
func (s *StreamSession) From() string {
return s.from
}
func (s *StreamSession) To() string {
return s.to
}
func (s *StreamSession) SignatureType() string {
return s.sigType
}
// Returns the local tunnel name of the I2P tunnel used for the stream session
func (ss StreamSession) ID() string {
return ss.id
func (s *StreamSession) ID() string {
return s.id
}
func (s *StreamSession) Close() error {
log.WithField("id", s.id).Debug("Closing StreamSession")
return s.conn.Close()
}
// Returns the I2P destination (the address) of the stream session
func (ss StreamSession) Addr() I2PAddr {
return ss.keys.Addr()
func (s *StreamSession) Addr() i2pkeys.I2PAddr {
return s.keys.Addr()
}
func (s *StreamSession) LocalAddr() net.Addr {
return s.keys.Addr()
}
// Returns the keys associated with the stream session
func (ss StreamSession) Keys() I2PKeys {
return ss.keys
func (s *StreamSession) Keys() i2pkeys.I2PKeys {
return s.keys
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSession(id string, keys I2PKeys, options []string) (*StreamSession, error) {
func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "options": options}).Debug("Creating new StreamSession")
conn, err := sam.newGenericSession("STREAM", id, keys, options, []string{})
if err != nil {
return nil, err
}
return &StreamSession{sam.address, id, conn, keys}, nil
log.WithField("id", id).Debug("Created new StreamSession")
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, nil
}
// Dials to an I2P destination and returns a SAMConn, which implements a net.Conn.
func (s *StreamSession) DialI2P(addr I2PAddr) (*SAMConn, error) {
sam, err := NewSAM(s.samAddr)
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "options": options, "sigType": sigType}).Debug("Creating new StreamSession with signature")
conn, err := sam.newGenericSessionWithSignature("STREAM", id, keys, sigType, options, []string{})
if err != nil {
return nil, err
}
conn := sam.conn
_,err = conn.Write([]byte("STREAM CONNECT ID=" + s.id + " DESTINATION=" + addr.Base64() + " SILENT=false\n"))
log.WithFields(logrus.Fields{"id": id, "sigType": sigType}).Debug("Created new StreamSession with signature")
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, "0", "0"}, nil
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id, from, to string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to, "options": options, "sigType": sigType}).Debug("Creating new StreamSession with signature and ports")
conn, err := sam.newGenericSessionWithSignatureAndPorts("STREAM", id, from, to, keys, sigType, options, []string{})
if err != nil {
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to, "sigType": sigType}).Debug("Created new StreamSession with signature and ports")
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, from, to}, nil
}
// lookup name, convenience function
func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Looking up address")
sam, err := NewSAM(s.samAddr)
if err == nil {
addr, err := sam.Lookup(name)
defer sam.Close()
if err != nil {
log.WithError(err).Error("Lookup failed")
} else {
log.WithField("addr", addr).Debug("Lookup successful")
}
return addr, err
}
log.WithError(err).Error("Failed to create SAM instance for lookup")
return i2pkeys.I2PAddr(""), err
}
// context-aware dialer, eventually...
func (s *StreamSession) DialContext(ctx context.Context, n, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("DialContext called")
return s.DialContextI2P(ctx, n, addr)
}
// context-aware dialer, eventually...
func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SAMConn, error) {
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("DialContextI2P called")
if ctx == nil {
log.Panic("nil context")
panic("nil context")
}
deadline := s.deadline(ctx, time.Now())
if !deadline.IsZero() {
if d, ok := ctx.Deadline(); !ok || deadline.Before(d) {
subCtx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
ctx = subCtx
}
}
i2paddr, err := i2pkeys.NewI2PAddrFromString(addr)
if err != nil {
log.WithError(err).Error("Failed to create I2P address from string")
return nil, err
}
return s.DialI2P(i2paddr)
}
/*
func (s *StreamSession) Cancel() chan *StreamSession {
ch := make(chan *StreamSession)
ch <- s
return ch
}*/
func minNonzeroTime(a, b time.Time) time.Time {
if a.IsZero() {
return b
}
if b.IsZero() || a.Before(b) {
return a
}
return b
}
// deadline returns the earliest of:
// - now+Timeout
// - d.Deadline
// - the context's deadline
//
// Or zero, if none of Timeout, Deadline, or context's deadline is set.
func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest time.Time) {
if s.Timeout != 0 { // including negative, for historical reasons
earliest = now.Add(s.Timeout)
}
if d, ok := ctx.Deadline(); ok {
earliest = minNonzeroTime(earliest, d)
}
return minNonzeroTime(earliest, s.Deadline)
}
// implement net.Dialer
func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) {
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("Dial called")
var i2paddr i2pkeys.I2PAddr
var host string
host, _, err = SplitHostPort(addr)
//log.Println("Dialing:", host)
if err = IgnorePortError(err); err == nil {
// check for name
if strings.HasSuffix(host, ".b32.i2p") || strings.HasSuffix(host, ".i2p") {
// name lookup
i2paddr, err = s.Lookup(host)
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Looked up I2P address")
} else {
// probably a destination
i2paddr, err = i2pkeys.NewI2PAddrFromBytes([]byte(host))
//i2paddr = i2pkeys.I2PAddr(host)
//log.Println("Destination:", i2paddr, err)
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Created I2P address from bytes")
}
if err == nil {
return s.DialI2P(i2paddr)
}
}
log.WithError(err).Error("Dial failed")
return
}
// Dials to an I2P destination and returns a SAMConn, which implements a net.Conn.
func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
log.WithField("addr", addr).Debug("DialI2P called")
sam, err := NewSAM(s.samAddr)
if err != nil {
log.WithError(err).Error("Failed to create new SAM instance")
return nil, err
}
conn := sam.conn
_, err = conn.Write([]byte("STREAM CONNECT ID=" + s.id + " FROM_PORT=" + s.from + " TO_PORT=" + s.to + " DESTINATION=" + addr.Base64() + " SILENT=false\n"))
if err != nil {
log.WithError(err).Error("Failed to write STREAM CONNECT command")
conn.Close()
return nil, err
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
if err != nil && err != io.EOF {
log.WithError(err).Error("Failed to write STREAM CONNECT command")
conn.Close()
return nil, err
}
scanner := bufio.NewScanner(bytes.NewReader(buf[:n]))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
switch scanner.Text() {
case "STREAM" :
case "STREAM":
continue
case "STATUS" :
case "STATUS":
continue
case "RESULT=OK" :
return &SAMConn{s.keys.addr, addr, conn}, nil
case "RESULT=CANT_REACH_PEER" :
case "RESULT=OK":
log.Debug("Successfully connected to I2P destination")
return &SAMConn{s.keys.Addr(), addr, conn}, nil
case "RESULT=CANT_REACH_PEER":
log.Error("Can't reach peer")
conn.Close()
return nil, errors.New("Can not reach peer")
case "RESULT=I2P_ERROR" :
case "RESULT=I2P_ERROR":
log.Error("I2P internal error")
conn.Close()
return nil, errors.New("I2P internal error")
case "RESULT=INVALID_KEY" :
return nil, errors.New("Invalid key")
case "RESULT=INVALID_ID" :
case "RESULT=INVALID_KEY":
log.Error("Invalid key - Stream Session")
conn.Close()
return nil, errors.New("Invalid key - Stream Session")
case "RESULT=INVALID_ID":
log.Error("Invalid tunnel ID")
conn.Close()
return nil, errors.New("Invalid tunnel ID")
case "RESULT=TIMEOUT" :
case "RESULT=TIMEOUT":
log.Error("Connection timeout")
conn.Close()
return nil, errors.New("Timeout")
default :
default:
log.WithField("error", scanner.Text()).Error("Unknown error")
conn.Close()
return nil, errors.New("Unknown error: " + scanner.Text() + " : " + string(buf[:n]))
}
}
log.Panic("Unexpected end of StreamSession.DialI2P()")
panic("sam3 go library error in StreamSession.DialI2P()")
}
// Returns a listener for the I2P destination (I2PAddr) associated with the
// StreamSession.
// create a new stream listener to accept inbound connections
func (s *StreamSession) Listen() (*StreamListener, error) {
sam, err := NewSAM(s.conn.RemoteAddr().String())
if err != nil {
return nil, err
}
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
sam.Close()
return nil, err
}
listener, err := net.Listen("tcp4", lhost + ":0")
_, lport, err := net.SplitHostPort(listener.Addr().String())
if err != nil {
sam.Close()
return nil, err
}
conn := sam.conn
_, err = conn.Write([]byte("STREAM FORWARD ID=" + s.id + " PORT=" + lport + " SILENT=false\n"))
if err != nil {
conn.Close()
return nil, err
}
buf := make([]byte, 512)
n, err := conn.Read(buf)
if err != nil {
conn.Close()
return nil, err
}
scanner := bufio.NewScanner(bytes.NewReader(buf[:n]))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
switch strings.TrimSpace(scanner.Text()) {
case "STREAM" :
continue
case "STATUS" :
continue
case "RESULT=OK" :
port,_ := strconv.Atoi(lport)
return &StreamListener{conn, listener, port, s.keys.Addr()}, nil
case "RESULT=I2P_ERROR" :
conn.Close()
return nil, errors.New("I2P internal error")
case "RESULT=INVALID_ID" :
conn.Close()
return nil, errors.New("Invalid tunnel ID")
default :
conn.Close()
return nil, errors.New("Unknown error: " + scanner.Text() + " : " + string(buf[:n]))
}
}
panic("sam3 go library error in StreamSession.Listener()")
}
// Implements net.Listener for I2P streaming sessions
type StreamListener struct {
conn net.Conn
listener net.Listener
lport int
laddr I2PAddr
}
const defaultListenReadLen = 516
// Accepts incomming connections to your StreamSession tunnel. Implements net.Listener
func (l *StreamListener) Accept() (*SAMConn, error) {
conn, err := l.listener.Accept()
if err != nil {
return nil, err
}
buf := make([]byte, defaultListenReadLen)
n, err := conn.Read(buf)
if n < defaultListenReadLen {
return nil, errors.New("Unknown destination type: " + string(buf[:n]))
}
// I2P inserts the I2P address ("destination") of the connecting peer into the datastream, followed by
// a \n. Since the length of a destination may vary, this reads until a newline is found. At the time
// of writing, the length is never less then, and almost always equals 516 bytes, which is why
// defaultListenReadLen is 516.
if rune(buf[defaultListenReadLen - 1]) != '\n' {
abuf := make([]byte, 1)
for {
n, err := conn.Read(abuf)
if n != 1 || err != nil {
return nil, errors.New("Failed to decode connecting peers I2P destination.")
}
buf = append(buf, abuf[0])
if rune(abuf[0]) == '\n' { break }
}
}
rAddr, err := NewI2PAddrFromString(string(buf[:len(buf)-1])) // the address minus the trailing newline
if err != nil {
conn.Close()
return nil, errors.New("Could not determine connecting tunnels address.")
}
return &SAMConn{l.laddr, rAddr, conn}, nil
}
// Closes the stream session. Implements net.Listener
func (l *StreamListener) Close() error {
err := l.listener.Close()
err2 := l.conn.Close()
if err2 != nil {
return err2
}
return err
}
// Returns the I2P destination (address) of the stream session. Implements net.Listener
func (l *StreamListener) Addr() net.Addr {
return l.laddr
log.WithFields(logrus.Fields{"id": s.id, "laddr": s.keys.Addr()}).Debug("Creating new StreamListener")
return &StreamListener{
session: s,
id: s.id,
laddr: s.keys.Addr(),
}, nil
}

140
streamListener.go Normal file
View File

@@ -0,0 +1,140 @@
package sam3
import (
"bufio"
"errors"
"github.com/sirupsen/logrus"
"io"
"net"
"strconv"
"strings"
"github.com/go-i2p/i2pkeys"
)
type StreamListener struct {
// parent stream session
session *StreamSession
// our session id
id string
// our local address for this sam socket
laddr i2pkeys.I2PAddr
}
func (l *StreamListener) From() string {
return l.session.from
}
func (l *StreamListener) To() string {
return l.session.to
}
// get our address
// implements net.Listener
func (l *StreamListener) Addr() net.Addr {
return l.laddr
}
// implements net.Listener
func (l *StreamListener) Close() error {
return l.session.Close()
}
// implements net.Listener
func (l *StreamListener) Accept() (net.Conn, error) {
return l.AcceptI2P()
}
func ExtractPairString(input, value string) string {
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("ExtractPairString called")
parts := strings.Split(input, " ")
for _, part := range parts {
if strings.HasPrefix(part, value) {
kv := strings.SplitN(input, "=", 2)
if len(kv) == 2 {
log.WithFields(logrus.Fields{"key": kv[0], "value": kv[1]}).Debug("Pair extracted")
return kv[1]
}
}
}
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
return ""
}
func ExtractPairInt(input, value string) int {
rv, err := strconv.Atoi(ExtractPairString(input, value))
if err != nil {
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
return 0
}
log.WithField("result", rv).Debug("Pair extracted and converted to int")
return rv
}
func ExtractDest(input string) string {
log.WithField("input", input).Debug("ExtractDest called")
dest := strings.Split(input, " ")[0]
log.WithField("dest", dest).Debug("Destination extracted")
return strings.Split(input, " ")[0]
}
// accept a new inbound connection
func (l *StreamListener) AcceptI2P() (*SAMConn, error) {
log.Debug("StreamListener.AcceptI2P() called")
s, err := NewSAM(l.session.samAddr)
if err == nil {
log.Debug("Connected to SAM bridge")
// we connected to sam
// send accept() command
_, err = io.WriteString(s.conn, "STREAM ACCEPT ID="+l.id+" SILENT=false\n")
if err != nil {
log.WithError(err).Error("Failed to send STREAM ACCEPT command")
s.Close()
return nil, err
}
// read reply
rd := bufio.NewReader(s.conn)
// read first line
line, err := rd.ReadString(10)
if err != nil {
log.WithError(err).Error("Failed to read SAM bridge response")
s.Close()
return nil, err
}
log.WithField("response", line).Debug("Received SAM bridge response")
log.Println(line)
if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") {
// we gud read destination line
destline, err := rd.ReadString(10)
if err == nil {
dest := ExtractDest(destline)
l.session.from = ExtractPairString(destline, "FROM_PORT")
l.session.to = ExtractPairString(destline, "TO_PORT")
// return wrapped connection
dest = strings.Trim(dest, "\n")
log.WithFields(logrus.Fields{
"dest": dest,
"from": l.session.from,
"to": l.session.to,
}).Debug("Accepted new I2P connection")
return &SAMConn{
laddr: l.laddr,
raddr: i2pkeys.I2PAddr(dest),
conn: s.conn,
}, nil
} else {
log.WithError(err).Error("Failed to read destination line")
s.Close()
return nil, err
}
} else {
log.WithField("line", line).Error("Invalid SAM response")
s.Close()
return nil, errors.New("invalid sam line: " + line)
}
} else {
log.WithError(err).Error("Failed to connect to SAM bridge")
s.Close()
return nil, err
}
}

View File

@@ -4,8 +4,9 @@ import (
"fmt"
"strings"
"testing"
)
"github.com/go-i2p/i2pkeys"
)
func Test_StreamingDial(t *testing.T) {
if testing.Short() {
@@ -14,7 +15,7 @@ func Test_StreamingDial(t *testing.T) {
fmt.Println("Test_StreamingDial")
sam, err := NewSAM(yoursam)
if err != nil {
fmt.Println(err.Error)
fmt.Println(err.Error())
t.Fail()
return
}
@@ -26,21 +27,21 @@ func Test_StreamingDial(t *testing.T) {
return
}
fmt.Println("\tBuilding tunnel")
ss, err := sam.NewStreamSession("streamTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
ss, err := sam.NewStreamSession("streamTun", keys, []string{"inbound.length=1", "outbound.length=1", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tNotice: This may fail if your I2P node is not well integrated in the I2P network.")
fmt.Println("\tLooking up forum.i2p")
forumAddr, err := sam.Lookup("forum.i2p")
fmt.Println("\tLooking up i2p-projekt.i2p")
forumAddr, err := sam.Lookup("i2p-projekt.i2p")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tDialing forum.i2p")
fmt.Println("\tDialing i2p-projekt.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
conn, err := ss.DialI2P(forumAddr)
if err != nil {
fmt.Println(err.Error())
@@ -52,14 +53,14 @@ func Test_StreamingDial(t *testing.T) {
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
t.Fail()
return
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("\tProbably failed to StreamSession.DialI2P(forum.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
fmt.Printf("\tProbably failed to StreamSession.DialI2P(i2p-projekt.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("\tRead HTTP/HTML from forum.i2p")
fmt.Println("\tRead HTTP/HTML from i2p-projekt.i2p")
}
}
@@ -67,7 +68,7 @@ func Test_StreamingServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_StreamingServerClient")
sam, err := NewSAM(yoursam)
if err != nil {
@@ -86,7 +87,7 @@ func Test_StreamingServerClient(t *testing.T) {
return
}
c, w := make(chan bool), make(chan bool)
go func(c, w chan(bool)) {
go func(c, w chan (bool)) {
if !(<-w) {
return
}
@@ -140,18 +141,17 @@ func Test_StreamingServerClient(t *testing.T) {
}
defer conn.Close()
buf := make([]byte, 512)
n,err := conn.Read(buf)
n, err := conn.Read(buf)
fmt.Printf("\tClient exited successfully: %t\n", <-c)
fmt.Println("\tServer: received from Client: " + string(buf[:n]))
}
func ExampleStreamSession() {
// Creates a new StreamingSession, dials to zzz.i2p and gets a SAMConn
// Creates a new StreamingSession, dials to idk.i2p and gets a SAMConn
// which behaves just like a normal net.Conn.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
@@ -169,12 +169,12 @@ func ExampleStreamSession() {
fmt.Println(err.Error())
return
}
someone, err := sam.Lookup("zzz.i2p")
someone, err := sam.Lookup("idk.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
conn, err := ss.DialI2P(someone)
if err != nil {
fmt.Println(err.Error())
@@ -184,27 +184,29 @@ func ExampleStreamSession() {
fmt.Println("Sending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
return
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("Probably failed to StreamSession.DialI2P(zzz.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
fmt.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
log.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("Read HTTP/HTML from zzz.i2p")
fmt.Println("Read HTTP/HTML from idk.i2p")
log.Println("Read HTTP/HTML from idk.i2p")
}
return
// Output:
//Sending HTTP GET /
//Read HTTP/HTML from zzz.i2p
//Read HTTP/HTML from idk.i2p
}
func ExampleStreamListener() {
// One server Accept()ing on a StreamListener, and one client that Dials
// through I2P to the server. Server writes "Hello world!" through a SAMConn
// (which implements net.Conn) and the client prints the message.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
@@ -213,7 +215,7 @@ func ExampleStreamListener() {
return
}
defer sam.Close()
keys, err := sam.NewKeys()
keys, err := sam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
@@ -222,14 +224,14 @@ func ExampleStreamListener() {
quit := make(chan bool)
// Client connecting to the server
go func(server I2PAddr) {
go func(server i2pkeys.I2PAddr) {
csam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
defer csam.Close()
keys, err := csam.NewKeys()
keys, err := csam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
@@ -255,7 +257,7 @@ func ExampleStreamListener() {
}
fmt.Println(string(buf[:n]))
quit <- true
}(keys.Addr()) // end of client
}(keys.Addr()) // end of client
ss, err := sam.NewStreamSession("server_example", keys, Options_Small)
if err != nil {
@@ -273,9 +275,9 @@ func ExampleStreamListener() {
return
}
conn.Write([]byte("Hello world!"))
<-quit // waits for client to die, for example only
// Output:
//Hello world!
}

157
style.css Normal file
View File

@@ -0,0 +1,157 @@
/* edgar default CSS file */
body {
font-family: "Roboto";
font-family: monospace;
text-align: justify;
background-color: #373636;
color: whitesmoke;
font-size: 1.15em;
}
ul {
width: 55%;
display: block;
}
ol {
width: 55%;
display: block;
}
li {
margin-top: 1%;
}
p {
max-width: 90%;
margin-top: 1%;
margin-left: 3%;
margin-right: 3%;
}
img {
float: left;
top: 5%;
left: 5%;
max-width: 60%;
display: inline;
}
.inline {
display: inline;
}
.link-button:focus {
outline: none;
}
.link-button:active {
color: red;
}
code {
font-family: monospace;
border-radius: 5%;
padding: 1%;
border-color: darkgray;
font-size: .9em;
}
a {
color: #C6D9FE;
padding: 1%;
}
ul li {
color: #C6D9FE;
}
iframe {
background: aliceblue;
border-radius: 15%;
margin: 2%;
}
.container {
width: 36vw;
height: 64vh;
display: inline-block;
margin: 0;
padding: 0;
}
.editor-toolbar a {
display: inline-block;
text-align: center;
text-decoration: none !important;
color: whitesmoke !important;
}
#feed {
width: 60vw;
height: unset !important;
margin: 0;
padding: 0;
float: right;
background-color: #373636;
color: whitesmoke;
border: #C6D9FE solid 1px;
}
.thread-post,
.thread {
color: whitesmoke !important;
background-color: #373636;
border: 1px solid darkgray;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
.thread-post {
margin-left: 4%;
}
input {
text-align: center;
color: whitesmoke !important;
background-color: #373636;
border: 1px solid darkgray;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
.thread-hash {
text-align: right;
color: whitesmoke !important;
background-color: #373636;
border: 1px solid darkgray;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
.post-body {
text-align: left;
color: whitesmoke !important;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
#show {display:none; }
#hide {display:block; }
#show:target {display: block; }
#hide:target {display: none; }
#shownav {display:none; }
#hidenav {display:block; }
#shownav:target {display: block; }
#hidenav:target {display: none; }
#navbar {
float: right;
width: 10%;
}

View File

@@ -1,40 +1,142 @@
package sam3
import (
"github.com/sirupsen/logrus"
"net"
"net/http"
"os"
"strings"
)
// Examples and suggestions for options when creating sessions.
var (
// Suitable options if you are shuffling A LOT of traffic. If unused, this
// Suitable options if you are shuffling A LOT of traffic. If unused, this
// will waste your resources.
Options_Humongous = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=3", "outbound.backupQuantity=3",
"inbound.quantity=6", "outbound.quantity=6"}
// Suitable for shuffling a lot of traffic.
Options_Fat = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=4", "outbound.quantity=4"}
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=3", "outbound.backupQuantity=3",
"inbound.quantity=6", "outbound.quantity=6"}
// Suitable for shuffling a lot of traffic.
Options_Large = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=4", "outbound.quantity=4"}
// Suitable for shuffling a lot of traffic quickly with minimum
// anonymity. Uses 1 hop and multiple tunnels.
Options_Wide = []string{"inbound.length=1", "outbound.length=1",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=2", "outbound.backupQuantity=2",
"inbound.quantity=3", "outbound.quantity=3"}
// Suitable for shuffling medium amounts of traffic.
Options_Medium = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2"}
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2"}
// Sensible defaults for most people
Options_Default = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=1", "outbound.quantity=1"}
// Suitable only for small dataflows, and very short lasting connections:
// You only have one tunnel in each direction, so if any of the nodes
// You only have one tunnel in each direction, so if any of the nodes
// through which any of your two tunnels pass through go offline, there will
// be a complete halt in the dataflow, until a new tunnel is built.
Options_Small = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=1", "outbound.quantity=1"}
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=1", "outbound.quantity=1"}
// Does not use any anonymization, you connect directly to others tunnel
// endpoints, thus revealing your identity but not theirs. Use this only
// if you don't care.
Options_Warning_ZeroHop = []string{"inbound.length=0", "outbound.length=0",
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2"}
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2"}
)
func PrimarySessionString() string {
log.Debug("Determining primary session type")
_, err := http.Get("http://127.0.0.1:7070")
if err != nil {
log.WithError(err).Debug("Failed to connect to 127.0.0.1:7070, trying 127.0.0.1:7657")
_, err := http.Get("http://127.0.0.1:7657")
if err != nil {
return "MASTER"
}
log.Debug("Connected to 127.0.0.1:7657, attempting to create a PRIMARY session")
// at this point we're probably running on Java I2P and thus probably
// have a PRIMARY session. Just to be sure, try to make one, check
// for errors, then immediately close it.
testSam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
log.WithError(err).Debug("Failed to create SAM instance, assuming MASTER session")
return "MASTER"
}
newKeys, err := testSam.NewKeys()
if err != nil {
log.WithError(err).Debug("Failed to create new keys, assuming MASTER session")
return "MASTER"
}
primarySession, err := testSam.newPrimarySession("PRIMARY", "primaryTestTunnel", newKeys, Options_Small)
if err != nil {
log.WithError(err).Debug("Failed to create primary session, assuming MASTER session")
return "MASTER"
}
primarySession.Close()
log.Debug("Successfully created and closed a PRIMARY session")
return "PRIMARY"
}
log.Debug("Connected to 127.0.0.1:7070, assuming MASTER session")
return "MASTER"
}
var PrimarySessionSwitch string = PrimarySessionString()
func getEnv(key, fallback string) string {
InitializeSAM3Logger()
value, ok := os.LookupEnv(key)
if !ok {
log.WithFields(logrus.Fields{
"key": key,
"fallback": fallback,
}).Debug("Environment variable not set, using fallback")
return fallback
}
log.WithFields(logrus.Fields{
"key": key,
"value": value,
}).Debug("Retrieved environment variable")
return value
}
var SAM_HOST = getEnv("sam_host", "127.0.0.1")
var SAM_PORT = getEnv("sam_port", "7656")
func SAMDefaultAddr(fallforward string) string {
if fallforward == "" {
addr := net.JoinHostPort(SAM_HOST, SAM_PORT)
log.WithField("addr", addr).Debug("Using default SAM address")
return addr
}
log.WithField("addr", fallforward).Debug("Using fallforward SAM address")
return fallforward
}
func GenerateOptionString(opts []string) string {
optStr := strings.Join(opts, " ")
log.WithField("options", optStr).Debug("Generating option string")
if strings.Contains(optStr, "i2cp.leaseSetEncType") {
log.Debug("i2cp.leaseSetEncType already present in options")
return optStr
}
finalOpts := optStr + " i2cp.leaseSetEncType=4,0"
log.WithField("finalOptions", finalOpts).Debug("Added default i2cp.leaseSetEncType to options")
return finalOpts
//return optStr + " i2cp.leaseSetEncType=4,0"
}