mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-03-07 06:09:42 +00:00
Compare commits
864 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90ea714e48 | ||
|
|
f9e4182624 | ||
|
|
caf2e469a6 | ||
|
|
45da2843ee | ||
|
|
8353f928a1 | ||
|
|
448b25a8b2 | ||
|
|
4c2d4009da | ||
|
|
67f1e07508 | ||
|
|
c49fdf1233 | ||
|
|
7c835bae20 | ||
|
|
ae81cc2644 | ||
|
|
3907b4101a | ||
|
|
aa5ea0e3a1 | ||
|
|
995bdb3f9e | ||
|
|
8363b4fda7 | ||
|
|
23979f4ce6 | ||
|
|
28b5f39b84 | ||
|
|
aa215f2a5a | ||
|
|
b03a6a5327 | ||
|
|
ca36a6fe41 | ||
|
|
bd6285c8b1 | ||
|
|
00cfdc7d92 | ||
|
|
5e2dc14dd5 | ||
|
|
c5f2890cbe | ||
|
|
36aaca997a | ||
|
|
e9f7c61113 | ||
|
|
2373b94d3e | ||
|
|
f131e31949 | ||
|
|
8fd55a210a | ||
|
|
678650beaf | ||
|
|
e09386be44 | ||
|
|
75db2867dc | ||
|
|
80e37df012 | ||
|
|
1f404bb622 | ||
|
|
54078087e5 | ||
|
|
23b8df1c36 | ||
|
|
65395516b0 | ||
|
|
849308e28d | ||
|
|
4d98a64000 | ||
|
|
0c8fdfca7d | ||
|
|
fd928e8d12 | ||
|
|
2a1fe99a29 | ||
|
|
4fa4ba6301 | ||
|
|
48b3959cfb | ||
|
|
9bbff744e9 | ||
|
|
19b0c266f9 | ||
|
|
fead940d10 | ||
|
|
687e17ac52 | ||
|
|
b1c85dcb74 | ||
|
|
a15aad9f9c | ||
|
|
06a1a8690d | ||
|
|
42b9b6426a | ||
|
|
332f0118a2 | ||
|
|
6ed709d6e6 | ||
|
|
7a461c1684 | ||
|
|
7cf171671d | ||
|
|
ebee94fb11 | ||
|
|
61e8becd38 | ||
|
|
a78caa2976 | ||
|
|
c54f7c81c4 | ||
|
|
85840872ab | ||
|
|
d582c30f6e | ||
|
|
4431d50635 | ||
|
|
e120e9a78e | ||
|
|
b6e379d14e | ||
|
|
9a86034162 | ||
|
|
8456c8b47b | ||
|
|
bb656ce44b | ||
|
|
3c2a3898e1 | ||
|
|
c265bd6c4d | ||
|
|
aff65083cc | ||
|
|
aff8cd478c | ||
|
|
bce2a63772 | ||
|
|
3f9d2601b4 | ||
|
|
04bfd52fba | ||
|
|
87dd890eb0 | ||
|
|
a5c0b48b57 | ||
|
|
5d38693b4d | ||
|
|
a4773d259d | ||
|
|
ef106f3232 | ||
|
|
c0b0df34d2 | ||
|
|
d15cc7cc47 | ||
|
|
6336d38a3e | ||
|
|
6a9d2ba653 | ||
|
|
34a8d4a57d | ||
|
|
ffc666eaaa | ||
|
|
c45aab7cef | ||
|
|
2ebb2d8f0e | ||
|
|
afe2935c9d | ||
|
|
380c7b7720 | ||
|
|
8657226594 | ||
|
|
405aa906c5 | ||
|
|
1c507a47d2 | ||
|
|
f48a7df80f | ||
|
|
5f73f09836 | ||
|
|
f63dd75f08 | ||
|
|
cc55335a8d | ||
|
|
b5875f3a0a | ||
|
|
cb8333a48f | ||
|
|
f412f4ca88 | ||
|
|
941f30d1ea | ||
|
|
97afa502c5 | ||
|
|
0bf2abaa4c | ||
|
|
1fc5dacd87 | ||
|
|
5c877de2c2 | ||
|
|
751b95d4af | ||
|
|
e0d5ba9915 | ||
|
|
e5fac08d1d | ||
|
|
df5b7c7d0d | ||
|
|
27649f7d4c | ||
|
|
350dea6228 | ||
|
|
aef6b7712c | ||
|
|
642bcfcdea | ||
|
|
e625d8aabc | ||
|
|
5888ecbdcd | ||
|
|
e2a76056b8 | ||
|
|
a98498eb06 | ||
|
|
8366c8d2a7 | ||
|
|
ed8d441a02 | ||
|
|
f1fb265119 | ||
|
|
6c628094ce | ||
|
|
a60c52e2f0 | ||
|
|
ac2e1709f8 | ||
|
|
db88183a23 | ||
|
|
c7d55ad858 | ||
|
|
06a4e6c323 | ||
|
|
d1de89f387 | ||
|
|
bbba01da92 | ||
|
|
25dbf62274 | ||
|
|
ed6851863b | ||
|
|
ba924e295e | ||
|
|
0828065a62 | ||
|
|
68c789dceb | ||
|
|
6424084502 | ||
|
|
4abea18afe | ||
|
|
0a3c4f131e | ||
|
|
f5e1077e20 | ||
|
|
44d1c3fd2f | ||
|
|
e345161763 | ||
|
|
64d7c87591 | ||
|
|
1fae3baaa3 | ||
|
|
38103aaac5 | ||
|
|
cc25b22f11 | ||
|
|
e6dbeda18e | ||
|
|
8437d45866 | ||
|
|
0bb89de821 | ||
|
|
905cad56d8 | ||
|
|
65eeb70eb3 | ||
|
|
266744f640 | ||
|
|
23d6739580 | ||
|
|
5c9970c786 | ||
|
|
3eae716a2d | ||
|
|
c57b13d922 | ||
|
|
17fb419fb1 | ||
|
|
598d0e216a | ||
|
|
7bbe926232 | ||
|
|
2e848a7c9a | ||
|
|
437225b43e | ||
|
|
d39229713f | ||
|
|
93911be1b9 | ||
|
|
b74055478c | ||
|
|
8614c4db73 | ||
|
|
215d39fc54 | ||
|
|
c4e5a130ee | ||
|
|
630072b574 | ||
|
|
5261a3e845 | ||
|
|
0096a91a57 | ||
|
|
56699a9f89 | ||
|
|
31ff1372ae | ||
|
|
3afb1922bb | ||
|
|
83c0a8b047 | ||
|
|
6699bd47b5 | ||
|
|
34223b8d4f | ||
|
|
5befe1f019 | ||
|
|
87f86e72f4 | ||
|
|
53b7eba31a | ||
|
|
12c12a8ad1 | ||
|
|
897cc7d355 | ||
|
|
2e5c56205c | ||
|
|
bc5ff37e37 | ||
|
|
20341a381f | ||
|
|
926b945846 | ||
|
|
aa877a73ba | ||
|
|
b28208d1bf | ||
|
|
9bd97383bd | ||
|
|
522c7b2f9d | ||
|
|
1833c0acbc | ||
|
|
c5644ee3f9 | ||
|
|
447566fe14 | ||
|
|
9692c34f6c | ||
|
|
37c450f1e1 | ||
|
|
a003e396c5 | ||
|
|
996f61efe1 | ||
|
|
40cdcf8b06 | ||
|
|
5947364846 | ||
|
|
9470107bba | ||
|
|
54b945511b | ||
|
|
acfaa0041e | ||
|
|
aeed2dbc3e | ||
|
|
0c6befe8a5 | ||
|
|
bdcb26edae | ||
|
|
e091667b42 | ||
|
|
d35b14f4cc | ||
|
|
87996c6811 | ||
|
|
a880c733c8 | ||
|
|
ca10dfeb5f | ||
|
|
91f55a637b | ||
|
|
8ae43cfd14 | ||
|
|
83d9513c4a | ||
|
|
1c76d43e44 | ||
|
|
1036ce0fa5 | ||
|
|
3dbab68f17 | ||
|
|
5896cebeaa | ||
|
|
fbe629154d | ||
|
|
364136213b | ||
|
|
136b663cef | ||
|
|
803f11bebb | ||
|
|
7c8036807a | ||
|
|
84ccca0e98 | ||
|
|
74efdb95e8 | ||
|
|
10e45ac493 | ||
|
|
60befdb36e | ||
|
|
59f99ea9bb | ||
|
|
1a894abcff | ||
|
|
4934fc8809 | ||
|
|
18cc6a184f | ||
|
|
0a08765d73 | ||
|
|
355c7437ed | ||
|
|
3c55c2d777 | ||
|
|
94806ad0b3 | ||
|
|
6840259734 | ||
|
|
a1fc48f2a6 | ||
|
|
400e3d21f9 | ||
|
|
8f3daad502 | ||
|
|
b0395933de | ||
|
|
f8f2ab9cba | ||
|
|
ae5f5375da | ||
|
|
ab5f1e712b | ||
|
|
4532ca97fa | ||
|
|
5a9ef57f78 | ||
|
|
8791f382b3 | ||
|
|
abdef67ccc | ||
|
|
33494c4f4b | ||
|
|
daad975f5d | ||
|
|
18c00f0a4b | ||
|
|
e7f46b4fbe | ||
|
|
74827cd8cf | ||
|
|
5ffe1893cd | ||
|
|
f24618e8df | ||
|
|
0e5b32ef13 | ||
|
|
0493a321d2 | ||
|
|
38b6c12153 | ||
|
|
74d4b8e0b9 | ||
|
|
f843d34234 | ||
|
|
95b2bf3645 | ||
|
|
121ac4f1de | ||
|
|
ec8550d587 | ||
|
|
e403c419e5 | ||
|
|
4b0d587fe1 | ||
|
|
ebd356c7bd | ||
|
|
507093dbad | ||
|
|
4cfdc77015 | ||
|
|
9096cacba8 | ||
|
|
607336d3ce | ||
|
|
6383fc3575 | ||
|
|
a5576ddbf3 | ||
|
|
e2a70873b8 | ||
|
|
23c7340afe | ||
|
|
380b56a89d | ||
|
|
8e09f3478f | ||
|
|
c1ce51eb12 | ||
|
|
9aeb773169 | ||
|
|
091c13ff41 | ||
|
|
ef0bab0c6e | ||
|
|
70bd16adf6 | ||
|
|
96a713afeb | ||
|
|
bf3615fb32 | ||
|
|
0f56b1c943 | ||
|
|
d541572882 | ||
|
|
ecfdc377ec | ||
|
|
fa67e90767 | ||
|
|
81b72d5481 | ||
|
|
ef6028e933 | ||
|
|
5d41fe4a35 | ||
|
|
1dc6cec1aa | ||
|
|
9378668e52 | ||
|
|
eb96ead80e | ||
|
|
9403fbaf81 | ||
|
|
79190f313d | ||
|
|
4c124284b6 | ||
|
|
6d892179c8 | ||
|
|
61675c20d8 | ||
|
|
4aae878db8 | ||
|
|
918884bd11 | ||
|
|
8799f9079b | ||
|
|
0b471cfd06 | ||
|
|
7b39a12396 | ||
|
|
57a53b4b6c | ||
|
|
f6d0b3368f | ||
|
|
0fe7bdf849 | ||
|
|
a26dc39a6d | ||
|
|
e45cfe7d0c | ||
|
|
efefa8caf5 | ||
|
|
cc13db9b1f | ||
|
|
f339544256 | ||
|
|
1a05bcb295 | ||
|
|
190e26276a | ||
|
|
bb33760e87 | ||
|
|
9e105b4983 | ||
|
|
8dcf70408d | ||
|
|
9d6d1825c7 | ||
|
|
1a4923cdce | ||
|
|
316e440390 | ||
|
|
7d66019220 | ||
|
|
f98a6fb665 | ||
|
|
dbdc7279c4 | ||
|
|
7726705b5c | ||
|
|
34b7e8815a | ||
|
|
8ac2b58a44 | ||
|
|
fe97f0929b | ||
|
|
6eec353c2b | ||
|
|
2b4c3b8d1f | ||
|
|
df99b37c4d | ||
|
|
ab6f3fcf8e | ||
|
|
ca6f656e1b | ||
|
|
88798b1a9e | ||
|
|
c197270125 | ||
|
|
dc344d4658 | ||
|
|
b4864831e0 | ||
|
|
476dffff13 | ||
|
|
389ee974f3 | ||
|
|
0d15eceacb | ||
|
|
b69fbdda9a | ||
|
|
d3746e0119 | ||
|
|
230af9cafa | ||
|
|
4db63d113c | ||
|
|
008583396d | ||
|
|
33a33e3c71 | ||
|
|
d312d753e9 | ||
|
|
02310d4af6 | ||
|
|
0e6d8c4e25 | ||
|
|
55315fca80 | ||
|
|
4eef9e780f | ||
|
|
7bfc3562af | ||
|
|
5b0b0d6d36 | ||
|
|
cb64072f7b | ||
|
|
c5b6da7201 | ||
|
|
f1d4818045 | ||
|
|
76b49f6985 | ||
|
|
094d9193b9 | ||
|
|
3053a9b6a0 | ||
|
|
47bf0ef591 | ||
|
|
e2aa2709ac | ||
|
|
9a6d478eb1 | ||
|
|
4f37e7dc3c | ||
|
|
2a4ba8d349 | ||
|
|
85bd7a63c6 | ||
|
|
138d57143a | ||
|
|
464a228106 | ||
|
|
2b92a039bb | ||
|
|
f190ee951c | ||
|
|
68cc75cada | ||
|
|
b4e324ec0e | ||
|
|
32fe2e7974 | ||
|
|
713513aacc | ||
|
|
b4ffca56a3 | ||
|
|
f2168774a5 | ||
|
|
febc00d357 | ||
|
|
01a8c507e5 | ||
|
|
bf7982cc2e | ||
|
|
2e9689886b | ||
|
|
2003b34036 | ||
|
|
e1995b5c70 | ||
|
|
3890acabc4 | ||
|
|
ba6c0d0423 | ||
|
|
882e7a845e | ||
|
|
ca56d3fc23 | ||
|
|
49b1e76585 | ||
|
|
80f81685d1 | ||
|
|
21dead3125 | ||
|
|
1521d08285 | ||
|
|
59b2e31add | ||
|
|
b5feb3fd66 | ||
|
|
7785e6ebd2 | ||
|
|
c561d71dc0 | ||
|
|
2cfb697867 | ||
|
|
c680ff006e | ||
|
|
333103f50e | ||
|
|
517385fb63 | ||
|
|
ee8ab58d64 | ||
|
|
b967acda58 | ||
|
|
d81ca5f919 | ||
|
|
07adf64aec | ||
|
|
fbb98e1aec | ||
|
|
2fdf927704 | ||
|
|
4b84656133 | ||
|
|
97c136d043 | ||
|
|
79bf44b3f5 | ||
|
|
ddd8d4aeb2 | ||
|
|
bfcb6f577f | ||
|
|
2b137b43e6 | ||
|
|
6d74493491 | ||
|
|
6f4271c054 | ||
|
|
f24054100e | ||
|
|
6e98649607 | ||
|
|
b2108ff2d0 | ||
|
|
8949ebf041 | ||
|
|
576801cd32 | ||
|
|
2f2b12811f | ||
|
|
d8ea3a9035 | ||
|
|
45c3b3987b | ||
|
|
93720fffd4 | ||
|
|
61ad6a2b88 | ||
|
|
c9d5b3c0ff | ||
|
|
d51bf735c4 | ||
|
|
22c388ab18 | ||
|
|
d5f831301f | ||
|
|
dcab37a148 | ||
|
|
60b2da3671 | ||
|
|
5c1b5816d4 | ||
|
|
7a0a45e9d2 | ||
|
|
70f72a78f6 | ||
|
|
e056c9c135 | ||
|
|
c754b5ae18 | ||
|
|
481fafc11d | ||
|
|
7d927b0e28 | ||
|
|
c314b07136 | ||
|
|
16fe13bf4a | ||
|
|
d19eda7e08 | ||
|
|
6f0a136727 | ||
|
|
e2e101e4fb | ||
|
|
74f03202b7 | ||
|
|
3d19e92059 | ||
|
|
bfff125cc5 | ||
|
|
e90baf3ca6 | ||
|
|
f3b277aeef | ||
|
|
76096747b6 | ||
|
|
4c6ef32d72 | ||
|
|
a8e12e624d | ||
|
|
88a43bfc28 | ||
|
|
3b268fe3cc | ||
|
|
4c72d43a8a | ||
|
|
0a5f8527b2 | ||
|
|
9f1b84d6f2 | ||
|
|
babcbcbcea | ||
|
|
823a6017fe | ||
|
|
f034aef2ae | ||
|
|
bf38bd5a1d | ||
|
|
b922809c9d | ||
|
|
05b0bda8bb | ||
|
|
0aa3aa1b8d | ||
|
|
d4febb4e84 | ||
|
|
21090eaa39 | ||
|
|
d0ea59c568 | ||
|
|
a292bc77ba | ||
|
|
98d5e0b56d | ||
|
|
7ca1cfab1a | ||
|
|
2e7ce38552 | ||
|
|
0ef3a2472d | ||
|
|
b97f095de4 | ||
|
|
10e2b35483 | ||
|
|
16920a89f3 | ||
|
|
1a5b9de82e | ||
|
|
4ef183fee6 | ||
|
|
2115ce6606 | ||
|
|
61d1b733f7 | ||
|
|
4978edb8be | ||
|
|
51f7aba807 | ||
|
|
b9b143e4e7 | ||
|
|
0e7596a205 | ||
|
|
8c401cf01b | ||
|
|
6782e6a532 | ||
|
|
4386bd93c3 | ||
|
|
72b3c10ebd | ||
|
|
62cec2a31c | ||
|
|
0c442622af | ||
|
|
bf3c4bc588 | ||
|
|
d98dd83369 | ||
|
|
21ecf309bb | ||
|
|
4bb4012d87 | ||
|
|
10fd8eb709 | ||
|
|
b1cc1db967 | ||
|
|
77d8bae2c2 | ||
|
|
7274d43645 | ||
|
|
3eeee1b08d | ||
|
|
64b2a32c9a | ||
|
|
4ced1e5075 | ||
|
|
8de15c9d0d | ||
|
|
31d716bd0c | ||
|
|
3da6b3930b | ||
|
|
900fc1cb46 | ||
|
|
deb87f1d4c | ||
|
|
ed44d23afb | ||
|
|
8baf7f3f6a | ||
|
|
d2d4fa29e4 | ||
|
|
0c56cd63bd | ||
|
|
c9cf84f2f4 | ||
|
|
0966369723 | ||
|
|
4f6c3d52b3 | ||
|
|
97f8ab5c51 | ||
|
|
8805f1e4d6 | ||
|
|
3ae57e0ca9 | ||
|
|
a8e4301f23 | ||
|
|
68bc78d00b | ||
|
|
1dc9e74df4 | ||
|
|
a69cee03e5 | ||
|
|
bf15ad3bba | ||
|
|
bb3f50f967 | ||
|
|
1042e19845 | ||
|
|
85830d5076 | ||
|
|
c053bebccd | ||
|
|
d6d6ae8af2 | ||
|
|
6d8b0e3a5d | ||
|
|
cfd7f1571b | ||
|
|
f31c04d92a | ||
|
|
89b58ec3af | ||
|
|
ab0d66c2ef | ||
|
|
9774865d4a | ||
|
|
3817a0c2a1 | ||
|
|
5215bdc035 | ||
|
|
8061d306dd | ||
|
|
30f68759ff | ||
|
|
3f0b595085 | ||
|
|
0c9ce6258c | ||
|
|
7da17ba21e | ||
|
|
7b23d79dc2 | ||
|
|
415314a90d | ||
|
|
0f7e2ad11a | ||
|
|
26d232c567 | ||
|
|
efa48a7e39 | ||
|
|
022642f4d5 | ||
|
|
e6e2f04a10 | ||
|
|
f7e21dbe5c | ||
|
|
f593802a51 | ||
|
|
f545e6eb27 | ||
|
|
1778d82bc3 | ||
|
|
03587d7035 | ||
|
|
6663788612 | ||
|
|
ac2cb773df | ||
|
|
b70b3ec85b | ||
|
|
1e69b8c41d | ||
|
|
d5aa1a4880 | ||
|
|
de0658eaab | ||
|
|
939c28b74b | ||
|
|
c10d628a45 | ||
|
|
92830172f9 | ||
|
|
431af2c0dd | ||
|
|
97ca8b7ada | ||
|
|
f3a7c233b3 | ||
|
|
928abf7094 | ||
|
|
2cace0008e | ||
|
|
db9c20f3dd | ||
|
|
e1a1aef990 | ||
|
|
23cf6ebc89 | ||
|
|
55c279cc7e | ||
|
|
4e89f90c4f | ||
|
|
bd0eb81c1b | ||
|
|
a77a0d98e0 | ||
|
|
e5037fc9f9 | ||
|
|
7ac2022159 | ||
|
|
bc41a15eba | ||
|
|
8aa158c1e0 | ||
|
|
1f6f4d9c49 | ||
|
|
3686a27c19 | ||
|
|
1bcc311738 | ||
|
|
2335d3879e | ||
|
|
209934ad67 | ||
|
|
35200a1ee5 | ||
|
|
6c4977ee78 | ||
|
|
5482a57c45 | ||
|
|
18914978d5 | ||
|
|
36750ab900 | ||
|
|
c5f6a690de | ||
|
|
9611f80a39 | ||
|
|
eb2d68fc28 | ||
|
|
937d346676 | ||
|
|
7565843fbe | ||
|
|
6740ec464c | ||
|
|
314e1e4bfe | ||
|
|
45d68d89a9 | ||
|
|
1d5194a138 | ||
|
|
05043f30dc | ||
|
|
cd549937c5 | ||
|
|
efdea07b7b | ||
|
|
06d4998d87 | ||
|
|
02b566055e | ||
|
|
c312dbaac1 | ||
|
|
b6dcb2f4c0 | ||
|
|
a85d3f2573 | ||
|
|
0ca3fb5af0 | ||
|
|
2a4d78d9bf | ||
|
|
d9e199092d | ||
|
|
02bbb46d2e | ||
|
|
13ffdc6dd2 | ||
|
|
c8c2c4d376 | ||
|
|
01f7343781 | ||
|
|
3acc244692 | ||
|
|
094068e4ff | ||
|
|
ec958697e2 | ||
|
|
208e8f8247 | ||
|
|
3d4890a28b | ||
|
|
fe4362f459 | ||
|
|
81d3ad2d35 | ||
|
|
ffb8c3e53c | ||
|
|
2d4d2374e3 | ||
|
|
09f31a9278 | ||
|
|
80b0a3cdec | ||
|
|
c533bfc83d | ||
|
|
8fa053f7c7 | ||
|
|
b152bb26e3 | ||
|
|
a0816b04e5 | ||
|
|
0819517902 | ||
|
|
55ea8c82e9 | ||
|
|
ffbbf88de4 | ||
|
|
e2ff49825f | ||
|
|
7f325827c4 | ||
|
|
cae9ccfda1 | ||
|
|
248ae7d4d5 | ||
|
|
7f08bbe938 | ||
|
|
81b2f2114d | ||
|
|
5eee430be3 | ||
|
|
623edf3bc9 | ||
|
|
bd4a224051 | ||
|
|
870e84a700 | ||
|
|
8d4fae24ea | ||
|
|
7a84daf3f7 | ||
|
|
7968279bc2 | ||
|
|
258be40285 | ||
|
|
b2ae30eba1 | ||
|
|
daaba1dbc0 | ||
|
|
a3c6ed4dd2 | ||
|
|
e4255ed712 | ||
|
|
5d510f1cf4 | ||
|
|
1819bd910a | ||
|
|
43eecdbb3f | ||
|
|
108c1bcac4 | ||
|
|
4b7e5864d4 | ||
|
|
fb1d2abbfa | ||
|
|
0c290e65ef | ||
|
|
5487fad2ce | ||
|
|
d41f930f69 | ||
|
|
595b2619fd | ||
|
|
26d305d866 | ||
|
|
c9d95ff161 | ||
|
|
9cc592b564 | ||
|
|
ff48422ec0 | ||
|
|
a26c5f85c3 | ||
|
|
727436e1cf | ||
|
|
d1c57a1872 | ||
|
|
b7c021af8c | ||
|
|
7149b509d7 | ||
|
|
45e7111dda | ||
|
|
9fc69db9eb | ||
|
|
2ba314d9d9 | ||
|
|
f35660c8e2 | ||
|
|
68b1fe8631 | ||
|
|
4242c86d40 | ||
|
|
ef4dc3cbc9 | ||
|
|
8daa7561fa | ||
|
|
2cc3dfc2ce | ||
|
|
459800568a | ||
|
|
3a35b84b03 | ||
|
|
79cfa52bf9 | ||
|
|
a0e8fe5848 | ||
|
|
2dae5bccb2 | ||
|
|
8e867ab0c0 | ||
|
|
1b2c88fe38 | ||
|
|
f3bee5ff3f | ||
|
|
196d7e8f72 | ||
|
|
16596c18fb | ||
|
|
7ea3a87bfc | ||
|
|
a57905b6cd | ||
|
|
f9c592ca22 | ||
|
|
aecac0ef85 | ||
|
|
ca315c51a0 | ||
|
|
45c8858140 | ||
|
|
06e45bff24 | ||
|
|
2635a658d0 | ||
|
|
f48a98f691 | ||
|
|
3badda95c1 | ||
|
|
364ccc05d5 | ||
|
|
d09fedf208 | ||
|
|
7936f8730f | ||
|
|
6c0dfc4356 | ||
|
|
d9af8c31a2 | ||
|
|
ca375314f0 | ||
|
|
5266d4d79c | ||
|
|
1cb0826de0 | ||
|
|
89e3178ea3 | ||
|
|
3b5d9d6cee | ||
|
|
ce4ed19029 | ||
|
|
01a502339c | ||
|
|
642d0e6f74 | ||
|
|
d9e659deb0 | ||
|
|
830fe7f9b8 | ||
|
|
3e8c247c05 | ||
|
|
16880074fa | ||
|
|
19c74ce9fa | ||
|
|
56ef0dad9c | ||
|
|
8d99808821 | ||
|
|
1cb08fdecc | ||
|
|
e8952d7e02 | ||
|
|
18fad9c9d9 | ||
|
|
89a0a94f3e | ||
|
|
0859cf30f8 | ||
|
|
a0fe02a560 | ||
|
|
3156f7dacd | ||
|
|
c3958bf042 | ||
|
|
facc5f8aa7 | ||
|
|
8170257c26 | ||
|
|
489e37b2a1 | ||
|
|
4899e0d2d5 | ||
|
|
762f9c4b23 | ||
|
|
6d3dac0ec1 | ||
|
|
f684815272 | ||
|
|
8e04218c95 | ||
|
|
23cb45454b | ||
|
|
7fc9a161b1 | ||
|
|
95a5473051 | ||
|
|
66ceb573dc | ||
|
|
5f8223ebb5 | ||
|
|
51146d4152 | ||
|
|
3334281949 | ||
|
|
8e85d9ac00 | ||
|
|
e1c69a6250 | ||
|
|
edd9a18257 | ||
|
|
65f993677f | ||
|
|
bc775140bb | ||
|
|
4b2bd6e18f | ||
|
|
c36a810bcb | ||
|
|
a994bbc36b | ||
|
|
c3238f4d0b | ||
|
|
632d26e398 | ||
|
|
214cc8b810 | ||
|
|
8f218141f4 | ||
|
|
3676304751 | ||
|
|
c605fd57aa | ||
|
|
4599f6919c | ||
|
|
8ad20c0db3 | ||
|
|
638a69e5f0 | ||
|
|
9fa6b1ebe1 | ||
|
|
5930e2d221 | ||
|
|
fdd96975fb | ||
|
|
de6dd77046 | ||
|
|
1b6ad8413e | ||
|
|
6096d572f3 | ||
|
|
badcd64b62 | ||
|
|
a7b8b52dbd | ||
|
|
d89f0f51df | ||
|
|
be358f3f2e | ||
|
|
f122da1485 | ||
|
|
0dda4728b6 | ||
|
|
45fd95e02b | ||
|
|
91aa2d7f6f | ||
|
|
a96b7d2a80 | ||
|
|
8f9cea54c5 | ||
|
|
045558bede | ||
|
|
58124ebaab | ||
|
|
0c87dd5624 | ||
|
|
b87f986a49 | ||
|
|
c6a6035bb9 | ||
|
|
1ef12f0645 | ||
|
|
ef3ec33ba3 | ||
|
|
c82ef1ee8f | ||
|
|
23b8a60242 | ||
|
|
ac9511165e | ||
|
|
9d70851eb9 | ||
|
|
759dfb28ce | ||
|
|
ff356b1f21 | ||
|
|
b2a6c1bc68 | ||
|
|
76549d0a4a | ||
|
|
e5c72cae83 | ||
|
|
bf47df46c9 | ||
|
|
0ef42870e5 | ||
|
|
da8a6a4c2b | ||
|
|
988007a8c9 | ||
|
|
710439e83c | ||
|
|
80a0a3d4fb | ||
|
|
43299aea10 | ||
|
|
f5aea766a7 | ||
|
|
c5308e3f2f | ||
|
|
2b8e662f81 | ||
|
|
0a6d849435 | ||
|
|
a0106fe5d8 | ||
|
|
cee1b8a64a | ||
|
|
4e2ba71d59 | ||
|
|
fb2bdfb9ee | ||
|
|
72785f6740 | ||
|
|
a94a05fac9 | ||
|
|
430368de97 | ||
|
|
7bfb499549 | ||
|
|
9bc477e1b6 | ||
|
|
f84ac18472 | ||
|
|
cd515a2e54 | ||
|
|
c73c8fdc47 | ||
|
|
e755a32b23 | ||
|
|
d4d1768575 | ||
|
|
0a5745c559 | ||
|
|
b24959205b | ||
|
|
d69f297c05 | ||
|
|
3c8e331809 | ||
|
|
d169471e8c | ||
|
|
56453f6b5c | ||
|
|
dac2e8c79e | ||
|
|
ccc96bc610 | ||
|
|
654371cb6a | ||
|
|
1af8d873bb | ||
|
|
b7a0e23309 | ||
|
|
4a0f868941 | ||
|
|
448073cdd6 | ||
|
|
ad79ec7b1f | ||
|
|
e194854c6d | ||
|
|
d01d033209 | ||
|
|
06c4aca490 | ||
|
|
885d57138a | ||
|
|
9e2a770a26 | ||
|
|
942b699bb9 | ||
|
|
c9d03a8094 | ||
|
|
d015538bb4 | ||
|
|
90d6c5c5bb | ||
|
|
387ce4b6fa | ||
|
|
7943b13891 | ||
|
|
50a7cd19b4 | ||
|
|
53e9335bb0 | ||
|
|
e5cb70972e | ||
|
|
0d84871037 | ||
|
|
1d37745c0c | ||
|
|
ad9ade7849 | ||
|
|
c1e2ee32b4 | ||
|
|
1588d2734c | ||
|
|
50dda4263f | ||
|
|
a8f2239495 | ||
|
|
c42636b0ee | ||
|
|
54b2c8bd7e | ||
|
|
d01a21a867 | ||
|
|
5d43052c05 | ||
|
|
4109ab1590 | ||
|
|
f6eabd695b | ||
|
|
24d9dacfd9 | ||
|
|
66d51a9eb1 | ||
|
|
302df75d83 | ||
|
|
11b7e637e9 | ||
|
|
135c92bd85 | ||
|
|
c15c26a233 | ||
|
|
5d94760cce | ||
|
|
79517a0ba3 | ||
|
|
64295e3541 | ||
|
|
cc2816aaf5 | ||
|
|
4a2fcb9deb | ||
|
|
7f27580f1b | ||
|
|
94d0915004 | ||
|
|
88db99e593 | ||
|
|
593b25a5cd | ||
|
|
5c58bf44c0 | ||
|
|
73ae6cf164 | ||
|
|
7749319c75 | ||
|
|
73037b86ac | ||
|
|
d50ba1259c | ||
|
|
962261fee7 | ||
|
|
4dea2ef1a4 | ||
|
|
aa12eb4ed4 | ||
|
|
8a75363784 | ||
|
|
01dd982587 | ||
|
|
62cf83921b |
29
.gitignore
vendored
29
.gitignore
vendored
@@ -1,26 +1,10 @@
|
|||||||
# i2pd
|
# i2pd
|
||||||
|
obj/*.o
|
||||||
router.info
|
router.info
|
||||||
router.keys
|
router.keys
|
||||||
i2p
|
i2p
|
||||||
libi2pd.so
|
libi2pd.so
|
||||||
netDb
|
netDb
|
||||||
tunnels.cfg
|
|
||||||
tests/tests
|
|
||||||
|
|
||||||
# Build files
|
|
||||||
CMakeCache.txt
|
|
||||||
build/CMakeFiles/*
|
|
||||||
build/tests
|
|
||||||
build/client
|
|
||||||
build/core
|
|
||||||
build/benchmark
|
|
||||||
build/i2pd
|
|
||||||
build/i2pd-tests
|
|
||||||
build/i2pd-benchmark
|
|
||||||
*.cmake
|
|
||||||
*.a
|
|
||||||
*.o
|
|
||||||
|
|
||||||
|
|
||||||
# Autotools
|
# Autotools
|
||||||
autom4te.cache
|
autom4te.cache
|
||||||
@@ -63,14 +47,6 @@ local.properties
|
|||||||
# PDT-specific
|
# PDT-specific
|
||||||
.buildpath
|
.buildpath
|
||||||
|
|
||||||
#################
|
|
||||||
## Netbeans
|
|
||||||
#################
|
|
||||||
|
|
||||||
# CMake: if "Project with Existing Sources" is properly setup, then only the following needs to be ignored:
|
|
||||||
# (e.g., don't select "Automatic" but instead select "Run Configure Script in Subfolder:" into ./build/)
|
|
||||||
build/compile_commands.json
|
|
||||||
nbproject/
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
## Visual Studio
|
## Visual Studio
|
||||||
@@ -254,3 +230,6 @@ pip-log.txt
|
|||||||
|
|
||||||
#Mr Developer
|
#Mr Developer
|
||||||
.mr.developer.cfg
|
.mr.developer.cfg
|
||||||
|
|
||||||
|
# Sphinx
|
||||||
|
docs/_build
|
||||||
|
|||||||
37
.travis.yml
Normal file
37
.travis.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
language: cpp
|
||||||
|
cache:
|
||||||
|
apt: true
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- build-essential
|
||||||
|
- cmake
|
||||||
|
- g++
|
||||||
|
- clang
|
||||||
|
- libboost-chrono-dev
|
||||||
|
- libboost-date-time-dev
|
||||||
|
- libboost-filesystem-dev
|
||||||
|
- libboost-program-options-dev
|
||||||
|
- libboost-regex-dev
|
||||||
|
- libboost-system-dev
|
||||||
|
- libboost-thread-dev
|
||||||
|
- libminiupnpc-dev
|
||||||
|
- libssl-dev
|
||||||
|
compiler:
|
||||||
|
- gcc
|
||||||
|
- clang
|
||||||
|
before_install:
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install openssl miniupnpc ; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink boost openssl && brew link boost openssl -f ; fi
|
||||||
|
env:
|
||||||
|
matrix:
|
||||||
|
- BUILD_TYPE=Release UPNP=ON
|
||||||
|
- BUILD_TYPE=Release UPNP=OFF
|
||||||
|
script:
|
||||||
|
- cd build && cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DWITH_UPNP=${UPNP} && make
|
||||||
851
AddressBook.cpp
Normal file
851
AddressBook.cpp
Normal file
@@ -0,0 +1,851 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <fstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "AddressBook.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
// TODO: this is actually proxy class
|
||||||
|
class AddressBookFilesystemStorage: public AddressBookStorage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
i2p::fs::HashedStorage storage;
|
||||||
|
std::string etagsPath, indexPath, localPath;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {};
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) const;
|
||||||
|
void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address);
|
||||||
|
void RemoveAddress (const i2p::data::IdentHash& ident);
|
||||||
|
|
||||||
|
bool Init ();
|
||||||
|
int Load (std::map<std::string, i2p::data::IdentHash>& addresses);
|
||||||
|
int LoadLocal (std::map<std::string, i2p::data::IdentHash>& addresses);
|
||||||
|
int Save (const std::map<std::string, i2p::data::IdentHash>& addresses);
|
||||||
|
|
||||||
|
void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified);
|
||||||
|
bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
int LoadFromFile (const std::string& filename, std::map<std::string, i2p::data::IdentHash>& addresses); // returns -1 if can't open file, otherwise number of records
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
bool AddressBookFilesystemStorage::Init()
|
||||||
|
{
|
||||||
|
storage.SetPlace(i2p::fs::GetDataDir());
|
||||||
|
// init storage
|
||||||
|
if (storage.Init(i2p::data::GetBase32SubstitutionTable(), 32))
|
||||||
|
{
|
||||||
|
// init ETags
|
||||||
|
etagsPath = i2p::fs::StorageRootPath (storage, "etags");
|
||||||
|
if (!i2p::fs::Exists (etagsPath))
|
||||||
|
i2p::fs::CreateDirectory (etagsPath);
|
||||||
|
// init address files
|
||||||
|
indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv");
|
||||||
|
localPath = i2p::fs::StorageRootPath (storage, "local.csv");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const
|
||||||
|
{
|
||||||
|
std::string filename = storage.Path(ident.ToBase32());
|
||||||
|
std::ifstream f(filename, std::ifstream::binary);
|
||||||
|
if (!f.is_open ()) {
|
||||||
|
LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.seekg (0,std::ios::end);
|
||||||
|
size_t len = f.tellg ();
|
||||||
|
if (len < i2p::data::DEFAULT_IDENTITY_SIZE) {
|
||||||
|
LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
f.seekg(0, std::ios::beg);
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
f.read((char *)buf, len);
|
||||||
|
auto address = std::make_shared<i2p::data::IdentityEx>(buf, len);
|
||||||
|
delete[] buf;
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBookFilesystemStorage::AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
|
||||||
|
{
|
||||||
|
std::string path = storage.Path( address->GetIdentHash().ToBase32() );
|
||||||
|
std::ofstream f (path, std::ofstream::binary | std::ofstream::out);
|
||||||
|
if (!f.is_open ()) {
|
||||||
|
LogPrint (eLogError, "Addressbook: can't open file ", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t len = address->GetFullLen ();
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
address->ToBuffer (buf, len);
|
||||||
|
f.write ((char *)buf, len);
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
storage.Remove( ident.ToBase32() );
|
||||||
|
}
|
||||||
|
|
||||||
|
int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map<std::string, i2p::data::IdentHash>& addresses)
|
||||||
|
{
|
||||||
|
int num = 0;
|
||||||
|
std::ifstream f (filename, std::ifstream::in); // in text mode
|
||||||
|
if (!f) return -1;
|
||||||
|
|
||||||
|
addresses.clear ();
|
||||||
|
while (!f.eof ())
|
||||||
|
{
|
||||||
|
std::string s;
|
||||||
|
getline(f, s);
|
||||||
|
if (!s.length()) continue; // skip empty line
|
||||||
|
|
||||||
|
std::size_t pos = s.find(',');
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string name = s.substr(0, pos++);
|
||||||
|
std::string addr = s.substr(pos);
|
||||||
|
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
ident.FromBase32 (addr);
|
||||||
|
addresses[name] = ident;
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AddressBookFilesystemStorage::Load (std::map<std::string, i2p::data::IdentHash>& addresses)
|
||||||
|
{
|
||||||
|
int num = LoadFromFile (indexPath, addresses);
|
||||||
|
if (num < 0)
|
||||||
|
{
|
||||||
|
LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LogPrint(eLogInfo, "Addressbook: using index file ", indexPath);
|
||||||
|
LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage");
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AddressBookFilesystemStorage::LoadLocal (std::map<std::string, i2p::data::IdentHash>& addresses)
|
||||||
|
{
|
||||||
|
int num = LoadFromFile (localPath, addresses);
|
||||||
|
if (num < 0) return 0;
|
||||||
|
LogPrint (eLogInfo, "Addressbook: ", num, " local addresses loaded");
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AddressBookFilesystemStorage::Save (const std::map<std::string, i2p::data::IdentHash>& addresses)
|
||||||
|
{
|
||||||
|
if (addresses.size() == 0) {
|
||||||
|
LogPrint(eLogWarning, "Addressbook: not saving empty addressbook");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int num = 0;
|
||||||
|
std::ofstream f (indexPath, std::ofstream::out); // in text mode
|
||||||
|
|
||||||
|
if (!f.is_open ()) {
|
||||||
|
LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it: addresses) {
|
||||||
|
f << it.first << "," << it.second.ToBase32 () << std::endl;
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved");
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBookFilesystemStorage::SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified)
|
||||||
|
{
|
||||||
|
std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt";
|
||||||
|
std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc);
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
f << etag << std::endl;
|
||||||
|
f<< lastModified << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressBookFilesystemStorage::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified)
|
||||||
|
{
|
||||||
|
std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt";
|
||||||
|
std::ifstream f (fname, std::ofstream::in);
|
||||||
|
if (!f || f.eof ()) return false;
|
||||||
|
std::getline (f, etag);
|
||||||
|
if (f.eof ()) return false;
|
||||||
|
std::getline (f, lastModified);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
AddressBook::AddressBook (): m_Storage(new AddressBookFilesystemStorage), m_IsLoaded (false), m_IsDownloading (false),
|
||||||
|
m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressBook::~AddressBook ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::Start ()
|
||||||
|
{
|
||||||
|
m_Storage->Init();
|
||||||
|
LoadHosts (); /* try storage, then hosts.txt, then download */
|
||||||
|
StartSubscriptions ();
|
||||||
|
StartLookups ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::StartResolvers ()
|
||||||
|
{
|
||||||
|
LoadLocal ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::Stop ()
|
||||||
|
{
|
||||||
|
StopLookups ();
|
||||||
|
StopSubscriptions ();
|
||||||
|
if (m_SubscriptionsUpdateTimer)
|
||||||
|
{
|
||||||
|
delete m_SubscriptionsUpdateTimer;
|
||||||
|
m_SubscriptionsUpdateTimer = nullptr;
|
||||||
|
}
|
||||||
|
if (m_IsDownloading)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Addressbook: subscriptions is downloading, abort");
|
||||||
|
for (int i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
|
if (!m_IsDownloading)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Addressbook: subscriptions download complete");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds
|
||||||
|
}
|
||||||
|
LogPrint (eLogError, "Addressbook: subscription download timeout");
|
||||||
|
m_IsDownloading = false;
|
||||||
|
}
|
||||||
|
if (m_Storage)
|
||||||
|
{
|
||||||
|
m_Storage->Save (m_Addresses);
|
||||||
|
delete m_Storage;
|
||||||
|
m_Storage = nullptr;
|
||||||
|
}
|
||||||
|
m_DefaultSubscription = nullptr;
|
||||||
|
for (auto it: m_Subscriptions)
|
||||||
|
delete it;
|
||||||
|
m_Subscriptions.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressBook::GetIdentHash (const std::string& address, i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
auto pos = address.find(".b32.i2p");
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
Base32ToByteStream (address.c_str(), pos, ident, 32);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos = address.find (".i2p");
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
auto identHash = FindAddress (address);
|
||||||
|
if (identHash)
|
||||||
|
{
|
||||||
|
ident = *identHash;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LookupAddress (address); // TODO:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if not .b32 we assume full base64 address
|
||||||
|
i2p::data::IdentityEx dest;
|
||||||
|
if (!dest.FromBase64 (address))
|
||||||
|
return false;
|
||||||
|
ident = dest.GetIdentHash ();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const i2p::data::IdentHash * AddressBook::FindAddress (const std::string& address)
|
||||||
|
{
|
||||||
|
auto it = m_Addresses.find (address);
|
||||||
|
if (it != m_Addresses.end ())
|
||||||
|
return &it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::InsertAddress (const std::string& address, const std::string& base64)
|
||||||
|
{
|
||||||
|
auto ident = std::make_shared<i2p::data::IdentityEx>();
|
||||||
|
ident->FromBase64 (base64);
|
||||||
|
m_Storage->AddAddress (ident);
|
||||||
|
m_Addresses[address] = ident->GetIdentHash ();
|
||||||
|
LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::InsertAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
|
||||||
|
{
|
||||||
|
m_Storage->AddAddress (address);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> AddressBook::GetAddress (const std::string& address)
|
||||||
|
{
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
if (!GetIdentHash (address, ident)) return nullptr;
|
||||||
|
return m_Storage->GetAddress (ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::LoadHosts ()
|
||||||
|
{
|
||||||
|
if (m_Storage->Load (m_Addresses) > 0)
|
||||||
|
{
|
||||||
|
m_IsLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// then try hosts.txt
|
||||||
|
std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode
|
||||||
|
if (f.is_open ())
|
||||||
|
{
|
||||||
|
LoadHostsFromStream (f);
|
||||||
|
m_IsLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressBook::LoadHostsFromStream (std::istream& f)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_AddressBookMutex);
|
||||||
|
int numAddresses = 0;
|
||||||
|
bool incomplete = false;
|
||||||
|
std::string s;
|
||||||
|
while (!f.eof ())
|
||||||
|
{
|
||||||
|
getline(f, s);
|
||||||
|
|
||||||
|
if (!s.length())
|
||||||
|
continue; // skip empty line
|
||||||
|
|
||||||
|
size_t pos = s.find('=');
|
||||||
|
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string name = s.substr(0, pos++);
|
||||||
|
std::string addr = s.substr(pos);
|
||||||
|
|
||||||
|
auto ident = std::make_shared<i2p::data::IdentityEx> ();
|
||||||
|
if (ident->FromBase64(addr))
|
||||||
|
{
|
||||||
|
m_Addresses[name] = ident->GetIdentHash ();
|
||||||
|
m_Storage->AddAddress (ident);
|
||||||
|
numAddresses++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name);
|
||||||
|
incomplete = f.eof ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
incomplete = f.eof ();
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed");
|
||||||
|
if (numAddresses > 0)
|
||||||
|
{
|
||||||
|
if (!incomplete) m_IsLoaded = true;
|
||||||
|
m_Storage->Save (m_Addresses);
|
||||||
|
}
|
||||||
|
return !incomplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::LoadSubscriptions ()
|
||||||
|
{
|
||||||
|
if (!m_Subscriptions.size ())
|
||||||
|
{
|
||||||
|
std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode
|
||||||
|
if (f.is_open ())
|
||||||
|
{
|
||||||
|
std::string s;
|
||||||
|
while (!f.eof ())
|
||||||
|
{
|
||||||
|
getline(f, s);
|
||||||
|
if (!s.length()) continue; // skip empty line
|
||||||
|
m_Subscriptions.push_back (new AddressBookSubscription (*this, s));
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Addressbook: subscriptions.txt not found in datadir");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Addressbook: subscriptions already loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::LoadLocal ()
|
||||||
|
{
|
||||||
|
std::map<std::string, i2p::data::IdentHash> localAddresses;
|
||||||
|
m_Storage->LoadLocal (localAddresses);
|
||||||
|
for (auto it: localAddresses)
|
||||||
|
{
|
||||||
|
auto dot = it.first.find ('.');
|
||||||
|
if (dot != std::string::npos)
|
||||||
|
{
|
||||||
|
auto domain = it.first.substr (dot + 1);
|
||||||
|
auto it1 = m_Addresses.find (domain); // find domain in our addressbook
|
||||||
|
if (it1 != m_Addresses.end ())
|
||||||
|
{
|
||||||
|
auto dest = context.FindLocalDestination (it1->second);
|
||||||
|
if (dest)
|
||||||
|
{
|
||||||
|
// address is ours
|
||||||
|
std::shared_ptr<AddressResolver> resolver;
|
||||||
|
auto it2 = m_Resolvers.find (it1->second);
|
||||||
|
if (it2 != m_Resolvers.end ())
|
||||||
|
resolver = it2->second; // resolver exists
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create new resolver
|
||||||
|
resolver = std::make_shared<AddressResolver>(dest);
|
||||||
|
m_Resolvers.insert (std::make_pair(it1->second, resolver));
|
||||||
|
}
|
||||||
|
resolver->AddAddress (it.first, it.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified)
|
||||||
|
{
|
||||||
|
if (m_Storage)
|
||||||
|
return m_Storage->GetEtag (subscription, etag, lastModified);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified)
|
||||||
|
{
|
||||||
|
m_IsDownloading = false;
|
||||||
|
int nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT;
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
if (m_DefaultSubscription) m_DefaultSubscription.reset (nullptr);
|
||||||
|
if (m_IsLoaded)
|
||||||
|
nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT;
|
||||||
|
else
|
||||||
|
m_IsLoaded = true;
|
||||||
|
if (m_Storage) m_Storage->SaveEtag (subscription, etag, lastModified);
|
||||||
|
}
|
||||||
|
if (m_SubscriptionsUpdateTimer)
|
||||||
|
{
|
||||||
|
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(nextUpdateTimeout));
|
||||||
|
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
|
||||||
|
this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::StartSubscriptions ()
|
||||||
|
{
|
||||||
|
LoadSubscriptions ();
|
||||||
|
if (m_IsLoaded && m_Subscriptions.empty ()) return;
|
||||||
|
|
||||||
|
auto dest = i2p::client::context.GetSharedLocalDestination ();
|
||||||
|
if (dest)
|
||||||
|
{
|
||||||
|
m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (dest->GetService ());
|
||||||
|
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT));
|
||||||
|
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
|
||||||
|
this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Addressbook: can't start subscriptions: missing shared local destination");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::StopSubscriptions ()
|
||||||
|
{
|
||||||
|
if (m_SubscriptionsUpdateTimer)
|
||||||
|
m_SubscriptionsUpdateTimer->cancel ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
auto dest = i2p::client::context.GetSharedLocalDestination ();
|
||||||
|
if (!dest) {
|
||||||
|
LogPrint(eLogWarning, "Addressbook: missing local destination, skip subscription update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_IsDownloading && dest->IsReady ())
|
||||||
|
{
|
||||||
|
if (!m_IsLoaded)
|
||||||
|
{
|
||||||
|
// download it from http://i2p-projekt.i2p/hosts.txt
|
||||||
|
LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription.");
|
||||||
|
if (!m_DefaultSubscription)
|
||||||
|
m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS));
|
||||||
|
m_IsDownloading = true;
|
||||||
|
m_DefaultSubscription->CheckSubscription ();
|
||||||
|
}
|
||||||
|
else if (!m_Subscriptions.empty ())
|
||||||
|
{
|
||||||
|
// pick random subscription
|
||||||
|
auto ind = rand () % m_Subscriptions.size();
|
||||||
|
m_IsDownloading = true;
|
||||||
|
m_Subscriptions[ind]->CheckSubscription ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// try it again later
|
||||||
|
m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT));
|
||||||
|
m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer,
|
||||||
|
this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::StartLookups ()
|
||||||
|
{
|
||||||
|
auto dest = i2p::client::context.GetSharedLocalDestination ();
|
||||||
|
if (dest)
|
||||||
|
{
|
||||||
|
auto datagram = dest->GetDatagramDestination ();
|
||||||
|
if (!datagram)
|
||||||
|
datagram = dest->CreateDatagramDestination ();
|
||||||
|
datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
|
||||||
|
ADDRESS_RESPONSE_DATAGRAM_PORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::StopLookups ()
|
||||||
|
{
|
||||||
|
auto dest = i2p::client::context.GetSharedLocalDestination ();
|
||||||
|
if (dest)
|
||||||
|
{
|
||||||
|
auto datagram = dest->GetDatagramDestination ();
|
||||||
|
if (datagram)
|
||||||
|
datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::LookupAddress (const std::string& address)
|
||||||
|
{
|
||||||
|
const i2p::data::IdentHash * ident = nullptr;
|
||||||
|
auto dot = address.find ('.');
|
||||||
|
if (dot != std::string::npos)
|
||||||
|
ident = FindAddress (address.substr (dot + 1));
|
||||||
|
if (!ident)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "AddressBook: Can't find domain for ", address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dest = i2p::client::context.GetSharedLocalDestination ();
|
||||||
|
if (dest)
|
||||||
|
{
|
||||||
|
auto datagram = dest->GetDatagramDestination ();
|
||||||
|
if (datagram)
|
||||||
|
{
|
||||||
|
uint32_t nonce;
|
||||||
|
RAND_bytes ((uint8_t *)&nonce, 4);
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_LookupsMutex);
|
||||||
|
m_Lookups[nonce] = address;
|
||||||
|
}
|
||||||
|
LogPrint (eLogDebug, "AddressBook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce);
|
||||||
|
size_t len = address.length () + 9;
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
memset (buf, 0, 4);
|
||||||
|
htobe32buf (buf + 4, nonce);
|
||||||
|
buf[8] = address.length ();
|
||||||
|
memcpy (buf + 9, address.c_str (), address.length ());
|
||||||
|
datagram->SendDatagramTo (buf, len, *ident, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT);
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (len < 44)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "AddressBook: Lookup response is too short ", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t nonce = bufbe32toh (buf + 4);
|
||||||
|
LogPrint (eLogDebug, "AddressBook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce);
|
||||||
|
std::string address;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_LookupsMutex);
|
||||||
|
auto it = m_Lookups.find (nonce);
|
||||||
|
if (it != m_Lookups.end ())
|
||||||
|
{
|
||||||
|
address = it->second;
|
||||||
|
m_Lookups.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (address.length () > 0)
|
||||||
|
{
|
||||||
|
// TODO: verify from
|
||||||
|
m_Addresses[address] = buf + 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link):
|
||||||
|
m_Book (book), m_Link (link)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBookSubscription::CheckSubscription ()
|
||||||
|
{
|
||||||
|
std::thread load_hosts(&AddressBookSubscription::Request, this);
|
||||||
|
load_hosts.detach(); // TODO: use join
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressBookSubscription::Request ()
|
||||||
|
{
|
||||||
|
// must be run in separate thread
|
||||||
|
LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
|
||||||
|
bool success = false;
|
||||||
|
i2p::util::http::url u (m_Link);
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
if (m_Book.GetIdentHash (u.host_, ident))
|
||||||
|
{
|
||||||
|
if (!m_Etag.length ())
|
||||||
|
{
|
||||||
|
// load ETag
|
||||||
|
m_Book.GetEtag (ident, m_Etag, m_LastModified);
|
||||||
|
LogPrint (eLogInfo, "Addressbook: set ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
|
||||||
|
}
|
||||||
|
std::condition_variable newDataReceived;
|
||||||
|
std::mutex newDataReceivedMutex;
|
||||||
|
auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident);
|
||||||
|
if (!leaseSet)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(newDataReceivedMutex);
|
||||||
|
i2p::client::context.GetSharedLocalDestination ()->RequestDestination (ident,
|
||||||
|
[&newDataReceived, &leaseSet](std::shared_ptr<i2p::data::LeaseSet> ls)
|
||||||
|
{
|
||||||
|
leaseSet = ls;
|
||||||
|
newDataReceived.notify_all ();
|
||||||
|
});
|
||||||
|
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired");
|
||||||
|
i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (leaseSet)
|
||||||
|
{
|
||||||
|
std::stringstream request, response;
|
||||||
|
// standard header
|
||||||
|
request << "GET " << u.path_ << " HTTP/1.1\r\n"
|
||||||
|
<< "Host: " << u.host_ << "\r\n"
|
||||||
|
<< "Accept: */*\r\n"
|
||||||
|
<< "User-Agent: Wget/1.11.4\r\n"
|
||||||
|
//<< "Accept-Encoding: gzip\r\n"
|
||||||
|
<< "X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n"
|
||||||
|
<< "Connection: close\r\n";
|
||||||
|
if (m_Etag.length () > 0) // etag
|
||||||
|
request << i2p::util::http::IF_NONE_MATCH << ": " << m_Etag << "\r\n";
|
||||||
|
if (m_LastModified.length () > 0) // if-modfief-since
|
||||||
|
request << i2p::util::http::IF_MODIFIED_SINCE << ": " << m_LastModified << "\r\n";
|
||||||
|
request << "\r\n"; // end of header
|
||||||
|
auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, u.port_);
|
||||||
|
stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ());
|
||||||
|
|
||||||
|
uint8_t buf[4096];
|
||||||
|
bool end = false;
|
||||||
|
while (!end)
|
||||||
|
{
|
||||||
|
stream->AsyncReceive (boost::asio::buffer (buf, 4096),
|
||||||
|
[&](const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (bytes_transferred)
|
||||||
|
response.write ((char *)buf, bytes_transferred);
|
||||||
|
if (ecode == boost::asio::error::timed_out || !stream->IsOpen ())
|
||||||
|
end = true;
|
||||||
|
newDataReceived.notify_all ();
|
||||||
|
},
|
||||||
|
30); // wait for 30 seconds
|
||||||
|
std::unique_lock<std::mutex> l(newDataReceivedMutex);
|
||||||
|
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
|
||||||
|
LogPrint (eLogError, "Addressbook: subscriptions request timeout expired");
|
||||||
|
}
|
||||||
|
// process remaining buffer
|
||||||
|
while (size_t len = stream->ReadSome (buf, 4096))
|
||||||
|
response.write ((char *)buf, len);
|
||||||
|
|
||||||
|
// parse response
|
||||||
|
std::string version;
|
||||||
|
response >> version; // HTTP version
|
||||||
|
int status = 0;
|
||||||
|
response >> status; // status
|
||||||
|
if (status == 200) // OK
|
||||||
|
{
|
||||||
|
bool isChunked = false, isGzip = false;
|
||||||
|
std::string header, statusMessage;
|
||||||
|
std::getline (response, statusMessage);
|
||||||
|
// read until new line meaning end of header
|
||||||
|
while (!response.eof () && header != "\r")
|
||||||
|
{
|
||||||
|
std::getline (response, header);
|
||||||
|
auto colon = header.find (':');
|
||||||
|
if (colon != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string field = header.substr (0, colon);
|
||||||
|
boost::to_lower (field); // field are not case-sensitive
|
||||||
|
colon++;
|
||||||
|
header.resize (header.length () - 1); // delete \r
|
||||||
|
if (field == i2p::util::http::ETAG)
|
||||||
|
m_Etag = header.substr (colon + 1);
|
||||||
|
else if (field == i2p::util::http::LAST_MODIFIED)
|
||||||
|
m_LastModified = header.substr (colon + 1);
|
||||||
|
else if (field == i2p::util::http::TRANSFER_ENCODING)
|
||||||
|
isChunked = !header.compare (colon + 1, std::string::npos, "chunked");
|
||||||
|
else if (field == i2p::util::http::CONTENT_ENCODING)
|
||||||
|
isGzip = !header.compare (colon + 1, std::string::npos, "gzip") ||
|
||||||
|
!header.compare (colon + 1, std::string::npos, "x-i2p-gzip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified);
|
||||||
|
if (!response.eof ())
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
if (!isChunked)
|
||||||
|
success = ProcessResponse (response, isGzip);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// merge chunks
|
||||||
|
std::stringstream merged;
|
||||||
|
i2p::util::http::MergeChunkedResponse (response, merged);
|
||||||
|
success = ProcessResponse (merged, isGzip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status == 304)
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
LogPrint (eLogInfo, "Addressbook: no updates from ", m_Link);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Adressbook: HTTP response ", status);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Addressbook: address ", u.host_, " not found");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed");
|
||||||
|
|
||||||
|
m_Book.DownloadComplete (success, ident, m_Etag, m_LastModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressBookSubscription::ProcessResponse (std::stringstream& s, bool isGzip)
|
||||||
|
{
|
||||||
|
if (isGzip)
|
||||||
|
{
|
||||||
|
std::stringstream uncompressed;
|
||||||
|
i2p::data::GzipInflator inflator;
|
||||||
|
inflator.Inflate (s, uncompressed);
|
||||||
|
if (!uncompressed.fail ())
|
||||||
|
return m_Book.LoadHostsFromStream (uncompressed);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return m_Book.LoadHostsFromStream (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressResolver::AddressResolver (std::shared_ptr<ClientDestination> destination):
|
||||||
|
m_LocalDestination (destination)
|
||||||
|
{
|
||||||
|
if (m_LocalDestination)
|
||||||
|
{
|
||||||
|
auto datagram = m_LocalDestination->GetDatagramDestination ();
|
||||||
|
if (!datagram)
|
||||||
|
datagram = m_LocalDestination->CreateDatagramDestination ();
|
||||||
|
datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
|
||||||
|
ADDRESS_RESOLVER_DATAGRAM_PORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressResolver::~AddressResolver ()
|
||||||
|
{
|
||||||
|
if (m_LocalDestination)
|
||||||
|
{
|
||||||
|
auto datagram = m_LocalDestination->GetDatagramDestination ();
|
||||||
|
if (datagram)
|
||||||
|
datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (len < 9 || len < buf[8] + 9U)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "AddressBook: Address request is too short ", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// read requested address
|
||||||
|
uint8_t l = buf[8];
|
||||||
|
char address[255];
|
||||||
|
memcpy (address, buf + 9, l);
|
||||||
|
address[l] = 0;
|
||||||
|
LogPrint (eLogDebug, "AddressBook: Address request ", address);
|
||||||
|
// send response
|
||||||
|
uint8_t response[44];
|
||||||
|
memset (response, 0, 4); // reserved
|
||||||
|
memcpy (response + 4, buf + 4, 4); // nonce
|
||||||
|
auto it = m_LocalAddresses.find (address); // address lookup
|
||||||
|
if (it != m_LocalAddresses.end ())
|
||||||
|
memcpy (response + 8, it->second, 32); // ident
|
||||||
|
else
|
||||||
|
memset (response + 8, 0, 32); // not found
|
||||||
|
memset (response + 40, 0, 4); // set expiration time to zero
|
||||||
|
m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 44, from.GetIdentHash (), toPort, fromPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
m_LocalAddresses[name] = ident;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
147
AddressBook.h
Normal file
147
AddressBook.h
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#ifndef ADDRESS_BOOK_H__
|
||||||
|
#define ADDRESS_BOOK_H__
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
|
||||||
|
const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes
|
||||||
|
const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes
|
||||||
|
const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours)
|
||||||
|
const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes
|
||||||
|
const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second
|
||||||
|
|
||||||
|
const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53;
|
||||||
|
const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54;
|
||||||
|
|
||||||
|
inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); }
|
||||||
|
|
||||||
|
class AddressBookStorage // interface for storage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~AddressBookStorage () {};
|
||||||
|
virtual std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) const = 0;
|
||||||
|
virtual void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address) = 0;
|
||||||
|
virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0;
|
||||||
|
|
||||||
|
virtual bool Init () = 0;
|
||||||
|
virtual int Load (std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
|
||||||
|
virtual int LoadLocal (std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
|
||||||
|
virtual int Save (const std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
|
||||||
|
|
||||||
|
virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0;
|
||||||
|
virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddressBookSubscription;
|
||||||
|
class AddressResolver;
|
||||||
|
class AddressBook
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
AddressBook ();
|
||||||
|
~AddressBook ();
|
||||||
|
void Start ();
|
||||||
|
void StartResolvers ();
|
||||||
|
void Stop ();
|
||||||
|
bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident);
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const std::string& address);
|
||||||
|
const i2p::data::IdentHash * FindAddress (const std::string& address);
|
||||||
|
void LookupAddress (const std::string& address);
|
||||||
|
void InsertAddress (const std::string& address, const std::string& base64); // for jump service
|
||||||
|
void InsertAddress (std::shared_ptr<const i2p::data::IdentityEx> address);
|
||||||
|
|
||||||
|
bool LoadHostsFromStream (std::istream& f);
|
||||||
|
void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified);
|
||||||
|
//This method returns the ".b32.i2p" address
|
||||||
|
std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); }
|
||||||
|
std::string ToAddress(std::shared_ptr<const i2p::data::IdentityEx> ident) { return ToAddress(ident->GetIdentHash ()); }
|
||||||
|
|
||||||
|
bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void StartSubscriptions ();
|
||||||
|
void StopSubscriptions ();
|
||||||
|
|
||||||
|
void LoadHosts ();
|
||||||
|
void LoadSubscriptions ();
|
||||||
|
void LoadLocal ();
|
||||||
|
|
||||||
|
void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
void StartLookups ();
|
||||||
|
void StopLookups ();
|
||||||
|
void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::mutex m_AddressBookMutex;
|
||||||
|
std::map<std::string, i2p::data::IdentHash> m_Addresses;
|
||||||
|
std::map<i2p::data::IdentHash, std::shared_ptr<AddressResolver> > m_Resolvers; // local destination->resolver
|
||||||
|
std::mutex m_LookupsMutex;
|
||||||
|
std::map<uint32_t, std::string> m_Lookups; // nonce -> address
|
||||||
|
AddressBookStorage * m_Storage;
|
||||||
|
volatile bool m_IsLoaded, m_IsDownloading;
|
||||||
|
std::vector<AddressBookSubscription *> m_Subscriptions;
|
||||||
|
std::unique_ptr<AddressBookSubscription> m_DefaultSubscription; // in case if we don't know any addresses yet
|
||||||
|
boost::asio::deadline_timer * m_SubscriptionsUpdateTimer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddressBookSubscription
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
AddressBookSubscription (AddressBook& book, const std::string& link);
|
||||||
|
void CheckSubscription ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Request ();
|
||||||
|
bool ProcessResponse (std::stringstream& s, bool isGzip = false);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
AddressBook& m_Book;
|
||||||
|
std::string m_Link, m_Etag, m_LastModified;
|
||||||
|
// m_Etag must be surrounded by ""
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddressResolver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
AddressResolver (std::shared_ptr<ClientDestination> destination);
|
||||||
|
~AddressResolver ();
|
||||||
|
void AddAddress (const std::string& name, const i2p::data::IdentHash& ident);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> m_LocalDestination;
|
||||||
|
std::map<std::string, i2p::data::IdentHash> m_LocalAddresses;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
643
BOB.cpp
Normal file
643
BOB.cpp
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include "Log.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "BOB.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
BOBI2PInboundTunnel::BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination):
|
||||||
|
BOBI2PTunnel (localDestination),
|
||||||
|
m_Acceptor (localDestination->GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBI2PInboundTunnel::~BOBI2PInboundTunnel ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::Start ()
|
||||||
|
{
|
||||||
|
m_Acceptor.listen ();
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::Stop ()
|
||||||
|
{
|
||||||
|
m_Acceptor.close();
|
||||||
|
ClearHandlers ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::Accept ()
|
||||||
|
{
|
||||||
|
auto receiver = std::make_shared<AddressReceiver> ();
|
||||||
|
receiver->socket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
|
||||||
|
m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this,
|
||||||
|
std::placeholders::_1, receiver));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
Accept ();
|
||||||
|
ReceiveAddress (receiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> receiver)
|
||||||
|
{
|
||||||
|
receiver->socket->async_read_some (boost::asio::buffer(
|
||||||
|
receiver->buffer + receiver->bufferOffset,
|
||||||
|
BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset),
|
||||||
|
std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, receiver));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||||
|
std::shared_ptr<AddressReceiver> receiver)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
LogPrint (eLogError, "BOB: inbound tunnel read error: ", ecode.message ());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
receiver->bufferOffset += bytes_transferred;
|
||||||
|
receiver->buffer[receiver->bufferOffset] = 0;
|
||||||
|
char * eol = strchr (receiver->buffer, '\n');
|
||||||
|
if (eol)
|
||||||
|
{
|
||||||
|
*eol = 0;
|
||||||
|
|
||||||
|
receiver->data = (uint8_t *)eol + 1;
|
||||||
|
receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1);
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident);
|
||||||
|
if (leaseSet)
|
||||||
|
CreateConnection (receiver, leaseSet);
|
||||||
|
else
|
||||||
|
GetLocalDestination ()->RequestDestination (ident,
|
||||||
|
std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete,
|
||||||
|
this, std::placeholders::_1, receiver));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE)
|
||||||
|
ReceiveAddress (receiver);
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "BOB: missing inbound address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver)
|
||||||
|
{
|
||||||
|
if (leaseSet)
|
||||||
|
CreateConnection (receiver, leaseSet);
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: New inbound connection");
|
||||||
|
auto connection = std::make_shared<I2PTunnelConnection>(this, receiver->socket, leaseSet);
|
||||||
|
AddHandler (connection);
|
||||||
|
connection->I2PConnect (receiver->data, receiver->dataLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port,
|
||||||
|
std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination),
|
||||||
|
m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2POutboundTunnel::Start ()
|
||||||
|
{
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2POutboundTunnel::Stop ()
|
||||||
|
{
|
||||||
|
ClearHandlers ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2POutboundTunnel::Accept ()
|
||||||
|
{
|
||||||
|
auto localDestination = GetLocalDestination ();
|
||||||
|
if (localDestination)
|
||||||
|
localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1));
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "BOB: Local destination not set for server tunnel");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), m_Endpoint, m_IsQuiet);
|
||||||
|
AddHandler (conn);
|
||||||
|
conn->Connect ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination):
|
||||||
|
m_LocalDestination (localDestination),
|
||||||
|
m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBDestination::~BOBDestination ()
|
||||||
|
{
|
||||||
|
delete m_OutboundTunnel;
|
||||||
|
delete m_InboundTunnel;
|
||||||
|
i2p::client::context.DeleteLocalDestination (m_LocalDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBDestination::Start ()
|
||||||
|
{
|
||||||
|
if (m_OutboundTunnel) m_OutboundTunnel->Start ();
|
||||||
|
if (m_InboundTunnel) m_InboundTunnel->Start ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBDestination::Stop ()
|
||||||
|
{
|
||||||
|
StopTunnels ();
|
||||||
|
m_LocalDestination->Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBDestination::StopTunnels ()
|
||||||
|
{
|
||||||
|
if (m_OutboundTunnel)
|
||||||
|
{
|
||||||
|
m_OutboundTunnel->Stop ();
|
||||||
|
delete m_OutboundTunnel;
|
||||||
|
m_OutboundTunnel = nullptr;
|
||||||
|
}
|
||||||
|
if (m_InboundTunnel)
|
||||||
|
{
|
||||||
|
m_InboundTunnel->Stop ();
|
||||||
|
delete m_InboundTunnel;
|
||||||
|
m_InboundTunnel = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBDestination::CreateInboundTunnel (int port)
|
||||||
|
{
|
||||||
|
if (!m_InboundTunnel)
|
||||||
|
m_InboundTunnel = new BOBI2PInboundTunnel (port, m_LocalDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet)
|
||||||
|
{
|
||||||
|
if (!m_OutboundTunnel)
|
||||||
|
m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner):
|
||||||
|
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0),
|
||||||
|
m_IsOpen (true), m_IsQuiet (false), m_InPort (0), m_OutPort (0),
|
||||||
|
m_CurrentDestination (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBCommandSession::~BOBCommandSession ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::Terminate ()
|
||||||
|
{
|
||||||
|
m_Socket.close ();
|
||||||
|
m_IsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::Receive ()
|
||||||
|
{
|
||||||
|
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset),
|
||||||
|
std::bind(&BOBCommandSession::HandleReceived, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "BOB: command channel read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t size = m_ReceiveBufferOffset + bytes_transferred;
|
||||||
|
m_ReceiveBuffer[size] = 0;
|
||||||
|
char * eol = strchr (m_ReceiveBuffer, '\n');
|
||||||
|
if (eol)
|
||||||
|
{
|
||||||
|
*eol = 0;
|
||||||
|
char * operand = strchr (m_ReceiveBuffer, ' ');
|
||||||
|
if (operand)
|
||||||
|
{
|
||||||
|
*operand = 0;
|
||||||
|
operand++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
operand = eol;
|
||||||
|
// process command
|
||||||
|
auto& handlers = m_Owner.GetCommandHandlers ();
|
||||||
|
auto it = handlers.find (m_ReceiveBuffer);
|
||||||
|
if (it != handlers.end ())
|
||||||
|
(this->*(it->second))(operand, eol - operand);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "BOB: unknown command ", m_ReceiveBuffer);
|
||||||
|
SendReplyError ("unknown command");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1;
|
||||||
|
memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (size < BOB_COMMAND_BUFFER_SIZE)
|
||||||
|
m_ReceiveBufferOffset = size;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "BOB: Malformed input of the command channel");
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::Send (size_t len)
|
||||||
|
{
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len),
|
||||||
|
boost::asio::transfer_all (),
|
||||||
|
std::bind(&BOBCommandSession::HandleSent, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_IsOpen)
|
||||||
|
Receive ();
|
||||||
|
else
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::SendReplyOK (const char * msg)
|
||||||
|
{
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
|
||||||
|
#else
|
||||||
|
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
|
||||||
|
#endif
|
||||||
|
Send (len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::SendReplyError (const char * msg)
|
||||||
|
{
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
|
||||||
|
#else
|
||||||
|
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
|
||||||
|
#endif
|
||||||
|
Send (len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::SendVersion ()
|
||||||
|
{
|
||||||
|
size_t len = strlen (BOB_VERSION);
|
||||||
|
memcpy (m_SendBuffer, BOB_VERSION, len);
|
||||||
|
Send (len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::SendData (const char * nickname)
|
||||||
|
{
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
|
||||||
|
#else
|
||||||
|
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
|
||||||
|
#endif
|
||||||
|
Send (len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: zap");
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: quit");
|
||||||
|
m_IsOpen = false;
|
||||||
|
SendReplyOK ("Bye!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::StartCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: start ", m_Nickname);
|
||||||
|
if (!m_CurrentDestination)
|
||||||
|
{
|
||||||
|
m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options));
|
||||||
|
m_Owner.AddDestination (m_Nickname, m_CurrentDestination);
|
||||||
|
}
|
||||||
|
if (m_InPort)
|
||||||
|
m_CurrentDestination->CreateInboundTunnel (m_InPort);
|
||||||
|
if (m_OutPort && !m_Address.empty ())
|
||||||
|
m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet);
|
||||||
|
m_CurrentDestination->Start ();
|
||||||
|
SendReplyOK ("tunnel starting");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::StopCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
auto dest = m_Owner.FindDestination (m_Nickname);
|
||||||
|
if (dest)
|
||||||
|
{
|
||||||
|
dest->StopTunnels ();
|
||||||
|
SendReplyOK ("tunnel stopping");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendReplyError ("tunnel not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: setnick ", operand);
|
||||||
|
m_Nickname = operand;
|
||||||
|
std::string msg ("Nickname set to ");
|
||||||
|
msg += operand;
|
||||||
|
SendReplyOK (msg.c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: getnick ", operand);
|
||||||
|
m_CurrentDestination = m_Owner.FindDestination (operand);
|
||||||
|
if (m_CurrentDestination)
|
||||||
|
{
|
||||||
|
m_Keys = m_CurrentDestination->GetKeys ();
|
||||||
|
m_Nickname = operand;
|
||||||
|
std::string msg ("Nickname set to ");
|
||||||
|
msg += operand;
|
||||||
|
SendReplyOK (msg.c_str ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendReplyError ("tunnel not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: newkeys");
|
||||||
|
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys ();
|
||||||
|
SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: setkeys ", operand);
|
||||||
|
m_Keys.FromBase64 (operand);
|
||||||
|
SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: getkeys");
|
||||||
|
SendReplyOK (m_Keys.ToBase64 ().c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: getdest");
|
||||||
|
SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: outhost ", operand);
|
||||||
|
m_Address = operand;
|
||||||
|
SendReplyOK ("outhost set");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: outport ", operand);
|
||||||
|
m_OutPort = boost::lexical_cast<int>(operand);
|
||||||
|
SendReplyOK ("outbound port set");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: inhost ", operand);
|
||||||
|
m_Address = operand;
|
||||||
|
SendReplyOK ("inhost set");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::InportCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: inport ", operand);
|
||||||
|
m_InPort = boost::lexical_cast<int>(operand);
|
||||||
|
SendReplyOK ("inbound port set");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: quiet");
|
||||||
|
m_IsQuiet = true;
|
||||||
|
SendReplyOK ("quiet");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: lookup ", operand);
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
if (!context.GetAddressBook ().GetIdentHash (operand, ident) || !m_CurrentDestination)
|
||||||
|
{
|
||||||
|
SendReplyError ("Address Not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto localDestination = m_CurrentDestination->GetLocalDestination ();
|
||||||
|
auto leaseSet = localDestination->FindLeaseSet (ident);
|
||||||
|
if (leaseSet)
|
||||||
|
SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
localDestination->RequestDestination (ident,
|
||||||
|
[s](std::shared_ptr<i2p::data::LeaseSet> ls)
|
||||||
|
{
|
||||||
|
if (ls)
|
||||||
|
s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ());
|
||||||
|
else
|
||||||
|
s->SendReplyError ("LeaseSet Not found");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: clear");
|
||||||
|
m_Owner.DeleteDestination (m_Nickname);
|
||||||
|
SendReplyOK ("cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::ListCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: list");
|
||||||
|
auto& destinations = m_Owner.GetDestinations ();
|
||||||
|
for (auto it: destinations)
|
||||||
|
SendData (it.first.c_str ());
|
||||||
|
SendReplyOK ("Listing done");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "BOB: option ", operand);
|
||||||
|
const char * value = strchr (operand, '=');
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
*(const_cast<char *>(value)) = 0;
|
||||||
|
m_Options[operand] = value + 1;
|
||||||
|
*(const_cast<char *>(value)) = '=';
|
||||||
|
SendReplyOK ("option");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendReplyError ("malformed");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBCommandChannel::BOBCommandChannel (const std::string& address, int port):
|
||||||
|
m_IsRunning (false), m_Thread (nullptr),
|
||||||
|
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port))
|
||||||
|
{
|
||||||
|
// command -> handler
|
||||||
|
m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler;
|
||||||
|
m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBCommandChannel::~BOBCommandChannel ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
for (auto it: m_Destinations)
|
||||||
|
delete it.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandChannel::Start ()
|
||||||
|
{
|
||||||
|
Accept ();
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Thread = new std::thread (std::bind (&BOBCommandChannel::Run, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandChannel::Stop ()
|
||||||
|
{
|
||||||
|
m_IsRunning = false;
|
||||||
|
for (auto it: m_Destinations)
|
||||||
|
it.second->Stop ();
|
||||||
|
m_Acceptor.cancel ();
|
||||||
|
m_Service.stop ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandChannel::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Service.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "BOB: runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest)
|
||||||
|
{
|
||||||
|
m_Destinations[name] = dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandChannel::DeleteDestination (const std::string& name)
|
||||||
|
{
|
||||||
|
auto it = m_Destinations.find (name);
|
||||||
|
if (it != m_Destinations.end ())
|
||||||
|
{
|
||||||
|
it->second->Stop ();
|
||||||
|
delete it->second;
|
||||||
|
m_Destinations.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOBDestination * BOBCommandChannel::FindDestination (const std::string& name)
|
||||||
|
{
|
||||||
|
auto it = m_Destinations.find (name);
|
||||||
|
if (it != m_Destinations.end ())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandChannel::Accept ()
|
||||||
|
{
|
||||||
|
auto newSession = std::make_shared<BOBCommandSession> (*this);
|
||||||
|
m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this,
|
||||||
|
std::placeholders::_1, newSession));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Accept ();
|
||||||
|
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ());
|
||||||
|
session->SendVersion ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "BOB: accept error: ", ecode.message ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
237
BOB.h
Normal file
237
BOB.h
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#ifndef BOB_H__
|
||||||
|
#define BOB_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "I2PTunnel.h"
|
||||||
|
#include "I2PService.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const size_t BOB_COMMAND_BUFFER_SIZE = 1024;
|
||||||
|
const char BOB_COMMAND_ZAP[] = "zap";
|
||||||
|
const char BOB_COMMAND_QUIT[] = "quit";
|
||||||
|
const char BOB_COMMAND_START[] = "start";
|
||||||
|
const char BOB_COMMAND_STOP[] = "stop";
|
||||||
|
const char BOB_COMMAND_SETNICK[] = "setnick";
|
||||||
|
const char BOB_COMMAND_GETNICK[] = "getnick";
|
||||||
|
const char BOB_COMMAND_NEWKEYS[] = "newkeys";
|
||||||
|
const char BOB_COMMAND_GETKEYS[] = "getkeys";
|
||||||
|
const char BOB_COMMAND_SETKEYS[] = "setkeys";
|
||||||
|
const char BOB_COMMAND_GETDEST[] = "getdest";
|
||||||
|
const char BOB_COMMAND_OUTHOST[] = "outhost";
|
||||||
|
const char BOB_COMMAND_OUTPORT[] = "outport";
|
||||||
|
const char BOB_COMMAND_INHOST[] = "inhost";
|
||||||
|
const char BOB_COMMAND_INPORT[] = "inport";
|
||||||
|
const char BOB_COMMAND_QUIET[] = "quiet";
|
||||||
|
const char BOB_COMMAND_LOOKUP[] = "lookup";
|
||||||
|
const char BOB_COMMAND_CLEAR[] = "clear";
|
||||||
|
const char BOB_COMMAND_LIST[] = "list";
|
||||||
|
const char BOB_COMMAND_OPTION[] = "option";
|
||||||
|
|
||||||
|
const char BOB_VERSION[] = "BOB 00.00.10\nOK\n";
|
||||||
|
const char BOB_REPLY_OK[] = "OK %s\n";
|
||||||
|
const char BOB_REPLY_ERROR[] = "ERROR %s\n";
|
||||||
|
const char BOB_DATA[] = "NICKNAME %s\n";
|
||||||
|
|
||||||
|
class BOBI2PTunnel: public I2PService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
BOBI2PTunnel (std::shared_ptr<ClientDestination> localDestination):
|
||||||
|
I2PService (localDestination) {};
|
||||||
|
|
||||||
|
virtual void Start () {};
|
||||||
|
virtual void Stop () {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class BOBI2PInboundTunnel: public BOBI2PTunnel
|
||||||
|
{
|
||||||
|
struct AddressReceiver
|
||||||
|
{
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> socket;
|
||||||
|
char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address
|
||||||
|
uint8_t * data; // pointer to buffer
|
||||||
|
size_t dataLen, bufferOffset;
|
||||||
|
|
||||||
|
AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination);
|
||||||
|
~BOBI2PInboundTunnel ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Accept ();
|
||||||
|
void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver);
|
||||||
|
|
||||||
|
void ReceiveAddress (std::shared_ptr<AddressReceiver> receiver);
|
||||||
|
void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||||
|
std::shared_ptr<AddressReceiver> receiver);
|
||||||
|
|
||||||
|
void HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver);
|
||||||
|
|
||||||
|
void CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BOBI2POutboundTunnel: public BOBI2PTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
void SetQuiet () { m_IsQuiet = true; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Accept ();
|
||||||
|
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::endpoint m_Endpoint;
|
||||||
|
bool m_IsQuiet;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class BOBDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
BOBDestination (std::shared_ptr<ClientDestination> localDestination);
|
||||||
|
~BOBDestination ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
void StopTunnels ();
|
||||||
|
void CreateInboundTunnel (int port);
|
||||||
|
void CreateOutboundTunnel (const std::string& address, int port, bool quiet);
|
||||||
|
const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); };
|
||||||
|
std::shared_ptr<ClientDestination> GetLocalDestination () const { return m_LocalDestination; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> m_LocalDestination;
|
||||||
|
BOBI2POutboundTunnel * m_OutboundTunnel;
|
||||||
|
BOBI2PInboundTunnel * m_InboundTunnel;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BOBCommandChannel;
|
||||||
|
class BOBCommandSession: public std::enable_shared_from_this<BOBCommandSession>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
BOBCommandSession (BOBCommandChannel& owner);
|
||||||
|
~BOBCommandSession ();
|
||||||
|
void Terminate ();
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
|
||||||
|
void SendVersion ();
|
||||||
|
|
||||||
|
// command handlers
|
||||||
|
void ZapCommandHandler (const char * operand, size_t len);
|
||||||
|
void QuitCommandHandler (const char * operand, size_t len);
|
||||||
|
void StartCommandHandler (const char * operand, size_t len);
|
||||||
|
void StopCommandHandler (const char * operand, size_t len);
|
||||||
|
void SetNickCommandHandler (const char * operand, size_t len);
|
||||||
|
void GetNickCommandHandler (const char * operand, size_t len);
|
||||||
|
void NewkeysCommandHandler (const char * operand, size_t len);
|
||||||
|
void SetkeysCommandHandler (const char * operand, size_t len);
|
||||||
|
void GetkeysCommandHandler (const char * operand, size_t len);
|
||||||
|
void GetdestCommandHandler (const char * operand, size_t len);
|
||||||
|
void OuthostCommandHandler (const char * operand, size_t len);
|
||||||
|
void OutportCommandHandler (const char * operand, size_t len);
|
||||||
|
void InhostCommandHandler (const char * operand, size_t len);
|
||||||
|
void InportCommandHandler (const char * operand, size_t len);
|
||||||
|
void QuietCommandHandler (const char * operand, size_t len);
|
||||||
|
void LookupCommandHandler (const char * operand, size_t len);
|
||||||
|
void ClearCommandHandler (const char * operand, size_t len);
|
||||||
|
void ListCommandHandler (const char * operand, size_t len);
|
||||||
|
void OptionCommandHandler (const char * operand, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Receive ();
|
||||||
|
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
|
||||||
|
void Send (size_t len);
|
||||||
|
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void SendReplyOK (const char * msg);
|
||||||
|
void SendReplyError (const char * msg);
|
||||||
|
void SendData (const char * nickname);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
BOBCommandChannel& m_Owner;
|
||||||
|
boost::asio::ip::tcp::socket m_Socket;
|
||||||
|
char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1];
|
||||||
|
size_t m_ReceiveBufferOffset;
|
||||||
|
bool m_IsOpen, m_IsQuiet;
|
||||||
|
std::string m_Nickname, m_Address;
|
||||||
|
int m_InPort, m_OutPort;
|
||||||
|
i2p::data::PrivateKeys m_Keys;
|
||||||
|
std::map<std::string, std::string> m_Options;
|
||||||
|
BOBDestination * m_CurrentDestination;
|
||||||
|
};
|
||||||
|
typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len);
|
||||||
|
|
||||||
|
class BOBCommandChannel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
BOBCommandChannel (const std::string& address, int port);
|
||||||
|
~BOBCommandChannel ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
boost::asio::io_service& GetService () { return m_Service; };
|
||||||
|
void AddDestination (const std::string& name, BOBDestination * dest);
|
||||||
|
void DeleteDestination (const std::string& name);
|
||||||
|
BOBDestination * FindDestination (const std::string& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void Accept ();
|
||||||
|
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
boost::asio::io_service m_Service;
|
||||||
|
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||||
|
std::map<std::string, BOBDestination *> m_Destinations;
|
||||||
|
std::map<std::string, BOBCommandHandler> m_CommandHandlers;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; };
|
||||||
|
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
390
Base.cpp
Normal file
390
Base.cpp
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Base.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
static const char T32[32] = {
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||||
|
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||||
|
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', '2', '3', '4', '5', '6', '7',
|
||||||
|
};
|
||||||
|
|
||||||
|
const char * GetBase32SubstitutionTable ()
|
||||||
|
{
|
||||||
|
return T32;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iT64Build(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* BASE64 Substitution Table
|
||||||
|
* -------------------------
|
||||||
|
*
|
||||||
|
* Direct Substitution Table
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const char T64[64] = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||||
|
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7', '8', '9', '-', '~'
|
||||||
|
};
|
||||||
|
|
||||||
|
const char * GetBase64SubstitutionTable ()
|
||||||
|
{
|
||||||
|
return T64;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverse Substitution Table (built in run time)
|
||||||
|
*/
|
||||||
|
|
||||||
|
static char iT64[256];
|
||||||
|
static int isFirstTime = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Padding
|
||||||
|
*/
|
||||||
|
|
||||||
|
static char P64 = '=';
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ByteStreamToBase64
|
||||||
|
* ------------------
|
||||||
|
*
|
||||||
|
* Converts binary encoded data to BASE64 format.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t /* Number of bytes in the encoded buffer */
|
||||||
|
ByteStreamToBase64 (
|
||||||
|
const uint8_t * InBuffer, /* Input buffer, binary data */
|
||||||
|
size_t InCount, /* Number of bytes in the input buffer */
|
||||||
|
char * OutBuffer, /* output buffer */
|
||||||
|
size_t len /* length of output buffer */
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
unsigned char * ps;
|
||||||
|
unsigned char * pd;
|
||||||
|
unsigned char acc_1;
|
||||||
|
unsigned char acc_2;
|
||||||
|
int i;
|
||||||
|
int n;
|
||||||
|
int m;
|
||||||
|
size_t outCount;
|
||||||
|
|
||||||
|
ps = (unsigned char *)InBuffer;
|
||||||
|
n = InCount/3;
|
||||||
|
m = InCount%3;
|
||||||
|
if (!m)
|
||||||
|
outCount = 4*n;
|
||||||
|
else
|
||||||
|
outCount = 4*(n+1);
|
||||||
|
if (outCount > len) return 0;
|
||||||
|
pd = (unsigned char *)OutBuffer;
|
||||||
|
for ( i = 0; i<n; i++ ){
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 = (acc_1<<4)&0x30;
|
||||||
|
acc_1 >>= 2; /* base64 digit #1 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
acc_1 &= 0x0f;
|
||||||
|
acc_1 <<=2;
|
||||||
|
acc_2 = *ps++;
|
||||||
|
acc_1 |= acc_2>>6; /* base64 digit #3 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
acc_2 &= 0x3f; /* base64 digit #4 */
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
}
|
||||||
|
if ( m == 1 ){
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */
|
||||||
|
acc_1 >>= 2; /* base64 digit #1 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
*pd++ = P64;
|
||||||
|
*pd++ = P64;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if ( m == 2 ){
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 = (acc_1<<4)&0x3f;
|
||||||
|
acc_1 >>= 2; /* base64 digit #1 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
acc_1 &= 0x0f;
|
||||||
|
acc_1 <<=2; /* base64 digit #3 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
*pd++ = P64;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Base64ToByteStream
|
||||||
|
* ------------------
|
||||||
|
*
|
||||||
|
* Converts BASE64 encoded data to binary format. If input buffer is
|
||||||
|
* not properly padded, buffer of negative length is returned
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t /* Number of output bytes */
|
||||||
|
Base64ToByteStream (
|
||||||
|
const char * InBuffer, /* BASE64 encoded buffer */
|
||||||
|
size_t InCount, /* Number of input bytes */
|
||||||
|
uint8_t * OutBuffer, /* output buffer length */
|
||||||
|
size_t len /* length of output buffer */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
unsigned char * ps;
|
||||||
|
unsigned char * pd;
|
||||||
|
unsigned char acc_1;
|
||||||
|
unsigned char acc_2;
|
||||||
|
int i;
|
||||||
|
int n;
|
||||||
|
int m;
|
||||||
|
size_t outCount;
|
||||||
|
|
||||||
|
if (isFirstTime) iT64Build();
|
||||||
|
n = InCount/4;
|
||||||
|
m = InCount%4;
|
||||||
|
if (InCount && !m)
|
||||||
|
outCount = 3*n;
|
||||||
|
else {
|
||||||
|
outCount = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ps = (unsigned char *)(InBuffer + InCount - 1);
|
||||||
|
while ( *ps-- == P64 ) outCount--;
|
||||||
|
ps = (unsigned char *)InBuffer;
|
||||||
|
|
||||||
|
if (outCount > len) return -1;
|
||||||
|
pd = OutBuffer;
|
||||||
|
auto endOfOutBuffer = OutBuffer + outCount;
|
||||||
|
for ( i = 0; i < n; i++ ){
|
||||||
|
acc_1 = iT64[*ps++];
|
||||||
|
acc_2 = iT64[*ps++];
|
||||||
|
acc_1 <<= 2;
|
||||||
|
acc_1 |= acc_2>>4;
|
||||||
|
*pd++ = acc_1;
|
||||||
|
if (pd >= endOfOutBuffer) break;
|
||||||
|
|
||||||
|
acc_2 <<= 4;
|
||||||
|
acc_1 = iT64[*ps++];
|
||||||
|
acc_2 |= acc_1 >> 2;
|
||||||
|
*pd++ = acc_2;
|
||||||
|
if (pd >= endOfOutBuffer) break;
|
||||||
|
|
||||||
|
acc_2 = iT64[*ps++];
|
||||||
|
acc_2 |= acc_1 << 6;
|
||||||
|
*pd++ = acc_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Base64EncodingBufferSize (const size_t input_size)
|
||||||
|
{
|
||||||
|
auto d = div (input_size, 3);
|
||||||
|
if (d.rem) d.quot++;
|
||||||
|
return 4*d.quot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* iT64
|
||||||
|
* ----
|
||||||
|
* Reverse table builder. P64 character is replaced with 0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void iT64Build()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
isFirstTime = 0;
|
||||||
|
for ( i=0; i<256; i++ ) iT64[i] = -1;
|
||||||
|
for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i;
|
||||||
|
iT64[(int)P64] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen)
|
||||||
|
{
|
||||||
|
int tmp = 0, bits = 0;
|
||||||
|
size_t ret = 0;
|
||||||
|
for (size_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
char ch = inBuf[i];
|
||||||
|
if (ch >= '2' && ch <= '7') // digit
|
||||||
|
ch = (ch - '2') + 26; // 26 means a-z
|
||||||
|
else if (ch >= 'a' && ch <= 'z')
|
||||||
|
ch = ch - 'a'; // a = 0
|
||||||
|
else
|
||||||
|
return 0; // unexpected character
|
||||||
|
|
||||||
|
tmp |= ch;
|
||||||
|
bits += 5;
|
||||||
|
if (bits >= 8)
|
||||||
|
{
|
||||||
|
if (ret >= outLen) return ret;
|
||||||
|
outBuf[ret] = tmp >> (bits - 8);
|
||||||
|
bits -= 8;
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
tmp <<= 5;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen)
|
||||||
|
{
|
||||||
|
size_t ret = 0, pos = 1;
|
||||||
|
int bits = 8, tmp = inBuf[0];
|
||||||
|
while (ret < outLen && (bits > 0 || pos < len))
|
||||||
|
{
|
||||||
|
if (bits < 5)
|
||||||
|
{
|
||||||
|
if (pos < len)
|
||||||
|
{
|
||||||
|
tmp <<= 8;
|
||||||
|
tmp |= inBuf[pos] & 0xFF;
|
||||||
|
pos++;
|
||||||
|
bits += 8;
|
||||||
|
}
|
||||||
|
else // last byte
|
||||||
|
{
|
||||||
|
tmp <<= (5 - bits);
|
||||||
|
bits = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bits -= 5;
|
||||||
|
int ind = (tmp >> bits) & 0x1F;
|
||||||
|
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
GzipInflator::GzipInflator (): m_IsDirty (false)
|
||||||
|
{
|
||||||
|
memset (&m_Inflator, 0, sizeof (m_Inflator));
|
||||||
|
inflateInit2 (&m_Inflator, MAX_WBITS + 16); // gzip
|
||||||
|
}
|
||||||
|
|
||||||
|
GzipInflator::~GzipInflator ()
|
||||||
|
{
|
||||||
|
inflateEnd (&m_Inflator);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen)
|
||||||
|
{
|
||||||
|
if (m_IsDirty) inflateReset (&m_Inflator);
|
||||||
|
m_IsDirty = true;
|
||||||
|
m_Inflator.next_in = const_cast<uint8_t *>(in);
|
||||||
|
m_Inflator.avail_in = inLen;
|
||||||
|
m_Inflator.next_out = out;
|
||||||
|
m_Inflator.avail_out = outLen;
|
||||||
|
int err;
|
||||||
|
if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END)
|
||||||
|
return outLen - m_Inflator.avail_out;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Decompression error ", err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& s)
|
||||||
|
{
|
||||||
|
m_IsDirty = true;
|
||||||
|
uint8_t * out = new uint8_t[GZIP_CHUNK_SIZE];
|
||||||
|
m_Inflator.next_in = const_cast<uint8_t *>(in);
|
||||||
|
m_Inflator.avail_in = inLen;
|
||||||
|
int ret;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
m_Inflator.next_out = out;
|
||||||
|
m_Inflator.avail_out = GZIP_CHUNK_SIZE;
|
||||||
|
ret = inflate (&m_Inflator, Z_NO_FLUSH);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Decompression error ", ret);
|
||||||
|
inflateEnd (&m_Inflator);
|
||||||
|
s.setstate(std::ios_base::failbit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out);
|
||||||
|
}
|
||||||
|
while (!m_Inflator.avail_out); // more data to read
|
||||||
|
delete[] out;
|
||||||
|
return ret == Z_STREAM_END || ret < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GzipInflator::Inflate (std::istream& in, std::ostream& out)
|
||||||
|
{
|
||||||
|
uint8_t * buf = new uint8_t[GZIP_CHUNK_SIZE];
|
||||||
|
while (!in.eof ())
|
||||||
|
{
|
||||||
|
in.read ((char *)buf, GZIP_CHUNK_SIZE);
|
||||||
|
Inflate (buf, in.gcount (), out);
|
||||||
|
}
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
GzipDeflator::GzipDeflator (): m_IsDirty (false)
|
||||||
|
{
|
||||||
|
memset (&m_Deflator, 0, sizeof (m_Deflator));
|
||||||
|
deflateInit2 (&m_Deflator, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); // 15 + 16 sets gzip
|
||||||
|
}
|
||||||
|
|
||||||
|
GzipDeflator::~GzipDeflator ()
|
||||||
|
{
|
||||||
|
deflateEnd (&m_Deflator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GzipDeflator::SetCompressionLevel (int level)
|
||||||
|
{
|
||||||
|
deflateParams (&m_Deflator, level, Z_DEFAULT_STRATEGY);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GzipDeflator::Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen)
|
||||||
|
{
|
||||||
|
if (m_IsDirty) deflateReset (&m_Deflator);
|
||||||
|
m_IsDirty = true;
|
||||||
|
m_Deflator.next_in = const_cast<uint8_t *>(in);
|
||||||
|
m_Deflator.avail_in = inLen;
|
||||||
|
m_Deflator.next_out = out;
|
||||||
|
m_Deflator.avail_out = outLen;
|
||||||
|
int err;
|
||||||
|
if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END)
|
||||||
|
return outLen - m_Deflator.avail_out;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Compression error ", err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
134
Base.h
Normal file
134
Base.h
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#ifndef BASE_H__
|
||||||
|
#define BASE_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len);
|
||||||
|
size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len );
|
||||||
|
const char * GetBase32SubstitutionTable ();
|
||||||
|
const char * GetBase64SubstitutionTable ();
|
||||||
|
|
||||||
|
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);
|
||||||
|
size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes
|
||||||
|
*/
|
||||||
|
size_t Base64EncodingBufferSize(const size_t input_size);
|
||||||
|
|
||||||
|
template<int sz>
|
||||||
|
class Tag
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); };
|
||||||
|
Tag (const Tag<sz>& ) = default;
|
||||||
|
#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it
|
||||||
|
Tag (Tag<sz>&& ) = default;
|
||||||
|
#endif
|
||||||
|
Tag () = default;
|
||||||
|
|
||||||
|
Tag<sz>& operator= (const Tag<sz>& ) = default;
|
||||||
|
#ifndef _WIN32
|
||||||
|
Tag<sz>& operator= (Tag<sz>&& ) = default;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t * operator()() { return m_Buf; };
|
||||||
|
const uint8_t * operator()() const { return m_Buf; };
|
||||||
|
|
||||||
|
operator uint8_t * () { return m_Buf; };
|
||||||
|
operator const uint8_t * () const { return m_Buf; };
|
||||||
|
|
||||||
|
const uint64_t * GetLL () const { return ll; };
|
||||||
|
|
||||||
|
bool operator== (const Tag<sz>& other) const { return !memcmp (m_Buf, other.m_Buf, sz); };
|
||||||
|
bool operator< (const Tag<sz>& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; };
|
||||||
|
|
||||||
|
bool IsZero () const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < sz/8; i++)
|
||||||
|
if (ll[i]) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToBase64 () const
|
||||||
|
{
|
||||||
|
char str[sz*2];
|
||||||
|
int l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2);
|
||||||
|
str[l] = 0;
|
||||||
|
return std::string (str);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToBase32 () const
|
||||||
|
{
|
||||||
|
char str[sz*2];
|
||||||
|
int l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2);
|
||||||
|
str[l] = 0;
|
||||||
|
return std::string (str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FromBase32 (const std::string& s)
|
||||||
|
{
|
||||||
|
i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FromBase64 (const std::string& s)
|
||||||
|
{
|
||||||
|
i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
union // 8 bytes alignment
|
||||||
|
{
|
||||||
|
uint8_t m_Buf[sz];
|
||||||
|
uint64_t ll[sz/8];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t GZIP_CHUNK_SIZE = 16384;
|
||||||
|
class GzipInflator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
GzipInflator ();
|
||||||
|
~GzipInflator ();
|
||||||
|
|
||||||
|
size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen);
|
||||||
|
bool Inflate (const uint8_t * in, size_t inLen, std::ostream& s);
|
||||||
|
// return true when finshed or error, s failbit will be set in case of error
|
||||||
|
void Inflate (std::istream& in, std::ostream& out);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
z_stream m_Inflator;
|
||||||
|
bool m_IsDirty;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GzipDeflator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
GzipDeflator ();
|
||||||
|
~GzipDeflator ();
|
||||||
|
|
||||||
|
void SetCompressionLevel (int level);
|
||||||
|
size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
z_stream m_Deflator;
|
||||||
|
bool m_IsDirty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
195
CMakeLists.txt
195
CMakeLists.txt
@@ -1,195 +0,0 @@
|
|||||||
cmake_minimum_required ( VERSION 2.8.12 )
|
|
||||||
project ( "i2pd" )
|
|
||||||
|
|
||||||
# configurale options
|
|
||||||
option(WITH_AESNI "Use AES-NI instructions set" OFF)
|
|
||||||
option(WITH_HARDENING "Use hardening compiler flags" OFF)
|
|
||||||
option(WITH_LIBRARY "Build library" ON)
|
|
||||||
option(WITH_BINARY "Build binary" ON)
|
|
||||||
option(WITH_STATIC "Static build" OFF)
|
|
||||||
option(WITH_UPNP "Include support for UPnP client" OFF)
|
|
||||||
option(WITH_TESTS "Build unit tests" OFF)
|
|
||||||
option(WITH_BENCHMARK "Build benchmarking code" OFF)
|
|
||||||
option(WITH_OPTIMIZE "Optimization flags" OFF)
|
|
||||||
option(I2PD_DATA_PATH "The path to the i2pd data folder")
|
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_modules")
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
||||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
||||||
|
|
||||||
# Default build is Debug
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# compiler flags customization (by vendor)
|
|
||||||
if(NOT MSVC)
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch")
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic")
|
|
||||||
# TODO: The following is incompatible with static build and enabled hardening
|
|
||||||
# for OpenWRT.
|
|
||||||
# Multiple definitions of __stack_chk_fail (libssp & libc)
|
|
||||||
set(
|
|
||||||
CMAKE_CXX_FLAGS_MINSIZEREL
|
|
||||||
"${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections"
|
|
||||||
)
|
|
||||||
# -flto is added from above
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Check for c++11 support
|
|
||||||
include(CheckCXXCompilerFlag)
|
|
||||||
CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED)
|
|
||||||
CHECK_CXX_COMPILER_FLAG("-std=c++0x" CXX0X_SUPPORTED)
|
|
||||||
if(CXX11_SUPPORTED)
|
|
||||||
add_definitions("-std=c++11")
|
|
||||||
elseif(CXX0X_SUPPORTED) # gcc 4.6
|
|
||||||
add_definitions("-std=c++0x")
|
|
||||||
elseif(NOT MSVC)
|
|
||||||
message(SEND_ERROR "C++11 standard not supported by compiler. Version too old?")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
||||||
if(WITH_HARDENING)
|
|
||||||
add_definitions( "-D_FORTIFY_SOURCE=2" )
|
|
||||||
set(
|
|
||||||
CMAKE_CXX_FLAGS
|
|
||||||
"${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security"
|
|
||||||
)
|
|
||||||
set(
|
|
||||||
CMAKE_CXX_FLAGS
|
|
||||||
"${CMAKE_CXX_FLAGS} -fstack-protector-strong -fPIE --param ssp-buffer-size=4 -z relro -z now"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|
||||||
# TODO: Clang-specific flags
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Compiler flags customization (by system)
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
|
||||||
# "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8
|
|
||||||
add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" )
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if(WITH_UPNP)
|
|
||||||
add_definitions(-DUSE_UPNP)
|
|
||||||
if(NOT MSVC)
|
|
||||||
set(DL_LIB ${CMAKE_DL_LIBS})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WITH_AESNI)
|
|
||||||
add_definitions( "-maes -DAESNI")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WITH_OPTIMIZE AND (NOT MSVC))
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
# TODO: once CMake 3.1+ becomes mainstream, see e.g.
|
|
||||||
# http://stackoverflow.com/a/29871891/673826 use imported Threads::Threads
|
|
||||||
# instead
|
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
|
||||||
find_package (Threads REQUIRED)
|
|
||||||
if(THREADS_HAVE_PTHREAD_ARG) # compile time flag
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WITH_STATIC)
|
|
||||||
set(Boost_USE_STATIC_LIBS ON)
|
|
||||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
|
||||||
if(NOT WIN32)
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
|
||||||
if(${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
|
||||||
set(
|
|
||||||
CMAKE_THREAD_LIBS_INIT
|
|
||||||
"gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
elseif(NOT WIN32)
|
|
||||||
# TODO: Consider separate compilation for COMMON_SRC for library.
|
|
||||||
# No need in -fPIC overhead for binary if not interested in library
|
|
||||||
# HINT: revert c266cff CMakeLists.txt: compilation speed up
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
|
||||||
else() # Not a static build
|
|
||||||
add_definitions(-DBOOST_ALL_DYN_LINK)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(
|
|
||||||
Boost COMPONENTS
|
|
||||||
system filesystem regex program_options date_time thread chrono REQUIRED
|
|
||||||
)
|
|
||||||
if(NOT DEFINED Boost_INCLUDE_DIRS)
|
|
||||||
message(SEND_ERROR "Boost not found, or version below 1.46. Please download Boost!")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(CryptoPP REQUIRED)
|
|
||||||
if(NOT DEFINED CRYPTO++_INCLUDE_DIR)
|
|
||||||
message(SEND_ERROR "Could not find Crypto++. Please download and install it first!")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(MiniUPnPc)
|
|
||||||
if(NOT ${MINIUPNPC_FOUND})
|
|
||||||
set(WITH_UPNP OFF)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Load includes
|
|
||||||
include_directories(
|
|
||||||
${CMAKE_SOURCE_DIR} ${Boost_INCLUDE_DIRS} ${CRYPTO++_INCLUDE_DIR}
|
|
||||||
"core/"
|
|
||||||
)
|
|
||||||
|
|
||||||
if(I2PD_DATA_PATH)
|
|
||||||
set(I2PD_DATA_DIR ${I2PD_DATA_PATH})
|
|
||||||
# Using custom path, make sure the code knows about this
|
|
||||||
add_definitions(-DI2PD_CUSTOM_DATA_PATH="${I2PD_DATA_PATH}")
|
|
||||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
|
||||||
set(I2PD_DATA_DIR "$ENV{APPDATA}\\i2pd")
|
|
||||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
|
||||||
set(I2PD_DATA_DIR "$ENV{HOME}/Library/Application Support/i2pd")
|
|
||||||
else()
|
|
||||||
set(I2PD_DATA_DIR "$ENV{HOME}/.i2pd")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Show summary
|
|
||||||
message(STATUS "---------------------------------------")
|
|
||||||
message(STATUS "Build type : ${CMAKE_BUILD_TYPE}")
|
|
||||||
message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}")
|
|
||||||
message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}")
|
|
||||||
message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}")
|
|
||||||
message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}")
|
|
||||||
message(STATUS "I2PD data directory: ${I2PD_DATA_DIR}")
|
|
||||||
message(STATUS "Options:")
|
|
||||||
message(STATUS " AESNI : ${WITH_AESNI}")
|
|
||||||
message(STATUS " HARDENING : ${WITH_HARDENING}")
|
|
||||||
message(STATUS " LIBRARY : ${WITH_LIBRARY}")
|
|
||||||
message(STATUS " BINARY : ${WITH_BINARY}")
|
|
||||||
message(STATUS " STATIC BUILD : ${WITH_STATIC}")
|
|
||||||
message(STATUS " UPnP : ${WITH_UPNP}")
|
|
||||||
message(STATUS " TESTS : ${WITH_TESTS}")
|
|
||||||
message(STATUS " BENCHMARKING : ${WITH_BENCHMARK}")
|
|
||||||
message(STATUS " OPTIMIZATION : ${WITH_OPTIMIZE}")
|
|
||||||
message(STATUS "---------------------------------------")
|
|
||||||
|
|
||||||
# Handle paths nicely
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
|
|
||||||
set(CORE_NAME "${PROJECT_NAME}-core")
|
|
||||||
set(CLIENT_NAME "${PROJECT_NAME}-client")
|
|
||||||
set(TESTS_NAME "${PROJECT_NAME}-tests")
|
|
||||||
set(BENCHMARK_NAME "${PROJECT_NAME}-benchmark")
|
|
||||||
add_subdirectory(core)
|
|
||||||
add_subdirectory(client)
|
|
||||||
add_subdirectory(tests)
|
|
||||||
add_subdirectory(benchmark)
|
|
||||||
|
|
||||||
if(WITH_BINARY)
|
|
||||||
file(MAKE_DIRECTORY "${I2PD_DATA_DIR}/webui")
|
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/webui" DESTINATION "${I2PD_DATA_DIR}")
|
|
||||||
endif()
|
|
||||||
417
ClientContext.cpp
Normal file
417
ClientContext.cpp
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
|
#include "Config.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
ClientContext context;
|
||||||
|
|
||||||
|
ClientContext::ClientContext (): m_SharedLocalDestination (nullptr),
|
||||||
|
m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr),
|
||||||
|
m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientContext::~ClientContext ()
|
||||||
|
{
|
||||||
|
delete m_HttpProxy;
|
||||||
|
delete m_SocksProxy;
|
||||||
|
delete m_SamBridge;
|
||||||
|
delete m_BOBCommandChannel;
|
||||||
|
delete m_I2CPServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientContext::Start ()
|
||||||
|
{
|
||||||
|
if (!m_SharedLocalDestination)
|
||||||
|
{
|
||||||
|
m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, DSA
|
||||||
|
m_Destinations[m_SharedLocalDestination->GetIdentity ()->GetIdentHash ()] = m_SharedLocalDestination;
|
||||||
|
m_SharedLocalDestination->Start ();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_AddressBook.Start ();
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> localDestination;
|
||||||
|
bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy);
|
||||||
|
if (httproxy) {
|
||||||
|
std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys);
|
||||||
|
std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr);
|
||||||
|
uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort);
|
||||||
|
LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort);
|
||||||
|
if (httpProxyKeys.length () > 0)
|
||||||
|
{
|
||||||
|
i2p::data::PrivateKeys keys;
|
||||||
|
LoadPrivateKeys (keys, httpProxyKeys);
|
||||||
|
localDestination = CreateNewLocalDestination (keys, false);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination);
|
||||||
|
m_HttpProxy->Start();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy);
|
||||||
|
if (socksproxy) {
|
||||||
|
std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys);
|
||||||
|
std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr);
|
||||||
|
uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort);
|
||||||
|
std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr);
|
||||||
|
uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort);
|
||||||
|
LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort);
|
||||||
|
if (socksProxyKeys.length () > 0)
|
||||||
|
{
|
||||||
|
i2p::data::PrivateKeys keys;
|
||||||
|
LoadPrivateKeys (keys, socksProxyKeys);
|
||||||
|
localDestination = CreateNewLocalDestination (keys, false);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, socksOutProxyAddr, socksOutProxyPort, localDestination);
|
||||||
|
m_SocksProxy->Start();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// I2P tunnels
|
||||||
|
ReadTunnels ();
|
||||||
|
|
||||||
|
// SAM
|
||||||
|
bool sam; i2p::config::GetOption("sam.enabled", sam);
|
||||||
|
if (sam) {
|
||||||
|
std::string samAddr; i2p::config::GetOption("sam.address", samAddr);
|
||||||
|
uint16_t samPort; i2p::config::GetOption("sam.port", samPort);
|
||||||
|
LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort);
|
||||||
|
try {
|
||||||
|
m_SamBridge = new SAMBridge (samAddr, samPort);
|
||||||
|
m_SamBridge->Start ();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOB
|
||||||
|
bool bob; i2p::config::GetOption("bob.enabled", bob);
|
||||||
|
if (bob) {
|
||||||
|
std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr);
|
||||||
|
uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort);
|
||||||
|
LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort);
|
||||||
|
try {
|
||||||
|
m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort);
|
||||||
|
m_BOBCommandChannel->Start ();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_AddressBook.StartResolvers ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientContext::Stop ()
|
||||||
|
{
|
||||||
|
if (m_HttpProxy)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Clients: stopping HTTP Proxy");
|
||||||
|
m_HttpProxy->Stop();
|
||||||
|
delete m_HttpProxy;
|
||||||
|
m_HttpProxy = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_SocksProxy)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy");
|
||||||
|
m_SocksProxy->Stop();
|
||||||
|
delete m_SocksProxy;
|
||||||
|
m_SocksProxy = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& it: m_ClientTunnels)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Clients: stopping I2P client tunnel on port ", it.first);
|
||||||
|
it.second->Stop ();
|
||||||
|
}
|
||||||
|
m_ClientTunnels.clear ();
|
||||||
|
|
||||||
|
for (auto& it: m_ServerTunnels)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Clients: stopping I2P server tunnel");
|
||||||
|
it.second->Stop ();
|
||||||
|
}
|
||||||
|
m_ServerTunnels.clear ();
|
||||||
|
|
||||||
|
if (m_SamBridge)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Clients: stopping SAM bridge");
|
||||||
|
m_SamBridge->Stop ();
|
||||||
|
delete m_SamBridge;
|
||||||
|
m_SamBridge = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_BOBCommandChannel)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Clients: stopping BOB command channel");
|
||||||
|
m_BOBCommandChannel->Stop ();
|
||||||
|
delete m_BOBCommandChannel;
|
||||||
|
m_BOBCommandChannel = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint(eLogInfo, "Clients: stopping AddressBook");
|
||||||
|
m_AddressBook.Stop ();
|
||||||
|
for (auto it: m_Destinations)
|
||||||
|
it.second->Stop ();
|
||||||
|
m_Destinations.clear ();
|
||||||
|
m_SharedLocalDestination = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientContext::ReloadConfig ()
|
||||||
|
{
|
||||||
|
ReadTunnels (); // TODO: it reads new tunnels only, should be implemented better
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType)
|
||||||
|
{
|
||||||
|
std::string fullPath = i2p::fs::DataDirPath (filename);
|
||||||
|
std::ifstream s(fullPath, std::ifstream::binary);
|
||||||
|
if (s.is_open ())
|
||||||
|
{
|
||||||
|
s.seekg (0, std::ios::end);
|
||||||
|
size_t len = s.tellg();
|
||||||
|
s.seekg (0, std::ios::beg);
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
s.read ((char *)buf, len);
|
||||||
|
keys.FromBuffer (buf, len);
|
||||||
|
delete[] buf;
|
||||||
|
LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one with signature type ", sigType);
|
||||||
|
keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
|
||||||
|
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
|
||||||
|
size_t len = keys.GetFullLen ();
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
len = keys.ToBuffer (buf, len);
|
||||||
|
f.write ((char *)buf, len);
|
||||||
|
delete[] buf;
|
||||||
|
|
||||||
|
LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType,
|
||||||
|
const std::map<std::string, std::string> * params)
|
||||||
|
{
|
||||||
|
i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
|
||||||
|
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
|
||||||
|
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||||
|
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
|
||||||
|
localDestination->Start ();
|
||||||
|
return localDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientContext::DeleteLocalDestination (std::shared_ptr<ClientDestination> destination)
|
||||||
|
{
|
||||||
|
if (!destination) return;
|
||||||
|
auto it = m_Destinations.find (destination->GetIdentHash ());
|
||||||
|
if (it != m_Destinations.end ())
|
||||||
|
{
|
||||||
|
auto d = it->second;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||||
|
m_Destinations.erase (it);
|
||||||
|
}
|
||||||
|
d->Stop ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
|
||||||
|
const std::map<std::string, std::string> * params)
|
||||||
|
{
|
||||||
|
auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ());
|
||||||
|
if (it != m_Destinations.end ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists");
|
||||||
|
if (!it->second->IsRunning ())
|
||||||
|
{
|
||||||
|
it->second->Start ();
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
|
||||||
|
std::unique_lock<std::mutex> l(m_DestinationsMutex);
|
||||||
|
m_Destinations[keys.GetPublic ()->GetIdentHash ()] = localDestination;
|
||||||
|
localDestination->Start ();
|
||||||
|
return localDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const
|
||||||
|
{
|
||||||
|
auto it = m_Destinations.find (destination);
|
||||||
|
if (it != m_Destinations.end ())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Section, typename Type>
|
||||||
|
std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const
|
||||||
|
{
|
||||||
|
return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Section>
|
||||||
|
void ClientContext::ReadI2CPOptions (const Section& section, std::map<std::string, std::string>& options) const
|
||||||
|
{
|
||||||
|
options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH);
|
||||||
|
options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH);
|
||||||
|
options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY);
|
||||||
|
options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY);
|
||||||
|
options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientContext::ReadTunnels ()
|
||||||
|
{
|
||||||
|
boost::property_tree::ptree pt;
|
||||||
|
std::string tunConf; i2p::config::GetOption("tunconf", tunConf);
|
||||||
|
if (tunConf == "") {
|
||||||
|
// TODO: cleanup this in 2.8.0
|
||||||
|
tunConf = i2p::fs::DataDirPath ("tunnels.cfg");
|
||||||
|
if (i2p::fs::Exists(tunConf)) {
|
||||||
|
LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf);
|
||||||
|
} else {
|
||||||
|
tunConf = i2p::fs::DataDirPath ("tunnels.conf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogPrint(eLogDebug, "FS: tunnels config file: ", tunConf);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::property_tree::read_ini (tunConf, pt);
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numClientTunnels = 0, numServerTunnels = 0;
|
||||||
|
for (auto& section: pt)
|
||||||
|
{
|
||||||
|
std::string name = section.first;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string type = section.second.get<std::string> (I2P_TUNNELS_SECTION_TYPE);
|
||||||
|
if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT)
|
||||||
|
{
|
||||||
|
// mandatory params
|
||||||
|
std::string dest = section.second.get<std::string> (I2P_CLIENT_TUNNEL_DESTINATION);
|
||||||
|
int port = section.second.get<int> (I2P_CLIENT_TUNNEL_PORT);
|
||||||
|
// optional params
|
||||||
|
std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "");
|
||||||
|
std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1");
|
||||||
|
int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0);
|
||||||
|
i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
|
||||||
|
// I2CP
|
||||||
|
std::map<std::string, std::string> options;
|
||||||
|
ReadI2CPOptions (section, options);
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> localDestination = nullptr;
|
||||||
|
if (keys.length () > 0)
|
||||||
|
{
|
||||||
|
i2p::data::PrivateKeys k;
|
||||||
|
LoadPrivateKeys (k, keys, sigType);
|
||||||
|
localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ());
|
||||||
|
if (!localDestination)
|
||||||
|
localDestination = CreateNewLocalDestination (k, false, &options);
|
||||||
|
}
|
||||||
|
auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort);
|
||||||
|
if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (),
|
||||||
|
std::unique_ptr<I2PClientTunnel>(clientTunnel))).second)
|
||||||
|
{
|
||||||
|
clientTunnel->Start ();
|
||||||
|
numClientTunnels++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists");
|
||||||
|
}
|
||||||
|
else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC)
|
||||||
|
{
|
||||||
|
// mandatory params
|
||||||
|
std::string host = section.second.get<std::string> (I2P_SERVER_TUNNEL_HOST);
|
||||||
|
int port = section.second.get<int> (I2P_SERVER_TUNNEL_PORT);
|
||||||
|
std::string keys = section.second.get<std::string> (I2P_SERVER_TUNNEL_KEYS);
|
||||||
|
// optional params
|
||||||
|
int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0);
|
||||||
|
std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, "");
|
||||||
|
std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, "");
|
||||||
|
std::string webircpass = section.second.get<std::string> (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, "");
|
||||||
|
bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true);
|
||||||
|
i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
|
||||||
|
// I2CP
|
||||||
|
std::map<std::string, std::string> options;
|
||||||
|
ReadI2CPOptions (section, options);
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> localDestination = nullptr;
|
||||||
|
i2p::data::PrivateKeys k;
|
||||||
|
LoadPrivateKeys (k, keys, sigType);
|
||||||
|
localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ());
|
||||||
|
if (!localDestination)
|
||||||
|
localDestination = CreateNewLocalDestination (k, true, &options);
|
||||||
|
|
||||||
|
I2PServerTunnel * serverTunnel;
|
||||||
|
if (type == I2P_TUNNELS_SECTION_TYPE_HTTP)
|
||||||
|
serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip);
|
||||||
|
else if (type == I2P_TUNNELS_SECTION_TYPE_IRC)
|
||||||
|
serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, webircpass, inPort, gzip);
|
||||||
|
else // regular server tunnel by default
|
||||||
|
serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip);
|
||||||
|
|
||||||
|
if (accessList.length () > 0)
|
||||||
|
{
|
||||||
|
std::set<i2p::data::IdentHash> idents;
|
||||||
|
size_t pos = 0, comma;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
comma = accessList.find (',', pos);
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos));
|
||||||
|
idents.insert (ident);
|
||||||
|
pos = comma + 1;
|
||||||
|
}
|
||||||
|
while (comma != std::string::npos);
|
||||||
|
serverTunnel->SetAccessList (idents);
|
||||||
|
}
|
||||||
|
if (m_ServerTunnels.insert (std::make_pair (
|
||||||
|
std::make_pair (localDestination->GetIdentHash (), inPort),
|
||||||
|
std::unique_ptr<I2PServerTunnel>(serverTunnel))).second)
|
||||||
|
{
|
||||||
|
serverTunnel->Start ();
|
||||||
|
numServerTunnels++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists");
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created");
|
||||||
|
LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
ClientContext.h
Normal file
101
ClientContext.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#ifndef CLIENT_CONTEXT_H__
|
||||||
|
#define CLIENT_CONTEXT_H__
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "HTTPProxy.h"
|
||||||
|
#include "SOCKS.h"
|
||||||
|
#include "I2PTunnel.h"
|
||||||
|
#include "SAM.h"
|
||||||
|
#include "BOB.h"
|
||||||
|
#include "I2CP.h"
|
||||||
|
#include "AddressBook.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const char I2P_TUNNELS_SECTION_TYPE[] = "type";
|
||||||
|
const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client";
|
||||||
|
const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server";
|
||||||
|
const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http";
|
||||||
|
const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc";
|
||||||
|
const char I2P_CLIENT_TUNNEL_PORT[] = "port";
|
||||||
|
const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address";
|
||||||
|
const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination";
|
||||||
|
const char I2P_CLIENT_TUNNEL_KEYS[] = "keys";
|
||||||
|
const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype";
|
||||||
|
const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport";
|
||||||
|
const char I2P_SERVER_TUNNEL_HOST[] = "host";
|
||||||
|
const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride";
|
||||||
|
const char I2P_SERVER_TUNNEL_PORT[] = "port";
|
||||||
|
const char I2P_SERVER_TUNNEL_KEYS[] = "keys";
|
||||||
|
const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype";
|
||||||
|
const char I2P_SERVER_TUNNEL_INPORT[] = "inport";
|
||||||
|
const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist";
|
||||||
|
const char I2P_SERVER_TUNNEL_GZIP[] = "gzip";
|
||||||
|
const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword";
|
||||||
|
|
||||||
|
class ClientContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ClientContext ();
|
||||||
|
~ClientContext ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
void ReloadConfig ();
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> GetSharedLocalDestination () const { return m_SharedLocalDestination; };
|
||||||
|
std::shared_ptr<ClientDestination> CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1,
|
||||||
|
const std::map<std::string, std::string> * params = nullptr); // transient
|
||||||
|
std::shared_ptr<ClientDestination> CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true,
|
||||||
|
const std::map<std::string, std::string> * params = nullptr);
|
||||||
|
void DeleteLocalDestination (std::shared_ptr<ClientDestination> destination);
|
||||||
|
std::shared_ptr<ClientDestination> FindLocalDestination (const i2p::data::IdentHash& destination) const;
|
||||||
|
void LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
|
||||||
|
|
||||||
|
AddressBook& GetAddressBook () { return m_AddressBook; };
|
||||||
|
const SAMBridge * GetSAMBridge () const { return m_SamBridge; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void ReadTunnels ();
|
||||||
|
template<typename Section, typename Type>
|
||||||
|
std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const;
|
||||||
|
template<typename Section>
|
||||||
|
void ReadI2CPOptions (const Section& section, std::map<std::string, std::string>& options) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::mutex m_DestinationsMutex;
|
||||||
|
std::map<i2p::data::IdentHash, std::shared_ptr<ClientDestination> > m_Destinations;
|
||||||
|
std::shared_ptr<ClientDestination> m_SharedLocalDestination;
|
||||||
|
|
||||||
|
AddressBook m_AddressBook;
|
||||||
|
|
||||||
|
i2p::proxy::HTTPProxy * m_HttpProxy;
|
||||||
|
i2p::proxy::SOCKSProxy * m_SocksProxy;
|
||||||
|
std::map<boost::asio::ip::tcp::endpoint, std::unique_ptr<I2PClientTunnel> > m_ClientTunnels; // local endpoint->tunnel
|
||||||
|
std::map<std::pair<i2p::data::IdentHash, int>, std::unique_ptr<I2PServerTunnel> > m_ServerTunnels; // <destination,port>->tunnel
|
||||||
|
SAMBridge * m_SamBridge;
|
||||||
|
BOBCommandChannel * m_BOBCommandChannel;
|
||||||
|
I2CPServer * m_I2CPServer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// for HTTP
|
||||||
|
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
|
||||||
|
const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; };
|
||||||
|
const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; };
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ClientContext context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
268
Config.cpp
Normal file
268
Config.cpp
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016, The PurpleI2P Project
|
||||||
|
*
|
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3
|
||||||
|
*
|
||||||
|
* See full license text in LICENSE file at top of project tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <boost/program_options/cmdline.hpp>
|
||||||
|
#include <boost/program_options/options_description.hpp>
|
||||||
|
#include <boost/program_options/parsers.hpp>
|
||||||
|
#include <boost/program_options/variables_map.hpp>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
using namespace boost::program_options;
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace config {
|
||||||
|
options_description m_OptionsDesc;
|
||||||
|
variables_map m_Options;
|
||||||
|
|
||||||
|
/* list of renamed options */
|
||||||
|
std::map<std::string, std::string> remapped_options = {
|
||||||
|
{ "tunnelscfg", "tunconf" },
|
||||||
|
{ "v6", "ipv6" },
|
||||||
|
{ "httpaddress", "http.address" },
|
||||||
|
{ "httpport", "http.port" },
|
||||||
|
{ "httpproxyaddress", "httpproxy.address" },
|
||||||
|
{ "httpproxyport", "httpproxy.port" },
|
||||||
|
{ "socksproxyaddress", "socksproxy.address" },
|
||||||
|
{ "socksproxyport", "socksproxy.port" },
|
||||||
|
{ "samaddress", "sam.address" },
|
||||||
|
{ "samport", "sam.port" },
|
||||||
|
{ "bobaddress", "bob.address" },
|
||||||
|
{ "bobport", "bob.port" },
|
||||||
|
{ "i2pcontroladdress", "i2pcontrol.address" },
|
||||||
|
{ "i2pcontroladdress", "i2pcontrol.port" },
|
||||||
|
{ "proxykeys", "httpproxy.keys" },
|
||||||
|
};
|
||||||
|
/* list of options, that loose their argument and become simple switch */
|
||||||
|
std::set<std::string> boolean_options = {
|
||||||
|
"daemon", "floodfill", "notransit", "service", "ipv6"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* this function is a solid piece of shit, remove it after 2.6.0 */
|
||||||
|
std::pair<std::string, std::string> old_syntax_parser(const std::string& s) {
|
||||||
|
std::string name = "";
|
||||||
|
std::string value = "";
|
||||||
|
std::size_t pos = 0;
|
||||||
|
/* shortcuts -- -h */
|
||||||
|
if (s.length() == 2 && s.at(0) == '-' && s.at(1) != '-')
|
||||||
|
return make_pair(s.substr(1), "");
|
||||||
|
/* old-style -- -log, /log, etc */
|
||||||
|
if (s.at(0) == '/' || (s.at(0) == '-' && s.at(1) != '-')) {
|
||||||
|
if ((pos = s.find_first_of("= ")) != std::string::npos) {
|
||||||
|
name = s.substr(1, pos - 1);
|
||||||
|
value = s.substr(pos + 1);
|
||||||
|
} else {
|
||||||
|
name = s.substr(1, pos);
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (boolean_options.count(name) > 0 && value != "")
|
||||||
|
std::cerr << "args: don't give an argument to switch option: " << s << std::endl;
|
||||||
|
if (m_OptionsDesc.find_nothrow(name, false)) {
|
||||||
|
std::cerr << "args: option " << s << " style is DEPRECATED, use --" << name << " instead" << std::endl;
|
||||||
|
return std::make_pair(name, value);
|
||||||
|
}
|
||||||
|
if (remapped_options.count(name) > 0) {
|
||||||
|
name = remapped_options[name];
|
||||||
|
std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl;
|
||||||
|
return std::make_pair(name, value);
|
||||||
|
} /* else */
|
||||||
|
}
|
||||||
|
/* long options -- --help */
|
||||||
|
if (s.substr(0, 2) == "--") {
|
||||||
|
if ((pos = s.find_first_of("= ")) != std::string::npos) {
|
||||||
|
name = s.substr(2, pos - 2);
|
||||||
|
value = s.substr(pos + 1);
|
||||||
|
} else {
|
||||||
|
name = s.substr(2, pos);
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (boolean_options.count(name) > 0 && value != "") {
|
||||||
|
std::cerr << "args: don't give an argument to switch option: " << s << std::endl;
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (m_OptionsDesc.find_nothrow(name, false))
|
||||||
|
return std::make_pair(name, value);
|
||||||
|
if (remapped_options.count(name) > 0) {
|
||||||
|
name = remapped_options[name];
|
||||||
|
std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl;
|
||||||
|
return std::make_pair(name, value);
|
||||||
|
} /* else */
|
||||||
|
}
|
||||||
|
std::cerr << "args: unknown option -- " << s << std::endl;
|
||||||
|
return std::make_pair("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init() {
|
||||||
|
options_description general("General options");
|
||||||
|
general.add_options()
|
||||||
|
("help", "Show this message")
|
||||||
|
("conf", value<std::string>()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)")
|
||||||
|
("tunconf", value<std::string>()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)")
|
||||||
|
("pidfile", value<std::string>()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)")
|
||||||
|
("log", value<std::string>()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)")
|
||||||
|
("logfile", value<std::string>()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)")
|
||||||
|
("loglevel", value<std::string>()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)")
|
||||||
|
("family", value<std::string>()->default_value(""), "Specify a family, router belongs to")
|
||||||
|
("datadir", value<std::string>()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)")
|
||||||
|
("host", value<std::string>()->default_value("0.0.0.0"), "External IP")
|
||||||
|
("port", value<uint16_t>()->default_value(0), "Port to listen for incoming connections (default: auto)")
|
||||||
|
("ipv4", value<bool>()->zero_tokens()->default_value(true), "Enable communication through ipv4")
|
||||||
|
("ipv6", value<bool>()->zero_tokens()->default_value(false), "Enable communication through ipv6")
|
||||||
|
("daemon", value<bool>()->zero_tokens()->default_value(false), "Router will go to background after start")
|
||||||
|
("service", value<bool>()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'")
|
||||||
|
("notransit", value<bool>()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup")
|
||||||
|
("floodfill", value<bool>()->zero_tokens()->default_value(false), "Router will be floodfill")
|
||||||
|
("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)")
|
||||||
|
#ifdef _WIN32
|
||||||
|
("svcctl", value<std::string>()->default_value(""), "Windows service management ('install' or 'remove')")
|
||||||
|
("insomnia", value<bool>()->zero_tokens()->default_value(false), "Prevent system from sleeping")
|
||||||
|
("close", value<std::string>()->default_value("ask"), "Action on close: minimize, exit, ask") // TODO: add custom validator or something
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description limits("Limits options");
|
||||||
|
limits.add_options()
|
||||||
|
("limits.transittunnels", value<uint16_t>()->default_value(2500), "Maximum active transit sessions (default:2500)")
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description httpserver("HTTP Server options");
|
||||||
|
httpserver.add_options()
|
||||||
|
("http.enabled", value<bool>()->default_value(true), "Enable or disable webconsole")
|
||||||
|
("http.address", value<std::string>()->default_value("127.0.0.1"), "Webconsole listen address")
|
||||||
|
("http.port", value<uint16_t>()->default_value(7070), "Webconsole listen port")
|
||||||
|
("http.auth", value<bool>()->default_value(false), "Enable Basic HTTP auth for webconsole")
|
||||||
|
("http.user", value<std::string>()->default_value("i2pd"), "Username for basic auth")
|
||||||
|
("http.pass", value<std::string>()->default_value(""), "Password for basic auth (default: random, see logs)")
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description httpproxy("HTTP Proxy options");
|
||||||
|
httpproxy.add_options()
|
||||||
|
("httpproxy.enabled", value<bool>()->default_value(true), "Enable or disable HTTP Proxy")
|
||||||
|
("httpproxy.address", value<std::string>()->default_value("127.0.0.1"), "HTTP Proxy listen address")
|
||||||
|
("httpproxy.port", value<uint16_t>()->default_value(4444), "HTTP Proxy listen port")
|
||||||
|
("httpproxy.keys", value<std::string>()->default_value(""), "File to persist HTTP Proxy keys")
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description socksproxy("SOCKS Proxy options");
|
||||||
|
socksproxy.add_options()
|
||||||
|
("socksproxy.enabled", value<bool>()->default_value(true), "Enable or disable SOCKS Proxy")
|
||||||
|
("socksproxy.address", value<std::string>()->default_value("127.0.0.1"), "SOCKS Proxy listen address")
|
||||||
|
("socksproxy.port", value<uint16_t>()->default_value(4447), "SOCKS Proxy listen port")
|
||||||
|
("socksproxy.keys", value<std::string>()->default_value(""), "File to persist SOCKS Proxy keys")
|
||||||
|
("socksproxy.outproxy", value<std::string>()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy")
|
||||||
|
("socksproxy.outproxyport", value<uint16_t>()->default_value(9050), "Upstream outproxy port for SOCKS Proxy")
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description sam("SAM bridge options");
|
||||||
|
sam.add_options()
|
||||||
|
("sam.enabled", value<bool>()->default_value(false), "Enable or disable SAM Application bridge")
|
||||||
|
("sam.address", value<std::string>()->default_value("127.0.0.1"), "SAM listen address")
|
||||||
|
("sam.port", value<uint16_t>()->default_value(7656), "SAM listen port")
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description bob("BOB options");
|
||||||
|
bob.add_options()
|
||||||
|
("bob.enabled", value<bool>()->default_value(false), "Enable or disable BOB command channel")
|
||||||
|
("bob.address", value<std::string>()->default_value("127.0.0.1"), "BOB listen address")
|
||||||
|
("bob.port", value<uint16_t>()->default_value(2827), "BOB listen port")
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description i2pcontrol("I2PControl options");
|
||||||
|
i2pcontrol.add_options()
|
||||||
|
("i2pcontrol.enabled", value<bool>()->default_value(false), "Enable or disable I2P Control Protocol")
|
||||||
|
("i2pcontrol.address", value<std::string>()->default_value("127.0.0.1"), "I2PCP listen address")
|
||||||
|
("i2pcontrol.port", value<uint16_t>()->default_value(7650), "I2PCP listen port")
|
||||||
|
("i2pcontrol.password", value<std::string>()->default_value("itoopie"), "I2PCP access password")
|
||||||
|
("i2pcontrol.cert", value<std::string>()->default_value("i2pcontrol.crt.pem"), "I2PCP connection cerificate")
|
||||||
|
("i2pcontrol.key", value<std::string>()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key")
|
||||||
|
;
|
||||||
|
|
||||||
|
options_description precomputation("Precomputation options");
|
||||||
|
precomputation.add_options()
|
||||||
|
("precomputation.elgamal",
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
value<bool>()->default_value(false),
|
||||||
|
#else
|
||||||
|
value<bool>()->default_value(true),
|
||||||
|
#endif
|
||||||
|
"Enable or disable elgamal precomputation table")
|
||||||
|
;
|
||||||
|
|
||||||
|
m_OptionsDesc
|
||||||
|
.add(general)
|
||||||
|
.add(limits)
|
||||||
|
.add(httpserver)
|
||||||
|
.add(httpproxy)
|
||||||
|
.add(socksproxy)
|
||||||
|
.add(sam)
|
||||||
|
.add(bob)
|
||||||
|
.add(i2pcontrol)
|
||||||
|
.add(precomputation)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseCmdline(int argc, char* argv[]) {
|
||||||
|
try {
|
||||||
|
auto style = boost::program_options::command_line_style::unix_style
|
||||||
|
| boost::program_options::command_line_style::allow_long_disguise;
|
||||||
|
style &= ~ boost::program_options::command_line_style::allow_guessing;
|
||||||
|
store(parse_command_line(argc, argv, m_OptionsDesc, style, old_syntax_parser), m_Options);
|
||||||
|
} catch (boost::program_options::error& e) {
|
||||||
|
std::cerr << "args: " << e.what() << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Options.count("help") || m_Options.count("h")) {
|
||||||
|
std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl;
|
||||||
|
std::cout << m_OptionsDesc;
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseConfig(const std::string& path) {
|
||||||
|
if (path == "") return;
|
||||||
|
|
||||||
|
std::ifstream config(path, std::ios::in);
|
||||||
|
|
||||||
|
if (!config.is_open())
|
||||||
|
{
|
||||||
|
std::cerr << "missing/unreadable config file: " << path << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
store(boost::program_options::parse_config_file(config, m_OptionsDesc), m_Options);
|
||||||
|
}
|
||||||
|
catch (boost::program_options::error& e)
|
||||||
|
{
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Finalize() {
|
||||||
|
notify(m_Options);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDefault(const char *name) {
|
||||||
|
if (!m_Options.count(name))
|
||||||
|
throw "try to check non-existent option";
|
||||||
|
|
||||||
|
if (m_Options[name].defaulted())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace config
|
||||||
|
} // namespace i2p
|
||||||
107
Config.h
Normal file
107
Config.h
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <boost/program_options/options_description.hpp>
|
||||||
|
#include <boost/program_options/variables_map.hpp>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions to parse and store i2pd parameters
|
||||||
|
*
|
||||||
|
* General usage flow:
|
||||||
|
* Init() -- early as possible
|
||||||
|
* ParseCmdline() -- somewhere close to main()
|
||||||
|
* ParseConfig() -- after detecting path to config
|
||||||
|
* Finalize() -- right after all Parse*() functions called
|
||||||
|
* GetOption() -- may be called after Finalize()
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace config {
|
||||||
|
extern boost::program_options::variables_map m_Options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize list of acceptable parameters
|
||||||
|
*
|
||||||
|
* Should be called before any Parse* functions.
|
||||||
|
*/
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse cmdline parameters, and show help if requested
|
||||||
|
* @param argc Cmdline arguments count, should be passed from main().
|
||||||
|
* @param argv Cmdline parameters array, should be passed from main()
|
||||||
|
*
|
||||||
|
* If --help is given in parameters, shows it's list with description
|
||||||
|
* terminates the program with exitcode 0.
|
||||||
|
*
|
||||||
|
* In case of parameter misuse boost throws an exception.
|
||||||
|
* We internally handle type boost::program_options::unknown_option,
|
||||||
|
* and then terminate program with exitcode 1.
|
||||||
|
*
|
||||||
|
* Other exceptions will be passed to higher level.
|
||||||
|
*/
|
||||||
|
void ParseCmdline(int argc, char* argv[]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load and parse given config file
|
||||||
|
* @param path Path to config file
|
||||||
|
*
|
||||||
|
* If error occured when opening file path is points to,
|
||||||
|
* we show the error message and terminate program.
|
||||||
|
*
|
||||||
|
* In case of parameter misuse boost throws an exception.
|
||||||
|
* We internally handle type boost::program_options::unknown_option,
|
||||||
|
* and then terminate program with exitcode 1.
|
||||||
|
*
|
||||||
|
* Other exceptions will be passed to higher level.
|
||||||
|
*/
|
||||||
|
void ParseConfig(const std::string& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Used to combine options from cmdline, config and default values
|
||||||
|
*/
|
||||||
|
void Finalize();
|
||||||
|
|
||||||
|
/* @brief Accessor to parameters by name
|
||||||
|
* @param name Name of the requested parameter
|
||||||
|
* @param value Variable where to store option
|
||||||
|
* @return this function returns false if parameter not found
|
||||||
|
*
|
||||||
|
* @example uint16_t port; GetOption("sam.port", port);
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
bool GetOption(const char *name, T& value) {
|
||||||
|
if (!m_Options.count(name))
|
||||||
|
return false;
|
||||||
|
value = m_Options[name].as<T>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set value of given parameter
|
||||||
|
* @param name Name of settable parameter
|
||||||
|
* @param value New parameter value
|
||||||
|
* @return true if value set up successful, false otherwise
|
||||||
|
*
|
||||||
|
* @example uint16_t port = 2827; SetOption("bob.port", port);
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
bool SetOption(const char *name, const T& value) {
|
||||||
|
if (!m_Options.count(name))
|
||||||
|
return false;
|
||||||
|
m_Options.at(name).value() = value;
|
||||||
|
notify(m_Options);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check is value explicitly given or default
|
||||||
|
* @param name Name of checked parameter
|
||||||
|
* @return true if value set to default, false othervise
|
||||||
|
*/
|
||||||
|
bool IsDefault(const char *name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
||||||
837
Crypto.cpp
Normal file
837
Crypto.cpp
Normal file
@@ -0,0 +1,837 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include <openssl/dh.h>
|
||||||
|
#include <openssl/md5.h>
|
||||||
|
#include <openssl/crypto.h>
|
||||||
|
#include "TunnelBase.h"
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace crypto
|
||||||
|
{
|
||||||
|
const uint8_t elgp_[256]=
|
||||||
|
{
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
|
||||||
|
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
|
||||||
|
0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
|
||||||
|
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
|
||||||
|
0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
|
||||||
|
0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
|
||||||
|
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
|
||||||
|
0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05,
|
||||||
|
0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
|
||||||
|
0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB,
|
||||||
|
0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04,
|
||||||
|
0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
|
||||||
|
0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F,
|
||||||
|
0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18,
|
||||||
|
0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
|
||||||
|
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
const int elgg_ = 2;
|
||||||
|
|
||||||
|
const uint8_t dsap_[128]=
|
||||||
|
{
|
||||||
|
0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c,
|
||||||
|
0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15,
|
||||||
|
0xfc, 0xad, 0xae, 0x31, 0xa0, 0xad, 0x18, 0xfa, 0xb3, 0xf0, 0x1b, 0x00, 0xa3, 0x58, 0xde, 0x23,
|
||||||
|
0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c,
|
||||||
|
0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8,
|
||||||
|
0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c,
|
||||||
|
0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11,
|
||||||
|
0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t dsaq_[20]=
|
||||||
|
{
|
||||||
|
0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d,
|
||||||
|
0x68, 0x40, 0x46, 0xb7
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t dsag_[128]=
|
||||||
|
{
|
||||||
|
0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0,
|
||||||
|
0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81,
|
||||||
|
0x07, 0x5f, 0xf9, 0x08, 0x2e, 0xd3, 0x23, 0x53, 0xd4, 0x37, 0x4d, 0x73, 0x01, 0xcd, 0xa1, 0xd2,
|
||||||
|
0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52,
|
||||||
|
0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc,
|
||||||
|
0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a,
|
||||||
|
0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c,
|
||||||
|
0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82
|
||||||
|
};
|
||||||
|
|
||||||
|
const int rsae_ = 65537;
|
||||||
|
|
||||||
|
struct CryptoConstants
|
||||||
|
{
|
||||||
|
// DH/ElGamal
|
||||||
|
BIGNUM * elgp;
|
||||||
|
BIGNUM * elgg;
|
||||||
|
|
||||||
|
// DSA
|
||||||
|
BIGNUM * dsap;
|
||||||
|
BIGNUM * dsaq;
|
||||||
|
BIGNUM * dsag;
|
||||||
|
|
||||||
|
// RSA
|
||||||
|
BIGNUM * rsae;
|
||||||
|
|
||||||
|
CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_,
|
||||||
|
const uint8_t * dsaq_, const uint8_t * dsag_, int rsae_)
|
||||||
|
{
|
||||||
|
elgp = BN_new ();
|
||||||
|
BN_bin2bn (elgp_, 256, elgp);
|
||||||
|
elgg = BN_new ();
|
||||||
|
BN_set_word (elgg, elgg_);
|
||||||
|
dsap = BN_new ();
|
||||||
|
BN_bin2bn (dsap_, 128, dsap);
|
||||||
|
dsaq = BN_new ();
|
||||||
|
BN_bin2bn (dsaq_, 20, dsaq);
|
||||||
|
dsag = BN_new ();
|
||||||
|
BN_bin2bn (dsag_, 128, dsag);
|
||||||
|
rsae = BN_new ();
|
||||||
|
BN_set_word (rsae, rsae_);
|
||||||
|
}
|
||||||
|
|
||||||
|
~CryptoConstants ()
|
||||||
|
{
|
||||||
|
BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const CryptoConstants& GetCryptoConstants ()
|
||||||
|
{
|
||||||
|
static CryptoConstants cryptoConstants (elgp_, elgg_, dsap_, dsaq_, dsag_, rsae_);
|
||||||
|
return cryptoConstants;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
int offset = len - BN_num_bytes (bn);
|
||||||
|
if (offset < 0) return false;
|
||||||
|
BN_bn2bin (bn, buf + offset);
|
||||||
|
memset (buf, 0, offset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSA
|
||||||
|
#define rsae GetCryptoConstants ().rsae
|
||||||
|
const BIGNUM * GetRSAE ()
|
||||||
|
{
|
||||||
|
return rsae;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DSA
|
||||||
|
#define dsap GetCryptoConstants ().dsap
|
||||||
|
#define dsaq GetCryptoConstants ().dsaq
|
||||||
|
#define dsag GetCryptoConstants ().dsag
|
||||||
|
DSA * CreateDSA ()
|
||||||
|
{
|
||||||
|
DSA * dsa = DSA_new ();
|
||||||
|
dsa->p = BN_dup (dsap);
|
||||||
|
dsa->q = BN_dup (dsaq);
|
||||||
|
dsa->g = BN_dup (dsag);
|
||||||
|
dsa->priv_key = NULL;
|
||||||
|
dsa->pub_key = NULL;
|
||||||
|
return dsa;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DH/ElGamal
|
||||||
|
|
||||||
|
const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226;
|
||||||
|
const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1;
|
||||||
|
const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048;
|
||||||
|
const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8;
|
||||||
|
|
||||||
|
#define elgp GetCryptoConstants ().elgp
|
||||||
|
#define elgg GetCryptoConstants ().elgg
|
||||||
|
|
||||||
|
static BN_MONT_CTX * g_MontCtx = nullptr;
|
||||||
|
static void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums
|
||||||
|
{
|
||||||
|
if (len <= 0) return;
|
||||||
|
BN_CTX * ctx = BN_CTX_new ();
|
||||||
|
g_MontCtx = BN_MONT_CTX_new ();
|
||||||
|
BN_MONT_CTX_set (g_MontCtx, elgp, ctx);
|
||||||
|
auto montCtx = BN_MONT_CTX_new ();
|
||||||
|
BN_MONT_CTX_copy (montCtx, g_MontCtx);
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
table[i][0] = BN_new ();
|
||||||
|
if (!i)
|
||||||
|
BN_to_montgomery (table[0][0], elgg, montCtx, ctx);
|
||||||
|
else
|
||||||
|
BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx);
|
||||||
|
for (int j = 1; j < 255; j++)
|
||||||
|
{
|
||||||
|
table[i][j] = BN_new ();
|
||||||
|
BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], montCtx, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BN_MONT_CTX_free (montCtx);
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DestroyElggTable (BIGNUM * table[][255], int len)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
for (int j = 0; j < 255; j++)
|
||||||
|
{
|
||||||
|
BN_free (table[i][j]);
|
||||||
|
table[i][j] = nullptr;
|
||||||
|
}
|
||||||
|
BN_MONT_CTX_free (g_MontCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx)
|
||||||
|
// exp is in Big Endian
|
||||||
|
{
|
||||||
|
if (len <= 0) return nullptr;
|
||||||
|
auto montCtx = BN_MONT_CTX_new ();
|
||||||
|
BN_MONT_CTX_copy (montCtx, g_MontCtx);
|
||||||
|
BIGNUM * res = nullptr;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
if (exp[i])
|
||||||
|
BN_mod_mul_montgomery (res, res, table[len-1-i][exp[i]-1], montCtx, ctx);
|
||||||
|
}
|
||||||
|
else if (exp[i])
|
||||||
|
res = BN_dup (table[len-i-1][exp[i]-1]);
|
||||||
|
}
|
||||||
|
if (res)
|
||||||
|
BN_from_montgomery (res, res, montCtx, ctx);
|
||||||
|
BN_MONT_CTX_free (montCtx);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx)
|
||||||
|
{
|
||||||
|
auto len = BN_num_bytes (exp);
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
BN_bn2bin (exp, buf);
|
||||||
|
auto ret = ElggPow (buf, len, table, ctx);
|
||||||
|
delete[] buf;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BIGNUM * (* g_ElggTable)[255] = nullptr;
|
||||||
|
|
||||||
|
// DH
|
||||||
|
|
||||||
|
DHKeys::DHKeys (): m_IsUpdated (true)
|
||||||
|
{
|
||||||
|
m_DH = DH_new ();
|
||||||
|
m_DH->p = BN_dup (elgp);
|
||||||
|
m_DH->g = BN_dup (elgg);
|
||||||
|
m_DH->priv_key = NULL;
|
||||||
|
m_DH->pub_key = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DHKeys::~DHKeys ()
|
||||||
|
{
|
||||||
|
DH_free (m_DH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DHKeys::GenerateKeys (uint8_t * priv, uint8_t * pub)
|
||||||
|
{
|
||||||
|
if (m_DH->priv_key) { BN_free (m_DH->priv_key); m_DH->priv_key = NULL; };
|
||||||
|
if (m_DH->pub_key) { BN_free (m_DH->pub_key); m_DH->pub_key = NULL; };
|
||||||
|
#if !defined(__x86_64__) // use short exponent for non x64
|
||||||
|
m_DH->priv_key = BN_new ();
|
||||||
|
BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1);
|
||||||
|
#endif
|
||||||
|
if (g_ElggTable)
|
||||||
|
{
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
m_DH->priv_key = BN_new ();
|
||||||
|
BN_rand (m_DH->priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1);
|
||||||
|
#endif
|
||||||
|
auto ctx = BN_CTX_new ();
|
||||||
|
m_DH->pub_key = ElggPow (m_DH->priv_key, g_ElggTable, ctx);
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DH_generate_key (m_DH);
|
||||||
|
|
||||||
|
if (priv) bn2buf (m_DH->priv_key, priv, 256);
|
||||||
|
if (pub) bn2buf (m_DH->pub_key, pub, 256);
|
||||||
|
m_IsUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t * DHKeys::GetPublicKey ()
|
||||||
|
{
|
||||||
|
if (m_IsUpdated)
|
||||||
|
{
|
||||||
|
bn2buf (m_DH->pub_key, m_PublicKey, 256);
|
||||||
|
BN_free (m_DH->pub_key); m_DH->pub_key = NULL;
|
||||||
|
m_IsUpdated= false;
|
||||||
|
}
|
||||||
|
return m_PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DHKeys::Agree (const uint8_t * pub, uint8_t * shared)
|
||||||
|
{
|
||||||
|
BIGNUM * pk = BN_bin2bn (pub, 256, NULL);
|
||||||
|
DH_compute_key (shared, pk, m_DH);
|
||||||
|
BN_free (pk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElGamal
|
||||||
|
|
||||||
|
ElGamalEncryption::ElGamalEncryption (const uint8_t * key)
|
||||||
|
{
|
||||||
|
ctx = BN_CTX_new ();
|
||||||
|
// select random k
|
||||||
|
BIGNUM * k = BN_new ();
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64
|
||||||
|
#else
|
||||||
|
BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits
|
||||||
|
#endif
|
||||||
|
// calculate a
|
||||||
|
a = BN_new ();
|
||||||
|
if (g_ElggTable)
|
||||||
|
a = ElggPow (k, g_ElggTable, ctx);
|
||||||
|
else
|
||||||
|
BN_mod_exp (a, elgg, k, elgp, ctx);
|
||||||
|
|
||||||
|
BIGNUM * y = BN_new ();
|
||||||
|
BN_bin2bn (key, 256, y);
|
||||||
|
// calculate b1
|
||||||
|
b1 = BN_new ();
|
||||||
|
BN_mod_exp (b1, y, k, elgp, ctx);
|
||||||
|
BN_free (y);
|
||||||
|
BN_free (k);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElGamalEncryption::~ElGamalEncryption ()
|
||||||
|
{
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
BN_free (a);
|
||||||
|
BN_free (b1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElGamalEncryption::Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding) const
|
||||||
|
{
|
||||||
|
// create m
|
||||||
|
uint8_t m[255];
|
||||||
|
m[0] = 0xFF;
|
||||||
|
memcpy (m+33, data, len);
|
||||||
|
SHA256 (m+33, 222, m+1);
|
||||||
|
// calculate b = b1*m mod p
|
||||||
|
BIGNUM * b = BN_new ();
|
||||||
|
BN_bin2bn (m, 255, b);
|
||||||
|
BN_mod_mul (b, b1, b, elgp, ctx);
|
||||||
|
// copy a and b
|
||||||
|
if (zeroPadding)
|
||||||
|
{
|
||||||
|
encrypted[0] = 0;
|
||||||
|
bn2buf (a, encrypted + 1, 256);
|
||||||
|
encrypted[257] = 0;
|
||||||
|
bn2buf (b, encrypted + 258, 256);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bn2buf (a, encrypted, 256);
|
||||||
|
bn2buf (b, encrypted + 256, 256);
|
||||||
|
}
|
||||||
|
BN_free (b);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted,
|
||||||
|
uint8_t * data, bool zeroPadding)
|
||||||
|
{
|
||||||
|
BN_CTX * ctx = BN_CTX_new ();
|
||||||
|
BIGNUM * x = BN_new (), * a = BN_new (), * b = BN_new ();
|
||||||
|
BN_bin2bn (key, 256, x);
|
||||||
|
BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1
|
||||||
|
BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a);
|
||||||
|
BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b);
|
||||||
|
// m = b*(a^x mod p) mod p
|
||||||
|
BN_mod_exp (x, a, x, elgp, ctx);
|
||||||
|
BN_mod_mul (b, b, x, elgp, ctx);
|
||||||
|
uint8_t m[255];
|
||||||
|
bn2buf (b, m, 255);
|
||||||
|
BN_free (x); BN_free (a); BN_free (b);
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
uint8_t hash[32];
|
||||||
|
SHA256 (m + 33, 222, hash);
|
||||||
|
if (memcmp (m + 1, hash, 32))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "ElGamal decrypt hash doesn't match");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy (data, m + 33, 222);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub)
|
||||||
|
{
|
||||||
|
#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
|
||||||
|
RAND_bytes (priv, 256);
|
||||||
|
#else
|
||||||
|
// lower 226 bits (28 bytes and 2 bits) only. short exponent
|
||||||
|
auto numBytes = (ELGAMAL_SHORT_EXPONENT_NUM_BITS)/8 + 1; // 29
|
||||||
|
auto numZeroBytes = 256 - numBytes;
|
||||||
|
RAND_bytes (priv + numZeroBytes, numBytes);
|
||||||
|
memset (priv, 0, numZeroBytes);
|
||||||
|
priv[numZeroBytes] &= 0x03;
|
||||||
|
#endif
|
||||||
|
BN_CTX * ctx = BN_CTX_new ();
|
||||||
|
BIGNUM * p = BN_new ();
|
||||||
|
BN_bin2bn (priv, 256, p);
|
||||||
|
BN_mod_exp (p, elgg, p, elgp, ctx);
|
||||||
|
bn2buf (p, pub, 256);
|
||||||
|
BN_free (p);
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMAC
|
||||||
|
const uint64_t IPAD = 0x3636363636363636;
|
||||||
|
const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C;
|
||||||
|
|
||||||
|
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest)
|
||||||
|
// key is 32 bytes
|
||||||
|
// digest is 16 bytes
|
||||||
|
// block size is 64 bytes
|
||||||
|
{
|
||||||
|
uint64_t buf[256];
|
||||||
|
// ikeypad
|
||||||
|
buf[0] = key.GetLL ()[0] ^ IPAD;
|
||||||
|
buf[1] = key.GetLL ()[1] ^ IPAD;
|
||||||
|
buf[2] = key.GetLL ()[2] ^ IPAD;
|
||||||
|
buf[3] = key.GetLL ()[3] ^ IPAD;
|
||||||
|
buf[4] = IPAD;
|
||||||
|
buf[5] = IPAD;
|
||||||
|
buf[6] = IPAD;
|
||||||
|
buf[7] = IPAD;
|
||||||
|
// concatenate with msg
|
||||||
|
memcpy (buf + 8, msg, len);
|
||||||
|
// calculate first hash
|
||||||
|
uint8_t hash[16]; // MD5
|
||||||
|
MD5((uint8_t *)buf, len + 64, hash);
|
||||||
|
|
||||||
|
// okeypad
|
||||||
|
buf[0] = key.GetLL ()[0] ^ OPAD;
|
||||||
|
buf[1] = key.GetLL ()[1] ^ OPAD;
|
||||||
|
buf[2] = key.GetLL ()[2] ^ OPAD;
|
||||||
|
buf[3] = key.GetLL ()[3] ^ OPAD;
|
||||||
|
buf[4] = OPAD;
|
||||||
|
buf[5] = OPAD;
|
||||||
|
buf[6] = OPAD;
|
||||||
|
buf[7] = OPAD;
|
||||||
|
// copy first hash after okeypad
|
||||||
|
memcpy (buf + 8, hash, 16);
|
||||||
|
// fill next 16 bytes with zeros (first hash size assumed 32 bytes in I2P)
|
||||||
|
memset (buf + 10, 0, 16);
|
||||||
|
|
||||||
|
// calculate digest
|
||||||
|
MD5((uint8_t *)buf, 96, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AES
|
||||||
|
#ifdef AESNI
|
||||||
|
|
||||||
|
#define KeyExpansion256(round0,round1) \
|
||||||
|
"pshufd $0xff, %%xmm2, %%xmm2 \n" \
|
||||||
|
"movaps %%xmm1, %%xmm4 \n" \
|
||||||
|
"pslldq $4, %%xmm4 \n" \
|
||||||
|
"pxor %%xmm4, %%xmm1 \n" \
|
||||||
|
"pslldq $4, %%xmm4 \n" \
|
||||||
|
"pxor %%xmm4, %%xmm1 \n" \
|
||||||
|
"pslldq $4, %%xmm4 \n" \
|
||||||
|
"pxor %%xmm4, %%xmm1 \n" \
|
||||||
|
"pxor %%xmm2, %%xmm1 \n" \
|
||||||
|
"movaps %%xmm1, "#round0"(%[sched]) \n" \
|
||||||
|
"aeskeygenassist $0, %%xmm1, %%xmm4 \n" \
|
||||||
|
"pshufd $0xaa, %%xmm4, %%xmm2 \n" \
|
||||||
|
"movaps %%xmm3, %%xmm4 \n" \
|
||||||
|
"pslldq $4, %%xmm4 \n" \
|
||||||
|
"pxor %%xmm4, %%xmm3 \n" \
|
||||||
|
"pslldq $4, %%xmm4 \n" \
|
||||||
|
"pxor %%xmm4, %%xmm3 \n" \
|
||||||
|
"pslldq $4, %%xmm4 \n" \
|
||||||
|
"pxor %%xmm4, %%xmm3 \n" \
|
||||||
|
"pxor %%xmm2, %%xmm3 \n" \
|
||||||
|
"movaps %%xmm3, "#round1"(%[sched]) \n"
|
||||||
|
|
||||||
|
void ECBCryptoAESNI::ExpandKey (const AESKey& key)
|
||||||
|
{
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[key]), %%xmm1 \n"
|
||||||
|
"movups 16(%[key]), %%xmm3 \n"
|
||||||
|
"movaps %%xmm1, (%[sched]) \n"
|
||||||
|
"movaps %%xmm3, 16(%[sched]) \n"
|
||||||
|
"aeskeygenassist $1, %%xmm3, %%xmm2 \n"
|
||||||
|
KeyExpansion256(32,48)
|
||||||
|
"aeskeygenassist $2, %%xmm3, %%xmm2 \n"
|
||||||
|
KeyExpansion256(64,80)
|
||||||
|
"aeskeygenassist $4, %%xmm3, %%xmm2 \n"
|
||||||
|
KeyExpansion256(96,112)
|
||||||
|
"aeskeygenassist $8, %%xmm3, %%xmm2 \n"
|
||||||
|
KeyExpansion256(128,144)
|
||||||
|
"aeskeygenassist $16, %%xmm3, %%xmm2 \n"
|
||||||
|
KeyExpansion256(160,176)
|
||||||
|
"aeskeygenassist $32, %%xmm3, %%xmm2 \n"
|
||||||
|
KeyExpansion256(192,208)
|
||||||
|
"aeskeygenassist $64, %%xmm3, %%xmm2 \n"
|
||||||
|
// key expansion final
|
||||||
|
"pshufd $0xff, %%xmm2, %%xmm2 \n"
|
||||||
|
"movaps %%xmm1, %%xmm4 \n"
|
||||||
|
"pslldq $4, %%xmm4 \n"
|
||||||
|
"pxor %%xmm4, %%xmm1 \n"
|
||||||
|
"pslldq $4, %%xmm4 \n"
|
||||||
|
"pxor %%xmm4, %%xmm1 \n"
|
||||||
|
"pslldq $4, %%xmm4 \n"
|
||||||
|
"pxor %%xmm4, %%xmm1 \n"
|
||||||
|
"pxor %%xmm2, %%xmm1 \n"
|
||||||
|
"movups %%xmm1, 224(%[sched]) \n"
|
||||||
|
: // output
|
||||||
|
: [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input
|
||||||
|
: "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EncryptAES256(sched) \
|
||||||
|
"pxor (%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 16(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 32(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 48(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 64(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 80(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 96(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 112(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 128(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 144(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 160(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 176(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 192(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenc 208(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesenclast 224(%["#sched"]), %%xmm0 \n"
|
||||||
|
|
||||||
|
void ECBEncryptionAESNI::Encrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||||
|
{
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
EncryptAES256(sched)
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DecryptAES256(sched) \
|
||||||
|
"pxor 224(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 208(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 192(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 176(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 160(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 144(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 128(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 112(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 96(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 80(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 64(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 48(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 32(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdec 16(%["#sched"]), %%xmm0 \n" \
|
||||||
|
"aesdeclast (%["#sched"]), %%xmm0 \n"
|
||||||
|
|
||||||
|
void ECBDecryptionAESNI::Decrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||||
|
{
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
DecryptAES256(sched)
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CallAESIMC(offset) \
|
||||||
|
"movaps "#offset"(%[shed]), %%xmm0 \n" \
|
||||||
|
"aesimc %%xmm0, %%xmm0 \n" \
|
||||||
|
"movaps %%xmm0, "#offset"(%[shed]) \n"
|
||||||
|
|
||||||
|
void ECBDecryptionAESNI::SetKey (const AESKey& key)
|
||||||
|
{
|
||||||
|
ExpandKey (key); // expand encryption key first
|
||||||
|
// then invert it using aesimc
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
CallAESIMC(16)
|
||||||
|
CallAESIMC(32)
|
||||||
|
CallAESIMC(48)
|
||||||
|
CallAESIMC(64)
|
||||||
|
CallAESIMC(80)
|
||||||
|
CallAESIMC(96)
|
||||||
|
CallAESIMC(112)
|
||||||
|
CallAESIMC(128)
|
||||||
|
CallAESIMC(144)
|
||||||
|
CallAESIMC(160)
|
||||||
|
CallAESIMC(176)
|
||||||
|
CallAESIMC(192)
|
||||||
|
CallAESIMC(208)
|
||||||
|
: : [shed]"r"(GetKeySchedule ()) : "%xmm0", "memory"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
|
||||||
|
{
|
||||||
|
#ifdef AESNI
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[iv]), %%xmm1 \n"
|
||||||
|
"1: \n"
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
"pxor %%xmm1, %%xmm0 \n"
|
||||||
|
EncryptAES256(sched)
|
||||||
|
"movaps %%xmm0, %%xmm1 \n"
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
"add $16, %[in] \n"
|
||||||
|
"add $16, %[out] \n"
|
||||||
|
"dec %[num] \n"
|
||||||
|
"jnz 1b \n"
|
||||||
|
"movups %%xmm1, (%[iv]) \n"
|
||||||
|
:
|
||||||
|
: [iv]"r"(&m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
|
||||||
|
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
|
||||||
|
: "%xmm0", "%xmm1", "cc", "memory"
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
for (int i = 0; i < numBlocks; i++)
|
||||||
|
{
|
||||||
|
m_LastBlock ^= in[i];
|
||||||
|
m_ECBEncryption.Encrypt (&m_LastBlock, &m_LastBlock);
|
||||||
|
out[i] = m_LastBlock;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out)
|
||||||
|
{
|
||||||
|
// len/16
|
||||||
|
int numBlocks = len >> 4;
|
||||||
|
if (numBlocks > 0)
|
||||||
|
Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out)
|
||||||
|
{
|
||||||
|
#ifdef AESNI
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[iv]), %%xmm1 \n"
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
"pxor %%xmm1, %%xmm0 \n"
|
||||||
|
EncryptAES256(sched)
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
"movups %%xmm0, (%[iv]) \n"
|
||||||
|
:
|
||||||
|
: [iv]"r"(&m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
|
||||||
|
[in]"r"(in), [out]"r"(out)
|
||||||
|
: "%xmm0", "%xmm1", "memory"
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
|
||||||
|
{
|
||||||
|
#ifdef AESNI
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[iv]), %%xmm1 \n"
|
||||||
|
"1: \n"
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
"movaps %%xmm0, %%xmm2 \n"
|
||||||
|
DecryptAES256(sched)
|
||||||
|
"pxor %%xmm1, %%xmm0 \n"
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
"movaps %%xmm2, %%xmm1 \n"
|
||||||
|
"add $16, %[in] \n"
|
||||||
|
"add $16, %[out] \n"
|
||||||
|
"dec %[num] \n"
|
||||||
|
"jnz 1b \n"
|
||||||
|
"movups %%xmm1, (%[iv]) \n"
|
||||||
|
:
|
||||||
|
: [iv]"r"(&m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
|
||||||
|
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
|
||||||
|
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
for (int i = 0; i < numBlocks; i++)
|
||||||
|
{
|
||||||
|
ChipherBlock tmp = in[i];
|
||||||
|
m_ECBDecryption.Decrypt (in + i, out + i);
|
||||||
|
out[i] ^= m_IV;
|
||||||
|
m_IV = tmp;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out)
|
||||||
|
{
|
||||||
|
int numBlocks = len >> 4;
|
||||||
|
if (numBlocks > 0)
|
||||||
|
Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out)
|
||||||
|
{
|
||||||
|
#ifdef AESNI
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[iv]), %%xmm1 \n"
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
"movups %%xmm0, (%[iv]) \n"
|
||||||
|
DecryptAES256(sched)
|
||||||
|
"pxor %%xmm1, %%xmm0 \n"
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
:
|
||||||
|
: [iv]"r"(&m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
|
||||||
|
[in]"r"(in), [out]"r"(out)
|
||||||
|
: "%xmm0", "%xmm1", "memory"
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out)
|
||||||
|
{
|
||||||
|
#ifdef AESNI
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
// encrypt IV
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
EncryptAES256(sched_iv)
|
||||||
|
"movaps %%xmm0, %%xmm1 \n"
|
||||||
|
// double IV encryption
|
||||||
|
EncryptAES256(sched_iv)
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
// encrypt data, IV is xmm1
|
||||||
|
"1: \n"
|
||||||
|
"add $16, %[in] \n"
|
||||||
|
"add $16, %[out] \n"
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
"pxor %%xmm1, %%xmm0 \n"
|
||||||
|
EncryptAES256(sched_l)
|
||||||
|
"movaps %%xmm0, %%xmm1 \n"
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
"dec %[num] \n"
|
||||||
|
"jnz 1b \n"
|
||||||
|
:
|
||||||
|
: [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.GetKeySchedule ()),
|
||||||
|
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
|
||||||
|
: "%xmm0", "%xmm1", "cc", "memory"
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
|
||||||
|
m_LayerEncryption.SetIV (out);
|
||||||
|
m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
|
||||||
|
m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out)
|
||||||
|
{
|
||||||
|
#ifdef AESNI
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
// decrypt IV
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
DecryptAES256(sched_iv)
|
||||||
|
"movaps %%xmm0, %%xmm1 \n"
|
||||||
|
// double IV encryption
|
||||||
|
DecryptAES256(sched_iv)
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
// decrypt data, IV is xmm1
|
||||||
|
"1: \n"
|
||||||
|
"add $16, %[in] \n"
|
||||||
|
"add $16, %[out] \n"
|
||||||
|
"movups (%[in]), %%xmm0 \n"
|
||||||
|
"movaps %%xmm0, %%xmm2 \n"
|
||||||
|
DecryptAES256(sched_l)
|
||||||
|
"pxor %%xmm1, %%xmm0 \n"
|
||||||
|
"movups %%xmm0, (%[out]) \n"
|
||||||
|
"movaps %%xmm2, %%xmm1 \n"
|
||||||
|
"dec %[num] \n"
|
||||||
|
"jnz 1b \n"
|
||||||
|
:
|
||||||
|
: [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.GetKeySchedule ()),
|
||||||
|
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
|
||||||
|
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
|
||||||
|
m_LayerDecryption.SetIV (out);
|
||||||
|
m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
|
||||||
|
m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* std::vector <std::unique_ptr<std::mutex> > m_OpenSSLMutexes;
|
||||||
|
static void OpensslLockingCallback(int mode, int type, const char * file, int line)
|
||||||
|
{
|
||||||
|
if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ())
|
||||||
|
{
|
||||||
|
if (mode & CRYPTO_LOCK)
|
||||||
|
m_OpenSSLMutexes[type]->lock ();
|
||||||
|
else
|
||||||
|
m_OpenSSLMutexes[type]->unlock ();
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
void InitCrypto (bool precomputation)
|
||||||
|
{
|
||||||
|
SSL_library_init ();
|
||||||
|
/* auto numLocks = CRYPTO_num_locks();
|
||||||
|
for (int i = 0; i < numLocks; i++)
|
||||||
|
m_OpenSSLMutexes.emplace_back (new std::mutex);
|
||||||
|
CRYPTO_set_locking_callback (OpensslLockingCallback);*/
|
||||||
|
if (precomputation)
|
||||||
|
{
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255];
|
||||||
|
PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES);
|
||||||
|
#else
|
||||||
|
g_ElggTable = new BIGNUM * [ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255];
|
||||||
|
PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerminateCrypto ()
|
||||||
|
{
|
||||||
|
if (g_ElggTable)
|
||||||
|
{
|
||||||
|
DestroyElggTable (g_ElggTable,
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
ELGAMAL_FULL_EXPONENT_NUM_BYTES
|
||||||
|
#else
|
||||||
|
ELGAMAL_SHORT_EXPONENT_NUM_BYTES
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
delete[] g_ElggTable; g_ElggTable = nullptr;
|
||||||
|
}
|
||||||
|
/* CRYPTO_set_locking_callback (nullptr);
|
||||||
|
m_OpenSSLMutexes.clear ();*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
283
Crypto.h
Normal file
283
Crypto.h
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
#ifndef CRYPTO_H__
|
||||||
|
#define CRYPTO_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <openssl/bn.h>
|
||||||
|
#include <openssl/dh.h>
|
||||||
|
#include <openssl/aes.h>
|
||||||
|
#include <openssl/dsa.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include "Base.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace crypto
|
||||||
|
{
|
||||||
|
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
// DSA
|
||||||
|
DSA * CreateDSA ();
|
||||||
|
|
||||||
|
// RSA
|
||||||
|
const BIGNUM * GetRSAE ();
|
||||||
|
|
||||||
|
// DH
|
||||||
|
class DHKeys
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
DHKeys ();
|
||||||
|
~DHKeys ();
|
||||||
|
|
||||||
|
void GenerateKeys (uint8_t * priv = nullptr, uint8_t * pub = nullptr);
|
||||||
|
const uint8_t * GetPublicKey ();
|
||||||
|
void Agree (const uint8_t * pub, uint8_t * shared);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
DH * m_DH;
|
||||||
|
uint8_t m_PublicKey[256];
|
||||||
|
bool m_IsUpdated;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ElGamal
|
||||||
|
class ElGamalEncryption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ElGamalEncryption (const uint8_t * key);
|
||||||
|
~ElGamalEncryption ();
|
||||||
|
|
||||||
|
void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
BN_CTX * ctx;
|
||||||
|
BIGNUM * a, * b1;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, bool zeroPadding = false);
|
||||||
|
void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub);
|
||||||
|
|
||||||
|
// HMAC
|
||||||
|
typedef i2p::data::Tag<32> MACKey;
|
||||||
|
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest);
|
||||||
|
|
||||||
|
// AES
|
||||||
|
struct ChipherBlock
|
||||||
|
{
|
||||||
|
uint8_t buf[16];
|
||||||
|
|
||||||
|
void operator^=(const ChipherBlock& other) // XOR
|
||||||
|
{
|
||||||
|
#if defined(__x86_64__) || defined(__SSE__) // for Intel x84 or with SSE
|
||||||
|
__asm__
|
||||||
|
(
|
||||||
|
"movups (%[buf]), %%xmm0 \n"
|
||||||
|
"movups (%[other]), %%xmm1 \n"
|
||||||
|
"pxor %%xmm1, %%xmm0 \n"
|
||||||
|
"movups %%xmm0, (%[buf]) \n"
|
||||||
|
:
|
||||||
|
: [buf]"r"(buf), [other]"r"(other.buf)
|
||||||
|
: "%xmm0", "%xmm1", "memory"
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
// TODO: implement it better
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
buf[i] ^= other.buf[i];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef i2p::data::Tag<32> AESKey;
|
||||||
|
|
||||||
|
template<size_t sz>
|
||||||
|
class AESAlignedBuffer // 16 bytes alignment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
AESAlignedBuffer ()
|
||||||
|
{
|
||||||
|
m_Buf = m_UnalignedBuffer;
|
||||||
|
uint8_t rem = ((size_t)m_Buf) & 0x0f;
|
||||||
|
if (rem)
|
||||||
|
m_Buf += (16 - rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
operator uint8_t * () { return m_Buf; };
|
||||||
|
operator const uint8_t * () const { return m_Buf; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment
|
||||||
|
uint8_t * m_Buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef AESNI
|
||||||
|
class ECBCryptoAESNI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
uint8_t * GetKeySchedule () { return m_KeySchedule; };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void ExpandKey (const AESKey& key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
class ECBEncryptionAESNI: public ECBCryptoAESNI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void SetKey (const AESKey& key) { ExpandKey (key); };
|
||||||
|
void Encrypt (const ChipherBlock * in, ChipherBlock * out);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ECBDecryptionAESNI: public ECBCryptoAESNI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void SetKey (const AESKey& key);
|
||||||
|
void Decrypt (const ChipherBlock * in, ChipherBlock * out);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef ECBEncryptionAESNI ECBEncryption;
|
||||||
|
typedef ECBDecryptionAESNI ECBDecryption;
|
||||||
|
|
||||||
|
#else // use openssl
|
||||||
|
|
||||||
|
class ECBEncryption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void SetKey (const AESKey& key)
|
||||||
|
{
|
||||||
|
AES_set_encrypt_key (key, 256, &m_Key);
|
||||||
|
}
|
||||||
|
void Encrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||||
|
{
|
||||||
|
AES_encrypt (in->buf, out->buf, &m_Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
AES_KEY m_Key;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ECBDecryption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void SetKey (const AESKey& key)
|
||||||
|
{
|
||||||
|
AES_set_decrypt_key (key, 256, &m_Key);
|
||||||
|
}
|
||||||
|
void Decrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||||
|
{
|
||||||
|
AES_decrypt (in->buf, out->buf, &m_Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
AES_KEY m_Key;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class CBCEncryption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
CBCEncryption () { memset (m_LastBlock.buf, 0, 16); };
|
||||||
|
|
||||||
|
void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes
|
||||||
|
void SetIV (const uint8_t * iv) { memcpy (m_LastBlock.buf, iv, 16); }; // 16 bytes
|
||||||
|
|
||||||
|
void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
|
||||||
|
void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out);
|
||||||
|
void Encrypt (const uint8_t * in, uint8_t * out); // one block
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ChipherBlock m_LastBlock;
|
||||||
|
|
||||||
|
ECBEncryption m_ECBEncryption;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CBCDecryption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
CBCDecryption () { memset (m_IV.buf, 0, 16); };
|
||||||
|
|
||||||
|
void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes
|
||||||
|
void SetIV (const uint8_t * iv) { memcpy (m_IV.buf, iv, 16); }; // 16 bytes
|
||||||
|
|
||||||
|
void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
|
||||||
|
void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out);
|
||||||
|
void Decrypt (const uint8_t * in, uint8_t * out); // one block
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ChipherBlock m_IV;
|
||||||
|
ECBDecryption m_ECBDecryption;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TunnelEncryption // with double IV encryption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void SetKeys (const AESKey& layerKey, const AESKey& ivKey)
|
||||||
|
{
|
||||||
|
m_LayerEncryption.SetKey (layerKey);
|
||||||
|
m_IVEncryption.SetKey (ivKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Encrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ECBEncryption m_IVEncryption;
|
||||||
|
#ifdef AESNI
|
||||||
|
ECBEncryption m_LayerEncryption;
|
||||||
|
#else
|
||||||
|
CBCEncryption m_LayerEncryption;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class TunnelDecryption // with double IV encryption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void SetKeys (const AESKey& layerKey, const AESKey& ivKey)
|
||||||
|
{
|
||||||
|
m_LayerDecryption.SetKey (layerKey);
|
||||||
|
m_IVDecryption.SetKey (ivKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ECBDecryption m_IVDecryption;
|
||||||
|
#ifdef AESNI
|
||||||
|
ECBDecryption m_LayerDecryption;
|
||||||
|
#else
|
||||||
|
CBCDecryption m_LayerDecryption;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
void InitCrypto (bool precomputation);
|
||||||
|
void TerminateCrypto ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
269
Daemon.cpp
Normal file
269
Daemon.cpp
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Base.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "NTCPSession.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "HTTP.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "Garlic.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "HTTPServer.h"
|
||||||
|
#include "I2PControl.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
|
||||||
|
#ifdef USE_UPNP
|
||||||
|
#include "UPnP.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
class Daemon_Singleton::Daemon_Singleton_Private
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Daemon_Singleton_Private() {};
|
||||||
|
~Daemon_Singleton_Private() {};
|
||||||
|
|
||||||
|
std::unique_ptr<i2p::http::HTTPServer> httpServer;
|
||||||
|
std::unique_ptr<i2p::client::I2PControlService> m_I2PControlService;
|
||||||
|
|
||||||
|
#ifdef USE_UPNP
|
||||||
|
i2p::transport::UPnP m_UPnP;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
Daemon_Singleton::Daemon_Singleton() : running(1), d(*new Daemon_Singleton_Private()) {};
|
||||||
|
Daemon_Singleton::~Daemon_Singleton() {
|
||||||
|
delete &d;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Daemon_Singleton::IsService () const
|
||||||
|
{
|
||||||
|
bool service = false;
|
||||||
|
#ifndef _WIN32
|
||||||
|
i2p::config::GetOption("service", service);
|
||||||
|
#endif
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Daemon_Singleton::init(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
i2p::config::Init();
|
||||||
|
i2p::config::ParseCmdline(argc, argv);
|
||||||
|
|
||||||
|
std::string config; i2p::config::GetOption("conf", config);
|
||||||
|
std::string datadir; i2p::config::GetOption("datadir", datadir);
|
||||||
|
i2p::fs::DetectDataDir(datadir, IsService());
|
||||||
|
i2p::fs::Init();
|
||||||
|
|
||||||
|
datadir = i2p::fs::GetDataDir();
|
||||||
|
// TODO: drop old name detection in v2.8.0
|
||||||
|
if (config == "")
|
||||||
|
{
|
||||||
|
config = i2p::fs::DataDirPath("i2p.conf");
|
||||||
|
if (i2p::fs::Exists (config)) {
|
||||||
|
LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config);
|
||||||
|
} else {
|
||||||
|
config = i2p::fs::DataDirPath("i2pd.conf");
|
||||||
|
if (!i2p::fs::Exists (config)) {
|
||||||
|
// use i2pd.conf only if exists
|
||||||
|
config = ""; /* reset */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i2p::config::ParseConfig(config);
|
||||||
|
i2p::config::Finalize();
|
||||||
|
|
||||||
|
i2p::config::GetOption("daemon", isDaemon);
|
||||||
|
|
||||||
|
std::string logs = ""; i2p::config::GetOption("log", logs);
|
||||||
|
std::string logfile = ""; i2p::config::GetOption("logfile", logfile);
|
||||||
|
std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel);
|
||||||
|
|
||||||
|
/* setup logging */
|
||||||
|
if (isDaemon && (logs == "" || logs == "stdout"))
|
||||||
|
logs = "file";
|
||||||
|
|
||||||
|
i2p::log::Logger().SetLogLevel(loglevel);
|
||||||
|
if (logs == "file") {
|
||||||
|
if (logfile == "")
|
||||||
|
logfile = i2p::fs::DataDirPath("i2pd.log");
|
||||||
|
LogPrint(eLogInfo, "Log: will send messages to ", logfile);
|
||||||
|
i2p::log::Logger().SendTo (logfile);
|
||||||
|
#ifndef _WIN32
|
||||||
|
} else if (logs == "syslog") {
|
||||||
|
LogPrint(eLogInfo, "Log: will send messages to syslog");
|
||||||
|
i2p::log::Logger().SendTo("i2pd", LOG_DAEMON);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
// use stdout -- default
|
||||||
|
}
|
||||||
|
i2p::log::Logger().Ready();
|
||||||
|
|
||||||
|
LogPrint(eLogInfo, "i2pd v", VERSION, " starting");
|
||||||
|
LogPrint(eLogDebug, "FS: main config file: ", config);
|
||||||
|
LogPrint(eLogDebug, "FS: data directory: ", datadir);
|
||||||
|
|
||||||
|
bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation);
|
||||||
|
i2p::crypto::InitCrypto (precomputation);
|
||||||
|
i2p::context.Init ();
|
||||||
|
|
||||||
|
uint16_t port; i2p::config::GetOption("port", port);
|
||||||
|
if (!i2p::config::IsDefault("port"))
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port);
|
||||||
|
i2p::context.UpdatePort (port);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string host; i2p::config::GetOption("host", host);
|
||||||
|
if (!i2p::config::IsDefault("host"))
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host);
|
||||||
|
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ipv6; i2p::config::GetOption("ipv6", ipv6);
|
||||||
|
bool ipv4; i2p::config::GetOption("ipv4", ipv4);
|
||||||
|
bool transit; i2p::config::GetOption("notransit", transit);
|
||||||
|
i2p::context.SetSupportsV6 (ipv6);
|
||||||
|
i2p::context.SetSupportsV4 (ipv4);
|
||||||
|
i2p::context.SetAcceptsTunnels (!transit);
|
||||||
|
uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
|
||||||
|
SetMaxNumTransitTunnels (transitTunnels);
|
||||||
|
|
||||||
|
bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill);
|
||||||
|
if (isFloodfill) {
|
||||||
|
LogPrint(eLogInfo, "Daemon: router will be floodfill");
|
||||||
|
i2p::context.SetFloodfill (true);
|
||||||
|
} else {
|
||||||
|
i2p::context.SetFloodfill (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this section also honors 'floodfill' flag, if set above */
|
||||||
|
std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth);
|
||||||
|
if (bandwidth.length () > 0)
|
||||||
|
{
|
||||||
|
if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X')
|
||||||
|
{
|
||||||
|
i2p::context.SetBandwidth (bandwidth[0]);
|
||||||
|
LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto value = std::atoi(bandwidth.c_str());
|
||||||
|
if (value > 0)
|
||||||
|
{
|
||||||
|
i2p::context.SetBandwidth (value);
|
||||||
|
LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'");
|
||||||
|
i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isFloodfill)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'");
|
||||||
|
i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'");
|
||||||
|
i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string family; i2p::config::GetOption("family", family);
|
||||||
|
i2p::context.SetFamily (family);
|
||||||
|
if (family.length () > 0)
|
||||||
|
LogPrint(eLogInfo, "Daemon: family set to ", family);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Daemon_Singleton::start()
|
||||||
|
{
|
||||||
|
bool http; i2p::config::GetOption("http.enabled", http);
|
||||||
|
if (http) {
|
||||||
|
std::string httpAddr; i2p::config::GetOption("http.address", httpAddr);
|
||||||
|
uint16_t httpPort; i2p::config::GetOption("http.port", httpPort);
|
||||||
|
LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort);
|
||||||
|
d.httpServer = std::unique_ptr<i2p::http::HTTPServer>(new i2p::http::HTTPServer(httpAddr, httpPort));
|
||||||
|
d.httpServer->Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint(eLogInfo, "Daemon: starting NetDB");
|
||||||
|
i2p::data::netdb.Start();
|
||||||
|
|
||||||
|
#ifdef USE_UPNP
|
||||||
|
LogPrint(eLogInfo, "Daemon: starting UPnP");
|
||||||
|
d.m_UPnP.Start ();
|
||||||
|
#endif
|
||||||
|
LogPrint(eLogInfo, "Daemon: starting Transports");
|
||||||
|
i2p::transport::transports.Start();
|
||||||
|
|
||||||
|
LogPrint(eLogInfo, "Daemon: starting Tunnels");
|
||||||
|
i2p::tunnel::tunnels.Start();
|
||||||
|
|
||||||
|
LogPrint(eLogInfo, "Daemon: starting Client");
|
||||||
|
i2p::client::context.Start ();
|
||||||
|
|
||||||
|
// I2P Control Protocol
|
||||||
|
bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol);
|
||||||
|
if (i2pcontrol) {
|
||||||
|
std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr);
|
||||||
|
uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort);
|
||||||
|
LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort);
|
||||||
|
d.m_I2PControlService = std::unique_ptr<i2p::client::I2PControlService>(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort));
|
||||||
|
d.m_I2PControlService->Start ();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Daemon_Singleton::stop()
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Daemon: shutting down");
|
||||||
|
LogPrint(eLogInfo, "Daemon: stopping Client");
|
||||||
|
i2p::client::context.Stop();
|
||||||
|
LogPrint(eLogInfo, "Daemon: stopping Tunnels");
|
||||||
|
i2p::tunnel::tunnels.Stop();
|
||||||
|
#ifdef USE_UPNP
|
||||||
|
LogPrint(eLogInfo, "Daemon: stopping UPnP");
|
||||||
|
d.m_UPnP.Stop ();
|
||||||
|
#endif
|
||||||
|
LogPrint(eLogInfo, "Daemon: stopping Transports");
|
||||||
|
i2p::transport::transports.Stop();
|
||||||
|
LogPrint(eLogInfo, "Daemon: stopping NetDB");
|
||||||
|
i2p::data::netdb.Stop();
|
||||||
|
if (d.httpServer) {
|
||||||
|
LogPrint(eLogInfo, "Daemon: stopping HTTP Server");
|
||||||
|
d.httpServer->Stop();
|
||||||
|
d.httpServer = nullptr;
|
||||||
|
}
|
||||||
|
if (d.m_I2PControlService)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Daemon: stopping I2PControl");
|
||||||
|
d.m_I2PControlService->Stop ();
|
||||||
|
d.m_I2PControlService = nullptr;
|
||||||
|
}
|
||||||
|
i2p::crypto::TerminateCrypto ();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
Daemon.h
Normal file
84
Daemon.h
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#ifndef DAEMON_H__
|
||||||
|
#define DAEMON_H__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define Daemon i2p::util::DaemonWin32::Instance()
|
||||||
|
#else
|
||||||
|
#define Daemon i2p::util::DaemonLinux::Instance()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
class Daemon_Singleton_Private;
|
||||||
|
class Daemon_Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual bool init(int argc, char* argv[]);
|
||||||
|
virtual bool start();
|
||||||
|
virtual bool stop();
|
||||||
|
virtual void run () {};
|
||||||
|
|
||||||
|
bool isLogging;
|
||||||
|
bool isDaemon;
|
||||||
|
|
||||||
|
bool running;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Daemon_Singleton();
|
||||||
|
virtual ~Daemon_Singleton();
|
||||||
|
|
||||||
|
bool IsService () const;
|
||||||
|
|
||||||
|
// d-pointer for httpServer, httpProxy, etc.
|
||||||
|
class Daemon_Singleton_Private;
|
||||||
|
Daemon_Singleton_Private &d;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
class DaemonWin32 : public Daemon_Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static DaemonWin32& Instance()
|
||||||
|
{
|
||||||
|
static DaemonWin32 instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool init(int argc, char* argv[]);
|
||||||
|
bool start();
|
||||||
|
bool stop();
|
||||||
|
void run ();
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
class DaemonLinux : public Daemon_Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static DaemonLinux& Instance()
|
||||||
|
{
|
||||||
|
static DaemonLinux instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start();
|
||||||
|
bool stop();
|
||||||
|
void run ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string pidfile;
|
||||||
|
int pidFH;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
int gracefullShutdownInterval; // in seconds
|
||||||
|
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DAEMON_H__
|
||||||
150
DaemonLinux.cpp
Normal file
150
DaemonLinux.cpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
|
||||||
|
void handle_signal(int sig)
|
||||||
|
{
|
||||||
|
switch (sig)
|
||||||
|
{
|
||||||
|
case SIGHUP:
|
||||||
|
LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log...");
|
||||||
|
i2p::log::Logger().Reopen ();
|
||||||
|
break;
|
||||||
|
case SIGINT:
|
||||||
|
if (i2p::context.AcceptsTunnels () && !Daemon.gracefullShutdownInterval)
|
||||||
|
{
|
||||||
|
i2p::context.SetAcceptsTunnels (false);
|
||||||
|
Daemon.gracefullShutdownInterval = 10*60; // 10 minutes
|
||||||
|
LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefullShutdownInterval, " seconds");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Daemon.running = 0;
|
||||||
|
break;
|
||||||
|
case SIGABRT:
|
||||||
|
case SIGTERM:
|
||||||
|
Daemon.running = 0; // Exit loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
bool DaemonLinux::start()
|
||||||
|
{
|
||||||
|
if (isDaemon == 1)
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
pid = fork();
|
||||||
|
if (pid > 0) // parent
|
||||||
|
::exit (EXIT_SUCCESS);
|
||||||
|
|
||||||
|
if (pid < 0) // error
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// child
|
||||||
|
umask(S_IWGRP | S_IRWXO); // 0027
|
||||||
|
int sid = setsid();
|
||||||
|
if (sid < 0)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "Daemon: could not create process group.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string d = i2p::fs::GetDataDir();
|
||||||
|
if (chdir(d.c_str()) != 0)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close stdin/stdout/stderr descriptors
|
||||||
|
freopen("/dev/null", "r", stdin);
|
||||||
|
freopen("/dev/null", "w", stdout);
|
||||||
|
freopen("/dev/null", "w", stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pidfile
|
||||||
|
// this code is c-styled and a bit ugly, but we need fd for locking pidfile
|
||||||
|
std::string pidfile; i2p::config::GetOption("pidfile", pidfile);
|
||||||
|
if (pidfile == "") {
|
||||||
|
pidfile = i2p::fs::DataDirPath("i2pd.pid");
|
||||||
|
}
|
||||||
|
if (pidfile != "") {
|
||||||
|
pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600);
|
||||||
|
if (pidFH < 0)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (lockf(pidFH, F_TLOCK, 0) != 0)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char pid[10];
|
||||||
|
sprintf(pid, "%d\n", getpid());
|
||||||
|
ftruncate(pidFH, 0);
|
||||||
|
if (write(pidFH, pid, strlen(pid)) < 0)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gracefullShutdownInterval = 0; // not specified
|
||||||
|
|
||||||
|
// Signal handler
|
||||||
|
struct sigaction sa;
|
||||||
|
sa.sa_handler = handle_signal;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = SA_RESTART;
|
||||||
|
sigaction(SIGHUP, &sa, 0);
|
||||||
|
sigaction(SIGABRT, &sa, 0);
|
||||||
|
sigaction(SIGTERM, &sa, 0);
|
||||||
|
sigaction(SIGINT, &sa, 0);
|
||||||
|
|
||||||
|
return Daemon_Singleton::start();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonLinux::stop()
|
||||||
|
{
|
||||||
|
i2p::fs::Remove(pidfile);
|
||||||
|
|
||||||
|
return Daemon_Singleton::stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonLinux::run ()
|
||||||
|
{
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for (std::chrono::seconds(1));
|
||||||
|
if (gracefullShutdownInterval)
|
||||||
|
{
|
||||||
|
gracefullShutdownInterval--; // - 1 second
|
||||||
|
if (gracefullShutdownInterval <= 0)
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "Graceful shutdown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
113
DaemonWin32.cpp
Normal file
113
DaemonWin32.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#include <thread>
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include "Win32/Win32Service.h"
|
||||||
|
#ifdef WIN32_APP
|
||||||
|
#include "Win32/Win32App.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
bool DaemonWin32::init(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
setlocale(LC_CTYPE, "");
|
||||||
|
SetConsoleCP(1251);
|
||||||
|
SetConsoleOutputCP(1251);
|
||||||
|
|
||||||
|
if (!Daemon_Singleton::init(argc, argv))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl);
|
||||||
|
if (serviceControl == "install")
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service");
|
||||||
|
InstallService(
|
||||||
|
SERVICE_NAME, // Name of service
|
||||||
|
SERVICE_DISPLAY_NAME, // Name to display
|
||||||
|
SERVICE_START_TYPE, // Service start type
|
||||||
|
SERVICE_DEPENDENCIES, // Dependencies
|
||||||
|
SERVICE_ACCOUNT, // Service running account
|
||||||
|
SERVICE_PASSWORD // Password of the account
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (serviceControl == "remove")
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service");
|
||||||
|
UninstallService(SERVICE_NAME);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDaemon == 1)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "Daemon: running as service");
|
||||||
|
I2PService service(SERVICE_NAME);
|
||||||
|
if (!I2PService::Run(service))
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint(eLogDebug, "Daemon: running as user");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonWin32::start()
|
||||||
|
{
|
||||||
|
setlocale(LC_CTYPE, "");
|
||||||
|
SetConsoleCP(1251);
|
||||||
|
SetConsoleOutputCP(1251);
|
||||||
|
setlocale(LC_ALL, "Russian");
|
||||||
|
#ifdef WIN32_APP
|
||||||
|
if (!i2p::win32::StartWin32App ()) return false;
|
||||||
|
|
||||||
|
// override log
|
||||||
|
i2p::config::SetOption("log", std::string ("file"));
|
||||||
|
#endif
|
||||||
|
bool ret = Daemon_Singleton::start();
|
||||||
|
if (ret && i2p::log::Logger().GetLogType() == eLogFile)
|
||||||
|
{
|
||||||
|
// TODO: find out where this garbage to console comes from
|
||||||
|
SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
|
||||||
|
SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
|
||||||
|
}
|
||||||
|
bool insomnia; i2p::config::GetOption("insomnia", insomnia);
|
||||||
|
if (insomnia)
|
||||||
|
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonWin32::stop()
|
||||||
|
{
|
||||||
|
#ifdef WIN32_APP
|
||||||
|
i2p::win32::StopWin32App ();
|
||||||
|
#endif
|
||||||
|
return Daemon_Singleton::stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonWin32::run ()
|
||||||
|
{
|
||||||
|
#ifdef WIN32_APP
|
||||||
|
i2p::win32::RunWin32App ();
|
||||||
|
#else
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for (std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
143
Datagram.cpp
Normal file
143
Datagram.cpp
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "TunnelBase.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "Datagram.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace datagram
|
||||||
|
{
|
||||||
|
DatagramDestination::DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner):
|
||||||
|
m_Owner (owner), m_Receiver (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DatagramDestination::~DatagramDestination ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort)
|
||||||
|
{
|
||||||
|
uint8_t buf[MAX_DATAGRAM_SIZE];
|
||||||
|
auto identityLen = m_Owner->GetIdentity ()->ToBuffer (buf, MAX_DATAGRAM_SIZE);
|
||||||
|
uint8_t * signature = buf + identityLen;
|
||||||
|
auto signatureLen = m_Owner->GetIdentity ()->GetSignatureLen ();
|
||||||
|
uint8_t * buf1 = signature + signatureLen;
|
||||||
|
size_t headerLen = identityLen + signatureLen;
|
||||||
|
|
||||||
|
memcpy (buf1, payload, len);
|
||||||
|
if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
|
||||||
|
{
|
||||||
|
uint8_t hash[32];
|
||||||
|
SHA256(buf1, len, hash);
|
||||||
|
m_Owner->Sign (hash, 32, signature);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_Owner->Sign (buf1, len, signature);
|
||||||
|
|
||||||
|
auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort);
|
||||||
|
auto remote = m_Owner->FindLeaseSet (ident);
|
||||||
|
if (remote)
|
||||||
|
m_Owner->GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote));
|
||||||
|
else
|
||||||
|
m_Owner->RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> remote, std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (remote)
|
||||||
|
SendMsg (msg, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatagramDestination::SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote)
|
||||||
|
{
|
||||||
|
auto outboundTunnel = m_Owner->GetTunnelPool ()->GetNextOutboundTunnel ();
|
||||||
|
auto leases = remote->GetNonExpiredLeases ();
|
||||||
|
if (!leases.empty () && outboundTunnel)
|
||||||
|
{
|
||||||
|
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
|
||||||
|
uint32_t i = rand () % leases.size ();
|
||||||
|
auto garlic = m_Owner->WrapMessage (remote, msg, true);
|
||||||
|
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||||
|
{
|
||||||
|
i2p::tunnel::eDeliveryTypeTunnel,
|
||||||
|
leases[i]->tunnelGateway, leases[i]->tunnelID,
|
||||||
|
garlic
|
||||||
|
});
|
||||||
|
outboundTunnel->SendTunnelDataMsg (msgs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (outboundTunnel)
|
||||||
|
LogPrint (eLogWarning, "Failed to send datagram. All leases expired");
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Failed to send datagram. No outbound tunnels");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
i2p::data::IdentityEx identity;
|
||||||
|
size_t identityLen = identity.FromBuffer (buf, len);
|
||||||
|
const uint8_t * signature = buf + identityLen;
|
||||||
|
size_t headerLen = identityLen + identity.GetSignatureLen ();
|
||||||
|
|
||||||
|
bool verified = false;
|
||||||
|
if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
|
||||||
|
{
|
||||||
|
uint8_t hash[32];
|
||||||
|
SHA256(buf + headerLen, len - headerLen, hash);
|
||||||
|
verified = identity.Verify (hash, 32, signature);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
verified = identity.Verify (buf + headerLen, len - headerLen, signature);
|
||||||
|
|
||||||
|
if (verified)
|
||||||
|
{
|
||||||
|
auto it = m_ReceiversByPorts.find (toPort);
|
||||||
|
if (it != m_ReceiversByPorts.end ())
|
||||||
|
it->second (identity, fromPort, toPort, buf + headerLen, len -headerLen);
|
||||||
|
else if (m_Receiver != nullptr)
|
||||||
|
m_Receiver (identity, fromPort, toPort, buf + headerLen, len -headerLen);
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Receiver for datagram is not set");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Datagram signature verification failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
// unzip it
|
||||||
|
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
|
||||||
|
size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE);
|
||||||
|
if (uncompressedLen)
|
||||||
|
HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPMessage ();
|
||||||
|
uint8_t * buf = msg->GetPayload ();
|
||||||
|
buf += 4; // reserve for length
|
||||||
|
size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len);
|
||||||
|
if (size)
|
||||||
|
{
|
||||||
|
htobe32buf (msg->GetPayload (), size); // length
|
||||||
|
htobe16buf (buf + 4, fromPort); // source port
|
||||||
|
htobe16buf (buf + 6, toPort); // destination port
|
||||||
|
buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
|
||||||
|
msg->len += size + 4;
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
msg = nullptr;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
61
Datagram.h
Normal file
61
Datagram.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef DATAGRAM_H__
|
||||||
|
#define DATAGRAM_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
class ClientDestination;
|
||||||
|
}
|
||||||
|
namespace datagram
|
||||||
|
{
|
||||||
|
const size_t MAX_DATAGRAM_SIZE = 32768;
|
||||||
|
class DatagramDestination
|
||||||
|
{
|
||||||
|
typedef std::function<void (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)> Receiver;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner);
|
||||||
|
~DatagramDestination ();
|
||||||
|
|
||||||
|
void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort = 0, uint16_t toPort = 0);
|
||||||
|
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
|
||||||
|
void ResetReceiver () { m_Receiver = nullptr; };
|
||||||
|
|
||||||
|
void SetReceiver (const Receiver& receiver, uint16_t port) { m_ReceiversByPorts[port] = receiver; };
|
||||||
|
void ResetReceiver (uint16_t port) { m_ReceiversByPorts.erase (port); };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void HandleLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort);
|
||||||
|
void SendMsg (std::shared_ptr<I2NPMessage> msg, std::shared_ptr<const i2p::data::LeaseSet> remote);
|
||||||
|
void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::client::ClientDestination> m_Owner;
|
||||||
|
Receiver m_Receiver; // default
|
||||||
|
std::map<uint16_t, Receiver> m_ReceiversByPorts;
|
||||||
|
|
||||||
|
i2p::data::GzipInflator m_Inflator;
|
||||||
|
i2p::data::GzipDeflator m_Deflator;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
799
Destination.cpp
Normal file
799
Destination.cpp
Normal file
@@ -0,0 +1,799 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
|
||||||
|
const std::map<std::string, std::string> * params):
|
||||||
|
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
|
||||||
|
m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0),
|
||||||
|
m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service),
|
||||||
|
m_PublishVerificationTimer (m_Service), m_CleanupTimer (m_Service)
|
||||||
|
{
|
||||||
|
if (m_IsPublic)
|
||||||
|
PersistTemporaryKeys ();
|
||||||
|
else
|
||||||
|
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
|
||||||
|
int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH;
|
||||||
|
int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH;
|
||||||
|
int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY;
|
||||||
|
int outboundTunnelsQuantity = DEFAULT_OUTBOUND_TUNNELS_QUANTITY;
|
||||||
|
int numTags = DEFAULT_TAGS_TO_SEND;
|
||||||
|
std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
|
||||||
|
if (params)
|
||||||
|
{
|
||||||
|
auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH);
|
||||||
|
if (it != params->end ())
|
||||||
|
{
|
||||||
|
|
||||||
|
int len = i2p::util::lexical_cast<int>(it->second, inboundTunnelLen);
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
inboundTunnelLen = len;
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", inboundTunnelLen);
|
||||||
|
}
|
||||||
|
it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH);
|
||||||
|
if (it != params->end ())
|
||||||
|
{
|
||||||
|
|
||||||
|
int len = i2p::util::lexical_cast<int>(it->second, outboundTunnelLen);
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
outboundTunnelLen = len;
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", outboundTunnelLen);
|
||||||
|
}
|
||||||
|
it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY);
|
||||||
|
if (it != params->end ())
|
||||||
|
{
|
||||||
|
int quantity = i2p::util::lexical_cast<int>(it->second, inboundTunnelsQuantity);
|
||||||
|
if (quantity > 0)
|
||||||
|
{
|
||||||
|
inboundTunnelsQuantity = quantity;
|
||||||
|
LogPrint (eLogInfo, "Destination: Inbound tunnels quantity set to ", quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY);
|
||||||
|
if (it != params->end ())
|
||||||
|
{
|
||||||
|
int quantity = i2p::util::lexical_cast<int>(it->second, outboundTunnelsQuantity);
|
||||||
|
if (quantity > 0)
|
||||||
|
{
|
||||||
|
outboundTunnelsQuantity = quantity;
|
||||||
|
LogPrint (eLogInfo, "Destination: Outbound tunnels quantity set to ", quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it = params->find (I2CP_PARAM_TAGS_TO_SEND);
|
||||||
|
if (it != params->end ())
|
||||||
|
{
|
||||||
|
int tagsToSend = i2p::util::lexical_cast<int>(it->second, numTags);
|
||||||
|
if (tagsToSend > 0)
|
||||||
|
{
|
||||||
|
numTags = tagsToSend;
|
||||||
|
LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it = params->find (I2CP_PARAM_EXPLICIT_PEERS);
|
||||||
|
if (it != params->end ())
|
||||||
|
{
|
||||||
|
explicitPeers = std::make_shared<std::vector<i2p::data::IdentHash> >();
|
||||||
|
std::stringstream ss(it->second);
|
||||||
|
std::string b64;
|
||||||
|
while (std::getline (ss, b64, ','))
|
||||||
|
{
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
ident.FromBase64 (b64);
|
||||||
|
explicitPeers->push_back (ident);
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Destination: Explicit peers set to ", it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetNumTags (numTags);
|
||||||
|
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity);
|
||||||
|
if (explicitPeers)
|
||||||
|
m_Pool->SetExplicitPeers (explicitPeers);
|
||||||
|
if (m_IsPublic)
|
||||||
|
LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created");
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientDestination::~ClientDestination ()
|
||||||
|
{
|
||||||
|
if (m_IsRunning)
|
||||||
|
Stop ();
|
||||||
|
for (auto it: m_LeaseSetRequests)
|
||||||
|
if (it.second->requestComplete) it.second->requestComplete (nullptr);
|
||||||
|
m_LeaseSetRequests.clear ();
|
||||||
|
if (m_Pool)
|
||||||
|
i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool);
|
||||||
|
if (m_DatagramDestination)
|
||||||
|
delete m_DatagramDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Service.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Destination: runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::Start ()
|
||||||
|
{
|
||||||
|
if (!m_IsRunning)
|
||||||
|
{
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Pool->SetLocalDestination (shared_from_this ());
|
||||||
|
m_Pool->SetActive (true);
|
||||||
|
m_Thread = new std::thread (std::bind (&ClientDestination::Run, shared_from_this ()));
|
||||||
|
m_StreamingDestination = std::make_shared<i2p::stream::StreamingDestination> (shared_from_this ()); // TODO:
|
||||||
|
m_StreamingDestination->Start ();
|
||||||
|
for (auto it: m_StreamingDestinationsByPorts)
|
||||||
|
it.second->Start ();
|
||||||
|
|
||||||
|
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
|
||||||
|
m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::Stop ()
|
||||||
|
{
|
||||||
|
if (m_IsRunning)
|
||||||
|
{
|
||||||
|
m_CleanupTimer.cancel ();
|
||||||
|
m_PublishConfirmationTimer.cancel ();
|
||||||
|
m_PublishVerificationTimer.cancel ();
|
||||||
|
m_IsRunning = false;
|
||||||
|
m_StreamingDestination->Stop ();
|
||||||
|
m_StreamingDestination = nullptr;
|
||||||
|
for (auto it: m_StreamingDestinationsByPorts)
|
||||||
|
it.second->Stop ();
|
||||||
|
if (m_DatagramDestination)
|
||||||
|
{
|
||||||
|
auto d = m_DatagramDestination;
|
||||||
|
m_DatagramDestination = nullptr;
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
if (m_Pool)
|
||||||
|
{
|
||||||
|
m_Pool->SetLocalDestination (nullptr);
|
||||||
|
i2p::tunnel::tunnels.StopTunnelPool (m_Pool);
|
||||||
|
}
|
||||||
|
m_Service.stop ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> ClientDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
auto it = m_RemoteLeaseSets.find (ident);
|
||||||
|
if (it != m_RemoteLeaseSets.end ())
|
||||||
|
{
|
||||||
|
if (!it->second->IsExpired ())
|
||||||
|
return it->second;
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Destination: remote LeaseSet expired");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto ls = i2p::data::netdb.FindLeaseSet (ident);
|
||||||
|
if (ls && !ls->IsExpired ())
|
||||||
|
{
|
||||||
|
ls->PopulateLeases (); // since we don't store them in netdb
|
||||||
|
m_RemoteLeaseSets[ident] = ls;
|
||||||
|
return ls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> ClientDestination::GetLeaseSet ()
|
||||||
|
{
|
||||||
|
if (!m_Pool) return nullptr;
|
||||||
|
if (!m_LeaseSet)
|
||||||
|
UpdateLeaseSet ();
|
||||||
|
return m_LeaseSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::UpdateLeaseSet ()
|
||||||
|
{
|
||||||
|
m_LeaseSet.reset (new i2p::data::LeaseSet (m_Pool));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClientDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t k[32], t[32];
|
||||||
|
} data;
|
||||||
|
memcpy (data.k, key, 32);
|
||||||
|
memcpy (data.t, tag, 32);
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
m_Service.post ([s,data](void)
|
||||||
|
{
|
||||||
|
s->AddSessionKey (data.k, data.t);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
m_Service.post (std::bind (&ClientDestination::HandleGarlicMessage, shared_from_this (), msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, shared_from_this (), msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||||
|
{
|
||||||
|
uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET];
|
||||||
|
switch (typeID)
|
||||||
|
{
|
||||||
|
case eI2NPData:
|
||||||
|
HandleDataMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
|
||||||
|
break;
|
||||||
|
case eI2NPDeliveryStatus:
|
||||||
|
// we assume tunnel tests non-encrypted
|
||||||
|
HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||||
|
break;
|
||||||
|
case eI2NPDatabaseStore:
|
||||||
|
HandleDatabaseStoreMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
|
||||||
|
break;
|
||||||
|
case eI2NPDatabaseSearchReply:
|
||||||
|
HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET);
|
||||||
|
size_t offset = DATABASE_STORE_HEADER_SIZE;
|
||||||
|
if (replyToken)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore");
|
||||||
|
offset += 36;
|
||||||
|
}
|
||||||
|
std::shared_ptr<i2p::data::LeaseSet> leaseSet;
|
||||||
|
if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Remote LeaseSet");
|
||||||
|
auto it = m_RemoteLeaseSets.find (buf + DATABASE_STORE_KEY_OFFSET);
|
||||||
|
if (it != m_RemoteLeaseSets.end ())
|
||||||
|
{
|
||||||
|
leaseSet = it->second;
|
||||||
|
if (leaseSet->IsNewer (buf + offset, len - offset))
|
||||||
|
{
|
||||||
|
leaseSet->Update (buf + offset, len - offset);
|
||||||
|
if (leaseSet->IsValid ())
|
||||||
|
LogPrint (eLogDebug, "Remote LeaseSet updated");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Remote LeaseSet update failed");
|
||||||
|
m_RemoteLeaseSets.erase (it);
|
||||||
|
leaseSet = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogDebug, "Remote LeaseSet is older. Not updated");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset);
|
||||||
|
if (leaseSet->IsValid ())
|
||||||
|
{
|
||||||
|
if (leaseSet->GetIdentHash () != GetIdentHash ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "New remote LeaseSet added");
|
||||||
|
m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogDebug, "Own remote LeaseSet dropped");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "New remote LeaseSet failed");
|
||||||
|
leaseSet = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped");
|
||||||
|
|
||||||
|
auto it1 = m_LeaseSetRequests.find (buf + DATABASE_STORE_KEY_OFFSET);
|
||||||
|
if (it1 != m_LeaseSetRequests.end ())
|
||||||
|
{
|
||||||
|
it1->second->requestTimeoutTimer.cancel ();
|
||||||
|
if (it1->second->requestComplete) it1->second->requestComplete (leaseSet);
|
||||||
|
m_LeaseSetRequests.erase (it1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
i2p::data::IdentHash key (buf);
|
||||||
|
int num = buf[32]; // num
|
||||||
|
LogPrint (eLogDebug, "Destination: DatabaseSearchReply for ", key.ToBase64 (), " num=", num);
|
||||||
|
auto it = m_LeaseSetRequests.find (key);
|
||||||
|
if (it != m_LeaseSetRequests.end ())
|
||||||
|
{
|
||||||
|
auto request = it->second;
|
||||||
|
bool found = false;
|
||||||
|
if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
i2p::data::IdentHash peerHash (buf + 33 + i*32);
|
||||||
|
auto floodfill = i2p::data::netdb.FindRouter (peerHash);
|
||||||
|
if (floodfill)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", peerHash.ToBase64 ());
|
||||||
|
if (SendLeaseSetRequest (key, floodfill, request))
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message
|
||||||
|
i2p::data::netdb.RequestDestination (peerHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
LogPrint (eLogError, "Destination: Suggested floodfills are not presented in netDb");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
if (request->requestComplete) request->requestComplete (nullptr);
|
||||||
|
m_LeaseSetRequests.erase (key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
|
||||||
|
if (msgID == m_PublishReplyToken)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed");
|
||||||
|
m_ExcludedFloodfills.clear ();
|
||||||
|
m_PublishReplyToken = 0;
|
||||||
|
// schedule verification
|
||||||
|
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
|
||||||
|
m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::SetLeaseSetUpdated ()
|
||||||
|
{
|
||||||
|
i2p::garlic::GarlicDestination::SetLeaseSetUpdated ();
|
||||||
|
UpdateLeaseSet ();
|
||||||
|
if (m_IsPublic)
|
||||||
|
{
|
||||||
|
m_PublishVerificationTimer.cancel ();
|
||||||
|
Publish ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::Publish ()
|
||||||
|
{
|
||||||
|
if (!m_LeaseSet || !m_Pool)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Destination: Can't publish non-existing LeaseSet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_PublishReplyToken)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Destination: Publishing LeaseSet is pending");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto outbound = m_Pool->GetNextOutboundTunnel ();
|
||||||
|
if (!outbound)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills);
|
||||||
|
if (!floodfill)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
|
||||||
|
m_ExcludedFloodfills.clear ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
|
||||||
|
LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ());
|
||||||
|
RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4);
|
||||||
|
auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken));
|
||||||
|
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
|
||||||
|
m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
if (m_PublishReplyToken)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again");
|
||||||
|
m_PublishReplyToken = 0;
|
||||||
|
Publish ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
RequestLeaseSet (GetIdentHash (),
|
||||||
|
// "this" added due to bug in gcc 4.7-4.8
|
||||||
|
[s,this](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
|
||||||
|
{
|
||||||
|
if (leaseSet)
|
||||||
|
{
|
||||||
|
if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet)
|
||||||
|
{
|
||||||
|
// we got latest LeasetSet
|
||||||
|
LogPrint (eLogDebug, "Destination: published LeaseSet verified");
|
||||||
|
s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL));
|
||||||
|
s->m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, s, std::placeholders::_1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet");
|
||||||
|
// we have to publish again
|
||||||
|
s->Publish ();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
uint32_t length = bufbe32toh (buf);
|
||||||
|
buf += 4;
|
||||||
|
// we assume I2CP payload
|
||||||
|
uint16_t fromPort = bufbe16toh (buf + 4), // source
|
||||||
|
toPort = bufbe16toh (buf + 6); // destination
|
||||||
|
switch (buf[9])
|
||||||
|
{
|
||||||
|
case PROTOCOL_TYPE_STREAMING:
|
||||||
|
{
|
||||||
|
// streaming protocol
|
||||||
|
auto dest = GetStreamingDestination (toPort);
|
||||||
|
if (dest)
|
||||||
|
dest->HandleDataMessagePayload (buf, length);
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Destination: Missing streaming destination");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PROTOCOL_TYPE_DATAGRAM:
|
||||||
|
// datagram protocol
|
||||||
|
if (m_DatagramDestination)
|
||||||
|
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length);
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Destination: Missing datagram destination");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port)
|
||||||
|
{
|
||||||
|
if (!streamRequestComplete)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Destination: request callback is not specified in CreateStream");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto leaseSet = FindLeaseSet (dest);
|
||||||
|
if (leaseSet)
|
||||||
|
streamRequestComplete(CreateStream (leaseSet, port));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
RequestDestination (dest,
|
||||||
|
[s, streamRequestComplete, port](std::shared_ptr<i2p::data::LeaseSet> ls)
|
||||||
|
{
|
||||||
|
if (ls)
|
||||||
|
streamRequestComplete(s->CreateStream (ls, port));
|
||||||
|
else
|
||||||
|
streamRequestComplete (nullptr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port)
|
||||||
|
{
|
||||||
|
if (m_StreamingDestination)
|
||||||
|
return m_StreamingDestination->CreateNewOutgoingStream (remote, port);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::GetStreamingDestination (int port) const
|
||||||
|
{
|
||||||
|
if (port)
|
||||||
|
{
|
||||||
|
auto it = m_StreamingDestinationsByPorts.find (port);
|
||||||
|
if (it != m_StreamingDestinationsByPorts.end ())
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
// if port is zero or not found, use default destination
|
||||||
|
return m_StreamingDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor)
|
||||||
|
{
|
||||||
|
if (m_StreamingDestination)
|
||||||
|
m_StreamingDestination->SetAcceptor (acceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::StopAcceptingStreams ()
|
||||||
|
{
|
||||||
|
if (m_StreamingDestination)
|
||||||
|
m_StreamingDestination->ResetAcceptor ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClientDestination::IsAcceptingStreams () const
|
||||||
|
{
|
||||||
|
if (m_StreamingDestination)
|
||||||
|
return m_StreamingDestination->IsAcceptorSet ();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::CreateStreamingDestination (int port, bool gzip)
|
||||||
|
{
|
||||||
|
auto dest = std::make_shared<i2p::stream::StreamingDestination> (shared_from_this (), port, gzip);
|
||||||
|
if (port)
|
||||||
|
m_StreamingDestinationsByPorts[port] = dest;
|
||||||
|
else // update default
|
||||||
|
m_StreamingDestination = dest;
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination ()
|
||||||
|
{
|
||||||
|
if (!m_DatagramDestination)
|
||||||
|
m_DatagramDestination = new i2p::datagram::DatagramDestination (shared_from_this ());
|
||||||
|
return m_DatagramDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClientDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
|
||||||
|
{
|
||||||
|
if (!m_Pool || !IsReady ())
|
||||||
|
{
|
||||||
|
if (requestComplete) requestComplete (nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_Service.post (std::bind (&ClientDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest)
|
||||||
|
{
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
m_Service.post ([dest, s](void)
|
||||||
|
{
|
||||||
|
auto it = s->m_LeaseSetRequests.find (dest);
|
||||||
|
if (it != s->m_LeaseSetRequests.end ())
|
||||||
|
{
|
||||||
|
auto requestComplete = it->second->requestComplete;
|
||||||
|
s->m_LeaseSetRequests.erase (it);
|
||||||
|
if (requestComplete) requestComplete (nullptr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
|
||||||
|
{
|
||||||
|
std::set<i2p::data::IdentHash> excluded;
|
||||||
|
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded);
|
||||||
|
if (floodfill)
|
||||||
|
{
|
||||||
|
auto request = std::make_shared<LeaseSetRequest> (m_Service);
|
||||||
|
request->requestComplete = requestComplete;
|
||||||
|
auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> >(dest,request));
|
||||||
|
if (ret.second) // inserted
|
||||||
|
{
|
||||||
|
if (!SendLeaseSetRequest (dest, floodfill, request))
|
||||||
|
{
|
||||||
|
// request failed
|
||||||
|
m_LeaseSetRequests.erase (dest);
|
||||||
|
if (request->requestComplete) request->requestComplete (nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // duplicate
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Destination: Request of LeaseSet ", dest.ToBase64 (), " is pending already");
|
||||||
|
// TODO: queue up requests
|
||||||
|
if (request->requestComplete) request->requestComplete (nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Destination: Can't request LeaseSet, no floodfills found");
|
||||||
|
if (requestComplete) requestComplete (nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest,
|
||||||
|
std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request)
|
||||||
|
{
|
||||||
|
if (!request->replyTunnel || !request->replyTunnel->IsEstablished ())
|
||||||
|
request->replyTunnel = m_Pool->GetNextInboundTunnel ();
|
||||||
|
if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found");
|
||||||
|
if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ())
|
||||||
|
request->outboundTunnel = m_Pool->GetNextOutboundTunnel ();
|
||||||
|
if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found");
|
||||||
|
|
||||||
|
if (request->replyTunnel && request->outboundTunnel)
|
||||||
|
{
|
||||||
|
request->excluded.insert (nextFloodfill->GetIdentHash ());
|
||||||
|
request->requestTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
request->requestTimeoutTimer.cancel ();
|
||||||
|
|
||||||
|
uint8_t replyKey[32], replyTag[32];
|
||||||
|
RAND_bytes (replyKey, 32); // random session key
|
||||||
|
RAND_bytes (replyTag, 32); // random session tag
|
||||||
|
AddSessionKey (replyKey, replyTag);
|
||||||
|
|
||||||
|
auto msg = WrapMessage (nextFloodfill,
|
||||||
|
CreateLeaseSetDatabaseLookupMsg (dest, request->excluded,
|
||||||
|
request->replyTunnel, replyKey, replyTag));
|
||||||
|
request->outboundTunnel->SendTunnelDataMsg (
|
||||||
|
{
|
||||||
|
i2p::tunnel::TunnelMessageBlock
|
||||||
|
{
|
||||||
|
i2p::tunnel::eDeliveryTypeRouter,
|
||||||
|
nextFloodfill->GetIdentHash (), 0, msg
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT));
|
||||||
|
request->requestTimeoutTimer.async_wait (std::bind (&ClientDestination::HandleRequestTimoutTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1, dest));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
auto it = m_LeaseSetRequests.find (dest);
|
||||||
|
if (it != m_LeaseSetRequests.end ())
|
||||||
|
{
|
||||||
|
bool done = false;
|
||||||
|
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
|
||||||
|
{
|
||||||
|
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
|
||||||
|
if (floodfill)
|
||||||
|
{
|
||||||
|
// reset tunnels, because one them might fail
|
||||||
|
it->second->outboundTunnel = nullptr;
|
||||||
|
it->second->replyTunnel = nullptr;
|
||||||
|
done = !SendLeaseSetRequest (dest, floodfill, it->second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds");
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done)
|
||||||
|
{
|
||||||
|
auto requestComplete = it->second->requestComplete;
|
||||||
|
m_LeaseSetRequests.erase (it);
|
||||||
|
if (requestComplete) requestComplete (nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::HandleCleanupTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
CleanupExpiredTags ();
|
||||||
|
CleanupRemoteLeaseSets ();
|
||||||
|
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
|
||||||
|
m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::CleanupRemoteLeaseSets ()
|
||||||
|
{
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();)
|
||||||
|
{
|
||||||
|
if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
|
||||||
|
it = m_RemoteLeaseSets.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientDestination::PersistTemporaryKeys ()
|
||||||
|
{
|
||||||
|
std::string ident = GetIdentHash().ToBase32();
|
||||||
|
std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat"));
|
||||||
|
std::ifstream f(path, std::ifstream::binary);
|
||||||
|
|
||||||
|
if (f) {
|
||||||
|
f.read ((char *)m_EncryptionPublicKey, 256);
|
||||||
|
f.read ((char *)m_EncryptionPrivateKey, 256);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p");
|
||||||
|
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
|
||||||
|
|
||||||
|
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
|
||||||
|
if (f1) {
|
||||||
|
f1.write ((char *)m_EncryptionPublicKey, 256);
|
||||||
|
f1.write ((char *)m_EncryptionPrivateKey, 256);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint(eLogError, "Destinations: Can't save keys to ", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<const i2p::stream::Stream> > ClientDestination::GetAllStreams () const
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<const i2p::stream::Stream> > ret;
|
||||||
|
if (m_StreamingDestination)
|
||||||
|
{
|
||||||
|
for (auto& it: m_StreamingDestination->GetStreams ())
|
||||||
|
ret.push_back (it.second);
|
||||||
|
}
|
||||||
|
for (auto& it: m_StreamingDestinationsByPorts)
|
||||||
|
for (auto& it1: it.second->GetStreams ())
|
||||||
|
ret.push_back (it1.second);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
167
Destination.h
Normal file
167
Destination.h
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#ifndef DESTINATION_H__
|
||||||
|
#define DESTINATION_H__
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "TunnelPool.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
#include "Garlic.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
#include "Datagram.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const uint8_t PROTOCOL_TYPE_STREAMING = 6;
|
||||||
|
const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
|
||||||
|
const uint8_t PROTOCOL_TYPE_RAW = 18;
|
||||||
|
const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
|
||||||
|
const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successfull publish
|
||||||
|
const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically
|
||||||
|
const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds
|
||||||
|
const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds
|
||||||
|
const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes
|
||||||
|
const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7;
|
||||||
|
|
||||||
|
// I2CP
|
||||||
|
const char I2CP_PARAM_INBOUND_TUNNEL_LENGTH[] = "inbound.length";
|
||||||
|
const int DEFAULT_INBOUND_TUNNEL_LENGTH = 3;
|
||||||
|
const char I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH[] = "outbound.length";
|
||||||
|
const int DEFAULT_OUTBOUND_TUNNEL_LENGTH = 3;
|
||||||
|
const char I2CP_PARAM_INBOUND_TUNNELS_QUANTITY[] = "inbound.quantity";
|
||||||
|
const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5;
|
||||||
|
const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity";
|
||||||
|
const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5;
|
||||||
|
const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers";
|
||||||
|
const int STREAM_REQUEST_TIMEOUT = 60; //in seconds
|
||||||
|
const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend";
|
||||||
|
const int DEFAULT_TAGS_TO_SEND = 40;
|
||||||
|
|
||||||
|
typedef std::function<void (std::shared_ptr<i2p::stream::Stream> stream)> StreamRequestComplete;
|
||||||
|
|
||||||
|
class ClientDestination: public i2p::garlic::GarlicDestination,
|
||||||
|
public std::enable_shared_from_this<ClientDestination>
|
||||||
|
{
|
||||||
|
typedef std::function<void (std::shared_ptr<i2p::data::LeaseSet> leaseSet)> RequestComplete;
|
||||||
|
// leaseSet = nullptr means not found
|
||||||
|
struct LeaseSetRequest
|
||||||
|
{
|
||||||
|
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
|
||||||
|
std::set<i2p::data::IdentHash> excluded;
|
||||||
|
uint64_t requestTime;
|
||||||
|
boost::asio::deadline_timer requestTimeoutTimer;
|
||||||
|
RequestComplete requestComplete;
|
||||||
|
std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
|
||||||
|
std::shared_ptr<i2p::tunnel::InboundTunnel> replyTunnel;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
|
||||||
|
~ClientDestination ();
|
||||||
|
|
||||||
|
virtual void Start ();
|
||||||
|
virtual void Stop ();
|
||||||
|
bool IsRunning () const { return m_IsRunning; };
|
||||||
|
boost::asio::io_service& GetService () { return m_Service; };
|
||||||
|
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () { return m_Pool; };
|
||||||
|
bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; };
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> FindLeaseSet (const i2p::data::IdentHash& ident);
|
||||||
|
bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr);
|
||||||
|
void CancelDestinationRequest (const i2p::data::IdentHash& dest);
|
||||||
|
|
||||||
|
// streaming
|
||||||
|
std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional
|
||||||
|
std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
|
||||||
|
// following methods operate with default streaming destination
|
||||||
|
void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0);
|
||||||
|
std::shared_ptr<i2p::stream::Stream> CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
|
||||||
|
void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor);
|
||||||
|
void StopAcceptingStreams ();
|
||||||
|
bool IsAcceptingStreams () const;
|
||||||
|
|
||||||
|
// datagram
|
||||||
|
i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; };
|
||||||
|
i2p::datagram::DatagramDestination * CreateDatagramDestination ();
|
||||||
|
|
||||||
|
// implements LocalDestination
|
||||||
|
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
|
||||||
|
const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; };
|
||||||
|
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; };
|
||||||
|
|
||||||
|
// implements GarlicDestination
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> GetLeaseSet ();
|
||||||
|
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
|
||||||
|
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||||
|
|
||||||
|
// override GarlicDestination
|
||||||
|
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
|
||||||
|
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
void SetLeaseSetUpdated ();
|
||||||
|
|
||||||
|
// I2CP
|
||||||
|
void HandleDataMessage (const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void UpdateLeaseSet ();
|
||||||
|
void Publish ();
|
||||||
|
void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
|
||||||
|
void HandlePublishVerificationTimer (const boost::system::error_code& ecode);
|
||||||
|
void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
|
||||||
|
void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len);
|
||||||
|
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete);
|
||||||
|
bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request);
|
||||||
|
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
|
||||||
|
void HandleCleanupTimer (const boost::system::error_code& ecode);
|
||||||
|
void CleanupRemoteLeaseSets ();
|
||||||
|
void PersistTemporaryKeys ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
volatile bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
boost::asio::io_service m_Service;
|
||||||
|
boost::asio::io_service::work m_Work;
|
||||||
|
i2p::data::PrivateKeys m_Keys;
|
||||||
|
uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256];
|
||||||
|
std::map<i2p::data::IdentHash, std::shared_ptr<i2p::data::LeaseSet> > m_RemoteLeaseSets;
|
||||||
|
std::map<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> > m_LeaseSetRequests;
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::tunnel::TunnelPool> m_Pool;
|
||||||
|
std::shared_ptr<i2p::data::LeaseSet> m_LeaseSet;
|
||||||
|
bool m_IsPublic;
|
||||||
|
uint32_t m_PublishReplyToken;
|
||||||
|
std::set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
|
||||||
|
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
|
||||||
|
i2p::datagram::DatagramDestination * m_DatagramDestination;
|
||||||
|
|
||||||
|
boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer, m_CleanupTimer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// for HTTP only
|
||||||
|
int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); };
|
||||||
|
std::vector<std::shared_ptr<const i2p::stream::Stream> > GetAllStreams () const;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
165
FS.cpp
Normal file
165
FS.cpp
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016, The PurpleI2P Project
|
||||||
|
*
|
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3
|
||||||
|
*
|
||||||
|
* See full license text in LICENSE file at top of project tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <shlobj.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Base.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace fs {
|
||||||
|
std::string appName = "i2pd";
|
||||||
|
std::string dataDir = "";
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::string dirSep = "\\";
|
||||||
|
#else
|
||||||
|
std::string dirSep = "/";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const std::string & GetAppName () {
|
||||||
|
return appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAppName (const std::string& name) {
|
||||||
|
appName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string & GetDataDir () {
|
||||||
|
return dataDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectDataDir(const std::string & cmdline_param, bool isService) {
|
||||||
|
if (cmdline_param != "") {
|
||||||
|
dataDir = cmdline_param;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#if defined(WIN32) || defined(_WIN32)
|
||||||
|
char localAppData[MAX_PATH];
|
||||||
|
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
|
||||||
|
dataDir = std::string(localAppData) + "\\" + appName;
|
||||||
|
return;
|
||||||
|
#elif defined(MAC_OSX)
|
||||||
|
char *home = getenv("HOME");
|
||||||
|
dataDir = (home != NULL && strlen(home) > 0) ? home : "";
|
||||||
|
dataDir += "/Library/Application Support/" + appName;
|
||||||
|
return;
|
||||||
|
#else /* other unix */
|
||||||
|
char *home = getenv("HOME");
|
||||||
|
if (isService) {
|
||||||
|
dataDir = "/var/lib/" + appName;
|
||||||
|
} else if (home != NULL && strlen(home) > 0) {
|
||||||
|
dataDir = std::string(home) + "/." + appName;
|
||||||
|
} else {
|
||||||
|
dataDir = "/tmp/" + appName;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Init() {
|
||||||
|
if (!boost::filesystem::exists(dataDir))
|
||||||
|
boost::filesystem::create_directory(dataDir);
|
||||||
|
std::string destinations = DataDirPath("destinations");
|
||||||
|
if (!boost::filesystem::exists(destinations))
|
||||||
|
boost::filesystem::create_directory(destinations);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadDir(const std::string & path, std::vector<std::string> & files) {
|
||||||
|
if (!boost::filesystem::exists(path))
|
||||||
|
return false;
|
||||||
|
boost::filesystem::directory_iterator it(path);
|
||||||
|
boost::filesystem::directory_iterator end;
|
||||||
|
|
||||||
|
for ( ; it != end; it++) {
|
||||||
|
if (!boost::filesystem::is_regular_file(it->status()))
|
||||||
|
continue;
|
||||||
|
files.push_back(it->path().string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Exists(const std::string & path) {
|
||||||
|
return boost::filesystem::exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Remove(const std::string & path) {
|
||||||
|
if (!boost::filesystem::exists(path))
|
||||||
|
return false;
|
||||||
|
return boost::filesystem::remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreateDirectory (const std::string& path)
|
||||||
|
{
|
||||||
|
if (boost::filesystem::exists(path) &&
|
||||||
|
boost::filesystem::is_directory (boost::filesystem::status (path))) return true;
|
||||||
|
return boost::filesystem::create_directory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HashedStorage::SetPlace(const std::string &path) {
|
||||||
|
root = path + i2p::fs::dirSep + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HashedStorage::Init(const char * chars, size_t count) {
|
||||||
|
if (!boost::filesystem::exists(root)) {
|
||||||
|
boost::filesystem::create_directories(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
|
||||||
|
if (boost::filesystem::exists(p))
|
||||||
|
continue;
|
||||||
|
if (boost::filesystem::create_directory(p))
|
||||||
|
continue; /* ^ throws exception on failure */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string HashedStorage::Path(const std::string & ident) const {
|
||||||
|
std::string safe_ident = ident;
|
||||||
|
std::replace(safe_ident.begin(), safe_ident.end(), '/', '-');
|
||||||
|
std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-');
|
||||||
|
|
||||||
|
std::stringstream t("");
|
||||||
|
t << this->root << i2p::fs::dirSep;
|
||||||
|
t << prefix1 << safe_ident[0] << i2p::fs::dirSep;
|
||||||
|
t << prefix2 << safe_ident << "." << suffix;
|
||||||
|
|
||||||
|
return t.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HashedStorage::Remove(const std::string & ident) {
|
||||||
|
std::string path = Path(ident);
|
||||||
|
if (!boost::filesystem::exists(path))
|
||||||
|
return;
|
||||||
|
boost::filesystem::remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HashedStorage::Traverse(std::vector<std::string> & files) {
|
||||||
|
boost::filesystem::path p(root);
|
||||||
|
boost::filesystem::recursive_directory_iterator it(p);
|
||||||
|
boost::filesystem::recursive_directory_iterator end;
|
||||||
|
|
||||||
|
for ( ; it != end; it++) {
|
||||||
|
if (!boost::filesystem::is_regular_file( it->status() ))
|
||||||
|
continue;
|
||||||
|
const std::string & t = it->path().string();
|
||||||
|
files.push_back(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // fs
|
||||||
|
} // i2p
|
||||||
155
FS.h
Normal file
155
FS.h
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016, The PurpleI2P Project
|
||||||
|
*
|
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3
|
||||||
|
*
|
||||||
|
* See full license text in LICENSE file at top of project tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FS_H__
|
||||||
|
#define FS_H__
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace fs {
|
||||||
|
extern std::string dirSep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Class to work with NetDb & Router profiles
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
|
||||||
|
* auto h = HashedStorage("name", "y", "z-", ".txt");
|
||||||
|
* h.SetPlace("/tmp/hs-test");
|
||||||
|
* h.GetName() -> gives "name"
|
||||||
|
* h.GetRoot() -> gives "/tmp/hs-test/name"
|
||||||
|
* h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet
|
||||||
|
* h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt
|
||||||
|
* h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists
|
||||||
|
* std::vector<std::string> files;
|
||||||
|
* h.Traverse(files); <- finds all files in storage and saves in given vector
|
||||||
|
*/
|
||||||
|
class HashedStorage {
|
||||||
|
protected:
|
||||||
|
std::string root; /**< path to storage with it's name included */
|
||||||
|
std::string name; /**< name of the storage */
|
||||||
|
std::string prefix1; /**< hashed directory prefix */
|
||||||
|
std::string prefix2; /**< prefix of file in storage */
|
||||||
|
std::string suffix; /**< suffix of file in storage (extension) */
|
||||||
|
|
||||||
|
public:
|
||||||
|
HashedStorage(const char *n, const char *p1, const char *p2, const char *s):
|
||||||
|
name(n), prefix1(p1), prefix2(p2), suffix(s) {};
|
||||||
|
|
||||||
|
/** create subdirs in storage */
|
||||||
|
bool Init(const char* chars, size_t cnt);
|
||||||
|
const std::string & GetRoot() const { return root; }
|
||||||
|
const std::string & GetName() const { return name; }
|
||||||
|
/** set directory where to place storage directory */
|
||||||
|
void SetPlace(const std::string & path);
|
||||||
|
/** path to file with given ident */
|
||||||
|
std::string Path(const std::string & ident) const;
|
||||||
|
/** remove file by ident */
|
||||||
|
void Remove(const std::string & ident);
|
||||||
|
/** find all files in storage and store list in provided vector */
|
||||||
|
void Traverse(std::vector<std::string> & files);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Returns current application name, default 'i2pd' */
|
||||||
|
const std::string & GetAppName ();
|
||||||
|
/** @brief Set applicaton name, affects autodetection of datadir */
|
||||||
|
void SetAppName (const std::string& name);
|
||||||
|
|
||||||
|
/** @brief Returns datadir path */
|
||||||
|
const std::string & GetDataDir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set datadir either from cmdline option or using autodetection
|
||||||
|
* @param cmdline_param Value of cmdline parameter --datadir=<something>
|
||||||
|
* @param isService Value of cmdline parameter --service
|
||||||
|
*
|
||||||
|
* Examples of autodetected paths:
|
||||||
|
*
|
||||||
|
* Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\
|
||||||
|
* Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\
|
||||||
|
* Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/
|
||||||
|
* Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/
|
||||||
|
*/
|
||||||
|
void DetectDataDir(const std::string & cmdline_datadir, bool isService = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create subdirectories inside datadir
|
||||||
|
*/
|
||||||
|
bool Init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get list of files in directory
|
||||||
|
* @param path Path to directory
|
||||||
|
* @param files Vector to store found files
|
||||||
|
* @return true on success and false if directory not exists
|
||||||
|
*/
|
||||||
|
bool ReadDir(const std::string & path, std::vector<std::string> & files);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove file with given path
|
||||||
|
* @param path Absolute path to file
|
||||||
|
* @return true on success, false if file not exists, throws exception on error
|
||||||
|
*/
|
||||||
|
bool Remove(const std::string & path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check existence of file
|
||||||
|
* @param path Absolute path to file
|
||||||
|
* @return true if file exists, false otherwise
|
||||||
|
*/
|
||||||
|
bool Exists(const std::string & path);
|
||||||
|
|
||||||
|
bool CreateDirectory (const std::string& path);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void _ExpandPath(std::stringstream & path, T c) {
|
||||||
|
path << i2p::fs::dirSep << c;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename ... Other>
|
||||||
|
void _ExpandPath(std::stringstream & path, T c, Other ... other) {
|
||||||
|
_ExpandPath(path, c);
|
||||||
|
_ExpandPath(path, other ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get path relative to datadir
|
||||||
|
*
|
||||||
|
* Examples (with datadir = "/tmp/i2pd"):
|
||||||
|
*
|
||||||
|
* i2p::fs::Path("test") -> '/tmp/i2pd/test'
|
||||||
|
* i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt'
|
||||||
|
*/
|
||||||
|
template<typename ... Other>
|
||||||
|
std::string DataDirPath(Other ... components) {
|
||||||
|
std::stringstream s("");
|
||||||
|
s << i2p::fs::GetDataDir();
|
||||||
|
_ExpandPath(s, components ...);
|
||||||
|
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Storage, typename... Filename>
|
||||||
|
std::string StorageRootPath (const Storage& storage, Filename... filenames)
|
||||||
|
{
|
||||||
|
std::stringstream s("");
|
||||||
|
s << storage.GetRoot ();
|
||||||
|
_ExpandPath(s, filenames...);
|
||||||
|
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // fs
|
||||||
|
} // i2p
|
||||||
|
|
||||||
|
#endif // /* FS_H__ */
|
||||||
175
Family.cpp
Normal file
175
Family.cpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Family.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
Families::Families ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Families::~Families ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Families::LoadCertificate (const std::string& filename)
|
||||||
|
{
|
||||||
|
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
|
||||||
|
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
SSL * ssl = SSL_new (ctx);
|
||||||
|
X509 * cert = SSL_get_certificate (ssl);
|
||||||
|
if (cert)
|
||||||
|
{
|
||||||
|
std::shared_ptr<i2p::crypto::Verifier> verifier;
|
||||||
|
// extract issuer name
|
||||||
|
char name[100];
|
||||||
|
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
|
||||||
|
char * cn = strstr (name, "CN=");
|
||||||
|
if (cn)
|
||||||
|
{
|
||||||
|
cn += 3;
|
||||||
|
char * family = strstr (cn, ".family");
|
||||||
|
if (family) family[0] = 0;
|
||||||
|
}
|
||||||
|
auto pkey = X509_get_pubkey (cert);
|
||||||
|
int keyType = EVP_PKEY_type(pkey->type);
|
||||||
|
switch (keyType)
|
||||||
|
{
|
||||||
|
case EVP_PKEY_DSA:
|
||||||
|
// TODO:
|
||||||
|
break;
|
||||||
|
case EVP_PKEY_EC:
|
||||||
|
{
|
||||||
|
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
|
||||||
|
if (ecKey)
|
||||||
|
{
|
||||||
|
auto group = EC_KEY_get0_group (ecKey);
|
||||||
|
if (group)
|
||||||
|
{
|
||||||
|
int curve = EC_GROUP_get_curve_name (group);
|
||||||
|
if (curve == NID_X9_62_prime256v1)
|
||||||
|
{
|
||||||
|
uint8_t signingKey[64];
|
||||||
|
BIGNUM * x = BN_new(), * y = BN_new();
|
||||||
|
EC_POINT_get_affine_coordinates_GFp (group,
|
||||||
|
EC_KEY_get0_public_key (ecKey), x, y, NULL);
|
||||||
|
i2p::crypto::bn2buf (x, signingKey, 32);
|
||||||
|
i2p::crypto::bn2buf (y, signingKey + 32, 32);
|
||||||
|
BN_free (x); BN_free (y);
|
||||||
|
verifier = std::make_shared<i2p::crypto::ECDSAP256Verifier>(signingKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
|
||||||
|
}
|
||||||
|
EC_KEY_free (ecKey);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported");
|
||||||
|
}
|
||||||
|
EVP_PKEY_free (pkey);
|
||||||
|
if (verifier && cn)
|
||||||
|
m_SigningKeys[cn] = verifier;
|
||||||
|
}
|
||||||
|
SSL_free (ssl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Family: Can't open certificate file ", filename);
|
||||||
|
SSL_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Families::LoadCertificates ()
|
||||||
|
{
|
||||||
|
std::string certDir = i2p::fs::DataDirPath("certificates", "family");
|
||||||
|
std::vector<std::string> files;
|
||||||
|
int numCertificates = 0;
|
||||||
|
|
||||||
|
if (!i2p::fs::ReadDir(certDir, files)) {
|
||||||
|
LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string & file : files) {
|
||||||
|
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
|
||||||
|
LogPrint(eLogWarning, "Family: ignoring file ", file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LoadCertificate (file);
|
||||||
|
numCertificates++;
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
|
||||||
|
const char * signature, const char * key)
|
||||||
|
{
|
||||||
|
uint8_t buf[50], signatureBuf[64];
|
||||||
|
size_t len = family.length (), signatureLen = strlen (signature);
|
||||||
|
memcpy (buf, family.c_str (), len);
|
||||||
|
memcpy (buf + len, (const uint8_t *)ident, 32);
|
||||||
|
len += 32;
|
||||||
|
Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
|
||||||
|
auto it = m_SigningKeys.find (family);
|
||||||
|
if (it != m_SigningKeys.end ())
|
||||||
|
return it->second->Verify (buf, len, signatureBuf);
|
||||||
|
// TODO: process key
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident)
|
||||||
|
{
|
||||||
|
auto filename = i2p::fs::DataDirPath("family", (family + ".key"));
|
||||||
|
std::string sig;
|
||||||
|
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
|
||||||
|
int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
SSL * ssl = SSL_new (ctx);
|
||||||
|
EVP_PKEY * pkey = SSL_get_privatekey (ssl);
|
||||||
|
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
|
||||||
|
if (ecKey)
|
||||||
|
{
|
||||||
|
auto group = EC_KEY_get0_group (ecKey);
|
||||||
|
if (group)
|
||||||
|
{
|
||||||
|
int curve = EC_GROUP_get_curve_name (group);
|
||||||
|
if (curve == NID_X9_62_prime256v1)
|
||||||
|
{
|
||||||
|
uint8_t signingPrivateKey[32], buf[50], signature[64];
|
||||||
|
i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32);
|
||||||
|
i2p::crypto::ECDSAP256Signer signer (signingPrivateKey);
|
||||||
|
size_t len = family.length ();
|
||||||
|
memcpy (buf, family.c_str (), len);
|
||||||
|
memcpy (buf + len, (const uint8_t *)ident, 32);
|
||||||
|
len += 32;
|
||||||
|
signer.Sign (buf, len, signature);
|
||||||
|
len = Base64EncodingBufferSize (64);
|
||||||
|
char * b64 = new char[len+1];
|
||||||
|
len = ByteStreamToBase64 (signature, 64, b64, len);
|
||||||
|
b64[len] = 0;
|
||||||
|
sig = b64;
|
||||||
|
delete[] b64;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSL_free (ssl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Family: Can't open keys file: ", filename);
|
||||||
|
SSL_CTX_free (ctx);
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
38
Family.h
Normal file
38
Family.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef FAMILY_H__
|
||||||
|
#define FAMILY_H__
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include "Signature.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
class Families
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
Families ();
|
||||||
|
~Families ();
|
||||||
|
void LoadCertificates ();
|
||||||
|
bool VerifyFamily (const std::string& family, const IdentHash& ident,
|
||||||
|
const char * signature, const char * key = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void LoadCertificate (const std::string& filename);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<i2p::crypto::Verifier> > m_SigningKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident);
|
||||||
|
// return base64 signature of empty string in case of failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
650
Garlic.cpp
Normal file
650
Garlic.cpp
Normal file
@@ -0,0 +1,650 @@
|
|||||||
|
#include <inttypes.h>
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "TunnelPool.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Garlic.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace garlic
|
||||||
|
{
|
||||||
|
GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner,
|
||||||
|
std::shared_ptr<const i2p::data::RoutingDestination> destination, int numTags, bool attachLeaseSet):
|
||||||
|
m_Owner (owner), m_Destination (destination), m_NumTags (numTags),
|
||||||
|
m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend),
|
||||||
|
m_ElGamalEncryption (new i2p::crypto::ElGamalEncryption (destination->GetEncryptionPublicKey ()))
|
||||||
|
{
|
||||||
|
// create new session tags and session key
|
||||||
|
RAND_bytes (m_SessionKey, 32);
|
||||||
|
m_Encryption.SetKey (m_SessionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag):
|
||||||
|
m_Owner (nullptr), m_Destination (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend)
|
||||||
|
{
|
||||||
|
memcpy (m_SessionKey, sessionKey, 32);
|
||||||
|
m_Encryption.SetKey (m_SessionKey);
|
||||||
|
m_SessionTags.push_back (sessionTag);
|
||||||
|
m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
}
|
||||||
|
|
||||||
|
GarlicRoutingSession::~GarlicRoutingSession ()
|
||||||
|
{
|
||||||
|
for (auto it: m_UnconfirmedTagsMsgs)
|
||||||
|
delete it.second;
|
||||||
|
m_UnconfirmedTagsMsgs.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<GarlicRoutingPath> GarlicRoutingSession::GetSharedRoutingPath ()
|
||||||
|
{
|
||||||
|
if (!m_SharedRoutingPath) return nullptr;
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED ||
|
||||||
|
!m_SharedRoutingPath->outboundTunnel->IsEstablished () ||
|
||||||
|
ts*1000LL > m_SharedRoutingPath->remoteLease->endDate ||
|
||||||
|
ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT)
|
||||||
|
m_SharedRoutingPath = nullptr;
|
||||||
|
if (m_SharedRoutingPath) m_SharedRoutingPath->numTimesUsed++;
|
||||||
|
return m_SharedRoutingPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicRoutingSession::SetSharedRoutingPath (std::shared_ptr<GarlicRoutingPath> path)
|
||||||
|
{
|
||||||
|
if (path && path->outboundTunnel && path->remoteLease)
|
||||||
|
{
|
||||||
|
path->updateTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
path->numTimesUsed = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
path = nullptr;
|
||||||
|
m_SharedRoutingPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags ()
|
||||||
|
{
|
||||||
|
auto tags = new UnconfirmedTags (m_NumTags);
|
||||||
|
tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (int i = 0; i < m_NumTags; i++)
|
||||||
|
{
|
||||||
|
RAND_bytes (tags->sessionTags[i], 32);
|
||||||
|
tags->sessionTags[i].creationTime = tags->tagsCreationTime;
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicRoutingSession::MessageConfirmed (uint32_t msgID)
|
||||||
|
{
|
||||||
|
TagsConfirmed (msgID);
|
||||||
|
if (msgID == m_LeaseSetUpdateMsgID)
|
||||||
|
{
|
||||||
|
m_LeaseSetUpdateStatus = eLeaseSetUpToDate;
|
||||||
|
LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
CleanupExpiredTags ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicRoutingSession::TagsConfirmed (uint32_t msgID)
|
||||||
|
{
|
||||||
|
auto it = m_UnconfirmedTagsMsgs.find (msgID);
|
||||||
|
if (it != m_UnconfirmedTagsMsgs.end ())
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
UnconfirmedTags * tags = it->second;
|
||||||
|
if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < tags->numTags; i++)
|
||||||
|
m_SessionTags.push_back (tags->sessionTags[i]);
|
||||||
|
}
|
||||||
|
m_UnconfirmedTagsMsgs.erase (it);
|
||||||
|
delete tags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GarlicRoutingSession::CleanupExpiredTags ()
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();)
|
||||||
|
{
|
||||||
|
if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
|
||||||
|
it = m_SessionTags.erase (it);
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
// delete expired unconfirmed tags
|
||||||
|
for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();)
|
||||||
|
{
|
||||||
|
if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
if (m_Owner)
|
||||||
|
m_Owner->RemoveDeliveryStatusSession (it->first);
|
||||||
|
delete it->second;
|
||||||
|
it = m_UnconfirmedTagsMsgs.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty ();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> GarlicRoutingSession::WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
auto m = NewI2NPMessage ();
|
||||||
|
m->Align (12); // in order to get buf aligned to 16 (12 + 4)
|
||||||
|
size_t len = 0;
|
||||||
|
uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
|
||||||
|
|
||||||
|
// find non-expired tag
|
||||||
|
bool tagFound = false;
|
||||||
|
SessionTag tag;
|
||||||
|
if (m_NumTags > 0)
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
while (!m_SessionTags.empty ())
|
||||||
|
{
|
||||||
|
if (ts < m_SessionTags.front ().creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
tag = m_SessionTags.front ();
|
||||||
|
m_SessionTags.pop_front (); // use same tag only once
|
||||||
|
tagFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_SessionTags.pop_front (); // remove expired tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create message
|
||||||
|
if (!tagFound) // new session
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Garlic: No tags available, will use ElGamal");
|
||||||
|
if (!m_Destination)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Garlic: Can't use ElGamal for unknown destination");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// create ElGamal block
|
||||||
|
ElGamalBlock elGamal;
|
||||||
|
memcpy (elGamal.sessionKey, m_SessionKey, 32);
|
||||||
|
RAND_bytes (elGamal.preIV, 32); // Pre-IV
|
||||||
|
uint8_t iv[32]; // IV is first 16 bytes
|
||||||
|
SHA256(elGamal.preIV, 32, iv);
|
||||||
|
m_ElGamalEncryption->Encrypt ((uint8_t *)&elGamal, sizeof(elGamal), buf, true);
|
||||||
|
m_Encryption.SetIV (iv);
|
||||||
|
buf += 514;
|
||||||
|
len += 514;
|
||||||
|
}
|
||||||
|
else // existing session
|
||||||
|
{
|
||||||
|
// session tag
|
||||||
|
memcpy (buf, tag, 32);
|
||||||
|
uint8_t iv[32]; // IV is first 16 bytes
|
||||||
|
SHA256(tag, 32, iv);
|
||||||
|
m_Encryption.SetIV (iv);
|
||||||
|
buf += 32;
|
||||||
|
len += 32;
|
||||||
|
}
|
||||||
|
// AES block
|
||||||
|
len += CreateAESBlock (buf, msg);
|
||||||
|
htobe32buf (m->GetPayload (), len);
|
||||||
|
m->len += len + 4;
|
||||||
|
m->FillI2NPMessageHeader (eI2NPGarlic);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
size_t blockSize = 0;
|
||||||
|
bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3);
|
||||||
|
UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr;
|
||||||
|
htobuf16 (buf, newTags ? htobe16 (newTags->numTags) : 0); // tag count
|
||||||
|
blockSize += 2;
|
||||||
|
if (newTags) // session tags recreated
|
||||||
|
{
|
||||||
|
for (int i = 0; i < newTags->numTags; i++)
|
||||||
|
{
|
||||||
|
memcpy (buf + blockSize, newTags->sessionTags[i], 32); // tags
|
||||||
|
blockSize += 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t * payloadSize = (uint32_t *)(buf + blockSize);
|
||||||
|
blockSize += 4;
|
||||||
|
uint8_t * payloadHash = buf + blockSize;
|
||||||
|
blockSize += 32;
|
||||||
|
buf[blockSize] = 0; // flag
|
||||||
|
blockSize++;
|
||||||
|
size_t len = CreateGarlicPayload (buf + blockSize, msg, newTags);
|
||||||
|
htobe32buf (payloadSize, len);
|
||||||
|
SHA256(buf + blockSize, len, payloadHash);
|
||||||
|
blockSize += len;
|
||||||
|
size_t rem = blockSize % 16;
|
||||||
|
if (rem)
|
||||||
|
blockSize += (16-rem); //padding
|
||||||
|
m_Encryption.Encrypt(buf, blockSize, buf);
|
||||||
|
return blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags)
|
||||||
|
{
|
||||||
|
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
|
||||||
|
uint32_t msgID;
|
||||||
|
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||||
|
size_t size = 0;
|
||||||
|
uint8_t * numCloves = payload + size;
|
||||||
|
*numCloves = 0;
|
||||||
|
size++;
|
||||||
|
|
||||||
|
if (m_Owner)
|
||||||
|
{
|
||||||
|
// resubmit non-confirmed LeaseSet
|
||||||
|
if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted &&
|
||||||
|
i2p::util::GetMillisecondsSinceEpoch () > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT)
|
||||||
|
m_LeaseSetUpdateStatus = eLeaseSetUpdated;
|
||||||
|
|
||||||
|
// attach DeviveryStatus if necessary
|
||||||
|
if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated
|
||||||
|
{
|
||||||
|
// clove is DeliveryStatus
|
||||||
|
auto cloveSize = CreateDeliveryStatusClove (payload + size, msgID);
|
||||||
|
if (cloveSize > 0) // successive?
|
||||||
|
{
|
||||||
|
size += cloveSize;
|
||||||
|
(*numCloves)++;
|
||||||
|
if (newTags) // new tags created
|
||||||
|
m_UnconfirmedTagsMsgs[msgID] = newTags;
|
||||||
|
m_Owner->DeliveryStatusSent (shared_from_this (), msgID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Garlic: DeliveryStatus clove was not created");
|
||||||
|
}
|
||||||
|
// attach LeaseSet
|
||||||
|
if (m_LeaseSetUpdateStatus == eLeaseSetUpdated)
|
||||||
|
{
|
||||||
|
m_LeaseSetUpdateStatus = eLeaseSetSubmitted;
|
||||||
|
m_LeaseSetUpdateMsgID = msgID;
|
||||||
|
m_LeaseSetSubmissionTime = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
// clove if our leaseSet must be attached
|
||||||
|
auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ());
|
||||||
|
size += CreateGarlicClove (payload + size, leaseSet, false);
|
||||||
|
(*numCloves)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg) // clove message ifself if presented
|
||||||
|
{
|
||||||
|
size += CreateGarlicClove (payload + size, msg, m_Destination ? m_Destination->IsDestination () : false);
|
||||||
|
(*numCloves)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset (payload + size, 0, 3); // certificate of message
|
||||||
|
size += 3;
|
||||||
|
htobe32buf (payload + size, msgID); // MessageID
|
||||||
|
size += 4;
|
||||||
|
htobe64buf (payload + size, ts); // Expiration of message
|
||||||
|
size += 8;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination)
|
||||||
|
{
|
||||||
|
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
|
||||||
|
size_t size = 0;
|
||||||
|
if (isDestination && m_Destination)
|
||||||
|
{
|
||||||
|
buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination
|
||||||
|
size++;
|
||||||
|
memcpy (buf + size, m_Destination->GetIdentHash (), 32);
|
||||||
|
size += 32;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buf[size] = 0;// delivery instructions flag local
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
|
||||||
|
size += msg->GetLength ();
|
||||||
|
uint32_t cloveID;
|
||||||
|
RAND_bytes ((uint8_t *)&cloveID, 4);
|
||||||
|
htobe32buf (buf + size, cloveID); // CloveID
|
||||||
|
size += 4;
|
||||||
|
htobe64buf (buf + size, ts); // Expiration of clove
|
||||||
|
size += 8;
|
||||||
|
memset (buf + size, 0, 3); // certificate of clove
|
||||||
|
size += 3;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GarlicRoutingSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID)
|
||||||
|
{
|
||||||
|
size_t size = 0;
|
||||||
|
if (m_Owner)
|
||||||
|
{
|
||||||
|
auto inboundTunnel = m_Owner->GetTunnelPool ()->GetNextInboundTunnel ();
|
||||||
|
if (inboundTunnel)
|
||||||
|
{
|
||||||
|
buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel
|
||||||
|
size++;
|
||||||
|
// hash and tunnelID sequence is reversed for Garlic
|
||||||
|
memcpy (buf + size, inboundTunnel->GetNextIdentHash (), 32); // To Hash
|
||||||
|
size += 32;
|
||||||
|
htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID
|
||||||
|
size += 4;
|
||||||
|
// create msg
|
||||||
|
auto msg = CreateDeliveryStatusMsg (msgID);
|
||||||
|
if (m_Owner)
|
||||||
|
{
|
||||||
|
//encrypt
|
||||||
|
uint8_t key[32], tag[32];
|
||||||
|
RAND_bytes (key, 32); // random session key
|
||||||
|
RAND_bytes (tag, 32); // random session tag
|
||||||
|
m_Owner->SubmitSessionKey (key, tag);
|
||||||
|
GarlicRoutingSession garlic (key, tag);
|
||||||
|
msg = garlic.WrapSingleMessage (msg);
|
||||||
|
}
|
||||||
|
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
|
||||||
|
size += msg->GetLength ();
|
||||||
|
// fill clove
|
||||||
|
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
|
||||||
|
uint32_t cloveID;
|
||||||
|
RAND_bytes ((uint8_t *)&cloveID, 4);
|
||||||
|
htobe32buf (buf + size, cloveID); // CloveID
|
||||||
|
size += 4;
|
||||||
|
htobe64buf (buf + size, ts); // Expiration of clove
|
||||||
|
size += 8;
|
||||||
|
memset (buf + size, 0, 3); // certificate of clove
|
||||||
|
size += 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Garlic: No inbound tunnels in the pool for DeliveryStatus");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Garlic: Missing local LeaseSet");
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
GarlicDestination::~GarlicDestination ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::AddSessionKey (const uint8_t * key, const uint8_t * tag)
|
||||||
|
{
|
||||||
|
if (key)
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
auto decryption = std::make_shared<i2p::crypto::CBCDecryption>();
|
||||||
|
decryption->SetKey (key);
|
||||||
|
m_Tags[SessionTag(tag, ts)] = decryption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
|
||||||
|
{
|
||||||
|
AddSessionKey (key, tag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
uint8_t * buf = msg->GetPayload ();
|
||||||
|
uint32_t length = bufbe32toh (buf);
|
||||||
|
if (length > msg->GetLength ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds I2NP message length ", msg->GetLength ());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf += 4; // length
|
||||||
|
auto it = m_Tags.find (SessionTag(buf));
|
||||||
|
if (it != m_Tags.end ())
|
||||||
|
{
|
||||||
|
// tag found. Use AES
|
||||||
|
if (length >= 32)
|
||||||
|
{
|
||||||
|
uint8_t iv[32]; // IV is first 16 bytes
|
||||||
|
SHA256(buf, 32, iv);
|
||||||
|
it->second->SetIV (iv);
|
||||||
|
it->second->Decrypt (buf + 32, length - 32, buf + 32);
|
||||||
|
HandleAESBlock (buf + 32, length - 32, it->second, msg->from);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes");
|
||||||
|
m_Tags.erase (it); // tag might be used only once
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// tag not found. Use ElGamal
|
||||||
|
ElGamalBlock elGamal;
|
||||||
|
if (length >= 514 && i2p::crypto::ElGamalDecrypt (GetEncryptionPrivateKey (), buf, (uint8_t *)&elGamal, true))
|
||||||
|
{
|
||||||
|
auto decryption = std::make_shared<i2p::crypto::CBCDecryption>();
|
||||||
|
decryption->SetKey (elGamal.sessionKey);
|
||||||
|
uint8_t iv[32]; // IV is first 16 bytes
|
||||||
|
SHA256(elGamal.preIV, 32, iv);
|
||||||
|
decryption->SetIV (iv);
|
||||||
|
decryption->Decrypt(buf + 514, length - 514, buf + 514);
|
||||||
|
HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Garlic: Failed to decrypt message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
|
||||||
|
std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||||
|
{
|
||||||
|
uint16_t tagCount = bufbe16toh (buf);
|
||||||
|
buf += 2; len -= 2;
|
||||||
|
if (tagCount > 0)
|
||||||
|
{
|
||||||
|
if (tagCount*32 > len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Garlic: Tag count ", tagCount, " exceeds length ", len);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (int i = 0; i < tagCount; i++)
|
||||||
|
m_Tags[SessionTag(buf + i*32, ts)] = decryption;
|
||||||
|
}
|
||||||
|
buf += tagCount*32;
|
||||||
|
len -= tagCount*32;
|
||||||
|
uint32_t payloadSize = bufbe32toh (buf);
|
||||||
|
if (payloadSize > len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Garlic: Unexpected payload size ", payloadSize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf += 4;
|
||||||
|
uint8_t * payloadHash = buf;
|
||||||
|
buf += 32;// payload hash.
|
||||||
|
if (*buf) // session key?
|
||||||
|
buf += 32; // new session key
|
||||||
|
buf++; // flag
|
||||||
|
|
||||||
|
// payload
|
||||||
|
uint8_t digest[32];
|
||||||
|
SHA256 (buf, payloadSize, digest);
|
||||||
|
if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Garlic: wrong payload hash");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HandleGarlicPayload (buf, payloadSize, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||||
|
{
|
||||||
|
const uint8_t * buf1 = buf;
|
||||||
|
int numCloves = buf[0];
|
||||||
|
LogPrint (eLogDebug, "Garlic: ", numCloves," cloves");
|
||||||
|
buf++;
|
||||||
|
for (int i = 0; i < numCloves; i++)
|
||||||
|
{
|
||||||
|
// delivery instructions
|
||||||
|
uint8_t flag = buf[0];
|
||||||
|
buf++; // flag
|
||||||
|
if (flag & 0x80) // encrypted?
|
||||||
|
{
|
||||||
|
// TODO: implement
|
||||||
|
LogPrint (eLogWarning, "Garlic: clove encrypted");
|
||||||
|
buf += 32;
|
||||||
|
}
|
||||||
|
GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03);
|
||||||
|
switch (deliveryType)
|
||||||
|
{
|
||||||
|
case eGarlicDeliveryTypeLocal:
|
||||||
|
LogPrint (eLogDebug, "Garlic: type local");
|
||||||
|
HandleI2NPMessage (buf, len, from);
|
||||||
|
break;
|
||||||
|
case eGarlicDeliveryTypeDestination:
|
||||||
|
LogPrint (eLogDebug, "Garlic: type destination");
|
||||||
|
buf += 32; // destination. check it later or for multiple destinations
|
||||||
|
HandleI2NPMessage (buf, len, from);
|
||||||
|
break;
|
||||||
|
case eGarlicDeliveryTypeTunnel:
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Garlic: type tunnel");
|
||||||
|
// gwHash and gwTunnel sequence is reverted
|
||||||
|
uint8_t * gwHash = buf;
|
||||||
|
buf += 32;
|
||||||
|
uint32_t gwTunnel = bufbe32toh (buf);
|
||||||
|
buf += 4;
|
||||||
|
std::shared_ptr<i2p::tunnel::OutboundTunnel> tunnel;
|
||||||
|
if (from && from->GetTunnelPool ())
|
||||||
|
tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel ();
|
||||||
|
if (tunnel) // we have send it through an outbound tunnel
|
||||||
|
{
|
||||||
|
auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from);
|
||||||
|
tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eGarlicDeliveryTypeRouter:
|
||||||
|
LogPrint (eLogWarning, "Garlic: type router not supported");
|
||||||
|
buf += 32;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType);
|
||||||
|
}
|
||||||
|
buf += GetI2NPMessageLength (buf); // I2NP
|
||||||
|
buf += 4; // CloveID
|
||||||
|
buf += 8; // Date
|
||||||
|
buf += 3; // Certificate
|
||||||
|
if (buf - buf1 > (int)len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Garlic: clove is too long");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> GarlicDestination::WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
|
||||||
|
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet)
|
||||||
|
{
|
||||||
|
auto session = GetRoutingSession (destination, attachLeaseSet);
|
||||||
|
return session->WrapSingleMessage (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
|
||||||
|
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet)
|
||||||
|
{
|
||||||
|
GarlicRoutingSessionPtr session;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||||
|
auto it = m_Sessions.find (destination->GetIdentHash ());
|
||||||
|
if (it != m_Sessions.end ())
|
||||||
|
session = it->second;
|
||||||
|
}
|
||||||
|
if (!session)
|
||||||
|
{
|
||||||
|
session = std::make_shared<GarlicRoutingSession> (this, destination,
|
||||||
|
attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests
|
||||||
|
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||||
|
m_Sessions[destination->GetIdentHash ()] = session;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::CleanupExpiredTags ()
|
||||||
|
{
|
||||||
|
// incoming
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
int numExpiredTags = 0;
|
||||||
|
for (auto it = m_Tags.begin (); it != m_Tags.end ();)
|
||||||
|
{
|
||||||
|
if (ts > it->first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
numExpiredTags++;
|
||||||
|
it = m_Tags.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
if (numExpiredTags > 0)
|
||||||
|
LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ());
|
||||||
|
|
||||||
|
// outgoing
|
||||||
|
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||||
|
for (auto it = m_Sessions.begin (); it != m_Sessions.end ();)
|
||||||
|
{
|
||||||
|
it->second->GetSharedRoutingPath (); // delete shared path if necessary
|
||||||
|
if (!it->second->CleanupExpiredTags ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted");
|
||||||
|
it = m_Sessions.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID)
|
||||||
|
{
|
||||||
|
m_DeliveryStatusSessions.erase (msgID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID)
|
||||||
|
{
|
||||||
|
m_DeliveryStatusSessions[msgID] = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
uint32_t msgID = bufbe32toh (msg->GetPayload ());
|
||||||
|
{
|
||||||
|
auto it = m_DeliveryStatusSessions.find (msgID);
|
||||||
|
if (it != m_DeliveryStatusSessions.end ())
|
||||||
|
{
|
||||||
|
it->second->MessageConfirmed (msgID);
|
||||||
|
m_DeliveryStatusSessions.erase (it);
|
||||||
|
LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::SetLeaseSetUpdated ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||||
|
for (auto it: m_Sessions)
|
||||||
|
it.second->SetLeaseSetUpdated ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
HandleGarlicMessage (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GarlicDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
HandleDeliveryStatusMessage (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
201
Garlic.h
Normal file
201
Garlic.h
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
#ifndef GARLIC_H__
|
||||||
|
#define GARLIC_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
#include "Queue.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
class OutboundTunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace garlic
|
||||||
|
{
|
||||||
|
|
||||||
|
enum GarlicDeliveryType
|
||||||
|
{
|
||||||
|
eGarlicDeliveryTypeLocal = 0,
|
||||||
|
eGarlicDeliveryTypeDestination = 1,
|
||||||
|
eGarlicDeliveryTypeRouter = 2,
|
||||||
|
eGarlicDeliveryTypeTunnel = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ElGamalBlock
|
||||||
|
{
|
||||||
|
uint8_t sessionKey[32];
|
||||||
|
uint8_t preIV[32];
|
||||||
|
uint8_t padding[158];
|
||||||
|
};
|
||||||
|
|
||||||
|
const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes
|
||||||
|
const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes
|
||||||
|
const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds
|
||||||
|
const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
|
||||||
|
const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds
|
||||||
|
const int ROUTING_PATH_MAX_NUM_TIMES_USED = 100; // how many times might be used
|
||||||
|
|
||||||
|
struct SessionTag: public i2p::data::Tag<32>
|
||||||
|
{
|
||||||
|
SessionTag (const uint8_t * buf, uint32_t ts = 0): Tag<32>(buf), creationTime (ts) {};
|
||||||
|
SessionTag () = default;
|
||||||
|
SessionTag (const SessionTag& ) = default;
|
||||||
|
SessionTag& operator= (const SessionTag& ) = default;
|
||||||
|
#ifndef _WIN32
|
||||||
|
SessionTag (SessionTag&& ) = default;
|
||||||
|
SessionTag& operator= (SessionTag&& ) = default;
|
||||||
|
#endif
|
||||||
|
uint32_t creationTime; // seconds since epoch
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GarlicRoutingPath
|
||||||
|
{
|
||||||
|
std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
|
||||||
|
std::shared_ptr<const i2p::data::Lease> remoteLease;
|
||||||
|
int rtt; // RTT
|
||||||
|
uint32_t updateTime; // seconds since epoch
|
||||||
|
int numTimesUsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GarlicDestination;
|
||||||
|
class GarlicRoutingSession: public std::enable_shared_from_this<GarlicRoutingSession>
|
||||||
|
{
|
||||||
|
enum LeaseSetUpdateStatus
|
||||||
|
{
|
||||||
|
eLeaseSetUpToDate = 0,
|
||||||
|
eLeaseSetUpdated,
|
||||||
|
eLeaseSetSubmitted,
|
||||||
|
eLeaseSetDoNotSend
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnconfirmedTags
|
||||||
|
{
|
||||||
|
UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; };
|
||||||
|
~UnconfirmedTags () { delete[] sessionTags; };
|
||||||
|
int numTags;
|
||||||
|
SessionTag * sessionTags;
|
||||||
|
uint32_t tagsCreationTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr<const i2p::data::RoutingDestination> destination,
|
||||||
|
int numTags, bool attachLeaseSet);
|
||||||
|
GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption
|
||||||
|
~GarlicRoutingSession ();
|
||||||
|
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg);
|
||||||
|
void MessageConfirmed (uint32_t msgID);
|
||||||
|
bool CleanupExpiredTags (); // returns true if something left
|
||||||
|
|
||||||
|
void SetLeaseSetUpdated ()
|
||||||
|
{
|
||||||
|
if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<GarlicRoutingPath> GetSharedRoutingPath ();
|
||||||
|
void SetSharedRoutingPath (std::shared_ptr<GarlicRoutingPath> path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
size_t CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg);
|
||||||
|
size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags);
|
||||||
|
size_t CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination);
|
||||||
|
size_t CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID);
|
||||||
|
|
||||||
|
void TagsConfirmed (uint32_t msgID);
|
||||||
|
UnconfirmedTags * GenerateSessionTags ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
GarlicDestination * m_Owner;
|
||||||
|
std::shared_ptr<const i2p::data::RoutingDestination> m_Destination;
|
||||||
|
i2p::crypto::AESKey m_SessionKey;
|
||||||
|
std::list<SessionTag> m_SessionTags;
|
||||||
|
int m_NumTags;
|
||||||
|
std::map<uint32_t, UnconfirmedTags *> m_UnconfirmedTagsMsgs;
|
||||||
|
|
||||||
|
LeaseSetUpdateStatus m_LeaseSetUpdateStatus;
|
||||||
|
uint32_t m_LeaseSetUpdateMsgID;
|
||||||
|
uint64_t m_LeaseSetSubmissionTime; // in milliseconds
|
||||||
|
|
||||||
|
i2p::crypto::CBCEncryption m_Encryption;
|
||||||
|
std::unique_ptr<const i2p::crypto::ElGamalEncryption> m_ElGamalEncryption;
|
||||||
|
|
||||||
|
std::shared_ptr<GarlicRoutingPath> m_SharedRoutingPath;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// for HTTP only
|
||||||
|
size_t GetNumOutgoingTags () const { return m_SessionTags.size (); };
|
||||||
|
};
|
||||||
|
//using GarlicRoutingSessionPtr = std::shared_ptr<GarlicRoutingSession>;
|
||||||
|
typedef std::shared_ptr<GarlicRoutingSession> GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8
|
||||||
|
|
||||||
|
class GarlicDestination: public i2p::data::LocalDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
GarlicDestination (): m_NumTags (32) {}; // 32 tags by default
|
||||||
|
~GarlicDestination ();
|
||||||
|
|
||||||
|
void SetNumTags (int numTags) { m_NumTags = numTags; };
|
||||||
|
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet);
|
||||||
|
void CleanupExpiredTags ();
|
||||||
|
void RemoveDeliveryStatusSession (uint32_t msgID);
|
||||||
|
std::shared_ptr<I2NPMessage> WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
|
||||||
|
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet = false);
|
||||||
|
|
||||||
|
void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
|
||||||
|
virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread
|
||||||
|
void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID);
|
||||||
|
|
||||||
|
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
virtual void SetLeaseSetUpdated ();
|
||||||
|
|
||||||
|
virtual std::shared_ptr<const i2p::data::LeaseSet> GetLeaseSet () = 0; // TODO
|
||||||
|
virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
|
||||||
|
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<i2p::crypto::CBCDecryption> decryption,
|
||||||
|
std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||||
|
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// outgoing sessions
|
||||||
|
int m_NumTags;
|
||||||
|
std::mutex m_SessionsMutex;
|
||||||
|
std::map<i2p::data::IdentHash, GarlicRoutingSessionPtr> m_Sessions;
|
||||||
|
// incoming
|
||||||
|
std::map<SessionTag, std::shared_ptr<i2p::crypto::CBCDecryption>> m_Tags;
|
||||||
|
// DeliveryStatus
|
||||||
|
std::map<uint32_t, GarlicRoutingSessionPtr> m_DeliveryStatusSessions; // msgID -> session
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// for HTTP only
|
||||||
|
size_t GetNumIncomingTags () const { return m_Tags.size (); }
|
||||||
|
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
389
HTTP.cpp
Normal file
389
HTTP.cpp
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016, The PurpleI2P Project
|
||||||
|
*
|
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3
|
||||||
|
*
|
||||||
|
* See full license text in LICENSE file at top of project tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "HTTP.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace http {
|
||||||
|
const std::vector<std::string> HTTP_METHODS = {
|
||||||
|
"GET", "HEAD", "POST", "PUT", "PATCH",
|
||||||
|
"DELETE", "OPTIONS", "CONNECT"
|
||||||
|
};
|
||||||
|
const std::vector<std::string> HTTP_VERSIONS = {
|
||||||
|
"HTTP/1.0", "HTTP/1.1"
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool is_http_version(const std::string & str) {
|
||||||
|
return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_http_method(const std::string & str) {
|
||||||
|
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
|
||||||
|
std::size_t count = 0;
|
||||||
|
std::stringstream ss(line);
|
||||||
|
std::string token;
|
||||||
|
while (1) {
|
||||||
|
count++;
|
||||||
|
if (limit > 0 && count >= limit)
|
||||||
|
delim = '\n'; /* reset delimiter */
|
||||||
|
if (!std::getline(ss, token, delim))
|
||||||
|
break;
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_header_line(const std::string & line, std::map<std::string, std::string> & headers) {
|
||||||
|
std::size_t pos = 0;
|
||||||
|
std::size_t len = 2; /* strlen(": ") */
|
||||||
|
if ((pos = line.find(": ", pos)) == std::string::npos)
|
||||||
|
return false;
|
||||||
|
while (isspace(line.at(pos + len)))
|
||||||
|
len++;
|
||||||
|
std::string name = line.substr(0, pos);
|
||||||
|
std::string value = line.substr(pos + len);
|
||||||
|
headers[name] = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URL::parse(const char *str, std::size_t len) {
|
||||||
|
std::string url(str, len ? len : strlen(str));
|
||||||
|
return parse(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URL::parse(const std::string& url) {
|
||||||
|
std::size_t pos_p = 0; /* < current parse position */
|
||||||
|
std::size_t pos_c = 0; /* < work position */
|
||||||
|
if (url.at(0) != '/') {
|
||||||
|
/* schema */
|
||||||
|
pos_c = url.find("://");
|
||||||
|
if (pos_c != std::string::npos) {
|
||||||
|
schema = url.substr(0, pos_c);
|
||||||
|
pos_p = pos_c + 3;
|
||||||
|
}
|
||||||
|
/* user[:pass] */
|
||||||
|
pos_c = url.find('@', pos_p);
|
||||||
|
if (pos_c != std::string::npos) {
|
||||||
|
std::size_t delim = url.find(':', pos_p);
|
||||||
|
if (delim != std::string::npos && delim < pos_c) {
|
||||||
|
user = url.substr(pos_p, delim - pos_p);
|
||||||
|
delim += 1;
|
||||||
|
pass = url.substr(delim, pos_c - delim);
|
||||||
|
} else {
|
||||||
|
user = url.substr(pos_p, pos_c - pos_p);
|
||||||
|
}
|
||||||
|
pos_p = pos_c + 1;
|
||||||
|
}
|
||||||
|
/* hostname[:port][/path] */
|
||||||
|
pos_c = url.find_first_of(":/", pos_p);
|
||||||
|
if (pos_c == std::string::npos) {
|
||||||
|
/* only hostname, without post and path */
|
||||||
|
host = url.substr(pos_p, std::string::npos);
|
||||||
|
return true;
|
||||||
|
} else if (url.at(pos_c) == ':') {
|
||||||
|
host = url.substr(pos_p, pos_c - pos_p);
|
||||||
|
/* port[/path] */
|
||||||
|
pos_p = pos_c + 1;
|
||||||
|
pos_c = url.find('/', pos_p);
|
||||||
|
std::string port_str = (pos_c == std::string::npos)
|
||||||
|
? url.substr(pos_p, std::string::npos)
|
||||||
|
: url.substr(pos_p, pos_c - pos_p);
|
||||||
|
/* stoi throws exception on failure, we don't need it */
|
||||||
|
for (char c : port_str) {
|
||||||
|
if (c < '0' || c > '9')
|
||||||
|
return false;
|
||||||
|
port *= 10;
|
||||||
|
port += c - '0';
|
||||||
|
}
|
||||||
|
if (pos_c == std::string::npos)
|
||||||
|
return true; /* no path part */
|
||||||
|
pos_p = pos_c;
|
||||||
|
} else {
|
||||||
|
/* start of path part found */
|
||||||
|
host = url.substr(pos_p, pos_c - pos_p);
|
||||||
|
pos_p = pos_c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pos_p now at start of path part */
|
||||||
|
pos_c = url.find_first_of("?#", pos_p);
|
||||||
|
if (pos_c == std::string::npos) {
|
||||||
|
/* only path, without fragment and query */
|
||||||
|
path = url.substr(pos_p, std::string::npos);
|
||||||
|
return true;
|
||||||
|
} else if (url.at(pos_c) == '?') {
|
||||||
|
/* found query part */
|
||||||
|
path = url.substr(pos_p, pos_c - pos_p);
|
||||||
|
pos_p = pos_c + 1;
|
||||||
|
pos_c = url.find('#', pos_p);
|
||||||
|
if (pos_c == std::string::npos) {
|
||||||
|
/* no fragment */
|
||||||
|
query = url.substr(pos_p, std::string::npos);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
query = url.substr(pos_p, pos_c - pos_p);
|
||||||
|
pos_p = pos_c + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* found fragment part */
|
||||||
|
path = url.substr(pos_p, pos_c - pos_p);
|
||||||
|
pos_p = pos_c + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pos_p now at start of fragment part */
|
||||||
|
frag = url.substr(pos_p, std::string::npos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool URL::parse_query(std::map<std::string, std::string> & params) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
strsplit(query, tokens, '&');
|
||||||
|
|
||||||
|
params.clear();
|
||||||
|
for (auto it : tokens) {
|
||||||
|
std::size_t eq = it.find ('=');
|
||||||
|
if (eq != std::string::npos) {
|
||||||
|
auto e = std::pair<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1));
|
||||||
|
params.insert(e);
|
||||||
|
} else {
|
||||||
|
auto e = std::pair<std::string, std::string>(it, "");
|
||||||
|
params.insert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string URL::to_string() {
|
||||||
|
std::string out = "";
|
||||||
|
if (schema != "") {
|
||||||
|
out = schema + "://";
|
||||||
|
if (user != "" && pass != "") {
|
||||||
|
out += user + ":" + pass + "@";
|
||||||
|
} else if (user != "") {
|
||||||
|
out += user + "@";
|
||||||
|
}
|
||||||
|
if (port) {
|
||||||
|
out += host + ":" + std::to_string(port);
|
||||||
|
} else {
|
||||||
|
out += host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out += path;
|
||||||
|
if (query != "")
|
||||||
|
out += "?" + query;
|
||||||
|
if (frag != "")
|
||||||
|
out += "#" + frag;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPReq::parse(const char *buf, size_t len) {
|
||||||
|
std::string str(buf, len);
|
||||||
|
return parse(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPReq::parse(const std::string& str) {
|
||||||
|
enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
|
||||||
|
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
|
||||||
|
std::size_t eol = 0, pos = 0;
|
||||||
|
URL url;
|
||||||
|
|
||||||
|
if (eoh == std::string::npos)
|
||||||
|
return 0; /* str not contains complete request */
|
||||||
|
|
||||||
|
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
|
||||||
|
if (expect == REQ_LINE) {
|
||||||
|
std::string line = str.substr(pos, eol - pos);
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
strsplit(line, tokens, ' ');
|
||||||
|
if (tokens.size() != 3)
|
||||||
|
return -1;
|
||||||
|
if (!is_http_method(tokens[0]))
|
||||||
|
return -1;
|
||||||
|
if (!is_http_version(tokens[2]))
|
||||||
|
return -1;
|
||||||
|
if (!url.parse(tokens[1]))
|
||||||
|
return -1;
|
||||||
|
/* all ok */
|
||||||
|
method = tokens[0];
|
||||||
|
uri = tokens[1];
|
||||||
|
version = tokens[2];
|
||||||
|
expect = HEADER_LINE;
|
||||||
|
} else {
|
||||||
|
std::string line = str.substr(pos, eol - pos);
|
||||||
|
if (!parse_header_line(line, headers))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pos = eol + strlen(CRLF);
|
||||||
|
if (pos >= eoh)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto it = headers.find("Host");
|
||||||
|
if (it != headers.end ()) {
|
||||||
|
host = it->second;
|
||||||
|
} else if (version == "HTTP/1.1") {
|
||||||
|
return -1; /* 'Host' header required for HTTP/1.1 */
|
||||||
|
} else if (url.host != "") {
|
||||||
|
host = url.host;
|
||||||
|
}
|
||||||
|
return eoh + strlen(HTTP_EOH);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string HTTPReq::to_string() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << method << " " << uri << " " << version << CRLF;
|
||||||
|
ss << "Host: " << host << CRLF;
|
||||||
|
for (auto & h : headers) {
|
||||||
|
ss << h.first << ": " << h.second << CRLF;
|
||||||
|
}
|
||||||
|
ss << CRLF;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPRes::is_chunked() {
|
||||||
|
auto it = headers.find("Transfer-Encoding");
|
||||||
|
if (it == headers.end())
|
||||||
|
return false;
|
||||||
|
if (it->second.find("chunked") == std::string::npos)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long int HTTPRes::length() {
|
||||||
|
unsigned long int length = 0;
|
||||||
|
auto it = headers.find("Content-Length");
|
||||||
|
if (it == headers.end())
|
||||||
|
return -1;
|
||||||
|
errno = 0;
|
||||||
|
length = std::strtoul(it->second.c_str(), (char **) NULL, 10);
|
||||||
|
if (errno != 0)
|
||||||
|
return -1;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPRes::parse(const char *buf, size_t len) {
|
||||||
|
std::string str(buf, len);
|
||||||
|
return parse(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPRes::parse(const std::string& str) {
|
||||||
|
enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
|
||||||
|
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
|
||||||
|
std::size_t eol = 0, pos = 0;
|
||||||
|
|
||||||
|
if (eoh == std::string::npos)
|
||||||
|
return 0; /* str not contains complete request */
|
||||||
|
|
||||||
|
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
|
||||||
|
if (expect == RES_LINE) {
|
||||||
|
std::string line = str.substr(pos, eol - pos);
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
strsplit(line, tokens, ' ', 3);
|
||||||
|
if (tokens.size() != 3)
|
||||||
|
return -1;
|
||||||
|
if (!is_http_version(tokens[0]))
|
||||||
|
return -1;
|
||||||
|
code = atoi(tokens[1].c_str());
|
||||||
|
if (code < 100 || code >= 600)
|
||||||
|
return -1;
|
||||||
|
/* all ok */
|
||||||
|
version = tokens[0];
|
||||||
|
status = tokens[2];
|
||||||
|
expect = HEADER_LINE;
|
||||||
|
} else {
|
||||||
|
std::string line = str.substr(pos, eol - pos);
|
||||||
|
if (!parse_header_line(line, headers))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pos = eol + strlen(CRLF);
|
||||||
|
if (pos >= eoh)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return eoh + strlen(HTTP_EOH);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string HTTPRes::to_string() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << version << " " << code << " " << status << CRLF;
|
||||||
|
for (auto & h : headers) {
|
||||||
|
ss << h.first << ": " << h.second << CRLF;
|
||||||
|
}
|
||||||
|
ss << CRLF;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * HTTPCodeToStatus(int code) {
|
||||||
|
const char *ptr;
|
||||||
|
switch (code) {
|
||||||
|
case 105: ptr = "Name Not Resolved"; break;
|
||||||
|
/* success */
|
||||||
|
case 200: ptr = "OK"; break;
|
||||||
|
case 206: ptr = "Partial Content"; break;
|
||||||
|
/* redirect */
|
||||||
|
case 301: ptr = "Moved Permanently"; break;
|
||||||
|
case 302: ptr = "Found"; break;
|
||||||
|
case 304: ptr = "Not Modified"; break;
|
||||||
|
case 307: ptr = "Temporary Redirect"; break;
|
||||||
|
/* client error */
|
||||||
|
case 400: ptr = "Bad Request"; break;
|
||||||
|
case 401: ptr = "Unauthorized"; break;
|
||||||
|
case 403: ptr = "Forbidden"; break;
|
||||||
|
case 404: ptr = "Not Found"; break;
|
||||||
|
case 407: ptr = "Proxy Authentication Required"; break;
|
||||||
|
case 408: ptr = "Request Timeout"; break;
|
||||||
|
/* server error */
|
||||||
|
case 500: ptr = "Internal Server Error"; break;
|
||||||
|
case 502: ptr = "Bad Gateway"; break;
|
||||||
|
case 503: ptr = "Not Implemented"; break;
|
||||||
|
case 504: ptr = "Gateway Timeout"; break;
|
||||||
|
default: ptr = "Unknown Status"; break;
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UrlDecode(const std::string& data, bool allow_null) {
|
||||||
|
std::string decoded(data);
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = decoded.find('%', pos)) != std::string::npos) {
|
||||||
|
char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
|
||||||
|
if (c == '\0' && !allow_null) {
|
||||||
|
pos += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
decoded.replace(pos, 3, 1, c);
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MergeChunkedResponse (std::istream& in, std::ostream& out) {
|
||||||
|
std::string hexLen;
|
||||||
|
long int len;
|
||||||
|
while (!in.eof ()) {
|
||||||
|
std::getline (in, hexLen);
|
||||||
|
errno = 0;
|
||||||
|
len = strtoul(hexLen.c_str(), (char **) NULL, 16);
|
||||||
|
if (errno != 0)
|
||||||
|
return false; /* conversion error */
|
||||||
|
if (len == 0)
|
||||||
|
return true; /* end of stream */
|
||||||
|
if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */
|
||||||
|
return false; /* too large chunk */
|
||||||
|
char * buf = new char[len];
|
||||||
|
in.read (buf, len);
|
||||||
|
out.write (buf, len);
|
||||||
|
delete[] buf;
|
||||||
|
std::getline (in, hexLen); // read \r\n after chunk
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // http
|
||||||
|
} // i2p
|
||||||
121
HTTP.h
Normal file
121
HTTP.h
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016, The PurpleI2P Project
|
||||||
|
*
|
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3
|
||||||
|
*
|
||||||
|
* See full license text in LICENSE file at top of project tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTP_H__
|
||||||
|
#define HTTP_H__
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace http {
|
||||||
|
const char CRLF[] = "\r\n"; /**< HTTP line terminator */
|
||||||
|
const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */
|
||||||
|
extern const std::vector<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
|
||||||
|
extern const std::vector<std::string> HTTP_VERSIONS; /**< list of valid HTTP versions */
|
||||||
|
|
||||||
|
struct URL {
|
||||||
|
std::string schema;
|
||||||
|
std::string user;
|
||||||
|
std::string pass;
|
||||||
|
std::string host;
|
||||||
|
unsigned short int port;
|
||||||
|
std::string path;
|
||||||
|
std::string query;
|
||||||
|
std::string frag;
|
||||||
|
|
||||||
|
URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to parse url from string
|
||||||
|
* @return true on success, false on invalid url
|
||||||
|
*/
|
||||||
|
bool parse (const char *str, size_t len = 0);
|
||||||
|
bool parse (const std::string& url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse query part of url to key/value map
|
||||||
|
* @note Honestly, this should be implemented with std::multimap
|
||||||
|
*/
|
||||||
|
bool parse_query(std::map<std::string, std::string> & params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serialize URL structure to url
|
||||||
|
* @note Returns relative url if schema if empty, absolute url otherwise
|
||||||
|
*/
|
||||||
|
std::string to_string ();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HTTPReq {
|
||||||
|
std::map<std::string, std::string> headers;
|
||||||
|
std::string version;
|
||||||
|
std::string method;
|
||||||
|
std::string uri;
|
||||||
|
std::string host;
|
||||||
|
|
||||||
|
HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to parse HTTP request from string
|
||||||
|
* @return -1 on error, 0 on incomplete query, >0 on success
|
||||||
|
* @note Positive return value is a size of header
|
||||||
|
*/
|
||||||
|
int parse(const char *buf, size_t len);
|
||||||
|
int parse(const std::string& buf);
|
||||||
|
|
||||||
|
/** @brief Serialize HTTP request to string */
|
||||||
|
std::string to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HTTPRes {
|
||||||
|
std::map<std::string, std::string> headers;
|
||||||
|
std::string version;
|
||||||
|
std::string status;
|
||||||
|
unsigned short int code;
|
||||||
|
|
||||||
|
HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to parse HTTP response from string
|
||||||
|
* @return -1 on error, 0 on incomplete query, >0 on success
|
||||||
|
* @note Positive return value is a size of header
|
||||||
|
*/
|
||||||
|
int parse(const char *buf, size_t len);
|
||||||
|
int parse(const std::string& buf);
|
||||||
|
|
||||||
|
/** @brief Serialize HTTP response to string */
|
||||||
|
std::string to_string();
|
||||||
|
|
||||||
|
/** @brief Checks that response declared as chunked data */
|
||||||
|
bool is_chunked();
|
||||||
|
|
||||||
|
/** @brief Returns declared response length or -1 if unknown */
|
||||||
|
long int length();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief returns HTTP status string by integer code
|
||||||
|
* @param code HTTP code [100, 599]
|
||||||
|
* @return Immutable string with status
|
||||||
|
*/
|
||||||
|
const char * HTTPCodeToStatus(int code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replaces %-encoded characters in string with their values
|
||||||
|
* @param data Source string
|
||||||
|
* @param null If set to true - decode also %00 sequence, otherwise - skip
|
||||||
|
* @return Decoded string
|
||||||
|
*/
|
||||||
|
std::string UrlDecode(const std::string& data, bool null = false);
|
||||||
|
} // http
|
||||||
|
} // i2p
|
||||||
|
|
||||||
|
#endif /* HTTP_H__ */
|
||||||
356
HTTPProxy.cpp
Normal file
356
HTTPProxy.cpp
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
#include <cstring>
|
||||||
|
#include <cassert>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <boost/regex.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
#include "HTTPProxy.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "I2PTunnel.h"
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace proxy
|
||||||
|
{
|
||||||
|
static const size_t http_buffer_size = 8192;
|
||||||
|
class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<HTTPProxyHandler>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
enum state
|
||||||
|
{
|
||||||
|
GET_METHOD,
|
||||||
|
GET_HOSTNAME,
|
||||||
|
GET_HTTPV,
|
||||||
|
GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed
|
||||||
|
DONE
|
||||||
|
};
|
||||||
|
|
||||||
|
void EnterState(state nstate);
|
||||||
|
bool HandleData(uint8_t *http_buff, std::size_t len);
|
||||||
|
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
|
||||||
|
void Terminate();
|
||||||
|
void AsyncSockRead();
|
||||||
|
void HTTPRequestFailed(/*std::string message*/);
|
||||||
|
void RedirectToJumpService();
|
||||||
|
void ExtractRequest();
|
||||||
|
bool IsI2PAddress();
|
||||||
|
bool ValidateHTTPRequest();
|
||||||
|
void HandleJumpServices();
|
||||||
|
bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len);
|
||||||
|
void SentHTTPFailed(const boost::system::error_code & ecode);
|
||||||
|
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
|
||||||
|
uint8_t m_http_buff[http_buffer_size];
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock;
|
||||||
|
std::string m_request; //Data left to be sent
|
||||||
|
std::string m_url; //URL
|
||||||
|
std::string m_method; //Method
|
||||||
|
std::string m_version; //HTTP version
|
||||||
|
std::string m_address; //Address
|
||||||
|
std::string m_path; //Path
|
||||||
|
int m_port; //Port
|
||||||
|
state m_state;//Parsing state
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
HTTPProxyHandler(HTTPProxyServer * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock) :
|
||||||
|
I2PServiceHandler(parent), m_sock(sock)
|
||||||
|
{ EnterState(GET_METHOD); }
|
||||||
|
~HTTPProxyHandler() { Terminate(); }
|
||||||
|
void Handle () { AsyncSockRead(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void HTTPProxyHandler::AsyncSockRead()
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "HTTPProxy: async sock read");
|
||||||
|
if(m_sock) {
|
||||||
|
m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size),
|
||||||
|
std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "HTTPProxy: no socket for read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::Terminate() {
|
||||||
|
if (Kill()) return;
|
||||||
|
if (m_sock)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "HTTPProxy: close sock");
|
||||||
|
m_sock->close();
|
||||||
|
m_sock = nullptr;
|
||||||
|
}
|
||||||
|
Done(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All hope is lost beyond this point */
|
||||||
|
//TODO: handle this apropriately
|
||||||
|
void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/)
|
||||||
|
{
|
||||||
|
static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n";
|
||||||
|
boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()),
|
||||||
|
std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::RedirectToJumpService(/*HTTPProxyHandler::errTypes error*/)
|
||||||
|
{
|
||||||
|
std::stringstream response;
|
||||||
|
std::string httpAddr; i2p::config::GetOption("http.address", httpAddr);
|
||||||
|
uint16_t httpPort; i2p::config::GetOption("http.port", httpPort);
|
||||||
|
|
||||||
|
response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n";
|
||||||
|
boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()),
|
||||||
|
std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate)
|
||||||
|
{
|
||||||
|
m_state = nstate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::ExtractRequest()
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url);
|
||||||
|
std::string server="";
|
||||||
|
std::string port="80";
|
||||||
|
boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)");
|
||||||
|
boost::smatch m;
|
||||||
|
std::string path;
|
||||||
|
if(boost::regex_search(m_url, m, rHTTP, boost::match_extra))
|
||||||
|
{
|
||||||
|
server=m[1].str();
|
||||||
|
if (m[2].str() != "") port=m[3].str();
|
||||||
|
path=m[4].str();
|
||||||
|
}
|
||||||
|
LogPrint(eLogDebug, "HTTPProxy: server: ", server, ", port: ", port, ", path: ", path);
|
||||||
|
m_address = server;
|
||||||
|
m_port = boost::lexical_cast<int>(port);
|
||||||
|
m_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPProxyHandler::ValidateHTTPRequest()
|
||||||
|
{
|
||||||
|
if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" )
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version);
|
||||||
|
HTTPRequestFailed(); //TODO: send right stuff
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::HandleJumpServices()
|
||||||
|
{
|
||||||
|
static const char * helpermark1 = "?i2paddresshelper=";
|
||||||
|
static const char * helpermark2 = "&i2paddresshelper=";
|
||||||
|
size_t addressHelperPos1 = m_path.rfind (helpermark1);
|
||||||
|
size_t addressHelperPos2 = m_path.rfind (helpermark2);
|
||||||
|
size_t addressHelperPos;
|
||||||
|
if (addressHelperPos1 == std::string::npos)
|
||||||
|
{
|
||||||
|
if (addressHelperPos2 == std::string::npos)
|
||||||
|
return; //Not a jump service
|
||||||
|
else
|
||||||
|
addressHelperPos = addressHelperPos2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (addressHelperPos2 == std::string::npos)
|
||||||
|
addressHelperPos = addressHelperPos1;
|
||||||
|
else if ( addressHelperPos1 > addressHelperPos2 )
|
||||||
|
addressHelperPos = addressHelperPos1;
|
||||||
|
else
|
||||||
|
addressHelperPos = addressHelperPos2;
|
||||||
|
}
|
||||||
|
auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1));
|
||||||
|
base64 = i2p::util::http::urlDecode(base64); //Some of the symbols may be urlencoded
|
||||||
|
LogPrint (eLogInfo, "HTTPProxy: jump service for ", m_address, ", inserting to address book");
|
||||||
|
//TODO: this is very dangerous and broken. We should ask the user before doing anything see http://pastethis.i2p/raw/pn5fL4YNJL7OSWj3Sc6N/
|
||||||
|
//TODO: we could redirect the user again to avoid dirtiness in the browser
|
||||||
|
i2p::client::context.GetAddressBook ().InsertAddress (m_address, base64);
|
||||||
|
m_path.erase(addressHelperPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPProxyHandler::IsI2PAddress()
|
||||||
|
{
|
||||||
|
auto pos = m_address.rfind (".i2p");
|
||||||
|
if (pos != std::string::npos && (pos+4) == m_address.length ())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len)
|
||||||
|
{
|
||||||
|
ExtractRequest(); //TODO: parse earlier
|
||||||
|
if (!ValidateHTTPRequest()) return false;
|
||||||
|
HandleJumpServices();
|
||||||
|
|
||||||
|
i2p::data::IdentHash identHash;
|
||||||
|
if (IsI2PAddress ())
|
||||||
|
{
|
||||||
|
if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){
|
||||||
|
RedirectToJumpService();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
m_request = m_method;
|
||||||
|
m_request.push_back(' ');
|
||||||
|
m_request += m_path;
|
||||||
|
m_request.push_back(' ');
|
||||||
|
m_request += m_version;
|
||||||
|
m_request.push_back('\r');
|
||||||
|
m_request.push_back('\n');
|
||||||
|
m_request.append("Connection: close\r\n");
|
||||||
|
// TODO: temporary shortcut. Must be implemented properly
|
||||||
|
uint8_t * eol = nullptr;
|
||||||
|
bool isEndOfHeader = false;
|
||||||
|
while (!isEndOfHeader && len && (eol = (uint8_t *)memchr (http_buff, '\r', len)))
|
||||||
|
{
|
||||||
|
if (eol)
|
||||||
|
{
|
||||||
|
*eol = 0; eol++;
|
||||||
|
if (strncmp ((const char *)http_buff, "Referer", 7) && strncmp ((const char *)http_buff, "Connection", 10)) // strip out referer and connection
|
||||||
|
{
|
||||||
|
if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent
|
||||||
|
m_request.append("User-Agent: MYOB/6.66 (AN/ON)");
|
||||||
|
else
|
||||||
|
m_request.append ((const char *)http_buff);
|
||||||
|
m_request.append ("\r\n");
|
||||||
|
}
|
||||||
|
isEndOfHeader = !http_buff[0];
|
||||||
|
auto l = eol - http_buff;
|
||||||
|
http_buff = eol;
|
||||||
|
len -= l;
|
||||||
|
if (len > 0) // \r
|
||||||
|
{
|
||||||
|
http_buff++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_request.append(reinterpret_cast<const char *>(http_buff),len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len)
|
||||||
|
{
|
||||||
|
while (len > 0)
|
||||||
|
{
|
||||||
|
//TODO: fallback to finding HOst: header if needed
|
||||||
|
switch (m_state)
|
||||||
|
{
|
||||||
|
case GET_METHOD:
|
||||||
|
switch (*http_buff)
|
||||||
|
{
|
||||||
|
case ' ': EnterState(GET_HOSTNAME); break;
|
||||||
|
default: m_method.push_back(*http_buff); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET_HOSTNAME:
|
||||||
|
switch (*http_buff)
|
||||||
|
{
|
||||||
|
case ' ': EnterState(GET_HTTPV); break;
|
||||||
|
default: m_url.push_back(*http_buff); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET_HTTPV:
|
||||||
|
switch (*http_buff)
|
||||||
|
{
|
||||||
|
case '\r': EnterState(GET_HTTPVNL); break;
|
||||||
|
default: m_version.push_back(*http_buff); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET_HTTPVNL:
|
||||||
|
switch (*http_buff)
|
||||||
|
{
|
||||||
|
case '\n': EnterState(DONE); break;
|
||||||
|
default:
|
||||||
|
LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff));
|
||||||
|
HTTPRequestFailed(); //TODO: add correct code
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state);
|
||||||
|
HTTPRequestFailed(); //TODO: add correct code 500
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
http_buff++;
|
||||||
|
len--;
|
||||||
|
if (m_state == DONE)
|
||||||
|
return CreateHTTPRequest(http_buff,len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes");
|
||||||
|
if(ecode)
|
||||||
|
{
|
||||||
|
LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode);
|
||||||
|
Terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HandleData(m_http_buff, len))
|
||||||
|
{
|
||||||
|
if (m_state == DONE)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url);
|
||||||
|
GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete,
|
||||||
|
shared_from_this(), std::placeholders::_1), m_address, m_port);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AsyncSockRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ());
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
if (Kill()) return;
|
||||||
|
LogPrint (eLogInfo, "HTTPProxy: New I2PTunnel connection");
|
||||||
|
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, stream);
|
||||||
|
GetOwner()->AddHandler (connection);
|
||||||
|
connection->I2PConnect (reinterpret_cast<const uint8_t*>(m_request.data()), m_request.size());
|
||||||
|
Done(shared_from_this());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info");
|
||||||
|
HTTPRequestFailed(); // TODO: Send correct error message host unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination):
|
||||||
|
TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::client::I2PServiceHandler> HTTPProxyServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||||
|
{
|
||||||
|
return std::make_shared<HTTPProxyHandler> (this, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
32
HTTPProxy.h
Normal file
32
HTTPProxy.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef HTTP_PROXY_H__
|
||||||
|
#define HTTP_PROXY_H__
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <mutex>
|
||||||
|
#include "I2PService.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace proxy
|
||||||
|
{
|
||||||
|
class HTTPProxyServer: public i2p::client::TCPIPAcceptor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
|
||||||
|
~HTTPProxyServer() {};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Implements TCPIPAcceptor
|
||||||
|
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||||
|
const char* GetName() { return "HTTP Proxy"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef HTTPProxyServer HTTPProxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
936
HTTPServer.cpp
Normal file
936
HTTPServer.cpp
Normal file
@@ -0,0 +1,936 @@
|
|||||||
|
#include <ctime>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
|
#include "Base.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "TransitTunnel.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "HTTP.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "HTTPServer.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
|
||||||
|
// For image and info
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace http {
|
||||||
|
const char *itoopieImage =
|
||||||
|
"<img alt=\"ICToopie Icon\" src=\"data:image/png;base64,"
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXM"
|
||||||
|
"AAA3XAAAN1wFCKJt4AAAAB3RJTUUH3ggRChYFXVBoSgAAIABJREFUeNrtnXl8VOX1/9/PvXcmewiQBB"
|
||||||
|
"J2CKsKihQXkCJuiD8VKyptXejXaikWbe1C1dqi0lpr7UvrgihV64ZCXaqCUBEUQVBAAZUl7EtYEkLIP"
|
||||||
|
"pmZ+zy/P+6dySx3JgESkpAcX/MiznLvc8/5POc55zznnAfaqFWTaIXPnAt0AzoBqYAB1AAVwAFgF3Ck"
|
||||||
|
"DQCnBvUHxgEXAMOBLsfw22+BT4ElwGKgrE1ftAwaBswEygFlv+QJvALX2AH8BchrY3HzpF8A+xtA4PU"
|
||||||
|
"BwxZgUhvLmwf9AfA1suBjgaEK+GWbDdA0dAswC0iwhVEvSk5A9smFThmIjFSUroPHC0cr0HYexNxTiH"
|
||||||
|
"aMfBFAiT2e99sA0PiUBXwMnFEfwZ/ZB3n1eTDmTDh3IMKdgoYZoi8CXBCABhioA/uRn3+H+OgreGcFq"
|
||||||
|
"vAoWj15udQ2Oj1tAGgcmgS8WJfgczsif3sd3D4OkZyCZnqPQUWEkKaBlgDbd2PO+gDx5H/B462TZwq4"
|
||||||
|
"zPYc2gDQgPQmcH084Z/eE/nkHYjRw9H8VQ17c02A5ka99j/kb59DHDgSl3cC+BswrQ0AJ04GsB4YFEv"
|
||||||
|
"47VJQr/8eNW4kuv8kKF8jEfXSfOSUf6JVe+PydhEwtg0Ax0/Jtv+dHesLU65EPn0Xmt/XJM+ibn0M8+"
|
||||||
|
"XF6HH4+xVwdhsAjp0Sgb1AB6dxCoH67B+oEaeh+80mVE8GLP0a8+LfI6R05KcA1gFntQHg2GgX0N3pg"
|
||||||
|
"87tkd/NRktPbj7jr/SghkxG7j7k6DEI23O5uLkxWWumwl8WS/i9OmPueQ3RnIQPkJKI2PUq+jkDgs5l"
|
||||||
|
"pGdwEfDPNgDUTQ9hbd5EUfds5PZ/owvRPIHr98Oqp9EvHBITBFOBa9qWgNg0FFjrZO1npKIOvgm61my"
|
||||||
|
"1Vq1d4IbhP0euzo9pE3TAih62ASCCioH2TrNn72vQuUPzF34QBDoqdyLywBHHMa+zwd62BITQX+yZEU"
|
||||||
|
"X/uR+V04TCN9ygFOaafNTbyzHnLsNc9g3S60ca7hjLgYlY9yxajNjFWcBNbRqgltKBUidmTRiFnPcnd"
|
||||||
|
"L9DwEUI0JNgz17k5xuRBYfRvX7I6YD8/mBEr+5o/uoTEHwC6vn3UE+9h9qwwxmAw/oh/3or4qJhaE5j"
|
||||||
|
"fGcF5vUzHH/rtV3dNgBgxfdvcfL1a+YjhIgep6Ej/zYX+eg8tMOlzs/RLQv52M/gujHo/pr6D0bXYG0"
|
||||||
|
"+5iX3II5W1I9Hlw/HnP8Qhimjtce432N+uDoKBAJ4AJje2gHQDjjqNPtn34265ZJwxmkarMnHvOi3iA"
|
||||||
|
"pP/cY/5izkx4/UL2CkaTBvGf6Jfw6L7gXus/aCCy4YcujQoZL8/HzdXrKC4x7UHfXdbLTI+1TXINPHO"
|
||||||
|
"/JbNLUMmoMNMN1J+DkdkLdeGc4cXYO3l+M/ZypaiPAFsHvMmDFFl1122ZoxY8Zsyc7OLgxl7JKv0YZM"
|
||||||
|
"RhquugezJh8zQvjmpEmT9hUWFuYrpc5etmyZsWXLliylVOLs2bPXCyFKA/fauAcxfjr+SLsgORHtjz+"
|
||||||
|
"OuYl1F62c/Dhk3My5F7/vQ1Toa8XjmIHPhRAK2L1w4cIDSimPiqCCgoJdI0aM2EtIptAtl+BTH4VfM/"
|
||||||
|
"SlPkalJ9feIyEhQa5fv36Nik/Fffv2LbHHIwH5v4ejx24uQkLttUNe+1uz8K/CIZUrIxVTLUWGMXAhM"
|
||||||
|
"tFdK/y8vLzNSimzDuGo++67b37oPdY8HS2cwOuZqWECqtm0adNaVT86AhQEftuvK361NAIAC1G/uc4R"
|
||||||
|
"AAo4s7UuAT9xUv+/uQ5l1tSqcE3A/f9GeWwru127dnu2bt3auz7jnzFjxriJEyeuEkIIgDufRjm5boY"
|
||||||
|
"bZn4QHIuYPn367gEDBtTXV2+/atWqI4GlIH8f2uYdhFkCUsG06x1/q2jCBNOmNgKVEwDK/otKctcK10"
|
||||||
|
"hEuS5G+U3LaNq5c2dhz549s4/hPj4hxFEgE6BoHmSkhj+7pmHqlwXvWaaUcmFtR9ebMjMzNxcXF/cHm"
|
||||||
|
"DEJNe2GcIAabjhnCuaXW6KAexCrYKVVaQDH2TW8PzItNXxcK9cjbeGTnZ295xiFD+CaMmWKPwD4uZ9G"
|
||||||
|
"g+7bnbX3vP766w8fq/ABpk2bFrTqV26ytorDjB0v3Oi8H5hje0OtCgCOrJh4ocWoUFqxsXac11xzzXG"
|
||||||
|
"Nefz48cGrLvsWZUSkcBwuq00RHTNmzHFlGFx55ZU5gb93HUQ6cffakTG17oWtDQDnO6n/K8+JUs1s3x"
|
||||||
|
"9cT8WgQYNkHdfdiVUVFEaDBw/2Bf7eVgCROTyGXntfl8t1XBmFOTk5e4O+vxflJOrcXLTUxKjdQgWc0"
|
||||||
|
"9oAcKZT5C+vdzjbBODzhwfqnC722Wef7cnMzNwthOglhEjMzMxct2HDhj1BARtG8CpHK6OF0yWz9u/8"
|
||||||
|
"/PxOAEoppJSlU6ZM2dipU6cCIcSXEyZM2KaUKncaQ3l5eXrQHkhHd/T8vTDydEctcEZrA0CPyDfOykP"
|
||||||
|
"hD2eOlJCdEXxPff7551FFmgsWLDg4atSorsXFxd3t2WQUFxcPGTJkSJeFCxceBti2bVtwoyk1CREpnD"
|
||||||
|
"7dEQGj9IknnvABFBcXl+u6rs+cOXNQYWFhLvC9t956K0/TtIMQvee/fPny4FUHdEcqf/RDmyYM6VN/m"
|
||||||
|
"+hUBUCa05uDutuhkgjdOLRvSFRvyZLIHcODV1xxRaxqHu3yyy/XgKqXXnopKI7enR3EZyLGnGnBwuPx"
|
||||||
|
"dP/666935+Xl7QNSIpYqJYToO3Xq1PWRN3vooYeqA98dOwzNdFislILeOTENwVYDAEeXp1uWNUOi7IJ"
|
||||||
|
"za4VbVFTUafXq1RtCZr+POFnDQIfbb7/962effbZdQDgjT7eyd8IsdB9MqQ09q6FDh3rKysoGOvquSq"
|
||||||
|
"mnnnoqzGpftGjRVxs3buwf+MrE0bFd7JwOxLJjcloLABz3/TukoTktmwkuxPgRwVmohg8fHtQg+/btK"
|
||||||
|
"60r1vD888+PCHXrbr7YWTjXjkHLzggKp59SKl5BUW9gD8CKFSu2jh07tm8AYPdMRCkVGwDtU2Omkbca"
|
||||||
|
"ACThLGhHhvtNeGZqqLEoemVnZx+srKwsGjhwYHo9A04A/L9zUZkZzs/t98D8GfUPjuXn538+ZsyYb0e"
|
||||||
|
"OHNkXq9sInTKQf/kpuowDHU3EvEdGawGA476cz4zN/OwMtNl3WxaCUkoVFRV1Sk1NTZg5c+aeY4k8vv"
|
||||||
|
"w7hN8f+wvD+qH9YzL1iQPI/v37T1y6dOnpAYClJKK+eQ7N74v/Q1PGXAJcrQUAjiyqjJO9oxTcOg7jr"
|
||||||
|
"7eGCSdtzpw5I6ln7eeqf0JaUvwZ7jfhVxMwnrmTuuINQa8By1CVB96AjLS6NUhI0CkKG60FAJVOb+4p"
|
||||||
|
"wtTjjMjvg2k3YCx6GJmUEK3eY1G3LGT+i6hhfev3vH4f/OwK9J2voEYPiS+UIX2Q707HXDsLPSkBrT7"
|
||||||
|
"rx/7imOOoONmCMJoIAMWOAChEF5qThx0+Q8eciV71PuqRNzGffg+xtyiaoalJyAuHwE8vR1w1yioaPZ"
|
||||||
|
"YScSmhayba0sfQjpYhF3yJ2rwXUVqJmdkO47QeyEuGItLSrHzF+qacCQFbC1Ax3NZDJ1sQTbUbmGxrg"
|
||||||
|
"TCZdEzHPPweRn0TOYUAPQHwYe4uRPj8kJwAudmAjoYv2t07YYYJazk67hnngot+g1yyzjE9zDjZy0BT"
|
||||||
|
"bgc7bgXXLEBqIqab1OLJSIbkSzCrvVFayw+4W4sNAFbxZxR9/DWnNB04gHQQPlhl5LQmAKx3evO9ldY"
|
||||||
|
"O4KlK76+KaYqsbG0AWO20BL35CWiJp6bwDRe8sTTmUvxxawOAIytKKtBWf4N5KgLA40EuXR+T5/NbGw"
|
||||||
|
"A+j/XB0/+1agBONZr5flxtqFobAMBqohRF//4IzedvGoY0mvpPRP15Tkz1/3JTjaupAfCvWK7oA68it"
|
||||||
|
"VOol/m8j5HFZTHd7tlNNa7mwOJYcT9VMx+haS2/pb2RiOr8A9ShEsdnWYjVXbRVagCAR2IAUdz+BKbR"
|
||||||
|
"wkNCQsATc5ExhC+AGU06vmbAowSs3rqOa/6GWaiB3WmxJmGlB5lxTUxeb8U61ILWrAFqgEdjgfHSe1C"
|
||||||
|
"Gq2UK30hAjbsvpvAF8KumHmNzmVnTsGLhUXTwCNqND+NvaSDQNXj4VczPN8bUspuABU0+zmbEs93EaK"
|
||||||
|
"H2zU60HlmYZ+WhqRbiHK74DnnTIzEnmMCqjDrU1ONsbhb2GuLkxy97DHX+ac0fBNv2Yw68NW73D59t+"
|
||||||
|
"zQ5NTfjamw8UI76NWLtVqRoxo7hzoP4T7utztYvbqyDrZp+qWpm/KvCSrUeH+sLsz9EDO+PHNANTTYj"
|
||||||
|
"TaAJWL8D84zb0eKlhIfQ97CaSnzVBoBwWgecS5zj2V5fitAE8sJhCGk2/TJmuOHVxcjL7zvm84ausgG"
|
||||||
|
"/rs0GAObOhQ8+QLz8Msp2D+Pa/qMGIz/8M8JtNGETSRfqhzMw3/jkuCeTAO4B/tpmBAJCMFIpXsc63r"
|
||||||
|
"VOJa8J1CvTUD+67OScFhI665evx3/FH9DKqsL4qM7nbDqSIQ9QqK3hm/rwWQBPY5192GoB4BaCuUpxN"
|
||||||
|
"cexNTq0L2r5P8DVyNrAcMGuA6jJT6AWrQnn37WMlT/kKg2UkCh0NHR01vKt+ojP1CrW1XXO0HvA1a0R"
|
||||||
|
"AFcC79ZzPMECzsgPrj4P+e4DDX+CSKAl7RfrMR94BSK7fmbTUT3Ar0QmGULGwK6Ojh+/eoV31XyWiDj"
|
||||||
|
"PtpwY7fJPVQC8BfxACOKWYuaQLccx2ncOZ/o6kam2sUu7h0dTvCFFRmf0Qm6Y7dxXONCvxzTrl9ZtGJ"
|
||||||
|
"anvnkr5pyl8NwCKyoZ7beOkrfzQ91H/fLPNTQKOCin8VdR41wgJbDyA88/1QEwGPiEOgoiu5Erf8r1n"
|
||||||
|
"rMY5K+mJmy8bzI/4W0WBlOp774W+eht4YWZhhtmvYf8cDVKSkSfXNSg7ojeOaiMVLT0ZJQmrPMAj1bC"
|
||||||
|
"7kPIrQVoq7cgF64BUzovKSkkq3uYrAaSp/uPI4Otkmp1O/fidwaOAOZhHZN3SgLgfuDBgBp3KrZIJkl"
|
||||||
|
"N4UbPBXzP54kQfIDms9T9Mm8HI2oFc1DZIZW/moCH30D+4aWGe84cstRVXMJYRmlefCd0rU1sM6fzRL"
|
||||||
|
"xw8R3AM41q05xkwacDn2L1BwqKPEL4YjyXem7mB14fPmIJX0Own0NB5o0dhszNQg+tzFWg/vDSiQ+6P"
|
||||||
|
"e3UBQzjIkbQk66ahxpOVPgAQxio96OXmc9OJxAo2zN4HauZdosHwDXA20RUBIXO/q50lvcztaoD7ZSv"
|
||||||
|
"DgYnkKDW8m1w/HeOR0SWZb++JLwGbzTnmns5oO2hAB9+R2AlkyS70ln0opsaSB8xmAGiI+21GrwoFB5"
|
||||||
|
"qGowhXnxcw2XiEZ6N9RUFPAXc2JIB4Lbdm8siLfcQ4Ysfc7XnOsZ5a/Ai6+EF7qZAL6E0cCKHuvz88A"
|
||||||
|
"JNw4B5n9UCII8e8lf8n2EiMdCRSFVOpfTiFQJBAm6VTpoukbqJiR8TZY+jIYUeSd9jcF3L049bMgBGA"
|
||||||
|
"EvsiJ5ygncG6eoh7q7sRKaswVtvS/o9/ucOXHPCBSj8EZE4F+r9lbWz/xauFQFB2tpFuHHp7pBgYxXV"
|
||||||
|
"nGwy0EV72vlLKNXrMJg3NMb9tUYE1hu2T+uKYeKIUWqY/wUeqcimo1THEPvREHzE58HrTr4SEen7L15"
|
||||||
|
"VO/s7k6UGM6BZppVJJNl0rCuMvKElaYAJwNxYwZoA/VbdVnkeQ81o/1nV6Zx8wJKg8NOTURcNR4SWlB"
|
||||||
|
"s6vLAo1Pi4tFHV+ImQAlzxxfBhS/IC/g3cHE/wncmSM/h1VRop6niEn0Sieo/FQd//l9egTE+EJtNRc"
|
||||||
|
"2oLz9TFjBD+ZlptJoA4QSQBvNqY929ItTizLuFfxAjfs8yoSCNF1RWW0NAQCAo4qCXgVoHzIrexWy/m"
|
||||||
|
"aFBl3j0hOkPovyHG32jORaKaLOCVSALVeKQ7Rum/hkYhxfH6Ec1pCRqgHzA5nvCvZaz3x4yvqcErnFW"
|
||||||
|
"hItA9TUPjOV5P/IgVLstZEGoU3/MNYZD5DouCxt+lZyPbpYX7/oYBL1rHs+gAlzASWWe/p8aY2YJt7J"
|
||||||
|
"YzeFJU4RG96Sb/zr1a5GzX0JTtzcRS/6olAOD78f1AF5OY4KmiWsRaCQPCr6BK/IoHU8qoDNn0UXzKl"
|
||||||
|
"65P+TLMoPzNhGjfH5D/XWmpiySS1Bn016rxnHQAHKRI3sujwefdwV7xPkvkWEaFCXtP7CODBPBcY4+z"
|
||||||
|
"oZaA5+NFq3T0uDo4FOJT+VOo8IO92CLzANuloi45L9pgeGtZ7VoymnOaxPhLJIFHmBX1/qesUu4Ip2g"
|
||||||
|
"jW+PN8HdbCgAgTkJnNR7xBesNZ+FLBAINwYv8J6EKjwgLFMW42S+uQpkR5wYaBrywqPYnFzAM1QRFxl"
|
||||||
|
"vZJQs4GMWLQooJPftaR+drNsYa4OsnY6wNCYAvgHtjgeBv4tmk6Li+InASvBu3WslaV9jMV+ERw9DWM"
|
||||||
|
"VOvRkQaf6YfteDL4DOp0+jXJMbfmhhueyQYXRis5CvRVOq/MQJBD2PFrsMPfRDgVT5xFw+mxArzSqRI"
|
||||||
|
"I1XhgCClrGtI25Yb0A3ZKSt67M8tqLX2hjMkZry/MUlHZyf7HD9zYYQ9/Vd8J2NMGA/WplmLA4C1jMP"
|
||||||
|
"fIx9MAUcpE1P5U6qJiSL02RVevNzFT6rDIgKiFkChdONF0Y0ZjUR44t3ae57DmcJsAt9fR6OcCkfg+U"
|
||||||
|
"JOw9DR+JgVsS7zwskab2OFR39rxwQEhG/3HqZETOa+1AqqRKTW60GuvIfJ1YrwXUKlwq8xfkT0rFm3G"
|
||||||
|
"XPL3tr3z2+CAzgkUr3CO3IHex0/r6Raq8KjAEykWs6aWNb/yy0dAACvAGdBtBleQZW4nftSN7FN1yNS"
|
||||||
|
"6Rdbvn/Y+h+6lAC8+jGyqgYZ6B1gGPDQa7UXGckw5cI4qeq/iCPyRu7mbRaJeJ7HS8yTblx8yCexwp5"
|
||||||
|
"+2546aZHIBiUFbGCwGMIGFfSKrAcaDCgNEbrdKy5hpHcyP/J48XMXD6QWUiycMoSc3ptwAfLBW6wzhT"
|
||||||
|
"In1D7L37mHbuSeTACom7hbefE5tX+NMnrGcaFawRpKKXca4zzghhYLgOD6Hf32UwLuUIE0sJDvJuKmM"
|
||||||
|
"1nmLgr0+gg/8v9Tk5CV1bWnjbzPbGIHnRo+4vcOi8w5vB+qTcsmZVDR1UXKp5Uc+ayKHKxDMlQ95HEX"
|
||||||
|
"8M8WuQTMJe52zi90xA9DPw58twYvuynQNa3W4g8FqF1rJ2JpglDhA5RSftKcfxcGK1gbVhiyrS/mUzl"
|
||||||
|
"0mZZJxv960rtyIPLGduyq54Q7cjKXrgYFwAgeZ26Mh7yXnoYf9YaAoQJEQPjBYI/t5gUEnKzhfzKHzS"
|
||||||
|
"t7oeZ2Y98vO7K/h5viyMJLJx37AUuUOEn5rjp6WDh3eBKHurnoEBiTX4GElOe70PPlLmyvBwgOt0gAf"
|
||||||
|
"AK8wi/FDaDmhrw/i1xm00esQ8kXEDxiFUL2Ddh0gRkf+i8gHu7EnkkZDDg9Ee3yVLo+lE3u9jwyN+Wx"
|
||||||
|
"9/I0CoK/dxjLG7wvKqk6KVogAmji0lQSvA539iuY0I4+d3TgmzpAcLBFAmA01llw07GS2QOa4Gfs51v"
|
||||||
|
"2iwXsls+QIbrSTaym1zYXYriyNUGE8EFAoog+W7BaQVcX3d7uRtdNeRR1dVEYg5ni1/xZSRq/lYSIsK"
|
||||||
|
"U6GbHz2kwFT+YwECiLc8k9LQ4AS4EPQNwMarptC1xvT843gMeplgB3YfIj9sov0LTpZH/lFlo7oCBU+"
|
||||||
|
"EKgBKhfH8SbJJz3cf0WELJ29aP9be2d1eoRSsXPuFcVU6Ias9XgTvbJiLHFTe8yFUaqFiNQ0FJtgPsB"
|
||||||
|
"RY9gHlhoOcvEoFrOEjdRpv5Cd93Axz5d4+IJsqJHD/KASiHANgeEUlCp6DpsJ4UaURGjIFVJ3E/m0Gd"
|
||||||
|
"GNt85gaCMCjGFP/Im800dXWkNpPAEgkQS1Lfkq9/zSJgDtNWLHg9ufiitkPSOiaeTTKIhZr+HjqKAYv"
|
||||||
|
"XTGN+5kgzxfxxVW+ijJZPAdo6I6jFKZp93iKLDaLNmcbEQLITa+kBbKwig9I4O+G/MgGGJVBjCPnNYw"
|
||||||
|
"EEfe5ZXoS2qQH+9FFUl4x68qC5mBOczlNPoRwJuzY9JfcPFOjoJuNjJPrmElfyPzwKuZlixaprGgbKB"
|
||||||
|
"5FZE6C6XgKMmBefuIHGXz/ngTKz0r5tbFAAA3gHtGpCRLuB0+/U4XfTVpMvz2MFWMrTNJJs3vbJTlJa"
|
||||||
|
"h3XGHJQEhKFSKzIALGOYOKstWsOko1rk6qdQ2WjrmtT6T9rIX3UQvutGJTNWJTC2NFBJJUAKBDz8VVI"
|
||||||
|
"rDlMj9HBJb2ckGtigPNYHQZTndkPTAoJCj5NMl4Nnel8XWGdlk+hUFm2vouaSSqldL8a6uJjcOz4WtP"
|
||||||
|
"OfRUmgW8G8QHzJAADzChVHfeYw8A+AfZGiv0V+MI1sD+N3vLH1805AgQ2YLgRTWul/7r9VLuKlfgWqm"
|
||||||
|
"EvpRwpWUcCc1/ALFFBQ/Zq/9eeT3Q1/1ucdJpxNKCfsZMJfB2uVsMDeBWMnSsIe4mk5iMO3Mn5OijaC"
|
||||||
|
"repAj2gIKzUsvRf/7v5A/vxS9x3pLA2ga+UohlLKqdYMbQfFiqvG0mosictERwC4U0LGelxAYlNIZHT"
|
||||||
|
"DRqKELKXTFSy7J+ElAEd7WsiNdSeMA5XQ+Xo1kz6eTTie0BCwgV4xjv3qZwdzMhmBk7zqgEz3FU+xSk"
|
||||||
|
"8gWP6VQ/RGrRChAd16A/s/PLOHfMQV95rPcISVPaAIlVVDgIiLCHP85UijhdLycQRIppAeXdwMvGyhm"
|
||||||
|
"KZmouKAXdOMw15KGP6SPX31ySqup4UU7sh0+VlHP8adgdUlrORpgHPvVJ8BoOwNGBE3Z03Czhz/QWXx"
|
||||||
|
"qFWKJj6nNzX7sJsQXr1hsnTYNo8SDlJJUzT40Mij8qzmAi1QOotjHUUpIohQFpNm3KyWLJLpSzun4aU"
|
||||||
|
"+P4MwMTRb14mYAOfSljH/hxU/HGI8kGUcy3uNo4phEAj+nmq8o5BAmAkEqCWThZxUGVTH7IAis+r+qF"
|
||||||
|
"qcBAjQfxBUhCJ8IooLBKoES8RZ7w5B/xyC0nhmoHpeiCtpBUhJi8mSUYTBL+cVtZuhEuRZBp5CRavYr"
|
||||||
|
"dE5Jju2oRZMynicZ6eCvp1PCJDpwoodNaiGawwCeZDvK0fUTWI2yf9dUdtwJO8ZzgSsi1NsboJLYpv0"
|
||||||
|
"nQvgPno22dyOqqBi1Efjr47D4BWsM0i8GmPG0pLIF7QO89svHsZ+zqZPO2BgRxA54G6SEQIYsG5Y6i3"
|
||||||
|
"XE/RtNKfwGAYBTD5Nr6KLNo0q+ZP//tN7wu3SE2o4amoc6+n2YPh2uGop+9W0BnqlBUbPDy+5Geeq+5"
|
||||||
|
"JLqcH5xSj3X+2PncCz137WpPbkGzwi6jjOEQZW6DvgJML0DHDyI0HOgSqCOjIO1WxFTf4Lr7AtRN90W"
|
||||||
|
"nMOZUVngnkaK4fqAc0iI0AKCdNo3+L0q2E3shpcjTzkAzOMbBTkqGM0YiOjTGfHwFtTi3jBnPaJfGVp"
|
||||||
|
"7N77Jd1rzzdDEwGCMNSzWGzNiduLUz8Ho6tgIVSRVIaDSHTeKup5SALBAsLE2GrgC9ccdlqAPZSB67E"
|
||||||
|
"XMWYt5ur3lcUMvhKlUXiD6F7bqF1HdaPs4brIhYonJaoQOEV5Sgi5gF6yMuHA6+5QDQPDJIh6tfwGs2"
|
||||||
|
"YGcPhqu3w6fPoo41AuhFJmOFziA0WjtrCXQJWLvwN0oRYQq5C+N9ChLt+8pC4C1ayE3t/b/P95sPfz0"
|
||||||
|
"T+BWgbjvPUR5KZLo42Ks0Gg57fFQ0iiDU4BOedh7+2PGB04k0lITtDUUGon4IxzZLqcsAAD2xyh+XeN"
|
||||||
|
"DLP8MuXYtAEVhnnqot7++Eas7wqOCimWUNnjLjEi7xkVCRFQw7ZQGQCxav8FeC28HYEuYpx66ibKaZF"
|
||||||
|
"z17B51rCGw0ohedKV0Ib+Bc/IOBw1LgUGNXa4sGjoY1+IAEGIkQWgihAjODs1eDJJZFzeF6vhIx0MZq"
|
||||||
|
"VE6YSGJeBvIGHRhssIOBen4cJFIDUaEBiht3QB4KfjXUlsEwlacHpKosVVzCnoDLwV7KMHauCECfCm8"
|
||||||
|
"SPkJc0YDlnGASjIAQXYwLhCph3a0bgDU0pwwdahIJBMdDRNFEkspaDBlqQFrHXoXdgFSUZhk8zrF6Mf"
|
||||||
|
"ZD1YDNnOIr+kKKFLxkYKLcnwOu5Gr2wBg0b+i1PFhBN0QgORbulLaQD1ziznM7qDraYbxIweNZHwcoS"
|
||||||
|
"MfUnbMRqEBrGIbi+kEKNz46GTnJRwOb5Nr0xdtAKh1/cJBUI2BH0V7u5Z8Dj70E8ycEVQx116HXUhyQ"
|
||||||
|
"7Zt/HiQQC4GBpJtdGQ1+49B81TxNkWsIc/WYT664wI0SvDhj2oV9kJTM725nRmUjXWapgpzC/uisxMT"
|
||||||
|
"PwbZ7OaH9Dgu5awo5jUSKSMZ8NMHHZBstwHREUmGHXoyMdll8+cHFNOZrLjTaC+FfEA6pp0QkoGfLFx"
|
||||||
|
"IwIdkDypiwgmgE1DYlAxvbsfGVWIdFnVWGHtr8JGDzlEklbSngqP0JbHeO3cGUEARr5OMh2QAeqAF/y"
|
||||||
|
"ulxj7ixyTN5omGhgs/lRhsQqMPB0iinQMHJYso5nOysGoC/HRB0Q6XvYUt7YBzpPDvp5G7gLZEDRAAZ"
|
||||||
|
"U0UwzrjRaFxyF6VsyjiCjTS6Ri2/05YGOko24EVlFFK96Bm6YYXt531I4B9gMcWVx4ayr63AA7hpxwd"
|
||||||
|
"8HIhRxlMeyRuNLx8w2E+IR1JKtauv4+sEDXvR7Eb6SD8X2CdBUAbAJzpOmqLjWupD4rDVFMa3GARJLC"
|
||||||
|
"fXAyS8JBCd2oopgwfJeiU0t6e/9Z33fjJBfQQ004g2YZJID0uG5O0kM814ACSimCF8mEySeEwEiuDAF"
|
||||||
|
"z46IwgwW4CJIBKajgQteYLrJPS/9ZcGN2MT+HlQ6wzBmopGS9dSKAUH4WIei5hVgQuE500jChNcRBJO"
|
||||||
|
"aEF6X76YKAIL1IvwUsxRths1jDJQpJur/UBQB3G5Kij/yBsO6eouTDZaMYAqHJ4x025zfAUFEe/Nz35"
|
||||||
|
"AAABiUlEQVTwUoHAjJppVk5vMpJ0dNwkhC0TGlCJj8OANyIeoDA4iEnnkJZe1sEGbtojqcCHHz8JGCT"
|
||||||
|
"jQqIH+13VYHIAiT8uX4cAi9s0QHxKBKqDccGIM4VIwkMSbhLwY+BGpxrwIzAwcKHZwgv9XQ1evAiq0C"
|
||||||
|
"hH2QEZFZMvafjojIGsg0cC6+yXIkyqo1LCnWgHcc5Fbn0AOA34zjEqeEM9x69C/lVYuwuh28surGNr6"
|
||||||
|
"pOfH6kffWQCabijMv1N/FQgKMVPTdQOX11jfgbrRLBWTgMdATia+pVSncyyMB8JmCQiSUQFtdOJXfMn"
|
||||||
|
"bRrAmcqD1vWpTQLoBexqykE0t3N0noCoLdpTlRQnsSFkS9AABlbCtqL1kKDVJ4TU0sWtzAISWAdptmk"
|
||||||
|
"Am9phNX9QTcwD1cg8K8HqBLYO+FEbAMIpF3gc+AGNv1G1GPgSqzYgkKeTBmTar2ygg22TGHZgqgBYb/"
|
||||||
|
"+mHGvzKrRS0R/yqsZq++6BRshpPMUDQcfzHFrIsqZHhWqasAtHc6b/D3cbSAuGcmWdAAAAAElFTkSuQmCC\" />";
|
||||||
|
|
||||||
|
const char *itoopieFavicon =
|
||||||
|
"data:image/png;base64,"
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv"
|
||||||
|
"8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4wOGVynO"
|
||||||
|
"EAAAIzSURBVDhPjZNdSFNhGMf3nm3n7OzMs+8JtfJGzdlgoPtoWBrkqc1OsLTMKEY3eZOQbbS6aBVYO"
|
||||||
|
"oO8CKSLXEulQtZNahAM9Cq6lS533UUaeDEEKcN/79x7kbQT/eDhfPB7/u/7Poej08JqtXoEQbhoMpmG"
|
||||||
|
"ZFn2stf/h8nEZ4aHue1SiWBlhSCV4n41NBifBINBjina8DyfzOUIVlcJtrYINjcJ3rw1oFAg4HnjHaZ"
|
||||||
|
"p4/Ppv8zPH0G5XKZNPZibO4lKpYJ8vgOqqv+uKMq/d9Hfz/0sFr3w+/3IZt2YnbWhszOAxUUv0mkCs9"
|
||||||
|
"ncyNT6hEL6dYBgY4Ngd5eger+zU7sODHA/mpubzUytj9FofLa0VGv4s9bWCCTJUGSaNvSzXT3stuHDM"
|
||||||
|
"rc3xEqF4N2CERciURyyHfgqSZKPqfuxUMyC+OKcL4YHyl28nDFAPdqDZMcQ7tPnSfURUt0jMBgMH1nL"
|
||||||
|
"fkRRDPvcLds3otfhbRTwasaE8b6He43VSrT3QW3tBT3iPdbyN3T7Ibsor988H8OxtiaMx2sB1aBbCRW"
|
||||||
|
"R1hbQhbqYXh+6QkaJn8DZyzF09x6HeiaOTC6NK9cSsFqkb3aH3cLU+tCAx9l8FoXPBUy9n8LgyCCmS9"
|
||||||
|
"MYez0Gm9P2iWna0GOcDp8KY2JhAsnbSQS6Ahh9OgrlklINeM40bWhAkBd4SLIEh8cBURLhOeiBIArVA"
|
||||||
|
"U4yTRvJItk5PRehQVFaYfpbt9PBtTmdziaXyyUzjaHT/QZBQuKHAA0UxAAAAABJRU5ErkJggg==";
|
||||||
|
|
||||||
|
const char *cssStyles =
|
||||||
|
"<style>\r\n"
|
||||||
|
" body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n"
|
||||||
|
" a { text-decoration: none; color: #894C84; }\r\n"
|
||||||
|
" a:hover { color: #FAFAFA; background: #894C84; }\r\n"
|
||||||
|
" .header { font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84; }\r\n"
|
||||||
|
" .wrapper { margin: 0 auto; padding: 1em; max-width: 60em; }\r\n"
|
||||||
|
" .left { float: left; position: absolute; }\r\n"
|
||||||
|
" .right { float: left; font-size: 1em; margin-left: 13em; max-width: 46em; overflow: auto; }\r\n"
|
||||||
|
" .tunnel.established { color: #56B734; }\r\n"
|
||||||
|
" .tunnel.expiring { color: #D3AE3F; }\r\n"
|
||||||
|
" .tunnel.failed { color: #D33F3F; }\r\n"
|
||||||
|
" .tunnel.another { color: #434343; }\r\n"
|
||||||
|
" caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n"
|
||||||
|
" table { width: 100%; border-collapse: collapse; text-align: center; }\r\n"
|
||||||
|
"</style>\r\n";
|
||||||
|
|
||||||
|
const char HTTP_PAGE_TUNNELS[] = "tunnels";
|
||||||
|
const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels";
|
||||||
|
const char HTTP_PAGE_TRANSPORTS[] = "transports";
|
||||||
|
const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations";
|
||||||
|
const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination";
|
||||||
|
const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions";
|
||||||
|
const char HTTP_PAGE_SAM_SESSION[] = "sam_session";
|
||||||
|
const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels";
|
||||||
|
const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices";
|
||||||
|
const char HTTP_PAGE_COMMANDS[] = "commands";
|
||||||
|
const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels";
|
||||||
|
const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels";
|
||||||
|
const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start";
|
||||||
|
const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel";
|
||||||
|
const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate";
|
||||||
|
const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test";
|
||||||
|
const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config";
|
||||||
|
const char HTTP_PARAM_BASE32_ADDRESS[] = "b32";
|
||||||
|
const char HTTP_PARAM_SAM_SESSION_ID[] = "id";
|
||||||
|
const char HTTP_PARAM_ADDRESS[] = "address";
|
||||||
|
|
||||||
|
std::map<std::string, std::string> jumpservices = {
|
||||||
|
{ "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" },
|
||||||
|
{ "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" },
|
||||||
|
};
|
||||||
|
|
||||||
|
void ShowUptime (std::stringstream& s, int seconds) {
|
||||||
|
int num;
|
||||||
|
|
||||||
|
if ((num = seconds / 86400) > 0) {
|
||||||
|
s << num << " days, ";
|
||||||
|
seconds -= num;
|
||||||
|
}
|
||||||
|
if ((num = seconds / 3600) > 0) {
|
||||||
|
s << num << " hours, ";
|
||||||
|
seconds -= num;
|
||||||
|
}
|
||||||
|
if ((num = seconds / 60) > 0) {
|
||||||
|
s << num << " min, ";
|
||||||
|
seconds -= num;
|
||||||
|
}
|
||||||
|
s << seconds << " seconds";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, int bytes)
|
||||||
|
{
|
||||||
|
std::string state;
|
||||||
|
switch (eState) {
|
||||||
|
case i2p::tunnel::eTunnelStateBuildReplyReceived :
|
||||||
|
case i2p::tunnel::eTunnelStatePending : state = "building"; break;
|
||||||
|
case i2p::tunnel::eTunnelStateBuildFailed :
|
||||||
|
case i2p::tunnel::eTunnelStateTestFailed :
|
||||||
|
case i2p::tunnel::eTunnelStateFailed : state = "failed"; break;
|
||||||
|
case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break;
|
||||||
|
case i2p::tunnel::eTunnelStateEstablished : state = "established"; break;
|
||||||
|
default: state = "unknown"; break;
|
||||||
|
}
|
||||||
|
s << "<span class=\"tunnel " << state << "\"> " << state << "</span>, ";
|
||||||
|
s << " " << (int) (bytes / 1024) << " KiB<br>\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowPageHead (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s <<
|
||||||
|
"<!DOCTYPE html>\r\n"
|
||||||
|
"<html lang=\"en\">\r\n" /* TODO: Add support for locale */
|
||||||
|
" <head>\r\n"
|
||||||
|
" <meta charset=\"UTF-8\">\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
|
||||||
|
" <link rel='shortcut icon' href='" << itoopieFavicon << "'>\r\n"
|
||||||
|
" <title>Purple I2P " VERSION " Webconsole</title>\r\n"
|
||||||
|
<< cssStyles <<
|
||||||
|
"</head>\r\n";
|
||||||
|
s <<
|
||||||
|
"<body>\r\n"
|
||||||
|
"<div class=header><b>i2pd</b> webconsole</div>\r\n"
|
||||||
|
"<div class=wrapper>\r\n"
|
||||||
|
"<div class=left>\r\n"
|
||||||
|
" <a href=/>Main page</a><br>\r\n<br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_COMMANDS << ">Router commands</a><br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << ">Local destinations</a><br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_TUNNELS << ">Tunnels</a><br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_TRANSIT_TUNNELS << ">Transit tunnels</a><br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_TRANSPORTS << ">Transports</a><br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_I2P_TUNNELS << ">I2P tunnels</a><br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_JUMPSERVICES << ">Jump services</a><br>\r\n"
|
||||||
|
" <a href=/?page=" << HTTP_PAGE_SAM_SESSIONS << ">SAM sessions</a><br>\r\n"
|
||||||
|
"</div>\r\n"
|
||||||
|
"<div class=right>";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowPageTail (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s <<
|
||||||
|
"</div></div>\r\n"
|
||||||
|
"</body>\r\n"
|
||||||
|
"</html>\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowError(std::stringstream& s, const std::string& string)
|
||||||
|
{
|
||||||
|
s << "<b>ERROR:</b> " << string << "<br>\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowStatus (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s << "<b>Uptime:</b> ";
|
||||||
|
ShowUptime(s, i2p::context.GetUptime ());
|
||||||
|
s << "<br>\r\n";
|
||||||
|
s << "<b>Status:</b> ";
|
||||||
|
switch (i2p::context.GetStatus ())
|
||||||
|
{
|
||||||
|
case eRouterStatusOK: s << "OK"; break;
|
||||||
|
case eRouterStatusTesting: s << "Testing"; break;
|
||||||
|
case eRouterStatusFirewalled: s << "Firewalled"; break;
|
||||||
|
default: s << "Unknown";
|
||||||
|
}
|
||||||
|
s << "<br>\r\n";
|
||||||
|
auto family = i2p::context.GetFamily ();
|
||||||
|
if (family.length () > 0)
|
||||||
|
s << "<b>Family:</b> " << family << "<br>\r\n";
|
||||||
|
s << "<b>Tunnel creation success rate:</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n";
|
||||||
|
s << "<b>Received:</b> ";
|
||||||
|
s << std::fixed << std::setprecision(2);
|
||||||
|
auto numKBytesReceived = (double) i2p::transport::transports.GetTotalReceivedBytes () / 1024;
|
||||||
|
if (numKBytesReceived < 1024)
|
||||||
|
s << numKBytesReceived << " KiB";
|
||||||
|
else if (numKBytesReceived < 1024 * 1024)
|
||||||
|
s << numKBytesReceived / 1024 << " MiB";
|
||||||
|
else
|
||||||
|
s << numKBytesReceived / 1024 / 1024 << " GiB";
|
||||||
|
s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)<br>\r\n";
|
||||||
|
s << "<b>Sent:</b> ";
|
||||||
|
auto numKBytesSent = (double) i2p::transport::transports.GetTotalSentBytes () / 1024;
|
||||||
|
if (numKBytesSent < 1024)
|
||||||
|
s << numKBytesSent << " KiB";
|
||||||
|
else if (numKBytesSent < 1024 * 1024)
|
||||||
|
s << numKBytesSent / 1024 << " MiB";
|
||||||
|
else
|
||||||
|
s << numKBytesSent / 1024 / 1024 << " GiB";
|
||||||
|
s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)<br>\r\n";
|
||||||
|
s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n<br>\r\n";
|
||||||
|
s << "<b>Our external address:</b>" << "<br>\r\n" ;
|
||||||
|
for (auto address : i2p::context.GetRouterInfo().GetAddresses())
|
||||||
|
{
|
||||||
|
switch (address->transportStyle)
|
||||||
|
{
|
||||||
|
case i2p::data::RouterInfo::eTransportNTCP:
|
||||||
|
if (address->host.is_v6 ())
|
||||||
|
s << "NTCP6 ";
|
||||||
|
else
|
||||||
|
s << "NTCP ";
|
||||||
|
break;
|
||||||
|
case i2p::data::RouterInfo::eTransportSSU:
|
||||||
|
if (address->host.is_v6 ())
|
||||||
|
s << "SSU6 ";
|
||||||
|
else
|
||||||
|
s << "SSU ";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
s << "Unknown ";
|
||||||
|
}
|
||||||
|
s << address->host.to_string() << ":" << address->port << "<br>\r\n";
|
||||||
|
}
|
||||||
|
s << "<br>\r\n<b>Routers:</b> " << i2p::data::netdb.GetNumRouters () << " ";
|
||||||
|
s << "<b>Floodfills:</b> " << i2p::data::netdb.GetNumFloodfills () << " ";
|
||||||
|
s << "<b>LeaseSets:</b> " << i2p::data::netdb.GetNumLeaseSets () << "<br>\r\n";
|
||||||
|
|
||||||
|
size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels();
|
||||||
|
clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels();
|
||||||
|
size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels();
|
||||||
|
|
||||||
|
s << "<b>Client Tunnels:</b> " << std::to_string(clientTunnelCount) << " ";
|
||||||
|
s << "<b>Transit Tunnels:</b> " << std::to_string(transitTunnelCount) << "<br>\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowJumpServices (std::stringstream& s, const std::string& address)
|
||||||
|
{
|
||||||
|
s << "<form type=\"GET\" action=\"/\">";
|
||||||
|
s << "<input type=\"hidden\" name=\"page\" value=\"jumpservices\">";
|
||||||
|
s << "<input type=\"text\" name=\"address\" value=\"" << address << "\">";
|
||||||
|
s << "<input type=\"submit\" value=\"Update\">";
|
||||||
|
s << "</form><br>\r\n";
|
||||||
|
s << "<b>Jump services for " << address << "</b>\r\n<ul>\r\n";
|
||||||
|
for (auto & js : jumpservices) {
|
||||||
|
s << " <li><a href=\"" << js.second << address << "\">" << js.first << "</a></li>\r\n";
|
||||||
|
}
|
||||||
|
s << "</ul>\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowLocalDestinations (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s << "<b>Local Destinations:</b><br>\r\n<br>\r\n";
|
||||||
|
for (auto& it: i2p::client::context.GetDestinations ())
|
||||||
|
{
|
||||||
|
auto ident = it.second->GetIdentHash ();;
|
||||||
|
s << "<a href=/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << ">";
|
||||||
|
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>\r\n" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowLocalDestination (std::stringstream& s, const std::string& b32)
|
||||||
|
{
|
||||||
|
s << "<b>Local Destination:</b><br>\r\n<br>\r\n";
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
ident.FromBase32 (b32);
|
||||||
|
auto dest = i2p::client::context.FindLocalDestination (ident);
|
||||||
|
if (dest)
|
||||||
|
{
|
||||||
|
s << "<b>Base64:</b><br>\r\n<textarea readonly=\"readonly\" cols=\"64\" rows=\"11\" wrap=\"on\">";
|
||||||
|
s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n";
|
||||||
|
s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>\r\n";
|
||||||
|
auto pool = dest->GetTunnelPool ();
|
||||||
|
if (pool)
|
||||||
|
{
|
||||||
|
s << "<b>Inbound tunnels:</b><br>\r\n";
|
||||||
|
for (auto & it : pool->GetInboundTunnels ()) {
|
||||||
|
it->Print(s);
|
||||||
|
ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ());
|
||||||
|
}
|
||||||
|
s << "<br>\r\n";
|
||||||
|
s << "<b>Outbound tunnels:</b><br>\r\n";
|
||||||
|
for (auto & it : pool->GetOutboundTunnels ()) {
|
||||||
|
it->Print(s);
|
||||||
|
ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s << "<br>\r\n";
|
||||||
|
s << "<b>Tags</b><br>Incoming: " << dest->GetNumIncomingTags () << "<br>Outgoing:<br>" << std::endl;
|
||||||
|
for (auto it: dest->GetSessions ())
|
||||||
|
{
|
||||||
|
s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " ";
|
||||||
|
s << it.second->GetNumOutgoingTags () << "<br>" << std::endl;
|
||||||
|
}
|
||||||
|
s << "<br>" << std::endl;
|
||||||
|
// s << "<br>\r\n<b>Streams:</b><br>\r\n";
|
||||||
|
// for (auto it: dest->GetStreamingDestination ()->GetStreams ())
|
||||||
|
// {
|
||||||
|
// s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " ";
|
||||||
|
// s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
|
||||||
|
// s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]";
|
||||||
|
// s << "[buf:" << it.second->GetSendBufferSize () << "]";
|
||||||
|
// s << "[RTT:" << it.second->GetRTT () << "]";
|
||||||
|
// s << "[Window:" << it.second->GetWindowSize () << "]";
|
||||||
|
// s << "[Status:" << (int)it.second->GetStatus () << "]";
|
||||||
|
// s << "<br>\r\n"<< std::endl;
|
||||||
|
// }
|
||||||
|
s << "<br>\r\n<table><caption>Streams</caption><tr>";
|
||||||
|
s << "<th>StreamID</th>";
|
||||||
|
s << "<th>Destination</th>";
|
||||||
|
s << "<th>Sent</th>";
|
||||||
|
s << "<th>Received</th>";
|
||||||
|
s << "<th>Out</th>";
|
||||||
|
s << "<th>In</th>";
|
||||||
|
s << "<th>Buf</th>";
|
||||||
|
s << "<th>RTT</th>";
|
||||||
|
s << "<th>Window</th>";
|
||||||
|
s << "<th>Status</th>";
|
||||||
|
s << "</tr>";
|
||||||
|
|
||||||
|
for (auto it: dest->GetAllStreams ())
|
||||||
|
{
|
||||||
|
s << "<tr>";
|
||||||
|
s << "<td>" << it->GetSendStreamID () << "</td>";
|
||||||
|
s << "<td>" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "</td>";
|
||||||
|
s << "<td>" << it->GetNumSentBytes () << "</td>";
|
||||||
|
s << "<td>" << it->GetNumReceivedBytes () << "</td>";
|
||||||
|
s << "<td>" << it->GetSendQueueSize () << "</td>";
|
||||||
|
s << "<td>" << it->GetReceiveQueueSize () << "</td>";
|
||||||
|
s << "<td>" << it->GetSendBufferSize () << "</td>";
|
||||||
|
s << "<td>" << it->GetRTT () << "</td>";
|
||||||
|
s << "<td>" << it->GetWindowSize () << "</td>";
|
||||||
|
s << "<td>" << (int)it->GetStatus () << "</td>";
|
||||||
|
s << "</tr><br>\r\n" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowTunnels (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s << "<b>Queue size:</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n";
|
||||||
|
|
||||||
|
s << "<b>Inbound tunnels:</b><br>\r\n";
|
||||||
|
for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) {
|
||||||
|
it->Print(s);
|
||||||
|
ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ());
|
||||||
|
}
|
||||||
|
s << "<br>\r\n";
|
||||||
|
s << "<b>Outbound tunnels:</b><br>\r\n";
|
||||||
|
for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) {
|
||||||
|
it->Print(s);
|
||||||
|
ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ());
|
||||||
|
}
|
||||||
|
s << "<br>\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowCommands (std::stringstream& s)
|
||||||
|
{
|
||||||
|
/* commands */
|
||||||
|
s << "<b>Router Commands</b><br>\r\n";
|
||||||
|
s << " <a href=/?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << ">Run peer test</a><br>\r\n";
|
||||||
|
//s << " <a href=/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << ">Reload config</a><br>\r\n";
|
||||||
|
if (i2p::context.AcceptsTunnels ())
|
||||||
|
s << " <a href=/?cmd=" << HTTP_COMMAND_STOP_ACCEPTING_TUNNELS << ">Stop accepting tunnels</a><br>\r\n";
|
||||||
|
else
|
||||||
|
s << " <a href=/?cmd=" << HTTP_COMMAND_START_ACCEPTING_TUNNELS << ">Start accepting tunnels</a><br>\r\n";
|
||||||
|
#ifndef WIN32
|
||||||
|
if (Daemon.gracefullShutdownInterval) {
|
||||||
|
s << " <a href=/?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << ">Cancel gracefull shutdown (";
|
||||||
|
s << Daemon.gracefullShutdownInterval;
|
||||||
|
s << " seconds remains)</a><br>\r\n";
|
||||||
|
} else {
|
||||||
|
s << " <a href=/?cmd=" << HTTP_COMMAND_SHUTDOWN_START << ">Start gracefull shutdown</a><br>\r\n";
|
||||||
|
}
|
||||||
|
s << " <a href=/?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << ">Force shutdown</a><br>\r\n";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowTransitTunnels (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s << "<b>Transit tunnels:</b><br>\r\n<br>\r\n";
|
||||||
|
for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ())
|
||||||
|
{
|
||||||
|
if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
|
||||||
|
s << it->GetTunnelID () << " ⇒ ";
|
||||||
|
else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it))
|
||||||
|
s << " ⇒ " << it->GetTunnelID ();
|
||||||
|
else
|
||||||
|
s << " ⇒ " << it->GetTunnelID () << " ⇒ ";
|
||||||
|
s << " " << it->GetNumTransmittedBytes () << "<br>\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowTransports (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s << "<b>Transports:</b><br>\r\n<br>\r\n";
|
||||||
|
auto ntcpServer = i2p::transport::transports.GetNTCPServer ();
|
||||||
|
if (ntcpServer)
|
||||||
|
{
|
||||||
|
s << "<b>NTCP</b><br>\r\n";
|
||||||
|
for (auto it: ntcpServer->GetNTCPSessions ())
|
||||||
|
{
|
||||||
|
if (it.second && it.second->IsEstablished ())
|
||||||
|
{
|
||||||
|
// incoming connection doesn't have remote RI
|
||||||
|
if (it.second->IsOutgoing ()) s << " ⇒ ";
|
||||||
|
s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": "
|
||||||
|
<< it.second->GetSocket ().remote_endpoint().address ().to_string ();
|
||||||
|
if (!it.second->IsOutgoing ()) s << " ⇒ ";
|
||||||
|
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
|
||||||
|
s << "<br>\r\n" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto ssuServer = i2p::transport::transports.GetSSUServer ();
|
||||||
|
if (ssuServer)
|
||||||
|
{
|
||||||
|
s << "<br>\r\n<b>SSU</b><br>\r\n";
|
||||||
|
for (auto it: ssuServer->GetSessions ())
|
||||||
|
{
|
||||||
|
auto endpoint = it.second->GetRemoteEndpoint ();
|
||||||
|
if (it.second->IsOutgoing ()) s << " ⇒ ";
|
||||||
|
s << endpoint.address ().to_string () << ":" << endpoint.port ();
|
||||||
|
if (!it.second->IsOutgoing ()) s << " ⇒ ";
|
||||||
|
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
|
||||||
|
if (it.second->GetRelayTag ())
|
||||||
|
s << " [itag:" << it.second->GetRelayTag () << "]";
|
||||||
|
s << "<br>\r\n" << std::endl;
|
||||||
|
}
|
||||||
|
s << "<br>\r\n<b>SSU6</b><br>\r\n";
|
||||||
|
for (auto it: ssuServer->GetSessionsV6 ())
|
||||||
|
{
|
||||||
|
auto endpoint = it.second->GetRemoteEndpoint ();
|
||||||
|
if (it.second->IsOutgoing ()) s << " ⇒ ";
|
||||||
|
s << endpoint.address ().to_string () << ":" << endpoint.port ();
|
||||||
|
if (!it.second->IsOutgoing ()) s << " ⇒ ";
|
||||||
|
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
|
||||||
|
s << "<br>\r\n" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowSAMSessions (std::stringstream& s)
|
||||||
|
{
|
||||||
|
auto sam = i2p::client::context.GetSAMBridge ();
|
||||||
|
if (!sam) {
|
||||||
|
ShowError(s, "SAM disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s << "<b>SAM Sessions:</b><br>\r\n<br>\r\n";
|
||||||
|
for (auto& it: sam->GetSessions ())
|
||||||
|
{
|
||||||
|
s << "<a href=/?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << it.first << ">";
|
||||||
|
s << it.first << "</a><br>\r\n" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowSAMSession (std::stringstream& s, const std::string& id)
|
||||||
|
{
|
||||||
|
s << "<b>SAM Session:</b><br>\r\n<br>\r\n";
|
||||||
|
auto sam = i2p::client::context.GetSAMBridge ();
|
||||||
|
if (!sam) {
|
||||||
|
ShowError(s, "SAM disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto session = sam->FindSession (id);
|
||||||
|
if (!session) {
|
||||||
|
ShowError(s, "SAM session not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& ident = session->localDestination->GetIdentHash();
|
||||||
|
s << "<a href=/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << ">";
|
||||||
|
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>\r\n";
|
||||||
|
s << "<br>\r\n";
|
||||||
|
s << "<b>Streams:</b><br>\r\n";
|
||||||
|
for (auto it: session->ListSockets())
|
||||||
|
{
|
||||||
|
switch (it->GetSocketType ())
|
||||||
|
{
|
||||||
|
case i2p::client::eSAMSocketTypeSession : s << "session"; break;
|
||||||
|
case i2p::client::eSAMSocketTypeStream : s << "stream"; break;
|
||||||
|
case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break;
|
||||||
|
default: s << "unknown"; break;
|
||||||
|
}
|
||||||
|
s << " [" << it->GetSocket ().remote_endpoint() << "]";
|
||||||
|
s << "<br>\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowI2PTunnels (std::stringstream& s)
|
||||||
|
{
|
||||||
|
s << "<b>Client Tunnels:</b><br>\r\n<br>\r\n";
|
||||||
|
for (auto& it: i2p::client::context.GetClientTunnels ())
|
||||||
|
{
|
||||||
|
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
|
||||||
|
s << "<a href=/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << ">";
|
||||||
|
s << it.second->GetName () << "</a> ⇐ ";
|
||||||
|
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
|
||||||
|
s << "<br>\r\n"<< std::endl;
|
||||||
|
}
|
||||||
|
s << "<br>\r\n<b>Server Tunnels:</b><br>\r\n<br>\r\n";
|
||||||
|
for (auto& it: i2p::client::context.GetServerTunnels ())
|
||||||
|
{
|
||||||
|
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
|
||||||
|
s << "<a href=/?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << ">";
|
||||||
|
s << it.second->GetName () << "</a> ⇒ ";
|
||||||
|
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
|
||||||
|
s << ":" << it.second->GetLocalPort ();
|
||||||
|
s << "</a><br>\r\n"<< std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPConnection::HTTPConnection (std::shared_ptr<boost::asio::ip::tcp::socket> socket):
|
||||||
|
m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0)
|
||||||
|
{
|
||||||
|
/* cache options */
|
||||||
|
i2p::config::GetOption("http.auth", needAuth);
|
||||||
|
i2p::config::GetOption("http.user", user);
|
||||||
|
i2p::config::GetOption("http.pass", pass);
|
||||||
|
};
|
||||||
|
|
||||||
|
void HTTPConnection::Receive ()
|
||||||
|
{
|
||||||
|
m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE),
|
||||||
|
std::bind(&HTTPConnection::HandleReceive, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate (ecode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_Buffer[bytes_transferred] = '\0';
|
||||||
|
m_BufferLen = bytes_transferred;
|
||||||
|
RunRequest();
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::RunRequest ()
|
||||||
|
{
|
||||||
|
HTTPReq request;
|
||||||
|
int ret = request.parse(m_Buffer);
|
||||||
|
if (ret < 0) {
|
||||||
|
m_Buffer[0] = '\0';
|
||||||
|
m_BufferLen = 0;
|
||||||
|
return; /* error */
|
||||||
|
}
|
||||||
|
if (ret == 0)
|
||||||
|
return; /* need more data */
|
||||||
|
|
||||||
|
HandleRequest (request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::Terminate (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode == boost::asio::error::operation_aborted)
|
||||||
|
return;
|
||||||
|
boost::system::error_code ignored_ec;
|
||||||
|
m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
||||||
|
m_Socket->close ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPConnection::CheckAuth (const HTTPReq & req) {
|
||||||
|
/* method #1: http://user:pass@127.0.0.1:7070/ */
|
||||||
|
if (req.uri.find('@') != std::string::npos) {
|
||||||
|
URL url;
|
||||||
|
if (url.parse(req.uri) && url.user == user && url.pass == pass)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/* method #2: 'Authorization' header sent */
|
||||||
|
if (req.headers.count("Authorization") > 0) {
|
||||||
|
std::string provided = req.headers.find("Authorization")->second;
|
||||||
|
std::string expected = user + ":" + pass;
|
||||||
|
char b64_creds[64];
|
||||||
|
std::size_t len = 0;
|
||||||
|
len = i2p::data::ByteStreamToBase64((unsigned char *)expected.c_str(), expected.length(), b64_creds, sizeof(b64_creds));
|
||||||
|
b64_creds[len] = '\0';
|
||||||
|
expected = "Basic ";
|
||||||
|
expected += b64_creds;
|
||||||
|
if (provided == expected)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::HandleRequest (const HTTPReq & req)
|
||||||
|
{
|
||||||
|
std::stringstream s;
|
||||||
|
std::string content;
|
||||||
|
HTTPRes res;
|
||||||
|
|
||||||
|
LogPrint(eLogDebug, "HTTPServer: request: ", req.uri);
|
||||||
|
|
||||||
|
if (needAuth && !CheckAuth(req)) {
|
||||||
|
res.code = 401;
|
||||||
|
res.headers.insert(std::pair<std::string, std::string>("WWW-Authenticate", "Basic realm=\"WebAdmin\""));
|
||||||
|
SendReply(res, content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Html5 head start
|
||||||
|
ShowPageHead (s);
|
||||||
|
if (req.uri.find("page=") != std::string::npos)
|
||||||
|
HandlePage (req, res, s);
|
||||||
|
else if (req.uri.find("cmd=") != std::string::npos)
|
||||||
|
HandleCommand (req, res, s);
|
||||||
|
else
|
||||||
|
ShowStatus (s);
|
||||||
|
ShowPageTail (s);
|
||||||
|
content = s.str ();
|
||||||
|
SendReply (res, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
std::string page("");
|
||||||
|
URL url;
|
||||||
|
|
||||||
|
url.parse(req.uri);
|
||||||
|
url.parse_query(params);
|
||||||
|
page = params["page"];
|
||||||
|
|
||||||
|
if (page == HTTP_PAGE_TRANSPORTS)
|
||||||
|
ShowTransports (s);
|
||||||
|
else if (page == HTTP_PAGE_TUNNELS)
|
||||||
|
ShowTunnels (s);
|
||||||
|
else if (page == HTTP_PAGE_COMMANDS)
|
||||||
|
ShowCommands (s);
|
||||||
|
else if (page == HTTP_PAGE_JUMPSERVICES)
|
||||||
|
ShowJumpServices (s, params["address"]);
|
||||||
|
else if (page == HTTP_PAGE_TRANSIT_TUNNELS)
|
||||||
|
ShowTransitTunnels (s);
|
||||||
|
else if (page == HTTP_PAGE_LOCAL_DESTINATIONS)
|
||||||
|
ShowLocalDestinations (s);
|
||||||
|
else if (page == HTTP_PAGE_LOCAL_DESTINATION)
|
||||||
|
ShowLocalDestination (s, params["b32"]);
|
||||||
|
else if (page == HTTP_PAGE_SAM_SESSIONS)
|
||||||
|
ShowSAMSessions (s);
|
||||||
|
else if (page == HTTP_PAGE_SAM_SESSION)
|
||||||
|
ShowSAMSession (s, params["sam_id"]);
|
||||||
|
else if (page == HTTP_PAGE_I2P_TUNNELS)
|
||||||
|
ShowI2PTunnels (s);
|
||||||
|
else {
|
||||||
|
res.code = 400;
|
||||||
|
ShowError(s, "Unknown page: " + page);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
std::string cmd("");
|
||||||
|
URL url;
|
||||||
|
|
||||||
|
url.parse(req.uri);
|
||||||
|
url.parse_query(params);
|
||||||
|
cmd = params["cmd"];
|
||||||
|
|
||||||
|
if (cmd == HTTP_COMMAND_RUN_PEER_TEST)
|
||||||
|
i2p::transport::transports.PeerTest ();
|
||||||
|
else if (cmd == HTTP_COMMAND_RELOAD_CONFIG)
|
||||||
|
i2p::client::context.ReloadConfig ();
|
||||||
|
else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS)
|
||||||
|
i2p::context.SetAcceptsTunnels (true);
|
||||||
|
else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS)
|
||||||
|
i2p::context.SetAcceptsTunnels (false);
|
||||||
|
else if (cmd == HTTP_COMMAND_SHUTDOWN_START) {
|
||||||
|
i2p::context.SetAcceptsTunnels (false);
|
||||||
|
#ifndef WIN32
|
||||||
|
Daemon.gracefullShutdownInterval = 10*60;
|
||||||
|
#endif
|
||||||
|
} else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) {
|
||||||
|
i2p::context.SetAcceptsTunnels (true);
|
||||||
|
#ifndef WIN32
|
||||||
|
Daemon.gracefullShutdownInterval = 0;
|
||||||
|
#endif
|
||||||
|
} else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) {
|
||||||
|
Daemon.running = false;
|
||||||
|
} else {
|
||||||
|
res.code = 400;
|
||||||
|
ShowError(s, "Unknown command: " + cmd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s << "<b>SUCCESS</b>: Command accepted<br><br>\r\n";
|
||||||
|
s << "<a href=\"/?page=commands\">Back to commands list</a>";
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::SendReply (HTTPRes& reply, std::string& content)
|
||||||
|
{
|
||||||
|
std::time_t time_now = std::time(nullptr);
|
||||||
|
char time_buff[128];
|
||||||
|
std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now));
|
||||||
|
reply.status = HTTPCodeToStatus(reply.code);
|
||||||
|
reply.headers.insert(std::pair<std::string, std::string>("Date", time_buff));
|
||||||
|
reply.headers.insert(std::pair<std::string, std::string>("Content-Type", "text/html"));
|
||||||
|
reply.headers.insert(std::pair<std::string, std::string>("Content-Length", std::to_string(content.size())));
|
||||||
|
|
||||||
|
std::string res = reply.to_string();
|
||||||
|
std::vector<boost::asio::const_buffer> buffers;
|
||||||
|
|
||||||
|
buffers.push_back(boost::asio::buffer(res));
|
||||||
|
buffers.push_back(boost::asio::buffer(content));
|
||||||
|
|
||||||
|
boost::asio::async_write (*m_Socket, buffers,
|
||||||
|
std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPServer::HTTPServer (const std::string& address, int port):
|
||||||
|
m_Thread (nullptr), m_Work (m_Service),
|
||||||
|
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPServer::~HTTPServer ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::Start ()
|
||||||
|
{
|
||||||
|
bool needAuth; i2p::config::GetOption("http.auth", needAuth);
|
||||||
|
std::string user; i2p::config::GetOption("http.user", user);
|
||||||
|
std::string pass; i2p::config::GetOption("http.pass", pass);
|
||||||
|
/* generate pass if needed */
|
||||||
|
if (needAuth && pass == "") {
|
||||||
|
char alnum[] = "0123456789"
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz";
|
||||||
|
pass.resize(16);
|
||||||
|
for (size_t i = 0; i < pass.size(); i++) {
|
||||||
|
pass[i] = alnum[rand() % (sizeof(alnum) - 1)];
|
||||||
|
}
|
||||||
|
i2p::config::SetOption("http.pass", pass);
|
||||||
|
LogPrint(eLogInfo, "HTTPServer: password set to ", pass);
|
||||||
|
}
|
||||||
|
m_Thread = std::unique_ptr<std::thread>(new std::thread (std::bind (&HTTPServer::Run, this)));
|
||||||
|
m_Acceptor.listen ();
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::Stop ()
|
||||||
|
{
|
||||||
|
m_Acceptor.close();
|
||||||
|
m_Service.stop ();
|
||||||
|
if (m_Thread) {
|
||||||
|
m_Thread->join ();
|
||||||
|
m_Thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::Run ()
|
||||||
|
{
|
||||||
|
m_Service.run ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::Accept ()
|
||||||
|
{
|
||||||
|
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (m_Service);
|
||||||
|
m_Acceptor.async_accept (*newSocket, boost::bind (&HTTPServer::HandleAccept, this,
|
||||||
|
boost::asio::placeholders::error, newSocket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::HandleAccept(const boost::system::error_code& ecode,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
return;
|
||||||
|
CreateConnection(newSocket);
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket)
|
||||||
|
{
|
||||||
|
auto conn = std::make_shared<HTTPConnection> (newSocket);
|
||||||
|
conn->Receive ();
|
||||||
|
}
|
||||||
|
} // http
|
||||||
|
} // i2p
|
||||||
68
HTTPServer.h
Normal file
68
HTTPServer.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef HTTP_SERVER_H__
|
||||||
|
#define HTTP_SERVER_H__
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace http {
|
||||||
|
extern const char *itoopieImage;
|
||||||
|
extern const char *itoopieFavicon;
|
||||||
|
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
HTTPConnection (std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||||
|
void Receive ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void Terminate (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
void RunRequest ();
|
||||||
|
bool CheckAuth (const HTTPReq & req);
|
||||||
|
void HandleRequest (const HTTPReq & req);
|
||||||
|
void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data);
|
||||||
|
void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data);
|
||||||
|
void SendReply (HTTPRes & res, std::string & content);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
|
||||||
|
boost::asio::deadline_timer m_Timer;
|
||||||
|
char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1];
|
||||||
|
size_t m_BufferLen;
|
||||||
|
bool needAuth;
|
||||||
|
std::string user;
|
||||||
|
std::string pass;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HTTPServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
HTTPServer (const std::string& address, int port);
|
||||||
|
~HTTPServer ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void Accept ();
|
||||||
|
void HandleAccept(const boost::system::error_code& ecode,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> newSocket);
|
||||||
|
void CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::unique_ptr<std::thread> m_Thread;
|
||||||
|
boost::asio::io_service m_Service;
|
||||||
|
boost::asio::io_service::work m_Work;
|
||||||
|
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||||
|
};
|
||||||
|
} // http
|
||||||
|
} // i2p
|
||||||
|
|
||||||
|
#endif /* HTTP_SERVER_H__ */
|
||||||
112
I2CP.cpp
Normal file
112
I2CP.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "I2CP.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
|
||||||
|
m_Owner (owner), m_Socket (socket),
|
||||||
|
m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0)
|
||||||
|
{
|
||||||
|
ReadProtocolByte ();
|
||||||
|
}
|
||||||
|
|
||||||
|
I2CPSession::~I2CPSession ()
|
||||||
|
{
|
||||||
|
delete[] m_NextMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2CPSession::ReadProtocolByte ()
|
||||||
|
{
|
||||||
|
if (m_Socket)
|
||||||
|
{
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
m_Socket->async_read_some (boost::asio::buffer (m_Buffer, 1),
|
||||||
|
[s](const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (!ecode && bytes_transferred > 0 && s->m_Buffer[0] == I2CP_PRTOCOL_BYTE)
|
||||||
|
s->Receive ();
|
||||||
|
else
|
||||||
|
s->Terminate ();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2CPSession::Receive ()
|
||||||
|
{
|
||||||
|
m_Socket->async_read_some (boost::asio::buffer (m_Buffer, I2CP_SESSION_BUFFER_SIZE),
|
||||||
|
std::bind (&I2CPSession::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2CPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
Terminate ();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t offset = 0;
|
||||||
|
if (m_NextMessage)
|
||||||
|
{
|
||||||
|
if (m_NextMessageOffset + bytes_transferred <= m_NextMessageLen)
|
||||||
|
{
|
||||||
|
memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred);
|
||||||
|
m_NextMessageOffset += bytes_transferred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
offset = m_NextMessageLen - m_NextMessageOffset;
|
||||||
|
memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset);
|
||||||
|
HandleNextMessage (m_NextMessage);
|
||||||
|
delete[] m_NextMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (offset < bytes_transferred)
|
||||||
|
{
|
||||||
|
auto msgLen = bufbe32toh (m_Buffer + offset + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE;
|
||||||
|
if (msgLen <= bytes_transferred - offset)
|
||||||
|
{
|
||||||
|
HandleNextMessage (m_Buffer + offset);
|
||||||
|
offset += msgLen;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_NextMessageLen = msgLen;
|
||||||
|
m_NextMessageOffset = bytes_transferred - offset;
|
||||||
|
m_NextMessage = new uint8_t[m_NextMessageLen];
|
||||||
|
memcpy (m_NextMessage, m_Buffer + offset, m_NextMessageOffset);
|
||||||
|
offset = bytes_transferred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2CPSession::HandleNextMessage (const uint8_t * buf)
|
||||||
|
{
|
||||||
|
auto handler = m_Owner.GetMessagesHandlers ()[buf[I2CP_HEADER_TYPE_OFFSET]];
|
||||||
|
if (handler)
|
||||||
|
(this->*handler)(buf + I2CP_HEADER_SIZE, bufbe32toh (buf + I2CP_HEADER_LENGTH_OFFSET));
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)buf[I2CP_HEADER_TYPE_OFFSET]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2CPSession::Terminate ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
I2CPServer::I2CPServer (const std::string& interface, int port)
|
||||||
|
{
|
||||||
|
memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers));
|
||||||
|
m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
68
I2CP.h
Normal file
68
I2CP.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef I2CP_H__
|
||||||
|
#define I2CP_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const uint8_t I2CP_PRTOCOL_BYTE = 0x2A;
|
||||||
|
const size_t I2CP_SESSION_BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
|
const size_t I2CP_HEADER_LENGTH_OFFSET = 0;
|
||||||
|
const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4;
|
||||||
|
const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1;
|
||||||
|
|
||||||
|
const uint8_t I2CP_GET_DATE_MESSAGE = 32;
|
||||||
|
|
||||||
|
class I2CPServer;
|
||||||
|
class I2CPSession: public std::enable_shared_from_this<I2CPSession>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2CPSession (I2CPServer& owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||||
|
~I2CPSession ();
|
||||||
|
|
||||||
|
// message handlers
|
||||||
|
void GetDateMessageHandler (const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void ReadProtocolByte ();
|
||||||
|
void Receive ();
|
||||||
|
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandleNextMessage (const uint8_t * buf);
|
||||||
|
void Terminate ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
I2CPServer& m_Owner;
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
|
||||||
|
uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE], * m_NextMessage;
|
||||||
|
size_t m_NextMessageLen, m_NextMessageOffset;
|
||||||
|
};
|
||||||
|
typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
class I2CPServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2CPServer (const std::string& interface, int port);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
I2CPMessageHandler m_MessagesHandlers[256];
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
const decltype(m_MessagesHandlers)& GetMessagesHandlers () const { return m_MessagesHandlers; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
645
I2NPProtocol.cpp
Normal file
645
I2NPProtocol.cpp
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "Garlic.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
|
||||||
|
using namespace i2p::transport;
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
std::shared_ptr<I2NPMessage> NewI2NPMessage ()
|
||||||
|
{
|
||||||
|
return std::make_shared<I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE> >();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> NewI2NPShortMessage ()
|
||||||
|
{
|
||||||
|
return std::make_shared<I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE> >();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> NewI2NPMessage (size_t len)
|
||||||
|
{
|
||||||
|
return (len < I2NP_MAX_SHORT_MESSAGE_SIZE/2) ? NewI2NPShortMessage () : NewI2NPMessage ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID)
|
||||||
|
{
|
||||||
|
SetTypeID (msgType);
|
||||||
|
if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4);
|
||||||
|
SetMsgID (replyMsgID);
|
||||||
|
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
|
||||||
|
UpdateSize ();
|
||||||
|
UpdateChks ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2NPMessage::RenewI2NPMessageHeader ()
|
||||||
|
{
|
||||||
|
uint32_t msgID;
|
||||||
|
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||||
|
SetMsgID (msgID);
|
||||||
|
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool I2NPMessage::IsExpired () const
|
||||||
|
{
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
auto exp = GetExpiration ();
|
||||||
|
return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPMessage (len);
|
||||||
|
if (msg->Concat (buf, len) < len)
|
||||||
|
LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length ", msg->maxLen);
|
||||||
|
msg->FillI2NPMessageHeader (msgType, replyMsgID);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPMessage ();
|
||||||
|
if (msg->offset + len < msg->maxLen)
|
||||||
|
{
|
||||||
|
memcpy (msg->GetBuffer (), buf, len);
|
||||||
|
msg->len = msg->offset + len;
|
||||||
|
msg->from = from;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length");
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (!msg) return nullptr;
|
||||||
|
auto newMsg = NewI2NPMessage (msg->len);
|
||||||
|
newMsg->offset = msg->offset;
|
||||||
|
*newMsg = *msg;
|
||||||
|
return newMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID)
|
||||||
|
{
|
||||||
|
auto m = NewI2NPShortMessage ();
|
||||||
|
uint8_t * buf = m->GetPayload ();
|
||||||
|
if (msgID)
|
||||||
|
{
|
||||||
|
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
|
||||||
|
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, i2p::util::GetMillisecondsSinceEpoch ());
|
||||||
|
}
|
||||||
|
else // for SSU establishment
|
||||||
|
{
|
||||||
|
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||||
|
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
|
||||||
|
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, 2); // netID = 2
|
||||||
|
}
|
||||||
|
m->len += DELIVERY_STATUS_SIZE;
|
||||||
|
m->FillI2NPMessageHeader (eI2NPDeliveryStatus);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
|
||||||
|
uint32_t replyTunnelID, bool exploratory, std::set<i2p::data::IdentHash> * excludedPeers)
|
||||||
|
{
|
||||||
|
auto m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage ();
|
||||||
|
uint8_t * buf = m->GetPayload ();
|
||||||
|
memcpy (buf, key, 32); // key
|
||||||
|
buf += 32;
|
||||||
|
memcpy (buf, from, 32); // from
|
||||||
|
buf += 32;
|
||||||
|
uint8_t flag = exploratory ? DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP : DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP;
|
||||||
|
if (replyTunnelID)
|
||||||
|
{
|
||||||
|
*buf = flag | DATABASE_LOOKUP_DELIVERY_FLAG; // set delivery flag
|
||||||
|
htobe32buf (buf+1, replyTunnelID);
|
||||||
|
buf += 5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*buf = flag; // flag
|
||||||
|
buf++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludedPeers)
|
||||||
|
{
|
||||||
|
int cnt = excludedPeers->size ();
|
||||||
|
htobe16buf (buf, cnt);
|
||||||
|
buf += 2;
|
||||||
|
for (auto& it: *excludedPeers)
|
||||||
|
{
|
||||||
|
memcpy (buf, it, 32);
|
||||||
|
buf += 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// nothing to exclude
|
||||||
|
htobuf16 (buf, 0);
|
||||||
|
buf += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
m->len += (buf - m->GetPayload ());
|
||||||
|
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
|
||||||
|
const std::set<i2p::data::IdentHash>& excludedFloodfills,
|
||||||
|
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag)
|
||||||
|
{
|
||||||
|
int cnt = excludedFloodfills.size ();
|
||||||
|
auto m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage ();
|
||||||
|
uint8_t * buf = m->GetPayload ();
|
||||||
|
memcpy (buf, dest, 32); // key
|
||||||
|
buf += 32;
|
||||||
|
memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW
|
||||||
|
buf += 32;
|
||||||
|
*buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags
|
||||||
|
htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID
|
||||||
|
buf += 5;
|
||||||
|
|
||||||
|
// excluded
|
||||||
|
htobe16buf (buf, cnt);
|
||||||
|
buf += 2;
|
||||||
|
if (cnt > 0)
|
||||||
|
{
|
||||||
|
for (auto& it: excludedFloodfills)
|
||||||
|
{
|
||||||
|
memcpy (buf, it, 32);
|
||||||
|
buf += 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// encryption
|
||||||
|
memcpy (buf, replyKey, 32);
|
||||||
|
buf[32] = 1; // 1 tag
|
||||||
|
memcpy (buf + 33, replyTag, 32);
|
||||||
|
buf += 65;
|
||||||
|
|
||||||
|
m->len += (buf - m->GetPayload ());
|
||||||
|
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident,
|
||||||
|
std::vector<i2p::data::IdentHash> routers)
|
||||||
|
{
|
||||||
|
auto m = NewI2NPShortMessage ();
|
||||||
|
uint8_t * buf = m->GetPayload ();
|
||||||
|
size_t len = 0;
|
||||||
|
memcpy (buf, ident, 32);
|
||||||
|
len += 32;
|
||||||
|
buf[len] = routers.size ();
|
||||||
|
len++;
|
||||||
|
for (auto it: routers)
|
||||||
|
{
|
||||||
|
memcpy (buf + len, it, 32);
|
||||||
|
len += 32;
|
||||||
|
}
|
||||||
|
memcpy (buf + len, i2p::context.GetRouterInfo ().GetIdentHash (), 32);
|
||||||
|
len += 32;
|
||||||
|
m->len += len;
|
||||||
|
m->FillI2NPMessageHeader (eI2NPDatabaseSearchReply);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router, uint32_t replyToken)
|
||||||
|
{
|
||||||
|
if (!router) // we send own RouterInfo
|
||||||
|
router = context.GetSharedRouterInfo ();
|
||||||
|
|
||||||
|
auto m = NewI2NPShortMessage ();
|
||||||
|
uint8_t * payload = m->GetPayload ();
|
||||||
|
|
||||||
|
memcpy (payload + DATABASE_STORE_KEY_OFFSET, router->GetIdentHash (), 32);
|
||||||
|
payload[DATABASE_STORE_TYPE_OFFSET] = 0; // RouterInfo
|
||||||
|
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
|
||||||
|
uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE;
|
||||||
|
if (replyToken)
|
||||||
|
{
|
||||||
|
memset (buf, 0, 4); // zero tunnelID means direct reply
|
||||||
|
buf += 4;
|
||||||
|
memcpy (buf, router->GetIdentHash (), 32);
|
||||||
|
buf += 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t * sizePtr = buf;
|
||||||
|
buf += 2;
|
||||||
|
m->len += (buf - payload); // payload size
|
||||||
|
i2p::data::GzipDeflator deflator;
|
||||||
|
size_t size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len);
|
||||||
|
if (size)
|
||||||
|
{
|
||||||
|
htobe16buf (sizePtr, size); // size
|
||||||
|
m->len += size;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m = nullptr;
|
||||||
|
if (m)
|
||||||
|
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet, uint32_t replyToken)
|
||||||
|
{
|
||||||
|
if (!leaseSet) return nullptr;
|
||||||
|
auto m = NewI2NPShortMessage ();
|
||||||
|
uint8_t * payload = m->GetPayload ();
|
||||||
|
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
|
||||||
|
payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet
|
||||||
|
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
|
||||||
|
size_t size = DATABASE_STORE_HEADER_SIZE;
|
||||||
|
if (replyToken)
|
||||||
|
{
|
||||||
|
auto leases = leaseSet->GetNonExpiredLeases ();
|
||||||
|
if (leases.size () > 0)
|
||||||
|
{
|
||||||
|
htobe32buf (payload + size, leases[0]->tunnelID);
|
||||||
|
size += 4; // reply tunnelID
|
||||||
|
memcpy (payload + size, leases[0]->tunnelGateway, 32);
|
||||||
|
size += 32; // reply tunnel gateway
|
||||||
|
}
|
||||||
|
else
|
||||||
|
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0);
|
||||||
|
}
|
||||||
|
memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ());
|
||||||
|
size += leaseSet->GetBufferLen ();
|
||||||
|
m->len += size;
|
||||||
|
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (!msg || msg->GetTypeID () != eI2NPDatabaseStore) return false;
|
||||||
|
return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO:
|
||||||
|
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels)
|
||||||
|
{
|
||||||
|
if (maxNumTransitTunnels > 0 && maxNumTransitTunnels <= 10000 && g_MaxNumTransitTunnels != maxNumTransitTunnels)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels);
|
||||||
|
g_MaxNumTransitTunnels = maxNumTransitTunnels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
|
||||||
|
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
|
||||||
|
|
||||||
|
i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText);
|
||||||
|
// replace record to reply
|
||||||
|
if (i2p::context.AcceptsTunnels () &&
|
||||||
|
i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels &&
|
||||||
|
!i2p::transport::transports.IsBandwidthExceeded ())
|
||||||
|
{
|
||||||
|
auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
|
||||||
|
bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
|
||||||
|
clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||||
|
bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
|
||||||
|
clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
|
||||||
|
clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
|
||||||
|
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80,
|
||||||
|
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40);
|
||||||
|
i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel);
|
||||||
|
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30)
|
||||||
|
|
||||||
|
//TODO: fill filler
|
||||||
|
SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret
|
||||||
|
record + BUILD_RESPONSE_RECORD_HASH_OFFSET);
|
||||||
|
// encrypt reply
|
||||||
|
i2p::crypto::CBCEncryption encryption;
|
||||||
|
for (int j = 0; j < num; j++)
|
||||||
|
{
|
||||||
|
encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
|
||||||
|
encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET);
|
||||||
|
uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
|
||||||
|
encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
int num = buf[0];
|
||||||
|
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records");
|
||||||
|
if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
|
||||||
|
if (tunnel)
|
||||||
|
{
|
||||||
|
// endpoint of inbound tunnel
|
||||||
|
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
|
||||||
|
if (tunnel->HandleTunnelBuildResponse (buf, len))
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
|
||||||
|
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
|
||||||
|
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
|
||||||
|
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
|
||||||
|
if (HandleBuildRequestRecords (num, buf + 1, clearText))
|
||||||
|
{
|
||||||
|
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel
|
||||||
|
{
|
||||||
|
// so we send it to reply tunnel
|
||||||
|
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||||
|
CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
|
||||||
|
eI2NPVariableTunnelBuildReply, buf, len,
|
||||||
|
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||||
|
CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
|
||||||
|
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "TunnelBuild message is too short ", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
|
||||||
|
if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText))
|
||||||
|
{
|
||||||
|
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel
|
||||||
|
{
|
||||||
|
// so we send it to reply tunnel
|
||||||
|
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||||
|
CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
|
||||||
|
eI2NPTunnelBuildReply, buf, len,
|
||||||
|
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||||
|
CreateI2NPMessage (eI2NPTunnelBuild, buf, len,
|
||||||
|
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
int num = buf[0];
|
||||||
|
LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID);
|
||||||
|
if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "VaribleTunnelBuildReply message of ", num, " records is too short ", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
|
||||||
|
if (tunnel)
|
||||||
|
{
|
||||||
|
// reply for outbound tunnel
|
||||||
|
if (tunnel->HandleTunnelBuildResponse (buf, len))
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
|
||||||
|
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
|
||||||
|
i2p::tunnel::tunnels.AddOutboundTunnel (tunnel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
|
||||||
|
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPShortMessage ();
|
||||||
|
msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE);
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPShortMessage ();
|
||||||
|
htobe32buf (msg->GetPayload (), tunnelID);
|
||||||
|
msg->len += 4; // tunnelID
|
||||||
|
msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4);
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ()
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPShortMessage ();
|
||||||
|
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPMessage (len);
|
||||||
|
uint8_t * payload = msg->GetPayload ();
|
||||||
|
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
|
||||||
|
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
|
||||||
|
msg->len += TUNNEL_GATEWAY_HEADER_SIZE;
|
||||||
|
if (msg->Concat (buf, len) < len)
|
||||||
|
LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen);
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (msg->offset >= I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE)
|
||||||
|
{
|
||||||
|
// message is capable to be used without copying
|
||||||
|
uint8_t * payload = msg->GetBuffer () - TUNNEL_GATEWAY_HEADER_SIZE;
|
||||||
|
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
|
||||||
|
int len = msg->GetLength ();
|
||||||
|
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
|
||||||
|
msg->offset -= (I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE);
|
||||||
|
msg->len = msg->offset + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE +len;
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
|
||||||
|
const uint8_t * buf, size_t len, uint32_t replyMsgID)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPMessage (len);
|
||||||
|
size_t gatewayMsgOffset = I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
|
||||||
|
msg->offset += gatewayMsgOffset;
|
||||||
|
msg->len += gatewayMsgOffset;
|
||||||
|
if (msg->Concat (buf, len) < len)
|
||||||
|
LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen);
|
||||||
|
msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message
|
||||||
|
len = msg->GetLength ();
|
||||||
|
msg->offset -= gatewayMsgOffset;
|
||||||
|
uint8_t * payload = msg->GetPayload ();
|
||||||
|
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
|
||||||
|
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPTunnelGateway); // gateway message
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetI2NPMessageLength (const uint8_t * msg)
|
||||||
|
{
|
||||||
|
return bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleI2NPMessage (uint8_t * msg, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET];
|
||||||
|
uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET);
|
||||||
|
LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
|
||||||
|
|
||||||
|
uint8_t * buf = msg + I2NP_HEADER_SIZE;
|
||||||
|
int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET);
|
||||||
|
switch (typeID)
|
||||||
|
{
|
||||||
|
case eI2NPVariableTunnelBuild:
|
||||||
|
HandleVariableTunnelBuildMsg (msgID, buf, size);
|
||||||
|
break;
|
||||||
|
case eI2NPVariableTunnelBuildReply:
|
||||||
|
HandleVariableTunnelBuildReplyMsg (msgID, buf, size);
|
||||||
|
break;
|
||||||
|
case eI2NPTunnelBuild:
|
||||||
|
HandleTunnelBuildMsg (buf, size);
|
||||||
|
break;
|
||||||
|
case eI2NPTunnelBuildReply:
|
||||||
|
// TODO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogWarning, "I2NP: Unexpected message ", (int)typeID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (msg)
|
||||||
|
{
|
||||||
|
uint8_t typeID = msg->GetTypeID ();
|
||||||
|
LogPrint (eLogDebug, "I2NP: Handling message with type ", (int)typeID);
|
||||||
|
switch (typeID)
|
||||||
|
{
|
||||||
|
case eI2NPTunnelData:
|
||||||
|
i2p::tunnel::tunnels.PostTunnelData (msg);
|
||||||
|
break;
|
||||||
|
case eI2NPTunnelGateway:
|
||||||
|
i2p::tunnel::tunnels.PostTunnelData (msg);
|
||||||
|
break;
|
||||||
|
case eI2NPGarlic:
|
||||||
|
{
|
||||||
|
if (msg->from)
|
||||||
|
{
|
||||||
|
if (msg->from->GetTunnelPool ())
|
||||||
|
msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg);
|
||||||
|
else
|
||||||
|
LogPrint (eLogInfo, "I2NP: Local destination for garlic doesn't exist anymore");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
i2p::context.ProcessGarlicMessage (msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eI2NPDatabaseStore:
|
||||||
|
case eI2NPDatabaseSearchReply:
|
||||||
|
case eI2NPDatabaseLookup:
|
||||||
|
// forward to netDb
|
||||||
|
i2p::data::netdb.PostI2NPMsg (msg);
|
||||||
|
break;
|
||||||
|
case eI2NPDeliveryStatus:
|
||||||
|
{
|
||||||
|
if (msg->from && msg->from->GetTunnelPool ())
|
||||||
|
msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg);
|
||||||
|
else
|
||||||
|
i2p::context.ProcessDeliveryStatusMessage (msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eI2NPVariableTunnelBuild:
|
||||||
|
case eI2NPVariableTunnelBuildReply:
|
||||||
|
case eI2NPTunnelBuild:
|
||||||
|
case eI2NPTunnelBuildReply:
|
||||||
|
// forward to tunnel thread
|
||||||
|
i2p::tunnel::tunnels.PostTunnelData (msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I2NPMessagesHandler::~I2NPMessagesHandler ()
|
||||||
|
{
|
||||||
|
Flush ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2NPMessagesHandler::PutNextMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (msg)
|
||||||
|
{
|
||||||
|
switch (msg->GetTypeID ())
|
||||||
|
{
|
||||||
|
case eI2NPTunnelData:
|
||||||
|
m_TunnelMsgs.push_back (msg);
|
||||||
|
break;
|
||||||
|
case eI2NPTunnelGateway:
|
||||||
|
m_TunnelGatewayMsgs.push_back (msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
HandleI2NPMessage (msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2NPMessagesHandler::Flush ()
|
||||||
|
{
|
||||||
|
if (!m_TunnelMsgs.empty ())
|
||||||
|
{
|
||||||
|
i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
|
||||||
|
m_TunnelMsgs.clear ();
|
||||||
|
}
|
||||||
|
if (!m_TunnelGatewayMsgs.empty ())
|
||||||
|
{
|
||||||
|
i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
|
||||||
|
m_TunnelGatewayMsgs.clear ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
265
I2NPProtocol.h
Normal file
265
I2NPProtocol.h
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
#ifndef I2NP_PROTOCOL_H__
|
||||||
|
#define I2NP_PROTOCOL_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <set>
|
||||||
|
#include <memory>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
// I2NP header
|
||||||
|
const size_t I2NP_HEADER_TYPEID_OFFSET = 0;
|
||||||
|
const size_t I2NP_HEADER_MSGID_OFFSET = I2NP_HEADER_TYPEID_OFFSET + 1;
|
||||||
|
const size_t I2NP_HEADER_EXPIRATION_OFFSET = I2NP_HEADER_MSGID_OFFSET + 4;
|
||||||
|
const size_t I2NP_HEADER_SIZE_OFFSET = I2NP_HEADER_EXPIRATION_OFFSET + 8;
|
||||||
|
const size_t I2NP_HEADER_CHKS_OFFSET = I2NP_HEADER_SIZE_OFFSET + 2;
|
||||||
|
const size_t I2NP_HEADER_SIZE = I2NP_HEADER_CHKS_OFFSET + 1;
|
||||||
|
|
||||||
|
// I2NP short header
|
||||||
|
const size_t I2NP_SHORT_HEADER_TYPEID_OFFSET = 0;
|
||||||
|
const size_t I2NP_SHORT_HEADER_EXPIRATION_OFFSET = I2NP_SHORT_HEADER_TYPEID_OFFSET + 1;
|
||||||
|
const size_t I2NP_SHORT_HEADER_SIZE = I2NP_SHORT_HEADER_EXPIRATION_OFFSET + 4;
|
||||||
|
|
||||||
|
// Tunnel Gateway header
|
||||||
|
const size_t TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET = 0;
|
||||||
|
const size_t TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET = TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET + 4;
|
||||||
|
const size_t TUNNEL_GATEWAY_HEADER_SIZE = TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET + 2;
|
||||||
|
|
||||||
|
// DeliveryStatus
|
||||||
|
const size_t DELIVERY_STATUS_MSGID_OFFSET = 0;
|
||||||
|
const size_t DELIVERY_STATUS_TIMESTAMP_OFFSET = DELIVERY_STATUS_MSGID_OFFSET + 4;
|
||||||
|
const size_t DELIVERY_STATUS_SIZE = DELIVERY_STATUS_TIMESTAMP_OFFSET + 8;
|
||||||
|
|
||||||
|
// DatabaseStore
|
||||||
|
const size_t DATABASE_STORE_KEY_OFFSET = 0;
|
||||||
|
const size_t DATABASE_STORE_TYPE_OFFSET = DATABASE_STORE_KEY_OFFSET + 32;
|
||||||
|
const size_t DATABASE_STORE_REPLY_TOKEN_OFFSET = DATABASE_STORE_TYPE_OFFSET + 1;
|
||||||
|
const size_t DATABASE_STORE_HEADER_SIZE = DATABASE_STORE_REPLY_TOKEN_OFFSET + 4;
|
||||||
|
|
||||||
|
// TunnelBuild
|
||||||
|
const size_t TUNNEL_BUILD_RECORD_SIZE = 528;
|
||||||
|
|
||||||
|
//BuildRequestRecordClearText
|
||||||
|
const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222;
|
||||||
|
|
||||||
|
// BuildRequestRecordEncrypted
|
||||||
|
const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0;
|
||||||
|
const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16;
|
||||||
|
|
||||||
|
// BuildResponseRecord
|
||||||
|
const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0;
|
||||||
|
const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32;
|
||||||
|
const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495;
|
||||||
|
const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE;
|
||||||
|
|
||||||
|
enum I2NPMessageType
|
||||||
|
{
|
||||||
|
eI2NPDatabaseStore = 1,
|
||||||
|
eI2NPDatabaseLookup = 2,
|
||||||
|
eI2NPDatabaseSearchReply = 3,
|
||||||
|
eI2NPDeliveryStatus = 10,
|
||||||
|
eI2NPGarlic = 11,
|
||||||
|
eI2NPTunnelData = 18,
|
||||||
|
eI2NPTunnelGateway = 19,
|
||||||
|
eI2NPData = 20,
|
||||||
|
eI2NPTunnelBuild = 21,
|
||||||
|
eI2NPTunnelBuildReply = 22,
|
||||||
|
eI2NPVariableTunnelBuild = 23,
|
||||||
|
eI2NPVariableTunnelBuildReply = 24
|
||||||
|
};
|
||||||
|
|
||||||
|
const int NUM_TUNNEL_BUILD_RECORDS = 8;
|
||||||
|
|
||||||
|
// DatabaseLookup flags
|
||||||
|
const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01;
|
||||||
|
const uint8_t DATABASE_LOOKUP_ENCYPTION_FLAG = 0x02;
|
||||||
|
const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C;
|
||||||
|
const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0;
|
||||||
|
const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100
|
||||||
|
const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000
|
||||||
|
const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100
|
||||||
|
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
class InboundTunnel;
|
||||||
|
class TunnelPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t I2NP_MAX_MESSAGE_SIZE = 32768;
|
||||||
|
const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096;
|
||||||
|
const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT)
|
||||||
|
const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds
|
||||||
|
|
||||||
|
struct I2NPMessage
|
||||||
|
{
|
||||||
|
uint8_t * buf;
|
||||||
|
size_t len, offset, maxLen;
|
||||||
|
std::shared_ptr<i2p::tunnel::InboundTunnel> from;
|
||||||
|
|
||||||
|
I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2),
|
||||||
|
offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header
|
||||||
|
|
||||||
|
// header accessors
|
||||||
|
uint8_t * GetHeader () { return GetBuffer (); };
|
||||||
|
const uint8_t * GetHeader () const { return GetBuffer (); };
|
||||||
|
void SetTypeID (uint8_t typeID) { GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = typeID; };
|
||||||
|
uint8_t GetTypeID () const { return GetHeader ()[I2NP_HEADER_TYPEID_OFFSET]; };
|
||||||
|
void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + I2NP_HEADER_MSGID_OFFSET, msgID); };
|
||||||
|
uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + I2NP_HEADER_MSGID_OFFSET); };
|
||||||
|
void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET, expiration); };
|
||||||
|
uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET); };
|
||||||
|
void SetSize (uint16_t size) { htobe16buf (GetHeader () + I2NP_HEADER_SIZE_OFFSET, size); };
|
||||||
|
uint16_t GetSize () const { return bufbe16toh (GetHeader () + I2NP_HEADER_SIZE_OFFSET); };
|
||||||
|
void UpdateSize () { SetSize (GetPayloadLength ()); };
|
||||||
|
void SetChks (uint8_t chks) { GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = chks; };
|
||||||
|
void UpdateChks ()
|
||||||
|
{
|
||||||
|
uint8_t hash[32];
|
||||||
|
SHA256(GetPayload (), GetPayloadLength (), hash);
|
||||||
|
GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = hash[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// payload
|
||||||
|
uint8_t * GetPayload () { return GetBuffer () + I2NP_HEADER_SIZE; };
|
||||||
|
const uint8_t * GetPayload () const { return GetBuffer () + I2NP_HEADER_SIZE; };
|
||||||
|
uint8_t * GetBuffer () { return buf + offset; };
|
||||||
|
const uint8_t * GetBuffer () const { return buf + offset; };
|
||||||
|
size_t GetLength () const { return len - offset; };
|
||||||
|
size_t GetPayloadLength () const { return GetLength () - I2NP_HEADER_SIZE; };
|
||||||
|
|
||||||
|
void Align (size_t alignment)
|
||||||
|
{
|
||||||
|
if (len + alignment > maxLen) return;
|
||||||
|
size_t rem = ((size_t)GetBuffer ()) % alignment;
|
||||||
|
if (rem)
|
||||||
|
{
|
||||||
|
offset += (alignment - rem);
|
||||||
|
len += (alignment - rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Concat (const uint8_t * buf1, size_t len1)
|
||||||
|
{
|
||||||
|
// make sure with don't write beyond maxLen
|
||||||
|
if (len + len1 > maxLen) len1 = maxLen - len;
|
||||||
|
memcpy (buf + len, buf1, len1);
|
||||||
|
len += len1;
|
||||||
|
return len1;
|
||||||
|
}
|
||||||
|
|
||||||
|
I2NPMessage& operator=(const I2NPMessage& other)
|
||||||
|
{
|
||||||
|
memcpy (buf + offset, other.buf + other.offset, other.GetLength ());
|
||||||
|
len = offset + other.GetLength ();
|
||||||
|
from = other.from;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for SSU only
|
||||||
|
uint8_t * GetSSUHeader () { return buf + offset + I2NP_HEADER_SIZE - I2NP_SHORT_HEADER_SIZE; };
|
||||||
|
void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular
|
||||||
|
{
|
||||||
|
const uint8_t * ssu = GetSSUHeader ();
|
||||||
|
GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET]; // typeid
|
||||||
|
SetMsgID (msgID);
|
||||||
|
SetExpiration (bufbe32toh (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET)*1000LL);
|
||||||
|
SetSize (len - offset - I2NP_HEADER_SIZE);
|
||||||
|
SetChks (0);
|
||||||
|
}
|
||||||
|
uint32_t ToSSU () // return msgID
|
||||||
|
{
|
||||||
|
uint8_t header[I2NP_HEADER_SIZE];
|
||||||
|
memcpy (header, GetHeader (), I2NP_HEADER_SIZE);
|
||||||
|
uint8_t * ssu = GetSSUHeader ();
|
||||||
|
ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET] = header[I2NP_HEADER_TYPEID_OFFSET]; // typeid
|
||||||
|
htobe32buf (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET, bufbe64toh (header + I2NP_HEADER_EXPIRATION_OFFSET)/1000LL);
|
||||||
|
len = offset + I2NP_SHORT_HEADER_SIZE + bufbe16toh (header + I2NP_HEADER_SIZE_OFFSET);
|
||||||
|
return bufbe32toh (header + I2NP_HEADER_MSGID_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0);
|
||||||
|
void RenewI2NPMessageHeader ();
|
||||||
|
bool IsExpired () const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<int sz>
|
||||||
|
struct I2NPMessageBuffer: public I2NPMessage
|
||||||
|
{
|
||||||
|
I2NPMessageBuffer () { buf = m_Buffer; maxLen = sz; };
|
||||||
|
uint8_t m_Buffer[sz + 32]; // 16 alignment + 16 padding
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> NewI2NPMessage ();
|
||||||
|
std::shared_ptr<I2NPMessage> NewI2NPShortMessage ();
|
||||||
|
std::shared_ptr<I2NPMessage> NewI2NPMessage (size_t len);
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from = nullptr);
|
||||||
|
std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
|
||||||
|
uint32_t replyTunnelID, bool exploratory = false, std::set<i2p::data::IdentHash> * excludedPeers = nullptr);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
|
||||||
|
const std::set<i2p::data::IdentHash>& excludedFloodfills,
|
||||||
|
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector<i2p::data::IdentHash> routers);
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, uint32_t replyToken = 0);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet, uint32_t replyToken = 0);
|
||||||
|
bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText);
|
||||||
|
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
|
||||||
|
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
|
||||||
|
void HandleTunnelBuildMsg (uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ();
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
|
||||||
|
const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
size_t GetI2NPMessageLength (const uint8_t * msg);
|
||||||
|
void HandleI2NPMessage (uint8_t * msg, size_t len);
|
||||||
|
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
class I2NPMessagesHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
~I2NPMessagesHandler ();
|
||||||
|
void PutNextMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
void Flush ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500;
|
||||||
|
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
569
I2PControl.cpp
Normal file
569
I2PControl.cpp
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <boost/date_time/local_time/local_time.hpp>
|
||||||
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||||
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
|
|
||||||
|
// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy
|
||||||
|
#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))
|
||||||
|
#if !GCC47_BOOST149
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "I2PControl.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
I2PControlService::I2PControlService (const std::string& address, int port):
|
||||||
|
m_IsRunning (false), m_Thread (nullptr),
|
||||||
|
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
|
||||||
|
m_SSLContext (m_Service, boost::asio::ssl::context::sslv23),
|
||||||
|
m_ShutdownTimer (m_Service)
|
||||||
|
{
|
||||||
|
i2p::config::GetOption("i2pcontrol.password", m_Password);
|
||||||
|
|
||||||
|
// certificate / keys
|
||||||
|
std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt);
|
||||||
|
std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key);
|
||||||
|
|
||||||
|
if (i2pcp_crt.at(0) != '/')
|
||||||
|
i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt);
|
||||||
|
if (i2pcp_key.at(0) != '/')
|
||||||
|
i2pcp_key = i2p::fs::DataDirPath(i2pcp_key);
|
||||||
|
if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) {
|
||||||
|
LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection");
|
||||||
|
CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogDebug, "I2PControl: using cert from ", i2pcp_crt);
|
||||||
|
}
|
||||||
|
m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
|
||||||
|
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem);
|
||||||
|
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem);
|
||||||
|
|
||||||
|
// handlers
|
||||||
|
m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler;
|
||||||
|
m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler;
|
||||||
|
m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler;
|
||||||
|
m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler;
|
||||||
|
m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler;
|
||||||
|
m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler;
|
||||||
|
|
||||||
|
// I2PControl
|
||||||
|
m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler;
|
||||||
|
|
||||||
|
// RouterInfo
|
||||||
|
m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler;
|
||||||
|
m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler;
|
||||||
|
m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler;
|
||||||
|
m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler;
|
||||||
|
m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler;
|
||||||
|
m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S;
|
||||||
|
m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S;
|
||||||
|
m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler;
|
||||||
|
m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler;
|
||||||
|
m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes;
|
||||||
|
m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes;
|
||||||
|
|
||||||
|
// RouterManager
|
||||||
|
m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler;
|
||||||
|
m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler;
|
||||||
|
m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler;
|
||||||
|
|
||||||
|
// NetworkSetting
|
||||||
|
m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit;
|
||||||
|
m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PControlService::~I2PControlService ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::Start ()
|
||||||
|
{
|
||||||
|
if (!m_IsRunning)
|
||||||
|
{
|
||||||
|
Accept ();
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Thread = new std::thread (std::bind (&I2PControlService::Run, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::Stop ()
|
||||||
|
{
|
||||||
|
if (m_IsRunning)
|
||||||
|
{
|
||||||
|
m_IsRunning = false;
|
||||||
|
m_Acceptor.cancel ();
|
||||||
|
m_Service.stop ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
m_Service.run ();
|
||||||
|
} catch (std::exception& ex) {
|
||||||
|
LogPrint (eLogError, "I2PControl: runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::Accept ()
|
||||||
|
{
|
||||||
|
auto newSocket = std::make_shared<ssl_socket> (m_Service, m_SSLContext);
|
||||||
|
m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this,
|
||||||
|
std::placeholders::_1, newSocket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Accept ();
|
||||||
|
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint (eLogError, "I2PControl: accept error: ", ecode.message ());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint (eLogDebug, "I2PControl: new request from ", socket->lowest_layer ().remote_endpoint ());
|
||||||
|
Handshake (socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket)
|
||||||
|
{
|
||||||
|
socket->async_handshake(boost::asio::ssl::stream_base::server,
|
||||||
|
std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint (eLogError, "I2PControl: handshake error: ", ecode.message ());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//std::this_thread::sleep_for (std::chrono::milliseconds(5));
|
||||||
|
ReadRequest (socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket)
|
||||||
|
{
|
||||||
|
auto request = std::make_shared<I2PControlBuffer>();
|
||||||
|
socket->async_read_some (
|
||||||
|
#if defined(BOOST_ASIO_HAS_STD_ARRAY)
|
||||||
|
boost::asio::buffer (*request),
|
||||||
|
#else
|
||||||
|
boost::asio::buffer (request->data (), request->size ()),
|
||||||
|
#endif
|
||||||
|
std::bind(&I2PControlService::HandleRequestReceived, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, socket, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode,
|
||||||
|
size_t bytes_transferred, std::shared_ptr<ssl_socket> socket,
|
||||||
|
std::shared_ptr<I2PControlBuffer> buf)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint (eLogError, "I2PControl: read error: ", ecode.message ());
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool isHtml = !memcmp (buf->data (), "POST", 4);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss.write (buf->data (), bytes_transferred);
|
||||||
|
if (isHtml)
|
||||||
|
{
|
||||||
|
std::string header;
|
||||||
|
size_t contentLength = 0;
|
||||||
|
while (!ss.eof () && header != "\r")
|
||||||
|
{
|
||||||
|
std::getline(ss, header);
|
||||||
|
auto colon = header.find (':');
|
||||||
|
if (colon != std::string::npos && header.substr (0, colon) == "Content-Length")
|
||||||
|
contentLength = std::stoi (header.substr (colon + 1));
|
||||||
|
}
|
||||||
|
if (ss.eof ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected");
|
||||||
|
return; // TODO:
|
||||||
|
}
|
||||||
|
std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read
|
||||||
|
if (rem > 0)
|
||||||
|
{
|
||||||
|
bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem));
|
||||||
|
ss.write (buf->data (), bytes_transferred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ostringstream response;
|
||||||
|
#if GCC47_BOOST149
|
||||||
|
LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7");
|
||||||
|
response << "{\"id\":null,\"error\":";
|
||||||
|
response << "{\"code\":-32603,\"message\":\"JSON requests is not supported with this version of boost\"},";
|
||||||
|
response << "\"jsonrpc\":\"2.0\"}";
|
||||||
|
#else
|
||||||
|
boost::property_tree::ptree pt;
|
||||||
|
boost::property_tree::read_json (ss, pt);
|
||||||
|
|
||||||
|
std::string id = pt.get<std::string>("id");
|
||||||
|
std::string method = pt.get<std::string>("method");
|
||||||
|
auto it = m_MethodHandlers.find (method);
|
||||||
|
if (it != m_MethodHandlers.end ())
|
||||||
|
{
|
||||||
|
response << "{\"id\":" << id << ",\"result\":{";
|
||||||
|
(this->*(it->second))(pt.get_child ("params"), response);
|
||||||
|
response << "},\"jsonrpc\":\"2.0\"}";
|
||||||
|
} else {
|
||||||
|
LogPrint (eLogWarning, "I2PControl: unknown method ", method);
|
||||||
|
response << "{\"id\":null,\"error\":";
|
||||||
|
response << "{\"code\":-32601,\"message\":\"Method not found\"},";
|
||||||
|
response << "\"jsonrpc\":\"2.0\"}";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
SendResponse (socket, buf, response, isHtml);
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ());
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PControl: handle request unknown exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const
|
||||||
|
{
|
||||||
|
ss << "\"" << name << "\":" << value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const
|
||||||
|
{
|
||||||
|
ss << "\"" << name << "\":";
|
||||||
|
if (value.length () > 0)
|
||||||
|
ss << "\"" << value << "\"";
|
||||||
|
else
|
||||||
|
ss << "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const
|
||||||
|
{
|
||||||
|
ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
|
||||||
|
std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml)
|
||||||
|
{
|
||||||
|
size_t len = response.str ().length (), offset = 0;
|
||||||
|
if (isHtml)
|
||||||
|
{
|
||||||
|
std::ostringstream header;
|
||||||
|
header << "HTTP/1.1 200 OK\r\n";
|
||||||
|
header << "Connection: close\r\n";
|
||||||
|
header << "Content-Length: " << boost::lexical_cast<std::string>(len) << "\r\n";
|
||||||
|
header << "Content-Type: application/json\r\n";
|
||||||
|
header << "Date: ";
|
||||||
|
auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT");
|
||||||
|
header.imbue(std::locale (header.getloc(), facet));
|
||||||
|
header << boost::posix_time::second_clock::local_time() << "\r\n";
|
||||||
|
header << "\r\n";
|
||||||
|
offset = header.str ().size ();
|
||||||
|
memcpy (buf->data (), header.str ().c_str (), offset);
|
||||||
|
}
|
||||||
|
memcpy (buf->data () + offset, response.str ().c_str (), len);
|
||||||
|
boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len),
|
||||||
|
boost::asio::transfer_all (),
|
||||||
|
std::bind(&I2PControlService::HandleResponseSent, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, socket, buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||||
|
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint (eLogError, "I2PControl: write error: ", ecode.message ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlers
|
||||||
|
|
||||||
|
void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
int api = params.get<int> ("API");
|
||||||
|
auto password = params.get<std::string> ("Password");
|
||||||
|
LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password);
|
||||||
|
if (password != m_Password) {
|
||||||
|
LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InsertParam (results, "API", api);
|
||||||
|
results << ",";
|
||||||
|
std::string token = boost::lexical_cast<std::string>(i2p::util::GetSecondsSinceEpoch ());
|
||||||
|
m_Tokens.insert (token);
|
||||||
|
InsertParam (results, "Token", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
auto echo = params.get<std::string> ("Echo");
|
||||||
|
LogPrint (eLogDebug, "I2PControl Echo Echo=", echo);
|
||||||
|
InsertParam (results, "Result", echo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// I2PControl
|
||||||
|
|
||||||
|
void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
for (auto& it: params)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first);
|
||||||
|
auto it1 = m_I2PControlHandlers.find (it.first);
|
||||||
|
if (it1 != m_I2PControlHandlers.end ())
|
||||||
|
{
|
||||||
|
(this->*(it1->second))(it.second.data ());
|
||||||
|
InsertParam (results, it.first, "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::PasswordHandler (const std::string& value)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "I2PControl: new password=", value, ", to make it persistent you should update your config!");
|
||||||
|
m_Password = value;
|
||||||
|
m_Tokens.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterInfo
|
||||||
|
|
||||||
|
void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
for (auto it = params.begin (); it != params.end (); it++)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first);
|
||||||
|
auto it1 = m_RouterInfoHandlers.find (it->first);
|
||||||
|
if (it1 != m_RouterInfoHandlers.end ())
|
||||||
|
{
|
||||||
|
if (it != params.begin ()) results << ",";
|
||||||
|
(this->*(it1->second))(results);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::UptimeHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::VersionHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.version", VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::StatusHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.status", "???"); // TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::NetStatusHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size ();
|
||||||
|
InsertParam (results, "i2p.router.net.tunnels.participating", transit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::InboundBandwidth1S (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
double bw = i2p::transport::transports.GetInBandwidth ();
|
||||||
|
InsertParam (results, "i2p.router.net.bw.inbound.1s", bw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::OutboundBandwidth1S (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
double bw = i2p::transport::transports.GetOutBandwidth ();
|
||||||
|
InsertParam (results, "i2p.router.net.bw.outbound.1s", bw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::NetTotalSentBytes (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterManager
|
||||||
|
|
||||||
|
void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
for (auto it = params.begin (); it != params.end (); it++)
|
||||||
|
{
|
||||||
|
if (it != params.begin ()) results << ",";
|
||||||
|
LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first);
|
||||||
|
auto it1 = m_RouterManagerHandlers.find (it->first);
|
||||||
|
if (it1 != m_RouterManagerHandlers.end ()) {
|
||||||
|
(this->*(it1->second))(results);
|
||||||
|
} else
|
||||||
|
LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void I2PControlService::ShutdownHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "I2PControl: Shutdown requested");
|
||||||
|
InsertParam (results, "Shutdown", "");
|
||||||
|
m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent
|
||||||
|
m_ShutdownTimer.async_wait (
|
||||||
|
[](const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
Daemon.running = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
i2p::context.SetAcceptsTunnels (false);
|
||||||
|
int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout ();
|
||||||
|
LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains");
|
||||||
|
InsertParam (results, "ShutdownGraceful", "");
|
||||||
|
m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second
|
||||||
|
m_ShutdownTimer.async_wait (
|
||||||
|
[](const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
Daemon.running = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::ReseedHandler (std::ostringstream& results)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "I2PControl: Reseed requested");
|
||||||
|
InsertParam (results, "Reseed", "");
|
||||||
|
i2p::data::netdb.Reseed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// network setting
|
||||||
|
void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
for (auto it = params.begin (); it != params.end (); it++)
|
||||||
|
{
|
||||||
|
if (it != params.begin ()) results << ",";
|
||||||
|
LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first);
|
||||||
|
auto it1 = m_NetworkSettingHandlers.find (it->first);
|
||||||
|
if (it1 != m_NetworkSettingHandlers.end ()) {
|
||||||
|
(this->*(it1->second))(it->second.data (), results);
|
||||||
|
} else
|
||||||
|
LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
if (value != "null")
|
||||||
|
i2p::context.SetBandwidth (std::atoi(value.c_str()));
|
||||||
|
int bw = i2p::context.GetBandwidthLimit();
|
||||||
|
InsertParam (results, "i2p.router.net.bw.in", bw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results)
|
||||||
|
{
|
||||||
|
if (value != "null")
|
||||||
|
i2p::context.SetBandwidth (std::atoi(value.c_str()));
|
||||||
|
int bw = i2p::context.GetBandwidthLimit();
|
||||||
|
InsertParam (results, "i2p.router.net.bw.out", bw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// certificate
|
||||||
|
void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path)
|
||||||
|
{
|
||||||
|
FILE *f = NULL;
|
||||||
|
EVP_PKEY * pkey = EVP_PKEY_new ();
|
||||||
|
RSA * rsa = RSA_new ();
|
||||||
|
BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ());
|
||||||
|
RSA_generate_key_ex (rsa, 4096, e, NULL);
|
||||||
|
BN_free (e);
|
||||||
|
if (rsa)
|
||||||
|
{
|
||||||
|
EVP_PKEY_assign_RSA (pkey, rsa);
|
||||||
|
X509 * x509 = X509_new ();
|
||||||
|
ASN1_INTEGER_set (X509_get_serialNumber (x509), 1);
|
||||||
|
X509_gmtime_adj (X509_get_notBefore (x509), 0);
|
||||||
|
X509_gmtime_adj (X509_get_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration
|
||||||
|
X509_set_pubkey (x509, pkey); // public key
|
||||||
|
X509_NAME * name = X509_get_subject_name (x509);
|
||||||
|
X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"RU", -1, -1, 0); // country (Russia by default)
|
||||||
|
X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization
|
||||||
|
X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name
|
||||||
|
X509_set_issuer_name (x509, name); // set issuer to ourselves
|
||||||
|
X509_sign (x509, pkey, EVP_sha1 ()); // sign
|
||||||
|
|
||||||
|
// save cert
|
||||||
|
if ((f = fopen (crt_path, "wb")) != NULL) {
|
||||||
|
LogPrint (eLogInfo, "I2PControl: saving new cert to ", crt_path);
|
||||||
|
PEM_write_X509 (f, x509);
|
||||||
|
fclose (f);
|
||||||
|
} else {
|
||||||
|
LogPrint (eLogError, "I2PControl: can't write cert: ", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
// save key
|
||||||
|
if ((f = fopen (key_path, "wb")) != NULL) {
|
||||||
|
LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path);
|
||||||
|
PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL);
|
||||||
|
fclose (f);
|
||||||
|
} else {
|
||||||
|
LogPrint (eLogError, "I2PControl: can't write key: ", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
X509_free (x509);
|
||||||
|
} else {
|
||||||
|
LogPrint (eLogError, "I2PControl: can't create RSA key for certificate");
|
||||||
|
}
|
||||||
|
EVP_PKEY_free (pkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
I2PControl.h
Normal file
122
I2PControl.h
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#ifndef I2P_CONTROL_H__
|
||||||
|
#define I2P_CONTROL_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/asio/ssl.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024;
|
||||||
|
typedef std::array<char, I2P_CONTROL_MAX_REQUEST_SIZE> I2PControlBuffer;
|
||||||
|
|
||||||
|
const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years
|
||||||
|
const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol";
|
||||||
|
const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P";
|
||||||
|
|
||||||
|
class I2PControlService
|
||||||
|
{
|
||||||
|
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PControlService (const std::string& address, int port);
|
||||||
|
~I2PControlService ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void Accept ();
|
||||||
|
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
|
||||||
|
void Handshake (std::shared_ptr<ssl_socket> socket);
|
||||||
|
void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
|
||||||
|
void ReadRequest (std::shared_ptr<ssl_socket> socket);
|
||||||
|
void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred,
|
||||||
|
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
|
||||||
|
void SendResponse (std::shared_ptr<ssl_socket> socket,
|
||||||
|
std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml);
|
||||||
|
void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
|
||||||
|
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
|
||||||
|
|
||||||
|
void CreateCertificate (const char *crt_path, const char *key_path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void InsertParam (std::ostringstream& ss, const std::string& name, int value) const;
|
||||||
|
void InsertParam (std::ostringstream& ss, const std::string& name, double value) const;
|
||||||
|
void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const;
|
||||||
|
|
||||||
|
// methods
|
||||||
|
typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results);
|
||||||
|
|
||||||
|
void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
|
||||||
|
void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
|
||||||
|
void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
|
||||||
|
void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
|
||||||
|
void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
|
||||||
|
void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
|
||||||
|
|
||||||
|
// I2PControl
|
||||||
|
typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value);
|
||||||
|
void PasswordHandler (const std::string& value);
|
||||||
|
|
||||||
|
// RouterInfo
|
||||||
|
typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results);
|
||||||
|
void UptimeHandler (std::ostringstream& results);
|
||||||
|
void VersionHandler (std::ostringstream& results);
|
||||||
|
void StatusHandler (std::ostringstream& results);
|
||||||
|
void NetDbKnownPeersHandler (std::ostringstream& results);
|
||||||
|
void NetDbActivePeersHandler (std::ostringstream& results);
|
||||||
|
void NetStatusHandler (std::ostringstream& results);
|
||||||
|
void TunnelsParticipatingHandler (std::ostringstream& results);
|
||||||
|
void InboundBandwidth1S (std::ostringstream& results);
|
||||||
|
void OutboundBandwidth1S (std::ostringstream& results);
|
||||||
|
void NetTotalReceivedBytes (std::ostringstream& results);
|
||||||
|
void NetTotalSentBytes (std::ostringstream& results);
|
||||||
|
|
||||||
|
// RouterManager
|
||||||
|
typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results);
|
||||||
|
void ShutdownHandler (std::ostringstream& results);
|
||||||
|
void ShutdownGracefulHandler (std::ostringstream& results);
|
||||||
|
void ReseedHandler (std::ostringstream& results);
|
||||||
|
|
||||||
|
// NetworkSetting
|
||||||
|
typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results);
|
||||||
|
void InboundBandwidthLimit (const std::string& value, std::ostringstream& results);
|
||||||
|
void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string m_Password;
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
|
||||||
|
boost::asio::io_service m_Service;
|
||||||
|
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||||
|
boost::asio::ssl::context m_SSLContext;
|
||||||
|
boost::asio::deadline_timer m_ShutdownTimer;
|
||||||
|
std::set<std::string> m_Tokens;
|
||||||
|
|
||||||
|
std::map<std::string, MethodHandler> m_MethodHandlers;
|
||||||
|
std::map<std::string, I2PControlRequestHandler> m_I2PControlHandlers;
|
||||||
|
std::map<std::string, RouterInfoRequestHandler> m_RouterInfoHandlers;
|
||||||
|
std::map<std::string, RouterManagerRequestHandler> m_RouterManagerHandlers;
|
||||||
|
std::map<std::string, NetworkSettingRequestHandler> m_NetworkSettingHandlers;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
83
I2PEndian.cpp
Normal file
83
I2PEndian.cpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include "I2PEndian.h"
|
||||||
|
|
||||||
|
// http://habrahabr.ru/post/121811/
|
||||||
|
// http://codepad.org/2ycmkz2y
|
||||||
|
|
||||||
|
#include "LittleBigEndian.h"
|
||||||
|
|
||||||
|
#ifdef NEEDS_LOCAL_ENDIAN
|
||||||
|
uint16_t htobe16(uint16_t int16)
|
||||||
|
{
|
||||||
|
BigEndian<uint16_t> u16(int16);
|
||||||
|
return u16.raw_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t htobe32(uint32_t int32)
|
||||||
|
{
|
||||||
|
BigEndian<uint32_t> u32(int32);
|
||||||
|
return u32.raw_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t htobe64(uint64_t int64)
|
||||||
|
{
|
||||||
|
BigEndian<uint64_t> u64(int64);
|
||||||
|
return u64.raw_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t be16toh(uint16_t big16)
|
||||||
|
{
|
||||||
|
LittleEndian<uint16_t> u16(big16);
|
||||||
|
return u16.raw_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t be32toh(uint32_t big32)
|
||||||
|
{
|
||||||
|
LittleEndian<uint32_t> u32(big32);
|
||||||
|
return u32.raw_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t be64toh(uint64_t big64)
|
||||||
|
{
|
||||||
|
LittleEndian<uint64_t> u64(big64);
|
||||||
|
return u64.raw_value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* it can be used in Windows 8
|
||||||
|
#include <Winsock2.h>
|
||||||
|
|
||||||
|
uint16_t htobe16(uint16_t int16)
|
||||||
|
{
|
||||||
|
return htons(int16);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t htobe32(uint32_t int32)
|
||||||
|
{
|
||||||
|
return htonl(int32);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t htobe64(uint64_t int64)
|
||||||
|
{
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
|
||||||
|
//return htonll(int64);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t be16toh(uint16_t big16)
|
||||||
|
{
|
||||||
|
return ntohs(big16);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t be32toh(uint32_t big32)
|
||||||
|
{
|
||||||
|
return ntohl(big32);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t be64toh(uint64_t big64)
|
||||||
|
{
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
|
||||||
|
//return ntohll(big64);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -49,68 +49,68 @@ uint64_t be64toh(uint64_t big64);
|
|||||||
|
|
||||||
inline uint16_t buf16toh(const void *buf)
|
inline uint16_t buf16toh(const void *buf)
|
||||||
{
|
{
|
||||||
uint16_t b16;
|
uint16_t b16;
|
||||||
memcpy(&b16, buf, sizeof(uint16_t));
|
memcpy(&b16, buf, sizeof(uint16_t));
|
||||||
return b16;
|
return b16;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t buf32toh(const void *buf)
|
inline uint32_t buf32toh(const void *buf)
|
||||||
{
|
{
|
||||||
uint32_t b32;
|
uint32_t b32;
|
||||||
memcpy(&b32, buf, sizeof(uint32_t));
|
memcpy(&b32, buf, sizeof(uint32_t));
|
||||||
return b32;
|
return b32;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint64_t buf64toh(const void *buf)
|
inline uint64_t buf64toh(const void *buf)
|
||||||
{
|
{
|
||||||
uint64_t b64;
|
uint64_t b64;
|
||||||
memcpy(&b64, buf, sizeof(uint64_t));
|
memcpy(&b64, buf, sizeof(uint64_t));
|
||||||
return b64;
|
return b64;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint16_t bufbe16toh(const void *buf)
|
inline uint16_t bufbe16toh(const void *buf)
|
||||||
{
|
{
|
||||||
return be16toh(buf16toh(buf));
|
return be16toh(buf16toh(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t bufbe32toh(const void *buf)
|
inline uint32_t bufbe32toh(const void *buf)
|
||||||
{
|
{
|
||||||
return be32toh(buf32toh(buf));
|
return be32toh(buf32toh(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint64_t bufbe64toh(const void *buf)
|
inline uint64_t bufbe64toh(const void *buf)
|
||||||
{
|
{
|
||||||
return be64toh(buf64toh(buf));
|
return be64toh(buf64toh(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void htobuf16(void *buf, uint16_t b16)
|
inline void htobuf16(void *buf, uint16_t b16)
|
||||||
{
|
{
|
||||||
memcpy(buf, &b16, sizeof(uint16_t));
|
memcpy(buf, &b16, sizeof(uint16_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void htobuf32(void *buf, uint32_t b32)
|
inline void htobuf32(void *buf, uint32_t b32)
|
||||||
{
|
{
|
||||||
memcpy(buf, &b32, sizeof(uint32_t));
|
memcpy(buf, &b32, sizeof(uint32_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void htobuf64(void *buf, uint64_t b64)
|
inline void htobuf64(void *buf, uint64_t b64)
|
||||||
{
|
{
|
||||||
memcpy(buf, &b64, sizeof(uint64_t));
|
memcpy(buf, &b64, sizeof(uint64_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void htobe16buf(void *buf, uint16_t big16)
|
inline void htobe16buf(void *buf, uint16_t big16)
|
||||||
{
|
{
|
||||||
htobuf16(buf, htobe16(big16));
|
htobuf16(buf, htobe16(big16));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void htobe32buf(void *buf, uint32_t big32)
|
inline void htobe32buf(void *buf, uint32_t big32)
|
||||||
{
|
{
|
||||||
htobuf32(buf, htobe32(big32));
|
htobuf32(buf, htobe32(big32));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void htobe64buf(void *buf, uint64_t big64)
|
inline void htobe64buf(void *buf, uint64_t big64)
|
||||||
{
|
{
|
||||||
htobuf64(buf, htobe64(big64));
|
htobuf64(buf, htobe64(big64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
211
I2PService.cpp
Normal file
211
I2PService.cpp
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
#include "Destination.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "I2PService.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256;
|
||||||
|
|
||||||
|
I2PService::I2PService (std::shared_ptr<ClientDestination> localDestination):
|
||||||
|
m_LocalDestination (localDestination ? localDestination :
|
||||||
|
i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PService::I2PService (i2p::data::SigningKeyType kt):
|
||||||
|
m_LocalDestination (i2p::client::context.CreateNewLocalDestination (false, kt))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) {
|
||||||
|
assert(streamRequestComplete);
|
||||||
|
i2p::data::IdentHash identHash;
|
||||||
|
if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash))
|
||||||
|
m_LocalDestination->CreateStream (streamRequestComplete, identHash, port);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "I2PService: Remote destination ", dest, " not found");
|
||||||
|
streamRequestComplete (nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) {}
|
||||||
|
|
||||||
|
TCPIPPipe::~TCPIPPipe()
|
||||||
|
{
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::Start()
|
||||||
|
{
|
||||||
|
AsyncReceiveUpstream();
|
||||||
|
AsyncReceiveDownstream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::Terminate()
|
||||||
|
{
|
||||||
|
if(Kill()) return;
|
||||||
|
Done(shared_from_this());
|
||||||
|
if (m_up) {
|
||||||
|
if (m_up->is_open()) {
|
||||||
|
m_up->close();
|
||||||
|
}
|
||||||
|
m_up = nullptr;
|
||||||
|
}
|
||||||
|
if (m_down) {
|
||||||
|
if (m_down->is_open()) {
|
||||||
|
m_down->close();
|
||||||
|
}
|
||||||
|
m_down = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::AsyncReceiveUpstream()
|
||||||
|
{
|
||||||
|
if (m_up) {
|
||||||
|
m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE),
|
||||||
|
std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "TCPIPPipe: no upstream socket for read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::AsyncReceiveDownstream()
|
||||||
|
{
|
||||||
|
if (m_down) {
|
||||||
|
m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE),
|
||||||
|
std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "TCPIPPipe: no downstream socket for read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::UpstreamWrite(const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (m_up) {
|
||||||
|
LogPrint(eLogDebug, "TCPIPPipe: write upstream ", (int)len);
|
||||||
|
boost::asio::async_write(*m_up, boost::asio::buffer(buf, len),
|
||||||
|
boost::asio::transfer_all(),
|
||||||
|
std::bind(&TCPIPPipe::HandleUpstreamWrite,
|
||||||
|
shared_from_this(),
|
||||||
|
std::placeholders::_1)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "tcpip pipe upstream socket null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::DownstreamWrite(const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (m_down) {
|
||||||
|
LogPrint(eLogDebug, "TCPIPPipe: write downstream ", (int)len);
|
||||||
|
boost::asio::async_write(*m_down, boost::asio::buffer(buf, len),
|
||||||
|
boost::asio::transfer_all(),
|
||||||
|
std::bind(&TCPIPPipe::HandleDownstreamWrite,
|
||||||
|
shared_from_this(),
|
||||||
|
std::placeholders::_1)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "tcpip pipe downstream socket null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "TCPIPPipe downstream got ", (int) bytes_transfered);
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint(eLogError, "TCPIPPipe Downstream read error:" , ecode.message());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate();
|
||||||
|
} else {
|
||||||
|
if (bytes_transfered > 0 ) {
|
||||||
|
memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered);
|
||||||
|
UpstreamWrite(m_upstream_buf, bytes_transfered);
|
||||||
|
}
|
||||||
|
AsyncReceiveDownstream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) {
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint(eLogError, "TCPIPPipe Downstream write error:" , ecode.message());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) {
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint(eLogError, "TCPIPPipe Upstream write error:" , ecode.message());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "TCPIPPipe upstream got ", (int) bytes_transfered);
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint(eLogError, "TCPIPPipe Upstream read error:" , ecode.message());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate();
|
||||||
|
} else {
|
||||||
|
if (bytes_transfered > 0 ) {
|
||||||
|
memcpy(m_upstream_buf, m_upstream_to_down_buf, bytes_transfered);
|
||||||
|
DownstreamWrite(m_upstream_buf, bytes_transfered);
|
||||||
|
}
|
||||||
|
AsyncReceiveUpstream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPAcceptor::Start ()
|
||||||
|
{
|
||||||
|
m_Acceptor.listen ();
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPAcceptor::Stop ()
|
||||||
|
{
|
||||||
|
m_Acceptor.close();
|
||||||
|
m_Timer.cancel ();
|
||||||
|
ClearHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPAcceptor::Accept ()
|
||||||
|
{
|
||||||
|
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
|
||||||
|
m_Acceptor.async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this,
|
||||||
|
std::placeholders::_1, newSocket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted");
|
||||||
|
auto handler = CreateHandler(socket);
|
||||||
|
if (handler)
|
||||||
|
{
|
||||||
|
AddHandler(handler);
|
||||||
|
handler->Handle();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
socket->close();
|
||||||
|
Accept();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
136
I2PService.h
Normal file
136
I2PService.h
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#ifndef I2PSERVICE_H__
|
||||||
|
#define I2PSERVICE_H__
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
class I2PServiceHandler;
|
||||||
|
class I2PService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
I2PService (std::shared_ptr<ClientDestination> localDestination = nullptr);
|
||||||
|
I2PService (i2p::data::SigningKeyType kt);
|
||||||
|
virtual ~I2PService () { ClearHandlers (); }
|
||||||
|
|
||||||
|
inline void AddHandler (std::shared_ptr<I2PServiceHandler> conn)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_HandlersMutex);
|
||||||
|
m_Handlers.insert(conn);
|
||||||
|
}
|
||||||
|
inline void RemoveHandler (std::shared_ptr<I2PServiceHandler> conn)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_HandlersMutex);
|
||||||
|
m_Handlers.erase(conn);
|
||||||
|
}
|
||||||
|
inline void ClearHandlers ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_HandlersMutex);
|
||||||
|
m_Handlers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::shared_ptr<ClientDestination> GetLocalDestination () { return m_LocalDestination; }
|
||||||
|
inline void SetLocalDestination (std::shared_ptr<ClientDestination> dest) { m_LocalDestination = dest; }
|
||||||
|
void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0);
|
||||||
|
|
||||||
|
inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }
|
||||||
|
|
||||||
|
virtual void Start () = 0;
|
||||||
|
virtual void Stop () = 0;
|
||||||
|
|
||||||
|
virtual const char* GetName() { return "Generic I2P Service"; }
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<ClientDestination> m_LocalDestination;
|
||||||
|
std::unordered_set<std::shared_ptr<I2PServiceHandler> > m_Handlers;
|
||||||
|
std::mutex m_HandlersMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*Simple interface for I2PHandlers, allows detection of finalization amongst other things */
|
||||||
|
class I2PServiceHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { }
|
||||||
|
virtual ~I2PServiceHandler() { }
|
||||||
|
//If you override this make sure you call it from the children
|
||||||
|
virtual void Handle() {}; //Start handling the socket
|
||||||
|
protected:
|
||||||
|
// Call when terminating or handing over to avoid race conditions
|
||||||
|
inline bool Kill () { return m_Dead.exchange(true); }
|
||||||
|
// Call to know if the handler is dead
|
||||||
|
inline bool Dead () { return m_Dead; }
|
||||||
|
// Call when done to clean up (make sure Kill is called first)
|
||||||
|
inline void Done (std::shared_ptr<I2PServiceHandler> me) { if(m_Service) m_Service->RemoveHandler(me); }
|
||||||
|
// Call to talk with the owner
|
||||||
|
inline I2PService * GetOwner() { return m_Service; }
|
||||||
|
private:
|
||||||
|
I2PService *m_Service;
|
||||||
|
std::atomic<bool> m_Dead; //To avoid cleaning up multiple times
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
// bidirectional pipe for 2 tcp/ip sockets
|
||||||
|
class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this<TCPIPPipe> {
|
||||||
|
public:
|
||||||
|
TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream);
|
||||||
|
~TCPIPPipe();
|
||||||
|
void Start();
|
||||||
|
protected:
|
||||||
|
void Terminate();
|
||||||
|
void AsyncReceiveUpstream();
|
||||||
|
void AsyncReceiveDownstream();
|
||||||
|
void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
|
||||||
|
void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
|
||||||
|
void HandleUpstreamWrite(const boost::system::error_code & ecode);
|
||||||
|
void HandleDownstreamWrite(const boost::system::error_code & ecode);
|
||||||
|
void UpstreamWrite(const uint8_t * buf, size_t len);
|
||||||
|
void DownstreamWrite(const uint8_t * buf, size_t len);
|
||||||
|
private:
|
||||||
|
uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE];
|
||||||
|
uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE];
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> m_up, m_down;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* TODO: support IPv6 too */
|
||||||
|
//This is a service that listens for connections on the IP network and interacts with I2P
|
||||||
|
class TCPIPAcceptor: public I2PService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TCPIPAcceptor (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination = nullptr) :
|
||||||
|
I2PService(localDestination),
|
||||||
|
m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
|
||||||
|
m_Timer (GetService ()) {}
|
||||||
|
TCPIPAcceptor (const std::string& address, int port, i2p::data::SigningKeyType kt) :
|
||||||
|
I2PService(kt),
|
||||||
|
m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
|
||||||
|
m_Timer (GetService ()) {}
|
||||||
|
virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); }
|
||||||
|
//If you override this make sure you call it from the children
|
||||||
|
void Start ();
|
||||||
|
//If you override this make sure you call it from the children
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
const boost::asio::ip::tcp::acceptor& GetAcceptor () const { return m_Acceptor; };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket) = 0;
|
||||||
|
virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; }
|
||||||
|
private:
|
||||||
|
void Accept();
|
||||||
|
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||||
|
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||||
|
boost::asio::deadline_timer m_Timer;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
503
I2PTunnel.cpp
Normal file
503
I2PTunnel.cpp
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "I2PTunnel.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port):
|
||||||
|
I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()),
|
||||||
|
m_IsQuiet (true)
|
||||||
|
{
|
||||||
|
m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<i2p::stream::Stream> stream):
|
||||||
|
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
|
||||||
|
m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> socket, const boost::asio::ip::tcp::endpoint& target, bool quiet):
|
||||||
|
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
|
||||||
|
m_RemoteEndpoint (target), m_IsQuiet (quiet)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PTunnelConnection::~I2PTunnelConnection ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len)
|
||||||
|
{
|
||||||
|
if (m_Stream)
|
||||||
|
{
|
||||||
|
if (msg)
|
||||||
|
m_Stream->Send (msg, len); // connect and send
|
||||||
|
else
|
||||||
|
m_Stream->Send (m_Buffer, 0); // connect
|
||||||
|
}
|
||||||
|
StreamReceive ();
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::Connect ()
|
||||||
|
{
|
||||||
|
if (m_Socket)
|
||||||
|
m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::Terminate ()
|
||||||
|
{
|
||||||
|
if (Kill()) return;
|
||||||
|
if (m_Stream)
|
||||||
|
{
|
||||||
|
m_Stream->Close ();
|
||||||
|
m_Stream.reset ();
|
||||||
|
}
|
||||||
|
m_Socket->close ();
|
||||||
|
Done(shared_from_this ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::Receive ()
|
||||||
|
{
|
||||||
|
m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
|
||||||
|
std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PTunnel: read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_Stream)
|
||||||
|
{
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
m_Stream->AsyncSend (m_Buffer, bytes_transferred,
|
||||||
|
[s](const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
s->Receive ();
|
||||||
|
else
|
||||||
|
s->Terminate ();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PTunnel: write error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
StreamReceive ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::StreamReceive ()
|
||||||
|
{
|
||||||
|
if (m_Stream)
|
||||||
|
{
|
||||||
|
if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew ||
|
||||||
|
m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular
|
||||||
|
{
|
||||||
|
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
|
||||||
|
std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2),
|
||||||
|
I2P_TUNNEL_CONNECTION_MAX_IDLE);
|
||||||
|
}
|
||||||
|
else // closed by peer
|
||||||
|
{
|
||||||
|
// get remaning data
|
||||||
|
auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE);
|
||||||
|
if (len > 0) // still some data
|
||||||
|
Write (m_StreamBuffer, len);
|
||||||
|
else // no more data
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
if (bytes_transferred > 0)
|
||||||
|
Write (m_StreamBuffer, bytes_transferred); // postpone termination
|
||||||
|
else
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Write (m_StreamBuffer, bytes_transferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::Write (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (),
|
||||||
|
std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PTunnel: connect error: ", ecode.message ());
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "I2PTunnel: connected");
|
||||||
|
if (m_IsQuiet)
|
||||||
|
StreamReceive ();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// send destination first like received from I2P
|
||||||
|
std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 ();
|
||||||
|
dest += "\n";
|
||||||
|
memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
|
||||||
|
HandleStreamReceive (boost::system::error_code (), dest.size ());
|
||||||
|
}
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
const boost::asio::ip::tcp::endpoint& target, const std::string& host):
|
||||||
|
I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false), m_From (stream->GetRemoteIdentity ())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (m_HeaderSent)
|
||||||
|
I2PTunnelConnection::Write (buf, len);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_InHeader.clear ();
|
||||||
|
m_InHeader.write ((const char *)buf, len);
|
||||||
|
std::string line;
|
||||||
|
bool endOfHeader = false;
|
||||||
|
while (!endOfHeader)
|
||||||
|
{
|
||||||
|
std::getline(m_InHeader, line);
|
||||||
|
if (!m_InHeader.fail ())
|
||||||
|
{
|
||||||
|
if (line == "\r") endOfHeader = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (line.find ("Host:") != std::string::npos)
|
||||||
|
m_OutHeader << "Host: " << m_Host << "\r\n";
|
||||||
|
else
|
||||||
|
m_OutHeader << line << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// add X-I2P fields
|
||||||
|
if (m_From)
|
||||||
|
{
|
||||||
|
m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n";
|
||||||
|
m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n";
|
||||||
|
m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endOfHeader)
|
||||||
|
{
|
||||||
|
m_OutHeader << "\r\n"; // end of header
|
||||||
|
m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header
|
||||||
|
m_HeaderSent = true;
|
||||||
|
I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass):
|
||||||
|
I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()),
|
||||||
|
m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (m_NeedsWebIrc) {
|
||||||
|
m_NeedsWebIrc = false;
|
||||||
|
m_OutPacket.str ("");
|
||||||
|
m_OutPacket << "WEBIRC " << this->m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " 127.0.0.1\n";
|
||||||
|
I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
m_OutPacket.str ("");
|
||||||
|
m_InPacket.clear ();
|
||||||
|
m_InPacket.write ((const char *)buf, len);
|
||||||
|
|
||||||
|
while (!m_InPacket.eof () && !m_InPacket.fail ())
|
||||||
|
{
|
||||||
|
std::getline (m_InPacket, line);
|
||||||
|
if (line.length () == 0 && m_InPacket.eof ()) {
|
||||||
|
m_InPacket.str ("");
|
||||||
|
}
|
||||||
|
auto pos = line.find ("USER");
|
||||||
|
if (pos != std::string::npos && pos == 0)
|
||||||
|
{
|
||||||
|
pos = line.find (" ");
|
||||||
|
pos++;
|
||||||
|
pos = line.find (" ", pos);
|
||||||
|
pos++;
|
||||||
|
auto nextpos = line.find (" ", pos);
|
||||||
|
m_OutPacket << line.substr (0, pos);
|
||||||
|
m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ());
|
||||||
|
m_OutPacket << line.substr (nextpos) << '\n';
|
||||||
|
} else {
|
||||||
|
m_OutPacket << line << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* This handler tries to stablish a connection with the desired server and dies if it fails to do so */
|
||||||
|
class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this<I2PClientTunnelHandler>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination,
|
||||||
|
int destinationPort, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
|
||||||
|
I2PServiceHandler(parent), m_DestinationIdentHash(destination),
|
||||||
|
m_DestinationPort (destinationPort), m_Socket(socket) {};
|
||||||
|
void Handle();
|
||||||
|
void Terminate();
|
||||||
|
private:
|
||||||
|
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
i2p::data::IdentHash m_DestinationIdentHash;
|
||||||
|
int m_DestinationPort;
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
void I2PClientTunnelHandler::Handle()
|
||||||
|
{
|
||||||
|
GetOwner()->GetLocalDestination ()->CreateStream (
|
||||||
|
std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1),
|
||||||
|
m_DestinationIdentHash, m_DestinationPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
if (Kill()) return;
|
||||||
|
LogPrint (eLogDebug, "I2PTunnel: new connection");
|
||||||
|
auto connection = std::make_shared<I2PTunnelConnection>(GetOwner(), m_Socket, stream);
|
||||||
|
GetOwner()->AddHandler (connection);
|
||||||
|
connection->I2PConnect ();
|
||||||
|
Done(shared_from_this());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "I2PTunnel: Client Tunnel Issue when creating the stream, check the previous warnings for more info.");
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PClientTunnelHandler::Terminate()
|
||||||
|
{
|
||||||
|
if (Kill()) return;
|
||||||
|
if (m_Socket)
|
||||||
|
{
|
||||||
|
m_Socket->close();
|
||||||
|
m_Socket = nullptr;
|
||||||
|
}
|
||||||
|
Done(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination,
|
||||||
|
const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, int destinationPort):
|
||||||
|
TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination),
|
||||||
|
m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PClientTunnel::Start ()
|
||||||
|
{
|
||||||
|
TCPIPAcceptor::Start ();
|
||||||
|
GetIdentHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PClientTunnel::Stop ()
|
||||||
|
{
|
||||||
|
TCPIPAcceptor::Stop();
|
||||||
|
auto *originalIdentHash = m_DestinationIdentHash;
|
||||||
|
m_DestinationIdentHash = nullptr;
|
||||||
|
delete originalIdentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HACK: maybe we should create a caching IdentHash provider in AddressBook */
|
||||||
|
const i2p::data::IdentHash * I2PClientTunnel::GetIdentHash ()
|
||||||
|
{
|
||||||
|
if (!m_DestinationIdentHash)
|
||||||
|
{
|
||||||
|
i2p::data::IdentHash identHash;
|
||||||
|
if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash))
|
||||||
|
m_DestinationIdentHash = new i2p::data::IdentHash (identHash);
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "I2PTunnel: Remote destination ", m_Destination, " not found");
|
||||||
|
}
|
||||||
|
return m_DestinationIdentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2PServiceHandler> I2PClientTunnel::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||||
|
{
|
||||||
|
const i2p::data::IdentHash *identHash = GetIdentHash();
|
||||||
|
if (identHash)
|
||||||
|
return std::make_shared<I2PClientTunnelHandler>(this, *identHash, m_DestinationPort, socket);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address,
|
||||||
|
int port, std::shared_ptr<ClientDestination> localDestination, int inport, bool gzip):
|
||||||
|
I2PService (localDestination), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false)
|
||||||
|
{
|
||||||
|
m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnel::Start ()
|
||||||
|
{
|
||||||
|
m_Endpoint.port (m_Port);
|
||||||
|
boost::system::error_code ec;
|
||||||
|
auto addr = boost::asio::ip::address::from_string (m_Address, ec);
|
||||||
|
if (!ec)
|
||||||
|
{
|
||||||
|
m_Endpoint.address (addr);
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(GetService ());
|
||||||
|
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""),
|
||||||
|
std::bind (&I2PServerTunnel::HandleResolve, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, resolver));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnel::Stop ()
|
||||||
|
{
|
||||||
|
ClearHandlers ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
auto addr = (*it).endpoint ().address ();
|
||||||
|
LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr);
|
||||||
|
m_Endpoint.address (addr);
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnel::SetAccessList (const std::set<i2p::data::IdentHash>& accessList)
|
||||||
|
{
|
||||||
|
m_AccessList = accessList;
|
||||||
|
m_IsAccessList = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnel::Accept ()
|
||||||
|
{
|
||||||
|
if (m_PortDestination)
|
||||||
|
m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
|
||||||
|
|
||||||
|
auto localDestination = GetLocalDestination ();
|
||||||
|
if (localDestination)
|
||||||
|
{
|
||||||
|
if (!localDestination->IsAcceptingStreams ()) // set it as default if not set yet
|
||||||
|
localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "I2PTunnel: Local destination not set for server tunnel");
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
if (m_IsAccessList)
|
||||||
|
{
|
||||||
|
if (!m_AccessList.count (stream->GetRemoteIdentity ()->GetIdentHash ()))
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "I2PTunnel: Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped");
|
||||||
|
stream->Close ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CreateI2PConnection (stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint ());
|
||||||
|
AddHandler (conn);
|
||||||
|
conn->Connect ();
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address,
|
||||||
|
int port, std::shared_ptr<ClientDestination> localDestination,
|
||||||
|
const std::string& host, int inport, bool gzip):
|
||||||
|
I2PServerTunnel (name, address, port, localDestination, inport, gzip),
|
||||||
|
m_Host (host.length () > 0 ? host : address)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
auto conn = std::make_shared<I2PTunnelConnectionHTTP> (this, stream,
|
||||||
|
std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), m_Host);
|
||||||
|
AddHandler (conn);
|
||||||
|
conn->Connect ();
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address,
|
||||||
|
int port, std::shared_ptr<ClientDestination> localDestination,
|
||||||
|
const std::string& webircpass, int inport, bool gzip):
|
||||||
|
I2PServerTunnel (name, address, port, localDestination, inport, gzip),
|
||||||
|
m_WebircPass (webircpass)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
auto conn = std::make_shared<I2PTunnelConnectionIRC> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), this->m_WebircPass);
|
||||||
|
AddHandler (conn);
|
||||||
|
conn->Connect ();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
208
I2PTunnel.h
Normal file
208
I2PTunnel.h
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
#ifndef I2PTUNNEL_H__
|
||||||
|
#define I2PTUNNEL_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
#include "I2PService.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 8192;
|
||||||
|
const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds
|
||||||
|
const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
|
||||||
|
// for HTTP tunnels
|
||||||
|
const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64
|
||||||
|
const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64
|
||||||
|
const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address
|
||||||
|
|
||||||
|
class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this<I2PTunnelConnection>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port = 0); // to I2P
|
||||||
|
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API
|
||||||
|
I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P
|
||||||
|
~I2PTunnelConnection ();
|
||||||
|
void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0);
|
||||||
|
void Connect ();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void Terminate ();
|
||||||
|
|
||||||
|
void Receive ();
|
||||||
|
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
virtual void Write (const uint8_t * buf, size_t len); // can be overloaded
|
||||||
|
void HandleWrite (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
void StreamReceive ();
|
||||||
|
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandleConnect (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE];
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
|
||||||
|
std::shared_ptr<i2p::stream::Stream> m_Stream;
|
||||||
|
boost::asio::ip::tcp::endpoint m_RemoteEndpoint;
|
||||||
|
bool m_IsQuiet; // don't send destination
|
||||||
|
};
|
||||||
|
|
||||||
|
class I2PTunnelConnectionHTTP: public I2PTunnelConnection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
const boost::asio::ip::tcp::endpoint& target, const std::string& host);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void Write (const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string m_Host;
|
||||||
|
std::stringstream m_InHeader, m_OutHeader;
|
||||||
|
bool m_HeaderSent;
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> m_From;
|
||||||
|
};
|
||||||
|
|
||||||
|
class I2PTunnelConnectionIRC: public I2PTunnelConnection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
|
||||||
|
const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void Write (const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> m_From;
|
||||||
|
std::stringstream m_OutPacket, m_InPacket;
|
||||||
|
bool m_NeedsWebIrc;
|
||||||
|
std::string m_WebircPass;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class I2PClientTunnel: public TCPIPAcceptor
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// Implements TCPIPAcceptor
|
||||||
|
std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PClientTunnel (const std::string& name, const std::string& destination,
|
||||||
|
const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, int destinationPort = 0);
|
||||||
|
~I2PClientTunnel () {}
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
const char* GetName() { return m_Name.c_str (); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const i2p::data::IdentHash * GetIdentHash ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string m_Name, m_Destination;
|
||||||
|
const i2p::data::IdentHash * m_DestinationIdentHash;
|
||||||
|
int m_DestinationPort;
|
||||||
|
};
|
||||||
|
|
||||||
|
class I2PServerTunnel: public I2PService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PServerTunnel (const std::string& name, const std::string& address, int port,
|
||||||
|
std::shared_ptr<ClientDestination> localDestination, int inport = 0, bool gzip = true);
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
void SetAccessList (const std::set<i2p::data::IdentHash>& accessList);
|
||||||
|
|
||||||
|
const std::string& GetAddress() const { return m_Address; }
|
||||||
|
int GetPort () const { return m_Port; };
|
||||||
|
uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); };
|
||||||
|
const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; }
|
||||||
|
|
||||||
|
const char* GetName() { return m_Name.c_str (); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
|
||||||
|
|
||||||
|
void Accept ();
|
||||||
|
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
virtual void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string m_Name, m_Address;
|
||||||
|
int m_Port;
|
||||||
|
boost::asio::ip::tcp::endpoint m_Endpoint;
|
||||||
|
std::shared_ptr<i2p::stream::StreamingDestination> m_PortDestination;
|
||||||
|
std::set<i2p::data::IdentHash> m_AccessList;
|
||||||
|
bool m_IsAccessList;
|
||||||
|
};
|
||||||
|
|
||||||
|
class I2PServerTunnelHTTP: public I2PServerTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port,
|
||||||
|
std::shared_ptr<ClientDestination> localDestination, const std::string& host,
|
||||||
|
int inport = 0, bool gzip = true);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string m_Host;
|
||||||
|
};
|
||||||
|
|
||||||
|
class I2PServerTunnelIRC: public I2PServerTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2PServerTunnelIRC (const std::string& name, const std::string& address, int port,
|
||||||
|
std::shared_ptr<ClientDestination> localDestination, const std::string& webircpass,
|
||||||
|
int inport = 0, bool gzip = true);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string m_WebircPass;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
577
Identity.cpp
Normal file
577
Identity.cpp
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
Identity& Identity::operator=(const Keys& keys)
|
||||||
|
{
|
||||||
|
// copy public and signing keys together
|
||||||
|
memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey));
|
||||||
|
memset (certificate, 0, sizeof (certificate));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if ( len < DEFAULT_IDENTITY_SIZE ) {
|
||||||
|
// buffer too small, don't overflow
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
|
||||||
|
return DEFAULT_IDENTITY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentHash Identity::Hash () const
|
||||||
|
{
|
||||||
|
IdentHash hash;
|
||||||
|
SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx::IdentityEx ():
|
||||||
|
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type)
|
||||||
|
{
|
||||||
|
memcpy (m_StandardIdentity.publicKey, publicKey, sizeof (m_StandardIdentity.publicKey));
|
||||||
|
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
|
||||||
|
{
|
||||||
|
size_t excessLen = 0;
|
||||||
|
uint8_t * excessBuf = nullptr;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||||
|
{
|
||||||
|
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
|
||||||
|
RAND_bytes (m_StandardIdentity.signingKey, padding);
|
||||||
|
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||||
|
{
|
||||||
|
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
|
||||||
|
RAND_bytes (m_StandardIdentity.signingKey, padding);
|
||||||
|
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP384_KEY_LENGTH);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||||
|
{
|
||||||
|
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||||
|
excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132 - 128
|
||||||
|
excessBuf = new uint8_t[excessLen];
|
||||||
|
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||||
|
{
|
||||||
|
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||||
|
excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256 - 128
|
||||||
|
excessBuf = new uint8_t[excessLen];
|
||||||
|
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||||
|
{
|
||||||
|
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||||
|
excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384 - 128
|
||||||
|
excessBuf = new uint8_t[excessLen];
|
||||||
|
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||||
|
{
|
||||||
|
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||||
|
excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512 - 128
|
||||||
|
excessBuf = new uint8_t[excessLen];
|
||||||
|
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||||
|
{
|
||||||
|
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
|
||||||
|
RAND_bytes (m_StandardIdentity.signingKey, padding);
|
||||||
|
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported");
|
||||||
|
}
|
||||||
|
m_ExtendedLen = 4 + excessLen; // 4 bytes extra + excess length
|
||||||
|
// fill certificate
|
||||||
|
m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY;
|
||||||
|
htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen);
|
||||||
|
// fill extended buffer
|
||||||
|
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||||
|
htobe16buf (m_ExtendedBuffer, type);
|
||||||
|
htobe16buf (m_ExtendedBuffer + 2, CRYPTO_KEY_TYPE_ELGAMAL);
|
||||||
|
if (excessLen && excessBuf)
|
||||||
|
{
|
||||||
|
memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
|
||||||
|
delete[] excessBuf;
|
||||||
|
}
|
||||||
|
// calculate ident hash
|
||||||
|
uint8_t * buf = new uint8_t[GetFullLen ()];
|
||||||
|
ToBuffer (buf, GetFullLen ());
|
||||||
|
SHA256(buf, GetFullLen (), m_IdentHash);
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
else // DSA-SHA1
|
||||||
|
{
|
||||||
|
memcpy (m_StandardIdentity.signingKey, signingKey, sizeof (m_StandardIdentity.signingKey));
|
||||||
|
memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate));
|
||||||
|
m_IdentHash = m_StandardIdentity.Hash ();
|
||||||
|
m_ExtendedLen = 0;
|
||||||
|
m_ExtendedBuffer = nullptr;
|
||||||
|
}
|
||||||
|
CreateVerifier ();
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx::IdentityEx (const uint8_t * buf, size_t len):
|
||||||
|
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||||
|
{
|
||||||
|
FromBuffer (buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx::IdentityEx (const IdentityEx& other):
|
||||||
|
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||||
|
{
|
||||||
|
*this = other;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx::IdentityEx (const Identity& standard):
|
||||||
|
m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||||
|
{
|
||||||
|
*this = standard;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx::~IdentityEx ()
|
||||||
|
{
|
||||||
|
delete[] m_ExtendedBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx& IdentityEx::operator=(const IdentityEx& other)
|
||||||
|
{
|
||||||
|
memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
|
||||||
|
m_IdentHash = other.m_IdentHash;
|
||||||
|
|
||||||
|
delete[] m_ExtendedBuffer;
|
||||||
|
m_ExtendedLen = other.m_ExtendedLen;
|
||||||
|
if (m_ExtendedLen > 0)
|
||||||
|
{
|
||||||
|
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||||
|
memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_ExtendedBuffer = nullptr;
|
||||||
|
|
||||||
|
m_Verifier = nullptr;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityEx& IdentityEx::operator=(const Identity& standard)
|
||||||
|
{
|
||||||
|
m_StandardIdentity = standard;
|
||||||
|
m_IdentHash = m_StandardIdentity.Hash ();
|
||||||
|
|
||||||
|
delete[] m_ExtendedBuffer;
|
||||||
|
m_ExtendedBuffer = nullptr;
|
||||||
|
m_ExtendedLen = 0;
|
||||||
|
|
||||||
|
m_Verifier = nullptr;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IdentityEx::FromBuffer (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (len < DEFAULT_IDENTITY_SIZE)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Identity: buffer length ", len, " is too small");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE);
|
||||||
|
|
||||||
|
delete[] m_ExtendedBuffer; m_ExtendedBuffer = nullptr;
|
||||||
|
m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1);
|
||||||
|
if (m_ExtendedLen)
|
||||||
|
{
|
||||||
|
if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len)
|
||||||
|
{
|
||||||
|
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||||
|
memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Identity: Certificate length ", m_ExtendedLen, " exceeds buffer length ", len - DEFAULT_IDENTITY_SIZE);
|
||||||
|
m_ExtendedLen = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ExtendedLen = 0;
|
||||||
|
m_ExtendedBuffer = nullptr;
|
||||||
|
}
|
||||||
|
SHA256(buf, GetFullLen (), m_IdentHash);
|
||||||
|
|
||||||
|
m_Verifier = nullptr;
|
||||||
|
|
||||||
|
return GetFullLen ();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const
|
||||||
|
{
|
||||||
|
const size_t fullLen = GetFullLen();
|
||||||
|
if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else
|
||||||
|
memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
|
||||||
|
if (m_ExtendedLen > 0 && m_ExtendedBuffer)
|
||||||
|
memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
|
||||||
|
return fullLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IdentityEx::FromBase64(const std::string& s)
|
||||||
|
{
|
||||||
|
const size_t slen = s.length();
|
||||||
|
std::vector<uint8_t> buf(slen); // binary data can't exceed base64
|
||||||
|
const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen);
|
||||||
|
return FromBuffer (buf.data(), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IdentityEx::ToBase64 () const
|
||||||
|
{
|
||||||
|
const size_t bufLen = GetFullLen();
|
||||||
|
const size_t strLen = Base64EncodingBufferSize(bufLen);
|
||||||
|
std::vector<uint8_t> buf(bufLen);
|
||||||
|
std::vector<char> str(strLen);
|
||||||
|
size_t l = ToBuffer (buf.data(), bufLen);
|
||||||
|
size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen);
|
||||||
|
return std::string (str.data(), l1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IdentityEx::GetSigningPublicKeyLen () const
|
||||||
|
{
|
||||||
|
if (!m_Verifier) CreateVerifier ();
|
||||||
|
if (m_Verifier)
|
||||||
|
return m_Verifier->GetPublicKeyLen ();
|
||||||
|
return 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IdentityEx::GetSigningPrivateKeyLen () const
|
||||||
|
{
|
||||||
|
if (!m_Verifier) CreateVerifier ();
|
||||||
|
if (m_Verifier)
|
||||||
|
return m_Verifier->GetPrivateKeyLen ();
|
||||||
|
return GetSignatureLen ()/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IdentityEx::GetSignatureLen () const
|
||||||
|
{
|
||||||
|
if (!m_Verifier) CreateVerifier ();
|
||||||
|
if (m_Verifier)
|
||||||
|
return m_Verifier->GetSignatureLen ();
|
||||||
|
return i2p::crypto::DSA_SIGNATURE_LENGTH;
|
||||||
|
}
|
||||||
|
bool IdentityEx::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||||
|
{
|
||||||
|
if (!m_Verifier) CreateVerifier ();
|
||||||
|
if (m_Verifier)
|
||||||
|
return m_Verifier->Verify (buf, len, signature);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SigningKeyType IdentityEx::GetSigningKeyType () const
|
||||||
|
{
|
||||||
|
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer)
|
||||||
|
return bufbe16toh (m_ExtendedBuffer); // signing key
|
||||||
|
return SIGNING_KEY_TYPE_DSA_SHA1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptoKeyType IdentityEx::GetCryptoKeyType () const
|
||||||
|
{
|
||||||
|
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer)
|
||||||
|
return bufbe16toh (m_ExtendedBuffer + 2); // crypto key
|
||||||
|
return CRYPTO_KEY_TYPE_ELGAMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityEx::CreateVerifier () const
|
||||||
|
{
|
||||||
|
auto keyType = GetSigningKeyType ();
|
||||||
|
switch (keyType)
|
||||||
|
{
|
||||||
|
case SIGNING_KEY_TYPE_DSA_SHA1:
|
||||||
|
UpdateVerifier (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||||
|
{
|
||||||
|
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
|
||||||
|
UpdateVerifier (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||||
|
{
|
||||||
|
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
|
||||||
|
UpdateVerifier (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||||
|
{
|
||||||
|
uint8_t signingKey[i2p::crypto::ECDSAP521_KEY_LENGTH];
|
||||||
|
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||||
|
size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128
|
||||||
|
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||||
|
UpdateVerifier (new i2p::crypto::ECDSAP521Verifier (signingKey));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||||
|
{
|
||||||
|
uint8_t signingKey[i2p::crypto::RSASHA2562048_KEY_LENGTH];
|
||||||
|
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||||
|
size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128
|
||||||
|
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||||
|
UpdateVerifier (new i2p::crypto:: RSASHA2562048Verifier (signingKey));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||||
|
{
|
||||||
|
uint8_t signingKey[i2p::crypto::RSASHA3843072_KEY_LENGTH];
|
||||||
|
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||||
|
size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128
|
||||||
|
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||||
|
UpdateVerifier (new i2p::crypto:: RSASHA3843072Verifier (signingKey));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||||
|
{
|
||||||
|
uint8_t signingKey[i2p::crypto::RSASHA5124096_KEY_LENGTH];
|
||||||
|
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||||
|
size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128
|
||||||
|
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||||
|
UpdateVerifier (new i2p::crypto:: RSASHA5124096Verifier (signingKey));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||||
|
{
|
||||||
|
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
|
||||||
|
UpdateVerifier (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const
|
||||||
|
{
|
||||||
|
if (!m_Verifier || !verifier)
|
||||||
|
m_Verifier.reset (verifier);
|
||||||
|
else
|
||||||
|
delete verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityEx::DropVerifier () const
|
||||||
|
{
|
||||||
|
// TODO: potential race condition with Verify
|
||||||
|
m_Verifier = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateKeys& PrivateKeys::operator=(const Keys& keys)
|
||||||
|
{
|
||||||
|
m_Public = std::make_shared<IdentityEx>(Identity (keys));
|
||||||
|
memcpy (m_PrivateKey, keys.privateKey, 256); // 256
|
||||||
|
memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ());
|
||||||
|
m_Signer = nullptr;
|
||||||
|
CreateSigner ();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateKeys& PrivateKeys::operator=(const PrivateKeys& other)
|
||||||
|
{
|
||||||
|
m_Public = std::make_shared<IdentityEx>(*other.m_Public);
|
||||||
|
memcpy (m_PrivateKey, other.m_PrivateKey, 256); // 256
|
||||||
|
memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_Public->GetSigningPrivateKeyLen ());
|
||||||
|
m_Signer = nullptr;
|
||||||
|
CreateSigner ();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
m_Public = std::make_shared<IdentityEx>(buf, len);
|
||||||
|
size_t ret = m_Public->GetFullLen ();
|
||||||
|
memcpy (m_PrivateKey, buf + ret, 256); // private key always 256
|
||||||
|
ret += 256;
|
||||||
|
size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
|
||||||
|
memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
|
||||||
|
ret += signingPrivateKeySize;
|
||||||
|
m_Signer = nullptr;
|
||||||
|
CreateSigner ();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const
|
||||||
|
{
|
||||||
|
size_t ret = m_Public->ToBuffer (buf, len);
|
||||||
|
memcpy (buf + ret, m_PrivateKey, 256); // private key always 256
|
||||||
|
ret += 256;
|
||||||
|
size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
|
||||||
|
memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
|
||||||
|
ret += signingPrivateKeySize;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PrivateKeys::FromBase64(const std::string& s)
|
||||||
|
{
|
||||||
|
uint8_t * buf = new uint8_t[s.length ()];
|
||||||
|
size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
|
||||||
|
size_t ret = FromBuffer (buf, l);
|
||||||
|
delete[] buf;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PrivateKeys::ToBase64 () const
|
||||||
|
{
|
||||||
|
uint8_t * buf = new uint8_t[GetFullLen ()];
|
||||||
|
char * str = new char[GetFullLen ()*2];
|
||||||
|
size_t l = ToBuffer (buf, GetFullLen ());
|
||||||
|
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, GetFullLen ()*2);
|
||||||
|
str[l1] = 0;
|
||||||
|
delete[] buf;
|
||||||
|
std::string ret(str);
|
||||||
|
delete[] str;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||||
|
{
|
||||||
|
if (m_Signer)
|
||||||
|
m_Signer->Sign (buf, len, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrivateKeys::CreateSigner ()
|
||||||
|
{
|
||||||
|
switch (m_Public->GetSigningKeyType ())
|
||||||
|
{
|
||||||
|
case SIGNING_KEY_TYPE_DSA_SHA1:
|
||||||
|
m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||||
|
m_Signer.reset (new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||||
|
m_Signer.reset (new i2p::crypto::ECDSAP384Signer (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||||
|
m_Signer.reset (new i2p::crypto::ECDSAP521Signer (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||||
|
m_Signer.reset (new i2p::crypto::RSASHA2562048Signer (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||||
|
m_Signer.reset (new i2p::crypto::RSASHA3843072Signer (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||||
|
m_Signer.reset (new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||||
|
m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Identity: Signing key type ", (int)m_Public->GetSigningKeyType (), " is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type)
|
||||||
|
{
|
||||||
|
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
|
||||||
|
{
|
||||||
|
PrivateKeys keys;
|
||||||
|
// signature
|
||||||
|
uint8_t signingPublicKey[512]; // signing public key is 512 bytes max
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||||
|
i2p::crypto::CreateECDSAP256RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||||
|
i2p::crypto::CreateECDSAP384RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||||
|
i2p::crypto::CreateECDSAP521RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||||
|
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA2562048_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||||
|
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA3843072_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||||
|
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA5124096_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
|
||||||
|
break;
|
||||||
|
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||||
|
i2p::crypto::CreateEDDSA25519RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
|
||||||
|
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
|
||||||
|
}
|
||||||
|
// encryption
|
||||||
|
uint8_t publicKey[256];
|
||||||
|
i2p::crypto::GenerateElGamalKeyPair (keys.m_PrivateKey, publicKey);
|
||||||
|
// identity
|
||||||
|
keys.m_Public = std::make_shared<IdentityEx> (publicKey, signingPublicKey, type);
|
||||||
|
|
||||||
|
keys.CreateSigner ();
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys CreateRandomKeys ()
|
||||||
|
{
|
||||||
|
Keys keys;
|
||||||
|
// encryption
|
||||||
|
i2p::crypto::GenerateElGamalKeyPair(keys.privateKey, keys.publicKey);
|
||||||
|
// signing
|
||||||
|
i2p::crypto::CreateDSARandomKeys (keys.signingPrivateKey, keys.signingKey);
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentHash CreateRoutingKey (const IdentHash& ident)
|
||||||
|
{
|
||||||
|
uint8_t buf[41]; // ident + yyyymmdd
|
||||||
|
memcpy (buf, (const uint8_t *)ident, 32);
|
||||||
|
time_t t = time (nullptr);
|
||||||
|
struct tm tm;
|
||||||
|
#ifdef _WIN32
|
||||||
|
gmtime_s(&tm, &t);
|
||||||
|
sprintf_s((char *)(buf + 32), 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||||
|
#else
|
||||||
|
gmtime_r(&t, &tm);
|
||||||
|
sprintf((char *)(buf + 32), "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||||
|
#endif
|
||||||
|
IdentHash key;
|
||||||
|
SHA256(buf, 40, key);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
XORMetric operator^(const IdentHash& key1, const IdentHash& key2)
|
||||||
|
{
|
||||||
|
XORMetric m;
|
||||||
|
const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL ();
|
||||||
|
m.metric_ll[0] = hash1[0] ^ hash2[0];
|
||||||
|
m.metric_ll[1] = hash1[1] ^ hash2[1];
|
||||||
|
m.metric_ll[2] = hash1[2] ^ hash2[2];
|
||||||
|
m.metric_ll[3] = hash1[3] ^ hash2[3];
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
196
Identity.h
Normal file
196
Identity.h
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#ifndef IDENTITY_H__
|
||||||
|
#define IDENTITY_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Signature.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
typedef Tag<32> IdentHash;
|
||||||
|
inline std::string GetIdentHashAbbreviation (const IdentHash& ident)
|
||||||
|
{
|
||||||
|
return ident.ToBase64 ().substr (0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Keys
|
||||||
|
{
|
||||||
|
uint8_t privateKey[256];
|
||||||
|
uint8_t signingPrivateKey[20];
|
||||||
|
uint8_t publicKey[256];
|
||||||
|
uint8_t signingKey[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t CERTIFICATE_TYPE_NULL = 0;
|
||||||
|
const uint8_t CERTIFICATE_TYPE_HASHCASH = 1;
|
||||||
|
const uint8_t CERTIFICATE_TYPE_HIDDEN = 2;
|
||||||
|
const uint8_t CERTIFICATE_TYPE_SIGNED = 3;
|
||||||
|
const uint8_t CERTIFICATE_TYPE_MULTIPLE = 4;
|
||||||
|
const uint8_t CERTIFICATE_TYPE_KEY = 5;
|
||||||
|
|
||||||
|
struct Identity
|
||||||
|
{
|
||||||
|
uint8_t publicKey[256];
|
||||||
|
uint8_t signingKey[128];
|
||||||
|
uint8_t certificate[3]; // byte 1 - type, bytes 2-3 - length
|
||||||
|
|
||||||
|
Identity () = default;
|
||||||
|
Identity (const Keys& keys) { *this = keys; };
|
||||||
|
Identity& operator=(const Keys& keys);
|
||||||
|
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||||
|
IdentHash Hash () const;
|
||||||
|
};
|
||||||
|
|
||||||
|
Keys CreateRandomKeys ();
|
||||||
|
|
||||||
|
const size_t DEFAULT_IDENTITY_SIZE = sizeof (Identity); // 387 bytes
|
||||||
|
|
||||||
|
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA512_P521 = 3;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_RSA_SHA256_2048 = 4;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6;
|
||||||
|
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7;
|
||||||
|
typedef uint16_t SigningKeyType;
|
||||||
|
typedef uint16_t CryptoKeyType;
|
||||||
|
|
||||||
|
class IdentityEx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
IdentityEx ();
|
||||||
|
IdentityEx (const uint8_t * publicKey, const uint8_t * signingKey,
|
||||||
|
SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
|
||||||
|
IdentityEx (const uint8_t * buf, size_t len);
|
||||||
|
IdentityEx (const IdentityEx& other);
|
||||||
|
IdentityEx (const Identity& standard);
|
||||||
|
~IdentityEx ();
|
||||||
|
IdentityEx& operator=(const IdentityEx& other);
|
||||||
|
IdentityEx& operator=(const Identity& standard);
|
||||||
|
|
||||||
|
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||||
|
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
||||||
|
size_t FromBase64(const std::string& s);
|
||||||
|
std::string ToBase64 () const;
|
||||||
|
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
|
||||||
|
const IdentHash& GetIdentHash () const { return m_IdentHash; };
|
||||||
|
const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; };
|
||||||
|
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
|
||||||
|
size_t GetSigningPublicKeyLen () const;
|
||||||
|
size_t GetSigningPrivateKeyLen () const;
|
||||||
|
size_t GetSignatureLen () const;
|
||||||
|
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
|
||||||
|
SigningKeyType GetSigningKeyType () const;
|
||||||
|
CryptoKeyType GetCryptoKeyType () const;
|
||||||
|
void DropVerifier () const; // to save memory
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void CreateVerifier () const;
|
||||||
|
void UpdateVerifier (i2p::crypto::Verifier * verifier) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Identity m_StandardIdentity;
|
||||||
|
IdentHash m_IdentHash;
|
||||||
|
mutable std::unique_ptr<i2p::crypto::Verifier> m_Verifier;
|
||||||
|
size_t m_ExtendedLen;
|
||||||
|
uint8_t * m_ExtendedBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrivateKeys // for eepsites
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
PrivateKeys () = default;
|
||||||
|
PrivateKeys (const PrivateKeys& other) { *this = other; };
|
||||||
|
PrivateKeys (const Keys& keys) { *this = keys; };
|
||||||
|
PrivateKeys& operator=(const Keys& keys);
|
||||||
|
PrivateKeys& operator=(const PrivateKeys& other);
|
||||||
|
~PrivateKeys () = default;
|
||||||
|
|
||||||
|
std::shared_ptr<const IdentityEx> GetPublic () const { return m_Public; };
|
||||||
|
const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
|
||||||
|
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
|
||||||
|
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
|
||||||
|
|
||||||
|
size_t GetFullLen () const { return m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); };
|
||||||
|
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||||
|
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
||||||
|
|
||||||
|
size_t FromBase64(const std::string& s);
|
||||||
|
std::string ToBase64 () const;
|
||||||
|
|
||||||
|
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void CreateSigner ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<IdentityEx> m_Public;
|
||||||
|
uint8_t m_PrivateKey[256];
|
||||||
|
uint8_t m_SigningPrivateKey[1024]; // assume private key doesn't exceed 1024 bytes
|
||||||
|
std::unique_ptr<i2p::crypto::Signer> m_Signer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// kademlia
|
||||||
|
struct XORMetric
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
uint8_t metric[32];
|
||||||
|
uint64_t metric_ll[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
void SetMin () { memset (metric, 0, 32); };
|
||||||
|
void SetMax () { memset (metric, 0xFF, 32); };
|
||||||
|
bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; };
|
||||||
|
};
|
||||||
|
|
||||||
|
IdentHash CreateRoutingKey (const IdentHash& ident);
|
||||||
|
XORMetric operator^(const IdentHash& key1, const IdentHash& key2);
|
||||||
|
|
||||||
|
// destination for delivery instuctions
|
||||||
|
class RoutingDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
RoutingDestination () {};
|
||||||
|
virtual ~RoutingDestination () {};
|
||||||
|
|
||||||
|
virtual const IdentHash& GetIdentHash () const = 0;
|
||||||
|
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
|
||||||
|
virtual bool IsDestination () const = 0; // for garlic
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~LocalDestination() {};
|
||||||
|
virtual const PrivateKeys& GetPrivateKeys () const = 0;
|
||||||
|
virtual const uint8_t * GetEncryptionPrivateKey () const = 0;
|
||||||
|
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
|
||||||
|
|
||||||
|
std::shared_ptr<const IdentityEx> GetIdentity () const { return GetPrivateKeys ().GetPublic (); };
|
||||||
|
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
|
||||||
|
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||||
|
{
|
||||||
|
GetPrivateKeys ().Sign (buf, len, signature);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
248
LeaseSet.cpp
Normal file
248
LeaseSet.cpp
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "TunnelPool.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
|
||||||
|
LeaseSet::LeaseSet (const uint8_t * buf, size_t len, bool storeLeases):
|
||||||
|
m_IsValid (true), m_StoreLeases (storeLeases), m_ExpirationTime (0)
|
||||||
|
{
|
||||||
|
m_Buffer = new uint8_t[len];
|
||||||
|
memcpy (m_Buffer, buf, len);
|
||||||
|
m_BufferLen = len;
|
||||||
|
ReadFromBuffer ();
|
||||||
|
}
|
||||||
|
|
||||||
|
LeaseSet::LeaseSet (std::shared_ptr<const i2p::tunnel::TunnelPool> pool):
|
||||||
|
m_IsValid (true), m_StoreLeases (true), m_ExpirationTime (0)
|
||||||
|
{
|
||||||
|
if (!pool) return;
|
||||||
|
// header
|
||||||
|
auto localDestination = pool->GetLocalDestination ();
|
||||||
|
if (!localDestination)
|
||||||
|
{
|
||||||
|
m_Buffer = nullptr;
|
||||||
|
m_BufferLen = 0;
|
||||||
|
m_IsValid = false;
|
||||||
|
LogPrint (eLogError, "LeaseSet: Destination for local LeaseSet doesn't exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_Buffer = new uint8_t[MAX_LS_BUFFER_SIZE];
|
||||||
|
m_BufferLen = localDestination->GetIdentity ()->ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE);
|
||||||
|
memcpy (m_Buffer + m_BufferLen, localDestination->GetEncryptionPublicKey (), 256);
|
||||||
|
m_BufferLen += 256;
|
||||||
|
auto signingKeyLen = localDestination->GetIdentity ()->GetSigningPublicKeyLen ();
|
||||||
|
memset (m_Buffer + m_BufferLen, 0, signingKeyLen);
|
||||||
|
m_BufferLen += signingKeyLen;
|
||||||
|
int numTunnels = pool->GetNumInboundTunnels () + 2; // 2 backup tunnels
|
||||||
|
if (numTunnels > 16) numTunnels = 16; // 16 tunnels maximum
|
||||||
|
auto tunnels = pool->GetInboundTunnels (numTunnels);
|
||||||
|
m_Buffer[m_BufferLen] = tunnels.size (); // num leases
|
||||||
|
m_BufferLen++;
|
||||||
|
// leases
|
||||||
|
auto currentTime = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
for (auto it: tunnels)
|
||||||
|
{
|
||||||
|
memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32);
|
||||||
|
m_BufferLen += 32; // gateway id
|
||||||
|
htobe32buf (m_Buffer + m_BufferLen, it->GetNextTunnelID ());
|
||||||
|
m_BufferLen += 4; // tunnel id
|
||||||
|
uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
|
||||||
|
ts *= 1000; // in milliseconds
|
||||||
|
if (ts > m_ExpirationTime) m_ExpirationTime = ts;
|
||||||
|
// make sure leaseset is newer than previous, but adding some time to expiration date
|
||||||
|
ts += (currentTime - it->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
|
||||||
|
htobe64buf (m_Buffer + m_BufferLen, ts);
|
||||||
|
m_BufferLen += 8; // end date
|
||||||
|
}
|
||||||
|
// signature
|
||||||
|
localDestination->Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen);
|
||||||
|
m_BufferLen += localDestination->GetIdentity ()->GetSignatureLen ();
|
||||||
|
LogPrint (eLogDebug, "LeaseSet: Local LeaseSet of ", tunnels.size (), " leases created");
|
||||||
|
|
||||||
|
ReadFromBuffer ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LeaseSet::Update (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (len > m_BufferLen)
|
||||||
|
{
|
||||||
|
auto oldBuffer = m_Buffer;
|
||||||
|
m_Buffer = new uint8_t[len];
|
||||||
|
delete[] oldBuffer;
|
||||||
|
}
|
||||||
|
memcpy (m_Buffer, buf, len);
|
||||||
|
m_BufferLen = len;
|
||||||
|
ReadFromBuffer (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LeaseSet::PopulateLeases ()
|
||||||
|
{
|
||||||
|
m_StoreLeases = true;
|
||||||
|
ReadFromBuffer (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LeaseSet::ReadFromBuffer (bool readIdentity)
|
||||||
|
{
|
||||||
|
if (readIdentity || !m_Identity)
|
||||||
|
m_Identity = std::make_shared<IdentityEx>(m_Buffer, m_BufferLen);
|
||||||
|
size_t size = m_Identity->GetFullLen ();
|
||||||
|
if (size > m_BufferLen)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", m_BufferLen);
|
||||||
|
m_IsValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy (m_EncryptionKey, m_Buffer + size, 256);
|
||||||
|
size += 256; // encryption key
|
||||||
|
size += m_Identity->GetSigningPublicKeyLen (); // unused signing key
|
||||||
|
uint8_t num = m_Buffer[size];
|
||||||
|
size++; // num
|
||||||
|
LogPrint (eLogDebug, "LeaseSet: read num=", (int)num);
|
||||||
|
if (!num || num > MAX_NUM_LEASES)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)num);
|
||||||
|
m_IsValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset existing leases
|
||||||
|
if (m_StoreLeases)
|
||||||
|
for (auto it: m_Leases)
|
||||||
|
it->isUpdated = false;
|
||||||
|
else
|
||||||
|
m_Leases.clear ();
|
||||||
|
|
||||||
|
// process leases
|
||||||
|
m_ExpirationTime = 0;
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
const uint8_t * leases = m_Buffer + size;
|
||||||
|
for (int i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
Lease lease;
|
||||||
|
lease.tunnelGateway = leases;
|
||||||
|
leases += 32; // gateway
|
||||||
|
lease.tunnelID = bufbe32toh (leases);
|
||||||
|
leases += 4; // tunnel ID
|
||||||
|
lease.endDate = bufbe64toh (leases);
|
||||||
|
leases += 8; // end date
|
||||||
|
if (ts < lease.endDate + LEASE_ENDDATE_THRESHOLD)
|
||||||
|
{
|
||||||
|
if (lease.endDate > m_ExpirationTime)
|
||||||
|
m_ExpirationTime = lease.endDate;
|
||||||
|
if (m_StoreLeases)
|
||||||
|
{
|
||||||
|
auto ret = m_Leases.insert (std::make_shared<Lease>(lease));
|
||||||
|
if (!ret.second) *(*ret.first) = lease; // update existing
|
||||||
|
(*ret.first)->isUpdated = true;
|
||||||
|
// check if lease's gateway is in our netDb
|
||||||
|
if (!netdb.FindRouter (lease.tunnelGateway))
|
||||||
|
{
|
||||||
|
// if not found request it
|
||||||
|
LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting");
|
||||||
|
netdb.RequestDestination (lease.tunnelGateway);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "LeaseSet: Lease is expired already ");
|
||||||
|
}
|
||||||
|
if (!m_ExpirationTime)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped");
|
||||||
|
m_IsValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_ExpirationTime += LEASE_ENDDATE_THRESHOLD;
|
||||||
|
// delete old leases
|
||||||
|
if (m_StoreLeases)
|
||||||
|
{
|
||||||
|
for (auto it = m_Leases.begin (); it != m_Leases.end ();)
|
||||||
|
{
|
||||||
|
if (!(*it)->isUpdated)
|
||||||
|
{
|
||||||
|
(*it)->endDate = 0; // somebody might still hold it
|
||||||
|
m_Leases.erase (it++);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify
|
||||||
|
if (!m_Identity->Verify (m_Buffer, leases - m_Buffer, leases))
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "LeaseSet: verification failed");
|
||||||
|
m_IsValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t LeaseSet::ExtractTimestamp (const uint8_t * buf, size_t len) const
|
||||||
|
{
|
||||||
|
if (!m_Identity) return 0;
|
||||||
|
size_t size = m_Identity->GetFullLen ();
|
||||||
|
if (size > len) return 0;
|
||||||
|
size += 256; // encryption key
|
||||||
|
size += m_Identity->GetSigningPublicKeyLen (); // unused signing key
|
||||||
|
if (size > len) return 0;
|
||||||
|
uint8_t num = buf[size];
|
||||||
|
size++; // num
|
||||||
|
if (size + num*44 > len) return 0;
|
||||||
|
uint64_t timestamp= 0 ;
|
||||||
|
for (int i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
size += 36; // gateway (32) + tunnelId(4)
|
||||||
|
auto endDate = bufbe64toh (buf + size);
|
||||||
|
size += 8; // end date
|
||||||
|
if (!timestamp || endDate < timestamp)
|
||||||
|
timestamp = endDate;
|
||||||
|
}
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const
|
||||||
|
{
|
||||||
|
return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::shared_ptr<const Lease> > LeaseSet::GetNonExpiredLeases (bool withThreshold) const
|
||||||
|
{
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
std::vector<std::shared_ptr<const Lease> > leases;
|
||||||
|
for (auto it: m_Leases)
|
||||||
|
{
|
||||||
|
auto endDate = it->endDate;
|
||||||
|
if (withThreshold)
|
||||||
|
endDate += LEASE_ENDDATE_THRESHOLD;
|
||||||
|
else
|
||||||
|
endDate -= LEASE_ENDDATE_THRESHOLD;
|
||||||
|
if (ts < endDate)
|
||||||
|
leases.push_back (it);
|
||||||
|
}
|
||||||
|
return leases;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LeaseSet::HasExpiredLeases () const
|
||||||
|
{
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
for (auto it: m_Leases)
|
||||||
|
if (ts >= it->endDate) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LeaseSet::IsExpired () const
|
||||||
|
{
|
||||||
|
if (IsEmpty ()) return true;
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
return ts > m_ExpirationTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
LeaseSet.h
Normal file
88
LeaseSet.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef LEASE_SET_H__
|
||||||
|
#define LEASE_SET_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
class TunnelPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
const int LEASE_ENDDATE_THRESHOLD = 51000; // in milliseconds
|
||||||
|
struct Lease
|
||||||
|
{
|
||||||
|
IdentHash tunnelGateway;
|
||||||
|
uint32_t tunnelID;
|
||||||
|
uint64_t endDate; // 0 means invalid
|
||||||
|
bool isUpdated; // trasient
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LeaseCmp
|
||||||
|
{
|
||||||
|
bool operator() (std::shared_ptr<const Lease> l1, std::shared_ptr<const Lease> l2) const
|
||||||
|
{
|
||||||
|
if (l1->tunnelID != l2->tunnelID)
|
||||||
|
return l1->tunnelID < l2->tunnelID;
|
||||||
|
else
|
||||||
|
return l1->tunnelGateway < l2->tunnelGateway;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const int MAX_LS_BUFFER_SIZE = 3072;
|
||||||
|
const uint8_t MAX_NUM_LEASES = 16;
|
||||||
|
class LeaseSet: public RoutingDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true);
|
||||||
|
LeaseSet (std::shared_ptr<const i2p::tunnel::TunnelPool> pool);
|
||||||
|
~LeaseSet () { delete[] m_Buffer; };
|
||||||
|
void Update (const uint8_t * buf, size_t len);
|
||||||
|
bool IsNewer (const uint8_t * buf, size_t len) const;
|
||||||
|
void PopulateLeases (); // from buffer
|
||||||
|
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
|
||||||
|
|
||||||
|
const uint8_t * GetBuffer () const { return m_Buffer; };
|
||||||
|
size_t GetBufferLen () const { return m_BufferLen; };
|
||||||
|
bool IsValid () const { return m_IsValid; };
|
||||||
|
const std::vector<std::shared_ptr<const Lease> > GetNonExpiredLeases (bool withThreshold = true) const;
|
||||||
|
bool HasExpiredLeases () const;
|
||||||
|
bool IsExpired () const;
|
||||||
|
bool IsEmpty () const { return m_Leases.empty (); };
|
||||||
|
uint64_t GetExpirationTime () const { return m_ExpirationTime; };
|
||||||
|
bool operator== (const LeaseSet& other) const
|
||||||
|
{ return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); };
|
||||||
|
|
||||||
|
// implements RoutingDestination
|
||||||
|
const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); };
|
||||||
|
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; };
|
||||||
|
bool IsDestination () const { return true; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void ReadFromBuffer (bool readIdentity = true);
|
||||||
|
uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // min expiration time
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool m_IsValid, m_StoreLeases; // we don't need to store leases for floodfill
|
||||||
|
std::set<std::shared_ptr<Lease>, LeaseCmp> m_Leases;
|
||||||
|
uint64_t m_ExpirationTime; // in milliseconds
|
||||||
|
std::shared_ptr<const IdentityEx> m_Identity;
|
||||||
|
uint8_t m_EncryptionKey[256];
|
||||||
|
uint8_t * m_Buffer;
|
||||||
|
size_t m_BufferLen;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -59,12 +59,12 @@ struct LittleEndian
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
const T operator = (const T t)
|
const T operator = (const T t)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < sizeof(T); i++)
|
for (unsigned i = 0; i < sizeof(T); i++)
|
||||||
bytes[sizeof(T)-1 - i] = static_cast<unsigned char>(t >> (i << 3));
|
bytes[sizeof(T)-1 - i] = static_cast<unsigned char>(t >> (i << 3));
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// operators
|
// operators
|
||||||
|
|
||||||
167
Log.cpp
Normal file
167
Log.cpp
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016, The PurpleI2P Project
|
||||||
|
*
|
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3
|
||||||
|
*
|
||||||
|
* See full license text in LICENSE file at top of project tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace log {
|
||||||
|
Log logger;
|
||||||
|
/**
|
||||||
|
* @enum Maps our loglevel to their symbolic name
|
||||||
|
*/
|
||||||
|
static const char * g_LogLevelStr[eNumLogLevels] =
|
||||||
|
{
|
||||||
|
"error", // eLogError
|
||||||
|
"warn", // eLogWarn
|
||||||
|
"info", // eLogInfo
|
||||||
|
"debug" // eLogDebug
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/**
|
||||||
|
* @brief Maps our log levels to syslog one
|
||||||
|
* @return syslog priority LOG_*, as defined in syslog.h
|
||||||
|
*/
|
||||||
|
static inline int GetSyslogPrio (enum LogLevel l) {
|
||||||
|
int priority = LOG_DEBUG;
|
||||||
|
switch (l) {
|
||||||
|
case eLogError : priority = LOG_ERR; break;
|
||||||
|
case eLogWarning : priority = LOG_WARNING; break;
|
||||||
|
case eLogInfo : priority = LOG_INFO; break;
|
||||||
|
case eLogDebug : priority = LOG_DEBUG; break;
|
||||||
|
default : priority = LOG_DEBUG; break;
|
||||||
|
}
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Log::Log():
|
||||||
|
m_Destination(eLogStdout), m_MinLevel(eLogInfo),
|
||||||
|
m_LogStream (nullptr), m_Logfile(""), m_IsReady(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::~Log ()
|
||||||
|
{
|
||||||
|
switch (m_Destination) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
case eLogSyslog :
|
||||||
|
closelog();
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case eLogFile:
|
||||||
|
case eLogStream:
|
||||||
|
m_LogStream->flush();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* do nothing */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Process();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log::SetLogLevel (const std::string& level) {
|
||||||
|
if (level == "error") { m_MinLevel = eLogError; }
|
||||||
|
else if (level == "warn") { m_MinLevel = eLogWarning; }
|
||||||
|
else if (level == "info") { m_MinLevel = eLogInfo; }
|
||||||
|
else if (level == "debug") { m_MinLevel = eLogDebug; }
|
||||||
|
else {
|
||||||
|
LogPrint(eLogError, "Log: unknown loglevel: ", level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint(eLogInfo, "Log: min messages level set to ", level);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * Log::TimeAsString(std::time_t t) {
|
||||||
|
if (t != m_LastTimestamp) {
|
||||||
|
strftime(m_LastDateTime, sizeof(m_LastDateTime), "%H:%M:%S", localtime(&t));
|
||||||
|
m_LastTimestamp = t;
|
||||||
|
}
|
||||||
|
return m_LastDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note This function better to be run in separate thread due to disk i/o.
|
||||||
|
* Unfortunately, with current startup process with late fork() this
|
||||||
|
* will give us nothing but pain. Maybe later. See in NetDb as example.
|
||||||
|
*/
|
||||||
|
void Log::Process() {
|
||||||
|
std::unique_lock<std::mutex> l(m_OutputLock);
|
||||||
|
std::hash<std::thread::id> hasher;
|
||||||
|
unsigned short short_tid;
|
||||||
|
while (1) {
|
||||||
|
auto msg = m_Queue.GetNextWithTimeout (1);
|
||||||
|
if (!msg)
|
||||||
|
break;
|
||||||
|
short_tid = (short) (hasher(msg->tid) % 1000);
|
||||||
|
switch (m_Destination) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
case eLogSyslog:
|
||||||
|
syslog(GetSyslogPrio(msg->level), "[%03u] %s", short_tid, msg->text.c_str());
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case eLogFile:
|
||||||
|
case eLogStream:
|
||||||
|
*m_LogStream << TimeAsString(msg->timestamp)
|
||||||
|
<< "@" << short_tid
|
||||||
|
<< "/" << g_LogLevelStr[msg->level]
|
||||||
|
<< " - " << msg->text << std::endl;
|
||||||
|
break;
|
||||||
|
case eLogStdout:
|
||||||
|
default:
|
||||||
|
std::cout << TimeAsString(msg->timestamp)
|
||||||
|
<< "@" << short_tid
|
||||||
|
<< "/" << g_LogLevelStr[msg->level]
|
||||||
|
<< " - " << msg->text << std::endl;
|
||||||
|
break;
|
||||||
|
} // switch
|
||||||
|
} // while
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log::Append(std::shared_ptr<i2p::log::LogMsg> & msg) {
|
||||||
|
m_Queue.Put(msg);
|
||||||
|
if (!m_IsReady)
|
||||||
|
return;
|
||||||
|
Process();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log::SendTo (const std::string& path) {
|
||||||
|
auto flags = std::ofstream::out | std::ofstream::app;
|
||||||
|
auto os = std::make_shared<std::ofstream> (path, flags);
|
||||||
|
if (os->is_open ()) {
|
||||||
|
m_Logfile = path;
|
||||||
|
m_Destination = eLogFile;
|
||||||
|
m_LogStream = os;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint(eLogError, "Log: can't open file ", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log::SendTo (std::shared_ptr<std::ostream> os) {
|
||||||
|
m_Destination = eLogStream;
|
||||||
|
m_LogStream = os;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
void Log::SendTo(const char *name, int facility) {
|
||||||
|
m_Destination = eLogSyslog;
|
||||||
|
m_LogStream = nullptr;
|
||||||
|
openlog(name, LOG_CONS | LOG_PID, facility);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Log::Reopen() {
|
||||||
|
if (m_Destination == eLogFile)
|
||||||
|
SendTo(m_Logfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log & Logger() {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
} // log
|
||||||
|
} // i2p
|
||||||
186
Log.h
Normal file
186
Log.h
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016, The PurpleI2P Project
|
||||||
|
*
|
||||||
|
* This file is part of Purple i2pd project and licensed under BSD3
|
||||||
|
*
|
||||||
|
* See full license text in LICENSE file at top of project tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LOG_H__
|
||||||
|
#define LOG_H__
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include "Queue.h"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <syslog.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum LogLevel
|
||||||
|
{
|
||||||
|
eLogError = 0,
|
||||||
|
eLogWarning,
|
||||||
|
eLogInfo,
|
||||||
|
eLogDebug,
|
||||||
|
eNumLogLevels
|
||||||
|
};
|
||||||
|
|
||||||
|
enum LogType {
|
||||||
|
eLogStdout = 0,
|
||||||
|
eLogStream,
|
||||||
|
eLogFile,
|
||||||
|
#ifndef _WIN32
|
||||||
|
eLogSyslog,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace i2p {
|
||||||
|
namespace log {
|
||||||
|
struct LogMsg; /* forward declaration */
|
||||||
|
|
||||||
|
class Log
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum LogType m_Destination;
|
||||||
|
enum LogLevel m_MinLevel;
|
||||||
|
std::shared_ptr<std::ostream> m_LogStream;
|
||||||
|
std::string m_Logfile;
|
||||||
|
std::time_t m_LastTimestamp;
|
||||||
|
char m_LastDateTime[64];
|
||||||
|
i2p::util::Queue<std::shared_ptr<LogMsg> > m_Queue;
|
||||||
|
volatile bool m_IsReady;
|
||||||
|
mutable std::mutex m_OutputLock;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** prevent making copies */
|
||||||
|
Log (const Log &);
|
||||||
|
const Log& operator=(const Log&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief process stored messages in queue
|
||||||
|
*/
|
||||||
|
void Process ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Makes formatted string from unix timestamp
|
||||||
|
* @param ts Second since epoch
|
||||||
|
*
|
||||||
|
* This function internally caches the result for last provided value
|
||||||
|
*/
|
||||||
|
const char * TimeAsString(std::time_t ts);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Log ();
|
||||||
|
~Log ();
|
||||||
|
|
||||||
|
LogType GetLogType () { return m_Destination; };
|
||||||
|
LogLevel GetLogLevel () { return m_MinLevel; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets minimal alloed level for log messages
|
||||||
|
* @param level String with wanted minimal msg level
|
||||||
|
*/
|
||||||
|
void SetLogLevel (const std::string& level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets log destination to logfile
|
||||||
|
* @param path Path to logfile
|
||||||
|
*/
|
||||||
|
void SendTo (const std::string &path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets log destination to given output stream
|
||||||
|
* @param os Output stream
|
||||||
|
*/
|
||||||
|
void SendTo (std::shared_ptr<std::ostream> s);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/**
|
||||||
|
* @brief Sets log destination to syslog
|
||||||
|
* @param name Wanted program name
|
||||||
|
* @param facility Wanted log category
|
||||||
|
*/
|
||||||
|
void SendTo (const char *name, int facility);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Format log message and write to output stream/syslog
|
||||||
|
* @param msg Pointer to processed message
|
||||||
|
*/
|
||||||
|
void Append(std::shared_ptr<i2p::log::LogMsg> &);
|
||||||
|
|
||||||
|
/** @brief Allow log output */
|
||||||
|
void Ready() { m_IsReady = true; }
|
||||||
|
|
||||||
|
/** @brief Flushes the output log stream */
|
||||||
|
void Flush();
|
||||||
|
|
||||||
|
/** @brief Reopen log file */
|
||||||
|
void Reopen();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct Log message container
|
||||||
|
*
|
||||||
|
* We creating it somewhere with LogPrint(),
|
||||||
|
* then put in MsgQueue for later processing.
|
||||||
|
*/
|
||||||
|
struct LogMsg {
|
||||||
|
std::time_t timestamp;
|
||||||
|
std::string text; /**< message text as single string */
|
||||||
|
LogLevel level; /**< message level */
|
||||||
|
std::thread::id tid; /**< id of thread that generated message */
|
||||||
|
|
||||||
|
LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
Log & Logger();
|
||||||
|
} // log
|
||||||
|
} // i2p
|
||||||
|
|
||||||
|
/** internal usage only -- folding args array to single string */
|
||||||
|
template<typename TValue>
|
||||||
|
void LogPrint (std::stringstream& s, TValue arg)
|
||||||
|
{
|
||||||
|
s << arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** internal usage only -- folding args array to single string */
|
||||||
|
template<typename TValue, typename... TArgs>
|
||||||
|
void LogPrint (std::stringstream& s, TValue arg, TArgs... args)
|
||||||
|
{
|
||||||
|
LogPrint (s, arg);
|
||||||
|
LogPrint (s, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create log message and send it to queue
|
||||||
|
* @param level Message level (eLogError, eLogInfo, ...)
|
||||||
|
* @param args Array of message parts
|
||||||
|
*/
|
||||||
|
template<typename... TArgs>
|
||||||
|
void LogPrint (LogLevel level, TArgs... args)
|
||||||
|
{
|
||||||
|
i2p::log::Log &log = i2p::log::Logger();
|
||||||
|
if (level > log.GetLogLevel ())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// fold message to single string
|
||||||
|
std::stringstream ss("");
|
||||||
|
LogPrint (ss, args ...);
|
||||||
|
|
||||||
|
auto msg = std::make_shared<i2p::log::LogMsg>(level, std::time(nullptr), ss.str());
|
||||||
|
msg->tid = std::this_thread::get_id();
|
||||||
|
log.Append(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LOG_H__
|
||||||
95
Makefile
Normal file
95
Makefile
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
UNAME := $(shell uname -s)
|
||||||
|
SHLIB := libi2pd.so
|
||||||
|
ARLIB := libi2pd.a
|
||||||
|
SHLIB_CLIENT := libi2pdclient.so
|
||||||
|
ARLIB_CLIENT := libi2pdclient.a
|
||||||
|
I2PD := i2pd
|
||||||
|
GREP := fgrep
|
||||||
|
DEPS := obj/make.dep
|
||||||
|
|
||||||
|
include filelist.mk
|
||||||
|
|
||||||
|
USE_AESNI := yes
|
||||||
|
USE_STATIC := no
|
||||||
|
|
||||||
|
ifeq ($(UNAME),Darwin)
|
||||||
|
DAEMON_SRC += DaemonLinux.cpp
|
||||||
|
ifeq ($(HOMEBREW),1)
|
||||||
|
include Makefile.homebrew
|
||||||
|
else
|
||||||
|
include Makefile.osx
|
||||||
|
endif
|
||||||
|
else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1)
|
||||||
|
DAEMON_SRC += DaemonLinux.cpp
|
||||||
|
include Makefile.bsd
|
||||||
|
else ifeq ($(UNAME),Linux)
|
||||||
|
DAEMON_SRC += DaemonLinux.cpp
|
||||||
|
include Makefile.linux
|
||||||
|
else # win32 mingw
|
||||||
|
DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp
|
||||||
|
include Makefile.mingw
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD)
|
||||||
|
|
||||||
|
mk_obj_dir:
|
||||||
|
@mkdir -p obj
|
||||||
|
@mkdir -p obj/Win32
|
||||||
|
|
||||||
|
api: mk_obj_dir $(SHLIB) $(ARLIB)
|
||||||
|
api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT)
|
||||||
|
|
||||||
|
## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time
|
||||||
|
## **without** overwriting the CXXFLAGS which we need in order to build.
|
||||||
|
## For example, when adding 'hardening flags' to the build
|
||||||
|
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
|
||||||
|
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
|
||||||
|
## custom FLAGS to work at build-time.
|
||||||
|
|
||||||
|
deps: mk_obj_dir
|
||||||
|
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS)
|
||||||
|
@sed -i -e '/\.o:/ s/^/obj\//' $(DEPS)
|
||||||
|
|
||||||
|
obj/%.o: %.cpp
|
||||||
|
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
# '-' is 'ignore if missing' on first run
|
||||||
|
-include $(DEPS)
|
||||||
|
|
||||||
|
DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC))
|
||||||
|
$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT)
|
||||||
|
$(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS)
|
||||||
|
|
||||||
|
$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC))
|
||||||
|
ifneq ($(USE_STATIC),yes)
|
||||||
|
$(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC))
|
||||||
|
$(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^
|
||||||
|
|
||||||
|
$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC))
|
||||||
|
ar -r $@ $^
|
||||||
|
|
||||||
|
$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC))
|
||||||
|
ar -r $@ $^
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf obj
|
||||||
|
$(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT)
|
||||||
|
|
||||||
|
strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB)
|
||||||
|
strip $^
|
||||||
|
|
||||||
|
LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl)
|
||||||
|
dist:
|
||||||
|
git archive --format=tar.gz -9 --worktree-attributes \
|
||||||
|
--prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
.PHONY: clean
|
||||||
|
.PHONY: deps
|
||||||
|
.PHONY: dist
|
||||||
|
.PHONY: api
|
||||||
|
.PHONY: api_client
|
||||||
|
.PHONY: mk_obj_dir
|
||||||
12
Makefile.bsd
Normal file
12
Makefile.bsd
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
CXX = clang++
|
||||||
|
CXXFLAGS = -O2
|
||||||
|
## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time
|
||||||
|
## **without** overwriting the CXXFLAGS which we need in order to build.
|
||||||
|
## For example, when adding 'hardening flags' to the build
|
||||||
|
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
|
||||||
|
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
|
||||||
|
## custom FLAGS to work at build-time.
|
||||||
|
NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1
|
||||||
|
INCFLAGS = -I/usr/include/ -I/usr/local/include/
|
||||||
|
LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib
|
||||||
|
LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread
|
||||||
29
Makefile.homebrew
Normal file
29
Makefile.homebrew
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# root directory holding homebrew
|
||||||
|
BREWROOT = /usr/local/
|
||||||
|
BOOSTROOT = ${BREWROOT}/opt/boost
|
||||||
|
SSLROOT = ${BREWROOT}/opt/libressl
|
||||||
|
CXX = clang++
|
||||||
|
CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX
|
||||||
|
INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include
|
||||||
|
LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib
|
||||||
|
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread
|
||||||
|
|
||||||
|
ifeq ($(USE_UPNP),1)
|
||||||
|
LDFLAGS += -ldl
|
||||||
|
CXXFLAGS += -DUSE_UPNP
|
||||||
|
endif
|
||||||
|
|
||||||
|
# OSX Notes
|
||||||
|
# http://www.hutsby.net/2011/08/macs-with-aes-ni.html
|
||||||
|
# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2
|
||||||
|
# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic
|
||||||
|
# note from psi: 2009 macbook does not have aesni
|
||||||
|
#ifeq ($(USE_AESNI),yes)
|
||||||
|
# CXXFLAGS += -maes -DAESNI
|
||||||
|
#endif
|
||||||
|
|
||||||
|
# Disabled, since it will be the default make rule. I think its better
|
||||||
|
# to define the default rule in Makefile and not Makefile.<ostype> - torkel
|
||||||
|
#install: all
|
||||||
|
# test -d ${PREFIX} || mkdir -p ${PREFIX}/
|
||||||
|
# cp -r i2p ${PREFIX}/
|
||||||
60
Makefile.linux
Normal file
60
Makefile.linux
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# set defaults instead redefine
|
||||||
|
CXXFLAGS ?= -g -Wall
|
||||||
|
INCFLAGS ?=
|
||||||
|
|
||||||
|
## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time
|
||||||
|
## **without** overwriting the CXXFLAGS which we need in order to build.
|
||||||
|
## For example, when adding 'hardening flags' to the build
|
||||||
|
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
|
||||||
|
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
|
||||||
|
## custom FLAGS to work at build-time.
|
||||||
|
|
||||||
|
# detect proper flag for c++11 support by compilers
|
||||||
|
CXXVER := $(shell $(CXX) -dumpversion)
|
||||||
|
ifeq ($(shell expr match $(CXX) 'clang'),5)
|
||||||
|
NEEDED_CXXFLAGS += -std=c++11
|
||||||
|
else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10
|
||||||
|
NEEDED_CXXFLAGS += -std=c++11
|
||||||
|
else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7
|
||||||
|
NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1
|
||||||
|
else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6
|
||||||
|
NEEDED_CXXFLAGS += -std=c++0x
|
||||||
|
else ifeq ($(shell expr match ${CXXVER} "[5-6]\.[0-9]"),3) # gcc >= 5.0
|
||||||
|
NEEDED_CXXFLAGS += -std=c++11
|
||||||
|
else # not supported
|
||||||
|
$(error Compiler too old)
|
||||||
|
endif
|
||||||
|
|
||||||
|
NEEDED_CXXFLAGS += -fPIC
|
||||||
|
|
||||||
|
ifeq ($(USE_STATIC),yes)
|
||||||
|
LIBDIR := /usr/lib
|
||||||
|
LDLIBS = $(LIBDIR)/libboost_system.a
|
||||||
|
LDLIBS += $(LIBDIR)/libboost_date_time.a
|
||||||
|
LDLIBS += $(LIBDIR)/libboost_filesystem.a
|
||||||
|
LDLIBS += $(LIBDIR)/libboost_regex.a
|
||||||
|
LDLIBS += $(LIBDIR)/libboost_program_options.a
|
||||||
|
LDLIBS += $(LIBDIR)/libcrypto.a
|
||||||
|
LDLIBS += $(LIBDIR)/libssl.a
|
||||||
|
LDLIBS += $(LIBDIR)/libz.a
|
||||||
|
LDLIBS += -lpthread -static-libstdc++ -static-libgcc
|
||||||
|
USE_AESNI := no
|
||||||
|
else
|
||||||
|
LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread
|
||||||
|
endif
|
||||||
|
|
||||||
|
# UPNP Support (miniupnpc 1.5 or 1.6)
|
||||||
|
ifeq ($(USE_UPNP),1)
|
||||||
|
LDFLAGS += -ldl
|
||||||
|
CXXFLAGS += -DUSE_UPNP
|
||||||
|
endif
|
||||||
|
|
||||||
|
IS_64 := $(shell $(CXX) -dumpmachine 2>&1 | $(GREP) -c "64")
|
||||||
|
ifeq ($(USE_AESNI),yes)
|
||||||
|
ifeq ($(IS_64),1)
|
||||||
|
#check if AES-NI is supported by CPU
|
||||||
|
ifneq ($(shell grep -c aes /proc/cpuinfo),0)
|
||||||
|
CPU_FLAGS = -maes -DAESNI
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endif
|
||||||
43
Makefile.mingw
Normal file
43
Makefile.mingw
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
USE_WIN32_APP=yes
|
||||||
|
CXX = g++
|
||||||
|
WINDRES = windres
|
||||||
|
CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN
|
||||||
|
NEEDED_CXXFLAGS = -std=c++11
|
||||||
|
BOOST_SUFFIX = -mt
|
||||||
|
INCFLAGS = -I/usr/include/ -I/usr/local/include/
|
||||||
|
LDFLAGS = -Wl,-rpath,/usr/local/lib \
|
||||||
|
-L/usr/local/lib \
|
||||||
|
-L/c/dev/openssl \
|
||||||
|
-L/c/dev/boost/lib
|
||||||
|
LDLIBS = \
|
||||||
|
-Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \
|
||||||
|
-Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \
|
||||||
|
-Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \
|
||||||
|
-Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) \
|
||||||
|
-Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \
|
||||||
|
-Wl,-Bstatic -lssl \
|
||||||
|
-Wl,-Bstatic -lcrypto \
|
||||||
|
-Wl,-Bstatic -lz \
|
||||||
|
-Wl,-Bstatic -lwsock32 \
|
||||||
|
-Wl,-Bstatic -lws2_32 \
|
||||||
|
-Wl,-Bstatic -lgdi32 \
|
||||||
|
-Wl,-Bstatic -liphlpapi \
|
||||||
|
-static-libgcc -static-libstdc++ \
|
||||||
|
-Wl,-Bstatic -lstdc++ \
|
||||||
|
-Wl,-Bstatic -lpthread
|
||||||
|
|
||||||
|
ifeq ($(USE_WIN32_APP), yes)
|
||||||
|
CXXFLAGS += -DWIN32_APP
|
||||||
|
LDFLAGS += -mwindows -s
|
||||||
|
DAEMON_RC += Win32/Resource.rc
|
||||||
|
DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC))
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(USE_AESNI),1)
|
||||||
|
CPU_FLAGS = -maes -DAESNI
|
||||||
|
else
|
||||||
|
CPU_FLAGS = -msse
|
||||||
|
endif
|
||||||
|
|
||||||
|
obj/%.o : %.rc
|
||||||
|
$(WINDRES) -i $< -o $@
|
||||||
25
Makefile.osx
Normal file
25
Makefile.osx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
CXX = clang++
|
||||||
|
CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX
|
||||||
|
#CXXFLAGS = -g -O2 -Wall -std=c++11
|
||||||
|
INCFLAGS = -I/usr/local/include -I/usr/local/ssl/include
|
||||||
|
LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/usr/local/ssl/lib
|
||||||
|
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread
|
||||||
|
|
||||||
|
ifeq ($(USE_UPNP),1)
|
||||||
|
LDFLAGS += -ldl
|
||||||
|
CXXFLAGS += -DUSE_UPNP
|
||||||
|
endif
|
||||||
|
|
||||||
|
# OSX Notes
|
||||||
|
# http://www.hutsby.net/2011/08/macs-with-aes-ni.html
|
||||||
|
# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2
|
||||||
|
# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic
|
||||||
|
ifeq ($(USE_AESNI),yes)
|
||||||
|
CXXFLAGS += -maes -DAESNI
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Disabled, since it will be the default make rule. I think its better
|
||||||
|
# to define the default rule in Makefile and not Makefile.<ostype> - torkel
|
||||||
|
#install: all
|
||||||
|
# test -d ${PREFIX} || mkdir -p ${PREFIX}/
|
||||||
|
# cp -r i2p ${PREFIX}/
|
||||||
966
NTCPSession.cpp
Normal file
966
NTCPSession.cpp
Normal file
@@ -0,0 +1,966 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "NTCPSession.h"
|
||||||
|
|
||||||
|
using namespace i2p::crypto;
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter):
|
||||||
|
TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()),
|
||||||
|
m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false),
|
||||||
|
m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false)
|
||||||
|
{
|
||||||
|
m_Establisher = new Establisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
NTCPSession::~NTCPSession ()
|
||||||
|
{
|
||||||
|
delete m_Establisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key)
|
||||||
|
{
|
||||||
|
uint8_t sharedKey[256];
|
||||||
|
m_DHKeysPair->Agree (pubKey, sharedKey);
|
||||||
|
|
||||||
|
uint8_t * aesKey = key;
|
||||||
|
if (sharedKey[0] & 0x80)
|
||||||
|
{
|
||||||
|
aesKey[0] = 0;
|
||||||
|
memcpy (aesKey + 1, sharedKey, 31);
|
||||||
|
}
|
||||||
|
else if (sharedKey[0])
|
||||||
|
memcpy (aesKey, sharedKey, 32);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// find first non-zero byte
|
||||||
|
uint8_t * nonZero = sharedKey + 1;
|
||||||
|
while (!*nonZero)
|
||||||
|
{
|
||||||
|
nonZero++;
|
||||||
|
if (nonZero - sharedKey > 32)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "NTCP: First 32 bytes of shared key is all zeros, ignored");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy (aesKey, nonZero, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::Done ()
|
||||||
|
{
|
||||||
|
m_Server.GetService ().post (std::bind (&NTCPSession::Terminate, shared_from_this ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::Terminate ()
|
||||||
|
{
|
||||||
|
if (!m_IsTerminated)
|
||||||
|
{
|
||||||
|
m_IsTerminated = true;
|
||||||
|
m_IsEstablished = false;
|
||||||
|
m_Socket.close ();
|
||||||
|
transports.PeerDisconnected (shared_from_this ());
|
||||||
|
m_Server.RemoveNTCPSession (shared_from_this ());
|
||||||
|
m_SendQueue.clear ();
|
||||||
|
m_NextMessage = nullptr;
|
||||||
|
m_TerminationTimer.cancel ();
|
||||||
|
LogPrint (eLogDebug, "NTCP: session terminated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::Connected ()
|
||||||
|
{
|
||||||
|
m_IsEstablished = true;
|
||||||
|
|
||||||
|
delete m_Establisher;
|
||||||
|
m_Establisher = nullptr;
|
||||||
|
|
||||||
|
m_DHKeysPair = nullptr;
|
||||||
|
|
||||||
|
SendTimeSyncMessage ();
|
||||||
|
transports.PeerConnected (shared_from_this ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::ClientLogin ()
|
||||||
|
{
|
||||||
|
if (!m_DHKeysPair)
|
||||||
|
m_DHKeysPair = transports.GetNextDHKeysPair ();
|
||||||
|
// send Phase1
|
||||||
|
const uint8_t * x = m_DHKeysPair->GetPublicKey ();
|
||||||
|
memcpy (m_Establisher->phase1.pubKey, x, 256);
|
||||||
|
SHA256(x, 256, m_Establisher->phase1.HXxorHI);
|
||||||
|
const uint8_t * ident = m_RemoteIdentity->GetIdentHash ();
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
m_Establisher->phase1.HXxorHI[i] ^= ident[i];
|
||||||
|
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase1Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
|
||||||
|
ScheduleTermination ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::ServerLogin ()
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
auto ep = m_Socket.remote_endpoint(ec);
|
||||||
|
if (!ec)
|
||||||
|
{
|
||||||
|
m_ConnectedFrom = ep.address ();
|
||||||
|
// receive Phase1
|
||||||
|
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
ScheduleTermination ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
(void) bytes_transferred;
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: couldn't send Phase 1 message: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase2Received, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
(void) bytes_transferred;
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: phase 1 read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// verify ident
|
||||||
|
uint8_t digest[32];
|
||||||
|
SHA256(m_Establisher->phase1.pubKey, 256, digest);
|
||||||
|
const uint8_t * ident = i2p::context.GetIdentHash ();
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i])
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: phase 1 error: ident mismatch");
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SendPhase2 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::SendPhase2 ()
|
||||||
|
{
|
||||||
|
if (!m_DHKeysPair)
|
||||||
|
m_DHKeysPair = transports.GetNextDHKeysPair ();
|
||||||
|
const uint8_t * y = m_DHKeysPair->GetPublicKey ();
|
||||||
|
memcpy (m_Establisher->phase2.pubKey, y, 256);
|
||||||
|
uint8_t xy[512];
|
||||||
|
memcpy (xy, m_Establisher->phase1.pubKey, 256);
|
||||||
|
memcpy (xy + 256, y, 256);
|
||||||
|
SHA256(xy, 512, m_Establisher->phase2.encrypted.hxy);
|
||||||
|
uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ());
|
||||||
|
memcpy (m_Establisher->phase2.encrypted.timestamp, &tsB, 4);
|
||||||
|
RAND_bytes (m_Establisher->phase2.encrypted.filler, 12);
|
||||||
|
|
||||||
|
i2p::crypto::AESKey aesKey;
|
||||||
|
CreateAESKey (m_Establisher->phase1.pubKey, aesKey);
|
||||||
|
m_Encryption.SetKey (aesKey);
|
||||||
|
m_Encryption.SetIV (y + 240);
|
||||||
|
m_Decryption.SetKey (aesKey);
|
||||||
|
m_Decryption.SetIV (m_Establisher->phase1.HXxorHI + 16);
|
||||||
|
|
||||||
|
m_Encryption.Encrypt ((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted);
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase2Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsB));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
|
||||||
|
{
|
||||||
|
(void) bytes_transferred;
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: Couldn't send Phase 2 message: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase3Received, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2, tsB));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
(void) bytes_transferred;
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed");
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
// this RI is not valid
|
||||||
|
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true);
|
||||||
|
transports.ReuseDHKeysPair (m_DHKeysPair);
|
||||||
|
m_DHKeysPair = nullptr;
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
i2p::crypto::AESKey aesKey;
|
||||||
|
CreateAESKey (m_Establisher->phase2.pubKey, aesKey);
|
||||||
|
m_Decryption.SetKey (aesKey);
|
||||||
|
m_Decryption.SetIV (m_Establisher->phase2.pubKey + 240);
|
||||||
|
m_Encryption.SetKey (aesKey);
|
||||||
|
m_Encryption.SetIV (m_Establisher->phase1.HXxorHI + 16);
|
||||||
|
|
||||||
|
m_Decryption.Decrypt((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted);
|
||||||
|
// verify
|
||||||
|
uint8_t xy[512];
|
||||||
|
memcpy (xy, m_DHKeysPair->GetPublicKey (), 256);
|
||||||
|
memcpy (xy + 256, m_Establisher->phase2.pubKey, 256);
|
||||||
|
uint8_t digest[32];
|
||||||
|
SHA256 (xy, 512, digest);
|
||||||
|
if (memcmp(m_Establisher->phase2.encrypted.hxy, digest, 32))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: Phase 2 process error: incorrect hash");
|
||||||
|
transports.ReuseDHKeysPair (m_DHKeysPair);
|
||||||
|
m_DHKeysPair = nullptr;
|
||||||
|
Terminate ();
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
SendPhase3 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::SendPhase3 ()
|
||||||
|
{
|
||||||
|
auto keys = i2p::context.GetPrivateKeys ();
|
||||||
|
uint8_t * buf = m_ReceiveBuffer;
|
||||||
|
htobe16buf (buf, keys.GetPublic ()->GetFullLen ());
|
||||||
|
buf += 2;
|
||||||
|
buf += i2p::context.GetIdentity ()->ToBuffer (buf, NTCP_BUFFER_SIZE);
|
||||||
|
uint32_t tsA = htobe32 (i2p::util::GetSecondsSinceEpoch ());
|
||||||
|
htobuf32(buf,tsA);
|
||||||
|
buf += 4;
|
||||||
|
size_t signatureLen = keys.GetPublic ()->GetSignatureLen ();
|
||||||
|
size_t len = (buf - m_ReceiveBuffer) + signatureLen;
|
||||||
|
size_t paddingSize = len & 0x0F; // %16
|
||||||
|
if (paddingSize > 0)
|
||||||
|
{
|
||||||
|
paddingSize = 16 - paddingSize;
|
||||||
|
// fill padding with random data
|
||||||
|
RAND_bytes(buf, paddingSize);
|
||||||
|
buf += paddingSize;
|
||||||
|
len += paddingSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignedData s;
|
||||||
|
s.Insert (m_Establisher->phase1.pubKey, 256); // x
|
||||||
|
s.Insert (m_Establisher->phase2.pubKey, 256); // y
|
||||||
|
s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident
|
||||||
|
s.Insert (tsA); // tsA
|
||||||
|
s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB
|
||||||
|
s.Sign (keys, buf);
|
||||||
|
|
||||||
|
m_Encryption.Encrypt(m_ReceiveBuffer, len, m_ReceiveBuffer);
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, len), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase3Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsA));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA)
|
||||||
|
{
|
||||||
|
(void) bytes_transferred;
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: Couldn't send Phase 3 message: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// wait for phase4
|
||||||
|
auto signatureLen = m_RemoteIdentity->GetSignatureLen ();
|
||||||
|
size_t paddingSize = signatureLen & 0x0F; // %16
|
||||||
|
if (paddingSize > 0) signatureLen += (16 - paddingSize);
|
||||||
|
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase4Received, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2, tsA));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: Phase 3 read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Decryption.Decrypt (m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer);
|
||||||
|
uint8_t * buf = m_ReceiveBuffer;
|
||||||
|
uint16_t size = bufbe16toh (buf);
|
||||||
|
SetRemoteIdentity (std::make_shared<i2p::data::IdentityEx> (buf + 2, size));
|
||||||
|
if (m_Server.FindNTCPSession (m_RemoteIdentity->GetIdentHash ()))
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: session already exists");
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity->GetSignatureLen ();
|
||||||
|
size_t paddingLen = expectedSize & 0x0F;
|
||||||
|
if (paddingLen) paddingLen = (16 - paddingLen);
|
||||||
|
if (expectedSize > NTCP_DEFAULT_PHASE3_SIZE)
|
||||||
|
{
|
||||||
|
// we need more bytes for Phase3
|
||||||
|
expectedSize += paddingLen;
|
||||||
|
boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize - NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase3ExtraReceived, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2, tsB, paddingLen));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
HandlePhase3 (tsB, paddingLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: Phase 3 extra read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Decryption.Decrypt (m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, bytes_transferred, m_ReceiveBuffer+ NTCP_DEFAULT_PHASE3_SIZE);
|
||||||
|
HandlePhase3 (tsB, paddingLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase3 (uint32_t tsB, size_t paddingLen)
|
||||||
|
{
|
||||||
|
uint8_t * buf = m_ReceiveBuffer + m_RemoteIdentity->GetFullLen () + 2 /*size*/;
|
||||||
|
uint32_t tsA = buf32toh(buf);
|
||||||
|
buf += 4;
|
||||||
|
buf += paddingLen;
|
||||||
|
|
||||||
|
// check timestamp
|
||||||
|
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
uint32_t tsA1 = be32toh (tsA);
|
||||||
|
if (tsA1 < ts - NTCP_CLOCK_SKEW || tsA1 > ts + NTCP_CLOCK_SKEW)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: Phase3 time difference ", ts - tsA1, " exceeds clock skew");
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check signature
|
||||||
|
SignedData s;
|
||||||
|
s.Insert (m_Establisher->phase1.pubKey, 256); // x
|
||||||
|
s.Insert (m_Establisher->phase2.pubKey, 256); // y
|
||||||
|
s.Insert (i2p::context.GetRouterInfo ().GetIdentHash (), 32); // ident
|
||||||
|
s.Insert (tsA); // tsA
|
||||||
|
s.Insert (tsB); // tsB
|
||||||
|
if (!s.Verify (m_RemoteIdentity, buf))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: signature verification failed");
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendPhase4 (tsA, tsB);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::SendPhase4 (uint32_t tsA, uint32_t tsB)
|
||||||
|
{
|
||||||
|
SignedData s;
|
||||||
|
s.Insert (m_Establisher->phase1.pubKey, 256); // x
|
||||||
|
s.Insert (m_Establisher->phase2.pubKey, 256); // y
|
||||||
|
s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident
|
||||||
|
s.Insert (tsA); // tsA
|
||||||
|
s.Insert (tsB); // tsB
|
||||||
|
auto keys = i2p::context.GetPrivateKeys ();
|
||||||
|
auto signatureLen = keys.GetPublic ()->GetSignatureLen ();
|
||||||
|
s.Sign (keys, m_ReceiveBuffer);
|
||||||
|
size_t paddingSize = signatureLen & 0x0F; // %16
|
||||||
|
if (paddingSize > 0) signatureLen += (16 - paddingSize);
|
||||||
|
m_Encryption.Encrypt (m_ReceiveBuffer, signatureLen, m_ReceiveBuffer);
|
||||||
|
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandlePhase4Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
(void) bytes_transferred;
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "NTCP: Couldn't send Phase 4 message: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: Server session from ", m_Socket.remote_endpoint (), " connected");
|
||||||
|
m_Server.AddNTCPSession (shared_from_this ());
|
||||||
|
|
||||||
|
Connected ();
|
||||||
|
m_ReceiveBufferOffset = 0;
|
||||||
|
m_NextMessage = nullptr;
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: Phase 4 read error: ", ecode.message (), ". Check your clock");
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
// this router doesn't like us
|
||||||
|
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true);
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Decryption.Decrypt(m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer);
|
||||||
|
|
||||||
|
// check timestamp
|
||||||
|
uint32_t tsB = bufbe32toh (m_Establisher->phase2.encrypted.timestamp);
|
||||||
|
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (tsB < ts - NTCP_CLOCK_SKEW || tsB > ts + NTCP_CLOCK_SKEW)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: Phase4 time difference ", ts - tsB, " exceeds clock skew");
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
SignedData s;
|
||||||
|
s.Insert (m_Establisher->phase1.pubKey, 256); // x
|
||||||
|
s.Insert (m_Establisher->phase2.pubKey, 256); // y
|
||||||
|
s.Insert (i2p::context.GetIdentHash (), 32); // ident
|
||||||
|
s.Insert (tsA); // tsA
|
||||||
|
s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB
|
||||||
|
|
||||||
|
if (!s.Verify (m_RemoteIdentity, m_ReceiveBuffer))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: Phase 4 process error: signature verification failed");
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint (eLogDebug, "NTCP: session to ", m_Socket.remote_endpoint (), " connected");
|
||||||
|
Connected ();
|
||||||
|
|
||||||
|
m_ReceiveBufferOffset = 0;
|
||||||
|
m_NextMessage = nullptr;
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::Receive ()
|
||||||
|
{
|
||||||
|
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, NTCP_BUFFER_SIZE - m_ReceiveBufferOffset),
|
||||||
|
std::bind(&NTCPSession::HandleReceived, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
LogPrint (eLogDebug, "NTCP: Read error: ", ecode.message ());
|
||||||
|
if (!m_NumReceivedBytes)
|
||||||
|
m_Server.Ban (m_ConnectedFrom);
|
||||||
|
//if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_NumReceivedBytes += bytes_transferred;
|
||||||
|
i2p::transport::transports.UpdateReceivedBytes (bytes_transferred);
|
||||||
|
m_ReceiveBufferOffset += bytes_transferred;
|
||||||
|
|
||||||
|
if (m_ReceiveBufferOffset >= 16)
|
||||||
|
{
|
||||||
|
int numReloads = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
uint8_t * nextBlock = m_ReceiveBuffer;
|
||||||
|
while (m_ReceiveBufferOffset >= 16)
|
||||||
|
{
|
||||||
|
if (!DecryptNextBlock (nextBlock)) // 16 bytes
|
||||||
|
{
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextBlock += 16;
|
||||||
|
m_ReceiveBufferOffset -= 16;
|
||||||
|
}
|
||||||
|
if (m_ReceiveBufferOffset > 0)
|
||||||
|
memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset);
|
||||||
|
|
||||||
|
// try to read more
|
||||||
|
if (numReloads < 5)
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
size_t moreBytes = m_Socket.available(ec);
|
||||||
|
if (moreBytes)
|
||||||
|
{
|
||||||
|
if (moreBytes > NTCP_BUFFER_SIZE - m_ReceiveBufferOffset)
|
||||||
|
moreBytes = NTCP_BUFFER_SIZE - m_ReceiveBufferOffset;
|
||||||
|
moreBytes = m_Socket.read_some (boost::asio::buffer (m_ReceiveBuffer + m_ReceiveBufferOffset, moreBytes));
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "NTCP: Read more bytes error: ", ec.message ());
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_NumReceivedBytes += moreBytes;
|
||||||
|
m_ReceiveBufferOffset += moreBytes;
|
||||||
|
numReloads++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (m_ReceiveBufferOffset >= 16);
|
||||||
|
m_Handler.Flush ();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleTermination (); // reset termination timer
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NTCPSession::DecryptNextBlock (const uint8_t * encrypted) // 16 bytes
|
||||||
|
{
|
||||||
|
if (!m_NextMessage) // new message, header expected
|
||||||
|
{
|
||||||
|
// decrypt header and extract length
|
||||||
|
uint8_t buf[16];
|
||||||
|
m_Decryption.Decrypt (encrypted, buf);
|
||||||
|
uint16_t dataSize = bufbe16toh (buf);
|
||||||
|
if (dataSize)
|
||||||
|
{
|
||||||
|
// new message
|
||||||
|
if (dataSize + 16U > NTCP_MAX_MESSAGE_SIZE - 2) // + 6 + padding
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: data size ", dataSize, " exceeds max size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto msg = (dataSize + 16U) <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage ();
|
||||||
|
m_NextMessage = msg;
|
||||||
|
memcpy (m_NextMessage->buf, buf, 16);
|
||||||
|
m_NextMessageOffset = 16;
|
||||||
|
m_NextMessage->offset = 2; // size field
|
||||||
|
m_NextMessage->len = dataSize + 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// timestamp
|
||||||
|
LogPrint (eLogDebug, "NTCP: Timestamp");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // message continues
|
||||||
|
{
|
||||||
|
m_Decryption.Decrypt (encrypted, m_NextMessage->buf + m_NextMessageOffset);
|
||||||
|
m_NextMessageOffset += 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_NextMessageOffset >= m_NextMessage->len + 4) // +checksum
|
||||||
|
{
|
||||||
|
// we have a complete I2NP message
|
||||||
|
uint8_t checksum[4];
|
||||||
|
htobe32buf (checksum, adler32 (adler32 (0, Z_NULL, 0), m_NextMessage->buf, m_NextMessageOffset - 4));
|
||||||
|
if (!memcmp (m_NextMessage->buf + m_NextMessageOffset - 4, checksum, 4))
|
||||||
|
{
|
||||||
|
if (!m_NextMessage->IsExpired ())
|
||||||
|
m_Handler.PutNextMessage (m_NextMessage);
|
||||||
|
else
|
||||||
|
LogPrint (eLogInfo, "NTCP: message expired");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "NTCP: Incorrect adler checksum of message, dropped");
|
||||||
|
m_NextMessage = nullptr;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::Send (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
m_IsSending = true;
|
||||||
|
boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector<std::shared_ptr<I2NPMessage> >{ msg }));
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
uint8_t * sendBuffer;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if (msg)
|
||||||
|
{
|
||||||
|
// regular I2NP
|
||||||
|
if (msg->offset < 2)
|
||||||
|
LogPrint (eLogError, "NTCP: Malformed I2NP message"); // TODO:
|
||||||
|
sendBuffer = msg->GetBuffer () - 2;
|
||||||
|
len = msg->GetLength ();
|
||||||
|
htobe16buf (sendBuffer, len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// prepare timestamp
|
||||||
|
sendBuffer = m_TimeSyncBuffer;
|
||||||
|
len = 4;
|
||||||
|
htobuf16(sendBuffer, 0);
|
||||||
|
htobe32buf (sendBuffer + 2, time (0));
|
||||||
|
}
|
||||||
|
int rem = (len + 6) & 0x0F; // %16
|
||||||
|
int padding = 0;
|
||||||
|
if (rem > 0) {
|
||||||
|
padding = 16 - rem;
|
||||||
|
// fill with random padding
|
||||||
|
RAND_bytes(sendBuffer + len + 2, padding);
|
||||||
|
}
|
||||||
|
htobe32buf (sendBuffer + len + 2 + padding, adler32 (adler32 (0, Z_NULL, 0), sendBuffer, len + 2+ padding));
|
||||||
|
|
||||||
|
int l = len + padding + 6;
|
||||||
|
m_Encryption.Encrypt(sendBuffer, l, sendBuffer);
|
||||||
|
return boost::asio::buffer ((const uint8_t *)sendBuffer, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NTCPSession::Send (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
|
||||||
|
{
|
||||||
|
m_IsSending = true;
|
||||||
|
std::vector<boost::asio::const_buffer> bufs;
|
||||||
|
for (auto it: msgs)
|
||||||
|
bufs.push_back (CreateMsgBuffer (it));
|
||||||
|
boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (),
|
||||||
|
std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<I2NPMessage> > msgs)
|
||||||
|
{
|
||||||
|
(void) msgs;
|
||||||
|
m_IsSending = false;
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "NTCP: Couldn't send msgs: ", ecode.message ());
|
||||||
|
// we shouldn't call Terminate () here, because HandleReceive takes care
|
||||||
|
// TODO: 'delete this' statement in Terminate () must be eliminated later
|
||||||
|
// Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_NumSentBytes += bytes_transferred;
|
||||||
|
i2p::transport::transports.UpdateSentBytes (bytes_transferred);
|
||||||
|
if (!m_SendQueue.empty())
|
||||||
|
{
|
||||||
|
Send (m_SendQueue);
|
||||||
|
m_SendQueue.clear ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ScheduleTermination (); // reset termination timer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NTCPSession::SendTimeSyncMessage ()
|
||||||
|
{
|
||||||
|
Send (nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NTCPSession::SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
|
||||||
|
{
|
||||||
|
m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
|
||||||
|
{
|
||||||
|
if (m_IsTerminated) return;
|
||||||
|
if (m_IsSending)
|
||||||
|
{
|
||||||
|
for (auto it: msgs)
|
||||||
|
m_SendQueue.push_back (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Send (msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::ScheduleTermination ()
|
||||||
|
{
|
||||||
|
m_TerminationTimer.cancel ();
|
||||||
|
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_TIMEOUT));
|
||||||
|
m_TerminationTimer.async_wait (std::bind (&NTCPSession::HandleTerminationTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPSession::HandleTerminationTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "NTCP: No activity for ", NTCP_TERMINATION_TIMEOUT, " seconds");
|
||||||
|
//Terminate ();
|
||||||
|
m_Socket.close ();// invoke Terminate () from HandleReceive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------
|
||||||
|
NTCPServer::NTCPServer ():
|
||||||
|
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
|
||||||
|
m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NTCPServer::~NTCPServer ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::Start ()
|
||||||
|
{
|
||||||
|
if (!m_IsRunning)
|
||||||
|
{
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Thread = new std::thread (std::bind (&NTCPServer::Run, this));
|
||||||
|
// create acceptors
|
||||||
|
auto& addresses = context.GetRouterInfo ().GetAddresses ();
|
||||||
|
for (auto address: addresses)
|
||||||
|
{
|
||||||
|
if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && address->host.is_v4 ())
|
||||||
|
{
|
||||||
|
m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service,
|
||||||
|
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port));
|
||||||
|
|
||||||
|
LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port);
|
||||||
|
auto conn = std::make_shared<NTCPSession>(*this);
|
||||||
|
m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this,
|
||||||
|
conn, std::placeholders::_1));
|
||||||
|
|
||||||
|
if (context.SupportsV6 ())
|
||||||
|
{
|
||||||
|
m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service);
|
||||||
|
m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6());
|
||||||
|
m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true));
|
||||||
|
m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port));
|
||||||
|
m_NTCPV6Acceptor->listen ();
|
||||||
|
|
||||||
|
LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port);
|
||||||
|
auto conn = std::make_shared<NTCPSession> (*this);
|
||||||
|
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6,
|
||||||
|
this, conn, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::Stop ()
|
||||||
|
{
|
||||||
|
m_NTCPSessions.clear ();
|
||||||
|
|
||||||
|
if (m_IsRunning)
|
||||||
|
{
|
||||||
|
m_IsRunning = false;
|
||||||
|
delete m_NTCPAcceptor;
|
||||||
|
m_NTCPAcceptor = nullptr;
|
||||||
|
delete m_NTCPV6Acceptor;
|
||||||
|
m_NTCPV6Acceptor = nullptr;
|
||||||
|
|
||||||
|
m_Service.stop ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NTCPServer::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Service.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NTCPServer::AddNTCPSession (std::shared_ptr<NTCPSession> session)
|
||||||
|
{
|
||||||
|
if (!session || !session->GetRemoteIdentity ()) return false;
|
||||||
|
auto& ident = session->GetRemoteIdentity ()->GetIdentHash ();
|
||||||
|
auto it = m_NTCPSessions.find (ident);
|
||||||
|
if (it != m_NTCPSessions.end ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "NTCP: session to ", ident.ToBase64 (), " already exists");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_NTCPSessions.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<NTCPSession> >(ident, session));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::RemoveNTCPSession (std::shared_ptr<NTCPSession> session)
|
||||||
|
{
|
||||||
|
if (session && session->GetRemoteIdentity ())
|
||||||
|
m_NTCPSessions.erase (session->GetRemoteIdentity ()->GetIdentHash ());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NTCPSession> NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
auto it = m_NTCPSessions.find (ident);
|
||||||
|
if (it != m_NTCPSessions.end ())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::HandleAccept (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error)
|
||||||
|
{
|
||||||
|
if (!error)
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
auto ep = conn->GetSocket ().remote_endpoint(ec);
|
||||||
|
if (!ec)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "NTCP: Connected from ", ep);
|
||||||
|
auto it = m_BanList.find (ep.address ());
|
||||||
|
if (it != m_BanList.end ())
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (ts < it->second)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "NTCP: ", ep.address (), " is banned for ", it->second - ts, " more seconds");
|
||||||
|
conn = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_BanList.erase (it);
|
||||||
|
}
|
||||||
|
if (conn)
|
||||||
|
conn->ServerLogin ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "NTCP: Connected from error ", ec.message ());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (error != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
conn = std::make_shared<NTCPSession> (*this);
|
||||||
|
m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this,
|
||||||
|
conn, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::HandleAcceptV6 (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error)
|
||||||
|
{
|
||||||
|
if (!error)
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
auto ep = conn->GetSocket ().remote_endpoint(ec);
|
||||||
|
if (!ec)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "NTCP: Connected from ", ep);
|
||||||
|
auto it = m_BanList.find (ep.address ());
|
||||||
|
if (it != m_BanList.end ())
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (ts < it->second)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "NTCP: ", ep.address (), " is banned for ", it->second - ts, " more seconds");
|
||||||
|
conn = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_BanList.erase (it);
|
||||||
|
}
|
||||||
|
if (conn)
|
||||||
|
conn->ServerLogin ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "NTCP: Connected from error ", ec.message ());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
conn = std::make_shared<NTCPSession> (*this);
|
||||||
|
m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this,
|
||||||
|
conn, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "NTCP: Connecting to ", address ,":", port);
|
||||||
|
m_Service.post([=]()
|
||||||
|
{
|
||||||
|
if (this->AddNTCPSession (conn))
|
||||||
|
conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port),
|
||||||
|
std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "NTCP: Connect error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true);
|
||||||
|
conn->Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "NTCP: Connected to ", conn->GetSocket ().remote_endpoint ());
|
||||||
|
if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6
|
||||||
|
context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ());
|
||||||
|
conn->ClientLogin ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NTCPServer::Ban (const boost::asio::ip::address& addr)
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
m_BanList[addr] = ts + NTCP_BAN_EXPIRATION_TIMEOUT;
|
||||||
|
LogPrint (eLogWarning, "NTCP: ", addr, " has been banned for ", NTCP_BAN_EXPIRATION_TIMEOUT, " seconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
177
NTCPSession.h
Normal file
177
NTCPSession.h
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#ifndef NTCP_SESSION_H__
|
||||||
|
#define NTCP_SESSION_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "TransportSession.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
struct NTCPPhase1
|
||||||
|
{
|
||||||
|
uint8_t pubKey[256];
|
||||||
|
uint8_t HXxorHI[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NTCPPhase2
|
||||||
|
{
|
||||||
|
uint8_t pubKey[256];
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t hxy[32];
|
||||||
|
uint8_t timestamp[4];
|
||||||
|
uint8_t filler[12];
|
||||||
|
} encrypted;
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t NTCP_MAX_MESSAGE_SIZE = 16384;
|
||||||
|
const size_t NTCP_BUFFER_SIZE = 4160; // fits 4 tunnel messages (4*1028)
|
||||||
|
const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes
|
||||||
|
const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448
|
||||||
|
const int NTCP_BAN_EXPIRATION_TIMEOUT = 70; // in second
|
||||||
|
const int NTCP_CLOCK_SKEW = 60; // in seconds
|
||||||
|
|
||||||
|
class NTCPServer;
|
||||||
|
class NTCPSession: public TransportSession, public std::enable_shared_from_this<NTCPSession>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
NTCPSession (NTCPServer& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter = nullptr);
|
||||||
|
~NTCPSession ();
|
||||||
|
void Terminate ();
|
||||||
|
void Done ();
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
|
||||||
|
bool IsEstablished () const { return m_IsEstablished; };
|
||||||
|
|
||||||
|
void ClientLogin ();
|
||||||
|
void ServerLogin ();
|
||||||
|
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
|
||||||
|
void Connected ();
|
||||||
|
void SendTimeSyncMessage ();
|
||||||
|
void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; }
|
||||||
|
|
||||||
|
void CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key);
|
||||||
|
|
||||||
|
// client
|
||||||
|
void SendPhase3 ();
|
||||||
|
void HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
|
||||||
|
void HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
|
||||||
|
|
||||||
|
//server
|
||||||
|
void SendPhase2 ();
|
||||||
|
void SendPhase4 (uint32_t tsA, uint32_t tsB);
|
||||||
|
void HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
|
||||||
|
void HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
|
||||||
|
void HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen);
|
||||||
|
void HandlePhase3 (uint32_t tsB, size_t paddingLen);
|
||||||
|
void HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
|
||||||
|
// common
|
||||||
|
void Receive ();
|
||||||
|
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
bool DecryptNextBlock (const uint8_t * encrypted);
|
||||||
|
|
||||||
|
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
void Send (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||||
|
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<I2NPMessage> > msgs);
|
||||||
|
|
||||||
|
|
||||||
|
// timer
|
||||||
|
void ScheduleTermination ();
|
||||||
|
void HandleTerminationTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
NTCPServer& m_Server;
|
||||||
|
boost::asio::ip::tcp::socket m_Socket;
|
||||||
|
boost::asio::deadline_timer m_TerminationTimer;
|
||||||
|
bool m_IsEstablished, m_IsTerminated;
|
||||||
|
|
||||||
|
i2p::crypto::CBCDecryption m_Decryption;
|
||||||
|
i2p::crypto::CBCEncryption m_Encryption;
|
||||||
|
|
||||||
|
struct Establisher
|
||||||
|
{
|
||||||
|
NTCPPhase1 phase1;
|
||||||
|
NTCPPhase2 phase2;
|
||||||
|
} * m_Establisher;
|
||||||
|
|
||||||
|
i2p::crypto::AESAlignedBuffer<NTCP_BUFFER_SIZE + 16> m_ReceiveBuffer;
|
||||||
|
i2p::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer;
|
||||||
|
int m_ReceiveBufferOffset;
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> m_NextMessage;
|
||||||
|
size_t m_NextMessageOffset;
|
||||||
|
i2p::I2NPMessagesHandler m_Handler;
|
||||||
|
|
||||||
|
bool m_IsSending;
|
||||||
|
std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
|
||||||
|
|
||||||
|
boost::asio::ip::address m_ConnectedFrom; // for ban
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: move to NTCP.h/.cpp
|
||||||
|
class NTCPServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
NTCPServer ();
|
||||||
|
~NTCPServer ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
bool AddNTCPSession (std::shared_ptr<NTCPSession> session);
|
||||||
|
void RemoveNTCPSession (std::shared_ptr<NTCPSession> session);
|
||||||
|
std::shared_ptr<NTCPSession> FindNTCPSession (const i2p::data::IdentHash& ident);
|
||||||
|
void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn);
|
||||||
|
|
||||||
|
boost::asio::io_service& GetService () { return m_Service; };
|
||||||
|
void Ban (const boost::asio::ip::address& addr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void HandleAccept (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
|
||||||
|
void HandleAcceptV6 (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
|
||||||
|
|
||||||
|
void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
boost::asio::io_service m_Service;
|
||||||
|
boost::asio::io_service::work m_Work;
|
||||||
|
boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor;
|
||||||
|
std::map<i2p::data::IdentHash, std::shared_ptr<NTCPSession> > m_NTCPSessions; // access from m_Thread only
|
||||||
|
std::map<boost::asio::ip::address, uint32_t> m_BanList; // IP -> ban expiration time in seconds
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// for HTTP/I2PControl
|
||||||
|
const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
120
NetDb.h
Normal file
120
NetDb.h
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#ifndef NETDB_H__
|
||||||
|
#define NETDB_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Queue.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "TunnelPool.h"
|
||||||
|
#include "Reseed.h"
|
||||||
|
#include "NetDbRequests.h"
|
||||||
|
#include "Family.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
const int NETDB_MIN_ROUTERS = 90;
|
||||||
|
const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60*60; // 1 hour, in seconds
|
||||||
|
const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60;
|
||||||
|
const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours
|
||||||
|
const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours
|
||||||
|
|
||||||
|
class NetDb
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
NetDb ();
|
||||||
|
~NetDb ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
bool AddRouterInfo (const uint8_t * buf, int len);
|
||||||
|
bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len);
|
||||||
|
bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||||
|
std::shared_ptr<RouterInfo> FindRouter (const IdentHash& ident) const;
|
||||||
|
std::shared_ptr<LeaseSet> FindLeaseSet (const IdentHash& destination) const;
|
||||||
|
std::shared_ptr<RouterProfile> FindRouterProfile (const IdentHash& ident) const;
|
||||||
|
|
||||||
|
void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr);
|
||||||
|
|
||||||
|
void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||||
|
void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||||
|
void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||||
|
|
||||||
|
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
|
||||||
|
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
|
||||||
|
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
|
||||||
|
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter () const;
|
||||||
|
std::shared_ptr<const RouterInfo> GetRandomIntroducer () const;
|
||||||
|
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
|
||||||
|
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
|
||||||
|
std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
|
||||||
|
std::shared_ptr<const RouterInfo> GetClosestNonFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
|
||||||
|
void SetUnreachable (const IdentHash& ident, bool unreachable);
|
||||||
|
|
||||||
|
void PostI2NPMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||||
|
|
||||||
|
void Reseed ();
|
||||||
|
Families& GetFamilies () { return m_Families; };
|
||||||
|
|
||||||
|
// for web interface
|
||||||
|
int GetNumRouters () const { return m_RouterInfos.size (); };
|
||||||
|
int GetNumFloodfills () const { return m_Floodfills.size (); };
|
||||||
|
int GetNumLeaseSets () const { return m_LeaseSets.size (); };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Load ();
|
||||||
|
bool LoadRouterInfo (const std::string & path);
|
||||||
|
void SaveUpdated ();
|
||||||
|
void Run (); // exploratory thread
|
||||||
|
void Explore (int numDestinations);
|
||||||
|
void Publish ();
|
||||||
|
void ManageLeaseSets ();
|
||||||
|
void ManageRequests ();
|
||||||
|
void ManageLookupResponses ();
|
||||||
|
|
||||||
|
template<typename Filter>
|
||||||
|
std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::map<IdentHash, std::shared_ptr<LeaseSet> > m_LeaseSets;
|
||||||
|
mutable std::mutex m_RouterInfosMutex;
|
||||||
|
std::map<IdentHash, std::shared_ptr<RouterInfo> > m_RouterInfos;
|
||||||
|
mutable std::mutex m_FloodfillsMutex;
|
||||||
|
std::list<std::shared_ptr<RouterInfo> > m_Floodfills;
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
uint64_t m_LastLoad;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
i2p::util::Queue<std::shared_ptr<const I2NPMessage> > m_Queue; // of I2NPDatabaseStoreMsg
|
||||||
|
|
||||||
|
GzipInflator m_Inflator;
|
||||||
|
Reseeder * m_Reseeder;
|
||||||
|
Families m_Families;
|
||||||
|
i2p::fs::HashedStorage m_Storage;
|
||||||
|
|
||||||
|
friend class NetDbRequests;
|
||||||
|
NetDbRequests m_Requests;
|
||||||
|
|
||||||
|
std::map<IdentHash, std::pair<std::vector<IdentHash>, uint64_t> > m_LookupResponses; // ident->(closest FFs, timestamp)
|
||||||
|
};
|
||||||
|
|
||||||
|
extern NetDb netdb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
149
NetDbRequests.cpp
Normal file
149
NetDbRequests.cpp
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#include "Log.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "NetDbRequests.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router,
|
||||||
|
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
|
||||||
|
{
|
||||||
|
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
|
||||||
|
replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory,
|
||||||
|
&m_ExcludedPeers);
|
||||||
|
m_ExcludedPeers.insert (router->GetIdentHash ());
|
||||||
|
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (const IdentHash& floodfill)
|
||||||
|
{
|
||||||
|
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
|
||||||
|
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
|
||||||
|
m_ExcludedPeers.insert (floodfill);
|
||||||
|
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestedDestination::ClearExcludedPeers ()
|
||||||
|
{
|
||||||
|
m_ExcludedPeers.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestedDestination::Success (std::shared_ptr<RouterInfo> r)
|
||||||
|
{
|
||||||
|
if (m_RequestComplete)
|
||||||
|
{
|
||||||
|
m_RequestComplete (r);
|
||||||
|
m_RequestComplete = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestedDestination::Fail ()
|
||||||
|
{
|
||||||
|
if (m_RequestComplete)
|
||||||
|
{
|
||||||
|
m_RequestComplete (nullptr);
|
||||||
|
m_RequestComplete = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetDbRequests::Start ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetDbRequests::Stop ()
|
||||||
|
{
|
||||||
|
m_RequestedDestinations.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete)
|
||||||
|
{
|
||||||
|
// request RouterInfo directly
|
||||||
|
auto dest = std::make_shared<RequestedDestination> (destination, isExploratory);
|
||||||
|
dest->SetRequestComplete (requestComplete);
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||||
|
if (!m_RequestedDestinations.insert (std::make_pair (destination,
|
||||||
|
std::shared_ptr<RequestedDestination> (dest))).second) // not inserted
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
|
||||||
|
{
|
||||||
|
auto it = m_RequestedDestinations.find (ident);
|
||||||
|
if (it != m_RequestedDestinations.end ())
|
||||||
|
{
|
||||||
|
if (r)
|
||||||
|
it->second->Success (r);
|
||||||
|
else
|
||||||
|
it->second->Fail ();
|
||||||
|
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||||
|
m_RequestedDestinations.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<RequestedDestination> NetDbRequests::FindRequest (const IdentHash& ident) const
|
||||||
|
{
|
||||||
|
auto it = m_RequestedDestinations.find (ident);
|
||||||
|
if (it != m_RequestedDestinations.end ())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetDbRequests::ManageRequests ()
|
||||||
|
{
|
||||||
|
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||||
|
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
|
||||||
|
{
|
||||||
|
auto& dest = it->second;
|
||||||
|
bool done = false;
|
||||||
|
if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute
|
||||||
|
{
|
||||||
|
if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds
|
||||||
|
{
|
||||||
|
auto count = dest->GetExcludedPeers ().size ();
|
||||||
|
if (!dest->IsExploratory () && count < 7)
|
||||||
|
{
|
||||||
|
auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
|
||||||
|
auto outbound = pool->GetNextOutboundTunnel ();
|
||||||
|
auto inbound = pool->GetNextInboundTunnel ();
|
||||||
|
auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
|
||||||
|
if (nextFloodfill && outbound && inbound)
|
||||||
|
outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0,
|
||||||
|
dest->CreateRequestMessage (nextFloodfill, inbound));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
done = true;
|
||||||
|
if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels");
|
||||||
|
if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels");
|
||||||
|
if (!nextFloodfill) LogPrint (eLogWarning, "NetDbReq: No more floodfills");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!dest->IsExploratory ())
|
||||||
|
LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts");
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // delete obsolete request
|
||||||
|
done = true;
|
||||||
|
|
||||||
|
if (done)
|
||||||
|
it = m_RequestedDestinations.erase (it);
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
69
NetDbRequests.h
Normal file
69
NetDbRequests.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#ifndef NETDB_REQUESTS_H__
|
||||||
|
#define NETDB_REQUESTS_H__
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
class RequestedDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef std::function<void (std::shared_ptr<RouterInfo>)> RequestComplete;
|
||||||
|
|
||||||
|
RequestedDestination (const IdentHash& destination, bool isExploratory = false):
|
||||||
|
m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {};
|
||||||
|
~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); };
|
||||||
|
|
||||||
|
const IdentHash& GetDestination () const { return m_Destination; };
|
||||||
|
int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); };
|
||||||
|
const std::set<IdentHash>& GetExcludedPeers () { return m_ExcludedPeers; };
|
||||||
|
void ClearExcludedPeers ();
|
||||||
|
bool IsExploratory () const { return m_IsExploratory; };
|
||||||
|
bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); };
|
||||||
|
uint64_t GetCreationTime () const { return m_CreationTime; };
|
||||||
|
std::shared_ptr<I2NPMessage> CreateRequestMessage (std::shared_ptr<const RouterInfo>, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateRequestMessage (const IdentHash& floodfill);
|
||||||
|
|
||||||
|
void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; };
|
||||||
|
bool IsRequestComplete () const { return m_RequestComplete != nullptr; };
|
||||||
|
void Success (std::shared_ptr<RouterInfo> r);
|
||||||
|
void Fail ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
IdentHash m_Destination;
|
||||||
|
bool m_IsExploratory;
|
||||||
|
std::set<IdentHash> m_ExcludedPeers;
|
||||||
|
uint64_t m_CreationTime;
|
||||||
|
RequestComplete m_RequestComplete;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetDbRequests
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr);
|
||||||
|
void RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r);
|
||||||
|
std::shared_ptr<RequestedDestination> FindRequest (const IdentHash& ident) const;
|
||||||
|
void ManageRequests ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::mutex m_RequestedDestinationsMutex;
|
||||||
|
std::map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
182
Profiling.cpp
Normal file
182
Profiling.cpp
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#include <sys/stat.h>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Profiling.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
|
||||||
|
|
||||||
|
RouterProfile::RouterProfile (const IdentHash& identHash):
|
||||||
|
m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()),
|
||||||
|
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
|
||||||
|
m_NumTimesTaken (0), m_NumTimesRejected (0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::posix_time::ptime RouterProfile::GetTime () const
|
||||||
|
{
|
||||||
|
return boost::posix_time::second_clock::local_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterProfile::UpdateTime ()
|
||||||
|
{
|
||||||
|
m_LastUpdateTime = GetTime ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterProfile::Save ()
|
||||||
|
{
|
||||||
|
// fill sections
|
||||||
|
boost::property_tree::ptree participation;
|
||||||
|
participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed);
|
||||||
|
participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined);
|
||||||
|
participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied);
|
||||||
|
boost::property_tree::ptree usage;
|
||||||
|
usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken);
|
||||||
|
usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected);
|
||||||
|
// fill property tree
|
||||||
|
boost::property_tree::ptree pt;
|
||||||
|
pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime));
|
||||||
|
pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation);
|
||||||
|
pt.put_child (PEER_PROFILE_SECTION_USAGE, usage);
|
||||||
|
|
||||||
|
// save to file
|
||||||
|
std::string ident = m_IdentHash.ToBase64 ();
|
||||||
|
std::string path = m_ProfilesStorage.Path(ident);
|
||||||
|
|
||||||
|
try {
|
||||||
|
boost::property_tree::write_ini (path, pt);
|
||||||
|
} catch (std::exception& ex) {
|
||||||
|
/* boost exception verbose enough */
|
||||||
|
LogPrint (eLogError, "Profiling: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterProfile::Load ()
|
||||||
|
{
|
||||||
|
std::string ident = m_IdentHash.ToBase64 ();
|
||||||
|
std::string path = m_ProfilesStorage.Path(ident);
|
||||||
|
boost::property_tree::ptree pt;
|
||||||
|
|
||||||
|
if (!i2p::fs::Exists(path)) {
|
||||||
|
LogPrint(eLogWarning, "Profiling: no profile yet for ", ident);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boost::property_tree::read_ini (path, pt);
|
||||||
|
} catch (std::exception& ex) {
|
||||||
|
/* boost exception verbose enough */
|
||||||
|
LogPrint (eLogError, "Profiling: ", ex.what ());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
|
||||||
|
if (t.length () > 0)
|
||||||
|
m_LastUpdateTime = boost::posix_time::time_from_string (t);
|
||||||
|
if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) {
|
||||||
|
try {
|
||||||
|
// read participations
|
||||||
|
auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION);
|
||||||
|
m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0);
|
||||||
|
m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0);
|
||||||
|
m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0);
|
||||||
|
} catch (boost::property_tree::ptree_bad_path& ex) {
|
||||||
|
LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_PARTICIPATION, " in profile for ", ident);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// read usage
|
||||||
|
auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE);
|
||||||
|
m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0);
|
||||||
|
m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0);
|
||||||
|
} catch (boost::property_tree::ptree_bad_path& ex) {
|
||||||
|
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*this = RouterProfile (m_IdentHash);
|
||||||
|
}
|
||||||
|
} catch (std::exception& ex) {
|
||||||
|
LogPrint (eLogError, "Profiling: Can't read profile ", ident, " :", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterProfile::TunnelBuildResponse (uint8_t ret)
|
||||||
|
{
|
||||||
|
UpdateTime ();
|
||||||
|
if (ret > 0)
|
||||||
|
m_NumTunnelsDeclined++;
|
||||||
|
else
|
||||||
|
m_NumTunnelsAgreed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterProfile::TunnelNonReplied ()
|
||||||
|
{
|
||||||
|
m_NumTunnelsNonReplied++;
|
||||||
|
UpdateTime ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterProfile::IsLowPartcipationRate () const
|
||||||
|
{
|
||||||
|
return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterProfile::IsLowReplyRate () const
|
||||||
|
{
|
||||||
|
auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined;
|
||||||
|
return m_NumTunnelsNonReplied > 10*(total + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterProfile::IsBad ()
|
||||||
|
{
|
||||||
|
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
|
||||||
|
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
|
||||||
|
{
|
||||||
|
// reset profile
|
||||||
|
m_NumTunnelsAgreed = 0;
|
||||||
|
m_NumTunnelsDeclined = 0;
|
||||||
|
m_NumTunnelsNonReplied = 0;
|
||||||
|
isBad = false;
|
||||||
|
}
|
||||||
|
if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++;
|
||||||
|
return isBad;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash)
|
||||||
|
{
|
||||||
|
auto profile = std::make_shared<RouterProfile> (identHash);
|
||||||
|
profile->Load (); // if possible
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitProfilesStorage ()
|
||||||
|
{
|
||||||
|
m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
|
||||||
|
m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteObsoleteProfiles ()
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
std::time_t now = std::time(nullptr);
|
||||||
|
|
||||||
|
std::vector<std::string> files;
|
||||||
|
m_ProfilesStorage.Traverse(files);
|
||||||
|
for (auto path: files) {
|
||||||
|
if (stat(path.c_str(), &st) != 0) {
|
||||||
|
LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) {
|
||||||
|
LogPrint(eLogDebug, "Profiling: removing expired peer profile: ", path);
|
||||||
|
i2p::fs::Remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Profiling.h
Normal file
68
Profiling.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef PROFILING_H__
|
||||||
|
#define PROFILING_H__
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
// sections
|
||||||
|
const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation";
|
||||||
|
const char PEER_PROFILE_SECTION_USAGE[] = "usage";
|
||||||
|
// params
|
||||||
|
const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime";
|
||||||
|
const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed";
|
||||||
|
const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined";
|
||||||
|
const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied";
|
||||||
|
const char PEER_PROFILE_USAGE_TAKEN[] = "taken";
|
||||||
|
const char PEER_PROFILE_USAGE_REJECTED[] = "rejected";
|
||||||
|
|
||||||
|
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days)
|
||||||
|
|
||||||
|
class RouterProfile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
RouterProfile (const IdentHash& identHash);
|
||||||
|
RouterProfile& operator= (const RouterProfile& ) = default;
|
||||||
|
|
||||||
|
void Save ();
|
||||||
|
void Load ();
|
||||||
|
|
||||||
|
bool IsBad ();
|
||||||
|
|
||||||
|
void TunnelBuildResponse (uint8_t ret);
|
||||||
|
void TunnelNonReplied ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
boost::posix_time::ptime GetTime () const;
|
||||||
|
void UpdateTime ();
|
||||||
|
|
||||||
|
bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
|
||||||
|
bool IsLowPartcipationRate () const;
|
||||||
|
bool IsLowReplyRate () const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
IdentHash m_IdentHash;
|
||||||
|
boost::posix_time::ptime m_LastUpdateTime;
|
||||||
|
// participation
|
||||||
|
uint32_t m_NumTunnelsAgreed;
|
||||||
|
uint32_t m_NumTunnelsDeclined;
|
||||||
|
uint32_t m_NumTunnelsNonReplied;
|
||||||
|
// usage
|
||||||
|
uint32_t m_NumTimesTaken;
|
||||||
|
uint32_t m_NumTimesRejected;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
|
||||||
|
void InitProfilesStorage ();
|
||||||
|
void DeleteObsoleteProfiles ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
123
Queue.h
Normal file
123
Queue.h
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#ifndef QUEUE_H__
|
||||||
|
#define QUEUE_H__
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
template<typename Element>
|
||||||
|
class Queue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
void Put (Element e)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
m_Queue.push (e);
|
||||||
|
m_NonEmpty.notify_one ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Put (const std::vector<Element>& vec)
|
||||||
|
{
|
||||||
|
if (!vec.empty ())
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
for (auto it: vec)
|
||||||
|
m_Queue.push (it);
|
||||||
|
m_NonEmpty.notify_one ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Element GetNext ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
auto el = GetNonThreadSafe ();
|
||||||
|
if (!el)
|
||||||
|
{
|
||||||
|
m_NonEmpty.wait (l);
|
||||||
|
el = GetNonThreadSafe ();
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
Element GetNextWithTimeout (int usec)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
auto el = GetNonThreadSafe ();
|
||||||
|
if (!el)
|
||||||
|
{
|
||||||
|
m_NonEmpty.wait_for (l, std::chrono::milliseconds (usec));
|
||||||
|
el = GetNonThreadSafe ();
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wait ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
m_NonEmpty.wait (l);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Wait (int sec, int usec)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
return m_NonEmpty.wait_for (l, std::chrono::seconds (sec) + std::chrono::milliseconds (usec)) != std::cv_status::timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEmpty ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
return m_Queue.empty ();
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetSize ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
return m_Queue.size ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WakeUp () { m_NonEmpty.notify_all (); };
|
||||||
|
|
||||||
|
Element Get ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
return GetNonThreadSafe ();
|
||||||
|
}
|
||||||
|
|
||||||
|
Element Peek ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||||
|
return GetNonThreadSafe (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Element GetNonThreadSafe (bool peek = false)
|
||||||
|
{
|
||||||
|
if (!m_Queue.empty ())
|
||||||
|
{
|
||||||
|
auto el = m_Queue.front ();
|
||||||
|
if (!peek)
|
||||||
|
m_Queue.pop ();
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::queue<Element> m_Queue;
|
||||||
|
std::mutex m_QueueMutex;
|
||||||
|
std::condition_variable m_NonEmpty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
41
README.md
41
README.md
@@ -1,21 +1,38 @@
|
|||||||
i2pd
|
i2pd
|
||||||
====
|
====
|
||||||
|
|
||||||
I2P router written in C++.
|
Independent C++ implementation of I2P router
|
||||||
|
|
||||||
Documentation for config files, command line options, build instructions and more can be found under the doc directory.
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
Downloads
|
This project is licensed under the BSD 3-clause license, which can be found in the file
|
||||||
|
LICENSE in the root of the project source code.
|
||||||
|
|
||||||
|
Donations
|
||||||
|
---------
|
||||||
|
|
||||||
|
BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY
|
||||||
|
LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59
|
||||||
|
ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z
|
||||||
|
DOGE: DNXLQKziRPAsD9H3DFNjk4fLQrdaSX893Y
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
--------------
|
||||||
|
http://i2pd.readthedocs.org
|
||||||
|
|
||||||
|
Supported OS
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Official binary releases could be found at:
|
* Linux x86/x64 - [](https://travis-ci.org/PurpleI2P/i2pd)
|
||||||
http://download.i2p.io/purplei2p/i2pd/releases/
|
* Windows - [](https://ci.appveyor.com/project/PurpleI2P/i2pd)
|
||||||
|
* Mac OS X
|
||||||
|
* FreeBSD
|
||||||
|
|
||||||
|
More documentation
|
||||||
|
------------------
|
||||||
|
|
||||||
Build Statuses
|
* [Building from source / unix](docs/build_notes_unix.md)
|
||||||
---------------
|
* [Building from source / windows](docs/build_notes_windows.md)
|
||||||
|
* [Configuring your i2pd](docs/configuration.md)
|
||||||
- Linux x64 - Maintenance
|
* [Github wiki](https://github.com/PurpleI2P/i2pd/wiki/)
|
||||||
- Linux ARM - Maintenance
|
|
||||||
- Mac OS X - Maintenance
|
|
||||||
- Microsoft VC13 - To be added
|
|
||||||
|
|||||||
419
Reseed.cpp
Normal file
419
Reseed.cpp
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <boost/regex.hpp>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/asio/ssl.hpp>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Reseed.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
static std::vector<std::string> httpsReseedHostList =
|
||||||
|
{
|
||||||
|
"https://reseed.i2p-projekt.de/", // Only HTTPS
|
||||||
|
"https://i2p.mooo.com/netDb/",
|
||||||
|
"https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required
|
||||||
|
"https://us.reseed.i2p2.no:444/",
|
||||||
|
"https://uk.reseed.i2p2.no:444/",
|
||||||
|
"https://i2p.manas.ca:8443/",
|
||||||
|
"https://i2p-0.manas.ca:8443/",
|
||||||
|
"https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support
|
||||||
|
"https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support
|
||||||
|
"https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support
|
||||||
|
};
|
||||||
|
|
||||||
|
Reseeder::Reseeder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Reseeder::~Reseeder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int Reseeder::ReseedNowSU3 ()
|
||||||
|
{
|
||||||
|
auto ind = rand () % httpsReseedHostList.size ();
|
||||||
|
std::string& reseedHost = httpsReseedHostList[ind];
|
||||||
|
return ReseedFromSU3 (reseedHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Reseeder::ReseedFromSU3 (const std::string& host)
|
||||||
|
{
|
||||||
|
std::string url = host + "i2pseeds.su3";
|
||||||
|
LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", host);
|
||||||
|
std::string su3 = HttpsRequest (url);
|
||||||
|
if (su3.length () > 0)
|
||||||
|
{
|
||||||
|
std::stringstream s(su3);
|
||||||
|
return ProcessSU3Stream (s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Reseed: SU3 download failed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Reseeder::ProcessSU3File (const char * filename)
|
||||||
|
{
|
||||||
|
std::ifstream s(filename, std::ifstream::binary);
|
||||||
|
if (s.is_open ())
|
||||||
|
return ProcessSU3Stream (s);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Reseed: Can't open file ", filename);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char SU3_MAGIC_NUMBER[]="I2Psu3";
|
||||||
|
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50;
|
||||||
|
const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50;
|
||||||
|
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008;
|
||||||
|
int Reseeder::ProcessSU3Stream (std::istream& s)
|
||||||
|
{
|
||||||
|
char magicNumber[7];
|
||||||
|
s.read (magicNumber, 7); // magic number and zero byte 6
|
||||||
|
if (strcmp (magicNumber, SU3_MAGIC_NUMBER))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Reseed: Unexpected SU3 magic number");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
s.seekg (1, std::ios::cur); // su3 file format version
|
||||||
|
SigningKeyType signatureType;
|
||||||
|
s.read ((char *)&signatureType, 2); // signature type
|
||||||
|
signatureType = be16toh (signatureType);
|
||||||
|
uint16_t signatureLength;
|
||||||
|
s.read ((char *)&signatureLength, 2); // signature length
|
||||||
|
signatureLength = be16toh (signatureLength);
|
||||||
|
s.seekg (1, std::ios::cur); // unused
|
||||||
|
uint8_t versionLength;
|
||||||
|
s.read ((char *)&versionLength, 1); // version length
|
||||||
|
s.seekg (1, std::ios::cur); // unused
|
||||||
|
uint8_t signerIDLength;
|
||||||
|
s.read ((char *)&signerIDLength, 1); // signer ID length
|
||||||
|
uint64_t contentLength;
|
||||||
|
s.read ((char *)&contentLength, 8); // content length
|
||||||
|
contentLength = be64toh (contentLength);
|
||||||
|
s.seekg (1, std::ios::cur); // unused
|
||||||
|
uint8_t fileType;
|
||||||
|
s.read ((char *)&fileType, 1); // file type
|
||||||
|
if (fileType != 0x00) // zip file
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
s.seekg (1, std::ios::cur); // unused
|
||||||
|
uint8_t contentType;
|
||||||
|
s.read ((char *)&contentType, 1); // content type
|
||||||
|
if (contentType != 0x03) // reseed data
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
s.seekg (12, std::ios::cur); // unused
|
||||||
|
|
||||||
|
s.seekg (versionLength, std::ios::cur); // skip version
|
||||||
|
char signerID[256];
|
||||||
|
s.read (signerID, signerIDLength); // signerID
|
||||||
|
signerID[signerIDLength] = 0;
|
||||||
|
|
||||||
|
//try to verify signature
|
||||||
|
auto it = m_SigningKeys.find (signerID);
|
||||||
|
if (it != m_SigningKeys.end ())
|
||||||
|
{
|
||||||
|
// TODO: implement all signature types
|
||||||
|
if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096)
|
||||||
|
{
|
||||||
|
size_t pos = s.tellg ();
|
||||||
|
size_t tbsLen = pos + contentLength;
|
||||||
|
uint8_t * tbs = new uint8_t[tbsLen];
|
||||||
|
s.seekg (0, std::ios::beg);
|
||||||
|
s.read ((char *)tbs, tbsLen);
|
||||||
|
uint8_t * signature = new uint8_t[signatureLength];
|
||||||
|
s.read ((char *)signature, signatureLength);
|
||||||
|
// RSA-raw
|
||||||
|
{
|
||||||
|
// calculate digest
|
||||||
|
uint8_t digest[64];
|
||||||
|
SHA512 (tbs, tbsLen, digest);
|
||||||
|
// encrypt signature
|
||||||
|
BN_CTX * bnctx = BN_CTX_new ();
|
||||||
|
BIGNUM * s = BN_new (), * n = BN_new ();
|
||||||
|
BN_bin2bn (signature, signatureLength, s);
|
||||||
|
BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n);
|
||||||
|
BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n
|
||||||
|
uint8_t * enSigBuf = new uint8_t[signatureLength];
|
||||||
|
i2p::crypto::bn2buf (s, enSigBuf, signatureLength);
|
||||||
|
// digest is right aligned
|
||||||
|
// we can't use RSA_verify due wrong padding in SU3
|
||||||
|
if (memcmp (enSigBuf + (signatureLength - 64), digest, 64))
|
||||||
|
LogPrint (eLogWarning, "Reseed: SU3 signature verification failed");
|
||||||
|
delete[] enSigBuf;
|
||||||
|
BN_free (s); BN_free (n);
|
||||||
|
BN_CTX_free (bnctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] signature;
|
||||||
|
delete[] tbs;
|
||||||
|
s.seekg (pos, std::ios::beg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded");
|
||||||
|
|
||||||
|
// handle content
|
||||||
|
int numFiles = 0;
|
||||||
|
size_t contentPos = s.tellg ();
|
||||||
|
while (!s.eof ())
|
||||||
|
{
|
||||||
|
uint32_t signature;
|
||||||
|
s.read ((char *)&signature, 4);
|
||||||
|
signature = le32toh (signature);
|
||||||
|
if (signature == ZIP_HEADER_SIGNATURE)
|
||||||
|
{
|
||||||
|
// next local file
|
||||||
|
s.seekg (2, std::ios::cur); // version
|
||||||
|
uint16_t bitFlag;
|
||||||
|
s.read ((char *)&bitFlag, 2);
|
||||||
|
bitFlag = le16toh (bitFlag);
|
||||||
|
uint16_t compressionMethod;
|
||||||
|
s.read ((char *)&compressionMethod, 2);
|
||||||
|
compressionMethod = le16toh (compressionMethod);
|
||||||
|
s.seekg (4, std::ios::cur); // skip fields we don't care about
|
||||||
|
uint32_t compressedSize, uncompressedSize;
|
||||||
|
uint32_t crc_32;
|
||||||
|
s.read ((char *)&crc_32, 4);
|
||||||
|
crc_32 = le32toh (crc_32);
|
||||||
|
s.read ((char *)&compressedSize, 4);
|
||||||
|
compressedSize = le32toh (compressedSize);
|
||||||
|
s.read ((char *)&uncompressedSize, 4);
|
||||||
|
uncompressedSize = le32toh (uncompressedSize);
|
||||||
|
uint16_t fileNameLength, extraFieldLength;
|
||||||
|
s.read ((char *)&fileNameLength, 2);
|
||||||
|
fileNameLength = le16toh (fileNameLength);
|
||||||
|
if ( fileNameLength > 255 ) {
|
||||||
|
// too big
|
||||||
|
LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength);
|
||||||
|
return numFiles;
|
||||||
|
}
|
||||||
|
s.read ((char *)&extraFieldLength, 2);
|
||||||
|
extraFieldLength = le16toh (extraFieldLength);
|
||||||
|
char localFileName[255];
|
||||||
|
s.read (localFileName, fileNameLength);
|
||||||
|
localFileName[fileNameLength] = 0;
|
||||||
|
s.seekg (extraFieldLength, std::ios::cur);
|
||||||
|
// take care about data desriptor if presented
|
||||||
|
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
|
||||||
|
{
|
||||||
|
size_t pos = s.tellg ();
|
||||||
|
if (!FindZipDataDescriptor (s))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found");
|
||||||
|
return numFiles;
|
||||||
|
}
|
||||||
|
s.read ((char *)&crc_32, 4);
|
||||||
|
crc_32 = le32toh (crc_32);
|
||||||
|
s.read ((char *)&compressedSize, 4);
|
||||||
|
compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data
|
||||||
|
s.read ((char *)&uncompressedSize, 4);
|
||||||
|
uncompressedSize = le32toh (uncompressedSize);
|
||||||
|
|
||||||
|
// now we know compressed and uncompressed size
|
||||||
|
s.seekg (pos, std::ios::beg); // back to compressed data
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint (eLogDebug, "Reseed: Proccessing file ", localFileName, " ", compressedSize, " bytes");
|
||||||
|
if (!compressedSize)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t * compressed = new uint8_t[compressedSize];
|
||||||
|
s.read ((char *)compressed, compressedSize);
|
||||||
|
if (compressionMethod) // we assume Deflate
|
||||||
|
{
|
||||||
|
z_stream inflator;
|
||||||
|
memset (&inflator, 0, sizeof (inflator));
|
||||||
|
inflateInit2 (&inflator, -MAX_WBITS); // no zlib header
|
||||||
|
uint8_t * uncompressed = new uint8_t[uncompressedSize];
|
||||||
|
inflator.next_in = compressed;
|
||||||
|
inflator.avail_in = compressedSize;
|
||||||
|
inflator.next_out = uncompressed;
|
||||||
|
inflator.avail_out = uncompressedSize;
|
||||||
|
int err;
|
||||||
|
if ((err = inflate (&inflator, Z_SYNC_FLUSH)) >= 0)
|
||||||
|
{
|
||||||
|
uncompressedSize -= inflator.avail_out;
|
||||||
|
if (crc32 (0, uncompressed, uncompressedSize) == crc_32)
|
||||||
|
{
|
||||||
|
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize);
|
||||||
|
numFiles++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Reseed: CRC32 verification failed");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Reseed: SU3 decompression error ", err);
|
||||||
|
delete[] uncompressed;
|
||||||
|
inflateEnd (&inflator);
|
||||||
|
}
|
||||||
|
else // no compression
|
||||||
|
{
|
||||||
|
i2p::data::netdb.AddRouterInfo (compressed, compressedSize);
|
||||||
|
numFiles++;
|
||||||
|
}
|
||||||
|
delete[] compressed;
|
||||||
|
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
|
||||||
|
s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE)
|
||||||
|
LogPrint (eLogWarning, "Reseed: Missing zip central directory header");
|
||||||
|
break; // no more files
|
||||||
|
}
|
||||||
|
size_t end = s.tellg ();
|
||||||
|
if (end - contentPos >= contentLength)
|
||||||
|
break; // we are beyond contentLength
|
||||||
|
}
|
||||||
|
return numFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = { 0x50, 0x4B, 0x07, 0x08 };
|
||||||
|
bool Reseeder::FindZipDataDescriptor (std::istream& s)
|
||||||
|
{
|
||||||
|
size_t nextInd = 0;
|
||||||
|
while (!s.eof ())
|
||||||
|
{
|
||||||
|
uint8_t nextByte;
|
||||||
|
s.read ((char *)&nextByte, 1);
|
||||||
|
if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd])
|
||||||
|
{
|
||||||
|
nextInd++;
|
||||||
|
if (nextInd >= sizeof (ZIP_DATA_DESCRIPTOR_SIGNATURE))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
nextInd = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reseeder::LoadCertificate (const std::string& filename)
|
||||||
|
{
|
||||||
|
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
|
||||||
|
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
SSL * ssl = SSL_new (ctx);
|
||||||
|
X509 * cert = SSL_get_certificate (ssl);
|
||||||
|
// verify
|
||||||
|
if (cert)
|
||||||
|
{
|
||||||
|
// extract issuer name
|
||||||
|
char name[100];
|
||||||
|
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
|
||||||
|
// extract RSA key (we need n only, e = 65537)
|
||||||
|
RSA * key = X509_get_pubkey (cert)->pkey.rsa;
|
||||||
|
PublicKey value;
|
||||||
|
i2p::crypto::bn2buf (key->n, value, 512);
|
||||||
|
m_SigningKeys[name] = value;
|
||||||
|
}
|
||||||
|
SSL_free (ssl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Reseed: Can't open certificate file ", filename);
|
||||||
|
SSL_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reseeder::LoadCertificates ()
|
||||||
|
{
|
||||||
|
std::string certDir = i2p::fs::DataDirPath("certificates", "reseed");
|
||||||
|
std::vector<std::string> files;
|
||||||
|
int numCertificates = 0;
|
||||||
|
|
||||||
|
if (!i2p::fs::ReadDir(certDir, files)) {
|
||||||
|
LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string & file : files) {
|
||||||
|
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
|
||||||
|
LogPrint(eLogWarning, "Reseed: ignoring file ", file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LoadCertificate (file);
|
||||||
|
numCertificates++;
|
||||||
|
}
|
||||||
|
LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Reseeder::HttpsRequest (const std::string& address)
|
||||||
|
{
|
||||||
|
i2p::util::http::url u(address);
|
||||||
|
if (u.port_ == 80) u.port_ = 443;
|
||||||
|
|
||||||
|
boost::asio::io_service service;
|
||||||
|
boost::system::error_code ecode;
|
||||||
|
auto it = boost::asio::ip::tcp::resolver(service).resolve (
|
||||||
|
boost::asio::ip::tcp::resolver::query (u.host_, std::to_string (u.port_)), ecode);
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23);
|
||||||
|
ctx.set_verify_mode(boost::asio::ssl::context::verify_none);
|
||||||
|
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> s(service, ctx);
|
||||||
|
s.lowest_layer().connect (*it, ecode);
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
s.handshake (boost::asio::ssl::stream_base::client, ecode);
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Reseed: Connected to ", u.host_, ":", u.port_);
|
||||||
|
// send request
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_
|
||||||
|
<< "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n";
|
||||||
|
s.write_some (boost::asio::buffer (ss.str ()));
|
||||||
|
// read response
|
||||||
|
std::stringstream rs;
|
||||||
|
char response[1024]; size_t l = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
l = s.read_some (boost::asio::buffer (response, 1024), ecode);
|
||||||
|
if (l) rs.write (response, l);
|
||||||
|
}
|
||||||
|
while (!ecode && l);
|
||||||
|
// process response
|
||||||
|
return i2p::util::http::GetHttpContent (rs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Reseed: Couldn't connect to ", u.host_, ": ", ecode.message ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Reseed: Couldn't resolve address ", u.host_, ": ", ecode.message ());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
47
Reseed.h
Normal file
47
Reseed.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef RESEED_H
|
||||||
|
#define RESEED_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
|
||||||
|
class Reseeder
|
||||||
|
{
|
||||||
|
typedef Tag<512> PublicKey;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Reseeder();
|
||||||
|
~Reseeder();
|
||||||
|
int ReseedNowSU3 ();
|
||||||
|
|
||||||
|
void LoadCertificates ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void LoadCertificate (const std::string& filename);
|
||||||
|
|
||||||
|
int ReseedFromSU3 (const std::string& host);
|
||||||
|
int ProcessSU3File (const char * filename);
|
||||||
|
int ProcessSU3Stream (std::istream& s);
|
||||||
|
|
||||||
|
bool FindZipDataDescriptor (std::istream& s);
|
||||||
|
|
||||||
|
std::string HttpsRequest (const std::string& address);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::map<std::string, PublicKey> m_SigningKeys;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
425
RouterContext.cpp
Normal file
425
RouterContext.cpp
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Family.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
RouterContext context;
|
||||||
|
|
||||||
|
RouterContext::RouterContext ():
|
||||||
|
m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false),
|
||||||
|
m_StartupTime (0), m_Status (eRouterStatusOK )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::Init ()
|
||||||
|
{
|
||||||
|
srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
|
||||||
|
m_StartupTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (!Load ())
|
||||||
|
CreateNewRouter ();
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::CreateNewRouter ()
|
||||||
|
{
|
||||||
|
#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
|
||||||
|
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519);
|
||||||
|
#else
|
||||||
|
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1);
|
||||||
|
#endif
|
||||||
|
SaveKeys ();
|
||||||
|
NewRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::NewRouterInfo ()
|
||||||
|
{
|
||||||
|
i2p::data::RouterInfo routerInfo;
|
||||||
|
routerInfo.SetRouterIdentity (GetIdentity ());
|
||||||
|
uint16_t port; i2p::config::GetOption("port", port);
|
||||||
|
if (!port)
|
||||||
|
port = rand () % (30777 - 9111) + 9111; // I2P network ports range
|
||||||
|
std::string host; i2p::config::GetOption("host", host);
|
||||||
|
if (i2p::config::IsDefault("host"))
|
||||||
|
host = "127.0.0.1"; // replace default address with safe value
|
||||||
|
routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ());
|
||||||
|
routerInfo.AddNTCPAddress (host.c_str(), port);
|
||||||
|
routerInfo.SetCaps (i2p::data::RouterInfo::eReachable |
|
||||||
|
i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC
|
||||||
|
routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID));
|
||||||
|
routerInfo.SetProperty ("router.version", I2P_VERSION);
|
||||||
|
routerInfo.CreateBuffer (m_Keys);
|
||||||
|
m_RouterInfo.SetRouterIdentity (GetIdentity ());
|
||||||
|
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::UpdateRouterInfo ()
|
||||||
|
{
|
||||||
|
m_RouterInfo.CreateBuffer (m_Keys);
|
||||||
|
m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
|
||||||
|
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetStatus (RouterStatus status)
|
||||||
|
{
|
||||||
|
if (status != m_Status)
|
||||||
|
{
|
||||||
|
m_Status = status;
|
||||||
|
switch (m_Status)
|
||||||
|
{
|
||||||
|
case eRouterStatusOK:
|
||||||
|
SetReachable ();
|
||||||
|
break;
|
||||||
|
case eRouterStatusFirewalled:
|
||||||
|
SetUnreachable ();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::UpdatePort (int port)
|
||||||
|
{
|
||||||
|
bool updated = false;
|
||||||
|
for (auto address : m_RouterInfo.GetAddresses ())
|
||||||
|
{
|
||||||
|
if (address->port != port)
|
||||||
|
{
|
||||||
|
address->port = port;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updated)
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::UpdateAddress (const boost::asio::ip::address& host)
|
||||||
|
{
|
||||||
|
bool updated = false;
|
||||||
|
for (auto address : m_RouterInfo.GetAddresses ())
|
||||||
|
{
|
||||||
|
if (address->host != host && address->IsCompatible (host))
|
||||||
|
{
|
||||||
|
address->host = host;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL)
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer)
|
||||||
|
{
|
||||||
|
bool ret = m_RouterInfo.AddIntroducer (introducer);
|
||||||
|
if (ret)
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
|
||||||
|
{
|
||||||
|
if (m_RouterInfo.RemoveIntroducer (e))
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetFloodfill (bool floodfill)
|
||||||
|
{
|
||||||
|
m_IsFloodfill = floodfill;
|
||||||
|
if (floodfill)
|
||||||
|
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
|
||||||
|
// we don't publish number of routers and leaseset for non-floodfill
|
||||||
|
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS);
|
||||||
|
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS);
|
||||||
|
}
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RouterContext::GetFamily () const
|
||||||
|
{
|
||||||
|
return m_RouterInfo.GetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetFamily (const std::string& family)
|
||||||
|
{
|
||||||
|
std::string signature;
|
||||||
|
if (family.length () > 0)
|
||||||
|
signature = i2p::data::CreateFamilySignature (family, GetIdentHash ());
|
||||||
|
if (signature.length () > 0)
|
||||||
|
{
|
||||||
|
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY, family);
|
||||||
|
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG, signature);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY);
|
||||||
|
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetBandwidth (char L) {
|
||||||
|
uint16_t limit = 0;
|
||||||
|
enum { low, high, extra } type = high;
|
||||||
|
/* detect parameters */
|
||||||
|
switch (L)
|
||||||
|
{
|
||||||
|
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break;
|
||||||
|
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = 48; type = low; break;
|
||||||
|
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break;
|
||||||
|
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break;
|
||||||
|
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = 256; type = high; break;
|
||||||
|
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break;
|
||||||
|
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 9999; type = extra; break;
|
||||||
|
default:
|
||||||
|
limit = 48; type = low;
|
||||||
|
}
|
||||||
|
/* update caps & flags in RI */
|
||||||
|
auto caps = m_RouterInfo.GetCaps ();
|
||||||
|
caps &= ~i2p::data::RouterInfo::eHighBandwidth;
|
||||||
|
caps &= ~i2p::data::RouterInfo::eExtraBandwidth;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case low : /* not set */; break;
|
||||||
|
case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break;
|
||||||
|
case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break;
|
||||||
|
}
|
||||||
|
m_RouterInfo.SetCaps (caps);
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
m_BandwidthLimit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetBandwidth (int limit)
|
||||||
|
{
|
||||||
|
if (limit > 2000) { SetBandwidth('X'); }
|
||||||
|
else if (limit > 256) { SetBandwidth('P'); }
|
||||||
|
else if (limit > 128) { SetBandwidth('O'); }
|
||||||
|
else if (limit > 64) { SetBandwidth('N'); }
|
||||||
|
else if (limit > 48) { SetBandwidth('M'); }
|
||||||
|
else if (limit > 12) { SetBandwidth('L'); }
|
||||||
|
else { SetBandwidth('K'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterContext::IsUnreachable () const
|
||||||
|
{
|
||||||
|
return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetUnreachable ()
|
||||||
|
{
|
||||||
|
// set caps
|
||||||
|
m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B
|
||||||
|
// remove NTCP address
|
||||||
|
auto& addresses = m_RouterInfo.GetAddresses ();
|
||||||
|
for (size_t i = 0; i < addresses.size (); i++)
|
||||||
|
{
|
||||||
|
if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
|
||||||
|
{
|
||||||
|
addresses.erase (addresses.begin () + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete previous introducers
|
||||||
|
for (auto addr : addresses)
|
||||||
|
addr->introducers.clear ();
|
||||||
|
|
||||||
|
// update
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetReachable ()
|
||||||
|
{
|
||||||
|
// update caps
|
||||||
|
uint8_t caps = m_RouterInfo.GetCaps ();
|
||||||
|
caps &= ~i2p::data::RouterInfo::eUnreachable;
|
||||||
|
caps |= i2p::data::RouterInfo::eReachable;
|
||||||
|
caps |= i2p::data::RouterInfo::eSSUIntroducer;
|
||||||
|
if (m_IsFloodfill)
|
||||||
|
caps |= i2p::data::RouterInfo::eFloodfill;
|
||||||
|
m_RouterInfo.SetCaps (caps);
|
||||||
|
|
||||||
|
// insert NTCP back
|
||||||
|
auto& addresses = m_RouterInfo.GetAddresses ();
|
||||||
|
for (size_t i = 0; i < addresses.size (); i++)
|
||||||
|
{
|
||||||
|
if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU)
|
||||||
|
{
|
||||||
|
// insert NTCP address with host/port from SSU
|
||||||
|
m_RouterInfo.AddNTCPAddress (addresses[i]->host.to_string ().c_str (), addresses[i]->port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete previous introducers
|
||||||
|
for (auto addr : addresses)
|
||||||
|
addr->introducers.clear ();
|
||||||
|
|
||||||
|
// update
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetSupportsV6 (bool supportsV6)
|
||||||
|
{
|
||||||
|
if (supportsV6)
|
||||||
|
m_RouterInfo.EnableV6 ();
|
||||||
|
else
|
||||||
|
m_RouterInfo.DisableV6 ();
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SetSupportsV4 (bool supportsV4)
|
||||||
|
{
|
||||||
|
if (supportsV4)
|
||||||
|
m_RouterInfo.EnableV4 ();
|
||||||
|
else
|
||||||
|
m_RouterInfo.DisableV4 ();
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host)
|
||||||
|
{
|
||||||
|
bool updated = false, found = false;
|
||||||
|
int port = 0;
|
||||||
|
auto& addresses = m_RouterInfo.GetAddresses ();
|
||||||
|
for (auto addr: addresses)
|
||||||
|
{
|
||||||
|
if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
|
||||||
|
{
|
||||||
|
if (addr->host != host)
|
||||||
|
{
|
||||||
|
addr->host = host;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
port = addr->port;
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
// create new address
|
||||||
|
m_RouterInfo.AddNTCPAddress (host.to_string ().c_str (), port);
|
||||||
|
auto mtu = i2p::util::net::GetMTU (host);
|
||||||
|
if (mtu)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu);
|
||||||
|
if (mtu > 1472) { // TODO: magic constant
|
||||||
|
mtu = 1472;
|
||||||
|
LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (updated)
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::UpdateStats ()
|
||||||
|
{
|
||||||
|
if (m_IsFloodfill)
|
||||||
|
{
|
||||||
|
// update routers and leasesets
|
||||||
|
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast<std::string>(i2p::data::netdb.GetNumLeaseSets ()));
|
||||||
|
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast<std::string>(i2p::data::netdb.GetNumRouters ()));
|
||||||
|
UpdateRouterInfo ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterContext::Load ()
|
||||||
|
{
|
||||||
|
std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary);
|
||||||
|
if (!fk.is_open ()) return false;
|
||||||
|
fk.seekg (0, std::ios::end);
|
||||||
|
size_t len = fk.tellg();
|
||||||
|
fk.seekg (0, std::ios::beg);
|
||||||
|
|
||||||
|
if (len == sizeof (i2p::data::Keys)) // old keys file format
|
||||||
|
{
|
||||||
|
i2p::data::Keys keys;
|
||||||
|
fk.read ((char *)&keys, sizeof (keys));
|
||||||
|
m_Keys = keys;
|
||||||
|
}
|
||||||
|
else // new keys file format
|
||||||
|
{
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
fk.read ((char *)buf, len);
|
||||||
|
m_Keys.FromBuffer (buf, len);
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_RouterInfo.SetRouterIdentity (GetIdentity ());
|
||||||
|
i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO));
|
||||||
|
if (!routerInfo.IsUnreachable ()) // router.info looks good
|
||||||
|
{
|
||||||
|
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
|
||||||
|
m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION);
|
||||||
|
m_RouterInfo.SetProperty ("router.version", I2P_VERSION);
|
||||||
|
|
||||||
|
// Migration to 0.9.24. TODO: remove later
|
||||||
|
m_RouterInfo.DeleteProperty ("coreVersion");
|
||||||
|
m_RouterInfo.DeleteProperty ("stat_uptime");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, ROUTER_INFO, " is malformed. Creating new");
|
||||||
|
NewRouterInfo ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsUnreachable ())
|
||||||
|
SetReachable (); // we assume reachable until we discover firewall through peer tests
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::SaveKeys ()
|
||||||
|
{
|
||||||
|
// save in the same format as .dat files
|
||||||
|
std::ofstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ofstream::binary | std::ofstream::out);
|
||||||
|
size_t len = m_Keys.GetFullLen ();
|
||||||
|
uint8_t * buf = new uint8_t[len];
|
||||||
|
m_Keys.ToBuffer (buf, len);
|
||||||
|
fk.write ((char *)buf, len);
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::tunnel::TunnelPool> RouterContext::GetTunnelPool () const
|
||||||
|
{
|
||||||
|
return i2p::tunnel::tunnels.GetExploratoryPool ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||||
|
{
|
||||||
|
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_GarlicMutex);
|
||||||
|
i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_GarlicMutex);
|
||||||
|
i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RouterContext::GetUptime () const
|
||||||
|
{
|
||||||
|
return i2p::util::GetSecondsSinceEpoch () - m_StartupTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
113
RouterContext.h
Normal file
113
RouterContext.h
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#ifndef ROUTER_CONTEXT_H__
|
||||||
|
#define ROUTER_CONTEXT_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "Garlic.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
const char ROUTER_INFO[] = "router.info";
|
||||||
|
const char ROUTER_KEYS[] = "router.keys";
|
||||||
|
const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes
|
||||||
|
|
||||||
|
enum RouterStatus
|
||||||
|
{
|
||||||
|
eRouterStatusOK = 0,
|
||||||
|
eRouterStatusTesting = 1,
|
||||||
|
eRouterStatusFirewalled = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
class RouterContext: public i2p::garlic::GarlicDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
RouterContext ();
|
||||||
|
void Init ();
|
||||||
|
|
||||||
|
i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; };
|
||||||
|
std::shared_ptr<const i2p::data::RouterInfo> GetSharedRouterInfo () const
|
||||||
|
{
|
||||||
|
return std::shared_ptr<const i2p::data::RouterInfo> (&m_RouterInfo,
|
||||||
|
[](const i2p::data::RouterInfo *) {});
|
||||||
|
}
|
||||||
|
std::shared_ptr<i2p::garlic::GarlicDestination> GetSharedDestination ()
|
||||||
|
{
|
||||||
|
return std::shared_ptr<i2p::garlic::GarlicDestination> (this,
|
||||||
|
[](i2p::garlic::GarlicDestination *) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetUptime () const;
|
||||||
|
uint32_t GetStartupTime () const { return m_StartupTime; };
|
||||||
|
uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
|
||||||
|
uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; };
|
||||||
|
RouterStatus GetStatus () const { return m_Status; };
|
||||||
|
void SetStatus (RouterStatus status);
|
||||||
|
|
||||||
|
void UpdatePort (int port); // called from Daemon
|
||||||
|
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon
|
||||||
|
bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer);
|
||||||
|
void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
|
||||||
|
bool IsUnreachable () const;
|
||||||
|
void SetUnreachable ();
|
||||||
|
void SetReachable ();
|
||||||
|
bool IsFloodfill () const { return m_IsFloodfill; };
|
||||||
|
void SetFloodfill (bool floodfill);
|
||||||
|
void SetFamily (const std::string& family);
|
||||||
|
std::string GetFamily () const;
|
||||||
|
void SetBandwidth (int limit); /* in kilobytes */
|
||||||
|
void SetBandwidth (char L); /* by letter */
|
||||||
|
bool AcceptsTunnels () const { return m_AcceptsTunnels; };
|
||||||
|
void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
|
||||||
|
bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
|
||||||
|
bool SupportsV4 () const { return m_RouterInfo.IsV4 (); };
|
||||||
|
void SetSupportsV6 (bool supportsV6);
|
||||||
|
void SetSupportsV4 (bool supportsV4);
|
||||||
|
|
||||||
|
void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session
|
||||||
|
void UpdateStats ();
|
||||||
|
|
||||||
|
// implements LocalDestination
|
||||||
|
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
|
||||||
|
const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); };
|
||||||
|
const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ()->GetStandardIdentity ().publicKey; };
|
||||||
|
void SetLeaseSetUpdated () {};
|
||||||
|
|
||||||
|
// implements GarlicDestination
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> GetLeaseSet () { return nullptr; };
|
||||||
|
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const;
|
||||||
|
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||||
|
|
||||||
|
// override GarlicDestination
|
||||||
|
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void CreateNewRouter ();
|
||||||
|
void NewRouterInfo ();
|
||||||
|
void UpdateRouterInfo ();
|
||||||
|
bool Load ();
|
||||||
|
void SaveKeys ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
i2p::data::RouterInfo m_RouterInfo;
|
||||||
|
i2p::data::PrivateKeys m_Keys;
|
||||||
|
uint64_t m_LastUpdateTime;
|
||||||
|
bool m_AcceptsTunnels, m_IsFloodfill;
|
||||||
|
uint64_t m_StartupTime; // in seconds since epoch
|
||||||
|
uint32_t m_BandwidthLimit; // allowed bandwidth
|
||||||
|
RouterStatus m_Status;
|
||||||
|
std::mutex m_GarlicMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern RouterContext context;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
787
RouterInfo.cpp
Normal file
787
RouterInfo.cpp
Normal file
@@ -0,0 +1,787 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include "version.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
RouterInfo::RouterInfo (const std::string& fullPath):
|
||||||
|
m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false),
|
||||||
|
m_SupportedTransports (0), m_Caps (0)
|
||||||
|
{
|
||||||
|
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||||
|
ReadFromFile ();
|
||||||
|
}
|
||||||
|
|
||||||
|
RouterInfo::RouterInfo (const uint8_t * buf, int len):
|
||||||
|
m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0)
|
||||||
|
{
|
||||||
|
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||||
|
memcpy (m_Buffer, buf, len);
|
||||||
|
m_BufferLen = len;
|
||||||
|
ReadFromBuffer (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
RouterInfo::~RouterInfo ()
|
||||||
|
{
|
||||||
|
delete[] m_Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::Update (const uint8_t * buf, int len)
|
||||||
|
{
|
||||||
|
// verify signature since we have indentity already
|
||||||
|
int l = len - m_RouterIdentity->GetSignatureLen ();
|
||||||
|
if (m_RouterIdentity->Verify (buf, l, buf + l))
|
||||||
|
{
|
||||||
|
// clean up
|
||||||
|
m_IsUpdated = true;
|
||||||
|
m_IsUnreachable = false;
|
||||||
|
m_SupportedTransports = 0;
|
||||||
|
m_Caps = 0;
|
||||||
|
m_Addresses.clear ();
|
||||||
|
m_Properties.clear ();
|
||||||
|
// copy buffer
|
||||||
|
if (!m_Buffer)
|
||||||
|
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||||
|
memcpy (m_Buffer, buf, len);
|
||||||
|
m_BufferLen = len;
|
||||||
|
// skip identity
|
||||||
|
size_t identityLen = m_RouterIdentity->GetFullLen ();
|
||||||
|
// read new RI
|
||||||
|
std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen));
|
||||||
|
ReadFromStream (str);
|
||||||
|
// don't delete buffer until saved to the file
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "RouterInfo: signature verification failed");
|
||||||
|
m_IsUnreachable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::SetRouterIdentity (std::shared_ptr<const IdentityEx> identity)
|
||||||
|
{
|
||||||
|
m_RouterIdentity = identity;
|
||||||
|
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::LoadFile ()
|
||||||
|
{
|
||||||
|
std::ifstream s(m_FullPath, std::ifstream::binary);
|
||||||
|
if (s.is_open ())
|
||||||
|
{
|
||||||
|
s.seekg (0,std::ios::end);
|
||||||
|
m_BufferLen = s.tellg ();
|
||||||
|
if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s.seekg(0, std::ios::beg);
|
||||||
|
if (!m_Buffer)
|
||||||
|
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||||
|
s.read((char *)m_Buffer, m_BufferLen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::ReadFromFile ()
|
||||||
|
{
|
||||||
|
if (LoadFile ())
|
||||||
|
ReadFromBuffer (false);
|
||||||
|
else
|
||||||
|
m_IsUnreachable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::ReadFromBuffer (bool verifySignature)
|
||||||
|
{
|
||||||
|
m_RouterIdentity = std::make_shared<IdentityEx>(m_Buffer, m_BufferLen);
|
||||||
|
size_t identityLen = m_RouterIdentity->GetFullLen ();
|
||||||
|
if (identityLen >= m_BufferLen)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "RouterInfo: identity length ", identityLen, " exceeds buffer size ", m_BufferLen);
|
||||||
|
m_IsUnreachable = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen));
|
||||||
|
ReadFromStream (str);
|
||||||
|
if (!str)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "RouterInfo: malformed message");
|
||||||
|
m_IsUnreachable = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (verifySignature)
|
||||||
|
{
|
||||||
|
// verify signature
|
||||||
|
int l = m_BufferLen - m_RouterIdentity->GetSignatureLen ();
|
||||||
|
if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "RouterInfo: signature verification failed");
|
||||||
|
m_IsUnreachable = true;
|
||||||
|
}
|
||||||
|
m_RouterIdentity->DropVerifier ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::ReadFromStream (std::istream& s)
|
||||||
|
{
|
||||||
|
s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
|
||||||
|
m_Timestamp = be64toh (m_Timestamp);
|
||||||
|
// read addresses
|
||||||
|
uint8_t numAddresses;
|
||||||
|
s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return;
|
||||||
|
bool introducers = false;
|
||||||
|
for (int i = 0; i < numAddresses; i++)
|
||||||
|
{
|
||||||
|
uint8_t supportedTransports = 0;
|
||||||
|
bool isValidAddress = true;
|
||||||
|
Address address;
|
||||||
|
s.read ((char *)&address.cost, sizeof (address.cost));
|
||||||
|
s.read ((char *)&address.date, sizeof (address.date));
|
||||||
|
char transportStyle[5];
|
||||||
|
ReadString (transportStyle, s);
|
||||||
|
if (!strcmp (transportStyle, "NTCP"))
|
||||||
|
address.transportStyle = eTransportNTCP;
|
||||||
|
else if (!strcmp (transportStyle, "SSU"))
|
||||||
|
address.transportStyle = eTransportSSU;
|
||||||
|
else
|
||||||
|
address.transportStyle = eTransportUnknown;
|
||||||
|
address.port = 0;
|
||||||
|
address.mtu = 0;
|
||||||
|
uint16_t size, r = 0;
|
||||||
|
s.read ((char *)&size, sizeof (size)); if (!s) return;
|
||||||
|
size = be16toh (size);
|
||||||
|
while (r < size)
|
||||||
|
{
|
||||||
|
char key[500], value[500];
|
||||||
|
r += ReadString (key, s);
|
||||||
|
s.seekg (1, std::ios_base::cur); r++; // =
|
||||||
|
r += ReadString (value, s);
|
||||||
|
s.seekg (1, std::ios_base::cur); r++; // ;
|
||||||
|
if (!strcmp (key, "host"))
|
||||||
|
{
|
||||||
|
boost::system::error_code ecode;
|
||||||
|
address.host = boost::asio::ip::address::from_string (value, ecode);
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
if (address.transportStyle == eTransportNTCP)
|
||||||
|
{
|
||||||
|
supportedTransports |= eNTCPV4; // TODO:
|
||||||
|
address.addressString = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
supportedTransports |= eSSUV4; // TODO:
|
||||||
|
address.addressString = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// add supported protocol
|
||||||
|
if (address.host.is_v4 ())
|
||||||
|
supportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4;
|
||||||
|
else
|
||||||
|
supportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!strcmp (key, "port"))
|
||||||
|
address.port = boost::lexical_cast<int>(value);
|
||||||
|
else if (!strcmp (key, "mtu"))
|
||||||
|
address.mtu = boost::lexical_cast<int>(value);
|
||||||
|
else if (!strcmp (key, "key"))
|
||||||
|
Base64ToByteStream (value, strlen (value), address.key, 32);
|
||||||
|
else if (!strcmp (key, "caps"))
|
||||||
|
ExtractCaps (value);
|
||||||
|
else if (key[0] == 'i')
|
||||||
|
{
|
||||||
|
// introducers
|
||||||
|
introducers = true;
|
||||||
|
size_t l = strlen(key);
|
||||||
|
unsigned char index = key[l-1] - '0'; // TODO:
|
||||||
|
key[l-1] = 0;
|
||||||
|
if (index >= address.introducers.size ())
|
||||||
|
address.introducers.resize (index + 1);
|
||||||
|
Introducer& introducer = address.introducers.at (index);
|
||||||
|
if (!strcmp (key, "ihost"))
|
||||||
|
{
|
||||||
|
boost::system::error_code ecode;
|
||||||
|
introducer.iHost = boost::asio::ip::address::from_string (value, ecode);
|
||||||
|
}
|
||||||
|
else if (!strcmp (key, "iport"))
|
||||||
|
introducer.iPort = boost::lexical_cast<int>(value);
|
||||||
|
else if (!strcmp (key, "itag"))
|
||||||
|
introducer.iTag = boost::lexical_cast<uint32_t>(value);
|
||||||
|
else if (!strcmp (key, "ikey"))
|
||||||
|
Base64ToByteStream (value, strlen (value), introducer.iKey, 32);
|
||||||
|
}
|
||||||
|
if (!s) return;
|
||||||
|
}
|
||||||
|
if (isValidAddress)
|
||||||
|
{
|
||||||
|
m_Addresses.push_back(std::make_shared<Address>(address));
|
||||||
|
m_SupportedTransports |= supportedTransports;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read peers
|
||||||
|
uint8_t numPeers;
|
||||||
|
s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
|
||||||
|
s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
|
||||||
|
// read properties
|
||||||
|
uint16_t size, r = 0;
|
||||||
|
s.read ((char *)&size, sizeof (size)); if (!s) return;
|
||||||
|
size = be16toh (size);
|
||||||
|
while (r < size)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
char key[500], value[500];
|
||||||
|
// TODO: investigate why properties get read as one long string under Windows
|
||||||
|
// length should not be more than 44
|
||||||
|
#else
|
||||||
|
char key[50], value[50];
|
||||||
|
#endif
|
||||||
|
r += ReadString (key, s);
|
||||||
|
s.seekg (1, std::ios_base::cur); r++; // =
|
||||||
|
r += ReadString (value, s);
|
||||||
|
s.seekg (1, std::ios_base::cur); r++; // ;
|
||||||
|
m_Properties[key] = value;
|
||||||
|
|
||||||
|
// extract caps
|
||||||
|
if (!strcmp (key, "caps"))
|
||||||
|
ExtractCaps (value);
|
||||||
|
// check netId
|
||||||
|
else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != I2PD_NET_ID)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value);
|
||||||
|
m_IsUnreachable = true;
|
||||||
|
}
|
||||||
|
// family
|
||||||
|
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY))
|
||||||
|
{
|
||||||
|
m_Family = value;
|
||||||
|
boost::to_lower (m_Family);
|
||||||
|
}
|
||||||
|
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG))
|
||||||
|
{
|
||||||
|
if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value))
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "RouterInfo: family signature verification failed");
|
||||||
|
m_Family.clear ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers))
|
||||||
|
SetUnreachable (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::ExtractCaps (const char * value)
|
||||||
|
{
|
||||||
|
const char * cap = value;
|
||||||
|
while (*cap)
|
||||||
|
{
|
||||||
|
switch (*cap)
|
||||||
|
{
|
||||||
|
case CAPS_FLAG_FLOODFILL:
|
||||||
|
m_Caps |= Caps::eFloodfill;
|
||||||
|
break;
|
||||||
|
case CAPS_FLAG_HIGH_BANDWIDTH1:
|
||||||
|
case CAPS_FLAG_HIGH_BANDWIDTH2:
|
||||||
|
case CAPS_FLAG_HIGH_BANDWIDTH3:
|
||||||
|
m_Caps |= Caps::eHighBandwidth;
|
||||||
|
break;
|
||||||
|
case CAPS_FLAG_EXTRA_BANDWIDTH1:
|
||||||
|
case CAPS_FLAG_EXTRA_BANDWIDTH2:
|
||||||
|
m_Caps |= Caps::eExtraBandwidth;
|
||||||
|
break;
|
||||||
|
case CAPS_FLAG_HIDDEN:
|
||||||
|
m_Caps |= Caps::eHidden;
|
||||||
|
break;
|
||||||
|
case CAPS_FLAG_REACHABLE:
|
||||||
|
m_Caps |= Caps::eReachable;
|
||||||
|
break;
|
||||||
|
case CAPS_FLAG_UNREACHABLE:
|
||||||
|
m_Caps |= Caps::eUnreachable;
|
||||||
|
break;
|
||||||
|
case CAPS_FLAG_SSU_TESTING:
|
||||||
|
m_Caps |= Caps::eSSUTesting;
|
||||||
|
break;
|
||||||
|
case CAPS_FLAG_SSU_INTRODUCER:
|
||||||
|
m_Caps |= Caps::eSSUIntroducer;
|
||||||
|
break;
|
||||||
|
default: ;
|
||||||
|
}
|
||||||
|
cap++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::UpdateCapsProperty ()
|
||||||
|
{
|
||||||
|
std::string caps;
|
||||||
|
if (m_Caps & eFloodfill) {
|
||||||
|
caps += CAPS_FLAG_FLOODFILL; // floodfill
|
||||||
|
caps += (m_Caps & eExtraBandwidth)
|
||||||
|
? CAPS_FLAG_EXTRA_BANDWIDTH1 // 'P'
|
||||||
|
: CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
|
||||||
|
} else {
|
||||||
|
if (m_Caps & eExtraBandwidth) {
|
||||||
|
caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P'
|
||||||
|
} else if (m_Caps & eHighBandwidth) {
|
||||||
|
caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
|
||||||
|
} else {
|
||||||
|
caps += CAPS_FLAG_LOW_BANDWIDTH2; // 'L'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden
|
||||||
|
if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable
|
||||||
|
if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable
|
||||||
|
|
||||||
|
SetProperty ("caps", caps.c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::WriteToStream (std::ostream& s)
|
||||||
|
{
|
||||||
|
uint64_t ts = htobe64 (m_Timestamp);
|
||||||
|
s.write ((char *)&ts, sizeof (ts));
|
||||||
|
|
||||||
|
// addresses
|
||||||
|
uint8_t numAddresses = m_Addresses.size ();
|
||||||
|
s.write ((char *)&numAddresses, sizeof (numAddresses));
|
||||||
|
for (auto addr : m_Addresses)
|
||||||
|
{
|
||||||
|
Address& address = *addr;
|
||||||
|
s.write ((char *)&address.cost, sizeof (address.cost));
|
||||||
|
s.write ((char *)&address.date, sizeof (address.date));
|
||||||
|
std::stringstream properties;
|
||||||
|
if (address.transportStyle == eTransportNTCP)
|
||||||
|
WriteString ("NTCP", s);
|
||||||
|
else if (address.transportStyle == eTransportSSU)
|
||||||
|
{
|
||||||
|
WriteString ("SSU", s);
|
||||||
|
// caps
|
||||||
|
WriteString ("caps", properties);
|
||||||
|
properties << '=';
|
||||||
|
std::string caps;
|
||||||
|
if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING;
|
||||||
|
if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
|
||||||
|
WriteString (caps, properties);
|
||||||
|
properties << ';';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
WriteString ("", s);
|
||||||
|
|
||||||
|
WriteString ("host", properties);
|
||||||
|
properties << '=';
|
||||||
|
WriteString (address.host.to_string (), properties);
|
||||||
|
properties << ';';
|
||||||
|
if (address.transportStyle == eTransportSSU)
|
||||||
|
{
|
||||||
|
// write introducers if any
|
||||||
|
if (address.introducers.size () > 0)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (auto introducer: address.introducers)
|
||||||
|
{
|
||||||
|
WriteString ("ihost" + boost::lexical_cast<std::string>(i), properties);
|
||||||
|
properties << '=';
|
||||||
|
WriteString (introducer.iHost.to_string (), properties);
|
||||||
|
properties << ';';
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
for (auto introducer: address.introducers)
|
||||||
|
{
|
||||||
|
WriteString ("ikey" + boost::lexical_cast<std::string>(i), properties);
|
||||||
|
properties << '=';
|
||||||
|
char value[64];
|
||||||
|
size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64);
|
||||||
|
value[l] = 0;
|
||||||
|
WriteString (value, properties);
|
||||||
|
properties << ';';
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
for (auto introducer: address.introducers)
|
||||||
|
{
|
||||||
|
WriteString ("iport" + boost::lexical_cast<std::string>(i), properties);
|
||||||
|
properties << '=';
|
||||||
|
WriteString (boost::lexical_cast<std::string>(introducer.iPort), properties);
|
||||||
|
properties << ';';
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
for (auto introducer: address.introducers)
|
||||||
|
{
|
||||||
|
WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
|
||||||
|
properties << '=';
|
||||||
|
WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
|
||||||
|
properties << ';';
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write intro key
|
||||||
|
WriteString ("key", properties);
|
||||||
|
properties << '=';
|
||||||
|
char value[64];
|
||||||
|
size_t l = ByteStreamToBase64 (address.key, 32, value, 64);
|
||||||
|
value[l] = 0;
|
||||||
|
WriteString (value, properties);
|
||||||
|
properties << ';';
|
||||||
|
// write mtu
|
||||||
|
if (address.mtu)
|
||||||
|
{
|
||||||
|
WriteString ("mtu", properties);
|
||||||
|
properties << '=';
|
||||||
|
WriteString (boost::lexical_cast<std::string>(address.mtu), properties);
|
||||||
|
properties << ';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WriteString ("port", properties);
|
||||||
|
properties << '=';
|
||||||
|
WriteString (boost::lexical_cast<std::string>(address.port), properties);
|
||||||
|
properties << ';';
|
||||||
|
|
||||||
|
uint16_t size = htobe16 (properties.str ().size ());
|
||||||
|
s.write ((char *)&size, sizeof (size));
|
||||||
|
s.write (properties.str ().c_str (), properties.str ().size ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// peers
|
||||||
|
uint8_t numPeers = 0;
|
||||||
|
s.write ((char *)&numPeers, sizeof (numPeers));
|
||||||
|
|
||||||
|
// properties
|
||||||
|
std::stringstream properties;
|
||||||
|
for (auto& p : m_Properties)
|
||||||
|
{
|
||||||
|
WriteString (p.first, properties);
|
||||||
|
properties << '=';
|
||||||
|
WriteString (p.second, properties);
|
||||||
|
properties << ';';
|
||||||
|
}
|
||||||
|
uint16_t size = htobe16 (properties.str ().size ());
|
||||||
|
s.write ((char *)&size, sizeof (size));
|
||||||
|
s.write (properties.str ().c_str (), properties.str ().size ());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const
|
||||||
|
{
|
||||||
|
if (!m_RouterIdentity) return false;
|
||||||
|
size_t size = m_RouterIdentity->GetFullLen ();
|
||||||
|
if (size + 8 > len) return false;
|
||||||
|
return bufbe64toh (buf + size) > m_Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t * RouterInfo::LoadBuffer ()
|
||||||
|
{
|
||||||
|
if (!m_Buffer)
|
||||||
|
{
|
||||||
|
if (LoadFile ())
|
||||||
|
LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file");
|
||||||
|
}
|
||||||
|
return m_Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys)
|
||||||
|
{
|
||||||
|
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp
|
||||||
|
std::stringstream s;
|
||||||
|
uint8_t ident[1024];
|
||||||
|
auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024);
|
||||||
|
s.write ((char *)ident, identLen);
|
||||||
|
WriteToStream (s);
|
||||||
|
m_BufferLen = s.str ().size ();
|
||||||
|
if (!m_Buffer)
|
||||||
|
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||||
|
memcpy (m_Buffer, s.str ().c_str (), m_BufferLen);
|
||||||
|
// signature
|
||||||
|
privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen);
|
||||||
|
m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::SaveToFile (const std::string& fullPath)
|
||||||
|
{
|
||||||
|
m_FullPath = fullPath;
|
||||||
|
if (!m_Buffer) {
|
||||||
|
LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
|
||||||
|
if (!f.is_open ()) {
|
||||||
|
LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
f.write ((char *)m_Buffer, m_BufferLen);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t RouterInfo::ReadString (char * str, std::istream& s)
|
||||||
|
{
|
||||||
|
uint8_t len;
|
||||||
|
s.read ((char *)&len, 1);
|
||||||
|
s.read (str, len);
|
||||||
|
str[len] = 0;
|
||||||
|
return len+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::WriteString (const std::string& str, std::ostream& s)
|
||||||
|
{
|
||||||
|
uint8_t len = str.size ();
|
||||||
|
s.write ((char *)&len, 1);
|
||||||
|
s.write (str.c_str (), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::AddNTCPAddress (const char * host, int port)
|
||||||
|
{
|
||||||
|
auto addr = std::make_shared<Address>();
|
||||||
|
addr->host = boost::asio::ip::address::from_string (host);
|
||||||
|
addr->port = port;
|
||||||
|
addr->transportStyle = eTransportNTCP;
|
||||||
|
addr->cost = 2;
|
||||||
|
addr->date = 0;
|
||||||
|
addr->mtu = 0;
|
||||||
|
for (auto it: m_Addresses) // don't insert same address twice
|
||||||
|
if (*it == *addr) return;
|
||||||
|
m_Addresses.push_back(addr);
|
||||||
|
m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu)
|
||||||
|
{
|
||||||
|
auto addr = std::make_shared<Address>();
|
||||||
|
addr->host = boost::asio::ip::address::from_string (host);
|
||||||
|
addr->port = port;
|
||||||
|
addr->transportStyle = eTransportSSU;
|
||||||
|
addr->cost = 10; // NTCP should have priority over SSU
|
||||||
|
addr->date = 0;
|
||||||
|
addr->mtu = mtu;
|
||||||
|
memcpy (addr->key, key, 32);
|
||||||
|
for (auto it: m_Addresses) // don't insert same address twice
|
||||||
|
if (*it == *addr) return;
|
||||||
|
m_Addresses.push_back(addr);
|
||||||
|
m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4;
|
||||||
|
m_Caps |= eSSUTesting;
|
||||||
|
m_Caps |= eSSUIntroducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::AddIntroducer (const Introducer& introducer)
|
||||||
|
{
|
||||||
|
for (auto addr : m_Addresses)
|
||||||
|
{
|
||||||
|
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
|
||||||
|
{
|
||||||
|
for (auto intro: addr->introducers)
|
||||||
|
if (intro.iTag == introducer.iTag) return false; // already presented
|
||||||
|
addr->introducers.push_back (introducer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
|
||||||
|
{
|
||||||
|
for (auto addr: m_Addresses)
|
||||||
|
{
|
||||||
|
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
|
||||||
|
{
|
||||||
|
for (std::vector<Introducer>::iterator it = addr->introducers.begin (); it != addr->introducers.end (); it++)
|
||||||
|
if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e)
|
||||||
|
{
|
||||||
|
addr->introducers.erase (it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::SetCaps (uint8_t caps)
|
||||||
|
{
|
||||||
|
m_Caps = caps;
|
||||||
|
UpdateCapsProperty ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::SetCaps (const char * caps)
|
||||||
|
{
|
||||||
|
SetProperty ("caps", caps);
|
||||||
|
m_Caps = 0;
|
||||||
|
ExtractCaps (caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::SetProperty (const std::string& key, const std::string& value)
|
||||||
|
{
|
||||||
|
m_Properties[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::DeleteProperty (const std::string& key)
|
||||||
|
{
|
||||||
|
m_Properties.erase (key);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RouterInfo::GetProperty (const std::string& key) const
|
||||||
|
{
|
||||||
|
auto it = m_Properties.find (key);
|
||||||
|
if (it != m_Properties.end ())
|
||||||
|
return it->second;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::IsNTCP (bool v4only) const
|
||||||
|
{
|
||||||
|
if (v4only)
|
||||||
|
return m_SupportedTransports & eNTCPV4;
|
||||||
|
else
|
||||||
|
return m_SupportedTransports & (eNTCPV4 | eNTCPV6);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::IsSSU (bool v4only) const
|
||||||
|
{
|
||||||
|
if (v4only)
|
||||||
|
return m_SupportedTransports & eSSUV4;
|
||||||
|
else
|
||||||
|
return m_SupportedTransports & (eSSUV4 | eSSUV6);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::IsV6 () const
|
||||||
|
{
|
||||||
|
return m_SupportedTransports & (eNTCPV6 | eSSUV6);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouterInfo::IsV4 () const
|
||||||
|
{
|
||||||
|
return m_SupportedTransports & (eNTCPV4 | eSSUV4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::EnableV6 ()
|
||||||
|
{
|
||||||
|
if (!IsV6 ())
|
||||||
|
m_SupportedTransports |= eNTCPV6 | eSSUV6;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::EnableV4 ()
|
||||||
|
{
|
||||||
|
if (!IsV4 ())
|
||||||
|
m_SupportedTransports |= eNTCPV4 | eSSUV4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void RouterInfo::DisableV6 ()
|
||||||
|
{
|
||||||
|
if (IsV6 ())
|
||||||
|
{
|
||||||
|
// NTCP
|
||||||
|
m_SupportedTransports &= ~eNTCPV6;
|
||||||
|
for (size_t i = 0; i < m_Addresses.size (); i++)
|
||||||
|
{
|
||||||
|
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
|
||||||
|
m_Addresses[i]->host.is_v6 ())
|
||||||
|
{
|
||||||
|
m_Addresses.erase (m_Addresses.begin () + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSU
|
||||||
|
m_SupportedTransports &= ~eSSUV6;
|
||||||
|
for (size_t i = 0; i < m_Addresses.size (); i++)
|
||||||
|
{
|
||||||
|
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU &&
|
||||||
|
m_Addresses[i]->host.is_v6 ())
|
||||||
|
{
|
||||||
|
m_Addresses.erase (m_Addresses.begin () + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouterInfo::DisableV4 ()
|
||||||
|
{
|
||||||
|
if (IsV4 ())
|
||||||
|
{
|
||||||
|
// NTCP
|
||||||
|
m_SupportedTransports &= ~eNTCPV4;
|
||||||
|
for (size_t i = 0; i < m_Addresses.size (); i++)
|
||||||
|
{
|
||||||
|
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
|
||||||
|
m_Addresses[i]->host.is_v4 ())
|
||||||
|
{
|
||||||
|
m_Addresses.erase (m_Addresses.begin () + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSU
|
||||||
|
m_SupportedTransports &= ~eSSUV4;
|
||||||
|
for (size_t i = 0; i < m_Addresses.size (); i++)
|
||||||
|
{
|
||||||
|
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU &&
|
||||||
|
m_Addresses[i]->host.is_v4 ())
|
||||||
|
{
|
||||||
|
m_Addresses.erase (m_Addresses.begin () + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool RouterInfo::UsesIntroducer () const
|
||||||
|
{
|
||||||
|
return m_Caps & Caps::eUnreachable; // non-reachable
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetNTCPAddress (bool v4only) const
|
||||||
|
{
|
||||||
|
return GetAddress (eTransportNTCP, v4only);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const
|
||||||
|
{
|
||||||
|
return GetAddress (eTransportSSU, v4only);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUV6Address () const
|
||||||
|
{
|
||||||
|
return GetAddress (eTransportSSU, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const
|
||||||
|
{
|
||||||
|
for (auto address : m_Addresses)
|
||||||
|
{
|
||||||
|
if (address->transportStyle == s)
|
||||||
|
{
|
||||||
|
if ((!v4only || address->host.is_v4 ()) && (!v6only || address->host.is_v6 ()))
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<RouterProfile> RouterInfo::GetProfile () const
|
||||||
|
{
|
||||||
|
if (!m_Profile)
|
||||||
|
m_Profile = GetRouterProfile (GetIdentHash ());
|
||||||
|
return m_Profile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
208
RouterInfo.h
Normal file
208
RouterInfo.h
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
#ifndef ROUTER_INFO_H__
|
||||||
|
#define ROUTER_INFO_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Profiling.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets";
|
||||||
|
const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters";
|
||||||
|
const char ROUTER_INFO_PROPERTY_NETID[] = "netId";
|
||||||
|
const char ROUTER_INFO_PROPERTY_FAMILY[] = "family";
|
||||||
|
const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig";
|
||||||
|
|
||||||
|
const char CAPS_FLAG_FLOODFILL = 'f';
|
||||||
|
const char CAPS_FLAG_HIDDEN = 'H';
|
||||||
|
const char CAPS_FLAG_REACHABLE = 'R';
|
||||||
|
const char CAPS_FLAG_UNREACHABLE = 'U';
|
||||||
|
/* bandwidth flags */
|
||||||
|
const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */
|
||||||
|
const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */
|
||||||
|
const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */
|
||||||
|
const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */
|
||||||
|
const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */
|
||||||
|
const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */
|
||||||
|
const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */
|
||||||
|
|
||||||
|
const char CAPS_FLAG_SSU_TESTING = 'B';
|
||||||
|
const char CAPS_FLAG_SSU_INTRODUCER = 'C';
|
||||||
|
|
||||||
|
const int MAX_RI_BUFFER_SIZE = 2048;
|
||||||
|
class RouterInfo: public RoutingDestination
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum SupportedTranports
|
||||||
|
{
|
||||||
|
eNTCPV4 = 0x01,
|
||||||
|
eNTCPV6 = 0x02,
|
||||||
|
eSSUV4 = 0x04,
|
||||||
|
eSSUV6 = 0x08
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Caps
|
||||||
|
{
|
||||||
|
eFloodfill = 0x01,
|
||||||
|
eHighBandwidth = 0x02,
|
||||||
|
eExtraBandwidth = 0x04,
|
||||||
|
eReachable = 0x08,
|
||||||
|
eSSUTesting = 0x10,
|
||||||
|
eSSUIntroducer = 0x20,
|
||||||
|
eHidden = 0x40,
|
||||||
|
eUnreachable = 0x80
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TransportStyle
|
||||||
|
{
|
||||||
|
eTransportUnknown = 0,
|
||||||
|
eTransportNTCP,
|
||||||
|
eTransportSSU
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey
|
||||||
|
struct Introducer
|
||||||
|
{
|
||||||
|
boost::asio::ip::address iHost;
|
||||||
|
int iPort;
|
||||||
|
IntroKey iKey;
|
||||||
|
uint32_t iTag;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Address
|
||||||
|
{
|
||||||
|
TransportStyle transportStyle;
|
||||||
|
boost::asio::ip::address host;
|
||||||
|
std::string addressString;
|
||||||
|
int port, mtu;
|
||||||
|
uint64_t date;
|
||||||
|
uint8_t cost;
|
||||||
|
// SSU only
|
||||||
|
IntroKey key; // intro key for SSU
|
||||||
|
std::vector<Introducer> introducers;
|
||||||
|
|
||||||
|
bool IsCompatible (const boost::asio::ip::address& other) const
|
||||||
|
{
|
||||||
|
return (host.is_v4 () && other.is_v4 ()) ||
|
||||||
|
(host.is_v6 () && other.is_v6 ());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Address& other) const
|
||||||
|
{
|
||||||
|
return transportStyle == other.transportStyle && host == other.host && port == other.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Address& other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RouterInfo (const std::string& fullPath);
|
||||||
|
RouterInfo (): m_Buffer (nullptr) { };
|
||||||
|
RouterInfo (const RouterInfo& ) = default;
|
||||||
|
RouterInfo& operator=(const RouterInfo& ) = default;
|
||||||
|
RouterInfo (const uint8_t * buf, int len);
|
||||||
|
~RouterInfo ();
|
||||||
|
|
||||||
|
std::shared_ptr<const IdentityEx> GetRouterIdentity () const { return m_RouterIdentity; };
|
||||||
|
void SetRouterIdentity (std::shared_ptr<const IdentityEx> identity);
|
||||||
|
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
|
||||||
|
uint64_t GetTimestamp () const { return m_Timestamp; };
|
||||||
|
std::vector<std::shared_ptr<Address> >& GetAddresses () { return m_Addresses; };
|
||||||
|
std::shared_ptr<const Address> GetNTCPAddress (bool v4only = true) const;
|
||||||
|
std::shared_ptr<const Address> GetSSUAddress (bool v4only = true) const;
|
||||||
|
std::shared_ptr<const Address> GetSSUV6Address () const;
|
||||||
|
|
||||||
|
void AddNTCPAddress (const char * host, int port);
|
||||||
|
void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0);
|
||||||
|
bool AddIntroducer (const Introducer& introducer);
|
||||||
|
bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
|
||||||
|
void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only
|
||||||
|
void DeleteProperty (const std::string& key); // called from RouterContext only
|
||||||
|
std::string GetProperty (const std::string& key) const; // called from RouterContext only
|
||||||
|
void ClearProperties () { m_Properties.clear (); };
|
||||||
|
bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
|
||||||
|
bool IsReachable () const { return m_Caps & Caps::eReachable; };
|
||||||
|
bool IsNTCP (bool v4only = true) const;
|
||||||
|
bool IsSSU (bool v4only = true) const;
|
||||||
|
bool IsV6 () const;
|
||||||
|
bool IsV4 () const;
|
||||||
|
void EnableV6 ();
|
||||||
|
void DisableV6 ();
|
||||||
|
void EnableV4 ();
|
||||||
|
void DisableV4 ();
|
||||||
|
bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; };
|
||||||
|
bool UsesIntroducer () const;
|
||||||
|
bool IsIntroducer () const { return m_Caps & eSSUIntroducer; };
|
||||||
|
bool IsPeerTesting () const { return m_Caps & eSSUTesting; };
|
||||||
|
bool IsHidden () const { return m_Caps & eHidden; };
|
||||||
|
bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
|
||||||
|
bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; };
|
||||||
|
|
||||||
|
uint8_t GetCaps () const { return m_Caps; };
|
||||||
|
void SetCaps (uint8_t caps);
|
||||||
|
void SetCaps (const char * caps);
|
||||||
|
|
||||||
|
void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; };
|
||||||
|
bool IsUnreachable () const { return m_IsUnreachable; };
|
||||||
|
|
||||||
|
const uint8_t * GetBuffer () const { return m_Buffer; };
|
||||||
|
const uint8_t * LoadBuffer (); // load if necessary
|
||||||
|
int GetBufferLen () const { return m_BufferLen; };
|
||||||
|
void CreateBuffer (const PrivateKeys& privateKeys);
|
||||||
|
|
||||||
|
bool IsUpdated () const { return m_IsUpdated; };
|
||||||
|
void SetUpdated (bool updated) { m_IsUpdated = updated; };
|
||||||
|
bool SaveToFile (const std::string& fullPath);
|
||||||
|
|
||||||
|
std::shared_ptr<RouterProfile> GetProfile () const;
|
||||||
|
void SaveProfile () { if (m_Profile) m_Profile->Save (); };
|
||||||
|
|
||||||
|
void Update (const uint8_t * buf, int len);
|
||||||
|
void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
|
||||||
|
bool IsNewer (const uint8_t * buf, size_t len) const;
|
||||||
|
|
||||||
|
// implements RoutingDestination
|
||||||
|
const IdentHash& GetIdentHash () const { return m_RouterIdentity->GetIdentHash (); };
|
||||||
|
const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; };
|
||||||
|
bool IsDestination () const { return false; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool LoadFile ();
|
||||||
|
void ReadFromFile ();
|
||||||
|
void ReadFromStream (std::istream& s);
|
||||||
|
void ReadFromBuffer (bool verifySignature);
|
||||||
|
void WriteToStream (std::ostream& s);
|
||||||
|
size_t ReadString (char * str, std::istream& s);
|
||||||
|
void WriteString (const std::string& str, std::ostream& s);
|
||||||
|
void ExtractCaps (const char * value);
|
||||||
|
std::shared_ptr<const Address> GetAddress (TransportStyle s, bool v4only, bool v6only = false) const;
|
||||||
|
void UpdateCapsProperty ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string m_FullPath, m_Family;
|
||||||
|
std::shared_ptr<const IdentityEx> m_RouterIdentity;
|
||||||
|
uint8_t * m_Buffer;
|
||||||
|
size_t m_BufferLen;
|
||||||
|
uint64_t m_Timestamp;
|
||||||
|
std::vector<std::shared_ptr<Address> > m_Addresses;
|
||||||
|
std::map<std::string, std::string> m_Properties;
|
||||||
|
bool m_IsUpdated, m_IsUnreachable;
|
||||||
|
uint8_t m_SupportedTransports, m_Caps;
|
||||||
|
mutable std::shared_ptr<RouterProfile> m_Profile;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
886
SAM.cpp
Normal file
886
SAM.cpp
Normal file
@@ -0,0 +1,886 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "SAM.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
SAMSocket::SAMSocket (SAMBridge& owner):
|
||||||
|
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()),
|
||||||
|
m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false),
|
||||||
|
m_Stream (nullptr), m_Session (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMSocket::~SAMSocket ()
|
||||||
|
{
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::CloseStream ()
|
||||||
|
{
|
||||||
|
if (m_Stream)
|
||||||
|
{
|
||||||
|
m_Stream->Close ();
|
||||||
|
m_Stream.reset ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::Terminate ()
|
||||||
|
{
|
||||||
|
CloseStream ();
|
||||||
|
|
||||||
|
switch (m_SocketType)
|
||||||
|
{
|
||||||
|
case eSAMSocketTypeSession:
|
||||||
|
m_Owner.CloseSession (m_ID);
|
||||||
|
break;
|
||||||
|
case eSAMSocketTypeStream:
|
||||||
|
{
|
||||||
|
if (m_Session)
|
||||||
|
m_Session->DelSocket (shared_from_this ());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eSAMSocketTypeAcceptor:
|
||||||
|
{
|
||||||
|
if (m_Session)
|
||||||
|
{
|
||||||
|
m_Session->DelSocket (shared_from_this ());
|
||||||
|
m_Session->localDestination->StopAcceptingStreams ();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
m_SocketType = eSAMSocketTypeTerminated;
|
||||||
|
if (m_Socket.is_open()) m_Socket.close ();
|
||||||
|
m_Session = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::ReceiveHandshake ()
|
||||||
|
{
|
||||||
|
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
|
||||||
|
std::bind(&SAMSocket::HandleHandshakeReceived, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: handshake read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Buffer[bytes_transferred] = 0;
|
||||||
|
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
|
||||||
|
if (eol)
|
||||||
|
*eol = 0;
|
||||||
|
LogPrint (eLogDebug, "SAM: handshake ", m_Buffer);
|
||||||
|
char * separator = strchr (m_Buffer, ' ');
|
||||||
|
if (separator)
|
||||||
|
{
|
||||||
|
separator = strchr (separator + 1, ' ');
|
||||||
|
if (separator)
|
||||||
|
*separator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp (m_Buffer, SAM_HANDSHAKE))
|
||||||
|
{
|
||||||
|
std::string version("3.0");
|
||||||
|
// try to find MIN and MAX, 3.0 if not found
|
||||||
|
if (separator)
|
||||||
|
{
|
||||||
|
separator++;
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
ExtractParams (separator, params);
|
||||||
|
auto it = params.find (SAM_PARAM_MAX);
|
||||||
|
// TODO: check MIN as well
|
||||||
|
if (it != params.end ())
|
||||||
|
version = it->second;
|
||||||
|
}
|
||||||
|
if (version[0] == '3') // we support v3 (3.0 and 3.1) only
|
||||||
|
{
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
|
||||||
|
#else
|
||||||
|
size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
|
||||||
|
#endif
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (),
|
||||||
|
std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendMessageReply (SAM_HANDSHAKE_I2P_ERROR, strlen (SAM_HANDSHAKE_I2P_ERROR), true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: handshake mismatch");
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: handshake reply send error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
|
||||||
|
std::bind(&SAMSocket::HandleMessage, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close)
|
||||||
|
{
|
||||||
|
if (!m_IsSilent)
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (),
|
||||||
|
std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2, close));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (close)
|
||||||
|
Terminate ();
|
||||||
|
else
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: reply send error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (close)
|
||||||
|
Terminate ();
|
||||||
|
else
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else if (m_SocketType == eSAMSocketTypeStream)
|
||||||
|
HandleReceived (ecode, bytes_transferred);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytes_transferred += m_BufferOffset;
|
||||||
|
m_BufferOffset = 0;
|
||||||
|
m_Buffer[bytes_transferred] = 0;
|
||||||
|
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
|
||||||
|
if (eol)
|
||||||
|
{
|
||||||
|
*eol = 0;
|
||||||
|
char * separator = strchr (m_Buffer, ' ');
|
||||||
|
if (separator)
|
||||||
|
{
|
||||||
|
separator = strchr (separator + 1, ' ');
|
||||||
|
if (separator)
|
||||||
|
*separator = 0;
|
||||||
|
else
|
||||||
|
separator = eol;
|
||||||
|
|
||||||
|
if (!strcmp (m_Buffer, SAM_SESSION_CREATE))
|
||||||
|
ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
|
||||||
|
else if (!strcmp (m_Buffer, SAM_STREAM_CONNECT))
|
||||||
|
ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
|
||||||
|
else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT))
|
||||||
|
ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
|
||||||
|
else if (!strcmp (m_Buffer, SAM_DEST_GENERATE))
|
||||||
|
ProcessDestGenerate ();
|
||||||
|
else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP))
|
||||||
|
ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
|
||||||
|
else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND))
|
||||||
|
{
|
||||||
|
size_t len = bytes_transferred - (separator - m_Buffer) - 1;
|
||||||
|
size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1);
|
||||||
|
if (processed < len)
|
||||||
|
{
|
||||||
|
m_BufferOffset = len - processed;
|
||||||
|
if (processed > 0)
|
||||||
|
memmove (m_Buffer, separator + 1 + processed, m_BufferOffset);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// restore string back
|
||||||
|
*separator = ' ';
|
||||||
|
*eol = '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// since it's SAM v1 reply is not expected
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: unexpected message ", m_Buffer);
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: malformed message ", m_Buffer);
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SAM: incomplete message ", bytes_transferred);
|
||||||
|
m_BufferOffset = bytes_transferred;
|
||||||
|
// try to receive remaining message
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::ProcessSessionCreate (char * buf, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: session create: ", buf);
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
ExtractParams (buf, params);
|
||||||
|
std::string& style = params[SAM_PARAM_STYLE];
|
||||||
|
std::string& id = params[SAM_PARAM_ID];
|
||||||
|
std::string& destination = params[SAM_PARAM_DESTINATION];
|
||||||
|
m_ID = id;
|
||||||
|
if (m_Owner.FindSession (id))
|
||||||
|
{
|
||||||
|
// session exists
|
||||||
|
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create destination
|
||||||
|
m_Session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms);
|
||||||
|
if (m_Session)
|
||||||
|
{
|
||||||
|
m_SocketType = eSAMSocketTypeSession;
|
||||||
|
if (style == SAM_VALUE_DATAGRAM)
|
||||||
|
{
|
||||||
|
auto dest = m_Session->localDestination->CreateDatagramDestination ();
|
||||||
|
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Session->localDestination->IsReady ())
|
||||||
|
SendSessionCreateReplyOk ();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
|
||||||
|
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
if (m_Session->localDestination->IsReady ())
|
||||||
|
SendSessionCreateReplyOk ();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
|
||||||
|
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::SendSessionCreateReplyOk ()
|
||||||
|
{
|
||||||
|
uint8_t buf[1024];
|
||||||
|
char priv[1024];
|
||||||
|
size_t l = m_Session->localDestination->GetPrivateKeys ().ToBuffer (buf, 1024);
|
||||||
|
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024);
|
||||||
|
priv[l1] = 0;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
|
||||||
|
#else
|
||||||
|
size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
|
||||||
|
#endif
|
||||||
|
SendMessageReply (m_Buffer, l2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::ProcessStreamConnect (char * buf, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: stream connect: ", buf);
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
ExtractParams (buf, params);
|
||||||
|
std::string& id = params[SAM_PARAM_ID];
|
||||||
|
std::string& destination = params[SAM_PARAM_DESTINATION];
|
||||||
|
std::string& silent = params[SAM_PARAM_SILENT];
|
||||||
|
if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
|
||||||
|
m_ID = id;
|
||||||
|
m_Session = m_Owner.FindSession (id);
|
||||||
|
if (m_Session)
|
||||||
|
{
|
||||||
|
auto dest = std::make_shared<i2p::data::IdentityEx> ();
|
||||||
|
size_t len = dest->FromBase64(destination);
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
context.GetAddressBook().InsertAddress(dest);
|
||||||
|
auto leaseSet = m_Session->localDestination->FindLeaseSet(dest->GetIdentHash());
|
||||||
|
if (leaseSet)
|
||||||
|
Connect(leaseSet);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Session->localDestination->RequestDestination(dest->GetIdentHash(),
|
||||||
|
std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
|
||||||
|
shared_from_this(), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::Connect (std::shared_ptr<const i2p::data::LeaseSet> remote)
|
||||||
|
{
|
||||||
|
m_SocketType = eSAMSocketTypeStream;
|
||||||
|
m_Session->AddSocket (shared_from_this ());
|
||||||
|
m_Stream = m_Session->localDestination->CreateStream (remote);
|
||||||
|
m_Stream->Send ((uint8_t *)m_Buffer, 0); // connect
|
||||||
|
I2PReceive ();
|
||||||
|
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet)
|
||||||
|
{
|
||||||
|
if (leaseSet)
|
||||||
|
Connect (leaseSet);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: destination to connect not found");
|
||||||
|
SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::ProcessStreamAccept (char * buf, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: stream accept: ", buf);
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
ExtractParams (buf, params);
|
||||||
|
std::string& id = params[SAM_PARAM_ID];
|
||||||
|
std::string& silent = params[SAM_PARAM_SILENT];
|
||||||
|
if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
|
||||||
|
m_ID = id;
|
||||||
|
m_Session = m_Owner.FindSession (id);
|
||||||
|
if (m_Session)
|
||||||
|
{
|
||||||
|
if (!m_Session->localDestination->IsAcceptingStreams ())
|
||||||
|
{
|
||||||
|
m_SocketType = eSAMSocketTypeAcceptor;
|
||||||
|
m_Session->AddSocket (shared_from_this ());
|
||||||
|
m_Session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1));
|
||||||
|
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendMessageReply (SAM_STREAM_STATUS_I2P_ERROR, strlen(SAM_STREAM_STATUS_I2P_ERROR), true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len);
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
ExtractParams (buf, params);
|
||||||
|
size_t size = boost::lexical_cast<int>(params[SAM_PARAM_SIZE]), offset = data - buf;
|
||||||
|
if (offset + size <= len)
|
||||||
|
{
|
||||||
|
if (m_Session)
|
||||||
|
{
|
||||||
|
auto d = m_Session->localDestination->GetDatagramDestination ();
|
||||||
|
if (d)
|
||||||
|
{
|
||||||
|
i2p::data::IdentityEx dest;
|
||||||
|
dest.FromBase64 (params[SAM_PARAM_DESTINATION]);
|
||||||
|
d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: missing datagram destination");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: session is not created from DATAGRAM SEND");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SAM: sent datagram size ", size, " exceeds buffer ", len - offset);
|
||||||
|
return 0; // try to receive more
|
||||||
|
}
|
||||||
|
return offset + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::ProcessDestGenerate ()
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: dest generate");
|
||||||
|
auto keys = i2p::data::PrivateKeys::CreateRandomKeys ();
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY,
|
||||||
|
keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ());
|
||||||
|
#else
|
||||||
|
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY,
|
||||||
|
keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ());
|
||||||
|
#endif
|
||||||
|
SendMessageReply (m_Buffer, len, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::ProcessNamingLookup (char * buf, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: naming lookup: ", buf);
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
ExtractParams (buf, params);
|
||||||
|
std::string& name = params[SAM_PARAM_NAME];
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> identity;
|
||||||
|
i2p::data::IdentHash ident;
|
||||||
|
if (name == "ME")
|
||||||
|
SendNamingLookupReply (m_Session->localDestination->GetIdentity ());
|
||||||
|
else if ((identity = context.GetAddressBook ().GetAddress (name)) != nullptr)
|
||||||
|
SendNamingLookupReply (identity);
|
||||||
|
else if (m_Session && m_Session->localDestination &&
|
||||||
|
context.GetAddressBook ().GetIdentHash (name, ident))
|
||||||
|
{
|
||||||
|
auto leaseSet = m_Session->localDestination->FindLeaseSet (ident);
|
||||||
|
if (leaseSet)
|
||||||
|
SendNamingLookupReply (leaseSet->GetIdentity ());
|
||||||
|
else
|
||||||
|
m_Session->localDestination->RequestDestination (ident,
|
||||||
|
std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete,
|
||||||
|
shared_from_this (), std::placeholders::_1, ident));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: naming failed, unknown address ", name);
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str());
|
||||||
|
#else
|
||||||
|
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str());
|
||||||
|
#endif
|
||||||
|
SendMessageReply (m_Buffer, len, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident)
|
||||||
|
{
|
||||||
|
if (leaseSet)
|
||||||
|
{
|
||||||
|
context.GetAddressBook ().InsertAddress (leaseSet->GetIdentity ());
|
||||||
|
SendNamingLookupReply (leaseSet->GetIdentity ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: naming lookup failed. LeaseSet for ", ident.ToBase32 (), " not found");
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY,
|
||||||
|
context.GetAddressBook ().ToAddress (ident).c_str());
|
||||||
|
#else
|
||||||
|
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY,
|
||||||
|
context.GetAddressBook ().ToAddress (ident).c_str());
|
||||||
|
#endif
|
||||||
|
SendMessageReply (m_Buffer, len, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::SendNamingLookupReply (std::shared_ptr<const i2p::data::IdentityEx> identity)
|
||||||
|
{
|
||||||
|
auto base64 = identity->ToBase64 ();
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
|
||||||
|
#else
|
||||||
|
size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
|
||||||
|
#endif
|
||||||
|
SendMessageReply (m_Buffer, l, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::ExtractParams (char * buf, std::map<std::string, std::string>& params)
|
||||||
|
{
|
||||||
|
char * separator;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
separator = strchr (buf, ' ');
|
||||||
|
if (separator) *separator = 0;
|
||||||
|
char * value = strchr (buf, '=');
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
*value = 0;
|
||||||
|
value++;
|
||||||
|
params[buf] = value;
|
||||||
|
}
|
||||||
|
buf = separator + 1;
|
||||||
|
}
|
||||||
|
while (separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::Receive ()
|
||||||
|
{
|
||||||
|
if (m_BufferOffset >= SAM_SOCKET_BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: Buffer is full, terminate");
|
||||||
|
Terminate ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset),
|
||||||
|
std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage,
|
||||||
|
shared_from_this (), std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_Stream)
|
||||||
|
{
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred,
|
||||||
|
[s](const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
s->Receive ();
|
||||||
|
else
|
||||||
|
s->Terminate ();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::I2PReceive ()
|
||||||
|
{
|
||||||
|
if (m_Stream)
|
||||||
|
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE),
|
||||||
|
std::bind (&SAMSocket::HandleI2PReceive, shared_from_this (),
|
||||||
|
std::placeholders::_1, std::placeholders::_2),
|
||||||
|
SAM_SOCKET_CONNECTION_MAX_IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: stream read error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred),
|
||||||
|
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: socket write error: ", ecode.message ());
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Terminate ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
I2PReceive ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: incoming I2P connection for session ", m_ID);
|
||||||
|
m_Stream = stream;
|
||||||
|
context.GetAddressBook ().InsertAddress (stream->GetRemoteIdentity ());
|
||||||
|
auto session = m_Owner.FindSession (m_ID);
|
||||||
|
if (session)
|
||||||
|
session->localDestination->StopAcceptingStreams ();
|
||||||
|
m_SocketType = eSAMSocketTypeStream;
|
||||||
|
if (!m_IsSilent)
|
||||||
|
{
|
||||||
|
// get remote peer address
|
||||||
|
auto ident_ptr = stream->GetRemoteIdentity();
|
||||||
|
const size_t ident_len = ident_ptr->GetFullLen();
|
||||||
|
uint8_t* ident = new uint8_t[ident_len];
|
||||||
|
|
||||||
|
// send remote peer address as base64
|
||||||
|
const size_t l = ident_ptr->ToBuffer (ident, ident_len);
|
||||||
|
const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE);
|
||||||
|
delete[] ident;
|
||||||
|
m_StreamBuffer[l1] = '\n';
|
||||||
|
HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream
|
||||||
|
}
|
||||||
|
else
|
||||||
|
I2PReceive ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "SAM: I2P acceptor has been reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: datagram received ", len);
|
||||||
|
auto base64 = from.ToBase64 ();
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len);
|
||||||
|
#else
|
||||||
|
size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len);
|
||||||
|
#endif
|
||||||
|
if (len < SAM_SOCKET_BUFFER_SIZE - l)
|
||||||
|
{
|
||||||
|
memcpy (m_StreamBuffer + l, buf, len);
|
||||||
|
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len + l),
|
||||||
|
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "SAM: received datagram size ", len," exceeds buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMSession::SAMSession (std::shared_ptr<ClientDestination> dest):
|
||||||
|
localDestination (dest)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMSession::~SAMSession ()
|
||||||
|
{
|
||||||
|
CloseStreams();
|
||||||
|
i2p::client::context.DeleteLocalDestination (localDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMSession::CloseStreams ()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_SocketsMutex);
|
||||||
|
for (auto sock : m_Sockets) {
|
||||||
|
sock->CloseStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// XXX: should this be done inside locked parts?
|
||||||
|
m_Sockets.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMBridge::SAMBridge (const std::string& address, int port):
|
||||||
|
m_IsRunning (false), m_Thread (nullptr),
|
||||||
|
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
|
||||||
|
m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMBridge::~SAMBridge ()
|
||||||
|
{
|
||||||
|
if (m_IsRunning)
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::Start ()
|
||||||
|
{
|
||||||
|
Accept ();
|
||||||
|
ReceiveDatagram ();
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Thread = new std::thread (std::bind (&SAMBridge::Run, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::Stop ()
|
||||||
|
{
|
||||||
|
m_IsRunning = false;
|
||||||
|
m_Acceptor.cancel ();
|
||||||
|
for (auto it: m_Sessions)
|
||||||
|
it.second->CloseStreams ();
|
||||||
|
m_Sessions.clear ();
|
||||||
|
m_Service.stop ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Service.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SAM: runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::Accept ()
|
||||||
|
{
|
||||||
|
auto newSocket = std::make_shared<SAMSocket> (*this);
|
||||||
|
m_Acceptor.async_accept (newSocket->GetSocket (), std::bind (&SAMBridge::HandleAccept, this,
|
||||||
|
std::placeholders::_1, newSocket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
auto ep = socket->GetSocket ().remote_endpoint (ec);
|
||||||
|
if (!ec)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SAM: new connection from ", ep);
|
||||||
|
socket->ReceiveHandshake ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: incoming connection error ", ec.message ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: accept error: ", ecode.message ());
|
||||||
|
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
Accept ();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SAMSession> SAMBridge::CreateSession (const std::string& id, const std::string& destination,
|
||||||
|
const std::map<std::string, std::string> * params)
|
||||||
|
{
|
||||||
|
std::shared_ptr<ClientDestination> localDestination = nullptr;
|
||||||
|
if (destination != "")
|
||||||
|
{
|
||||||
|
i2p::data::PrivateKeys keys;
|
||||||
|
keys.FromBase64 (destination);
|
||||||
|
localDestination = i2p::client::context.CreateNewLocalDestination (keys, true, params);
|
||||||
|
}
|
||||||
|
else // transient
|
||||||
|
{
|
||||||
|
// extract signature type
|
||||||
|
i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1;
|
||||||
|
if (params)
|
||||||
|
{
|
||||||
|
auto it = params->find (SAM_PARAM_SIGNATURE_TYPE);
|
||||||
|
if (it != params->end ())
|
||||||
|
// TODO: extract string values
|
||||||
|
signatureType = boost::lexical_cast<int> (it->second);
|
||||||
|
}
|
||||||
|
localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, params);
|
||||||
|
}
|
||||||
|
if (localDestination)
|
||||||
|
{
|
||||||
|
auto session = std::make_shared<SAMSession>(localDestination);
|
||||||
|
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||||
|
auto ret = m_Sessions.insert (std::make_pair(id, session));
|
||||||
|
if (!ret.second)
|
||||||
|
LogPrint (eLogWarning, "SAM: Session ", id, " already exists");
|
||||||
|
return ret.first->second;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::CloseSession (const std::string& id)
|
||||||
|
{
|
||||||
|
std::shared_ptr<SAMSession> session;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||||
|
auto it = m_Sessions.find (id);
|
||||||
|
if (it != m_Sessions.end ())
|
||||||
|
{
|
||||||
|
session = it->second;
|
||||||
|
m_Sessions.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (session)
|
||||||
|
{
|
||||||
|
session->localDestination->StopAcceptingStreams ();
|
||||||
|
session->CloseStreams ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SAMSession> SAMBridge::FindSession (const std::string& id) const
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||||
|
auto it = m_Sessions.find (id);
|
||||||
|
if (it != m_Sessions.end ())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::ReceiveDatagram ()
|
||||||
|
{
|
||||||
|
m_DatagramSocket.async_receive_from (
|
||||||
|
boost::asio::buffer (m_DatagramReceiveBuffer, i2p::datagram::MAX_DATAGRAM_SIZE),
|
||||||
|
m_SenderEndpoint,
|
||||||
|
std::bind (&SAMBridge::HandleReceivedDatagram, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SAMBridge::HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
m_DatagramReceiveBuffer[bytes_transferred] = 0;
|
||||||
|
char * eol = strchr ((char *)m_DatagramReceiveBuffer, '\n');
|
||||||
|
*eol = 0; eol++;
|
||||||
|
size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer);
|
||||||
|
LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen);
|
||||||
|
char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' ');
|
||||||
|
if (sessionID)
|
||||||
|
{
|
||||||
|
sessionID++;
|
||||||
|
char * destination = strchr (sessionID, ' ');
|
||||||
|
if (destination)
|
||||||
|
{
|
||||||
|
*destination = 0; destination++;
|
||||||
|
auto session = FindSession (sessionID);
|
||||||
|
if (session)
|
||||||
|
{
|
||||||
|
i2p::data::IdentityEx dest;
|
||||||
|
dest.FromBase64 (destination);
|
||||||
|
session->localDestination->GetDatagramDestination ()->
|
||||||
|
SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: Session ", sessionID, " not found");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: Missing destination key");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: Missing sessionID");
|
||||||
|
ReceiveDatagram ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
215
SAM.h
Normal file
215
SAM.h
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
#ifndef SAM_H__
|
||||||
|
#define SAM_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
const size_t SAM_SOCKET_BUFFER_SIZE = 8192;
|
||||||
|
const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds
|
||||||
|
const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds
|
||||||
|
const char SAM_HANDSHAKE[] = "HELLO VERSION";
|
||||||
|
const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n";
|
||||||
|
const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n";
|
||||||
|
const char SAM_SESSION_CREATE[] = "SESSION CREATE";
|
||||||
|
const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n";
|
||||||
|
const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n";
|
||||||
|
const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n";
|
||||||
|
const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n";
|
||||||
|
const char SAM_STREAM_CONNECT[] = "STREAM CONNECT";
|
||||||
|
const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n";
|
||||||
|
const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n";
|
||||||
|
const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n";
|
||||||
|
const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n";
|
||||||
|
const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT";
|
||||||
|
const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND";
|
||||||
|
const char SAM_DEST_GENERATE[] = "DEST GENERATE";
|
||||||
|
const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n";
|
||||||
|
const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n";
|
||||||
|
const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP";
|
||||||
|
const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n";
|
||||||
|
const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n";
|
||||||
|
const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n";
|
||||||
|
const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n";
|
||||||
|
const char SAM_PARAM_MIN[] = "MIN";
|
||||||
|
const char SAM_PARAM_MAX[] = "MAX";
|
||||||
|
const char SAM_PARAM_STYLE[] = "STYLE";
|
||||||
|
const char SAM_PARAM_ID[] = "ID";
|
||||||
|
const char SAM_PARAM_SILENT[] = "SILENT";
|
||||||
|
const char SAM_PARAM_DESTINATION[] = "DESTINATION";
|
||||||
|
const char SAM_PARAM_NAME[] = "NAME";
|
||||||
|
const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE";
|
||||||
|
const char SAM_PARAM_SIZE[] = "SIZE";
|
||||||
|
const char SAM_VALUE_TRANSIENT[] = "TRANSIENT";
|
||||||
|
const char SAM_VALUE_STREAM[] = "STREAM";
|
||||||
|
const char SAM_VALUE_DATAGRAM[] = "DATAGRAM";
|
||||||
|
const char SAM_VALUE_RAW[] = "RAW";
|
||||||
|
const char SAM_VALUE_TRUE[] = "true";
|
||||||
|
const char SAM_VALUE_FALSE[] = "false";
|
||||||
|
|
||||||
|
enum SAMSocketType
|
||||||
|
{
|
||||||
|
eSAMSocketTypeUnknown,
|
||||||
|
eSAMSocketTypeSession,
|
||||||
|
eSAMSocketTypeStream,
|
||||||
|
eSAMSocketTypeAcceptor,
|
||||||
|
eSAMSocketTypeTerminated
|
||||||
|
};
|
||||||
|
|
||||||
|
class SAMBridge;
|
||||||
|
struct SAMSession;
|
||||||
|
class SAMSocket: public std::enable_shared_from_this<SAMSocket>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SAMSocket (SAMBridge& owner);
|
||||||
|
~SAMSocket ();
|
||||||
|
void CloseStream (); // TODO: implement it better
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
|
||||||
|
void ReceiveHandshake ();
|
||||||
|
void SetSocketType (SAMSocketType socketType) { m_SocketType = socketType; };
|
||||||
|
SAMSocketType GetSocketType () const { return m_SocketType; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Terminate ();
|
||||||
|
void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void SendMessageReply (const char * msg, size_t len, bool close);
|
||||||
|
void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close);
|
||||||
|
void Receive ();
|
||||||
|
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
|
||||||
|
void I2PReceive ();
|
||||||
|
void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
void HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
void HandleWriteI2PData (const boost::system::error_code& ecode);
|
||||||
|
void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
void ProcessSessionCreate (char * buf, size_t len);
|
||||||
|
void ProcessStreamConnect (char * buf, size_t len);
|
||||||
|
void ProcessStreamAccept (char * buf, size_t len);
|
||||||
|
void ProcessDestGenerate ();
|
||||||
|
void ProcessNamingLookup (char * buf, size_t len);
|
||||||
|
size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0
|
||||||
|
void ExtractParams (char * buf, std::map<std::string, std::string>& params);
|
||||||
|
|
||||||
|
void Connect (std::shared_ptr<const i2p::data::LeaseSet> remote);
|
||||||
|
void HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet);
|
||||||
|
void SendNamingLookupReply (std::shared_ptr<const i2p::data::IdentityEx> identity);
|
||||||
|
void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident);
|
||||||
|
void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode);
|
||||||
|
void SendSessionCreateReplyOk ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SAMBridge& m_Owner;
|
||||||
|
boost::asio::ip::tcp::socket m_Socket;
|
||||||
|
boost::asio::deadline_timer m_Timer;
|
||||||
|
char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1];
|
||||||
|
size_t m_BufferOffset;
|
||||||
|
uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE];
|
||||||
|
SAMSocketType m_SocketType;
|
||||||
|
std::string m_ID; // nickname
|
||||||
|
bool m_IsSilent;
|
||||||
|
std::shared_ptr<i2p::stream::Stream> m_Stream;
|
||||||
|
std::shared_ptr<SAMSession> m_Session;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SAMSession
|
||||||
|
{
|
||||||
|
std::shared_ptr<ClientDestination> localDestination;
|
||||||
|
std::list<std::shared_ptr<SAMSocket> > m_Sockets;
|
||||||
|
std::mutex m_SocketsMutex;
|
||||||
|
|
||||||
|
/** safely add a socket to this session */
|
||||||
|
void AddSocket(std::shared_ptr<SAMSocket> sock) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_SocketsMutex);
|
||||||
|
m_Sockets.push_back(sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** safely remove a socket from this session */
|
||||||
|
void DelSocket(std::shared_ptr<SAMSocket> sock) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_SocketsMutex);
|
||||||
|
m_Sockets.remove(sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** get a list holding a copy of all sam sockets from this session */
|
||||||
|
std::list<std::shared_ptr<SAMSocket> > ListSockets() {
|
||||||
|
std::list<std::shared_ptr<SAMSocket> > l;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_SocketsMutex);
|
||||||
|
for( auto & sock : m_Sockets ) l.push_back(sock);
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMSession (std::shared_ptr<ClientDestination> dest);
|
||||||
|
~SAMSession ();
|
||||||
|
|
||||||
|
void CloseStreams ();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SAMBridge
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SAMBridge (const std::string& address, int port);
|
||||||
|
~SAMBridge ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
boost::asio::io_service& GetService () { return m_Service; };
|
||||||
|
std::shared_ptr<SAMSession> CreateSession (const std::string& id, const std::string& destination, // empty string means transient
|
||||||
|
const std::map<std::string, std::string> * params);
|
||||||
|
void CloseSession (const std::string& id);
|
||||||
|
std::shared_ptr<SAMSession> FindSession (const std::string& id) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
|
||||||
|
void Accept ();
|
||||||
|
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket);
|
||||||
|
|
||||||
|
void ReceiveDatagram ();
|
||||||
|
void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
boost::asio::io_service m_Service;
|
||||||
|
boost::asio::ip::tcp::acceptor m_Acceptor;
|
||||||
|
boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint;
|
||||||
|
boost::asio::ip::udp::socket m_DatagramSocket;
|
||||||
|
mutable std::mutex m_SessionsMutex;
|
||||||
|
std::map<std::string, std::shared_ptr<SAMSession> > m_Sessions;
|
||||||
|
uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1];
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// for HTTP
|
||||||
|
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
791
SOCKS.cpp
Normal file
791
SOCKS.cpp
Normal file
@@ -0,0 +1,791 @@
|
|||||||
|
#include <cstring>
|
||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
#include "SOCKS.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "ClientContext.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "I2PTunnel.h"
|
||||||
|
#include "I2PService.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace proxy
|
||||||
|
{
|
||||||
|
static const size_t socks_buffer_size = 8192;
|
||||||
|
static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse
|
||||||
|
|
||||||
|
static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8;
|
||||||
|
|
||||||
|
struct SOCKSDnsAddress
|
||||||
|
{
|
||||||
|
uint8_t size;
|
||||||
|
char value[max_socks_hostname_size];
|
||||||
|
void FromString (std::string str)
|
||||||
|
{
|
||||||
|
size = str.length();
|
||||||
|
if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size;
|
||||||
|
memcpy(value,str.c_str(),size);
|
||||||
|
}
|
||||||
|
std::string ToString() { return std::string(value, size); }
|
||||||
|
void push_back (char c) { value[size++] = c; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class SOCKSServer;
|
||||||
|
class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<SOCKSHandler>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
enum state
|
||||||
|
{
|
||||||
|
GET_SOCKSV,
|
||||||
|
GET_COMMAND,
|
||||||
|
GET_PORT,
|
||||||
|
GET_IPV4,
|
||||||
|
GET4_IDENT,
|
||||||
|
GET4A_HOST,
|
||||||
|
GET5_AUTHNUM,
|
||||||
|
GET5_AUTH,
|
||||||
|
GET5_REQUESTV,
|
||||||
|
GET5_GETRSV,
|
||||||
|
GET5_GETADDRTYPE,
|
||||||
|
GET5_IPV6,
|
||||||
|
GET5_HOST_SIZE,
|
||||||
|
GET5_HOST,
|
||||||
|
READY,
|
||||||
|
UPSTREAM_RESOLVE,
|
||||||
|
UPSTREAM_CONNECT,
|
||||||
|
UPSTREAM_HANDSHAKE
|
||||||
|
};
|
||||||
|
enum authMethods
|
||||||
|
{
|
||||||
|
AUTH_NONE = 0, //No authentication, skip to next step
|
||||||
|
AUTH_GSSAPI = 1, //GSSAPI authentication
|
||||||
|
AUTH_USERPASSWD = 2, //Username and password
|
||||||
|
AUTH_UNACCEPTABLE = 0xff //No acceptable method found
|
||||||
|
};
|
||||||
|
enum addrTypes
|
||||||
|
{
|
||||||
|
ADDR_IPV4 = 1, //IPv4 address (4 octets)
|
||||||
|
ADDR_DNS = 3, // DNS name (up to 255 octets)
|
||||||
|
ADDR_IPV6 = 4 //IPV6 address (16 octets)
|
||||||
|
};
|
||||||
|
enum errTypes
|
||||||
|
{
|
||||||
|
SOCKS5_OK = 0, // No error for SOCKS5
|
||||||
|
SOCKS5_GEN_FAIL = 1, // General server failure
|
||||||
|
SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset
|
||||||
|
SOCKS5_NET_UNREACH = 3, // Network unreachable
|
||||||
|
SOCKS5_HOST_UNREACH = 4, // Host unreachable
|
||||||
|
SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer
|
||||||
|
SOCKS5_TTL_EXPIRED = 6, // TTL Expired
|
||||||
|
SOCKS5_CMD_UNSUP = 7, // Command unsuported
|
||||||
|
SOCKS5_ADDR_UNSUP = 8, // Address type unsuported
|
||||||
|
SOCKS4_OK = 90, // No error for SOCKS4
|
||||||
|
SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed
|
||||||
|
SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server
|
||||||
|
SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ
|
||||||
|
};
|
||||||
|
enum cmdTypes
|
||||||
|
{
|
||||||
|
CMD_CONNECT = 1, // TCP Connect
|
||||||
|
CMD_BIND = 2, // TCP Bind
|
||||||
|
CMD_UDP = 3 // UDP associate
|
||||||
|
};
|
||||||
|
enum socksVersions
|
||||||
|
{
|
||||||
|
SOCKS4 = 4, // SOCKS4
|
||||||
|
SOCKS5 = 5 // SOCKS5
|
||||||
|
};
|
||||||
|
union address
|
||||||
|
{
|
||||||
|
uint32_t ip;
|
||||||
|
SOCKSDnsAddress dns;
|
||||||
|
uint8_t ipv6[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
void EnterState(state nstate, uint8_t parseleft = 1);
|
||||||
|
bool HandleData(uint8_t *sock_buff, std::size_t len);
|
||||||
|
bool ValidateSOCKSRequest();
|
||||||
|
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
|
||||||
|
void Terminate();
|
||||||
|
void AsyncSockRead();
|
||||||
|
boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method);
|
||||||
|
boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port);
|
||||||
|
boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port);
|
||||||
|
boost::asio::const_buffers_1 GenerateUpstreamRequest();
|
||||||
|
bool Socks5ChooseAuth();
|
||||||
|
void SocksRequestFailed(errTypes error);
|
||||||
|
void SocksRequestSuccess();
|
||||||
|
void SentSocksFailed(const boost::system::error_code & ecode);
|
||||||
|
void SentSocksDone(const boost::system::error_code & ecode);
|
||||||
|
void SentSocksResponse(const boost::system::error_code & ecode);
|
||||||
|
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
|
||||||
|
void ForwardSOCKS();
|
||||||
|
|
||||||
|
void SocksUpstreamSuccess();
|
||||||
|
void AsyncUpstreamSockRead();
|
||||||
|
void SendUpstreamRequest();
|
||||||
|
void HandleUpstreamData(uint8_t * buff, std::size_t len);
|
||||||
|
void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered);
|
||||||
|
void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
|
||||||
|
void HandleUpstreamConnected(const boost::system::error_code & ecode,
|
||||||
|
boost::asio::ip::tcp::resolver::iterator itr);
|
||||||
|
void HandleUpstreamResolved(const boost::system::error_code & ecode,
|
||||||
|
boost::asio::ip::tcp::resolver::iterator itr);
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::resolver m_proxy_resolver;
|
||||||
|
uint8_t m_sock_buff[socks_buffer_size];
|
||||||
|
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock, m_upstreamSock;
|
||||||
|
std::shared_ptr<i2p::stream::Stream> m_stream;
|
||||||
|
uint8_t *m_remaining_data; //Data left to be sent
|
||||||
|
uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded
|
||||||
|
uint8_t m_response[7+max_socks_hostname_size];
|
||||||
|
uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE];
|
||||||
|
uint8_t m_upstream_request[14+max_socks_hostname_size];
|
||||||
|
std::size_t m_upstream_response_len;
|
||||||
|
address m_address; //Address
|
||||||
|
std::size_t m_remaining_data_len; //Size of the data left to be sent
|
||||||
|
uint32_t m_4aip; //Used in 4a requests
|
||||||
|
uint16_t m_port;
|
||||||
|
uint8_t m_command;
|
||||||
|
uint8_t m_parseleft; //Octets left to parse
|
||||||
|
authMethods m_authchosen; //Authentication chosen
|
||||||
|
addrTypes m_addrtype; //Address type chosen
|
||||||
|
socksVersions m_socksv; //Socks version
|
||||||
|
cmdTypes m_cmd; // Command requested
|
||||||
|
state m_state;
|
||||||
|
const bool m_UseUpstreamProxy; // do we want to use the upstream proxy for non i2p addresses?
|
||||||
|
const std::string m_UpstreamProxyAddress;
|
||||||
|
const uint16_t m_UpstreamProxyPort;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SOCKSHandler(SOCKSServer * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) :
|
||||||
|
I2PServiceHandler(parent),
|
||||||
|
m_proxy_resolver(parent->GetService()),
|
||||||
|
m_sock(sock), m_stream(nullptr),
|
||||||
|
m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4),
|
||||||
|
m_UseUpstreamProxy(useUpstream),
|
||||||
|
m_UpstreamProxyAddress(upstreamAddr),
|
||||||
|
m_UpstreamProxyPort(upstreamPort)
|
||||||
|
{ m_address.ip = 0; EnterState(GET_SOCKSV); }
|
||||||
|
|
||||||
|
~SOCKSHandler() { Terminate(); }
|
||||||
|
void Handle() { AsyncSockRead(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void SOCKSHandler::AsyncSockRead()
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "SOCKS: async sock read");
|
||||||
|
if (m_sock) {
|
||||||
|
m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size),
|
||||||
|
std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError,"SOCKS: no socket for read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::Terminate()
|
||||||
|
{
|
||||||
|
if (Kill()) return;
|
||||||
|
if (m_sock)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "SOCKS: closing socket");
|
||||||
|
m_sock->close();
|
||||||
|
m_sock = nullptr;
|
||||||
|
}
|
||||||
|
if (m_upstreamSock)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "SOCKS: closing upstream socket");
|
||||||
|
m_upstreamSock->close();
|
||||||
|
m_upstreamSock = nullptr;
|
||||||
|
}
|
||||||
|
if (m_stream)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "SOCKS: closing stream");
|
||||||
|
m_stream.reset ();
|
||||||
|
}
|
||||||
|
Done(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port)
|
||||||
|
{
|
||||||
|
assert(error >= SOCKS4_OK);
|
||||||
|
m_response[0] = '\x00'; //Version
|
||||||
|
m_response[1] = error; //Response code
|
||||||
|
htobe16buf(m_response+2,port); //Port
|
||||||
|
htobe32buf(m_response+4,ip); //IP
|
||||||
|
return boost::asio::const_buffers_1(m_response,8);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port)
|
||||||
|
{
|
||||||
|
size_t size = 6;
|
||||||
|
assert(error <= SOCKS5_ADDR_UNSUP);
|
||||||
|
m_response[0] = '\x05'; //Version
|
||||||
|
m_response[1] = error; //Response code
|
||||||
|
m_response[2] = '\x00'; //RSV
|
||||||
|
m_response[3] = type; //Address type
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ADDR_IPV4:
|
||||||
|
size = 10;
|
||||||
|
htobe32buf(m_response+4,addr.ip);
|
||||||
|
break;
|
||||||
|
case ADDR_IPV6:
|
||||||
|
size = 22;
|
||||||
|
memcpy(m_response+4,addr.ipv6, 16);
|
||||||
|
break;
|
||||||
|
case ADDR_DNS:
|
||||||
|
size = 7+addr.dns.size;
|
||||||
|
m_response[4] = addr.dns.size;
|
||||||
|
memcpy(m_response+5,addr.dns.value, addr.dns.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
htobe16buf(m_response+size-2,port); //Port
|
||||||
|
return boost::asio::const_buffers_1(m_response,size);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest()
|
||||||
|
{
|
||||||
|
size_t upstreamRequestSize = 0;
|
||||||
|
// TODO: negotiate with upstream
|
||||||
|
// SOCKS 4a
|
||||||
|
m_upstream_request[0] = '\x04'; //version
|
||||||
|
m_upstream_request[1] = m_cmd;
|
||||||
|
htobe16buf(m_upstream_request+2, m_port);
|
||||||
|
m_upstream_request[4] = 0;
|
||||||
|
m_upstream_request[5] = 0;
|
||||||
|
m_upstream_request[6] = 0;
|
||||||
|
m_upstream_request[7] = 1;
|
||||||
|
// user id
|
||||||
|
m_upstream_request[8] = 'i';
|
||||||
|
m_upstream_request[9] = '2';
|
||||||
|
m_upstream_request[10] = 'p';
|
||||||
|
m_upstream_request[11] = 'd';
|
||||||
|
m_upstream_request[12] = 0;
|
||||||
|
upstreamRequestSize += 13;
|
||||||
|
if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) {
|
||||||
|
// bounds check okay
|
||||||
|
memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size);
|
||||||
|
upstreamRequestSize += m_address.dns.size;
|
||||||
|
// null terminate
|
||||||
|
m_upstream_request[++upstreamRequestSize] = 0;
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )");
|
||||||
|
}
|
||||||
|
return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SOCKSHandler::Socks5ChooseAuth()
|
||||||
|
{
|
||||||
|
m_response[0] = '\x05'; //Version
|
||||||
|
m_response[1] = m_authchosen; //Response code
|
||||||
|
boost::asio::const_buffers_1 response(m_response,2);
|
||||||
|
if (m_authchosen == AUTH_UNACCEPTABLE)
|
||||||
|
{
|
||||||
|
LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed");
|
||||||
|
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed,
|
||||||
|
shared_from_this(), std::placeholders::_1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen);
|
||||||
|
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse,
|
||||||
|
shared_from_this(), std::placeholders::_1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All hope is lost beyond this point */
|
||||||
|
void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error)
|
||||||
|
{
|
||||||
|
boost::asio::const_buffers_1 response(nullptr,0);
|
||||||
|
assert(error != SOCKS4_OK && error != SOCKS5_OK);
|
||||||
|
switch (m_socksv)
|
||||||
|
{
|
||||||
|
case SOCKS4:
|
||||||
|
LogPrint(eLogWarning, "SOCKS: v4 request failed: ", error);
|
||||||
|
if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors
|
||||||
|
response = GenerateSOCKS4Response(error, m_4aip, m_port);
|
||||||
|
break;
|
||||||
|
case SOCKS5:
|
||||||
|
LogPrint(eLogWarning, "SOCKS: v5 request failed: ", error);
|
||||||
|
response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed,
|
||||||
|
shared_from_this(), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::SocksRequestSuccess()
|
||||||
|
{
|
||||||
|
boost::asio::const_buffers_1 response(nullptr,0);
|
||||||
|
//TODO: this should depend on things like the command type and callbacks may change
|
||||||
|
switch (m_socksv)
|
||||||
|
{
|
||||||
|
case SOCKS4:
|
||||||
|
LogPrint(eLogInfo, "SOCKS: v4 connection success");
|
||||||
|
response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port);
|
||||||
|
break;
|
||||||
|
case SOCKS5:
|
||||||
|
LogPrint(eLogInfo, "SOCKS: v5 connection success");
|
||||||
|
auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash());
|
||||||
|
address ad; ad.dns.FromString(s);
|
||||||
|
//HACK only 16 bits passed in port as SOCKS5 doesn't allow for more
|
||||||
|
response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone,
|
||||||
|
shared_from_this(), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) {
|
||||||
|
switch (nstate)
|
||||||
|
{
|
||||||
|
case GET_PORT: parseleft = 2; break;
|
||||||
|
case GET_IPV4: m_addrtype = ADDR_IPV4; m_address.ip = 0; parseleft = 4; break;
|
||||||
|
case GET4_IDENT: m_4aip = m_address.ip; break;
|
||||||
|
case GET4A_HOST:
|
||||||
|
case GET5_HOST: m_addrtype = ADDR_DNS; m_address.dns.size = 0; break;
|
||||||
|
case GET5_IPV6: m_addrtype = ADDR_IPV6; parseleft = 16; break;
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
m_parseleft = parseleft;
|
||||||
|
m_state = nstate;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SOCKSHandler::ValidateSOCKSRequest()
|
||||||
|
{
|
||||||
|
if ( m_cmd != CMD_CONNECT )
|
||||||
|
{
|
||||||
|
//TODO: we need to support binds and other shit!
|
||||||
|
LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd);
|
||||||
|
SocksRequestFailed(SOCKS5_CMD_UNSUP);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//TODO: we may want to support other address types!
|
||||||
|
if ( m_addrtype != ADDR_DNS )
|
||||||
|
{
|
||||||
|
switch (m_socksv)
|
||||||
|
{
|
||||||
|
case SOCKS5:
|
||||||
|
LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype);
|
||||||
|
break;
|
||||||
|
case SOCKS4:
|
||||||
|
LogPrint(eLogError, "SOCKS: request with v4a rejected because it's actually SOCKS4");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len)
|
||||||
|
{
|
||||||
|
assert(len); // This should always be called with a least a byte left to parse
|
||||||
|
while (len > 0)
|
||||||
|
{
|
||||||
|
switch (m_state)
|
||||||
|
{
|
||||||
|
case GET_SOCKSV:
|
||||||
|
m_socksv = (SOCKSHandler::socksVersions) *sock_buff;
|
||||||
|
switch (*sock_buff)
|
||||||
|
{
|
||||||
|
case SOCKS4:
|
||||||
|
EnterState(GET_COMMAND); //Initialize the parser at the right position
|
||||||
|
break;
|
||||||
|
case SOCKS5:
|
||||||
|
EnterState(GET5_AUTHNUM); //Initialize the parser at the right position
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint(eLogError, "SOCKS: rejected invalid version: ", ((int)*sock_buff));
|
||||||
|
Terminate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET5_AUTHNUM:
|
||||||
|
EnterState(GET5_AUTH, *sock_buff);
|
||||||
|
break;
|
||||||
|
case GET5_AUTH:
|
||||||
|
m_parseleft --;
|
||||||
|
if (*sock_buff == AUTH_NONE)
|
||||||
|
m_authchosen = AUTH_NONE;
|
||||||
|
if ( m_parseleft == 0 )
|
||||||
|
{
|
||||||
|
if (!Socks5ChooseAuth()) return false;
|
||||||
|
EnterState(GET5_REQUESTV);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET_COMMAND:
|
||||||
|
switch (*sock_buff)
|
||||||
|
{
|
||||||
|
case CMD_CONNECT:
|
||||||
|
case CMD_BIND:
|
||||||
|
break;
|
||||||
|
case CMD_UDP:
|
||||||
|
if (m_socksv == SOCKS5) break;
|
||||||
|
default:
|
||||||
|
LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff));
|
||||||
|
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_cmd = (SOCKSHandler::cmdTypes)*sock_buff;
|
||||||
|
switch (m_socksv)
|
||||||
|
{
|
||||||
|
case SOCKS5: EnterState(GET5_GETRSV); break;
|
||||||
|
case SOCKS4: EnterState(GET_PORT); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET_PORT:
|
||||||
|
m_port = (m_port << 8)|((uint16_t)*sock_buff);
|
||||||
|
m_parseleft--;
|
||||||
|
if (m_parseleft == 0)
|
||||||
|
{
|
||||||
|
switch (m_socksv)
|
||||||
|
{
|
||||||
|
case SOCKS5: EnterState(READY); break;
|
||||||
|
case SOCKS4: EnterState(GET_IPV4); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET_IPV4:
|
||||||
|
m_address.ip = (m_address.ip << 8)|((uint32_t)*sock_buff);
|
||||||
|
m_parseleft--;
|
||||||
|
if (m_parseleft == 0)
|
||||||
|
{
|
||||||
|
switch (m_socksv)
|
||||||
|
{
|
||||||
|
case SOCKS5: EnterState(GET_PORT); break;
|
||||||
|
case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET4_IDENT:
|
||||||
|
if (!*sock_buff)
|
||||||
|
{
|
||||||
|
if( m_4aip == 0 || m_4aip > 255 )
|
||||||
|
EnterState(READY);
|
||||||
|
else
|
||||||
|
EnterState(GET4A_HOST);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET4A_HOST:
|
||||||
|
if (!*sock_buff)
|
||||||
|
{
|
||||||
|
EnterState(READY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (m_address.dns.size >= max_socks_hostname_size)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "SOCKS: v4a req failed: destination is too large");
|
||||||
|
SocksRequestFailed(SOCKS4_FAIL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_address.dns.push_back(*sock_buff);
|
||||||
|
break;
|
||||||
|
case GET5_REQUESTV:
|
||||||
|
if (*sock_buff != SOCKS5)
|
||||||
|
{
|
||||||
|
LogPrint(eLogError,"SOCKS: v5 rejected unknown request version: ", ((int)*sock_buff));
|
||||||
|
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EnterState(GET_COMMAND);
|
||||||
|
break;
|
||||||
|
case GET5_GETRSV:
|
||||||
|
if ( *sock_buff != 0 )
|
||||||
|
{
|
||||||
|
LogPrint(eLogError, "SOCKS: v5 unknown reserved field: ", ((int)*sock_buff));
|
||||||
|
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EnterState(GET5_GETADDRTYPE);
|
||||||
|
break;
|
||||||
|
case GET5_GETADDRTYPE:
|
||||||
|
switch (*sock_buff)
|
||||||
|
{
|
||||||
|
case ADDR_IPV4: EnterState(GET_IPV4); break;
|
||||||
|
case ADDR_IPV6: EnterState(GET5_IPV6); break;
|
||||||
|
case ADDR_DNS : EnterState(GET5_HOST_SIZE); break;
|
||||||
|
default:
|
||||||
|
LogPrint(eLogError, "SOCKS: v5 unknown address type: ", ((int)*sock_buff));
|
||||||
|
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET5_IPV6:
|
||||||
|
m_address.ipv6[16-m_parseleft] = *sock_buff;
|
||||||
|
m_parseleft--;
|
||||||
|
if (m_parseleft == 0) EnterState(GET_PORT);
|
||||||
|
break;
|
||||||
|
case GET5_HOST_SIZE:
|
||||||
|
EnterState(GET5_HOST, *sock_buff);
|
||||||
|
break;
|
||||||
|
case GET5_HOST:
|
||||||
|
m_address.dns.push_back(*sock_buff);
|
||||||
|
m_parseleft--;
|
||||||
|
if (m_parseleft == 0) EnterState(GET_PORT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint(eLogError, "SOCKS: parse state?? ", m_state);
|
||||||
|
Terminate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sock_buff++;
|
||||||
|
len--;
|
||||||
|
if (m_state == READY)
|
||||||
|
{
|
||||||
|
m_remaining_data_len = len;
|
||||||
|
m_remaining_data = sock_buff;
|
||||||
|
return ValidateSOCKSRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "SOCKS: recieved ", len, " bytes");
|
||||||
|
if(ecode)
|
||||||
|
{
|
||||||
|
LogPrint(eLogWarning, "SOCKS: recv got error: ", ecode);
|
||||||
|
Terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HandleData(m_sock_buff, len))
|
||||||
|
{
|
||||||
|
if (m_state == READY)
|
||||||
|
{
|
||||||
|
const std::string addr = m_address.dns.ToString();
|
||||||
|
LogPrint(eLogInfo, "SOCKS: requested ", addr, ":" , m_port);
|
||||||
|
const size_t addrlen = addr.size();
|
||||||
|
// does it end with .i2p?
|
||||||
|
if ( addr.rfind(".i2p") == addrlen - 4) {
|
||||||
|
// yes it does, make an i2p session
|
||||||
|
GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete,
|
||||||
|
shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port);
|
||||||
|
} else if (m_UseUpstreamProxy) {
|
||||||
|
// forward it to upstream proxy
|
||||||
|
ForwardSOCKS();
|
||||||
|
} else {
|
||||||
|
// no upstream proxy
|
||||||
|
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AsyncSockRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
LogPrint (eLogError, "SOCKS: closing socket after sending failure because: ", ecode.message ());
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
if (Kill()) return;
|
||||||
|
LogPrint (eLogInfo, "SOCKS: new I2PTunnel connection");
|
||||||
|
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, m_stream);
|
||||||
|
GetOwner()->AddHandler (connection);
|
||||||
|
connection->I2PConnect (m_remaining_data,m_remaining_data_len);
|
||||||
|
Done(shared_from_this());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SOCKS: closing socket after completion reply because: ", ecode.message ());
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode)
|
||||||
|
{
|
||||||
|
if (ecode)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SOCKS: closing socket after sending reply because: ", ecode.message ());
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
m_stream = stream;
|
||||||
|
SocksRequestSuccess();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SOCKS: error when creating the stream, check the previous warnings for more info");
|
||||||
|
SocksRequestFailed(SOCKS5_HOST_UNREACH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::ForwardSOCKS()
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "SOCKS: forwarding to upstream");
|
||||||
|
EnterState(UPSTREAM_RESOLVE);
|
||||||
|
boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress,boost::lexical_cast<std::string>(m_UpstreamProxyPort) );
|
||||||
|
m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::AsyncUpstreamSockRead()
|
||||||
|
{
|
||||||
|
LogPrint(eLogDebug, "SOCKS: async upstream sock read");
|
||||||
|
if (m_upstreamSock) {
|
||||||
|
m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE),
|
||||||
|
std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "SOCKS: no upstream socket for read");
|
||||||
|
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
if (m_state == UPSTREAM_HANDSHAKE ) {
|
||||||
|
// we are trying to handshake but it failed
|
||||||
|
SocksRequestFailed(SOCKS5_NET_UNREACH);
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "SOCKS: bad state when reading from upstream: ", (int) m_state);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HandleUpstreamData(m_upstream_response, bytes_transfered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::SocksUpstreamSuccess()
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "SOCKS: upstream success");
|
||||||
|
boost::asio::const_buffers_1 response(nullptr, 0);
|
||||||
|
switch (m_socksv)
|
||||||
|
{
|
||||||
|
case SOCKS4:
|
||||||
|
LogPrint(eLogInfo, "SOCKS: v4 connection success");
|
||||||
|
response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port);
|
||||||
|
break;
|
||||||
|
case SOCKS5:
|
||||||
|
LogPrint(eLogInfo, "SOCKS: v5 connection success");
|
||||||
|
//HACK only 16 bits passed in port as SOCKS5 doesn't allow for more
|
||||||
|
response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, m_address, m_port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_sock->send(response);
|
||||||
|
auto forwarder = std::make_shared<i2p::client::TCPIPPipe>(GetOwner(), m_sock, m_upstreamSock);
|
||||||
|
m_upstreamSock = nullptr;
|
||||||
|
m_sock = nullptr;
|
||||||
|
GetOwner()->AddHandler(forwarder);
|
||||||
|
forwarder->Start();
|
||||||
|
Terminate();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len)
|
||||||
|
{
|
||||||
|
if (m_state == UPSTREAM_HANDSHAKE) {
|
||||||
|
m_upstream_response_len += len;
|
||||||
|
// handle handshake data
|
||||||
|
if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
|
||||||
|
// too small, continue reading
|
||||||
|
AsyncUpstreamSockRead();
|
||||||
|
} else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
|
||||||
|
// just right
|
||||||
|
uint8_t resp = m_upstream_response[1];
|
||||||
|
if (resp == SOCKS4_OK) {
|
||||||
|
// we have connected !
|
||||||
|
SocksUpstreamSuccess();
|
||||||
|
} else {
|
||||||
|
// upstream failure
|
||||||
|
LogPrint(eLogError, "SOCKS: upstream proxy failure: ", (int) resp);
|
||||||
|
// TODO: runtime error?
|
||||||
|
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// too big
|
||||||
|
SocksRequestFailed(SOCKS5_GEN_FAIL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// invalid state
|
||||||
|
LogPrint(eLogError, "SOCKS: invalid state reading from upstream: ", (int) m_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::SendUpstreamRequest()
|
||||||
|
{
|
||||||
|
LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy");
|
||||||
|
EnterState(UPSTREAM_HANDSHAKE);
|
||||||
|
if (m_upstreamSock) {
|
||||||
|
boost::asio::write(*m_upstreamSock,
|
||||||
|
GenerateUpstreamRequest());
|
||||||
|
AsyncUpstreamSockRead();
|
||||||
|
} else {
|
||||||
|
LogPrint(eLogError, "SOCKS: no upstream socket to send handshake to");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message());
|
||||||
|
SocksRequestFailed(SOCKS5_NET_UNREACH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint(eLogInfo, "SOCKS: connected to upstream proxy");
|
||||||
|
SendUpstreamRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)
|
||||||
|
{
|
||||||
|
if (ecode) {
|
||||||
|
// error resolving
|
||||||
|
LogPrint(eLogWarning, "SOCKS: upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message());
|
||||||
|
SocksRequestFailed(SOCKS5_NET_UNREACH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint(eLogInfo, "SOCKS: upstream proxy resolved");
|
||||||
|
EnterState(UPSTREAM_CONNECT);
|
||||||
|
auto & service = GetOwner()->GetService();
|
||||||
|
m_upstreamSock = std::make_shared<boost::asio::ip::tcp::socket>(service);
|
||||||
|
boost::asio::async_connect(*m_upstreamSock, itr,
|
||||||
|
std::bind(&SOCKSHandler::HandleUpstreamConnected,
|
||||||
|
shared_from_this(), std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKSServer::SOCKSServer(const std::string& address, int port, const std::string& outAddress, uint16_t outPort,
|
||||||
|
std::shared_ptr<i2p::client::ClientDestination> localDestination) :
|
||||||
|
TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ())
|
||||||
|
{
|
||||||
|
m_UseUpstreamProxy = false;
|
||||||
|
if (outAddress.length() > 0)
|
||||||
|
SetUpstreamProxy(outAddress, outPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::client::I2PServiceHandler> SOCKSServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
|
||||||
|
{
|
||||||
|
return std::make_shared<SOCKSHandler> (this, socket, m_UpstreamProxyAddress, m_UpstreamProxyPort, m_UseUpstreamProxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SOCKSServer::SetUpstreamProxy(const std::string & addr, const uint16_t port)
|
||||||
|
{
|
||||||
|
m_UpstreamProxyAddress = addr;
|
||||||
|
m_UpstreamProxyPort = port;
|
||||||
|
m_UseUpstreamProxy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
SOCKS.h
Normal file
40
SOCKS.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef SOCKS_H__
|
||||||
|
#define SOCKS_H__
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <mutex>
|
||||||
|
#include "I2PService.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace proxy
|
||||||
|
{
|
||||||
|
class SOCKSServer: public i2p::client::TCPIPAcceptor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SOCKSServer(const std::string& address, int port, const std::string& outAddress, uint16_t outPort,
|
||||||
|
std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
|
||||||
|
~SOCKSServer() {};
|
||||||
|
|
||||||
|
void SetUpstreamProxy(const std::string & addr, const uint16_t port);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Implements TCPIPAcceptor
|
||||||
|
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||||
|
const char* GetName() { return "SOCKS"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_UpstreamProxyAddress;
|
||||||
|
uint16_t m_UpstreamProxyPort;
|
||||||
|
bool m_UseUpstreamProxy;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef SOCKSServer SOCKSProxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
604
SSU.cpp
Normal file
604
SSU.cpp
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "SSU.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
SSUServer::SSUServer (int port): m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
|
||||||
|
m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService),
|
||||||
|
m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port),
|
||||||
|
m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService),
|
||||||
|
m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service)
|
||||||
|
{
|
||||||
|
m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535));
|
||||||
|
m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535));
|
||||||
|
if (context.SupportsV6 ())
|
||||||
|
{
|
||||||
|
m_SocketV6.open (boost::asio::ip::udp::v6());
|
||||||
|
m_SocketV6.set_option (boost::asio::ip::v6_only (true));
|
||||||
|
m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (65535));
|
||||||
|
m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (65535));
|
||||||
|
m_SocketV6.bind (m_EndpointV6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SSUServer::~SSUServer ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::Start ()
|
||||||
|
{
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this));
|
||||||
|
m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
|
||||||
|
m_ReceiversService.post (std::bind (&SSUServer::Receive, this));
|
||||||
|
if (context.SupportsV6 ())
|
||||||
|
{
|
||||||
|
m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this));
|
||||||
|
m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this));
|
||||||
|
}
|
||||||
|
SchedulePeerTestsCleanupTimer ();
|
||||||
|
ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::Stop ()
|
||||||
|
{
|
||||||
|
DeleteAllSessions ();
|
||||||
|
m_IsRunning = false;
|
||||||
|
m_Service.stop ();
|
||||||
|
m_Socket.close ();
|
||||||
|
m_ServiceV6.stop ();
|
||||||
|
m_SocketV6.close ();
|
||||||
|
m_ReceiversService.stop ();
|
||||||
|
if (m_ReceiversThread)
|
||||||
|
{
|
||||||
|
m_ReceiversThread->join ();
|
||||||
|
delete m_ReceiversThread;
|
||||||
|
m_ReceiversThread = nullptr;
|
||||||
|
}
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = nullptr;
|
||||||
|
}
|
||||||
|
if (m_ThreadV6)
|
||||||
|
{
|
||||||
|
m_ThreadV6->join ();
|
||||||
|
delete m_ThreadV6;
|
||||||
|
m_ThreadV6 = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Service.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::RunV6 ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_ServiceV6.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::RunReceivers ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_ReceiversService.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay)
|
||||||
|
{
|
||||||
|
m_Relays[tag] = relay;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SSUSession> SSUServer::FindRelaySession (uint32_t tag)
|
||||||
|
{
|
||||||
|
auto it = m_Relays.find (tag);
|
||||||
|
if (it != m_Relays.end ())
|
||||||
|
return FindSession (it->second);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to)
|
||||||
|
{
|
||||||
|
if (to.protocol () == boost::asio::ip::udp::v4())
|
||||||
|
m_Socket.send_to (boost::asio::buffer (buf, len), to);
|
||||||
|
else
|
||||||
|
m_SocketV6.send_to (boost::asio::buffer (buf, len), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::Receive ()
|
||||||
|
{
|
||||||
|
SSUPacket * packet = new SSUPacket ();
|
||||||
|
m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from,
|
||||||
|
std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::ReceiveV6 ()
|
||||||
|
{
|
||||||
|
SSUPacket * packet = new SSUPacket ();
|
||||||
|
m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from,
|
||||||
|
std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
packet->len = bytes_transferred;
|
||||||
|
std::vector<SSUPacket *> packets;
|
||||||
|
packets.push_back (packet);
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
size_t moreBytes = m_Socket.available(ec);
|
||||||
|
while (moreBytes && packets.size () < 25)
|
||||||
|
{
|
||||||
|
packet = new SSUPacket ();
|
||||||
|
packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from);
|
||||||
|
packets.push_back (packet);
|
||||||
|
moreBytes = m_Socket.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions));
|
||||||
|
Receive ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SSU: receive error: ", ecode.message ());
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
|
||||||
|
{
|
||||||
|
if (!ecode)
|
||||||
|
{
|
||||||
|
packet->len = bytes_transferred;
|
||||||
|
std::vector<SSUPacket *> packets;
|
||||||
|
packets.push_back (packet);
|
||||||
|
|
||||||
|
size_t moreBytes = m_SocketV6.available ();
|
||||||
|
while (moreBytes && packets.size () < 25)
|
||||||
|
{
|
||||||
|
packet = new SSUPacket ();
|
||||||
|
packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from);
|
||||||
|
packets.push_back (packet);
|
||||||
|
moreBytes = m_SocketV6.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6));
|
||||||
|
ReceiveV6 ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ());
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::HandleReceivedPackets (std::vector<SSUPacket *> packets,
|
||||||
|
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > * sessions)
|
||||||
|
{
|
||||||
|
std::shared_ptr<SSUSession> session;
|
||||||
|
for (auto it1: packets)
|
||||||
|
{
|
||||||
|
auto packet = it1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous
|
||||||
|
{
|
||||||
|
if (session) session->FlushData ();
|
||||||
|
auto it = sessions->find (packet->from);
|
||||||
|
if (it != sessions->end ())
|
||||||
|
session = it->second;
|
||||||
|
if (!session)
|
||||||
|
{
|
||||||
|
session = std::make_shared<SSUSession> (*this, packet->from);
|
||||||
|
session->WaitForConnect ();
|
||||||
|
(*sessions)[packet->from] = session;
|
||||||
|
LogPrint (eLogInfo, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session->ProcessNextMessage (packet->buf, packet->len, packet->from);
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ());
|
||||||
|
if (session) session->FlushData ();
|
||||||
|
session = nullptr;
|
||||||
|
}
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
if (session) session->FlushData ();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SSUSession> SSUServer::FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const
|
||||||
|
{
|
||||||
|
if (!router) return nullptr;
|
||||||
|
auto address = router->GetSSUAddress (true); // v4 only
|
||||||
|
if (!address) return nullptr;
|
||||||
|
auto session = FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
|
||||||
|
if (session || !context.SupportsV6 ())
|
||||||
|
return session;
|
||||||
|
// try v6
|
||||||
|
address = router->GetSSUV6Address ();
|
||||||
|
if (!address) return nullptr;
|
||||||
|
return FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SSUSession> SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const
|
||||||
|
{
|
||||||
|
auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
|
||||||
|
auto it = sessions.find (e);
|
||||||
|
if (it != sessions.end ())
|
||||||
|
return it->second;
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest)
|
||||||
|
{
|
||||||
|
auto address = router->GetSSUAddress (!context.SupportsV6 ());
|
||||||
|
if (address)
|
||||||
|
CreateSession (router, address->host, address->port, peerTest);
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
|
||||||
|
const boost::asio::ip::address& addr, int port, bool peerTest)
|
||||||
|
{
|
||||||
|
if (router)
|
||||||
|
{
|
||||||
|
if (router->UsesIntroducer ())
|
||||||
|
m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::asio::ip::udp::endpoint remoteEndpoint (addr, port);
|
||||||
|
auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service;
|
||||||
|
s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest)
|
||||||
|
{
|
||||||
|
auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
|
||||||
|
auto it = sessions.find (remoteEndpoint);
|
||||||
|
if (it != sessions.end ())
|
||||||
|
{
|
||||||
|
auto session = it->second;
|
||||||
|
if (peerTest && session->GetState () == eSessionStateEstablished)
|
||||||
|
session->SendPeerTest ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// otherwise create new session
|
||||||
|
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
|
||||||
|
sessions[remoteEndpoint] = session;
|
||||||
|
// connect
|
||||||
|
LogPrint (eLogInfo, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ",
|
||||||
|
remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ());
|
||||||
|
session->Connect ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest)
|
||||||
|
{
|
||||||
|
if (router && router->UsesIntroducer ())
|
||||||
|
{
|
||||||
|
auto address = router->GetSSUAddress (true); // v4 only for now
|
||||||
|
if (address)
|
||||||
|
{
|
||||||
|
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
|
||||||
|
auto it = m_Sessions.find (remoteEndpoint);
|
||||||
|
// check if session if presented alredy
|
||||||
|
if (it != m_Sessions.end ())
|
||||||
|
{
|
||||||
|
auto session = it->second;
|
||||||
|
if (peerTest && session->GetState () == eSessionStateEstablished)
|
||||||
|
session->SendPeerTest ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// create new session
|
||||||
|
int numIntroducers = address->introducers.size ();
|
||||||
|
if (numIntroducers > 0)
|
||||||
|
{
|
||||||
|
std::shared_ptr<SSUSession> introducerSession;
|
||||||
|
const i2p::data::RouterInfo::Introducer * introducer = nullptr;
|
||||||
|
// we might have a session to introducer already
|
||||||
|
for (int i = 0; i < numIntroducers; i++)
|
||||||
|
{
|
||||||
|
auto intr = &(address->introducers[i]);
|
||||||
|
boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort);
|
||||||
|
if (ep.address ().is_v4 ()) // ipv4 only
|
||||||
|
{
|
||||||
|
if (!introducer) introducer = intr; // we pick first one for now
|
||||||
|
it = m_Sessions.find (ep);
|
||||||
|
if (it != m_Sessions.end ())
|
||||||
|
{
|
||||||
|
introducerSession = it->second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!introducer)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 introducers present");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (introducerSession) // session found
|
||||||
|
LogPrint (eLogInfo, "SSU: Session to introducer already exists");
|
||||||
|
else // create new
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "SSU: Creating new session to introducer");
|
||||||
|
boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort);
|
||||||
|
introducerSession = std::make_shared<SSUSession> (*this, introducerEndpoint, router);
|
||||||
|
m_Sessions[introducerEndpoint] = introducerSession;
|
||||||
|
}
|
||||||
|
// create session
|
||||||
|
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
|
||||||
|
m_Sessions[remoteEndpoint] = session;
|
||||||
|
// introduce
|
||||||
|
LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()),
|
||||||
|
"] through introducer ", introducer->iHost, ":", introducer->iPort);
|
||||||
|
session->WaitForIntroduction ();
|
||||||
|
if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable
|
||||||
|
{
|
||||||
|
uint8_t buf[1];
|
||||||
|
Send (buf, 0, remoteEndpoint); // send HolePunch
|
||||||
|
}
|
||||||
|
introducerSession->Introduce (*introducer, router);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::DeleteSession (std::shared_ptr<SSUSession> session)
|
||||||
|
{
|
||||||
|
if (session)
|
||||||
|
{
|
||||||
|
session->Close ();
|
||||||
|
auto& ep = session->GetRemoteEndpoint ();
|
||||||
|
if (ep.address ().is_v6 ())
|
||||||
|
m_SessionsV6.erase (ep);
|
||||||
|
else
|
||||||
|
m_Sessions.erase (ep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::DeleteAllSessions ()
|
||||||
|
{
|
||||||
|
for (auto it: m_Sessions)
|
||||||
|
it.second->Close ();
|
||||||
|
m_Sessions.clear ();
|
||||||
|
|
||||||
|
for (auto it: m_SessionsV6)
|
||||||
|
it.second->Close ();
|
||||||
|
m_SessionsV6.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Filter>
|
||||||
|
std::shared_ptr<SSUSession> SSUServer::GetRandomV4Session (Filter filter) // v4 only
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<SSUSession> > filteredSessions;
|
||||||
|
for (auto s :m_Sessions)
|
||||||
|
if (filter (s.second)) filteredSessions.push_back (s.second);
|
||||||
|
if (filteredSessions.size () > 0)
|
||||||
|
{
|
||||||
|
auto ind = rand () % filteredSessions.size ();
|
||||||
|
return filteredSessions[ind];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SSUSession> SSUServer::GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded) // v4 only
|
||||||
|
{
|
||||||
|
return GetRandomV4Session (
|
||||||
|
[excluded](std::shared_ptr<SSUSession> session)->bool
|
||||||
|
{
|
||||||
|
return session->GetState () == eSessionStateEstablished && !session->IsV6 () &&
|
||||||
|
session != excluded;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<SSUSession *> SSUServer::FindIntroducers (int maxNumIntroducers)
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
std::set<SSUSession *> ret;
|
||||||
|
for (int i = 0; i < maxNumIntroducers; i++)
|
||||||
|
{
|
||||||
|
auto session = GetRandomV4Session (
|
||||||
|
[&ret, ts](std::shared_ptr<SSUSession> session)->bool
|
||||||
|
{
|
||||||
|
return session->GetRelayTag () && !ret.count (session.get ()) &&
|
||||||
|
session->GetState () == eSessionStateEstablished &&
|
||||||
|
ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (session)
|
||||||
|
{
|
||||||
|
ret.insert (session.get ());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::ScheduleIntroducersUpdateTimer ()
|
||||||
|
{
|
||||||
|
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
|
||||||
|
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
|
||||||
|
this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
// timeout expired
|
||||||
|
if (i2p::context.GetStatus () == eRouterStatusTesting)
|
||||||
|
{
|
||||||
|
// we still don't know if we need introducers
|
||||||
|
ScheduleIntroducersUpdateTimer ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore
|
||||||
|
// we are firewalled
|
||||||
|
if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable ();
|
||||||
|
std::list<boost::asio::ip::udp::endpoint> newList;
|
||||||
|
size_t numIntroducers = 0;
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (auto it :m_Introducers)
|
||||||
|
{
|
||||||
|
auto session = FindSession (it);
|
||||||
|
if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION)
|
||||||
|
{
|
||||||
|
session->SendKeepAlive ();
|
||||||
|
newList.push_back (it);
|
||||||
|
numIntroducers++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
i2p::context.RemoveIntroducer (it);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numIntroducers < SSU_MAX_NUM_INTRODUCERS)
|
||||||
|
{
|
||||||
|
// create new
|
||||||
|
auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS);
|
||||||
|
if (introducers.size () > 0)
|
||||||
|
{
|
||||||
|
for (auto it1: introducers)
|
||||||
|
{
|
||||||
|
auto& ep = it1->GetRemoteEndpoint ();
|
||||||
|
i2p::data::RouterInfo::Introducer introducer;
|
||||||
|
introducer.iHost = ep.address ();
|
||||||
|
introducer.iPort = ep.port ();
|
||||||
|
introducer.iTag = it1->GetRelayTag ();
|
||||||
|
introducer.iKey = it1->GetIntroKey ();
|
||||||
|
if (i2p::context.AddIntroducer (introducer))
|
||||||
|
{
|
||||||
|
newList.push_back (ep);
|
||||||
|
if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_Introducers = newList;
|
||||||
|
if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS)
|
||||||
|
{
|
||||||
|
auto introducer = i2p::data::netdb.GetRandomIntroducer ();
|
||||||
|
if (introducer)
|
||||||
|
CreateSession (introducer);
|
||||||
|
}
|
||||||
|
ScheduleIntroducersUpdateTimer ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session)
|
||||||
|
{
|
||||||
|
m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session };
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce)
|
||||||
|
{
|
||||||
|
auto it = m_PeerTests.find (nonce);
|
||||||
|
if (it != m_PeerTests.end ())
|
||||||
|
return it->second.role;
|
||||||
|
else
|
||||||
|
return ePeerTestParticipantUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SSUSession> SSUServer::GetPeerTestSession (uint32_t nonce)
|
||||||
|
{
|
||||||
|
auto it = m_PeerTests.find (nonce);
|
||||||
|
if (it != m_PeerTests.end ())
|
||||||
|
return it->second.session;
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role)
|
||||||
|
{
|
||||||
|
auto it = m_PeerTests.find (nonce);
|
||||||
|
if (it != m_PeerTests.end ())
|
||||||
|
it->second.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::RemovePeerTest (uint32_t nonce)
|
||||||
|
{
|
||||||
|
m_PeerTests.erase (nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::SchedulePeerTestsCleanupTimer ()
|
||||||
|
{
|
||||||
|
m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT));
|
||||||
|
m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer,
|
||||||
|
this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
int numDeleted = 0;
|
||||||
|
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
|
||||||
|
{
|
||||||
|
if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL)
|
||||||
|
{
|
||||||
|
numDeleted++;
|
||||||
|
it = m_PeerTests.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
if (numDeleted > 0)
|
||||||
|
LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired");
|
||||||
|
SchedulePeerTestsCleanupTimer ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
119
SSU.h
Normal file
119
SSU.h
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#ifndef SSU_H__
|
||||||
|
#define SSU_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "SSUSession.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds
|
||||||
|
const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds
|
||||||
|
const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
|
||||||
|
const size_t SSU_MAX_NUM_INTRODUCERS = 3;
|
||||||
|
|
||||||
|
struct SSUPacket
|
||||||
|
{
|
||||||
|
i2p::crypto::AESAlignedBuffer<1500> buf;
|
||||||
|
boost::asio::ip::udp::endpoint from;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SSUServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SSUServer (int port);
|
||||||
|
~SSUServer ();
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false);
|
||||||
|
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
|
||||||
|
const boost::asio::ip::address& addr, int port, bool peerTest = false);
|
||||||
|
void CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest);
|
||||||
|
std::shared_ptr<SSUSession> FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const;
|
||||||
|
std::shared_ptr<SSUSession> FindSession (const boost::asio::ip::udp::endpoint& e) const;
|
||||||
|
std::shared_ptr<SSUSession> GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded);
|
||||||
|
void DeleteSession (std::shared_ptr<SSUSession> session);
|
||||||
|
void DeleteAllSessions ();
|
||||||
|
|
||||||
|
boost::asio::io_service& GetService () { return m_Service; };
|
||||||
|
boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; };
|
||||||
|
const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; };
|
||||||
|
void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to);
|
||||||
|
void AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay);
|
||||||
|
std::shared_ptr<SSUSession> FindRelaySession (uint32_t tag);
|
||||||
|
|
||||||
|
void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session = nullptr);
|
||||||
|
PeerTestParticipant GetPeerTestParticipant (uint32_t nonce);
|
||||||
|
std::shared_ptr<SSUSession> GetPeerTestSession (uint32_t nonce);
|
||||||
|
void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role);
|
||||||
|
void RemovePeerTest (uint32_t nonce);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void RunV6 ();
|
||||||
|
void RunReceivers ();
|
||||||
|
void Receive ();
|
||||||
|
void ReceiveV6 ();
|
||||||
|
void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
|
||||||
|
void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
|
||||||
|
void HandleReceivedPackets (std::vector<SSUPacket *> packets,
|
||||||
|
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> >* sessions);
|
||||||
|
|
||||||
|
void CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false);
|
||||||
|
template<typename Filter>
|
||||||
|
std::shared_ptr<SSUSession> GetRandomV4Session (Filter filter);
|
||||||
|
|
||||||
|
std::set<SSUSession *> FindIntroducers (int maxNumIntroducers);
|
||||||
|
void ScheduleIntroducersUpdateTimer ();
|
||||||
|
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
void SchedulePeerTestsCleanupTimer ();
|
||||||
|
void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct PeerTest
|
||||||
|
{
|
||||||
|
uint64_t creationTime;
|
||||||
|
PeerTestParticipant role;
|
||||||
|
std::shared_ptr<SSUSession> session; // for Bob to Alice
|
||||||
|
};
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread;
|
||||||
|
boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService;
|
||||||
|
boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork;
|
||||||
|
boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6;
|
||||||
|
boost::asio::ip::udp::socket m_Socket, m_SocketV6;
|
||||||
|
boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer;
|
||||||
|
std::list<boost::asio::ip::udp::endpoint> m_Introducers; // introducers we are connected to
|
||||||
|
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > m_Sessions, m_SessionsV6;
|
||||||
|
std::map<uint32_t, boost::asio::ip::udp::endpoint> m_Relays; // we are introducer
|
||||||
|
std::map<uint32_t, PeerTest> m_PeerTests; // nonce -> creation time in milliseconds
|
||||||
|
|
||||||
|
public:
|
||||||
|
// for HTTP only
|
||||||
|
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
|
||||||
|
const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
508
SSUData.cpp
Normal file
508
SSUData.cpp
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "SSU.h"
|
||||||
|
#include "SSUData.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize)
|
||||||
|
{
|
||||||
|
if (msg->len + fragmentSize > msg->maxLen)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough");
|
||||||
|
auto newMsg = NewI2NPMessage ();
|
||||||
|
*newMsg = *msg;
|
||||||
|
msg = newMsg;
|
||||||
|
}
|
||||||
|
if (msg->Concat (fragment, fragmentSize) < fragmentSize)
|
||||||
|
LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen);
|
||||||
|
nextFragmentNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSUData::SSUData (SSUSession& session):
|
||||||
|
m_Session (session), m_ResendTimer (session.GetService ()), m_DecayTimer (session.GetService ()),
|
||||||
|
m_IncompleteMessagesCleanupTimer (session.GetService ()),
|
||||||
|
m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE),
|
||||||
|
m_PacketSize (m_MaxPacketSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SSUData::~SSUData ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::Start ()
|
||||||
|
{
|
||||||
|
ScheduleIncompleteMessagesCleanup ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::Stop ()
|
||||||
|
{
|
||||||
|
m_ResendTimer.cancel ();
|
||||||
|
m_DecayTimer.cancel ();
|
||||||
|
m_IncompleteMessagesCleanupTimer.cancel ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter)
|
||||||
|
{
|
||||||
|
if (remoteRouter) return;
|
||||||
|
auto ssuAddress = remoteRouter->GetSSUAddress ();
|
||||||
|
if (ssuAddress && ssuAddress->mtu)
|
||||||
|
{
|
||||||
|
if (m_Session.IsV6 ())
|
||||||
|
m_PacketSize = ssuAddress->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE;
|
||||||
|
else
|
||||||
|
m_PacketSize = ssuAddress->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE;
|
||||||
|
if (m_PacketSize > 0)
|
||||||
|
{
|
||||||
|
// make sure packet size multiple of 16
|
||||||
|
m_PacketSize >>= 4;
|
||||||
|
m_PacketSize <<= 4;
|
||||||
|
if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize;
|
||||||
|
LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->mtu, " packet size=", m_PacketSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->mtu);
|
||||||
|
m_PacketSize = m_MaxPacketSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent)
|
||||||
|
{
|
||||||
|
auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent);
|
||||||
|
if (routerInfo)
|
||||||
|
AdjustPacketSize (routerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::ProcessSentMessageAck (uint32_t msgID)
|
||||||
|
{
|
||||||
|
auto it = m_SentMessages.find (msgID);
|
||||||
|
if (it != m_SentMessages.end ())
|
||||||
|
{
|
||||||
|
m_SentMessages.erase (it);
|
||||||
|
if (m_SentMessages.empty ())
|
||||||
|
m_ResendTimer.cancel ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag)
|
||||||
|
{
|
||||||
|
if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED)
|
||||||
|
{
|
||||||
|
// explicit ACKs
|
||||||
|
uint8_t numAcks =*buf;
|
||||||
|
buf++;
|
||||||
|
for (int i = 0; i < numAcks; i++)
|
||||||
|
ProcessSentMessageAck (bufbe32toh (buf+i*4));
|
||||||
|
buf += numAcks*4;
|
||||||
|
}
|
||||||
|
if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED)
|
||||||
|
{
|
||||||
|
// explicit ACK bitfields
|
||||||
|
uint8_t numBitfields =*buf;
|
||||||
|
buf++;
|
||||||
|
for (int i = 0; i < numBitfields; i++)
|
||||||
|
{
|
||||||
|
uint32_t msgID = bufbe32toh (buf);
|
||||||
|
buf += 4; // msgID
|
||||||
|
auto it = m_SentMessages.find (msgID);
|
||||||
|
// process individual Ack bitfields
|
||||||
|
bool isNonLast = false;
|
||||||
|
int fragment = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
uint8_t bitfield = *buf;
|
||||||
|
isNonLast = bitfield & 0x80;
|
||||||
|
bitfield &= 0x7F; // clear MSB
|
||||||
|
if (bitfield && it != m_SentMessages.end ())
|
||||||
|
{
|
||||||
|
int numSentFragments = it->second->fragments.size ();
|
||||||
|
// process bits
|
||||||
|
uint8_t mask = 0x01;
|
||||||
|
for (int j = 0; j < 7; j++)
|
||||||
|
{
|
||||||
|
if (bitfield & mask)
|
||||||
|
{
|
||||||
|
if (fragment < numSentFragments)
|
||||||
|
it->second->fragments[fragment].reset (nullptr);
|
||||||
|
}
|
||||||
|
fragment++;
|
||||||
|
mask <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf++;
|
||||||
|
}
|
||||||
|
while (isNonLast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::ProcessFragments (uint8_t * buf)
|
||||||
|
{
|
||||||
|
uint8_t numFragments = *buf; // number of fragments
|
||||||
|
buf++;
|
||||||
|
for (int i = 0; i < numFragments; i++)
|
||||||
|
{
|
||||||
|
uint32_t msgID = bufbe32toh (buf); // message ID
|
||||||
|
buf += 4;
|
||||||
|
uint8_t frag[4];
|
||||||
|
frag[0] = 0;
|
||||||
|
memcpy (frag + 1, buf, 3);
|
||||||
|
buf += 3;
|
||||||
|
uint32_t fragmentInfo = bufbe32toh (frag); // fragment info
|
||||||
|
uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13
|
||||||
|
bool isLast = fragmentInfo & 0x010000; // bit 16
|
||||||
|
uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17
|
||||||
|
if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find message with msgID
|
||||||
|
auto it = m_IncompleteMessages.find (msgID);
|
||||||
|
if (it == m_IncompleteMessages.end ())
|
||||||
|
{
|
||||||
|
// create new message
|
||||||
|
auto msg = NewI2NPShortMessage ();
|
||||||
|
msg->len -= I2NP_SHORT_HEADER_SIZE;
|
||||||
|
it = m_IncompleteMessages.insert (std::make_pair (msgID,
|
||||||
|
std::unique_ptr<IncompleteMessage>(new IncompleteMessage (msg)))).first;
|
||||||
|
}
|
||||||
|
std::unique_ptr<IncompleteMessage>& incompleteMessage = it->second;
|
||||||
|
|
||||||
|
// handle current fragment
|
||||||
|
if (fragmentNum == incompleteMessage->nextFragmentNum)
|
||||||
|
{
|
||||||
|
// expected fragment
|
||||||
|
incompleteMessage->AttachNextFragment (buf, fragmentSize);
|
||||||
|
if (!isLast && !incompleteMessage->savedFragments.empty ())
|
||||||
|
{
|
||||||
|
// try saved fragments
|
||||||
|
for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();)
|
||||||
|
{
|
||||||
|
auto& savedFragment = *it1;
|
||||||
|
if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum)
|
||||||
|
{
|
||||||
|
incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len);
|
||||||
|
isLast = savedFragment->isLast;
|
||||||
|
incompleteMessage->savedFragments.erase (it1++);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (isLast)
|
||||||
|
LogPrint (eLogDebug, "SSU: Message ", msgID, " complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fragmentNum < incompleteMessage->nextFragmentNum)
|
||||||
|
// duplicate fragment
|
||||||
|
LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// missing fragment
|
||||||
|
LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID);
|
||||||
|
auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast);
|
||||||
|
if (incompleteMessage->savedFragments.insert (std::unique_ptr<Fragment>(savedFragment)).second)
|
||||||
|
incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved");
|
||||||
|
}
|
||||||
|
isLast = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLast)
|
||||||
|
{
|
||||||
|
// delete incomplete message
|
||||||
|
auto msg = incompleteMessage->msg;
|
||||||
|
incompleteMessage->msg = nullptr;
|
||||||
|
m_IncompleteMessages.erase (msgID);
|
||||||
|
// process message
|
||||||
|
SendMsgAck (msgID);
|
||||||
|
msg->FromSSU (msgID);
|
||||||
|
if (m_Session.GetState () == eSessionStateEstablished)
|
||||||
|
{
|
||||||
|
if (!m_ReceivedMessages.count (msgID))
|
||||||
|
{
|
||||||
|
if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES)
|
||||||
|
m_ReceivedMessages.clear ();
|
||||||
|
else
|
||||||
|
ScheduleDecay ();
|
||||||
|
m_ReceivedMessages.insert (msgID);
|
||||||
|
if (!msg->IsExpired ())
|
||||||
|
m_Handler.PutNextMessage (msg);
|
||||||
|
else
|
||||||
|
LogPrint (eLogInfo, "SSU: message expired");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "SSU: Message ", msgID, " already received");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we expect DeliveryStatus
|
||||||
|
if (msg->GetTypeID () == eI2NPDeliveryStatus)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "SSU: session established");
|
||||||
|
m_Session.Established ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendFragmentAck (msgID, fragmentNum);
|
||||||
|
buf += fragmentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::FlushReceivedMessage ()
|
||||||
|
{
|
||||||
|
m_Handler.Flush ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::ProcessMessage (uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
//uint8_t * start = buf;
|
||||||
|
uint8_t flag = *buf;
|
||||||
|
buf++;
|
||||||
|
LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len);
|
||||||
|
// process acks if presented
|
||||||
|
if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED))
|
||||||
|
ProcessAcks (buf, flag);
|
||||||
|
// extended data if presented
|
||||||
|
if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED)
|
||||||
|
{
|
||||||
|
uint8_t extendedDataSize = *buf;
|
||||||
|
buf++; // size
|
||||||
|
LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present");
|
||||||
|
buf += extendedDataSize;
|
||||||
|
}
|
||||||
|
// process data
|
||||||
|
ProcessFragments (buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::Send (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
uint32_t msgID = msg->ToSSU ();
|
||||||
|
if (m_SentMessages.count (msgID) > 0)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: message ", msgID, " already sent");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_SentMessages.empty ()) // schedule resend at first message only
|
||||||
|
ScheduleResend ();
|
||||||
|
|
||||||
|
auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr<SentMessage>(new SentMessage)));
|
||||||
|
std::unique_ptr<SentMessage>& sentMessage = ret.first->second;
|
||||||
|
if (ret.second)
|
||||||
|
{
|
||||||
|
sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL;
|
||||||
|
sentMessage->numResends = 0;
|
||||||
|
}
|
||||||
|
auto& fragments = sentMessage->fragments;
|
||||||
|
size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3)
|
||||||
|
size_t len = msg->GetLength ();
|
||||||
|
uint8_t * msgBuf = msg->GetSSUHeader ();
|
||||||
|
|
||||||
|
uint32_t fragmentNum = 0;
|
||||||
|
while (len > 0)
|
||||||
|
{
|
||||||
|
Fragment * fragment = new Fragment;
|
||||||
|
fragment->fragmentNum = fragmentNum;
|
||||||
|
uint8_t * buf = fragment->buf;
|
||||||
|
uint8_t * payload = buf + sizeof (SSUHeader);
|
||||||
|
*payload = DATA_FLAG_WANT_REPLY; // for compatibility
|
||||||
|
payload++;
|
||||||
|
*payload = 1; // always 1 message fragment per message
|
||||||
|
payload++;
|
||||||
|
htobe32buf (payload, msgID);
|
||||||
|
payload += 4;
|
||||||
|
bool isLast = (len <= payloadSize);
|
||||||
|
size_t size = isLast ? len : payloadSize;
|
||||||
|
uint32_t fragmentInfo = (fragmentNum << 17);
|
||||||
|
if (isLast)
|
||||||
|
fragmentInfo |= 0x010000;
|
||||||
|
|
||||||
|
fragmentInfo |= size;
|
||||||
|
fragmentInfo = htobe32 (fragmentInfo);
|
||||||
|
memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3);
|
||||||
|
payload += 3;
|
||||||
|
memcpy (payload, msgBuf, size);
|
||||||
|
|
||||||
|
size += payload - buf;
|
||||||
|
if (size & 0x0F) // make sure 16 bytes boundary
|
||||||
|
size = ((size >> 4) + 1) << 4; // (/16 + 1)*16
|
||||||
|
fragment->len = size;
|
||||||
|
fragments.push_back (std::unique_ptr<Fragment> (fragment));
|
||||||
|
|
||||||
|
// encrypt message with session key
|
||||||
|
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Session.Send (buf, size);
|
||||||
|
}
|
||||||
|
catch (boost::system::system_error& ec)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ());
|
||||||
|
}
|
||||||
|
if (!isLast)
|
||||||
|
{
|
||||||
|
len -= payloadSize;
|
||||||
|
msgBuf += payloadSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
len = 0;
|
||||||
|
fragmentNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::SendMsgAck (uint32_t msgID)
|
||||||
|
{
|
||||||
|
uint8_t buf[48 + 18]; // actual length is 44 = 37 + 7 but pad it to multiple of 16
|
||||||
|
uint8_t * payload = buf + sizeof (SSUHeader);
|
||||||
|
*payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag
|
||||||
|
payload++;
|
||||||
|
*payload = 1; // number of ACKs
|
||||||
|
payload++;
|
||||||
|
htobe32buf (payload, msgID); // msgID
|
||||||
|
payload += 4;
|
||||||
|
*payload = 0; // number of fragments
|
||||||
|
|
||||||
|
// encrypt message with session key
|
||||||
|
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48);
|
||||||
|
m_Session.Send (buf, 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum)
|
||||||
|
{
|
||||||
|
if (fragmentNum > 64)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t buf[64 + 18];
|
||||||
|
uint8_t * payload = buf + sizeof (SSUHeader);
|
||||||
|
*payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag
|
||||||
|
payload++;
|
||||||
|
*payload = 1; // number of ACK bitfields
|
||||||
|
payload++;
|
||||||
|
// one ack
|
||||||
|
*(uint32_t *)(payload) = htobe32 (msgID); // msgID
|
||||||
|
payload += 4;
|
||||||
|
div_t d = div (fragmentNum, 7);
|
||||||
|
memset (payload, 0x80, d.quot); // 0x80 means non-last
|
||||||
|
payload += d.quot;
|
||||||
|
*payload = 0x01 << d.rem; // set corresponding bit
|
||||||
|
payload++;
|
||||||
|
*payload = 0; // number of fragments
|
||||||
|
|
||||||
|
size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1)
|
||||||
|
// encrypt message with session key
|
||||||
|
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len);
|
||||||
|
m_Session.Send (buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::ScheduleResend()
|
||||||
|
{
|
||||||
|
m_ResendTimer.cancel ();
|
||||||
|
m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL));
|
||||||
|
auto s = m_Session.shared_from_this();
|
||||||
|
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
|
||||||
|
{ s->m_Data.HandleResendTimer (ecode); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::HandleResendTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();)
|
||||||
|
{
|
||||||
|
if (ts >= it->second->nextResendTime)
|
||||||
|
{
|
||||||
|
if (it->second->numResends < MAX_NUM_RESENDS)
|
||||||
|
{
|
||||||
|
for (auto& f: it->second->fragments)
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Session.Send (f->buf, f->len); // resend
|
||||||
|
}
|
||||||
|
catch (boost::system::system_error& ec)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it->second->numResends++;
|
||||||
|
it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL;
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted");
|
||||||
|
it = m_SentMessages.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
ScheduleResend ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::ScheduleDecay ()
|
||||||
|
{
|
||||||
|
m_DecayTimer.cancel ();
|
||||||
|
m_DecayTimer.expires_from_now (boost::posix_time::seconds(DECAY_INTERVAL));
|
||||||
|
auto s = m_Session.shared_from_this();
|
||||||
|
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
|
||||||
|
{ s->m_Data.HandleDecayTimer (ecode); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::HandleDecayTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
m_ReceivedMessages.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::ScheduleIncompleteMessagesCleanup ()
|
||||||
|
{
|
||||||
|
m_IncompleteMessagesCleanupTimer.cancel ();
|
||||||
|
m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT));
|
||||||
|
auto s = m_Session.shared_from_this();
|
||||||
|
m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode)
|
||||||
|
{ s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();)
|
||||||
|
{
|
||||||
|
if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted");
|
||||||
|
it = m_IncompleteMessages.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
ScheduleIncompleteMessagesCleanup ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
128
SSUData.h
Normal file
128
SSUData.h
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#ifndef SSU_DATA_H__
|
||||||
|
#define SSU_DATA_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
|
||||||
|
const size_t SSU_MTU_V4 = 1484;
|
||||||
|
const size_t SSU_MTU_V6 = 1472;
|
||||||
|
const size_t IPV4_HEADER_SIZE = 20;
|
||||||
|
const size_t IPV6_HEADER_SIZE = 40;
|
||||||
|
const size_t UDP_HEADER_SIZE = 8;
|
||||||
|
const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456
|
||||||
|
const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1424
|
||||||
|
const int RESEND_INTERVAL = 3; // in seconds
|
||||||
|
const int MAX_NUM_RESENDS = 5;
|
||||||
|
const int DECAY_INTERVAL = 20; // in seconds
|
||||||
|
const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
|
||||||
|
const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check
|
||||||
|
// data flags
|
||||||
|
const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02;
|
||||||
|
const uint8_t DATA_FLAG_WANT_REPLY = 0x04;
|
||||||
|
const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08;
|
||||||
|
const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10;
|
||||||
|
const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40;
|
||||||
|
const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80;
|
||||||
|
|
||||||
|
struct Fragment
|
||||||
|
{
|
||||||
|
int fragmentNum;
|
||||||
|
size_t len;
|
||||||
|
bool isLast;
|
||||||
|
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest
|
||||||
|
|
||||||
|
Fragment () = default;
|
||||||
|
Fragment (int n, const uint8_t * b, int l, bool last):
|
||||||
|
fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FragmentCmp
|
||||||
|
{
|
||||||
|
bool operator() (const std::unique_ptr<Fragment>& f1, const std::unique_ptr<Fragment>& f2) const
|
||||||
|
{
|
||||||
|
return f1->fragmentNum < f2->fragmentNum;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IncompleteMessage
|
||||||
|
{
|
||||||
|
std::shared_ptr<I2NPMessage> msg;
|
||||||
|
int nextFragmentNum;
|
||||||
|
uint32_t lastFragmentInsertTime; // in seconds
|
||||||
|
std::set<std::unique_ptr<Fragment>, FragmentCmp> savedFragments;
|
||||||
|
|
||||||
|
IncompleteMessage (std::shared_ptr<I2NPMessage> m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {};
|
||||||
|
void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SentMessage
|
||||||
|
{
|
||||||
|
std::vector<std::unique_ptr<Fragment> > fragments;
|
||||||
|
uint32_t nextResendTime; // in seconds
|
||||||
|
int numResends;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SSUSession;
|
||||||
|
class SSUData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SSUData (SSUSession& session);
|
||||||
|
~SSUData ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
void ProcessMessage (uint8_t * buf, size_t len);
|
||||||
|
void FlushReceivedMessage ();
|
||||||
|
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
|
||||||
|
void AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter);
|
||||||
|
void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void SendMsgAck (uint32_t msgID);
|
||||||
|
void SendFragmentAck (uint32_t msgID, int fragmentNum);
|
||||||
|
void ProcessAcks (uint8_t *& buf, uint8_t flag);
|
||||||
|
void ProcessFragments (uint8_t * buf);
|
||||||
|
void ProcessSentMessageAck (uint32_t msgID);
|
||||||
|
|
||||||
|
void ScheduleResend ();
|
||||||
|
void HandleResendTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
void ScheduleDecay ();
|
||||||
|
void HandleDecayTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
void ScheduleIncompleteMessagesCleanup ();
|
||||||
|
void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SSUSession& m_Session;
|
||||||
|
std::map<uint32_t, std::unique_ptr<IncompleteMessage> > m_IncompleteMessages;
|
||||||
|
std::map<uint32_t, std::unique_ptr<SentMessage> > m_SentMessages;
|
||||||
|
std::set<uint32_t> m_ReceivedMessages;
|
||||||
|
boost::asio::deadline_timer m_ResendTimer, m_DecayTimer, m_IncompleteMessagesCleanupTimer;
|
||||||
|
int m_MaxPacketSize, m_PacketSize;
|
||||||
|
i2p::I2NPMessagesHandler m_Handler;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
1157
SSUSession.cpp
Normal file
1157
SSUSession.cpp
Normal file
File diff suppressed because it is too large
Load Diff
164
SSUSession.h
Normal file
164
SSUSession.h
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#ifndef SSU_SESSION_H__
|
||||||
|
#define SSU_SESSION_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <set>
|
||||||
|
#include <memory>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "TransportSession.h"
|
||||||
|
#include "SSUData.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04;
|
||||||
|
struct SSUHeader
|
||||||
|
{
|
||||||
|
uint8_t mac[16];
|
||||||
|
uint8_t iv[16];
|
||||||
|
uint8_t flag;
|
||||||
|
uint8_t time[4];
|
||||||
|
|
||||||
|
uint8_t GetPayloadType () const { return flag >> 4; };
|
||||||
|
bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; };
|
||||||
|
};
|
||||||
|
|
||||||
|
const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds
|
||||||
|
const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes
|
||||||
|
|
||||||
|
// payload types (4 bits)
|
||||||
|
const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0;
|
||||||
|
const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1;
|
||||||
|
const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2;
|
||||||
|
const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3;
|
||||||
|
const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4;
|
||||||
|
const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5;
|
||||||
|
const uint8_t PAYLOAD_TYPE_DATA = 6;
|
||||||
|
const uint8_t PAYLOAD_TYPE_PEER_TEST = 7;
|
||||||
|
const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8;
|
||||||
|
|
||||||
|
// extended options
|
||||||
|
const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001;
|
||||||
|
|
||||||
|
enum SessionState
|
||||||
|
{
|
||||||
|
eSessionStateUnknown,
|
||||||
|
eSessionStateIntroduced,
|
||||||
|
eSessionStateEstablished,
|
||||||
|
eSessionStateClosed,
|
||||||
|
eSessionStateFailed
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PeerTestParticipant
|
||||||
|
{
|
||||||
|
ePeerTestParticipantUnknown = 0,
|
||||||
|
ePeerTestParticipantAlice1,
|
||||||
|
ePeerTestParticipantAlice2,
|
||||||
|
ePeerTestParticipantBob,
|
||||||
|
ePeerTestParticipantCharlie
|
||||||
|
};
|
||||||
|
|
||||||
|
class SSUServer;
|
||||||
|
class SSUSession: public TransportSession, public std::enable_shared_from_this<SSUSession>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint,
|
||||||
|
std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, bool peerTest = false);
|
||||||
|
void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
|
||||||
|
~SSUSession ();
|
||||||
|
|
||||||
|
void Connect ();
|
||||||
|
void WaitForConnect ();
|
||||||
|
void Introduce (const i2p::data::RouterInfo::Introducer& introducer,
|
||||||
|
std::shared_ptr<const i2p::data::RouterInfo> to); // Alice to Charlie
|
||||||
|
void WaitForIntroduction ();
|
||||||
|
void Close ();
|
||||||
|
void Done ();
|
||||||
|
boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
|
||||||
|
bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); };
|
||||||
|
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||||
|
void SendPeerTest (); // Alice
|
||||||
|
|
||||||
|
SessionState GetState () const { return m_State; };
|
||||||
|
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||||
|
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||||
|
|
||||||
|
void SendKeepAlive ();
|
||||||
|
uint32_t GetRelayTag () const { return m_RelayTag; };
|
||||||
|
const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; };
|
||||||
|
uint32_t GetCreationTime () const { return m_CreationTime; };
|
||||||
|
|
||||||
|
void FlushData ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
boost::asio::io_service& GetService ();
|
||||||
|
void CreateAESandMacKey (const uint8_t * pubKey);
|
||||||
|
size_t GetSSUHeaderSize (const uint8_t * buf) const;
|
||||||
|
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
|
||||||
|
void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session
|
||||||
|
void ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
|
||||||
|
void SendSessionRequest ();
|
||||||
|
void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce);
|
||||||
|
void ProcessSessionCreated (uint8_t * buf, size_t len);
|
||||||
|
void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true);
|
||||||
|
void ProcessSessionConfirmed (const uint8_t * buf, size_t len);
|
||||||
|
void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen);
|
||||||
|
void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);
|
||||||
|
void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from,
|
||||||
|
const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to);
|
||||||
|
void SendRelayIntro (std::shared_ptr<SSUSession> session, const boost::asio::ip::udp::endpoint& from);
|
||||||
|
void ProcessRelayResponse (const uint8_t * buf, size_t len);
|
||||||
|
void ProcessRelayIntro (const uint8_t * buf, size_t len);
|
||||||
|
void Established ();
|
||||||
|
void Failed ();
|
||||||
|
void ScheduleConnectTimer ();
|
||||||
|
void HandleConnectTimer (const boost::system::error_code& ecode);
|
||||||
|
void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
|
||||||
|
void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true);
|
||||||
|
void ProcessData (uint8_t * buf, size_t len);
|
||||||
|
void SendSesionDestroyed ();
|
||||||
|
void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key
|
||||||
|
void Send (const uint8_t * buf, size_t size);
|
||||||
|
|
||||||
|
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey,
|
||||||
|
const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0);
|
||||||
|
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key
|
||||||
|
void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey);
|
||||||
|
void DecryptSessionKey (uint8_t * buf, size_t len);
|
||||||
|
bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey);
|
||||||
|
|
||||||
|
void ScheduleTermination ();
|
||||||
|
void HandleTerminationTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class SSUData; // TODO: change in later
|
||||||
|
SSUServer& m_Server;
|
||||||
|
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
|
||||||
|
boost::asio::deadline_timer m_Timer;
|
||||||
|
bool m_IsPeerTest;
|
||||||
|
SessionState m_State;
|
||||||
|
bool m_IsSessionKey;
|
||||||
|
uint32_t m_RelayTag;
|
||||||
|
i2p::crypto::CBCEncryption m_SessionKeyEncryption;
|
||||||
|
i2p::crypto::CBCDecryption m_SessionKeyDecryption;
|
||||||
|
i2p::crypto::AESKey m_SessionKey;
|
||||||
|
i2p::crypto::MACKey m_MacKey;
|
||||||
|
i2p::data::RouterInfo::IntroKey m_IntroKey;
|
||||||
|
uint32_t m_CreationTime; // seconds since epoch
|
||||||
|
SSUData m_Data;
|
||||||
|
bool m_IsDataReceived;
|
||||||
|
std::unique_ptr<SignedData> m_SignedData; // we need it for SessionConfirmed only
|
||||||
|
std::map<uint32_t, std::shared_ptr<const i2p::data::RouterInfo> > m_RelayRequests; // nonce->Charlie
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
491
Signature.cpp
Normal file
491
Signature.cpp
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
#include <memory>
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Signature.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace crypto
|
||||||
|
{
|
||||||
|
class Ed25519
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
Ed25519 ()
|
||||||
|
{
|
||||||
|
BN_CTX * ctx = BN_CTX_new ();
|
||||||
|
BIGNUM * tmp = BN_new ();
|
||||||
|
|
||||||
|
q = BN_new ();
|
||||||
|
// 2^255-19
|
||||||
|
BN_set_bit (q, 255); // 2^255
|
||||||
|
BN_sub_word (q, 19);
|
||||||
|
|
||||||
|
l = BN_new ();
|
||||||
|
// 2^252 + 27742317777372353535851937790883648493
|
||||||
|
BN_set_bit (l, 252);
|
||||||
|
two_252_2 = BN_dup (l);
|
||||||
|
BN_dec2bn (&tmp, "27742317777372353535851937790883648493");
|
||||||
|
BN_add (l, l, tmp);
|
||||||
|
BN_sub_word (two_252_2, 2); // 2^252 - 2
|
||||||
|
|
||||||
|
// -121665*inv(121666)
|
||||||
|
d = BN_new ();
|
||||||
|
BN_set_word (tmp, 121666);
|
||||||
|
BN_mod_inverse (tmp, tmp, q, ctx);
|
||||||
|
BN_set_word (d, 121665);
|
||||||
|
BN_set_negative (d, 1);
|
||||||
|
BN_mul (d, d, tmp, ctx);
|
||||||
|
|
||||||
|
// 2^((q-1)/4)
|
||||||
|
I = BN_new ();
|
||||||
|
BN_free (tmp);
|
||||||
|
tmp = BN_dup (q);
|
||||||
|
BN_sub_word (tmp, 1);
|
||||||
|
BN_div_word (tmp, 4);
|
||||||
|
BN_set_word (I, 2);
|
||||||
|
BN_mod_exp (I, I, tmp, q, ctx);
|
||||||
|
BN_free (tmp);
|
||||||
|
|
||||||
|
// 4*inv(5)
|
||||||
|
BIGNUM * By = BN_new ();
|
||||||
|
BN_set_word (By, 5);
|
||||||
|
BN_mod_inverse (By, By, q, ctx);
|
||||||
|
BN_mul_word (By, 4);
|
||||||
|
BIGNUM * Bx = RecoverX (By, ctx);
|
||||||
|
BN_mod (Bx, Bx, q, ctx); // % q
|
||||||
|
BN_mod (By, By, q, ctx); // % q
|
||||||
|
|
||||||
|
// precalculate Bi256 table
|
||||||
|
Bi256Carry = { Bx, By }; // B
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
Bi256[i][0] = Bi256Carry; // first point
|
||||||
|
for (int j = 1; j < 128; j++)
|
||||||
|
Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B
|
||||||
|
Bi256Carry = Bi256[i][127];
|
||||||
|
for (int j = 0; j < 128; j++) // add first point 128 more times
|
||||||
|
Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)),
|
||||||
|
d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)),
|
||||||
|
Bi256Carry (other.Bi256Carry)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
for (int j = 0; j < 128; j++)
|
||||||
|
Bi256[i][j] = other.Bi256[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
~Ed25519 ()
|
||||||
|
{
|
||||||
|
BN_free (q);
|
||||||
|
BN_free (l);
|
||||||
|
BN_free (d);
|
||||||
|
BN_free (I);
|
||||||
|
BN_free (two_252_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
return DecodePoint (buf, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
EncodePoint (Normalize (publicKey, ctx), buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const
|
||||||
|
{
|
||||||
|
BN_CTX * ctx = BN_CTX_new ();
|
||||||
|
BIGNUM * h = DecodeBN<64> (digest);
|
||||||
|
// signature 0..31 - R, 32..63 - S
|
||||||
|
// B*S = R + PK*h => R = B*S - PK*h
|
||||||
|
// we don't decode R, but encode (B*S - PK*h)
|
||||||
|
auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S;
|
||||||
|
BN_mod (h, h, l, ctx); // public key is multiple of B, but B%l = 0
|
||||||
|
auto PKh = Mul (publicKey, h, ctx); // PK*h
|
||||||
|
uint8_t diff[32];
|
||||||
|
EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded
|
||||||
|
bool passed = !memcmp (signature, diff, 32); // R
|
||||||
|
BN_free (h);
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
if (!passed)
|
||||||
|
LogPrint (eLogError, "25519 signature verification failed");
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len,
|
||||||
|
uint8_t * signature) const
|
||||||
|
{
|
||||||
|
BN_CTX * bnCtx = BN_CTX_new ();
|
||||||
|
// calculate r
|
||||||
|
SHA512_CTX ctx;
|
||||||
|
SHA512_Init (&ctx);
|
||||||
|
SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key
|
||||||
|
SHA512_Update (&ctx, buf, len); // data
|
||||||
|
uint8_t digest[64];
|
||||||
|
SHA512_Final (digest, &ctx);
|
||||||
|
BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors
|
||||||
|
// calculate R
|
||||||
|
uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf
|
||||||
|
EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors
|
||||||
|
// calculate S
|
||||||
|
SHA512_Init (&ctx);
|
||||||
|
SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R
|
||||||
|
SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
|
||||||
|
SHA512_Update (&ctx, buf, len); // data
|
||||||
|
SHA512_Final (digest, &ctx);
|
||||||
|
BIGNUM * h = DecodeBN<64> (digest);
|
||||||
|
// S = (r + h*a) % l
|
||||||
|
BIGNUM * a = DecodeBN<EDDSA25519_PRIVATE_KEY_LENGTH> (expandedPrivateKey); // left half of expanded key
|
||||||
|
BN_mod_mul (h, h, a, l, bnCtx); // %l
|
||||||
|
BN_mod_add (h, h, r, l, bnCtx); // %l
|
||||||
|
memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2);
|
||||||
|
EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S
|
||||||
|
BN_free (r); BN_free (h); BN_free (a);
|
||||||
|
BN_CTX_free (bnCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
// x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2)
|
||||||
|
// y3 = (y1*y2+x1*x2)*(z1*z2+d*t1*t2)
|
||||||
|
// z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2)
|
||||||
|
// t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2)
|
||||||
|
BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new ();
|
||||||
|
|
||||||
|
BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2
|
||||||
|
BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2
|
||||||
|
|
||||||
|
BIGNUM * t1 = p1.t, * t2 = p2.t;
|
||||||
|
if (!t1) { t1 = BN_new (); BN_mul (t1, p1.x, p1.y, ctx); }
|
||||||
|
if (!t2) { t2 = BN_new (); BN_mul (t2, p2.x, p2.y, ctx); }
|
||||||
|
BN_mul (t3, t1, t2, ctx);
|
||||||
|
BN_mul (t3, t3, d, ctx); // C = d*t1*t2
|
||||||
|
if (!p1.t) BN_free (t1);
|
||||||
|
if (!p2.t) BN_free (t2);
|
||||||
|
|
||||||
|
if (p1.z)
|
||||||
|
{
|
||||||
|
if (p2.z)
|
||||||
|
BN_mul (z3, p1.z, p2.z, ctx); // D = z1*z2
|
||||||
|
else
|
||||||
|
BN_copy (z3, p1.z); // D = z1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (p2.z)
|
||||||
|
BN_copy (z3, p2.z); // D = z2
|
||||||
|
else
|
||||||
|
BN_one (z3); // D = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new ();
|
||||||
|
BN_add (E, p1.x, p1.y);
|
||||||
|
BN_add (F, p2.x, p2.y);
|
||||||
|
BN_mul (E, E, F, ctx); // (x1 + y1)*(x2 + y2)
|
||||||
|
BN_sub (E, E, x3);
|
||||||
|
BN_sub (E, E, y3); // E = (x1 + y1)*(x2 + y2) - A - B
|
||||||
|
BN_sub (F, z3, t3); // F = D - C
|
||||||
|
BN_add (G, z3, t3); // G = D + C
|
||||||
|
BN_add (H, y3, x3); // H = B + A
|
||||||
|
|
||||||
|
BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F
|
||||||
|
BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H
|
||||||
|
BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G
|
||||||
|
BN_mod_mul (t3, E, H, q, ctx); // t3 = E*H
|
||||||
|
|
||||||
|
BN_free (E); BN_free (F); BN_free (G); BN_free (H);
|
||||||
|
|
||||||
|
return EDDSAPoint {x3, y3, z3, t3};
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint Double (const EDDSAPoint& p, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
BIGNUM * x2 = BN_new (), * y2 = BN_new (), * z2 = BN_new (), * t2 = BN_new ();
|
||||||
|
|
||||||
|
BN_sqr (x2, p.x, ctx); // x2 = A = x^2
|
||||||
|
BN_sqr (y2, p.y, ctx); // y2 = B = y^2
|
||||||
|
if (p.t)
|
||||||
|
BN_sqr (t2, p.t, ctx); // t2 = t^2
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BN_mul (t2, p.x, p.y, ctx); // t = x*y
|
||||||
|
BN_sqr (t2, t2, ctx); // t2 = t^2
|
||||||
|
}
|
||||||
|
BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2
|
||||||
|
if (p.z)
|
||||||
|
BN_sqr (z2, p.z, ctx); // z2 = D = z^2
|
||||||
|
else
|
||||||
|
BN_one (z2); // z2 = 1
|
||||||
|
|
||||||
|
BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new ();
|
||||||
|
// E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy
|
||||||
|
BN_mul (E, p.x, p.y, ctx);
|
||||||
|
BN_lshift1 (E, E); // E =2*x*y
|
||||||
|
BN_sub (F, z2, t2); // F = D - C
|
||||||
|
BN_add (G, z2, t2); // G = D + C
|
||||||
|
BN_add (H, y2, x2); // H = B + A
|
||||||
|
|
||||||
|
BN_mod_mul (x2, E, F, q, ctx); // x2 = E*F
|
||||||
|
BN_mod_mul (y2, G, H, q, ctx); // y2 = G*H
|
||||||
|
BN_mod_mul (z2, F, G, q, ctx); // z2 = F*G
|
||||||
|
BN_mod_mul (t2, E, H, q, ctx); // t2 = E*H
|
||||||
|
|
||||||
|
BN_free (E); BN_free (F); BN_free (G); BN_free (H);
|
||||||
|
|
||||||
|
return EDDSAPoint {x2, y2, z2, t2};
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
BIGNUM * zero = BN_new (), * one = BN_new ();
|
||||||
|
BN_zero (zero); BN_one (one);
|
||||||
|
EDDSAPoint res {zero, one};
|
||||||
|
if (!BN_is_zero (e))
|
||||||
|
{
|
||||||
|
int bitCount = BN_num_bits (e);
|
||||||
|
for (int i = bitCount - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
res = Double (res, ctx);
|
||||||
|
if (BN_is_bit_set (e, i)) res = Sum (res, p, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e, e is 32 bytes Little Endian
|
||||||
|
{
|
||||||
|
BIGNUM * zero = BN_new (), * one = BN_new ();
|
||||||
|
BN_zero (zero); BN_one (one);
|
||||||
|
EDDSAPoint res {zero, one};
|
||||||
|
bool carry = false;
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
uint8_t x = e[i];
|
||||||
|
if (carry)
|
||||||
|
{
|
||||||
|
if (x < 255)
|
||||||
|
{
|
||||||
|
x++;
|
||||||
|
carry = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
if (x > 0)
|
||||||
|
{
|
||||||
|
if (x <= 128)
|
||||||
|
res = Sum (res, Bi256[i][x-1], ctx);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = Sum (res, -Bi256[i][255-x], ctx); // -Bi[256-x]
|
||||||
|
carry = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (carry) res = Sum (res, Bi256Carry, ctx);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
if (p.z)
|
||||||
|
{
|
||||||
|
BIGNUM * x = BN_new (), * y = BN_new ();
|
||||||
|
BN_mod_inverse (y, p.z, q, ctx);
|
||||||
|
BN_mod_mul (x, p.x, y, q, ctx); // x = x/z
|
||||||
|
BN_mod_mul (y, p.y, y, q, ctx); // y = y/z
|
||||||
|
return EDDSAPoint{x, y};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
BIGNUM * x2 = BN_new ();
|
||||||
|
BN_sqr (x2, p.x, ctx); // x^2
|
||||||
|
BIGNUM * y2 = BN_new ();
|
||||||
|
BN_sqr (y2, p.y, ctx); // y^2
|
||||||
|
// y^2 - x^2 - 1 - d*x^2*y^2
|
||||||
|
BIGNUM * tmp = BN_new ();
|
||||||
|
BN_mul (tmp, d, x2, ctx);
|
||||||
|
BN_mul (tmp, tmp, y2, ctx);
|
||||||
|
BN_sub (tmp, y2, tmp);
|
||||||
|
BN_sub (tmp, tmp, x2);
|
||||||
|
BN_sub_word (tmp, 1);
|
||||||
|
BN_mod (tmp, tmp, q, ctx); // % q
|
||||||
|
bool ret = BN_is_zero (tmp);
|
||||||
|
BN_free (x2);
|
||||||
|
BN_free (y2);
|
||||||
|
BN_free (tmp);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
BIGNUM * y2 = BN_new ();
|
||||||
|
BN_sqr (y2, y, ctx); // y^2
|
||||||
|
// xx = (y^2 -1)*inv(d*y^2 +1)
|
||||||
|
BIGNUM * xx = BN_new ();
|
||||||
|
BN_mul (xx, d, y2, ctx);
|
||||||
|
BN_add_word (xx, 1);
|
||||||
|
BN_mod_inverse (xx, xx, q, ctx);
|
||||||
|
BN_sub_word (y2, 1);
|
||||||
|
BN_mul (xx, y2, xx, ctx);
|
||||||
|
// x = srqt(xx) = xx^(2^252-2)
|
||||||
|
BIGNUM * x = BN_new ();
|
||||||
|
BN_mod_exp (x, xx, two_252_2, q, ctx);
|
||||||
|
// check (x^2 -xx) % q
|
||||||
|
BN_sqr (y2, x, ctx);
|
||||||
|
BN_mod_sub (y2, y2, xx, q, ctx);
|
||||||
|
if (!BN_is_zero (y2))
|
||||||
|
BN_mod_mul (x, x, I, q, ctx);
|
||||||
|
if (BN_is_odd (x))
|
||||||
|
BN_sub (x, q, x);
|
||||||
|
BN_free (y2);
|
||||||
|
BN_free (xx);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const
|
||||||
|
{
|
||||||
|
// buf is 32 bytes Little Endian, convert it to Big Endian
|
||||||
|
uint8_t buf1[EDDSA25519_PUBLIC_KEY_LENGTH];
|
||||||
|
for (size_t i = 0; i < EDDSA25519_PUBLIC_KEY_LENGTH/2; i++) // invert bytes
|
||||||
|
{
|
||||||
|
buf1[i] = buf[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i];
|
||||||
|
buf1[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i] = buf[i];
|
||||||
|
}
|
||||||
|
bool isHighestBitSet = buf1[0] & 0x80;
|
||||||
|
if (isHighestBitSet)
|
||||||
|
buf1[0] &= 0x7f; // clear highest bit
|
||||||
|
BIGNUM * y = BN_new ();
|
||||||
|
BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y);
|
||||||
|
auto x = RecoverX (y, ctx);
|
||||||
|
if (BN_is_bit_set (x, 0) != isHighestBitSet)
|
||||||
|
BN_sub (x, q, x); // x = q - x
|
||||||
|
BIGNUM * z = BN_new (), * t = BN_new ();
|
||||||
|
BN_one (z); BN_mod_mul (t, x, y, q, ctx); // pre-calculate t
|
||||||
|
EDDSAPoint p {x, y, z, t};
|
||||||
|
if (!IsOnCurve (p, ctx))
|
||||||
|
LogPrint (eLogError, "Decoded point is not on 25519");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const
|
||||||
|
{
|
||||||
|
EncodeBN (p.y, buf,EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||||
|
if (BN_is_bit_set (p.x, 0)) // highest bit
|
||||||
|
buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int len>
|
||||||
|
BIGNUM * DecodeBN (const uint8_t * buf) const
|
||||||
|
{
|
||||||
|
// buf is Little Endian convert it to Big Endian
|
||||||
|
uint8_t buf1[len];
|
||||||
|
for (size_t i = 0; i < len/2; i++) // invert bytes
|
||||||
|
{
|
||||||
|
buf1[i] = buf[len -1 - i];
|
||||||
|
buf1[len -1 - i] = buf[i];
|
||||||
|
}
|
||||||
|
BIGNUM * res = BN_new ();
|
||||||
|
BN_bin2bn (buf1, len, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const
|
||||||
|
{
|
||||||
|
bn2buf (bn, buf, len);
|
||||||
|
// To Little Endian
|
||||||
|
for (size_t i = 0; i < len/2; i++) // invert bytes
|
||||||
|
{
|
||||||
|
uint8_t tmp = buf[i];
|
||||||
|
buf[i] = buf[len -1 - i];
|
||||||
|
buf[len -1 - i] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
BIGNUM * q, * l, * d, * I;
|
||||||
|
// transient values
|
||||||
|
BIGNUM * two_252_2; // 2^252-2
|
||||||
|
EDDSAPoint Bi256[32][128]; // per byte, Bi256[i][j] = (256+j+1)^i*B, we don't store zeroes
|
||||||
|
// if j > 128 we use 256 - j and carry 1 to next byte
|
||||||
|
// Bi256[0][0] = B, base point
|
||||||
|
EDDSAPoint Bi256Carry; // Bi256[32][0]
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unique_ptr<Ed25519> g_Ed25519;
|
||||||
|
std::unique_ptr<Ed25519>& GetEd25519 ()
|
||||||
|
{
|
||||||
|
if (!g_Ed25519)
|
||||||
|
{
|
||||||
|
auto c = new Ed25519();
|
||||||
|
if (!g_Ed25519) // make sure it was not created already
|
||||||
|
g_Ed25519.reset (c);
|
||||||
|
else
|
||||||
|
delete c;
|
||||||
|
}
|
||||||
|
return g_Ed25519;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey)
|
||||||
|
{
|
||||||
|
memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||||
|
BN_CTX * ctx = BN_CTX_new ();
|
||||||
|
m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx);
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||||
|
{
|
||||||
|
uint8_t digest[64];
|
||||||
|
SHA512_CTX ctx;
|
||||||
|
SHA512_Init (&ctx);
|
||||||
|
SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R
|
||||||
|
SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
|
||||||
|
SHA512_Update (&ctx, buf, len); // data
|
||||||
|
SHA512_Final (digest, &ctx);
|
||||||
|
|
||||||
|
return GetEd25519 ()->Verify (m_PublicKey, digest, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey)
|
||||||
|
{
|
||||||
|
// expand key
|
||||||
|
SHA512 (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH, m_ExpandedPrivateKey);
|
||||||
|
m_ExpandedPrivateKey[0] &= 0xF8; // drop last 3 bits
|
||||||
|
m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x1F; // drop first 3 bits
|
||||||
|
m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit
|
||||||
|
// generate and encode public key
|
||||||
|
BN_CTX * ctx = BN_CTX_new ();
|
||||||
|
auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx);
|
||||||
|
GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx);
|
||||||
|
BN_CTX_free (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||||
|
{
|
||||||
|
GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
442
Signature.h
Normal file
442
Signature.h
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
#ifndef SIGNATURE_H__
|
||||||
|
#define SIGNATURE_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <openssl/dsa.h>
|
||||||
|
#include <openssl/ec.h>
|
||||||
|
#include <openssl/ecdsa.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include "Crypto.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace crypto
|
||||||
|
{
|
||||||
|
class Verifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~Verifier () {};
|
||||||
|
virtual bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const = 0;
|
||||||
|
virtual size_t GetPublicKeyLen () const = 0;
|
||||||
|
virtual size_t GetSignatureLen () const = 0;
|
||||||
|
virtual size_t GetPrivateKeyLen () const { return GetSignatureLen ()/2; };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Signer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~Signer () {};
|
||||||
|
virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t DSA_PUBLIC_KEY_LENGTH = 128;
|
||||||
|
const size_t DSA_SIGNATURE_LENGTH = 40;
|
||||||
|
const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2;
|
||||||
|
class DSAVerifier: public Verifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
DSAVerifier (const uint8_t * signingKey)
|
||||||
|
{
|
||||||
|
m_PublicKey = CreateDSA ();
|
||||||
|
m_PublicKey->pub_key = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
~DSAVerifier ()
|
||||||
|
{
|
||||||
|
DSA_free (m_PublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||||
|
{
|
||||||
|
// calculate SHA1 digest
|
||||||
|
uint8_t digest[20];
|
||||||
|
SHA1 (buf, len, digest);
|
||||||
|
// signature
|
||||||
|
DSA_SIG * sig = DSA_SIG_new();
|
||||||
|
sig->r = BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL);
|
||||||
|
sig->s = BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL);
|
||||||
|
// DSA verification
|
||||||
|
int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
|
||||||
|
DSA_SIG_free(sig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
|
||||||
|
size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
DSA * m_PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DSASigner: public Signer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
DSASigner (const uint8_t * signingPrivateKey)
|
||||||
|
{
|
||||||
|
m_PrivateKey = CreateDSA ();
|
||||||
|
m_PrivateKey->priv_key = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
~DSASigner ()
|
||||||
|
{
|
||||||
|
DSA_free (m_PrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||||
|
{
|
||||||
|
uint8_t digest[20];
|
||||||
|
SHA1 (buf, len, digest);
|
||||||
|
DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
|
||||||
|
bn2buf (sig->r, signature, DSA_SIGNATURE_LENGTH/2);
|
||||||
|
bn2buf (sig->s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
|
||||||
|
DSA_SIG_free(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
DSA * m_PrivateKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||||
|
{
|
||||||
|
DSA * dsa = CreateDSA ();
|
||||||
|
DSA_generate_key (dsa);
|
||||||
|
bn2buf (dsa->priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
|
||||||
|
bn2buf (dsa->pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
|
||||||
|
DSA_free (dsa);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SHA256Hash
|
||||||
|
{
|
||||||
|
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||||
|
{
|
||||||
|
SHA256 (buf, len, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { hashLen = 32 };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SHA384Hash
|
||||||
|
{
|
||||||
|
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||||
|
{
|
||||||
|
SHA384 (buf, len, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { hashLen = 48 };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SHA512Hash
|
||||||
|
{
|
||||||
|
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||||
|
{
|
||||||
|
SHA512 (buf, len, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { hashLen = 64 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Hash, int curve, size_t keyLen>
|
||||||
|
class ECDSAVerifier: public Verifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ECDSAVerifier (const uint8_t * signingKey)
|
||||||
|
{
|
||||||
|
m_PublicKey = EC_KEY_new_by_curve_name (curve);
|
||||||
|
EC_KEY_set_public_key_affine_coordinates (m_PublicKey,
|
||||||
|
BN_bin2bn (signingKey, keyLen/2, NULL),
|
||||||
|
BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
~ECDSAVerifier ()
|
||||||
|
{
|
||||||
|
EC_KEY_free (m_PublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||||
|
{
|
||||||
|
uint8_t digest[Hash::hashLen];
|
||||||
|
Hash::CalculateHash (buf, len, digest);
|
||||||
|
ECDSA_SIG * sig = ECDSA_SIG_new();
|
||||||
|
sig->r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL);
|
||||||
|
sig->s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL);
|
||||||
|
// ECDSA verification
|
||||||
|
int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey);
|
||||||
|
ECDSA_SIG_free(sig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetPublicKeyLen () const { return keyLen; };
|
||||||
|
size_t GetSignatureLen () const { return keyLen; }; // signature length = key length
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
EC_KEY * m_PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Hash, int curve, size_t keyLen>
|
||||||
|
class ECDSASigner: public Signer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ECDSASigner (const uint8_t * signingPrivateKey)
|
||||||
|
{
|
||||||
|
m_PrivateKey = EC_KEY_new_by_curve_name (curve);
|
||||||
|
EC_KEY_set_private_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen/2, NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
~ECDSASigner ()
|
||||||
|
{
|
||||||
|
EC_KEY_free (m_PrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||||
|
{
|
||||||
|
uint8_t digest[Hash::hashLen];
|
||||||
|
Hash::CalculateHash (buf, len, digest);
|
||||||
|
ECDSA_SIG * sig = ECDSA_do_sign (digest, Hash::hashLen, m_PrivateKey);
|
||||||
|
// signatureLen = keyLen
|
||||||
|
bn2buf (sig->r, signature, keyLen/2);
|
||||||
|
bn2buf (sig->s, signature + keyLen/2, keyLen/2);
|
||||||
|
ECDSA_SIG_free(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
EC_KEY * m_PrivateKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||||
|
{
|
||||||
|
EC_KEY * signingKey = EC_KEY_new_by_curve_name (curve);
|
||||||
|
EC_KEY_generate_key (signingKey);
|
||||||
|
bn2buf (EC_KEY_get0_private_key (signingKey), signingPrivateKey, keyLen/2);
|
||||||
|
BIGNUM * x = BN_new(), * y = BN_new();
|
||||||
|
EC_POINT_get_affine_coordinates_GFp (EC_KEY_get0_group(signingKey),
|
||||||
|
EC_KEY_get0_public_key (signingKey), x, y, NULL);
|
||||||
|
bn2buf (x, signingPublicKey, keyLen/2);
|
||||||
|
bn2buf (y, signingPublicKey + keyLen/2, keyLen/2);
|
||||||
|
BN_free (x); BN_free (y);
|
||||||
|
EC_KEY_free (signingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSA_SHA256_P256
|
||||||
|
const size_t ECDSAP256_KEY_LENGTH = 64;
|
||||||
|
typedef ECDSAVerifier<SHA256Hash, NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH> ECDSAP256Verifier;
|
||||||
|
typedef ECDSASigner<SHA256Hash, NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH> ECDSAP256Signer;
|
||||||
|
|
||||||
|
inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||||
|
{
|
||||||
|
CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSA_SHA384_P384
|
||||||
|
const size_t ECDSAP384_KEY_LENGTH = 96;
|
||||||
|
typedef ECDSAVerifier<SHA384Hash, NID_secp384r1, ECDSAP384_KEY_LENGTH> ECDSAP384Verifier;
|
||||||
|
typedef ECDSASigner<SHA384Hash, NID_secp384r1, ECDSAP384_KEY_LENGTH> ECDSAP384Signer;
|
||||||
|
|
||||||
|
inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||||
|
{
|
||||||
|
CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSA_SHA512_P521
|
||||||
|
const size_t ECDSAP521_KEY_LENGTH = 132;
|
||||||
|
typedef ECDSAVerifier<SHA512Hash, NID_secp521r1, ECDSAP521_KEY_LENGTH> ECDSAP521Verifier;
|
||||||
|
typedef ECDSASigner<SHA512Hash, NID_secp521r1, ECDSAP521_KEY_LENGTH> ECDSAP521Signer;
|
||||||
|
|
||||||
|
inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||||
|
{
|
||||||
|
CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSA
|
||||||
|
template<typename Hash, int type, size_t keyLen>
|
||||||
|
class RSAVerifier: public Verifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
RSAVerifier (const uint8_t * signingKey)
|
||||||
|
{
|
||||||
|
m_PublicKey = RSA_new ();
|
||||||
|
memset (m_PublicKey, 0, sizeof (RSA));
|
||||||
|
m_PublicKey->e = BN_dup (GetRSAE ());
|
||||||
|
m_PublicKey->n = BN_bin2bn (signingKey, keyLen, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
~RSAVerifier ()
|
||||||
|
{
|
||||||
|
RSA_free (m_PublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||||
|
{
|
||||||
|
uint8_t digest[Hash::hashLen];
|
||||||
|
Hash::CalculateHash (buf, len, digest);
|
||||||
|
return RSA_verify (type, digest, Hash::hashLen, signature, GetSignatureLen (), m_PublicKey);
|
||||||
|
}
|
||||||
|
size_t GetPublicKeyLen () const { return keyLen; }
|
||||||
|
size_t GetSignatureLen () const { return keyLen; }
|
||||||
|
size_t GetPrivateKeyLen () const { return GetSignatureLen ()*2; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
RSA * m_PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Hash, int type, size_t keyLen>
|
||||||
|
class RSASigner: public Signer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
RSASigner (const uint8_t * signingPrivateKey)
|
||||||
|
{
|
||||||
|
m_PrivateKey = RSA_new ();
|
||||||
|
memset (m_PrivateKey, 0, sizeof (RSA));
|
||||||
|
m_PrivateKey->e = BN_dup (GetRSAE ());
|
||||||
|
m_PrivateKey->n = BN_bin2bn (signingPrivateKey, keyLen, NULL);
|
||||||
|
m_PrivateKey->d = BN_bin2bn (signingPrivateKey + keyLen, keyLen, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
~RSASigner ()
|
||||||
|
{
|
||||||
|
RSA_free (m_PrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||||
|
{
|
||||||
|
uint8_t digest[Hash::hashLen];
|
||||||
|
Hash::CalculateHash (buf, len, digest);
|
||||||
|
unsigned int signatureLen = keyLen;
|
||||||
|
RSA_sign (type, digest, Hash::hashLen, signature, &signatureLen, m_PrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
RSA * m_PrivateKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void CreateRSARandomKeys (size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||||
|
{
|
||||||
|
RSA * rsa = RSA_new ();
|
||||||
|
BIGNUM * e = BN_dup (GetRSAE ()); // make it non-const
|
||||||
|
RSA_generate_key_ex (rsa, publicKeyLen*8, e, NULL);
|
||||||
|
bn2buf (rsa->n, signingPrivateKey, publicKeyLen);
|
||||||
|
bn2buf (rsa->d, signingPrivateKey + publicKeyLen, publicKeyLen);
|
||||||
|
bn2buf (rsa->n, signingPublicKey, publicKeyLen);
|
||||||
|
BN_free (e); // this e is not assigned to rsa->e
|
||||||
|
RSA_free (rsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSA_SHA256_2048
|
||||||
|
const size_t RSASHA2562048_KEY_LENGTH = 256;
|
||||||
|
typedef RSAVerifier<SHA256Hash, NID_sha256, RSASHA2562048_KEY_LENGTH> RSASHA2562048Verifier;
|
||||||
|
typedef RSASigner<SHA256Hash, NID_sha256, RSASHA2562048_KEY_LENGTH> RSASHA2562048Signer;
|
||||||
|
|
||||||
|
// RSA_SHA384_3072
|
||||||
|
const size_t RSASHA3843072_KEY_LENGTH = 384;
|
||||||
|
typedef RSAVerifier<SHA384Hash, NID_sha384, RSASHA3843072_KEY_LENGTH> RSASHA3843072Verifier;
|
||||||
|
typedef RSASigner<SHA384Hash, NID_sha384, RSASHA3843072_KEY_LENGTH> RSASHA3843072Signer;
|
||||||
|
|
||||||
|
// RSA_SHA512_4096
|
||||||
|
const size_t RSASHA5124096_KEY_LENGTH = 512;
|
||||||
|
typedef RSAVerifier<SHA512Hash, NID_sha512, RSASHA5124096_KEY_LENGTH> RSASHA5124096Verifier;
|
||||||
|
typedef RSASigner<SHA512Hash, NID_sha512, RSASHA5124096_KEY_LENGTH> RSASHA5124096Signer;
|
||||||
|
|
||||||
|
// EdDSA
|
||||||
|
struct EDDSAPoint
|
||||||
|
{
|
||||||
|
BIGNUM * x, * y;
|
||||||
|
BIGNUM * z, * t; // projective coordinates
|
||||||
|
EDDSAPoint (): x(nullptr), y(nullptr), z(nullptr), t(nullptr) {};
|
||||||
|
EDDSAPoint (const EDDSAPoint& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr)
|
||||||
|
{ *this = other; };
|
||||||
|
EDDSAPoint (EDDSAPoint&& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr)
|
||||||
|
{ *this = std::move (other); };
|
||||||
|
EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr): x(x1), y(y1), z(z1), t(t1) {};
|
||||||
|
~EDDSAPoint () { BN_free (x); BN_free (y); BN_free(z); BN_free(t); };
|
||||||
|
|
||||||
|
EDDSAPoint& operator=(EDDSAPoint&& other)
|
||||||
|
{
|
||||||
|
if (x) BN_free (x); x = other.x; other.x = nullptr;
|
||||||
|
if (y) BN_free (y); y = other.y; other.y = nullptr;
|
||||||
|
if (z) BN_free (z); z = other.z; other.z = nullptr;
|
||||||
|
if (t) BN_free (t); t = other.t; other.t = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint& operator=(const EDDSAPoint& other)
|
||||||
|
{
|
||||||
|
if (x) BN_free (x); x = other.x ? BN_dup (other.x) : nullptr;
|
||||||
|
if (y) BN_free (y); y = other.y ? BN_dup (other.y) : nullptr;
|
||||||
|
if (z) BN_free (z); z = other.z ? BN_dup (other.z) : nullptr;
|
||||||
|
if (t) BN_free (t); t = other.t ? BN_dup (other.t) : nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDDSAPoint operator-() const
|
||||||
|
{
|
||||||
|
BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL;
|
||||||
|
if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); };
|
||||||
|
if (y) y1 = BN_dup (y);
|
||||||
|
if (z) z1 = BN_dup (z);
|
||||||
|
if (t) { t1 = BN_dup (t); BN_set_negative (t1, !BN_is_negative (t)); };
|
||||||
|
return EDDSAPoint {x1, y1, z1, t1};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32;
|
||||||
|
const size_t EDDSA25519_SIGNATURE_LENGTH = 64;
|
||||||
|
const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32;
|
||||||
|
class EDDSA25519Verifier: public Verifier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
EDDSA25519Verifier (const uint8_t * signingKey);
|
||||||
|
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
|
||||||
|
|
||||||
|
size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; };
|
||||||
|
size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
EDDSAPoint m_PublicKey;
|
||||||
|
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
class EDDSA25519Signer: public Signer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
EDDSA25519Signer (const uint8_t * signingPrivateKey);
|
||||||
|
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
|
||||||
|
const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t m_ExpandedPrivateKey[64];
|
||||||
|
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||||
|
{
|
||||||
|
RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH);
|
||||||
|
EDDSA25519Signer signer (signingPrivateKey);
|
||||||
|
memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
999
Streaming.cpp
Normal file
999
Streaming.cpp
Normal file
@@ -0,0 +1,999 @@
|
|||||||
|
#include "Crypto.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "Streaming.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace stream
|
||||||
|
{
|
||||||
|
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local,
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> remote, int port): m_Service (service),
|
||||||
|
m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
|
||||||
|
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
|
||||||
|
m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service),
|
||||||
|
m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port),
|
||||||
|
m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO),
|
||||||
|
m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0)
|
||||||
|
{
|
||||||
|
RAND_bytes ((uint8_t *)&m_RecvStreamID, 4);
|
||||||
|
m_RemoteIdentity = remote->GetIdentity ();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local):
|
||||||
|
m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
|
||||||
|
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
|
||||||
|
m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service),
|
||||||
|
m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE),
|
||||||
|
m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0)
|
||||||
|
{
|
||||||
|
RAND_bytes ((uint8_t *)&m_RecvStreamID, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream::~Stream ()
|
||||||
|
{
|
||||||
|
Terminate ();
|
||||||
|
while (!m_ReceiveQueue.empty ())
|
||||||
|
{
|
||||||
|
auto packet = m_ReceiveQueue.front ();
|
||||||
|
m_ReceiveQueue.pop ();
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it: m_SentPackets)
|
||||||
|
delete it;
|
||||||
|
m_SentPackets.clear ();
|
||||||
|
|
||||||
|
for (auto it: m_SavedPackets)
|
||||||
|
delete it;
|
||||||
|
m_SavedPackets.clear ();
|
||||||
|
|
||||||
|
LogPrint (eLogDebug, "Streaming: Stream deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::Terminate ()
|
||||||
|
{
|
||||||
|
m_AckSendTimer.cancel ();
|
||||||
|
m_ReceiveTimer.cancel ();
|
||||||
|
m_ResendTimer.cancel ();
|
||||||
|
if (m_SendHandler)
|
||||||
|
{
|
||||||
|
auto handler = m_SendHandler;
|
||||||
|
m_SendHandler = nullptr;
|
||||||
|
handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::HandleNextPacket (Packet * packet)
|
||||||
|
{
|
||||||
|
m_NumReceivedBytes += packet->GetLength ();
|
||||||
|
if (!m_SendStreamID)
|
||||||
|
m_SendStreamID = packet->GetReceiveStreamID ();
|
||||||
|
|
||||||
|
if (!packet->IsNoAck ()) // ack received
|
||||||
|
ProcessAck (packet);
|
||||||
|
|
||||||
|
int32_t receivedSeqn = packet->GetSeqn ();
|
||||||
|
bool isSyn = packet->IsSYN ();
|
||||||
|
if (!receivedSeqn && !isSyn)
|
||||||
|
{
|
||||||
|
// plain ack
|
||||||
|
LogPrint (eLogDebug, "Streaming: Plain ACK received");
|
||||||
|
delete packet;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn);
|
||||||
|
if (receivedSeqn == m_LastReceivedSequenceNumber + 1)
|
||||||
|
{
|
||||||
|
// we have received next in sequence message
|
||||||
|
ProcessPacket (packet);
|
||||||
|
|
||||||
|
// we should also try stored messages if any
|
||||||
|
for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();)
|
||||||
|
{
|
||||||
|
if ((*it)->GetSeqn () == (uint32_t)(m_LastReceivedSequenceNumber + 1))
|
||||||
|
{
|
||||||
|
Packet * savedPacket = *it;
|
||||||
|
m_SavedPackets.erase (it++);
|
||||||
|
|
||||||
|
ProcessPacket (savedPacket);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// schedule ack for last message
|
||||||
|
if (m_Status == eStreamStatusOpen)
|
||||||
|
{
|
||||||
|
if (!m_IsAckSendScheduled)
|
||||||
|
{
|
||||||
|
m_IsAckSendScheduled = true;
|
||||||
|
auto ackTimeout = m_RTT/10;
|
||||||
|
if (ackTimeout > ACK_SEND_TIMEOUT) ackTimeout = ACK_SEND_TIMEOUT;
|
||||||
|
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout));
|
||||||
|
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isSyn)
|
||||||
|
// we have to send SYN back to incoming connection
|
||||||
|
SendBuffer (); // also sets m_IsOpen
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (receivedSeqn <= m_LastReceivedSequenceNumber)
|
||||||
|
{
|
||||||
|
// we have received duplicate
|
||||||
|
LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " received");
|
||||||
|
SendQuickAck (); // resend ack for previous message again
|
||||||
|
delete packet; // packet dropped
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Streaming: Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1);
|
||||||
|
// save message and wait for missing message again
|
||||||
|
SavePacket (packet);
|
||||||
|
if (m_LastReceivedSequenceNumber >= 0)
|
||||||
|
{
|
||||||
|
// send NACKs for missing messages ASAP
|
||||||
|
if (m_IsAckSendScheduled)
|
||||||
|
{
|
||||||
|
m_IsAckSendScheduled = false;
|
||||||
|
m_AckSendTimer.cancel ();
|
||||||
|
}
|
||||||
|
SendQuickAck ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// wait for SYN
|
||||||
|
m_IsAckSendScheduled = true;
|
||||||
|
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ACK_SEND_TIMEOUT));
|
||||||
|
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::SavePacket (Packet * packet)
|
||||||
|
{
|
||||||
|
if (!m_SavedPackets.insert (packet).second)
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::ProcessPacket (Packet * packet)
|
||||||
|
{
|
||||||
|
// process flags
|
||||||
|
uint32_t receivedSeqn = packet->GetSeqn ();
|
||||||
|
uint16_t flags = packet->GetFlags ();
|
||||||
|
LogPrint (eLogDebug, "Streaming: Process seqn=", receivedSeqn, ", flags=", flags);
|
||||||
|
|
||||||
|
const uint8_t * optionData = packet->GetOptionData ();
|
||||||
|
|
||||||
|
if (flags & PACKET_FLAG_DELAY_REQUESTED)
|
||||||
|
optionData += 2;
|
||||||
|
|
||||||
|
if (flags & PACKET_FLAG_FROM_INCLUDED)
|
||||||
|
{
|
||||||
|
m_RemoteIdentity = std::make_shared<i2p::data::IdentityEx>(optionData, packet->GetOptionSize ());
|
||||||
|
optionData += m_RemoteIdentity->GetFullLen ();
|
||||||
|
if (!m_RemoteLeaseSet)
|
||||||
|
LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 ());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED)
|
||||||
|
{
|
||||||
|
uint16_t maxPacketSize = bufbe16toh (optionData);
|
||||||
|
LogPrint (eLogDebug, "Streaming: Max packet size ", maxPacketSize);
|
||||||
|
optionData += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & PACKET_FLAG_SIGNATURE_INCLUDED)
|
||||||
|
{
|
||||||
|
uint8_t signature[256];
|
||||||
|
auto signatureLen = m_RemoteIdentity->GetSignatureLen ();
|
||||||
|
memcpy (signature, optionData, signatureLen);
|
||||||
|
memset (const_cast<uint8_t *>(optionData), 0, signatureLen);
|
||||||
|
if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature))
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Streaming: Signature verification failed");
|
||||||
|
Close ();
|
||||||
|
flags |= PACKET_FLAG_CLOSE;
|
||||||
|
}
|
||||||
|
memcpy (const_cast<uint8_t *>(optionData), signature, signatureLen);
|
||||||
|
optionData += signatureLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
packet->offset = packet->GetPayload () - packet->buf;
|
||||||
|
if (packet->GetLength () > 0)
|
||||||
|
{
|
||||||
|
m_ReceiveQueue.push (packet);
|
||||||
|
m_ReceiveTimer.cancel ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
delete packet;
|
||||||
|
|
||||||
|
m_LastReceivedSequenceNumber = receivedSeqn;
|
||||||
|
|
||||||
|
if (flags & (PACKET_FLAG_CLOSE | PACKET_FLAG_RESET))
|
||||||
|
{
|
||||||
|
m_Status = eStreamStatusReset;
|
||||||
|
Close ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::ProcessAck (Packet * packet)
|
||||||
|
{
|
||||||
|
bool acknowledged = false;
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
uint32_t ackThrough = packet->GetAckThrough ();
|
||||||
|
int nackCount = packet->GetNACKCount ();
|
||||||
|
for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();)
|
||||||
|
{
|
||||||
|
auto seqn = (*it)->GetSeqn ();
|
||||||
|
if (seqn <= ackThrough)
|
||||||
|
{
|
||||||
|
if (nackCount > 0)
|
||||||
|
{
|
||||||
|
bool nacked = false;
|
||||||
|
for (int i = 0; i < nackCount; i++)
|
||||||
|
if (seqn == packet->GetNACK (i))
|
||||||
|
{
|
||||||
|
nacked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (nacked)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Streaming: Packet ", seqn, " NACK");
|
||||||
|
it++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto sentPacket = *it;
|
||||||
|
uint64_t rtt = ts - sentPacket->sendTime;
|
||||||
|
m_RTT = (m_RTT*seqn + rtt)/(seqn + 1);
|
||||||
|
m_RTO = m_RTT*1.5; // TODO: implement it better
|
||||||
|
LogPrint (eLogDebug, "Packet ", seqn, " acknowledged rtt=", rtt);
|
||||||
|
m_SentPackets.erase (it++);
|
||||||
|
delete sentPacket;
|
||||||
|
acknowledged = true;
|
||||||
|
if (m_WindowSize < WINDOW_SIZE)
|
||||||
|
m_WindowSize++; // slow start
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// linear growth
|
||||||
|
if (ts > m_LastWindowSizeIncreaseTime + m_RTT)
|
||||||
|
{
|
||||||
|
m_WindowSize++;
|
||||||
|
if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
|
||||||
|
m_LastWindowSizeIncreaseTime = ts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!seqn && m_RoutingSession) // first message confirmed
|
||||||
|
m_RoutingSession->SetSharedRoutingPath (
|
||||||
|
std::make_shared<i2p::garlic::GarlicRoutingPath> (
|
||||||
|
i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0, 0}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (m_SentPackets.empty ())
|
||||||
|
m_ResendTimer.cancel ();
|
||||||
|
if (acknowledged)
|
||||||
|
{
|
||||||
|
m_NumResendAttempts = 0;
|
||||||
|
SendBuffer ();
|
||||||
|
}
|
||||||
|
if (m_Status == eStreamStatusClosing)
|
||||||
|
Close (); // all outgoing messages have been sent
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Stream::Send (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
if (len > 0 && buf)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SendBufferMutex);
|
||||||
|
m_SendBuffer.clear ();
|
||||||
|
m_SendBuffer.write ((const char *)buf, len);
|
||||||
|
}
|
||||||
|
m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ()));
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler)
|
||||||
|
{
|
||||||
|
if (m_SendHandler)
|
||||||
|
handler (boost::asio::error::make_error_code (boost::asio::error::in_progress));
|
||||||
|
else
|
||||||
|
m_SendHandler = handler;
|
||||||
|
Send (buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::SendBuffer ()
|
||||||
|
{
|
||||||
|
int numMsgs = m_WindowSize - m_SentPackets.size ();
|
||||||
|
if (numMsgs <= 0) return; // window is full
|
||||||
|
|
||||||
|
bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet
|
||||||
|
std::vector<Packet *> packets;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SendBufferMutex);
|
||||||
|
while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.eof () && numMsgs > 0))
|
||||||
|
{
|
||||||
|
Packet * p = new Packet ();
|
||||||
|
uint8_t * packet = p->GetBuffer ();
|
||||||
|
// TODO: implement setters
|
||||||
|
size_t size = 0;
|
||||||
|
htobe32buf (packet + size, m_SendStreamID);
|
||||||
|
size += 4; // sendStreamID
|
||||||
|
htobe32buf (packet + size, m_RecvStreamID);
|
||||||
|
size += 4; // receiveStreamID
|
||||||
|
htobe32buf (packet + size, m_SequenceNumber++);
|
||||||
|
size += 4; // sequenceNum
|
||||||
|
if (isNoAck)
|
||||||
|
htobe32buf (packet + size, m_LastReceivedSequenceNumber);
|
||||||
|
else
|
||||||
|
htobuf32 (packet + size, 0);
|
||||||
|
size += 4; // ack Through
|
||||||
|
packet[size] = 0;
|
||||||
|
size++; // NACK count
|
||||||
|
packet[size] = m_RTO/1000;
|
||||||
|
size++; // resend delay
|
||||||
|
if (m_Status == eStreamStatusNew)
|
||||||
|
{
|
||||||
|
// initial packet
|
||||||
|
m_Status = eStreamStatusOpen;
|
||||||
|
uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED |
|
||||||
|
PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED;
|
||||||
|
if (isNoAck) flags |= PACKET_FLAG_NO_ACK;
|
||||||
|
htobe16buf (packet + size, flags);
|
||||||
|
size += 2; // flags
|
||||||
|
size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen ();
|
||||||
|
size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen ();
|
||||||
|
htobe16buf (packet + size, identityLen + signatureLen + 2); // identity + signature + packet size
|
||||||
|
size += 2; // options size
|
||||||
|
m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen);
|
||||||
|
size += identityLen; // from
|
||||||
|
htobe16buf (packet + size, STREAMING_MTU);
|
||||||
|
size += 2; // max packet size
|
||||||
|
uint8_t * signature = packet + size; // set it later
|
||||||
|
memset (signature, 0, signatureLen); // zeroes for now
|
||||||
|
size += signatureLen; // signature
|
||||||
|
m_SendBuffer.read ((char *)(packet + size), STREAMING_MTU - size);
|
||||||
|
size += m_SendBuffer.gcount (); // payload
|
||||||
|
m_LocalDestination.GetOwner ()->Sign (packet, size, signature);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// follow on packet
|
||||||
|
htobuf16 (packet + size, 0);
|
||||||
|
size += 2; // flags
|
||||||
|
htobuf16 (packet + size, 0); // no options
|
||||||
|
size += 2; // options size
|
||||||
|
m_SendBuffer.read((char *)(packet + size), STREAMING_MTU - size);
|
||||||
|
size += m_SendBuffer.gcount (); // payload
|
||||||
|
}
|
||||||
|
p->len = size;
|
||||||
|
packets.push_back (p);
|
||||||
|
numMsgs--;
|
||||||
|
}
|
||||||
|
if (m_SendBuffer.eof () && m_SendHandler)
|
||||||
|
{
|
||||||
|
m_SendHandler (boost::system::error_code ());
|
||||||
|
m_SendHandler = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (packets.size () > 0)
|
||||||
|
{
|
||||||
|
m_IsAckSendScheduled = false;
|
||||||
|
m_AckSendTimer.cancel ();
|
||||||
|
bool isEmpty = m_SentPackets.empty ();
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
for (auto it: packets)
|
||||||
|
{
|
||||||
|
it->sendTime = ts;
|
||||||
|
m_SentPackets.insert (it);
|
||||||
|
}
|
||||||
|
SendPackets (packets);
|
||||||
|
if (m_Status == eStreamStatusClosing && m_SendBuffer.eof ())
|
||||||
|
SendClose ();
|
||||||
|
if (isEmpty)
|
||||||
|
ScheduleResend ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::SendQuickAck ()
|
||||||
|
{
|
||||||
|
int32_t lastReceivedSeqn = m_LastReceivedSequenceNumber;
|
||||||
|
if (!m_SavedPackets.empty ())
|
||||||
|
{
|
||||||
|
int32_t seqn = (*m_SavedPackets.rbegin ())->GetSeqn ();
|
||||||
|
if (seqn > lastReceivedSeqn) lastReceivedSeqn = seqn;
|
||||||
|
}
|
||||||
|
if (lastReceivedSeqn < 0)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Streaming: No packets have been received yet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Packet p;
|
||||||
|
uint8_t * packet = p.GetBuffer ();
|
||||||
|
size_t size = 0;
|
||||||
|
htobe32buf (packet + size, m_SendStreamID);
|
||||||
|
size += 4; // sendStreamID
|
||||||
|
htobe32buf (packet + size, m_RecvStreamID);
|
||||||
|
size += 4; // receiveStreamID
|
||||||
|
htobuf32 (packet + size, 0); // this is plain Ack message
|
||||||
|
size += 4; // sequenceNum
|
||||||
|
htobe32buf (packet + size, lastReceivedSeqn);
|
||||||
|
size += 4; // ack Through
|
||||||
|
uint8_t numNacks = 0;
|
||||||
|
if (lastReceivedSeqn > m_LastReceivedSequenceNumber)
|
||||||
|
{
|
||||||
|
// fill NACKs
|
||||||
|
uint8_t * nacks = packet + size + 1;
|
||||||
|
auto nextSeqn = m_LastReceivedSequenceNumber + 1;
|
||||||
|
for (auto it: m_SavedPackets)
|
||||||
|
{
|
||||||
|
auto seqn = it->GetSeqn ();
|
||||||
|
if (numNacks + (seqn - nextSeqn) >= 256)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn);
|
||||||
|
htobe32buf (packet + 12, nextSeqn); // change ack Through
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (uint32_t i = nextSeqn; i < seqn; i++)
|
||||||
|
{
|
||||||
|
htobe32buf (nacks, i);
|
||||||
|
nacks += 4;
|
||||||
|
numNacks++;
|
||||||
|
}
|
||||||
|
nextSeqn = seqn + 1;
|
||||||
|
}
|
||||||
|
packet[size] = numNacks;
|
||||||
|
size++; // NACK count
|
||||||
|
size += numNacks*4; // NACKs
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No NACKs
|
||||||
|
packet[size] = 0;
|
||||||
|
size++; // NACK count
|
||||||
|
}
|
||||||
|
size++; // resend delay
|
||||||
|
htobuf16 (packet + size, 0); // nof flags set
|
||||||
|
size += 2; // flags
|
||||||
|
htobuf16 (packet + size, 0); // no options
|
||||||
|
size += 2; // options size
|
||||||
|
p.len = size;
|
||||||
|
|
||||||
|
SendPackets (std::vector<Packet *> { &p });
|
||||||
|
LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::Close ()
|
||||||
|
{
|
||||||
|
switch (m_Status)
|
||||||
|
{
|
||||||
|
case eStreamStatusOpen:
|
||||||
|
m_Status = eStreamStatusClosing;
|
||||||
|
Close (); // recursion
|
||||||
|
if (m_Status == eStreamStatusClosing) //still closing
|
||||||
|
LogPrint (eLogInfo, "Streaming: Trying to send stream data before closing");
|
||||||
|
break;
|
||||||
|
case eStreamStatusReset:
|
||||||
|
SendClose ();
|
||||||
|
Terminate ();
|
||||||
|
m_LocalDestination.DeleteStream (shared_from_this ());
|
||||||
|
break;
|
||||||
|
case eStreamStatusClosing:
|
||||||
|
if (m_SentPackets.empty () && m_SendBuffer.eof ()) // nothing to send
|
||||||
|
{
|
||||||
|
m_Status = eStreamStatusClosed;
|
||||||
|
SendClose ();
|
||||||
|
Terminate ();
|
||||||
|
m_LocalDestination.DeleteStream (shared_from_this ());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case eStreamStatusClosed:
|
||||||
|
// already closed
|
||||||
|
Terminate ();
|
||||||
|
m_LocalDestination.DeleteStream (shared_from_this ());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::SendClose ()
|
||||||
|
{
|
||||||
|
Packet * p = new Packet ();
|
||||||
|
uint8_t * packet = p->GetBuffer ();
|
||||||
|
size_t size = 0;
|
||||||
|
htobe32buf (packet + size, m_SendStreamID);
|
||||||
|
size += 4; // sendStreamID
|
||||||
|
htobe32buf (packet + size, m_RecvStreamID);
|
||||||
|
size += 4; // receiveStreamID
|
||||||
|
htobe32buf (packet + size, m_SequenceNumber++);
|
||||||
|
size += 4; // sequenceNum
|
||||||
|
htobe32buf (packet + size, m_LastReceivedSequenceNumber);
|
||||||
|
size += 4; // ack Through
|
||||||
|
packet[size] = 0;
|
||||||
|
size++; // NACK count
|
||||||
|
size++; // resend delay
|
||||||
|
htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED);
|
||||||
|
size += 2; // flags
|
||||||
|
size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen ();
|
||||||
|
htobe16buf (packet + size, signatureLen); // signature only
|
||||||
|
size += 2; // options size
|
||||||
|
uint8_t * signature = packet + size;
|
||||||
|
memset (packet + size, 0, signatureLen);
|
||||||
|
size += signatureLen; // signature
|
||||||
|
m_LocalDestination.GetOwner ()->Sign (packet, size, signature);
|
||||||
|
|
||||||
|
p->len = size;
|
||||||
|
m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p));
|
||||||
|
LogPrint (eLogDebug, "Streaming: FIN sent");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos < len && !m_ReceiveQueue.empty ())
|
||||||
|
{
|
||||||
|
Packet * packet = m_ReceiveQueue.front ();
|
||||||
|
size_t l = std::min (packet->GetLength (), len - pos);
|
||||||
|
memcpy (buf + pos, packet->GetBuffer (), l);
|
||||||
|
pos += l;
|
||||||
|
packet->offset += l;
|
||||||
|
if (!packet->GetLength ())
|
||||||
|
{
|
||||||
|
m_ReceiveQueue.pop ();
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Stream::SendPacket (Packet * packet)
|
||||||
|
{
|
||||||
|
if (packet)
|
||||||
|
{
|
||||||
|
if (m_IsAckSendScheduled)
|
||||||
|
{
|
||||||
|
m_IsAckSendScheduled = false;
|
||||||
|
m_AckSendTimer.cancel ();
|
||||||
|
}
|
||||||
|
SendPackets (std::vector<Packet *> { packet });
|
||||||
|
if (m_Status == eStreamStatusOpen)
|
||||||
|
{
|
||||||
|
bool isEmpty = m_SentPackets.empty ();
|
||||||
|
m_SentPackets.insert (packet);
|
||||||
|
if (isEmpty)
|
||||||
|
ScheduleResend ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
delete packet;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::SendPackets (const std::vector<Packet *>& packets)
|
||||||
|
{
|
||||||
|
if (!m_RemoteLeaseSet)
|
||||||
|
{
|
||||||
|
UpdateCurrentRemoteLease ();
|
||||||
|
if (!m_RemoteLeaseSet)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Streaming: Can't send packets, missing remote LeaseSet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_CurrentOutboundTunnel) // first message to send
|
||||||
|
{
|
||||||
|
// try to get shared path first
|
||||||
|
if (!m_RoutingSession)
|
||||||
|
m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true);
|
||||||
|
if (m_RoutingSession)
|
||||||
|
{
|
||||||
|
auto routingPath = m_RoutingSession->GetSharedRoutingPath ();
|
||||||
|
if (routingPath)
|
||||||
|
{
|
||||||
|
m_CurrentOutboundTunnel = routingPath->outboundTunnel;
|
||||||
|
m_CurrentRemoteLease = routingPath->remoteLease;
|
||||||
|
m_RTT = routingPath->rtt;
|
||||||
|
m_RTO = m_RTT*1.5; // TODO: implement it better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ())
|
||||||
|
m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel);
|
||||||
|
if (!m_CurrentOutboundTunnel)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Streaming: No outbound tunnels in the pool");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
if (!m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD)
|
||||||
|
UpdateCurrentRemoteLease (true);
|
||||||
|
if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD)
|
||||||
|
{
|
||||||
|
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
|
||||||
|
for (auto it: packets)
|
||||||
|
{
|
||||||
|
auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage (it->GetBuffer (), it->GetLength (), m_Port));
|
||||||
|
msgs.push_back (i2p::tunnel::TunnelMessageBlock
|
||||||
|
{
|
||||||
|
i2p::tunnel::eDeliveryTypeTunnel,
|
||||||
|
m_CurrentRemoteLease->tunnelGateway, m_CurrentRemoteLease->tunnelID,
|
||||||
|
msg
|
||||||
|
});
|
||||||
|
m_NumSentBytes += it->GetLength ();
|
||||||
|
}
|
||||||
|
m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Streaming: All leases are expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Stream::ScheduleResend ()
|
||||||
|
{
|
||||||
|
m_ResendTimer.cancel ();
|
||||||
|
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO));
|
||||||
|
m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::HandleResendTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
// check for resend attempts
|
||||||
|
if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate");
|
||||||
|
m_Status = eStreamStatusReset;
|
||||||
|
Close ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect packets to resend
|
||||||
|
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
std::vector<Packet *> packets;
|
||||||
|
for (auto it : m_SentPackets)
|
||||||
|
{
|
||||||
|
if (ts >= it->sendTime + m_RTO)
|
||||||
|
{
|
||||||
|
it->sendTime = ts;
|
||||||
|
packets.push_back (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// select tunnels if necessary and send
|
||||||
|
if (packets.size () > 0)
|
||||||
|
{
|
||||||
|
m_NumResendAttempts++;
|
||||||
|
m_RTO *= 2;
|
||||||
|
switch (m_NumResendAttempts)
|
||||||
|
{
|
||||||
|
case 1: // congesion avoidance
|
||||||
|
m_WindowSize /= 2;
|
||||||
|
if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time
|
||||||
|
// no break here
|
||||||
|
case 4:
|
||||||
|
if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
|
||||||
|
UpdateCurrentRemoteLease (); // pick another lease
|
||||||
|
LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// pick another outbound tunnel
|
||||||
|
if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
|
||||||
|
m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel);
|
||||||
|
LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream");
|
||||||
|
break;
|
||||||
|
default: ;
|
||||||
|
}
|
||||||
|
SendPackets (packets);
|
||||||
|
}
|
||||||
|
ScheduleResend ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::HandleAckSendTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (m_IsAckSendScheduled)
|
||||||
|
{
|
||||||
|
if (m_LastReceivedSequenceNumber < 0)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Streaming: SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate");
|
||||||
|
m_Status = eStreamStatusReset;
|
||||||
|
Close ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_Status == eStreamStatusOpen)
|
||||||
|
SendQuickAck ();
|
||||||
|
m_IsAckSendScheduled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::UpdateCurrentRemoteLease (bool expired)
|
||||||
|
{
|
||||||
|
if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ())
|
||||||
|
{
|
||||||
|
m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());
|
||||||
|
if (!m_RemoteLeaseSet)
|
||||||
|
LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found");
|
||||||
|
}
|
||||||
|
if (m_RemoteLeaseSet)
|
||||||
|
{
|
||||||
|
if (!m_RoutingSession)
|
||||||
|
m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true);
|
||||||
|
auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first
|
||||||
|
if (leases.empty ())
|
||||||
|
{
|
||||||
|
expired = false;
|
||||||
|
m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // time to request
|
||||||
|
leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold
|
||||||
|
}
|
||||||
|
if (!leases.empty ())
|
||||||
|
{
|
||||||
|
bool updated = false;
|
||||||
|
if (expired && m_CurrentRemoteLease)
|
||||||
|
{
|
||||||
|
for (auto it: leases)
|
||||||
|
if ((it->tunnelGateway == m_CurrentRemoteLease->tunnelGateway) && (it->tunnelID != m_CurrentRemoteLease->tunnelID))
|
||||||
|
{
|
||||||
|
m_CurrentRemoteLease = it;
|
||||||
|
updated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updated)
|
||||||
|
{
|
||||||
|
uint32_t i = rand () % leases.size ();
|
||||||
|
if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID)
|
||||||
|
// make sure we don't select previous
|
||||||
|
i = (i + 1) % leases.size (); // if so, pick next
|
||||||
|
m_CurrentRemoteLease = leases[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_RemoteLeaseSet = nullptr;
|
||||||
|
m_CurrentRemoteLease = nullptr;
|
||||||
|
// we have requested expired before, no need to do it twice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_CurrentRemoteLease = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort, bool gzip):
|
||||||
|
m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip),
|
||||||
|
m_PendingIncomingTimer (m_Owner->GetService ())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamingDestination::~StreamingDestination ()
|
||||||
|
{
|
||||||
|
for (auto it: m_SavedPackets)
|
||||||
|
{
|
||||||
|
for (auto it1: it.second) delete it1;
|
||||||
|
it.second.clear ();
|
||||||
|
}
|
||||||
|
m_SavedPackets.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::Start ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::Stop ()
|
||||||
|
{
|
||||||
|
ResetAcceptor ();
|
||||||
|
m_PendingIncomingTimer.cancel ();
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||||
|
m_Streams.clear ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::HandleNextPacket (Packet * packet)
|
||||||
|
{
|
||||||
|
uint32_t sendStreamID = packet->GetSendStreamID ();
|
||||||
|
if (sendStreamID)
|
||||||
|
{
|
||||||
|
auto it = m_Streams.find (sendStreamID);
|
||||||
|
if (it != m_Streams.end ())
|
||||||
|
it->second->HandleNextPacket (packet);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Streaming: Unknown stream sendStreamID=", sendStreamID);
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream
|
||||||
|
{
|
||||||
|
auto incomingStream = CreateNewIncomingStream ();
|
||||||
|
uint32_t receiveStreamID = packet->GetReceiveStreamID ();
|
||||||
|
incomingStream->HandleNextPacket (packet); // SYN
|
||||||
|
// handle saved packets if any
|
||||||
|
{
|
||||||
|
auto it = m_SavedPackets.find (receiveStreamID);
|
||||||
|
if (it != m_SavedPackets.end ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Streaming: Processing ", it->second.size (), " saved packets for receiveStreamID=", receiveStreamID);
|
||||||
|
for (auto it1: it->second)
|
||||||
|
incomingStream->HandleNextPacket (it1);
|
||||||
|
m_SavedPackets.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// accept
|
||||||
|
if (m_Acceptor != nullptr)
|
||||||
|
m_Acceptor (incomingStream);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Streaming: Acceptor for incoming stream is not set");
|
||||||
|
if (m_PendingIncomingStreams.size () < MAX_PENDING_INCOMING_BACKLOG)
|
||||||
|
{
|
||||||
|
m_PendingIncomingStreams.push_back (incomingStream);
|
||||||
|
m_PendingIncomingTimer.cancel ();
|
||||||
|
m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT));
|
||||||
|
m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer,
|
||||||
|
shared_from_this (), std::placeholders::_1));
|
||||||
|
LogPrint (eLogDebug, "Streaming: Pending incoming stream added");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Streaming: Pending incoming streams backlog exceeds ", MAX_PENDING_INCOMING_BACKLOG);
|
||||||
|
incomingStream->Close ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // follow on packet without SYN
|
||||||
|
{
|
||||||
|
uint32_t receiveStreamID = packet->GetReceiveStreamID ();
|
||||||
|
for (auto it: m_Streams)
|
||||||
|
if (it.second->GetSendStreamID () == receiveStreamID)
|
||||||
|
{
|
||||||
|
// found
|
||||||
|
it.second->HandleNextPacket (packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// save follow on packet
|
||||||
|
auto it = m_SavedPackets.find (receiveStreamID);
|
||||||
|
if (it != m_SavedPackets.end ())
|
||||||
|
it->second.push_back (packet);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_SavedPackets[receiveStreamID] = std::list<Packet *>{ packet };
|
||||||
|
auto timer = std::make_shared<boost::asio::deadline_timer> (m_Owner->GetService ());
|
||||||
|
timer->expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT));
|
||||||
|
auto s = shared_from_this ();
|
||||||
|
timer->async_wait ([s,timer,receiveStreamID](const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
auto it = s->m_SavedPackets.find (receiveStreamID);
|
||||||
|
if (it != s->m_SavedPackets.end ())
|
||||||
|
{
|
||||||
|
for (auto it1: it->second) delete it1;
|
||||||
|
it->second.clear ();
|
||||||
|
s->m_SavedPackets.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Stream> StreamingDestination::CreateNewOutgoingStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port)
|
||||||
|
{
|
||||||
|
auto s = std::make_shared<Stream> (m_Owner->GetService (), *this, remote, port);
|
||||||
|
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||||
|
m_Streams[s->GetRecvStreamID ()] = s;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Stream> StreamingDestination::CreateNewIncomingStream ()
|
||||||
|
{
|
||||||
|
auto s = std::make_shared<Stream> (m_Owner->GetService (), *this);
|
||||||
|
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||||
|
m_Streams[s->GetRecvStreamID ()] = s;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::DeleteStream (std::shared_ptr<Stream> stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_StreamsMutex);
|
||||||
|
auto it = m_Streams.find (stream->GetRecvStreamID ());
|
||||||
|
if (it != m_Streams.end ())
|
||||||
|
m_Streams.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::SetAcceptor (const Acceptor& acceptor)
|
||||||
|
{
|
||||||
|
m_Owner->GetService ().post([acceptor, this](void)
|
||||||
|
{
|
||||||
|
m_Acceptor = acceptor;
|
||||||
|
for (auto it: m_PendingIncomingStreams)
|
||||||
|
if (it->GetStatus () == eStreamStatusOpen) // still open?
|
||||||
|
m_Acceptor (it);
|
||||||
|
m_PendingIncomingStreams.clear ();
|
||||||
|
m_PendingIncomingTimer.cancel ();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::ResetAcceptor ()
|
||||||
|
{
|
||||||
|
if (m_Acceptor) m_Acceptor (nullptr);
|
||||||
|
m_Acceptor = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Streaming: Pending incoming timeout expired");
|
||||||
|
for (auto it: m_PendingIncomingStreams)
|
||||||
|
it->Close ();
|
||||||
|
m_PendingIncomingStreams.clear ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
// unzip it
|
||||||
|
Packet * uncompressed = new Packet;
|
||||||
|
uncompressed->offset = 0;
|
||||||
|
uncompressed->len = m_Inflator.Inflate (buf, len, uncompressed->buf, MAX_PACKET_SIZE);
|
||||||
|
if (uncompressed->len)
|
||||||
|
HandleNextPacket (uncompressed);
|
||||||
|
else
|
||||||
|
delete uncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<I2NPMessage> StreamingDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort)
|
||||||
|
{
|
||||||
|
auto msg = NewI2NPShortMessage ();
|
||||||
|
if (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)
|
||||||
|
m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION);
|
||||||
|
else
|
||||||
|
m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION);
|
||||||
|
uint8_t * buf = msg->GetPayload ();
|
||||||
|
buf += 4; // reserve for lengthlength
|
||||||
|
msg->len += 4;
|
||||||
|
size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len);
|
||||||
|
if (size)
|
||||||
|
{
|
||||||
|
htobe32buf (msg->GetPayload (), size); // length
|
||||||
|
htobe16buf (buf + 4, m_LocalPort); // source port
|
||||||
|
htobe16buf (buf + 6, toPort); // destination port
|
||||||
|
buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol
|
||||||
|
msg->len += size;
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
msg = nullptr;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
280
Streaming.h
Normal file
280
Streaming.h
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
#ifndef STREAMING_H__
|
||||||
|
#define STREAMING_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <queue>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "Base.h"
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "LeaseSet.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Garlic.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace client
|
||||||
|
{
|
||||||
|
class ClientDestination;
|
||||||
|
}
|
||||||
|
namespace stream
|
||||||
|
{
|
||||||
|
const uint16_t PACKET_FLAG_SYNCHRONIZE = 0x0001;
|
||||||
|
const uint16_t PACKET_FLAG_CLOSE = 0x0002;
|
||||||
|
const uint16_t PACKET_FLAG_RESET = 0x0004;
|
||||||
|
const uint16_t PACKET_FLAG_SIGNATURE_INCLUDED = 0x0008;
|
||||||
|
const uint16_t PACKET_FLAG_SIGNATURE_REQUESTED = 0x0010;
|
||||||
|
const uint16_t PACKET_FLAG_FROM_INCLUDED = 0x0020;
|
||||||
|
const uint16_t PACKET_FLAG_DELAY_REQUESTED = 0x0040;
|
||||||
|
const uint16_t PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED = 0x0080;
|
||||||
|
const uint16_t PACKET_FLAG_PROFILE_INTERACTIVE = 0x0100;
|
||||||
|
const uint16_t PACKET_FLAG_ECHO = 0x0200;
|
||||||
|
const uint16_t PACKET_FLAG_NO_ACK = 0x0400;
|
||||||
|
|
||||||
|
const size_t STREAMING_MTU = 1730;
|
||||||
|
const size_t MAX_PACKET_SIZE = 4096;
|
||||||
|
const size_t COMPRESSION_THRESHOLD_SIZE = 66;
|
||||||
|
const int ACK_SEND_TIMEOUT = 200; // in milliseconds
|
||||||
|
const int MAX_NUM_RESEND_ATTEMPTS = 6;
|
||||||
|
const int WINDOW_SIZE = 6; // in messages
|
||||||
|
const int MIN_WINDOW_SIZE = 1;
|
||||||
|
const int MAX_WINDOW_SIZE = 128;
|
||||||
|
const int INITIAL_RTT = 8000; // in milliseconds
|
||||||
|
const int INITIAL_RTO = 9000; // in milliseconds
|
||||||
|
const size_t MAX_PENDING_INCOMING_BACKLOG = 128;
|
||||||
|
const int PENDING_INCOMING_TIMEOUT = 10; // in seconds
|
||||||
|
|
||||||
|
struct Packet
|
||||||
|
{
|
||||||
|
size_t len, offset;
|
||||||
|
uint8_t buf[MAX_PACKET_SIZE];
|
||||||
|
uint64_t sendTime;
|
||||||
|
|
||||||
|
Packet (): len (0), offset (0), sendTime (0) {};
|
||||||
|
uint8_t * GetBuffer () { return buf + offset; };
|
||||||
|
size_t GetLength () const { return len - offset; };
|
||||||
|
|
||||||
|
uint32_t GetSendStreamID () const { return bufbe32toh (buf); };
|
||||||
|
uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); };
|
||||||
|
uint32_t GetSeqn () const { return bufbe32toh (buf + 8); };
|
||||||
|
uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); };
|
||||||
|
uint8_t GetNACKCount () const { return buf[16]; };
|
||||||
|
uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); };
|
||||||
|
const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags
|
||||||
|
uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); };
|
||||||
|
uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); };
|
||||||
|
const uint8_t * GetOptionData () const { return GetOption () + 2; };
|
||||||
|
const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); };
|
||||||
|
|
||||||
|
bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; };
|
||||||
|
bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PacketCmp
|
||||||
|
{
|
||||||
|
bool operator() (const Packet * p1, const Packet * p2) const
|
||||||
|
{
|
||||||
|
return p1->GetSeqn () < p2->GetSeqn ();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum StreamStatus
|
||||||
|
{
|
||||||
|
eStreamStatusNew = 0,
|
||||||
|
eStreamStatusOpen,
|
||||||
|
eStreamStatusReset,
|
||||||
|
eStreamStatusClosing,
|
||||||
|
eStreamStatusClosed
|
||||||
|
};
|
||||||
|
|
||||||
|
class StreamingDestination;
|
||||||
|
class Stream: public std::enable_shared_from_this<Stream>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef std::function<void (const boost::system::error_code& ecode)> SendHandler;
|
||||||
|
|
||||||
|
Stream (boost::asio::io_service& service, StreamingDestination& local,
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0); // outgoing
|
||||||
|
Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming
|
||||||
|
|
||||||
|
~Stream ();
|
||||||
|
uint32_t GetSendStreamID () const { return m_SendStreamID; };
|
||||||
|
uint32_t GetRecvStreamID () const { return m_RecvStreamID; };
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> GetRemoteLeaseSet () const { return m_RemoteLeaseSet; };
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () const { return m_RemoteIdentity; };
|
||||||
|
bool IsOpen () const { return m_Status == eStreamStatusOpen; };
|
||||||
|
bool IsEstablished () const { return m_SendStreamID; };
|
||||||
|
StreamStatus GetStatus () const { return m_Status; };
|
||||||
|
StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
|
||||||
|
|
||||||
|
void HandleNextPacket (Packet * packet);
|
||||||
|
size_t Send (const uint8_t * buf, size_t len);
|
||||||
|
void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler);
|
||||||
|
|
||||||
|
template<typename Buffer, typename ReceiveHandler>
|
||||||
|
void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0);
|
||||||
|
size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); };
|
||||||
|
|
||||||
|
void Close ();
|
||||||
|
void Cancel () { m_ReceiveTimer.cancel (); };
|
||||||
|
|
||||||
|
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||||
|
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||||
|
size_t GetSendQueueSize () const { return m_SentPackets.size (); };
|
||||||
|
size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); };
|
||||||
|
size_t GetSendBufferSize () const { return m_SendBuffer.rdbuf ()->in_avail (); };
|
||||||
|
int GetWindowSize () const { return m_WindowSize; };
|
||||||
|
int GetRTT () const { return m_RTT; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Terminate ();
|
||||||
|
|
||||||
|
void SendBuffer ();
|
||||||
|
void SendQuickAck ();
|
||||||
|
void SendClose ();
|
||||||
|
bool SendPacket (Packet * packet);
|
||||||
|
void SendPackets (const std::vector<Packet *>& packets);
|
||||||
|
|
||||||
|
void SavePacket (Packet * packet);
|
||||||
|
void ProcessPacket (Packet * packet);
|
||||||
|
void ProcessAck (Packet * packet);
|
||||||
|
size_t ConcatenatePackets (uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
void UpdateCurrentRemoteLease (bool expired = false);
|
||||||
|
|
||||||
|
template<typename Buffer, typename ReceiveHandler>
|
||||||
|
void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler);
|
||||||
|
|
||||||
|
void ScheduleResend ();
|
||||||
|
void HandleResendTimer (const boost::system::error_code& ecode);
|
||||||
|
void HandleAckSendTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
boost::asio::io_service& m_Service;
|
||||||
|
uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
|
||||||
|
int32_t m_LastReceivedSequenceNumber;
|
||||||
|
StreamStatus m_Status;
|
||||||
|
bool m_IsAckSendScheduled;
|
||||||
|
StreamingDestination& m_LocalDestination;
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
|
||||||
|
std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
|
||||||
|
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
|
||||||
|
std::shared_ptr<const i2p::data::Lease> m_CurrentRemoteLease;
|
||||||
|
std::shared_ptr<i2p::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
|
||||||
|
std::queue<Packet *> m_ReceiveQueue;
|
||||||
|
std::set<Packet *, PacketCmp> m_SavedPackets;
|
||||||
|
std::set<Packet *, PacketCmp> m_SentPackets;
|
||||||
|
boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer;
|
||||||
|
size_t m_NumSentBytes, m_NumReceivedBytes;
|
||||||
|
uint16_t m_Port;
|
||||||
|
|
||||||
|
std::mutex m_SendBufferMutex;
|
||||||
|
std::stringstream m_SendBuffer;
|
||||||
|
int m_WindowSize, m_RTT, m_RTO;
|
||||||
|
uint64_t m_LastWindowSizeIncreaseTime;
|
||||||
|
int m_NumResendAttempts;
|
||||||
|
SendHandler m_SendHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StreamingDestination: public std::enable_shared_from_this<StreamingDestination>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef std::function<void (std::shared_ptr<Stream>)> Acceptor;
|
||||||
|
|
||||||
|
StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort = 0, bool gzip = true);
|
||||||
|
~StreamingDestination ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
std::shared_ptr<Stream> CreateNewOutgoingStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
|
||||||
|
void DeleteStream (std::shared_ptr<Stream> stream);
|
||||||
|
void SetAcceptor (const Acceptor& acceptor);
|
||||||
|
void ResetAcceptor ();
|
||||||
|
bool IsAcceptorSet () const { return m_Acceptor != nullptr; };
|
||||||
|
std::shared_ptr<i2p::client::ClientDestination> GetOwner () const { return m_Owner; };
|
||||||
|
uint16_t GetLocalPort () const { return m_LocalPort; };
|
||||||
|
|
||||||
|
void HandleDataMessagePayload (const uint8_t * buf, size_t len);
|
||||||
|
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void HandleNextPacket (Packet * packet);
|
||||||
|
std::shared_ptr<Stream> CreateNewIncomingStream ();
|
||||||
|
void HandlePendingIncomingTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::client::ClientDestination> m_Owner;
|
||||||
|
uint16_t m_LocalPort;
|
||||||
|
bool m_Gzip; // gzip compression of data messages
|
||||||
|
std::mutex m_StreamsMutex;
|
||||||
|
std::map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream
|
||||||
|
Acceptor m_Acceptor;
|
||||||
|
std::list<std::shared_ptr<Stream> > m_PendingIncomingStreams;
|
||||||
|
boost::asio::deadline_timer m_PendingIncomingTimer;
|
||||||
|
std::map<uint32_t, std::list<Packet *> > m_SavedPackets; // receiveStreamID->packets, arrived before SYN
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
i2p::data::GzipInflator m_Inflator;
|
||||||
|
i2p::data::GzipDeflator m_Deflator;
|
||||||
|
|
||||||
|
// for HTTP only
|
||||||
|
const decltype(m_Streams)& GetStreams () const { return m_Streams; };
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
template<typename Buffer, typename ReceiveHandler>
|
||||||
|
void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout)
|
||||||
|
{
|
||||||
|
auto s = shared_from_this();
|
||||||
|
m_Service.post ([=](void)
|
||||||
|
{
|
||||||
|
if (!m_ReceiveQueue.empty () || m_Status == eStreamStatusReset)
|
||||||
|
s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout));
|
||||||
|
s->m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode)
|
||||||
|
{ s->HandleReceiveTimer (ecode, buffer, handler); });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Buffer, typename ReceiveHandler>
|
||||||
|
void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler)
|
||||||
|
{
|
||||||
|
size_t received = ConcatenatePackets (boost::asio::buffer_cast<uint8_t *>(buffer), boost::asio::buffer_size(buffer));
|
||||||
|
if (received > 0)
|
||||||
|
handler (boost::system::error_code (), received);
|
||||||
|
else if (ecode == boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
// timeout not expired
|
||||||
|
if (m_Status == eStreamStatusReset)
|
||||||
|
handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0);
|
||||||
|
else
|
||||||
|
handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// timeout expired
|
||||||
|
handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
16
TODO
16
TODO
@@ -1,16 +0,0 @@
|
|||||||
Refactoring:
|
|
||||||
- SSUSession:637, SSUSession:635 get rid of casting to SSUHeader
|
|
||||||
- Identity.cpp:156 check for self asignment
|
|
||||||
- Rely on a library for TLS and SSL.
|
|
||||||
- Move parsing code out of networking code, to allow better testing.
|
|
||||||
- Move streaming code to a separate directory.
|
|
||||||
- Separate front-end code (SAM, BOB, ...) from the back-end.
|
|
||||||
|
|
||||||
Additions:
|
|
||||||
- Write tests.
|
|
||||||
- Add documentation.
|
|
||||||
- Create a specialized IRC tunnel (instead of just a generic ClientTunnel instance).
|
|
||||||
- Ban peers with unexpected / bad behaviour.
|
|
||||||
- EdDSA support.
|
|
||||||
- Return correct error codes in HTTPProxy.
|
|
||||||
- Modify web interface layout and use AJAX to update information.
|
|
||||||
32
Timestamp.h
Normal file
32
Timestamp.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef TIMESTAMP_H__
|
||||||
|
#define TIMESTAMP_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
inline uint64_t GetMillisecondsSinceEpoch ()
|
||||||
|
{
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::system_clock::now().time_since_epoch()).count ();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t GetHoursSinceEpoch ()
|
||||||
|
{
|
||||||
|
return std::chrono::duration_cast<std::chrono::hours>(
|
||||||
|
std::chrono::system_clock::now().time_since_epoch()).count ();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t GetSecondsSinceEpoch ()
|
||||||
|
{
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
std::chrono::system_clock::now().time_since_epoch()).count ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
110
TransitTunnel.cpp
Normal file
110
TransitTunnel.cpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "TransitTunnel.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
TransitTunnel::TransitTunnel (uint32_t receiveTunnelID,
|
||||||
|
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||||
|
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||||
|
TunnelBase (receiveTunnelID, nextTunnelID, nextIdent)
|
||||||
|
{
|
||||||
|
m_Encryption.SetKeys (layerKey, ivKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
|
||||||
|
{
|
||||||
|
m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TransitTunnelParticipant::~TransitTunnelParticipant ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||||
|
{
|
||||||
|
auto newMsg = CreateEmptyTunnelDataMsg ();
|
||||||
|
EncryptTunnelMsg (tunnelMsg, newMsg);
|
||||||
|
|
||||||
|
m_NumTransmittedBytes += tunnelMsg->GetLength ();
|
||||||
|
htobe32buf (newMsg->GetPayload (), GetNextTunnelID ());
|
||||||
|
newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||||
|
m_TunnelDataMsgs.push_back (newMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnelParticipant::FlushTunnelDataMsgs ()
|
||||||
|
{
|
||||||
|
if (!m_TunnelDataMsgs.empty ())
|
||||||
|
{
|
||||||
|
auto num = m_TunnelDataMsgs.size ();
|
||||||
|
if (num > 1)
|
||||||
|
LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num);
|
||||||
|
i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs);
|
||||||
|
m_TunnelDataMsgs.clear ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
TunnelMessageBlock block;
|
||||||
|
block.deliveryType = eDeliveryTypeLocal;
|
||||||
|
block.data = msg;
|
||||||
|
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||||
|
m_Gateway.PutTunnelDataMsg (block);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnelGateway::FlushTunnelDataMsgs ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||||
|
m_Gateway.SendBuffer ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||||
|
{
|
||||||
|
auto newMsg = CreateEmptyTunnelDataMsg ();
|
||||||
|
EncryptTunnelMsg (tunnelMsg, newMsg);
|
||||||
|
|
||||||
|
LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
|
||||||
|
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
|
||||||
|
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||||
|
const uint8_t * layerKey,const uint8_t * ivKey,
|
||||||
|
bool isGateway, bool isEndpoint)
|
||||||
|
{
|
||||||
|
if (isEndpoint)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "TransitTunnel: endpoint ", receiveTunnelID, " created");
|
||||||
|
return std::make_shared<TransitTunnelEndpoint> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||||
|
}
|
||||||
|
else if (isGateway)
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created");
|
||||||
|
return std::make_shared<TransitTunnelGateway> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created");
|
||||||
|
return std::make_shared<TransitTunnelParticipant> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
TransitTunnel.h
Normal file
103
TransitTunnel.h
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#ifndef TRANSIT_TUNNEL_H__
|
||||||
|
#define TRANSIT_TUNNEL_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "TunnelEndpoint.h"
|
||||||
|
#include "TunnelGateway.h"
|
||||||
|
#include "TunnelBase.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
class TransitTunnel: public TunnelBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
TransitTunnel (uint32_t receiveTunnelID,
|
||||||
|
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||||
|
const uint8_t * layerKey,const uint8_t * ivKey);
|
||||||
|
|
||||||
|
virtual size_t GetNumTransmittedBytes () const { return 0; };
|
||||||
|
|
||||||
|
// implements TunnelBase
|
||||||
|
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||||
|
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
|
||||||
|
private:
|
||||||
|
|
||||||
|
i2p::crypto::TunnelEncryption m_Encryption;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransitTunnelParticipant: public TransitTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
TransitTunnelParticipant (uint32_t receiveTunnelID,
|
||||||
|
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||||
|
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||||
|
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
|
||||||
|
layerKey, ivKey), m_NumTransmittedBytes (0) {};
|
||||||
|
~TransitTunnelParticipant ();
|
||||||
|
|
||||||
|
size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; };
|
||||||
|
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||||
|
void FlushTunnelDataMsgs ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
size_t m_NumTransmittedBytes;
|
||||||
|
std::vector<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransitTunnelGateway: public TransitTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
TransitTunnelGateway (uint32_t receiveTunnelID,
|
||||||
|
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||||
|
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||||
|
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
|
||||||
|
layerKey, ivKey), m_Gateway(this) {};
|
||||||
|
|
||||||
|
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
void FlushTunnelDataMsgs ();
|
||||||
|
size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::mutex m_SendMutex;
|
||||||
|
TunnelGateway m_Gateway;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransitTunnelEndpoint: public TransitTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
TransitTunnelEndpoint (uint32_t receiveTunnelID,
|
||||||
|
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||||
|
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||||
|
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
|
||||||
|
m_Endpoint (false) {}; // transit endpoint is always outbound
|
||||||
|
|
||||||
|
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||||
|
size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
TunnelEndpoint m_Endpoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
|
||||||
|
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||||
|
const uint8_t * layerKey,const uint8_t * ivKey,
|
||||||
|
bool isGateway, bool isEndpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
85
TransportSession.h
Normal file
85
TransportSession.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef TRANSPORT_SESSION_H__
|
||||||
|
#define TRANSPORT_SESSION_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
class SignedData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SignedData () {}
|
||||||
|
SignedData (const SignedData& other)
|
||||||
|
{
|
||||||
|
m_Stream << other.m_Stream.rdbuf ();
|
||||||
|
}
|
||||||
|
void Insert (const uint8_t * buf, size_t len)
|
||||||
|
{
|
||||||
|
m_Stream.write ((char *)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void Insert (T t)
|
||||||
|
{
|
||||||
|
m_Stream.write ((char *)&t, sizeof (T));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const
|
||||||
|
{
|
||||||
|
return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const
|
||||||
|
{
|
||||||
|
keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::stringstream m_Stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransportSession
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
TransportSession (std::shared_ptr<const i2p::data::RouterInfo> router):
|
||||||
|
m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router)
|
||||||
|
{
|
||||||
|
if (router)
|
||||||
|
m_RemoteIdentity = router->GetRouterIdentity ();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~TransportSession () {};
|
||||||
|
virtual void Done () = 0;
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () { return m_RemoteIdentity; };
|
||||||
|
void SetRemoteIdentity (std::shared_ptr<const i2p::data::IdentityEx> ident) { m_RemoteIdentity = ident; };
|
||||||
|
|
||||||
|
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||||
|
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||||
|
bool IsOutgoing () const { return m_IsOutgoing; };
|
||||||
|
|
||||||
|
virtual void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
|
||||||
|
std::shared_ptr<i2p::crypto::DHKeys> m_DHKeysPair; // X - for client and Y - for server
|
||||||
|
size_t m_NumSentBytes, m_NumReceivedBytes;
|
||||||
|
bool m_IsOutgoing;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
617
Transports.cpp
Normal file
617
Transports.cpp
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
#include "Log.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
|
||||||
|
using namespace i2p::data;
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
DHKeysPairSupplier::DHKeysPairSupplier (int size):
|
||||||
|
m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DHKeysPairSupplier::~DHKeysPairSupplier ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DHKeysPairSupplier::Start ()
|
||||||
|
{
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Thread = new std::thread (std::bind (&DHKeysPairSupplier::Run, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DHKeysPairSupplier::Stop ()
|
||||||
|
{
|
||||||
|
m_IsRunning = false;
|
||||||
|
m_Acquired.notify_one ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DHKeysPairSupplier::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
int num;
|
||||||
|
while ((num = m_QueueSize - m_Queue.size ()) > 0)
|
||||||
|
CreateDHKeysPairs (num);
|
||||||
|
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||||
|
m_Acquired.wait (l); // wait for element gets aquired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DHKeysPairSupplier::CreateDHKeysPairs (int num)
|
||||||
|
{
|
||||||
|
if (num > 0)
|
||||||
|
{
|
||||||
|
i2p::crypto::DHKeys dh;
|
||||||
|
for (int i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
auto pair = std::make_shared<i2p::crypto::DHKeys> ();
|
||||||
|
pair->GenerateKeys ();
|
||||||
|
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||||
|
m_Queue.push (pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::crypto::DHKeys> DHKeysPairSupplier::Acquire ()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||||
|
if (!m_Queue.empty ())
|
||||||
|
{
|
||||||
|
auto pair = m_Queue.front ();
|
||||||
|
m_Queue.pop ();
|
||||||
|
m_Acquired.notify_one ();
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// queue is empty, create new
|
||||||
|
auto pair = std::make_shared<i2p::crypto::DHKeys> ();
|
||||||
|
pair->GenerateKeys ();
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DHKeysPairSupplier::Return (std::shared_ptr<i2p::crypto::DHKeys> pair)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||||
|
m_Queue.push (pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transports transports;
|
||||||
|
|
||||||
|
Transports::Transports ():
|
||||||
|
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service),
|
||||||
|
m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys
|
||||||
|
m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0),
|
||||||
|
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Transports::~Transports ()
|
||||||
|
{
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::Start ()
|
||||||
|
{
|
||||||
|
m_DHKeysPairSupplier.Start ();
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Thread = new std::thread (std::bind (&Transports::Run, this));
|
||||||
|
// create acceptors
|
||||||
|
auto& addresses = context.GetRouterInfo ().GetAddresses ();
|
||||||
|
for (auto address : addresses)
|
||||||
|
{
|
||||||
|
if (!m_NTCPServer)
|
||||||
|
{
|
||||||
|
m_NTCPServer = new NTCPServer ();
|
||||||
|
m_NTCPServer->Start ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ())
|
||||||
|
{
|
||||||
|
if (!m_SSUServer)
|
||||||
|
{
|
||||||
|
m_SSUServer = new SSUServer (address->port);
|
||||||
|
LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port);
|
||||||
|
m_SSUServer->Start ();
|
||||||
|
DetectExternalIP ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Transports: SSU server already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
|
||||||
|
m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::Stop ()
|
||||||
|
{
|
||||||
|
m_PeerCleanupTimer.cancel ();
|
||||||
|
m_Peers.clear ();
|
||||||
|
if (m_SSUServer)
|
||||||
|
{
|
||||||
|
m_SSUServer->Stop ();
|
||||||
|
delete m_SSUServer;
|
||||||
|
m_SSUServer = nullptr;
|
||||||
|
}
|
||||||
|
if (m_NTCPServer)
|
||||||
|
{
|
||||||
|
m_NTCPServer->Stop ();
|
||||||
|
delete m_NTCPServer;
|
||||||
|
m_NTCPServer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_DHKeysPairSupplier.Stop ();
|
||||||
|
m_IsRunning = false;
|
||||||
|
m_Service.stop ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::Run ()
|
||||||
|
{
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Service.run ();
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Transports: runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::UpdateBandwidth ()
|
||||||
|
{
|
||||||
|
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||||
|
if (m_LastBandwidthUpdateTime > 0)
|
||||||
|
{
|
||||||
|
auto delta = ts - m_LastBandwidthUpdateTime;
|
||||||
|
if (delta > 0)
|
||||||
|
{
|
||||||
|
m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second
|
||||||
|
m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_LastBandwidthUpdateTime = ts;
|
||||||
|
m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
|
||||||
|
m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Transports::IsBandwidthExceeded () const
|
||||||
|
{
|
||||||
|
auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes
|
||||||
|
auto bw = std::max (m_InBandwidth, m_OutBandwidth);
|
||||||
|
return bw > limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
|
||||||
|
{
|
||||||
|
m_Service.post (std::bind (&Transports::PostMessages, this, ident, msgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs)
|
||||||
|
{
|
||||||
|
if (ident == i2p::context.GetRouterInfo ().GetIdentHash ())
|
||||||
|
{
|
||||||
|
// we send it to ourself
|
||||||
|
for (auto it: msgs)
|
||||||
|
i2p::HandleI2NPMessage (it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto it = m_Peers.find (ident);
|
||||||
|
if (it == m_Peers.end ())
|
||||||
|
{
|
||||||
|
bool connected = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto r = netdb.FindRouter (ident);
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, { 0, r, {},
|
||||||
|
i2p::util::GetSecondsSinceEpoch (), {} })).first;
|
||||||
|
}
|
||||||
|
connected = ConnectToPeer (ident, it->second);
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ());
|
||||||
|
}
|
||||||
|
if (!connected) return;
|
||||||
|
}
|
||||||
|
if (!it->second.sessions.empty ())
|
||||||
|
it->second.sessions.front ()->SendI2NPMessages (msgs);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto it1: msgs)
|
||||||
|
it->second.delayedMessages.push_back (it1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer)
|
||||||
|
{
|
||||||
|
if (peer.router) // we have RI already
|
||||||
|
{
|
||||||
|
if (!peer.numAttempts) // NTCP
|
||||||
|
{
|
||||||
|
peer.numAttempts++;
|
||||||
|
auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ());
|
||||||
|
if (address && m_NTCPServer)
|
||||||
|
{
|
||||||
|
#if BOOST_VERSION >= 104900
|
||||||
|
if (!address->host.is_unspecified ()) // we have address now
|
||||||
|
#else
|
||||||
|
boost::system::error_code ecode;
|
||||||
|
address->host.to_string (ecode);
|
||||||
|
if (!ecode)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ())
|
||||||
|
{
|
||||||
|
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
|
||||||
|
m_NTCPServer->Connect (address->host, address->port, s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // we don't have address
|
||||||
|
{
|
||||||
|
if (address->addressString.length () > 0) // trying to resolve
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Transports: Resolving NTCP ", address->addressString);
|
||||||
|
NTCPResolve (address->addressString, ident);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU");
|
||||||
|
}
|
||||||
|
if (peer.numAttempts == 1)// SSU
|
||||||
|
{
|
||||||
|
peer.numAttempts++;
|
||||||
|
if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ()))
|
||||||
|
{
|
||||||
|
auto address = peer.router->GetSSUAddress (!context.SupportsV6 ());
|
||||||
|
#if BOOST_VERSION >= 104900
|
||||||
|
if (!address->host.is_unspecified ()) // we have address now
|
||||||
|
#else
|
||||||
|
boost::system::error_code ecode;
|
||||||
|
address->host.to_string (ecode);
|
||||||
|
if (!ecode)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
m_SSUServer->CreateSession (peer.router, address->host, address->port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else // we don't have address
|
||||||
|
{
|
||||||
|
if (address->addressString.length () > 0) // trying to resolve
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Transports: Resolving SSU ", address->addressString);
|
||||||
|
SSUResolve (address->addressString, ident);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogPrint (eLogError, "Transports: No NTCP or SSU addresses available");
|
||||||
|
peer.Done ();
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
m_Peers.erase (ident);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else // otherwise request RI
|
||||||
|
{
|
||||||
|
LogPrint (eLogInfo, "Transports: RouterInfo for ", ident.ToBase64 (), " not found, requested");
|
||||||
|
i2p::data::netdb.RequestDestination (ident, std::bind (
|
||||||
|
&Transports::RequestComplete, this, std::placeholders::_1, ident));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
m_Service.post (std::bind (&Transports::HandleRequestComplete, this, r, ident));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident)
|
||||||
|
{
|
||||||
|
auto it = m_Peers.find (ident);
|
||||||
|
if (it != m_Peers.end ())
|
||||||
|
{
|
||||||
|
if (r)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, Trying to connect");
|
||||||
|
it->second.router = r;
|
||||||
|
ConnectToPeer (ident, it->second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages");
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
m_Peers.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(m_Service);
|
||||||
|
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""),
|
||||||
|
std::bind (&Transports::HandleNTCPResolve, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, ident, resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||||
|
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
|
||||||
|
{
|
||||||
|
auto it1 = m_Peers.find (ident);
|
||||||
|
if (it1 != m_Peers.end ())
|
||||||
|
{
|
||||||
|
auto& peer = it1->second;
|
||||||
|
if (!ecode && peer.router)
|
||||||
|
{
|
||||||
|
while (it != boost::asio::ip::tcp::resolver::iterator())
|
||||||
|
{
|
||||||
|
auto address = (*it).endpoint ().address ();
|
||||||
|
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
|
||||||
|
if (address.is_v4 () || context.SupportsV6 ())
|
||||||
|
{
|
||||||
|
auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested
|
||||||
|
if (addr)
|
||||||
|
{
|
||||||
|
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
|
||||||
|
m_NTCPServer->Connect (address, addr->port, s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported");
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ());
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
m_Peers.erase (it1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident)
|
||||||
|
{
|
||||||
|
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(m_Service);
|
||||||
|
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""),
|
||||||
|
std::bind (&Transports::HandleSSUResolve, this,
|
||||||
|
std::placeholders::_1, std::placeholders::_2, ident, resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||||
|
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
|
||||||
|
{
|
||||||
|
auto it1 = m_Peers.find (ident);
|
||||||
|
if (it1 != m_Peers.end ())
|
||||||
|
{
|
||||||
|
auto& peer = it1->second;
|
||||||
|
if (!ecode && peer.router)
|
||||||
|
{
|
||||||
|
while (it != boost::asio::ip::tcp::resolver::iterator())
|
||||||
|
{
|
||||||
|
auto address = (*it).endpoint ().address ();
|
||||||
|
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
|
||||||
|
if (address.is_v4 () || context.SupportsV6 ())
|
||||||
|
{
|
||||||
|
auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested
|
||||||
|
if (addr)
|
||||||
|
{
|
||||||
|
m_SSUServer->CreateSession (peer.router, address, addr->port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported");
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ());
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
m_Peers.erase (it1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
|
||||||
|
{
|
||||||
|
if (!router) return;
|
||||||
|
m_Service.post (std::bind (&Transports::PostCloseSession, this, router));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
|
||||||
|
{
|
||||||
|
auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (router) : nullptr;
|
||||||
|
if (ssuSession) // try SSU first
|
||||||
|
{
|
||||||
|
m_SSUServer->DeleteSession (ssuSession);
|
||||||
|
LogPrint (eLogDebug, "Transports: SSU session closed");
|
||||||
|
}
|
||||||
|
// TODO: delete NTCP
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::DetectExternalIP ()
|
||||||
|
{
|
||||||
|
if (m_SSUServer)
|
||||||
|
{
|
||||||
|
i2p::context.SetStatus (eRouterStatusTesting);
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
auto router = i2p::data::netdb.GetRandomPeerTestRouter ();
|
||||||
|
if (router && router->IsSSU (!context.SupportsV6 ()))
|
||||||
|
m_SSUServer->CreateSession (router, true); // peer test
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if not peer test capable routers found pick any
|
||||||
|
router = i2p::data::netdb.GetRandomRouter ();
|
||||||
|
if (router && router->IsSSU ())
|
||||||
|
m_SSUServer->CreateSession (router); // no peer test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::PeerTest ()
|
||||||
|
{
|
||||||
|
if (m_SSUServer)
|
||||||
|
{
|
||||||
|
bool statusChanged = false;
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
auto router = i2p::data::netdb.GetRandomPeerTestRouter ();
|
||||||
|
if (router && router->IsSSU (!context.SupportsV6 ()))
|
||||||
|
{
|
||||||
|
if (!statusChanged)
|
||||||
|
{
|
||||||
|
statusChanged = true;
|
||||||
|
i2p::context.SetStatus (eRouterStatusTesting); // first time only
|
||||||
|
}
|
||||||
|
m_SSUServer->CreateSession (router, true); // peer test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::crypto::DHKeys> Transports::GetNextDHKeysPair ()
|
||||||
|
{
|
||||||
|
return m_DHKeysPairSupplier.Acquire ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair)
|
||||||
|
{
|
||||||
|
m_DHKeysPairSupplier.Return (pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::PeerConnected (std::shared_ptr<TransportSession> session)
|
||||||
|
{
|
||||||
|
m_Service.post([session, this]()
|
||||||
|
{
|
||||||
|
auto remoteIdentity = session->GetRemoteIdentity ();
|
||||||
|
if (!remoteIdentity) return;
|
||||||
|
auto ident = remoteIdentity->GetIdentHash ();
|
||||||
|
auto it = m_Peers.find (ident);
|
||||||
|
if (it != m_Peers.end ())
|
||||||
|
{
|
||||||
|
bool sendDatabaseStore = true;
|
||||||
|
if (it->second.delayedMessages.size () > 0)
|
||||||
|
{
|
||||||
|
// check if first message is our DatabaseStore (publishing)
|
||||||
|
auto firstMsg = it->second.delayedMessages[0];
|
||||||
|
if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore &&
|
||||||
|
i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ())
|
||||||
|
sendDatabaseStore = false; // we have it in the list already
|
||||||
|
}
|
||||||
|
if (sendDatabaseStore)
|
||||||
|
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () });
|
||||||
|
it->second.sessions.push_back (session);
|
||||||
|
session->SendI2NPMessages (it->second.delayedMessages);
|
||||||
|
it->second.delayedMessages.clear ();
|
||||||
|
}
|
||||||
|
else // incoming connection
|
||||||
|
{
|
||||||
|
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::PeerDisconnected (std::shared_ptr<TransportSession> session)
|
||||||
|
{
|
||||||
|
m_Service.post([session, this]()
|
||||||
|
{
|
||||||
|
auto remoteIdentity = session->GetRemoteIdentity ();
|
||||||
|
if (!remoteIdentity) return;
|
||||||
|
auto ident = remoteIdentity->GetIdentHash ();
|
||||||
|
auto it = m_Peers.find (ident);
|
||||||
|
if (it != m_Peers.end ())
|
||||||
|
{
|
||||||
|
it->second.sessions.remove (session);
|
||||||
|
if (it->second.sessions.empty ()) // TODO: why?
|
||||||
|
{
|
||||||
|
if (it->second.delayedMessages.size () > 0)
|
||||||
|
ConnectToPeer (ident, it->second);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
m_Peers.erase (it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Transports::IsConnected (const i2p::data::IdentHash& ident) const
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
auto it = m_Peers.find (ident);
|
||||||
|
return it != m_Peers.end ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode)
|
||||||
|
{
|
||||||
|
if (ecode != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (auto it = m_Peers.begin (); it != m_Peers.end (); )
|
||||||
|
{
|
||||||
|
if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds");
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
it = m_Peers.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
UpdateBandwidth (); // TODO: use separate timer(s) for it
|
||||||
|
if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test
|
||||||
|
DetectExternalIP ();
|
||||||
|
m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
|
||||||
|
m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer () const
|
||||||
|
{
|
||||||
|
if (!m_Peers.size ()) return nullptr;
|
||||||
|
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||||
|
auto it = m_Peers.begin ();
|
||||||
|
std::advance (it, rand () % m_Peers.size ());
|
||||||
|
return it != m_Peers.end () ? it->second.router : nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
155
Transports.h
Normal file
155
Transports.h
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
#ifndef TRANSPORTS_H__
|
||||||
|
#define TRANSPORTS_H__
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <atomic>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include "TransportSession.h"
|
||||||
|
#include "NTCPSession.h"
|
||||||
|
#include "SSU.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace transport
|
||||||
|
{
|
||||||
|
class DHKeysPairSupplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
DHKeysPairSupplier (int size);
|
||||||
|
~DHKeysPairSupplier ();
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
std::shared_ptr<i2p::crypto::DHKeys> Acquire ();
|
||||||
|
void Return (std::shared_ptr<i2p::crypto::DHKeys> pair);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void CreateDHKeysPairs (int num);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const int m_QueueSize;
|
||||||
|
std::queue<std::shared_ptr<i2p::crypto::DHKeys> > m_Queue;
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
std::condition_variable m_Acquired;
|
||||||
|
std::mutex m_AcquiredMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Peer
|
||||||
|
{
|
||||||
|
int numAttempts;
|
||||||
|
std::shared_ptr<const i2p::data::RouterInfo> router;
|
||||||
|
std::list<std::shared_ptr<TransportSession> > sessions;
|
||||||
|
uint64_t creationTime;
|
||||||
|
std::vector<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
|
||||||
|
|
||||||
|
void Done ()
|
||||||
|
{
|
||||||
|
for (auto it: sessions)
|
||||||
|
it->Done ();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds
|
||||||
|
class Transports
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
Transports ();
|
||||||
|
~Transports ();
|
||||||
|
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
boost::asio::io_service& GetService () { return m_Service; };
|
||||||
|
std::shared_ptr<i2p::crypto::DHKeys> GetNextDHKeysPair ();
|
||||||
|
void ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair);
|
||||||
|
|
||||||
|
void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
void SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs);
|
||||||
|
void CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
|
||||||
|
|
||||||
|
void PeerConnected (std::shared_ptr<TransportSession> session);
|
||||||
|
void PeerDisconnected (std::shared_ptr<TransportSession> session);
|
||||||
|
bool IsConnected (const i2p::data::IdentHash& ident) const;
|
||||||
|
|
||||||
|
void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; };
|
||||||
|
void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; };
|
||||||
|
uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; };
|
||||||
|
uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; };
|
||||||
|
uint32_t GetInBandwidth () const { return m_InBandwidth; };
|
||||||
|
uint32_t GetOutBandwidth () const { return m_OutBandwidth; };
|
||||||
|
bool IsBandwidthExceeded () const;
|
||||||
|
size_t GetNumPeers () const { return m_Peers.size (); };
|
||||||
|
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer () const;
|
||||||
|
|
||||||
|
void PeerTest ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident);
|
||||||
|
void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident);
|
||||||
|
void PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs);
|
||||||
|
void PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
|
||||||
|
bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer);
|
||||||
|
void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
|
||||||
|
|
||||||
|
void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident);
|
||||||
|
void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||||
|
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
|
||||||
|
void SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident);
|
||||||
|
void HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||||
|
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
|
||||||
|
|
||||||
|
void UpdateBandwidth ();
|
||||||
|
void DetectExternalIP ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
boost::asio::io_service m_Service;
|
||||||
|
boost::asio::io_service::work m_Work;
|
||||||
|
boost::asio::deadline_timer m_PeerCleanupTimer;
|
||||||
|
|
||||||
|
NTCPServer * m_NTCPServer;
|
||||||
|
SSUServer * m_SSUServer;
|
||||||
|
mutable std::mutex m_PeersMutex;
|
||||||
|
std::map<i2p::data::IdentHash, Peer> m_Peers;
|
||||||
|
|
||||||
|
DHKeysPairSupplier m_DHKeysPairSupplier;
|
||||||
|
|
||||||
|
std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes;
|
||||||
|
uint32_t m_InBandwidth, m_OutBandwidth; // bytes per second
|
||||||
|
uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes;
|
||||||
|
uint64_t m_LastBandwidthUpdateTime;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// for HTTP only
|
||||||
|
const NTCPServer * GetNTCPServer () const { return m_NTCPServer; };
|
||||||
|
const SSUServer * GetSSUServer () const { return m_SSUServer; };
|
||||||
|
const decltype(m_Peers)& GetPeers () const { return m_Peers; };
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Transports transports;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
866
Tunnel.cpp
Normal file
866
Tunnel.cpp
Normal file
@@ -0,0 +1,866 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "I2PEndian.h"
|
||||||
|
#include <thread>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "RouterContext.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Transports.h"
|
||||||
|
#include "NetDb.h"
|
||||||
|
#include "Tunnel.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
|
||||||
|
Tunnel::Tunnel (std::shared_ptr<const TunnelConfig> config):
|
||||||
|
TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()),
|
||||||
|
m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Tunnel::~Tunnel ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel)
|
||||||
|
{
|
||||||
|
auto numHops = m_Config->GetNumHops ();
|
||||||
|
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops;
|
||||||
|
auto msg = NewI2NPShortMessage ();
|
||||||
|
*msg->GetPayload () = numRecords;
|
||||||
|
msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1;
|
||||||
|
|
||||||
|
// shuffle records
|
||||||
|
std::vector<int> recordIndicies;
|
||||||
|
for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i);
|
||||||
|
std::random_shuffle (recordIndicies.begin(), recordIndicies.end());
|
||||||
|
|
||||||
|
// create real records
|
||||||
|
uint8_t * records = msg->GetPayload () + 1;
|
||||||
|
TunnelHopConfig * hop = m_Config->GetFirstHop ();
|
||||||
|
int i = 0;
|
||||||
|
while (hop)
|
||||||
|
{
|
||||||
|
uint32_t msgID;
|
||||||
|
if (hop->next) // we set replyMsgID for last hop only
|
||||||
|
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||||
|
else
|
||||||
|
msgID = replyMsgID;
|
||||||
|
int idx = recordIndicies[i];
|
||||||
|
hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID);
|
||||||
|
hop->recordIndex = idx;
|
||||||
|
i++;
|
||||||
|
hop = hop->next;
|
||||||
|
}
|
||||||
|
// fill up fake records with random data
|
||||||
|
for (int i = numHops; i < numRecords; i++)
|
||||||
|
{
|
||||||
|
int idx = recordIndicies[i];
|
||||||
|
RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt real records
|
||||||
|
i2p::crypto::CBCDecryption decryption;
|
||||||
|
hop = m_Config->GetLastHop ()->prev;
|
||||||
|
while (hop)
|
||||||
|
{
|
||||||
|
decryption.SetKey (hop->replyKey);
|
||||||
|
// decrypt records after current hop
|
||||||
|
TunnelHopConfig * hop1 = hop->next;
|
||||||
|
while (hop1)
|
||||||
|
{
|
||||||
|
decryption.SetIV (hop->replyIV);
|
||||||
|
uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
|
||||||
|
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
|
||||||
|
hop1 = hop1->next;
|
||||||
|
}
|
||||||
|
hop = hop->prev;
|
||||||
|
}
|
||||||
|
msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild);
|
||||||
|
|
||||||
|
// send message
|
||||||
|
if (outboundTunnel)
|
||||||
|
outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg);
|
||||||
|
else
|
||||||
|
i2p::transport::transports.SendMessage (GetNextIdentHash (), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records.");
|
||||||
|
|
||||||
|
i2p::crypto::CBCDecryption decryption;
|
||||||
|
TunnelHopConfig * hop = m_Config->GetLastHop ();
|
||||||
|
while (hop)
|
||||||
|
{
|
||||||
|
decryption.SetKey (hop->replyKey);
|
||||||
|
// decrypt records before and including current hop
|
||||||
|
TunnelHopConfig * hop1 = hop;
|
||||||
|
while (hop1)
|
||||||
|
{
|
||||||
|
auto idx = hop1->recordIndex;
|
||||||
|
if (idx >= 0 && idx < msg[0])
|
||||||
|
{
|
||||||
|
uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE;
|
||||||
|
decryption.SetIV (hop->replyIV);
|
||||||
|
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range");
|
||||||
|
hop1 = hop1->prev;
|
||||||
|
}
|
||||||
|
hop = hop->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool established = true;
|
||||||
|
hop = m_Config->GetFirstHop ();
|
||||||
|
while (hop)
|
||||||
|
{
|
||||||
|
const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
|
||||||
|
uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET];
|
||||||
|
LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret);
|
||||||
|
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
|
||||||
|
if (profile)
|
||||||
|
profile->TunnelBuildResponse (ret);
|
||||||
|
if (ret)
|
||||||
|
// if any of participants declined the tunnel is not established
|
||||||
|
established = false;
|
||||||
|
hop = hop->next;
|
||||||
|
}
|
||||||
|
if (established)
|
||||||
|
{
|
||||||
|
// create tunnel decryptions from layer and iv keys in reverse order
|
||||||
|
hop = m_Config->GetLastHop ();
|
||||||
|
while (hop)
|
||||||
|
{
|
||||||
|
auto tunnelHop = new TunnelHop;
|
||||||
|
tunnelHop->ident = hop->ident;
|
||||||
|
tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey);
|
||||||
|
m_Hops.push_back (std::unique_ptr<TunnelHop>(tunnelHop));
|
||||||
|
hop = hop->prev;
|
||||||
|
}
|
||||||
|
m_Config = nullptr;
|
||||||
|
}
|
||||||
|
if (established) m_State = eTunnelStateEstablished;
|
||||||
|
return established;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
|
||||||
|
{
|
||||||
|
const uint8_t * inPayload = in->GetPayload () + 4;
|
||||||
|
uint8_t * outPayload = out->GetPayload () + 4;
|
||||||
|
for (auto& it: m_Hops)
|
||||||
|
{
|
||||||
|
it->decryption.Decrypt (inPayload, outPayload);
|
||||||
|
inPayload = outPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > Tunnel::GetPeers () const
|
||||||
|
{
|
||||||
|
auto peers = GetInvertedPeers ();
|
||||||
|
std::reverse (peers.begin (), peers.end ());
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > Tunnel::GetInvertedPeers () const
|
||||||
|
{
|
||||||
|
// hops are in inverted order
|
||||||
|
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > ret;
|
||||||
|
for (auto& it: m_Hops)
|
||||||
|
ret.push_back (it->ident);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnel::PrintHops (std::stringstream& s) const
|
||||||
|
{
|
||||||
|
for (auto& it: m_Hops)
|
||||||
|
{
|
||||||
|
s << " ⇒ ";
|
||||||
|
s << i2p::data::GetIdentHashAbbreviation (it->ident->GetIdentHash ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
|
||||||
|
auto newMsg = CreateEmptyTunnelDataMsg ();
|
||||||
|
EncryptTunnelMsg (msg, newMsg);
|
||||||
|
newMsg->from = shared_from_this ();
|
||||||
|
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InboundTunnel::Print (std::stringstream& s) const
|
||||||
|
{
|
||||||
|
PrintHops (s);
|
||||||
|
s << " ⇒ " << GetTunnelID () << ":me";
|
||||||
|
}
|
||||||
|
|
||||||
|
ZeroHopsInboundTunnel::ZeroHopsInboundTunnel ():
|
||||||
|
InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
|
||||||
|
m_NumReceivedBytes (0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (msg)
|
||||||
|
{
|
||||||
|
m_NumReceivedBytes += msg->GetLength ();
|
||||||
|
HandleI2NPMessage (msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZeroHopsInboundTunnel::Print (std::stringstream& s) const
|
||||||
|
{
|
||||||
|
s << " ⇒ " << GetTunnelID () << ":me";
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
TunnelMessageBlock block;
|
||||||
|
if (gwHash)
|
||||||
|
{
|
||||||
|
block.hash = gwHash;
|
||||||
|
if (gwTunnel)
|
||||||
|
{
|
||||||
|
block.deliveryType = eDeliveryTypeTunnel;
|
||||||
|
block.tunnelID = gwTunnel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
block.deliveryType = eDeliveryTypeRouter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
block.deliveryType = eDeliveryTypeLocal;
|
||||||
|
block.data = msg;
|
||||||
|
|
||||||
|
SendTunnelDataMsg ({block});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutboundTunnel::SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||||
|
for (auto& it : msgs)
|
||||||
|
m_Gateway.PutTunnelDataMsg (it);
|
||||||
|
m_Gateway.SendBuffer ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutboundTunnel::Print (std::stringstream& s) const
|
||||||
|
{
|
||||||
|
s << GetTunnelID () << ":me";
|
||||||
|
PrintHops (s);
|
||||||
|
s << " ⇒ ";
|
||||||
|
}
|
||||||
|
|
||||||
|
ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel ():
|
||||||
|
OutboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
|
||||||
|
m_NumSentBytes (0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs)
|
||||||
|
{
|
||||||
|
for (auto& msg : msgs)
|
||||||
|
{
|
||||||
|
switch (msg.deliveryType)
|
||||||
|
{
|
||||||
|
case eDeliveryTypeLocal:
|
||||||
|
i2p::HandleI2NPMessage (msg.data);
|
||||||
|
break;
|
||||||
|
case eDeliveryTypeTunnel:
|
||||||
|
i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
|
||||||
|
break;
|
||||||
|
case eDeliveryTypeRouter:
|
||||||
|
i2p::transport::transports.SendMessage (msg.hash, msg.data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const
|
||||||
|
{
|
||||||
|
s << GetTunnelID () << ":me ⇒ ";
|
||||||
|
}
|
||||||
|
|
||||||
|
Tunnels tunnels;
|
||||||
|
|
||||||
|
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr),
|
||||||
|
m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Tunnels::~Tunnels ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TunnelBase> Tunnels::GetTunnel (uint32_t tunnelID)
|
||||||
|
{
|
||||||
|
auto it = m_Tunnels.find(tunnelID);
|
||||||
|
if (it != m_Tunnels.end ())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<InboundTunnel> Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID)
|
||||||
|
{
|
||||||
|
return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<OutboundTunnel> Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID)
|
||||||
|
{
|
||||||
|
return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class TTunnel>
|
||||||
|
std::shared_ptr<TTunnel> Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels)
|
||||||
|
{
|
||||||
|
auto it = pendingTunnels.find(replyMsgID);
|
||||||
|
if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending)
|
||||||
|
{
|
||||||
|
it->second->SetState (eTunnelStateBuildReplyReceived);
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<InboundTunnel> Tunnels::GetNextInboundTunnel ()
|
||||||
|
{
|
||||||
|
std::shared_ptr<InboundTunnel> tunnel;
|
||||||
|
size_t minReceived = 0;
|
||||||
|
for (auto it : m_InboundTunnels)
|
||||||
|
{
|
||||||
|
if (!it->IsEstablished ()) continue;
|
||||||
|
if (!tunnel || it->GetNumReceivedBytes () < minReceived)
|
||||||
|
{
|
||||||
|
tunnel = it;
|
||||||
|
minReceived = it->GetNumReceivedBytes ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel ()
|
||||||
|
{
|
||||||
|
if (!m_OutboundTunnels.size ()) return nullptr;
|
||||||
|
uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0;
|
||||||
|
std::shared_ptr<OutboundTunnel> tunnel;
|
||||||
|
for (auto it: m_OutboundTunnels)
|
||||||
|
{
|
||||||
|
if (it->IsEstablished ())
|
||||||
|
{
|
||||||
|
tunnel = it;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i > ind && tunnel) break;
|
||||||
|
}
|
||||||
|
return tunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (int numInboundHops,
|
||||||
|
int numOutboundHops, int numInboundTunnels, int numOutboundTunnels)
|
||||||
|
{
|
||||||
|
auto pool = std::make_shared<TunnelPool> (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels);
|
||||||
|
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||||
|
m_Pools.push_back (pool);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::DeleteTunnelPool (std::shared_ptr<TunnelPool> pool)
|
||||||
|
{
|
||||||
|
if (pool)
|
||||||
|
{
|
||||||
|
StopTunnelPool (pool);
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||||
|
m_Pools.remove (pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::StopTunnelPool (std::shared_ptr<TunnelPool> pool)
|
||||||
|
{
|
||||||
|
if (pool)
|
||||||
|
{
|
||||||
|
pool->SetActive (false);
|
||||||
|
pool->DetachTunnels ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
|
||||||
|
{
|
||||||
|
if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second)
|
||||||
|
m_TransitTunnels.push_back (tunnel);
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::Start ()
|
||||||
|
{
|
||||||
|
m_IsRunning = true;
|
||||||
|
m_Thread = new std::thread (std::bind (&Tunnels::Run, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::Stop ()
|
||||||
|
{
|
||||||
|
m_IsRunning = false;
|
||||||
|
m_Queue.WakeUp ();
|
||||||
|
if (m_Thread)
|
||||||
|
{
|
||||||
|
m_Thread->join ();
|
||||||
|
delete m_Thread;
|
||||||
|
m_Thread = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::Run ()
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready
|
||||||
|
|
||||||
|
uint64_t lastTs = 0;
|
||||||
|
while (m_IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
|
||||||
|
if (msg)
|
||||||
|
{
|
||||||
|
uint32_t prevTunnelID = 0, tunnelID = 0;
|
||||||
|
std::shared_ptr<TunnelBase> prevTunnel;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
std::shared_ptr<TunnelBase> tunnel;
|
||||||
|
uint8_t typeID = msg->GetTypeID ();
|
||||||
|
switch (typeID)
|
||||||
|
{
|
||||||
|
case eI2NPTunnelData:
|
||||||
|
case eI2NPTunnelGateway:
|
||||||
|
{
|
||||||
|
tunnelID = bufbe32toh (msg->GetPayload ());
|
||||||
|
if (tunnelID == prevTunnelID)
|
||||||
|
tunnel = prevTunnel;
|
||||||
|
else if (prevTunnel)
|
||||||
|
prevTunnel->FlushTunnelDataMsgs ();
|
||||||
|
|
||||||
|
if (!tunnel)
|
||||||
|
tunnel = GetTunnel (tunnelID);
|
||||||
|
if (tunnel)
|
||||||
|
{
|
||||||
|
if (typeID == eI2NPTunnelData)
|
||||||
|
tunnel->HandleTunnelDataMsg (msg);
|
||||||
|
else // tunnel gateway assumed
|
||||||
|
HandleTunnelGatewayMsg (tunnel, msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eI2NPVariableTunnelBuild:
|
||||||
|
case eI2NPVariableTunnelBuildReply:
|
||||||
|
case eI2NPTunnelBuild:
|
||||||
|
case eI2NPTunnelBuildReply:
|
||||||
|
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogWarning, "Tunnel: unexpected messsage type ", (int) typeID);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = m_Queue.Get ();
|
||||||
|
if (msg)
|
||||||
|
{
|
||||||
|
prevTunnelID = tunnelID;
|
||||||
|
prevTunnel = tunnel;
|
||||||
|
}
|
||||||
|
else if (tunnel)
|
||||||
|
tunnel->FlushTunnelDataMsgs ();
|
||||||
|
}
|
||||||
|
while (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
if (ts - lastTs >= 15) // manage tunnels every 15 seconds
|
||||||
|
{
|
||||||
|
ManageTunnels ();
|
||||||
|
lastTs = ts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception& ex)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (!tunnel)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Tunnel: missing tunnel for gateway");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint8_t * payload = msg->GetPayload ();
|
||||||
|
uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET);
|
||||||
|
// we make payload as new I2NP message to send
|
||||||
|
msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
|
||||||
|
if (msg->offset + len > msg->len)
|
||||||
|
{
|
||||||
|
LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msg->len = msg->offset + len;
|
||||||
|
auto typeID = msg->GetTypeID ();
|
||||||
|
LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID);
|
||||||
|
|
||||||
|
if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply)
|
||||||
|
// transit DatabaseStore my contain new/updated RI
|
||||||
|
// or DatabaseSearchReply with new routers
|
||||||
|
i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg));
|
||||||
|
tunnel->SendTunnelDataMsg (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::ManageTunnels ()
|
||||||
|
{
|
||||||
|
ManagePendingTunnels ();
|
||||||
|
ManageInboundTunnels ();
|
||||||
|
ManageOutboundTunnels ();
|
||||||
|
ManageTransitTunnels ();
|
||||||
|
ManageTunnelPools ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::ManagePendingTunnels ()
|
||||||
|
{
|
||||||
|
ManagePendingTunnels (m_PendingInboundTunnels);
|
||||||
|
ManagePendingTunnels (m_PendingOutboundTunnels);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class PendingTunnels>
|
||||||
|
void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels)
|
||||||
|
{
|
||||||
|
// check pending tunnel. delete failed or timeout
|
||||||
|
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();)
|
||||||
|
{
|
||||||
|
auto tunnel = it->second;
|
||||||
|
switch (tunnel->GetState ())
|
||||||
|
{
|
||||||
|
case eTunnelStatePending:
|
||||||
|
if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " timeout, deleted");
|
||||||
|
// update stats
|
||||||
|
auto config = tunnel->GetTunnelConfig ();
|
||||||
|
if (config)
|
||||||
|
{
|
||||||
|
auto hop = config->GetFirstHop ();
|
||||||
|
while (hop)
|
||||||
|
{
|
||||||
|
if (hop->ident)
|
||||||
|
{
|
||||||
|
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
|
||||||
|
if (profile)
|
||||||
|
profile->TunnelNonReplied ();
|
||||||
|
}
|
||||||
|
hop = hop->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete
|
||||||
|
it = pendingTunnels.erase (it);
|
||||||
|
m_NumFailedTunnelCreations++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
break;
|
||||||
|
case eTunnelStateBuildFailed:
|
||||||
|
LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " failed, deleted");
|
||||||
|
it = pendingTunnels.erase (it);
|
||||||
|
m_NumFailedTunnelCreations++;
|
||||||
|
break;
|
||||||
|
case eTunnelStateBuildReplyReceived:
|
||||||
|
// intermediate state, will be either established of build failed
|
||||||
|
it++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// success
|
||||||
|
it = pendingTunnels.erase (it);
|
||||||
|
m_NumSuccesiveTunnelCreations++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::ManageOutboundTunnels ()
|
||||||
|
{
|
||||||
|
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
{
|
||||||
|
for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
|
||||||
|
{
|
||||||
|
auto tunnel = *it;
|
||||||
|
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired");
|
||||||
|
auto pool = tunnel->GetTunnelPool ();
|
||||||
|
if (pool)
|
||||||
|
pool->TunnelExpired (tunnel);
|
||||||
|
// we don't have outbound tunnels in m_Tunnels
|
||||||
|
it = m_OutboundTunnels.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (tunnel->IsEstablished ())
|
||||||
|
{
|
||||||
|
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
tunnel->SetIsRecreated ();
|
||||||
|
auto pool = tunnel->GetTunnelPool ();
|
||||||
|
if (pool)
|
||||||
|
pool->RecreateOutboundTunnel (tunnel);
|
||||||
|
}
|
||||||
|
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||||
|
tunnel->SetState (eTunnelStateExpiring);
|
||||||
|
}
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_OutboundTunnels.size () < 5)
|
||||||
|
{
|
||||||
|
// trying to create one more oubound tunnel
|
||||||
|
auto inboundTunnel = GetNextInboundTunnel ();
|
||||||
|
auto router = i2p::data::netdb.GetRandomRouter ();
|
||||||
|
if (!inboundTunnel || !router) return;
|
||||||
|
LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel");
|
||||||
|
CreateTunnel<OutboundTunnel> (
|
||||||
|
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () },
|
||||||
|
inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::ManageInboundTunnels ()
|
||||||
|
{
|
||||||
|
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
{
|
||||||
|
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
|
||||||
|
{
|
||||||
|
auto tunnel = *it;
|
||||||
|
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired");
|
||||||
|
auto pool = tunnel->GetTunnelPool ();
|
||||||
|
if (pool)
|
||||||
|
pool->TunnelExpired (tunnel);
|
||||||
|
m_Tunnels.erase (tunnel->GetTunnelID ());
|
||||||
|
it = m_InboundTunnels.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (tunnel->IsEstablished ())
|
||||||
|
{
|
||||||
|
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
tunnel->SetIsRecreated ();
|
||||||
|
auto pool = tunnel->GetTunnelPool ();
|
||||||
|
if (pool)
|
||||||
|
pool->RecreateInboundTunnel (tunnel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||||
|
tunnel->SetState (eTunnelStateExpiring);
|
||||||
|
}
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_InboundTunnels.empty ())
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel");
|
||||||
|
CreateZeroHopsInboundTunnel ();
|
||||||
|
CreateZeroHopsOutboundTunnel ();
|
||||||
|
if (!m_ExploratoryPool)
|
||||||
|
{
|
||||||
|
m_ExploratoryPool = CreateTunnelPool (2, 2, 5, 5); // 2-hop exploratory, 5 tunnels
|
||||||
|
m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5)
|
||||||
|
{
|
||||||
|
// trying to create one more inbound tunnel
|
||||||
|
auto router = i2p::data::netdb.GetRandomRouter ();
|
||||||
|
if (!router) {
|
||||||
|
LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel");
|
||||||
|
CreateTunnel<InboundTunnel> (
|
||||||
|
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::ManageTransitTunnels ()
|
||||||
|
{
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
|
||||||
|
{
|
||||||
|
auto tunnel = *it;
|
||||||
|
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||||
|
{
|
||||||
|
LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired");
|
||||||
|
m_Tunnels.erase (tunnel->GetTunnelID ());
|
||||||
|
it = m_TransitTunnels.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::ManageTunnelPools ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||||
|
for (auto it: m_Pools)
|
||||||
|
{
|
||||||
|
auto pool = it;
|
||||||
|
if (pool && pool->IsActive ())
|
||||||
|
{
|
||||||
|
pool->CreateTunnels ();
|
||||||
|
pool->TestTunnels ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::PostTunnelData (std::shared_ptr<I2NPMessage> msg)
|
||||||
|
{
|
||||||
|
if (msg) m_Queue.Put (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
|
||||||
|
{
|
||||||
|
m_Queue.Put (msgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class TTunnel>
|
||||||
|
std::shared_ptr<TTunnel> Tunnels::CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel)
|
||||||
|
{
|
||||||
|
auto newTunnel = std::make_shared<TTunnel> (config);
|
||||||
|
uint32_t replyMsgID;
|
||||||
|
RAND_bytes ((uint8_t *)&replyMsgID, 4);
|
||||||
|
AddPendingTunnel (replyMsgID, newTunnel);
|
||||||
|
newTunnel->Build (replyMsgID, outboundTunnel);
|
||||||
|
return newTunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel)
|
||||||
|
{
|
||||||
|
m_PendingInboundTunnels[replyMsgID] = tunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel)
|
||||||
|
{
|
||||||
|
m_PendingOutboundTunnels[replyMsgID] = tunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel)
|
||||||
|
{
|
||||||
|
// we don't need to insert it to m_Tunnels
|
||||||
|
m_OutboundTunnels.push_back (newTunnel);
|
||||||
|
auto pool = newTunnel->GetTunnelPool ();
|
||||||
|
if (pool && pool->IsActive ())
|
||||||
|
pool->TunnelCreated (newTunnel);
|
||||||
|
else
|
||||||
|
newTunnel->SetTunnelPool (nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel)
|
||||||
|
{
|
||||||
|
if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second)
|
||||||
|
{
|
||||||
|
m_InboundTunnels.push_back (newTunnel);
|
||||||
|
auto pool = newTunnel->GetTunnelPool ();
|
||||||
|
if (!pool)
|
||||||
|
{
|
||||||
|
// build symmetric outbound tunnel
|
||||||
|
CreateTunnel<OutboundTunnel> (std::make_shared<TunnelConfig>(newTunnel->GetInvertedPeers (),
|
||||||
|
newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()),
|
||||||
|
GetNextOutboundTunnel ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (pool->IsActive ())
|
||||||
|
pool->TunnelCreated (newTunnel);
|
||||||
|
else
|
||||||
|
newTunnel->SetTunnelPool (nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Tunnels::CreateZeroHopsInboundTunnel ()
|
||||||
|
{
|
||||||
|
auto inboundTunnel = std::make_shared<ZeroHopsInboundTunnel> ();
|
||||||
|
inboundTunnel->SetState (eTunnelStateEstablished);
|
||||||
|
m_InboundTunnels.push_back (inboundTunnel);
|
||||||
|
m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tunnels::CreateZeroHopsOutboundTunnel ()
|
||||||
|
{
|
||||||
|
auto outboundTunnel = std::make_shared<ZeroHopsOutboundTunnel> ();
|
||||||
|
outboundTunnel->SetState (eTunnelStateEstablished);
|
||||||
|
m_OutboundTunnels.push_back (outboundTunnel);
|
||||||
|
// we don't insert into m_Tunnels
|
||||||
|
}
|
||||||
|
|
||||||
|
int Tunnels::GetTransitTunnelsExpirationTimeout ()
|
||||||
|
{
|
||||||
|
int timeout = 0;
|
||||||
|
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||||
|
// TODO: possible race condition with I2PControl
|
||||||
|
for (auto it: m_TransitTunnels)
|
||||||
|
{
|
||||||
|
int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
|
||||||
|
if (t > timeout) timeout = t;
|
||||||
|
}
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Tunnels::CountTransitTunnels() const
|
||||||
|
{
|
||||||
|
// TODO: locking
|
||||||
|
return m_TransitTunnels.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Tunnels::CountInboundTunnels() const
|
||||||
|
{
|
||||||
|
// TODO: locking
|
||||||
|
return m_InboundTunnels.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Tunnels::CountOutboundTunnels() const
|
||||||
|
{
|
||||||
|
// TODO: locking
|
||||||
|
return m_OutboundTunnels.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
251
Tunnel.h
Normal file
251
Tunnel.h
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
#ifndef TUNNEL_H__
|
||||||
|
#define TUNNEL_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <list>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <memory>
|
||||||
|
#include "Queue.h"
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "TunnelConfig.h"
|
||||||
|
#include "TunnelPool.h"
|
||||||
|
#include "TransitTunnel.h"
|
||||||
|
#include "TunnelEndpoint.h"
|
||||||
|
#include "TunnelGateway.h"
|
||||||
|
#include "TunnelBase.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes
|
||||||
|
const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute
|
||||||
|
const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes
|
||||||
|
const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds
|
||||||
|
const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message
|
||||||
|
|
||||||
|
enum TunnelState
|
||||||
|
{
|
||||||
|
eTunnelStatePending,
|
||||||
|
eTunnelStateBuildReplyReceived,
|
||||||
|
eTunnelStateBuildFailed,
|
||||||
|
eTunnelStateEstablished,
|
||||||
|
eTunnelStateTestFailed,
|
||||||
|
eTunnelStateFailed,
|
||||||
|
eTunnelStateExpiring
|
||||||
|
};
|
||||||
|
|
||||||
|
class OutboundTunnel;
|
||||||
|
class InboundTunnel;
|
||||||
|
class Tunnel: public TunnelBase
|
||||||
|
{
|
||||||
|
struct TunnelHop
|
||||||
|
{
|
||||||
|
std::shared_ptr<const i2p::data::IdentityEx> ident;
|
||||||
|
i2p::crypto::TunnelDecryption decryption;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Tunnel (std::shared_ptr<const TunnelConfig> config);
|
||||||
|
~Tunnel ();
|
||||||
|
|
||||||
|
void Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
|
||||||
|
|
||||||
|
std::shared_ptr<const TunnelConfig> GetTunnelConfig () const { return m_Config; }
|
||||||
|
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetPeers () const;
|
||||||
|
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetInvertedPeers () const;
|
||||||
|
TunnelState GetState () const { return m_State; };
|
||||||
|
void SetState (TunnelState state) { m_State = state; };
|
||||||
|
bool IsEstablished () const { return m_State == eTunnelStateEstablished; };
|
||||||
|
bool IsFailed () const { return m_State == eTunnelStateFailed; };
|
||||||
|
bool IsRecreated () const { return m_IsRecreated; };
|
||||||
|
void SetIsRecreated () { m_IsRecreated = true; };
|
||||||
|
|
||||||
|
std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
|
||||||
|
void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; };
|
||||||
|
|
||||||
|
bool HandleTunnelBuildResponse (uint8_t * msg, size_t len);
|
||||||
|
|
||||||
|
virtual void Print (std::stringstream& s) const {};
|
||||||
|
|
||||||
|
// implements TunnelBase
|
||||||
|
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void PrintHops (std::stringstream& s) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<const TunnelConfig> m_Config;
|
||||||
|
std::vector<std::unique_ptr<TunnelHop> > m_Hops;
|
||||||
|
std::shared_ptr<TunnelPool> m_Pool; // pool, tunnel belongs to, or null
|
||||||
|
TunnelState m_State;
|
||||||
|
bool m_IsRecreated;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OutboundTunnel: public Tunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
OutboundTunnel (std::shared_ptr<const TunnelConfig> config):
|
||||||
|
Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {};
|
||||||
|
|
||||||
|
void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
virtual void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
|
||||||
|
const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; };
|
||||||
|
virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); };
|
||||||
|
void Print (std::stringstream& s) const;
|
||||||
|
|
||||||
|
// implements TunnelBase
|
||||||
|
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::mutex m_SendMutex;
|
||||||
|
TunnelGateway m_Gateway;
|
||||||
|
i2p::data::IdentHash m_EndpointIdentHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InboundTunnel: public Tunnel, public std::enable_shared_from_this<InboundTunnel>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
InboundTunnel (std::shared_ptr<const TunnelConfig> config): Tunnel (config), m_Endpoint (true) {};
|
||||||
|
void HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||||
|
virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
|
||||||
|
void Print (std::stringstream& s) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
TunnelEndpoint m_Endpoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ZeroHopsInboundTunnel: public InboundTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ZeroHopsInboundTunnel ();
|
||||||
|
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||||
|
void Print (std::stringstream& s) const;
|
||||||
|
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
size_t m_NumReceivedBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ZeroHopsOutboundTunnel: public OutboundTunnel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
ZeroHopsOutboundTunnel ();
|
||||||
|
void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs);
|
||||||
|
void Print (std::stringstream& s) const;
|
||||||
|
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
size_t m_NumSentBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Tunnels
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
Tunnels ();
|
||||||
|
~Tunnels ();
|
||||||
|
void Start ();
|
||||||
|
void Stop ();
|
||||||
|
|
||||||
|
std::shared_ptr<InboundTunnel> GetPendingInboundTunnel (uint32_t replyMsgID);
|
||||||
|
std::shared_ptr<OutboundTunnel> GetPendingOutboundTunnel (uint32_t replyMsgID);
|
||||||
|
std::shared_ptr<InboundTunnel> GetNextInboundTunnel ();
|
||||||
|
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel ();
|
||||||
|
std::shared_ptr<TunnelPool> GetExploratoryPool () const { return m_ExploratoryPool; };
|
||||||
|
std::shared_ptr<TunnelBase> GetTunnel (uint32_t tunnelID);
|
||||||
|
int GetTransitTunnelsExpirationTimeout ();
|
||||||
|
void AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
|
||||||
|
void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel);
|
||||||
|
void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel);
|
||||||
|
void PostTunnelData (std::shared_ptr<I2NPMessage> msg);
|
||||||
|
void PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||||
|
template<class TTunnel>
|
||||||
|
std::shared_ptr<TTunnel> CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
|
||||||
|
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel);
|
||||||
|
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel);
|
||||||
|
std::shared_ptr<TunnelPool> CreateTunnelPool (int numInboundHops,
|
||||||
|
int numOuboundHops, int numInboundTunnels, int numOutboundTunnels);
|
||||||
|
void DeleteTunnelPool (std::shared_ptr<TunnelPool> pool);
|
||||||
|
void StopTunnelPool (std::shared_ptr<TunnelPool> pool);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template<class TTunnel>
|
||||||
|
std::shared_ptr<TTunnel> GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels);
|
||||||
|
|
||||||
|
void HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg);
|
||||||
|
|
||||||
|
void Run ();
|
||||||
|
void ManageTunnels ();
|
||||||
|
void ManageOutboundTunnels ();
|
||||||
|
void ManageInboundTunnels ();
|
||||||
|
void ManageTransitTunnels ();
|
||||||
|
void ManagePendingTunnels ();
|
||||||
|
template<class PendingTunnels>
|
||||||
|
void ManagePendingTunnels (PendingTunnels& pendingTunnels);
|
||||||
|
void ManageTunnelPools ();
|
||||||
|
|
||||||
|
void CreateZeroHopsInboundTunnel ();
|
||||||
|
void CreateZeroHopsOutboundTunnel ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool m_IsRunning;
|
||||||
|
std::thread * m_Thread;
|
||||||
|
std::map<uint32_t, std::shared_ptr<InboundTunnel> > m_PendingInboundTunnels; // by replyMsgID
|
||||||
|
std::map<uint32_t, std::shared_ptr<OutboundTunnel> > m_PendingOutboundTunnels; // by replyMsgID
|
||||||
|
std::list<std::shared_ptr<InboundTunnel> > m_InboundTunnels;
|
||||||
|
std::list<std::shared_ptr<OutboundTunnel> > m_OutboundTunnels;
|
||||||
|
std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
|
||||||
|
std::unordered_map<uint32_t, std::shared_ptr<TunnelBase> > m_Tunnels; // tunnelID->tunnel known by this id
|
||||||
|
std::mutex m_PoolsMutex;
|
||||||
|
std::list<std::shared_ptr<TunnelPool>> m_Pools;
|
||||||
|
std::shared_ptr<TunnelPool> m_ExploratoryPool;
|
||||||
|
i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_Queue;
|
||||||
|
|
||||||
|
// some stats
|
||||||
|
int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// for HTTP only
|
||||||
|
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
|
||||||
|
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
|
||||||
|
const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; };
|
||||||
|
|
||||||
|
size_t CountTransitTunnels() const;
|
||||||
|
size_t CountInboundTunnels() const;
|
||||||
|
size_t CountOutboundTunnels() const;
|
||||||
|
|
||||||
|
int GetQueueSize () { return m_Queue.GetSize (); };
|
||||||
|
int GetTunnelCreationSuccessRate () const // in percents
|
||||||
|
{
|
||||||
|
int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations;
|
||||||
|
return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Tunnels tunnels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
72
TunnelBase.h
Normal file
72
TunnelBase.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#ifndef TUNNEL_BASE_H__
|
||||||
|
#define TUNNEL_BASE_H__
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <memory>
|
||||||
|
#include "Timestamp.h"
|
||||||
|
#include "I2NPProtocol.h"
|
||||||
|
#include "Identity.h"
|
||||||
|
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace tunnel
|
||||||
|
{
|
||||||
|
const size_t TUNNEL_DATA_MSG_SIZE = 1028;
|
||||||
|
const size_t TUNNEL_DATA_ENCRYPTED_SIZE = 1008;
|
||||||
|
const size_t TUNNEL_DATA_MAX_PAYLOAD_SIZE = 1003;
|
||||||
|
|
||||||
|
enum TunnelDeliveryType
|
||||||
|
{
|
||||||
|
eDeliveryTypeLocal = 0,
|
||||||
|
eDeliveryTypeTunnel = 1,
|
||||||
|
eDeliveryTypeRouter = 2
|
||||||
|
};
|
||||||
|
struct TunnelMessageBlock
|
||||||
|
{
|
||||||
|
TunnelDeliveryType deliveryType;
|
||||||
|
i2p::data::IdentHash hash;
|
||||||
|
uint32_t tunnelID;
|
||||||
|
std::shared_ptr<I2NPMessage> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TunnelBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent):
|
||||||
|
m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent),
|
||||||
|
m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {};
|
||||||
|
virtual ~TunnelBase () {};
|
||||||
|
|
||||||
|
virtual void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg) = 0;
|
||||||
|
virtual void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) = 0;
|
||||||
|
virtual void FlushTunnelDataMsgs () {};
|
||||||
|
virtual void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) = 0;
|
||||||
|
uint32_t GetNextTunnelID () const { return m_NextTunnelID; };
|
||||||
|
const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; };
|
||||||
|
virtual uint32_t GetTunnelID () const { return m_TunnelID; }; // as known at our side
|
||||||
|
|
||||||
|
uint32_t GetCreationTime () const { return m_CreationTime; };
|
||||||
|
void SetCreationTime (uint32_t t) { m_CreationTime = t; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint32_t m_TunnelID, m_NextTunnelID;
|
||||||
|
i2p::data::IdentHash m_NextIdent;
|
||||||
|
uint32_t m_CreationTime; // seconds since epoch
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TunnelCreationTimeCmp
|
||||||
|
{
|
||||||
|
bool operator() (std::shared_ptr<const TunnelBase> t1, std::shared_ptr<const TunnelBase> t2) const
|
||||||
|
{
|
||||||
|
if (t1->GetCreationTime () != t2->GetCreationTime ())
|
||||||
|
return t1->GetCreationTime () > t2->GetCreationTime ();
|
||||||
|
else
|
||||||
|
return t1 < t2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user