Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c391092ea6 | ||
|
|
b3a29bec1b | ||
|
|
b055c9a4a2 | ||
|
|
3daceac668 | ||
|
|
4611e8825a | ||
|
|
00e4729533 | ||
|
|
517a2e0e0a | ||
|
|
bb8c7c599c | ||
|
|
0d0fc39236 | ||
|
|
302a5a4efc | ||
|
|
3b29e8e6f2 | ||
|
|
a1dff70ba2 | ||
|
|
12671be260 | ||
|
|
9a4adccec6 | ||
|
|
f90390974a | ||
|
|
f598ae297b | ||
|
|
4b0fe502f0 | ||
|
|
e4295a2da9 | ||
|
|
216a0fe4fc | ||
|
|
80770d39e5 | ||
|
|
beb15fa32c | ||
|
|
0adf7626d0 | ||
|
|
9c17fe259c | ||
|
|
ecab4b4716 | ||
|
|
7660ad761a | ||
|
|
c8ff2de3b3 | ||
|
|
24a2e43547 | ||
|
|
a9d212c888 | ||
|
|
c1b1276aaf | ||
|
|
370212609c | ||
|
|
281937cf82 | ||
|
|
d11b6e8075 | ||
|
|
7709c6d0bd | ||
|
|
ef3e478506 | ||
|
|
93eef26d0b | ||
|
|
14d78cf08c | ||
|
|
e96a63f550 | ||
|
|
7a5b69f850 | ||
|
|
a71d2a1682 | ||
|
|
257b1a5353 | ||
|
|
b45ac75735 | ||
|
|
c7c669fd11 | ||
|
|
ba266ef8d3 | ||
|
|
f434fad3be | ||
|
|
bab758ebf9 | ||
|
|
800f4663b6 | ||
|
|
b1b62afa90 | ||
|
|
6b90ef7078 | ||
|
|
2ed4b45c24 | ||
|
|
47ba9748b4 | ||
|
|
077ad4208f | ||
|
|
c19edec364 | ||
|
|
0c2aa494ae | ||
|
|
73e0a6bb0a | ||
|
|
abf1db0045 | ||
|
|
b40b78ece0 | ||
|
|
3ade796e8d | ||
|
|
7c2e808924 | ||
|
|
7d2227c5a4 | ||
|
|
8a517d5c7c | ||
|
|
48658f011f | ||
|
|
4ebb13fa94 | ||
|
|
ff9a6b91a4 | ||
|
|
86fc37a968 | ||
|
|
37ae169323 | ||
|
|
1ce0df4e94 | ||
|
|
db5ba61915 | ||
|
|
db120fb9be | ||
|
|
ea6e171a7f | ||
|
|
246cb76e50 | ||
|
|
ecf4a82b99 | ||
|
|
357f67255e | ||
|
|
7015ab8717 | ||
|
|
67195bc9f3 | ||
|
|
ce18509aa7 | ||
|
|
cbaa92dcb2 | ||
|
|
f09d87c112 | ||
|
|
7a8bba4d95 | ||
|
|
e6e414a017 | ||
|
|
81df06b3f4 | ||
|
|
5401037743 | ||
|
|
9d9768ee6f | ||
|
|
3043217b2c | ||
|
|
afcb84d42c | ||
|
|
d5aad8f8d2 | ||
|
|
c46febd7df | ||
|
|
f880da4827 | ||
|
|
85c9cf43f7 | ||
|
|
ce90d2389b | ||
|
|
d8cfbc4e08 | ||
|
|
88fad251aa | ||
|
|
1a1d8b7c74 | ||
|
|
a4ef96690e | ||
|
|
f28ad350bd | ||
|
|
0afc3db383 | ||
|
|
ca1cce3232 | ||
|
|
f9228eeb46 | ||
|
|
0c077b8bb1 | ||
|
|
0e9991f7d4 | ||
|
|
41482aff01 | ||
|
|
791fc9d6b1 | ||
|
|
776ccac00a | ||
|
|
1539009362 | ||
|
|
9542b77a34 | ||
|
|
b709cdeab5 | ||
|
|
e2f936fde3 | ||
|
|
a4ca389f2e | ||
|
|
c973808b20 | ||
|
|
e12dbb4605 | ||
|
|
c341399498 | ||
|
|
d1ba4ab7f4 | ||
|
|
85d4b1f137 | ||
|
|
b9792f8f96 | ||
|
|
e0cb9cf24d | ||
|
|
4e41d4d451 | ||
|
|
059cc65b55 | ||
|
|
d434b50dfc | ||
|
|
96f69583e7 | ||
|
|
ce25f58639 | ||
|
|
367b73e6e3 | ||
|
|
98a7d3fc99 | ||
|
|
6fd0f121a0 | ||
|
|
68d6c262d1 | ||
|
|
1d9ef76080 | ||
|
|
b51b1adfcd | ||
|
|
ab902b45aa | ||
|
|
ed14561afd | ||
|
|
86d2db5cc2 | ||
|
|
700851cdd5 | ||
|
|
c3e1f5a6c6 | ||
|
|
9bf18b1761 | ||
|
|
1374e4c4de | ||
|
|
0ef57a102f | ||
|
|
1c7954a43d | ||
|
|
be1dc8ef96 | ||
|
|
18fc6ccc03 | ||
|
|
7ec46983aa | ||
|
|
eddcf93942 | ||
|
|
42807f7b8a | ||
|
|
0b521272ae | ||
|
|
ecd93bbfe4 | ||
|
|
a6f07fbaac | ||
|
|
f87876c1c3 | ||
|
|
ec3de1a072 | ||
|
|
c1d2c159f0 | ||
|
|
9f86e2ba43 | ||
|
|
d0be36dc9b | ||
|
|
af604a1477 | ||
|
|
c981cff450 | ||
|
|
ab1033c26c | ||
|
|
01706df7c8 | ||
|
|
827a7c1cf1 | ||
|
|
d14d9933b4 | ||
|
|
af946893f0 | ||
|
|
245ae5e987 | ||
|
|
29f3d711c6 | ||
|
|
3909936cc8 | ||
|
|
0602c91c54 | ||
|
|
a7bdb02a2b | ||
|
|
2cfaf5a119 | ||
|
|
20a48f05b3 | ||
|
|
4e70fb7f9d | ||
|
|
1f438dbf71 | ||
|
|
619d0e2bdd | ||
|
|
ec420c66d4 | ||
|
|
ac1cf633c7 | ||
|
|
6f25d2170d | ||
|
|
295a2ced32 | ||
|
|
cbb2ad018d | ||
|
|
7e15eabe65 | ||
|
|
eb9b5b535f | ||
|
|
a13351a9eb | ||
|
|
74fdf6beec | ||
|
|
cd7c88e5a5 | ||
|
|
a22b88ed89 | ||
|
|
5eb99fe5ef | ||
|
|
85e465cfa4 | ||
|
|
e101dde62e | ||
|
|
1980ffea30 | ||
|
|
0935e0a13d | ||
|
|
b2b68119fc | ||
|
|
4420057d4f | ||
|
|
bd2b8df7c5 | ||
|
|
068bbe0837 | ||
|
|
7e0e1fbaad | ||
|
|
9723476392 | ||
|
|
9dee2f2230 | ||
|
|
175f38414e | ||
|
|
8bbf8231d2 | ||
|
|
2fbbb802bd | ||
|
|
cb65a80dce | ||
|
|
3a9bcbcf6b | ||
|
|
e38018ab1e | ||
|
|
044c6474b2 | ||
|
|
5f42a4d95e | ||
|
|
6d3fd6da07 | ||
|
|
34e7996f2f | ||
|
|
f2d7a28258 | ||
|
|
ba6f8da1e4 | ||
|
|
0a5e79891d | ||
|
|
901bf63cb8 | ||
|
|
350b8efb0b | ||
|
|
2f69b4d190 | ||
|
|
57a15a4e96 | ||
|
|
13042002d7 | ||
|
|
8ac745b4e5 | ||
|
|
8a4f038ec4 | ||
|
|
890522f6f4 | ||
|
|
c8aa032051 | ||
|
|
9830c146d0 | ||
|
|
00c9164fdb | ||
|
|
a31e7118b4 | ||
|
|
2058ce3f3a | ||
|
|
61e3bf4a20 | ||
|
|
ea5bae711b | ||
|
|
44a2c5fdff | ||
|
|
3b43960779 | ||
|
|
89f27ec99a | ||
|
|
82cf4358ce | ||
|
|
0f9b117744 | ||
|
|
c420c21219 | ||
|
|
3bae4b0d93 | ||
|
|
1432bd7a81 | ||
|
|
bdcf930e25 | ||
|
|
2e42d74c59 | ||
|
|
bbf26fc3e8 | ||
|
|
6dcc42cdeb | ||
|
|
385b0aa07f | ||
|
|
f2b1ee3398 | ||
|
|
2a2c60201f | ||
|
|
53749156a7 | ||
|
|
ee12b34334 | ||
|
|
b5a4aefe8b | ||
|
|
c2d5cab1b8 | ||
|
|
f25af8f7d6 | ||
|
|
33648b083c | ||
|
|
eb79711e0a | ||
|
|
03ce6052cc | ||
|
|
7992bf4177 | ||
|
|
ddb390d5ec | ||
|
|
1500b1dc00 | ||
|
|
9263d3e82c | ||
|
|
02b7fd53ed | ||
|
|
3cd199e852 | ||
|
|
77bc22baf5 | ||
|
|
f57988761c | ||
|
|
7987d09438 | ||
|
|
262352f8bb | ||
|
|
e8c56a8e8e | ||
|
|
312691376a | ||
|
|
a9611c782b | ||
|
|
bcc70b36fd | ||
|
|
9ee3b1bfbd | ||
|
|
e80a83b30b | ||
|
|
d80e72dfcd | ||
|
|
d01c26e618 | ||
|
|
cab2e11ca9 | ||
|
|
b48994b1b3 | ||
|
|
fb48acddb2 | ||
|
|
7d627f7af4 | ||
|
|
f458491d88 | ||
|
|
de802f9d76 | ||
|
|
804e6ba586 | ||
|
|
af3fc82b77 | ||
|
|
3bb7036cfd | ||
|
|
d802eb485b | ||
|
|
b4dd9c2490 | ||
|
|
04ab198e95 | ||
|
|
c345700963 | ||
|
|
b21afcb78f | ||
|
|
7f65530b26 | ||
|
|
e230eeb486 | ||
|
|
b1b03f3a97 | ||
|
|
36ef6d8da2 | ||
|
|
28bb0dd3b0 | ||
|
|
79f8a8f909 | ||
|
|
bfe2513540 | ||
|
|
2536aef737 | ||
|
|
0661ccdea9 | ||
|
|
29f7015e58 | ||
|
|
7dc1b50d3b | ||
|
|
682133a56a | ||
|
|
76db732f1b | ||
|
|
64aa2b5cc3 | ||
|
|
1eed5fd271 | ||
|
|
dd7ffa4501 | ||
|
|
5e79da0c9c | ||
|
|
65fe753a58 | ||
|
|
17c835e672 | ||
|
|
fa5bcd0457 | ||
|
|
499d6312b7 | ||
|
|
20d5903696 | ||
|
|
f039221ec9 | ||
|
|
dc9f8762ca | ||
|
|
e5c69801a7 | ||
|
|
ab458368de | ||
|
|
8eb680fcc9 | ||
|
|
71b05972f4 | ||
|
|
7deeb8dba5 | ||
|
|
daad190188 | ||
|
|
7cc0052451 | ||
|
|
18f8e29af8 | ||
|
|
d78f6614cc | ||
|
|
53ac43202a | ||
|
|
bfb050c9cc | ||
|
|
97622b961c | ||
|
|
b7e7c11a57 | ||
|
|
59e11e4e8a | ||
|
|
39b0357517 | ||
|
|
878a12002c | ||
|
|
d3d2863d52 | ||
|
|
f3f23b43b5 | ||
|
|
006262ed8f | ||
|
|
519aef86fa | ||
|
|
8bd77c0d47 | ||
|
|
7a5c910cd5 | ||
|
|
4e34f2001c | ||
|
|
54b7e2c874 | ||
|
|
19f89f7122 | ||
|
|
9f25d72a4a | ||
|
|
3e99b71e0f | ||
|
|
fb3b690c5f | ||
|
|
18fe553d23 | ||
|
|
e7ff6a2191 | ||
|
|
ad15e5a234 | ||
|
|
094c7aa438 | ||
|
|
472ed5a7d3 | ||
|
|
f284300870 | ||
|
|
ab5f4410b6 | ||
|
|
a400a3dd09 | ||
|
|
c4855e8123 | ||
|
|
3abb657777 | ||
|
|
4dd63d8f72 | ||
|
|
6c0bdec2c9 | ||
|
|
d6dd1f269a | ||
|
|
2bd23f3709 | ||
|
|
79dc464b83 | ||
|
|
3dbd5b11d8 | ||
|
|
549fd5fe29 | ||
|
|
0ab5c65185 | ||
|
|
5f4753f881 | ||
|
|
e03a45f8c5 | ||
|
|
67c2d53e59 | ||
|
|
7b01dd9794 | ||
|
|
04e50ff02c | ||
|
|
e57ccdc858 | ||
|
|
a73a65b48a | ||
|
|
781c805e10 | ||
|
|
7c4af88911 | ||
|
|
0aca173e96 | ||
|
|
60db57a41c | ||
|
|
df68f7dcb9 | ||
|
|
e95e3cc29d | ||
|
|
67e9310cc1 | ||
|
|
94984df047 | ||
|
|
861bf953bf | ||
|
|
06eaac8690 | ||
|
|
6b9e8816d1 | ||
|
|
cf9f715a28 | ||
|
|
08e043fea7 | ||
|
|
54ba1009d3 | ||
|
|
4a6a46fd7b | ||
|
|
0eb7414f72 | ||
|
|
c02691a70c | ||
|
|
8742957f75 | ||
|
|
ace372cfc6 | ||
|
|
27c5f5f49f | ||
|
|
ec5a1a3977 | ||
|
|
abc653c87d | ||
|
|
30a128c00b | ||
|
|
77c7963bde | ||
|
|
b80c1343c3 | ||
|
|
e5c5fbcea3 | ||
|
|
fc7d04ce73 | ||
|
|
35f57b3ed2 | ||
|
|
ed9ade05ea | ||
|
|
f485b729bc | ||
|
|
64f7953423 | ||
|
|
fc1d837501 | ||
|
|
6a238818da | ||
|
|
9800c43bfa | ||
|
|
b3cda39b0b | ||
|
|
7e1b7a2fcd | ||
|
|
ccae8d42fd | ||
|
|
38f78286c7 | ||
|
|
c3a53a6ec5 | ||
|
|
2bbe6a2ad8 | ||
|
|
f64f63f675 | ||
|
|
f0fb44f6d4 | ||
|
|
fa972615e6 | ||
|
|
f7cb2ef25d | ||
|
|
949f8bb2e7 | ||
|
|
5eaff802d4 | ||
|
|
b37d428726 | ||
|
|
a6336149f8 | ||
|
|
12f5ec40b5 | ||
|
|
6a672cf8bc | ||
|
|
036b7d9f66 | ||
|
|
ca6bce3820 | ||
|
|
9b4f2297b0 | ||
|
|
355c4bc58e | ||
|
|
a31b2876d0 | ||
|
|
b697b6e57b | ||
|
|
4792fe6391 | ||
|
|
7408f72de2 | ||
|
|
203a3cfdd1 | ||
|
|
3a029686e3 | ||
|
|
9fa7b3956d | ||
|
|
b888ace508 | ||
|
|
2e54efe34d | ||
|
|
94d7f33a03 | ||
|
|
11598295cf | ||
|
|
383c3769aa | ||
|
|
caccba18e9 | ||
|
|
95ce00e551 | ||
|
|
85dad58cfe | ||
|
|
acdc70f72a | ||
|
|
bc9db72212 | ||
|
|
7905f81d7e | ||
|
|
2ef9cc6a1c | ||
|
|
2357b69a5f | ||
|
|
98b47f866b | ||
|
|
d2dfb0d15d | ||
|
|
63339c026d | ||
|
|
40922b88f7 | ||
|
|
674c617c16 | ||
|
|
720862dafb | ||
|
|
5120b4b468 | ||
|
|
c5f5d4543e | ||
|
|
baf92da9f2 | ||
|
|
526c9c3d73 | ||
|
|
119d87b795 | ||
|
|
17b030cca1 | ||
|
|
81c9b3f327 | ||
|
|
50198a2159 | ||
|
|
20c8b1eaf6 | ||
|
|
faf94b033d | ||
|
|
342b3d3d08 | ||
|
|
26d43f193e | ||
|
|
f04ae91c82 | ||
|
|
bb5515113f | ||
|
|
c09d84efc6 | ||
|
|
2a99a2a597 | ||
|
|
08f6dcf08d | ||
|
|
284d78c6b2 | ||
|
|
b8b5fb9eae | ||
|
|
697ee62d9a | ||
|
|
20ac0edda8 | ||
|
|
a1bbb06e2f | ||
|
|
a87cc5c3b1 | ||
|
|
5ff740cddc | ||
|
|
db9229f485 | ||
|
|
ace6c01b00 | ||
|
|
2f4d460c04 | ||
|
|
d5401ae3cb | ||
|
|
3482a7f1f1 | ||
|
|
23762e0639 | ||
|
|
50cc15af3b | ||
|
|
a0f0367e60 | ||
|
|
3f08ddefe9 | ||
|
|
debe656f9f | ||
|
|
4350b6402f | ||
|
|
5d2892f6fc | ||
|
|
91f27ddd66 | ||
|
|
6a674d0d67 | ||
|
|
0d165487b8 | ||
|
|
bacc5836ea | ||
|
|
230931a2c9 | ||
|
|
61d289f414 | ||
|
|
ed9577150b | ||
|
|
80319e7087 | ||
|
|
9ac655d774 | ||
|
|
9b17470129 | ||
|
|
5c114fc6fe | ||
|
|
efb12f41b2 | ||
|
|
813edcfe0a | ||
|
|
e7f91b7529 | ||
|
|
5e8a64508e | ||
|
|
21ebb7a585 | ||
|
|
34606e0f9f | ||
|
|
5f62f9edc7 | ||
|
|
207ee8667b | ||
|
|
5984ed921e | ||
|
|
aac5fb8c24 | ||
|
|
8cfc8b3132 | ||
|
|
0b5ebd7b1d | ||
|
|
35141a4aef | ||
|
|
e702234fdc | ||
|
|
05dbe23563 | ||
|
|
fd07f59d16 | ||
|
|
b36ffe1c9d | ||
|
|
1f1451e666 | ||
|
|
1000839309 | ||
|
|
ed4ed5fdcd | ||
|
|
eaf1f7cc13 | ||
|
|
ddfc8c2fa4 | ||
|
|
91afa7f7f4 | ||
|
|
da49e71eb8 | ||
|
|
5b8ce8ebae | ||
|
|
827bcd898e | ||
|
|
54a4add40f | ||
|
|
0093eb1220 | ||
|
|
b56ff7678c | ||
|
|
431197042d | ||
|
|
295876e1d1 | ||
|
|
e8f975e44f | ||
|
|
bc026ab49a | ||
|
|
f56457271d | ||
|
|
bc5250c899 | ||
|
|
971511e1c5 | ||
|
|
51317dca70 | ||
|
|
91dda08165 | ||
|
|
67bfa5bc90 | ||
|
|
0841f4a27d | ||
|
|
c6edf8421f | ||
|
|
97fb90c37a | ||
|
|
1d00bdd2a4 | ||
|
|
0ff01871f3 | ||
|
|
e00a675e6e | ||
|
|
d5e63b875b | ||
|
|
a31ccef99b | ||
|
|
375fd9140b | ||
|
|
fbb2c213a9 | ||
|
|
d323de3c21 | ||
|
|
18872cc8ae | ||
|
|
33085e3798 | ||
|
|
e42c811797 | ||
|
|
6b321e4d02 | ||
|
|
e6b17c701d | ||
|
|
60bda7ca25 | ||
|
|
b37d2ec6d1 | ||
|
|
751ccd02f6 | ||
|
|
431d04bd40 | ||
|
|
e756f07cad | ||
|
|
b9e025b50e | ||
|
|
7a83a71a02 | ||
|
|
8aaf4cfaf0 | ||
|
|
8a62d13cee | ||
|
|
5a45471c68 | ||
|
|
6dc21bf433 | ||
|
|
1da24c68ca | ||
|
|
3d3b7a5672 | ||
|
|
f221a6d953 | ||
|
|
bcf71c0fee | ||
|
|
a689317696 | ||
|
|
14a5c12404 | ||
|
|
35b2968b60 | ||
|
|
7e0ae6112c | ||
|
|
9ad1ccf19f | ||
|
|
5b7599e1b0 | ||
|
|
19c44b83d2 | ||
|
|
41691957d2 | ||
|
|
e3182624fd | ||
|
|
b8b3e6f9cd | ||
|
|
520c7286c4 | ||
|
|
d547829c5f | ||
|
|
49459cfc9b | ||
|
|
827ba446ab | ||
|
|
e5316221bb | ||
|
|
357174e351 | ||
|
|
5fc2f3236e | ||
|
|
a3484cce43 | ||
|
|
cdd107b06b | ||
|
|
6619b584bc | ||
|
|
c24dc6609d | ||
|
|
b5e7e9fc5c | ||
|
|
58dec7334b | ||
|
|
725f99ea4c | ||
|
|
4b43cac093 | ||
|
|
0159e26631 | ||
|
|
19cb1ae8e1 | ||
|
|
f8e06f9dbc | ||
|
|
e68ca761a9 | ||
|
|
fc6f3993bd | ||
|
|
f8b074f696 | ||
|
|
66f14099ed | ||
|
|
f3dc681f61 | ||
|
|
62f62e1fd5 | ||
|
|
f3360e61e1 | ||
|
|
5208dcd67e | ||
|
|
6f7d4a7a01 | ||
|
|
958a3bbd99 | ||
|
|
d5c85a3d53 | ||
|
|
5e7b527af2 | ||
|
|
ddbd33a8e3 | ||
|
|
a7dc368191 | ||
|
|
5b1d0c55a0 | ||
|
|
58515ca36e | ||
|
|
2d753ebf1a | ||
|
|
50cd716220 | ||
|
|
47c97e8191 | ||
|
|
d704ba175f | ||
|
|
c1036c5e4f | ||
|
|
356d06915b | ||
|
|
2b4fd738da | ||
|
|
f9de6df426 | ||
|
|
8ee409d9bd | ||
|
|
0cf3de4541 | ||
|
|
3a79be7426 | ||
|
|
30589bb580 | ||
|
|
e07b18bb18 | ||
|
|
b31eb3db82 | ||
|
|
da2f98fa8f | ||
|
|
7ac532a1d0 | ||
|
|
b98ab70517 | ||
|
|
0bdc84d42a | ||
|
|
7d7c5ce63e | ||
|
|
547c9e7f5a | ||
|
|
3e4d8a3ffe | ||
|
|
74c3b0d079 | ||
|
|
846aa98a9c | ||
|
|
17b7789531 | ||
|
|
b86d9bf1ab | ||
|
|
6e06ce2af1 | ||
|
|
dc275d7009 | ||
|
|
a67b9f566a | ||
|
|
9344289262 | ||
|
|
6713282a43 | ||
|
|
f834626f00 | ||
|
|
0a475bd4cc | ||
|
|
0820c484d6 | ||
|
|
dbf39403d3 | ||
|
|
14dde9ec32 | ||
|
|
3c266dce8c | ||
|
|
f7be34f5bb | ||
|
|
64b47ca3c7 | ||
|
|
e39974eb07 | ||
|
|
76895c76a1 | ||
|
|
98e8ef4e11 | ||
|
|
2ffe9b53f7 | ||
|
|
405c2889f0 | ||
|
|
40cf392504 | ||
|
|
42baa03985 | ||
|
|
016f20b2d4 | ||
|
|
d5faf31d43 | ||
|
|
12324e3550 | ||
|
|
ff67499562 | ||
|
|
2ba5abc2bb | ||
|
|
3d4a8aaad1 | ||
|
|
4ded1f6193 | ||
|
|
940a0a67a5 | ||
|
|
be304365a0 | ||
|
|
fc00767cf8 | ||
|
|
2163b39268 | ||
|
|
aa61cbd574 | ||
|
|
2a89d53d7d | ||
|
|
f7ce6a1fdb | ||
|
|
20b35f3139 | ||
|
|
46d454730f | ||
|
|
c2ad4c5633 | ||
|
|
6e0795253e | ||
|
|
9956d422f2 | ||
|
|
57e78e2bec | ||
|
|
5aa58e6043 | ||
|
|
5860492a38 | ||
|
|
e74b6cbcaf | ||
|
|
1e2344341d | ||
|
|
b712ba1e04 | ||
|
|
9ae6435317 | ||
|
|
81e8ba223f |
No files matched your search
+10
-3
@@ -39,24 +39,31 @@ prog/weavehosts/weavehosts
|
||||
prog/weaveproxy/weaveproxy
|
||||
prog/sigproxy/sigproxy
|
||||
prog/weavewait/weavewait
|
||||
prog/weavewait/weavewait_noop
|
||||
prog/weavewait/weavewait_nomcast
|
||||
prog/netcheck/netcheck
|
||||
prog/docker_tls_args/docker_tls_args
|
||||
prog/plugin/plugin
|
||||
testing/cover/cover
|
||||
testing/runner/runner
|
||||
tools/bin
|
||||
tools/build
|
||||
releases
|
||||
.weaver.uptodate
|
||||
.weavedns.uptodate
|
||||
.weaveexec.uptodate
|
||||
weave.tar
|
||||
.dockerplugin.uptodate
|
||||
weave.tar.gz
|
||||
prog/weaveexec/weave
|
||||
prog/weaveexec/weavehosts
|
||||
prog/weaveexec/weaveproxy
|
||||
prog/weaveexec/weavewait
|
||||
prog/weaveexec/weavewait_noop
|
||||
prog/weaveexec/weavewait_nomcast
|
||||
prog/weaveexec/sigproxy
|
||||
prog/weaveexec/netcheck
|
||||
prog/weaveexec/docker_tls_args
|
||||
prog/weaveexec/docker*.tgz
|
||||
Vagrantfile.local
|
||||
test/tls/tls
|
||||
test/tls/*.pem
|
||||
test/coverage
|
||||
test/coverage.*
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "tools"]
|
||||
path = tools
|
||||
url = https://github.com/weaveworks/build-tools
|
||||
@@ -21,7 +21,6 @@ install:
|
||||
- go get ./...
|
||||
|
||||
script:
|
||||
- bin/lint .
|
||||
- make tests
|
||||
|
||||
after_success:
|
||||
|
||||
+101
@@ -1,3 +1,104 @@
|
||||
## Release 1.4.2
|
||||
|
||||
Bug-fixes and minor improvements.
|
||||
|
||||
* A race condition in weavewait that would occasionally hang
|
||||
containers at startup #1882/#1884
|
||||
* Having the plugin auto-restart prevents successful `weave launch` on
|
||||
reboot #1869
|
||||
* Work round weave router failure on CoreOS 4.3 caused by kernel bug
|
||||
#1854
|
||||
* `weave launch` would exit with error code on docker <1.9 #1851
|
||||
* Running `eval $(weave env)` multiple times would break `eval $(weave
|
||||
env --restore)` #1824/#1825
|
||||
* Don't complain in `weave stop` about "Plugin is not running" when
|
||||
plugin is not enabled #1840/#1841
|
||||
* `weave --local launch` would fail if utility program
|
||||
`docker_tls_args` could not be found #1844
|
||||
* Improved error reporting when TLS arg detection fails #1843
|
||||
* Improve error reporting when docker isn't running #1845
|
||||
* Add `--trusted-subnets` usage to `weave` script #1842
|
||||
* weave run can hang under rare combinations of options #1858
|
||||
|
||||
## Release 1.4.1
|
||||
|
||||
This is a bug-fix release to cover a few issues that came up since the
|
||||
release of 1.4.0.
|
||||
|
||||
* Weave would fail to launch when `$DOCKER_HOST` was set to a TCP socket secured with TLS #1820/#1822
|
||||
* Weave would fail to stop when run against Docker pre-1.9 #1815/#1817
|
||||
* Issue a warning instead of failing on `--with-dns` option to proxy, which was removed #1810/#1812
|
||||
* Make `weave version` show the plugin version #1797/#1813
|
||||
* Make `weave launch` show when the a container is restarting #1778/#1814
|
||||
* Make `weave launch` fail if the plugin is running, for consistency with router and proxy. #1818/#1819
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.4.1).
|
||||
|
||||
## Release 1.4.0
|
||||
|
||||
**Highlights**
|
||||
|
||||
* The Docker Network plugin can now operate without a cluster store, so it is now run by default.
|
||||
* You can now use the fast datapath over trusted links and Weave encryption over untrusted links, in the same network.
|
||||
|
||||
Plus many bug fixes and minor enhancements. More details below and in
|
||||
the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.4.0).
|
||||
|
||||
## Release 1.3.1
|
||||
|
||||
**Highlights**
|
||||
|
||||
* The minimum Docker version has been increased to 1.6 due to the
|
||||
[upcoming deprecation of Dockerhub access](https://blog.docker.com/2015/10/docker-hub-deprecation-1-5/)
|
||||
for old clients. From December 7th onwards previous versions of the
|
||||
`weave` script will fail to pull down images from the hub; if you
|
||||
are unable to upgrade to 1.3.1 immediately you can work around this
|
||||
by running `weave --local setup` in conjunction with a compatible
|
||||
Docker client installation
|
||||
* Docker networking plugin now works with older kernels and allows you
|
||||
to configure the MTU
|
||||
|
||||
More details in the
|
||||
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.1).
|
||||
|
||||
## Release 1.3.0
|
||||
|
||||
**Highlights**
|
||||
|
||||
This release includes a Docker Plugin, so you have the option to use
|
||||
Weave Net that way. More details in the
|
||||
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.0).
|
||||
|
||||
## Release 1.2.1
|
||||
|
||||
This release contains a number of bug fixes and minor
|
||||
enhancements. More details in the
|
||||
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.2.1).
|
||||
|
||||
The release is fully compatible with 1.2.0 versions, so existing
|
||||
clusters can be upgraded incrementally.
|
||||
|
||||
## Release 1.2.0
|
||||
|
||||
**Highlights**
|
||||
|
||||
This release introduces the
|
||||
[Fast Data Path](http://docs.weave.works/weave/master/head/features.html#fast-data-path),
|
||||
which allows Weave networks to operate at near wire-level speeds. This
|
||||
new feature is enabled by default.
|
||||
|
||||
Other highlights:
|
||||
|
||||
- auto-configuration of TLS for the Weave Docker API proxy, making it
|
||||
easier to run Weave on Macs and in conjunction with tools like
|
||||
Docker Swarm
|
||||
- support for restart policies on application containers and weave
|
||||
infrastructure containers
|
||||
- better compatibility with recent and future Docker versions
|
||||
|
||||
More details in the
|
||||
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.2.0).
|
||||
|
||||
## Release 1.1.2
|
||||
|
||||
This release contains a small number of bug fixes. More details in the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PUBLISH=publish_weave publish_weaveexec
|
||||
PUBLISH=publish_weave publish_weaveexec publish_plugin
|
||||
|
||||
.DEFAULT: all
|
||||
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
|
||||
.PHONY: all update tests lint publish $(PUBLISH) clean clean-bin prerequisites build travis run-smoketests
|
||||
|
||||
# If you can use docker without being root, you can do "make SUDO="
|
||||
SUDO=sudo
|
||||
@@ -13,26 +13,31 @@ WEAVER_EXE=prog/weaver/weaver
|
||||
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
WEAVEHOSTS_EXE=prog/weavehosts/weavehosts
|
||||
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
|
||||
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
|
||||
NETCHECK_EXE=prog/netcheck/netcheck
|
||||
COVER_EXE=testing/cover/cover
|
||||
RUNNER_EXE=testing/runner/runner
|
||||
DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args
|
||||
DOCKERPLUGIN_EXE=prog/plugin/plugin
|
||||
RUNNER_EXE=tools/runner/runner
|
||||
TEST_TLS_EXE=test/tls/tls
|
||||
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE) $(COVER_EXE) $(RUNNER_EXE)
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
WEAVER_UPTODATE=.weaver.uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
||||
DOCKERPLUGIN_UPTODATE=.dockerplugin.uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE)
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(DOCKERPLUGIN_UPTODATE)
|
||||
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||
DOCKERPLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE)
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(DOCKERPLUGIN_IMAGE)
|
||||
|
||||
WEAVE_EXPORT=weave.tar
|
||||
WEAVE_EXPORT=weave.tar.gz
|
||||
|
||||
WEAVEEXEC_DOCKER_VERSION=1.3.1
|
||||
WEAVEEXEC_DOCKER_VERSION=1.6.2
|
||||
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
@@ -45,7 +50,9 @@ NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
}
|
||||
BUILD_FLAGS=-ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo
|
||||
|
||||
all: $(WEAVE_EXPORT) $(COVER_EXE) $(RUNNER_EXE)
|
||||
PACKAGE_BASE=$(shell go list -e ./)
|
||||
|
||||
all: $(WEAVE_EXPORT) $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
travis: $(EXES)
|
||||
|
||||
@@ -54,7 +61,7 @@ update:
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE): common/*.go common/*/*.go net/*.go
|
||||
ifeq ($(COVERAGE),true)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "weaveworks") | paste -s -d,))
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | paste -s -d,))
|
||||
go get -t -tags netgo ./$(@D)
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
else
|
||||
@@ -68,59 +75,87 @@ $(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVER_EXE): router/*.go mesh/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go
|
||||
$(NETCHECK_EXE): prog/netcheck/netcheck.go
|
||||
|
||||
# Sigproxy and weavewait need separate rules as they fail the netgo check in
|
||||
# These next programs need separate rules as they fail the netgo check in
|
||||
# the main build stanza due to not importing net package
|
||||
$(SIGPROXY_EXE): prog/sigproxy/main.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/main.go net/*.go
|
||||
$(WEAVEHOSTS_EXE): prog/weavehosts/weavehosts.go
|
||||
$(COVER_EXE): testing/cover/cover.go
|
||||
$(RUNNER_EXE): testing/runner/runner.go
|
||||
$(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go
|
||||
$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/net/*.go plugin/ipam/*.go plugin/skel/*.go api/*.go common/docker/*.go
|
||||
$(TEST_TLS_EXE): test/tls/*.go
|
||||
|
||||
$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(WEAVEHOSTS_EXE) $(COVER_EXE) $(RUNNER_EXE):
|
||||
$(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE) $(DOCKERPLUGIN_EXE) $(TEST_TLS_EXE):
|
||||
go get -tags netgo ./$(@D)
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
go build $(BUILD_FLAGS) -tags "netgo iface mcast" -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
go build $(BUILD_FLAGS) -tags "netgo iface" -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE)
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE)
|
||||
cp weave prog/weaveexec/weave
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEHOSTS_EXE) prog/weaveexec/weavehosts
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
cp $(NETCHECK_EXE) prog/weaveexec/netcheck
|
||||
cp $(DOCKERTLSARGS_EXE) prog/weaveexec/docker_tls_args
|
||||
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
|
||||
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
touch $@
|
||||
|
||||
$(DOCKERPLUGIN_UPTODATE): prog/plugin/Dockerfile $(DOCKERPLUGIN_EXE)
|
||||
$(SUDO) docker build -t $(DOCKERPLUGIN_IMAGE) prog/plugin
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
|
||||
$(SUDO) docker save $(addsuffix :latest,$(IMAGES)) > $@
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
|
||||
$(DOCKER_DISTRIB):
|
||||
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
||||
|
||||
tests: $(COVER_EXE)
|
||||
@test/units.sh
|
||||
tests: tools/.git
|
||||
tools/test
|
||||
|
||||
lint: tools/.git
|
||||
tools/lint -nocomment -notestpackage .
|
||||
|
||||
tools/.git:
|
||||
git submodule update --init
|
||||
|
||||
$(RUNNER_EXE): tools/.git
|
||||
make -C tools/runner
|
||||
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
$(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
publish: $(PUBLISH)
|
||||
|
||||
clean:
|
||||
-$(SUDO) docker rmi $(IMAGES)
|
||||
go clean -r ./...
|
||||
clean-bin:
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||
go clean -r $(addprefix ./,$(dir $(EXES)))
|
||||
rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT)
|
||||
|
||||
clean: clean-bin
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage
|
||||
|
||||
build:
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
[Weaveworks](http://weave.works) is the company that develops Weave -
|
||||
the most productive way for developers to connect, observe and control
|
||||
Docker containers. To learn about our products, including getting
|
||||
started tutorials, visit our [website](http://weave.works), read about
|
||||
the
|
||||
[1.0 release of Weave](http://blog.weave.works/2015/06/18/weave-1-0-is-out-and-it-is-good/),
|
||||
visit our [documentation](http://docs.weave.works) or continue to read
|
||||
about some of the more technical aspects of Weave in this readme.
|
||||
started tutorials, visit our [website](http://weave.works) and
|
||||
[documentation](http://docs.weave.works) or continue to read about
|
||||
some of the more technical aspects of Weave in this readme.
|
||||
|
||||
# Weave
|
||||
|
||||
@@ -43,19 +41,29 @@ capabilities, so these can continue to be used by containers.
|
||||
## Installation
|
||||
|
||||
Ensure you are running Linux (kernel 3.8 or later) and have Docker
|
||||
(version 1.3.1 or later) installed. Then install weave with
|
||||
(version 1.6.0 or later) installed. Then install weave with
|
||||
|
||||
sudo curl -L git.io/weave -o /usr/local/bin/weave
|
||||
sudo chmod a+x /usr/local/bin/weave
|
||||
|
||||
For usage on OSX (with Docker Machine) you first need to make sure
|
||||
that a VM is running and configured, as shown in [the Docker Machine
|
||||
documentation](https://docs.docker.com/installation/mac/#from-your-shell).
|
||||
Then you can launch weave directly from the OSX host.
|
||||
|
||||
CoreOS users see [here](https://github.com/fintanr/weave-gs/blob/master/coreos-simple/user-data) for an example of installing weave using cloud-config.
|
||||
|
||||
Amazon ECS users see
|
||||
[here](https://github.com/weaveworks/guides/blob/master/aws-ecs/LATESTAMIs.md)
|
||||
for the latest Weave AMIs and
|
||||
[here](http://weave.works/guides/service-discovery-with-weave-aws-ecs.html) to get started with Weave on ECS.
|
||||
|
||||
Weave respects the environment variable `DOCKER_HOST`, so you can run
|
||||
it locally to control a weave network on a remote host.
|
||||
|
||||
## Quick Start Screencast
|
||||
|
||||
<a href="https://youtu.be/BF9YJSPQUcY" alt="Click to watch the screencast" target="_blank">
|
||||
<a href="https://youtu.be/kihQCCT1ykE" alt="Click to watch the screencast" target="_blank">
|
||||
<img src="/docs/hello-screencast.png" />
|
||||
</a>
|
||||
|
||||
@@ -99,9 +107,8 @@ The only difference, apart from the name of the application container,
|
||||
is that we tell our weave that it should peer with the weave on
|
||||
`$HOST1` (specified as the IP address or hostname, and optional
|
||||
`:port`, by which `$HOST2` can reach it). NB: if there is a firewall
|
||||
between `$HOST1` and `$HOST2`, you must open the weave port (6783 by
|
||||
default; this can be overriden by setting `WEAVE_PORT`) for TCP and
|
||||
UDP.
|
||||
between `$HOST1` and `$HOST2`, you must permit traffic to the weave
|
||||
control port (TCP 6783) and data ports (UDP 6783/6784).
|
||||
|
||||
Note that we could instead have told the weave on `$HOST1` to connect to
|
||||
`$HOST2`, or told both about each other. Order does not matter here;
|
||||
@@ -148,12 +155,16 @@ to each other.
|
||||
|
||||
## Contact Us
|
||||
|
||||
Found a bug, want to suggest a feature, or have a question?
|
||||
[File an issue](https://github.com/weaveworks/weave/issues), or email
|
||||
help@weave.works. When reporting a bug, please include which version of
|
||||
weave you are running, as shown by `weave version`.
|
||||
Found a bug, want to suggest a feature, or have a question? You can
|
||||
[File an issue](https://github.com/weaveworks/weave/issues). And
|
||||
there is a [Weave Users Google
|
||||
Group](https://groups.google.com/a/weave.works/forum/#!forum/weave-users),
|
||||
which you can post to by email at weave-users@weave.works. Or you can
|
||||
contact Weaveworks directly at help@weave.works. When reporting a
|
||||
bug, please include which version of weave you are running, as shown
|
||||
by `weave version`.
|
||||
|
||||
Follow weave on Twitter:
|
||||
Follow on Twitter:
|
||||
[@weaveworks](https://twitter.com/weaveworks).
|
||||
|
||||
Read the Weave blog:
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (client *Client) RegisterWithDNS(ID string, fqdn string, ip string) error {
|
||||
data := url.Values{}
|
||||
data.Add("fqdn", fqdn)
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/name/%s/%s", ID, ip), data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) DeregisterWithDNS(ID string, ip string) error {
|
||||
_, err := client.httpVerb("DELETE", fmt.Sprintf("/name/%s/%s", ID, ip), nil)
|
||||
return err
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseIP(ip)
|
||||
}
|
||||
|
||||
// returns an IP for the ID given, allocating a fresh one if necessary
|
||||
func (client *Client) AllocateIP(ID string) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "POST")
|
||||
}
|
||||
|
||||
// returns an IP for the ID given, or nil if one has not been
|
||||
// allocated
|
||||
func (client *Client) LookupIP(ID string) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "GET")
|
||||
}
|
||||
|
||||
// release an IP which is no longer needed
|
||||
func (client *Client) ReleaseIP(ID string) error {
|
||||
_, err := client.ipamOp(ID, "DELETE")
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) DefaultSubnet() (*net.IPNet, error) {
|
||||
cidr, err := client.httpVerb("GET", fmt.Sprintf("/ipinfo/defaultsubnet"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
return ipnet, err
|
||||
}
|
||||
|
||||
func parseIP(body string) (*net.IPNet, error) {
|
||||
ip, ipnet, err := net.ParseCIDR(string(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipnet.IP = ip
|
||||
return ipnet, nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
WeaveHttpPort = 6784
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
baseUrl string
|
||||
resolve func() (string, error)
|
||||
}
|
||||
|
||||
func (client *Client) httpVerb(verb string, url string, values url.Values) (string, error) {
|
||||
baseUrl := client.baseUrl
|
||||
if client.resolve != nil {
|
||||
addr, err := client.resolve()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
baseUrl = fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)
|
||||
}
|
||||
url = baseUrl + url
|
||||
Log.Debugf("weave %s to %s with %v", verb, url, values)
|
||||
var body io.Reader
|
||||
if values != nil {
|
||||
body = strings.NewReader(values.Encode())
|
||||
}
|
||||
req, err := http.NewRequest(verb, url, body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if values != nil {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
if resp, err := http.DefaultClient.Do(req); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent {
|
||||
return string(body), nil
|
||||
} else {
|
||||
return "", errors.New(resp.Status + ": " + string(body))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(addr string) *Client {
|
||||
return &Client{baseUrl: fmt.Sprintf("http://%s:%d", addr, WeaveHttpPort)}
|
||||
}
|
||||
|
||||
func NewClientWithResolver(resolver func() (string, error)) *Client {
|
||||
return &Client{resolve: resolver}
|
||||
}
|
||||
|
||||
func (client *Client) Connect(remote string) error {
|
||||
_, err := client.httpVerb("POST", "/connect", url.Values{"peer": {remote}})
|
||||
return err
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This scipt lints go files for common errors.
|
||||
#
|
||||
# Its runs gofmt and go vet, and optionally golint and
|
||||
# gocyclo, if they are installed.
|
||||
#
|
||||
# With no arguments, it lints the current files staged
|
||||
# for git commit. Or you can pass it explicit filenames
|
||||
# (or directories) and it will lint them.
|
||||
#
|
||||
# To use this script automatically, run:
|
||||
# ln -s ../../bin/lint .git/hooks/pre-commit
|
||||
|
||||
set -eu
|
||||
|
||||
function lint_go {
|
||||
filename="$1"
|
||||
result=0
|
||||
|
||||
if [ -n "$(gofmt -l "${filename}")" ]; then
|
||||
result=1
|
||||
echo "${filename}: run gofmt -w ${filename}!"
|
||||
fi
|
||||
|
||||
go tool vet "${filename}" || result=$?
|
||||
|
||||
# golint is completely optional. If you don't like it
|
||||
# don't have it installed.
|
||||
if type golint >/dev/null 2>&1; then
|
||||
# golint doesn't set an exit code it seems
|
||||
lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports')
|
||||
if [ "$lintoutput" != "" ]; then
|
||||
result=1
|
||||
echo "$lintoutput"
|
||||
fi
|
||||
fi
|
||||
|
||||
# gocyclo is completely optional. If you don't like it
|
||||
# don't have it installed. Also never blocks a commit,
|
||||
# it just warns.
|
||||
if type gocyclo >/dev/null 2>&1; then
|
||||
gocyclo -over 25 "${filename}" | while read line; do
|
||||
echo "${filename}": higher than 25 cyclomatic complexity - "${line}"
|
||||
done
|
||||
fi
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function lint {
|
||||
filename="$1"
|
||||
ext="${filename##*\.}"
|
||||
|
||||
# Don't lint this script
|
||||
if [ "${filename}" = "bin/lint" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Don't lint files that don't exist
|
||||
if ! [ -a "${filename}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
case "$ext" in
|
||||
go) lint_go "${filename}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function lint_files {
|
||||
lint_result=0
|
||||
while read filename; do
|
||||
lint "${filename}" || lint_result=1
|
||||
done
|
||||
exit $lint_result
|
||||
}
|
||||
|
||||
function list_files {
|
||||
if [ $# -gt 0 ]; then
|
||||
find "$@" -type f
|
||||
else
|
||||
git diff --cached --name-only
|
||||
fi
|
||||
}
|
||||
|
||||
list_files "$@" | lint_files
|
||||
@@ -29,6 +29,7 @@ random_mac() {
|
||||
case "$1" in
|
||||
launch)
|
||||
shift 1
|
||||
echo $(date -R) launching
|
||||
for i in $(seq $START $FINISH); do
|
||||
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch-router --iface '' --name $(random_mac) --nickname weave$i --port $PORT --no-dns "$@"
|
||||
done
|
||||
@@ -36,6 +37,7 @@ case "$1" in
|
||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
|
||||
PREV="${status#true }"
|
||||
fi
|
||||
echo $(date -R) connecting
|
||||
for i in $(seq $START $FINISH); do
|
||||
if [ -n "$PREV" ] ; then
|
||||
echo connecting weave$i to $PREV
|
||||
@@ -44,6 +46,7 @@ case "$1" in
|
||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i)
|
||||
PREV="${status#true }"
|
||||
done
|
||||
echo $(date -R) done
|
||||
;;
|
||||
stop)
|
||||
for i in $(seq $START $FINISH); do
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ FROM ubuntu
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
|
||||
RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.3.1 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.6.2 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
|
||||
|
||||
# When doing a build in a container, "apt-get update" happens twice,
|
||||
# which can be a very significant overhead for incremental builds.
|
||||
|
||||
+1
-5
@@ -36,11 +36,7 @@ EOF
|
||||
echo "weave:*:::::::" >>/etc/shadow
|
||||
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
|
||||
|
||||
if [ "$1" = "tests" ] ; then
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC tests"
|
||||
else
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
|
||||
fi
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
|
||||
else
|
||||
# There are arguments to pass to git-clone
|
||||
mkdir -p ${WEAVE_SRC%/*}
|
||||
|
||||
+26
-14
@@ -12,41 +12,45 @@ machine:
|
||||
SRCDIR: /home/ubuntu/src/github.com/weaveworks/weave
|
||||
PATH: $PATH:$HOME/.local/bin:$HOME/google-cloud-sdk/bin/
|
||||
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
|
||||
WEAVE_BUILD: $HOME/docker/weave-build.tar
|
||||
WEAVE_TEST: $HOME/docker/weave-test.tar
|
||||
|
||||
dependencies:
|
||||
cache_directories:
|
||||
- "~/docker"
|
||||
override:
|
||||
- if [[ -e "$WEAVE_BUILD" ]]; then
|
||||
docker load -i $WEAVE_BUILD;
|
||||
- if [[ -e "$WEAVE_TEST" ]]; then
|
||||
docker load -i $WEAVE_TEST;
|
||||
else
|
||||
docker build -t weaveworks/weave-build build;
|
||||
mkdir -p $(dirname "$WEAVE_BUILD");
|
||||
docker save weaveworks/weave-build >$WEAVE_BUILD;
|
||||
docker build -t weaveworks/weave-test test/image;
|
||||
mkdir -p $(dirname "$WEAVE_TEST");
|
||||
docker save weaveworks/weave-test >$WEAVE_TEST;
|
||||
fi
|
||||
post:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install bc jq
|
||||
- pip install requests
|
||||
- sudo apt-get update && sudo apt-get install bc jq && pip install requests
|
||||
- curl https://sdk.cloud.google.com | bash
|
||||
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||
- mkdir -p $(dirname $SRCDIR)
|
||||
- cp -r $(pwd)/ $SRCDIR
|
||||
- mkdir -p $(dirname $SRCDIR) && cp -r $(pwd)/ $SRCDIR
|
||||
- cd $SRCDIR/test; ./gce.sh make_template:
|
||||
parallel: false
|
||||
|
||||
test:
|
||||
override:
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-build tests:
|
||||
- cd $SRCDIR; make lint:
|
||||
parallel: true
|
||||
- docker run -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-test:
|
||||
parallel: true
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERAGE=true weaveworks/weave-build:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; ./gce.sh setup:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; eval $(./gce.sh hosts); ./setup.sh:
|
||||
- cd $SRCDIR/test; ./gce.sh setup && eval $(./gce.sh hosts) && ./setup.sh:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; eval $(./gce.sh hosts); export COVERAGE=true; ./run_all.sh:
|
||||
parallel: true
|
||||
timeout: 300
|
||||
- cd $SRCDIR; make clean-bin:
|
||||
parallel: true
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go weaveworks/weave-build:
|
||||
parallel: true
|
||||
post:
|
||||
- cd $SRCDIR/test; ./gce.sh destroy:
|
||||
parallel: true
|
||||
@@ -59,3 +63,11 @@ teardown:
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
|
||||
parallel: true
|
||||
|
||||
deployment:
|
||||
hub:
|
||||
branch: master
|
||||
owner: weaveworks
|
||||
commands:
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- cd $SRCDIR; make publish
|
||||
+72
-4
@@ -1,12 +1,18 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// An observer for container events
|
||||
type ContainerObserver interface {
|
||||
ContainerStarted(ident string)
|
||||
ContainerDied(ident string)
|
||||
}
|
||||
|
||||
@@ -16,18 +22,52 @@ type Client struct {
|
||||
|
||||
// NewClient creates a new Docker client and checks we can talk to Docker
|
||||
func NewClient(apiPath string) (*Client, error) {
|
||||
if apiPath != "" && !strings.Contains(apiPath, "://") {
|
||||
apiPath = "tcp://" + apiPath
|
||||
}
|
||||
dc, err := docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
env, err := client.Version()
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) {
|
||||
if !strings.Contains(apiPath, "://") {
|
||||
apiPath = "tcp://" + apiPath
|
||||
}
|
||||
dc, err := docker.NewVersionedClient(apiPath, apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
|
||||
return client, nil
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
|
||||
dc, err := docker.NewVersionedClientFromEnv(apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func (c *Client) checkWorking() error {
|
||||
_, err := c.Version()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Info() string {
|
||||
if env, err := c.Version(); err != nil {
|
||||
return fmt.Sprintf("Docker API error: %s", err)
|
||||
} else {
|
||||
return fmt.Sprintf("Docker API on %s: %v", c.Endpoint(), env)
|
||||
}
|
||||
}
|
||||
|
||||
// AddObserver adds an observer for docker events
|
||||
@@ -41,6 +81,9 @@ func (c *Client) AddObserver(ob ContainerObserver) error {
|
||||
go func() {
|
||||
for event := range events {
|
||||
switch event.Status {
|
||||
case "start":
|
||||
id := event.ID
|
||||
ob.ContainerStarted(id)
|
||||
case "die":
|
||||
id := event.ID
|
||||
ob.ContainerDied(id)
|
||||
@@ -62,3 +105,28 @@ func (c *Client) IsContainerNotRunning(idStr string) bool {
|
||||
Log.Errorf("[docker] Could not check container status: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// This is intended to find an IP address that we can reach the container on;
|
||||
// if it is on the Docker bridge network then that address; if on the host network
|
||||
// then localhost
|
||||
func (c *Client) GetContainerIP(nameOrID string) (string, error) {
|
||||
Log.Debugf("Getting IP for container %s", nameOrID)
|
||||
info, err := c.InspectContainer(nameOrID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.NetworkSettings.Networks != nil {
|
||||
Log.Debugln("Networks: ", info.NetworkSettings.Networks)
|
||||
if bridgeNetwork, ok := info.NetworkSettings.Networks["bridge"]; ok {
|
||||
return bridgeNetwork.IPAddress, nil
|
||||
} else if _, ok := info.NetworkSettings.Networks["host"]; ok {
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
} else if info.HostConfig.NetworkMode == "host" {
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
if info.NetworkSettings.IPAddress == "" {
|
||||
return "", errors.New("No IP address found for container " + nameOrID)
|
||||
}
|
||||
return info.NetworkSettings.IPAddress, nil
|
||||
}
|
||||
@@ -52,3 +52,15 @@ func SetLogLevel(levelname string) {
|
||||
}
|
||||
Log.Level = level
|
||||
}
|
||||
|
||||
func CheckFatal(e error) {
|
||||
if e != nil {
|
||||
Log.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckWarn(e error) {
|
||||
if e != nil {
|
||||
Log.Warnln(e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package odp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/weaveworks/go-odp/odp"
|
||||
)
|
||||
|
||||
// ODP admin functionality
|
||||
|
||||
func CreateDatapath(dpname string) (err error, supported bool) {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
if odp.IsKernelLacksODPError(err) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return err, true
|
||||
}
|
||||
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.CreateDatapath(dpname)
|
||||
if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return err, true
|
||||
}
|
||||
|
||||
// Pick an ephemeral port number to use in probing for vxlan
|
||||
// support.
|
||||
udpconn, err := net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
return err, true
|
||||
}
|
||||
|
||||
// we leave the UDP socket open, so creating a vxlan vport on
|
||||
// the same port number should fail. But that's fine: It's
|
||||
// still sufficient to probe for support.
|
||||
portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
|
||||
vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(
|
||||
fmt.Sprintf("vxlan-%d", portno), portno))
|
||||
if nlerr, ok := err.(odp.NetlinkError); ok {
|
||||
if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
|
||||
dp.Delete()
|
||||
return fmt.Errorf("kernel does not have Open vSwitch VXLAN support"), false
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
dp.DeleteVport(vpid)
|
||||
}
|
||||
|
||||
udpconn.Close()
|
||||
return nil, true
|
||||
}
|
||||
|
||||
func DeleteDatapath(dpname string) error {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.LookupDatapath(dpname)
|
||||
if err != nil {
|
||||
if odp.IsNoSuchDatapathError(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return dp.Delete()
|
||||
}
|
||||
|
||||
func AddDatapathInterface(dpname string, ifname string) error {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.LookupDatapath(dpname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
|
||||
return err
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@ type SignalReceiver interface {
|
||||
|
||||
func SignalHandlerLoop(ss ...SignalReceiver) {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGTERM)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||
buf := make([]byte, 1<<20)
|
||||
for {
|
||||
switch <-sigs {
|
||||
|
||||
+13
-13
@@ -66,23 +66,23 @@ Router:
|
||||
simply responds to traffic received from the remote peer via TCP
|
||||
5. We register this connection with the local peer
|
||||
6a. If we initiated this connection then we now start sending fast
|
||||
heartbeats to the remote peer so that the remote peer can determine what
|
||||
address/port it should use to send UDP back to us. To do this, we
|
||||
spawn off two further threads which are the forwarder
|
||||
loops. These receive frames which are to be sent to the remote
|
||||
peer. One of them sends frames without the DF flag set. To do this
|
||||
it just sends the packets out of the UDP Listener socket. The
|
||||
other needs to send frames with the DF flag set and needs its own
|
||||
socket so that it can do PMTU discovery easily. To do this, it
|
||||
uses a Raw IP socket (IP has no ports, so there's no collision
|
||||
issue with the UDP Listener socket) and so it must add UDP headers
|
||||
itself.
|
||||
6b. If we did not initiate this connection then the UDP Listener
|
||||
heartbeats to the remote peer so that the remote peer can
|
||||
determine what address/port it should use to send UDP back to
|
||||
us. To do this, we spawn off a "forwarder" thread to send
|
||||
heartbeats, monitor incoming heartbeats, and some other auxiliary
|
||||
duties. It also consumes frames to be encapsulated and send via
|
||||
UDP from two channels, for DF and non-DF cases. In the non-DF
|
||||
case, it can just send the packets out of the UDP Listener
|
||||
socket. In the DF case, it needs its own socket so that it can do
|
||||
PMTU discovery easily. To do this, it uses a Raw IP socket (IP has
|
||||
no ports, so there's no collision issue with the UDP Listener
|
||||
socket) and so it must add UDP headers itself.
|
||||
6b. If we did not initiate this connection then the UDP Listener
|
||||
should start receiving fast heartbeats from the remote peer. From
|
||||
those it should be able to identify the local connection via the
|
||||
local peer. It will tell the local connection (communicating to
|
||||
the actor thread) about the UDP address of the remote peer. The
|
||||
local connection will then start its forwarder threads as
|
||||
local connection will then start its forwarder thread as
|
||||
described in 6a, and start sending fast heartbeats. We send to the
|
||||
remote peer via TCP a ConnectionEstablished message. The remote
|
||||
peer receives this (on the TCP receiver process), tells the
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
# Overlay Method Selection
|
||||
|
||||
Weave automatically selects the best working overlay implementation
|
||||
when connecting to a new peer. It does this by initiating both
|
||||
forwarders in parallel, eventually settling on the most preferred
|
||||
established forwarder at which point the other forwarder is shut down,
|
||||
assuming it had not failed already. In the event that the remaining
|
||||
forwarder fails at a later time (e.g. because of a heartbeat timeout)
|
||||
the control plane TCP connection will recycle allowing the selection
|
||||
algorithm to run again. By default fast datapath is preferred over
|
||||
Sleeve.
|
||||
|
||||
# Local Bridging
|
||||
|
||||
Unlike the weave bridge netdev used by Sleeve, the OVS datapath has no
|
||||
inherent bridging capability. Consequently fastdp implements an
|
||||
ethernet bridge in addition to the vxlan overlay, maintaining its own
|
||||
forwarding database by learning MAC addresses and dispatching
|
||||
broadcast, unicast & multicast traffic accordingly.
|
||||
|
||||
# Short Peer IDs
|
||||
|
||||
The router relies on having access to the source and destination peer
|
||||
when deciding how to forward packets. When the Sleeve overlay is in
|
||||
use this information is conveyed directly within the encapsulation by
|
||||
including the names of the peers in question, a solution not
|
||||
accomodated directly by the vxlan wire format. Fortunately vxlan does
|
||||
have a twenty four bit segment ID field in the header we can use to
|
||||
encode this data - the challenge is to identify peers uniquely with a
|
||||
twelve bit identifier instead of the seventeen bytes used by Sleeve.
|
||||
In practice this is achieved by peers adopting a random 'short ID' and
|
||||
resolving ownership collisions of same via the existing gossip
|
||||
mechanism.
|
||||
|
||||
# Heartbeats via vxlan
|
||||
|
||||
Heartbeats are implemented by sending an encapsulated ethernet frame
|
||||
with source and destination MAC addresses of `00:00:00:00:00:00` and a
|
||||
payload consisting of the connection UID and total ethernet frame
|
||||
length. The vxlan vport miss handler on the receiving side detects the
|
||||
all-zero addresses and acknowledges the heartbeat via the TCP control
|
||||
channel after validating the connection UID and frame length against
|
||||
their expected values.
|
||||
|
||||
# PMTU Discovery
|
||||
|
||||
The length of the heartbeat ethernet frame is set to the MTU of the
|
||||
datapath interface. In the event the vxlan packet is dropped or
|
||||
truncated, the heartbeat will not be acknowledged; this lack of
|
||||
acknowledgement will cause the peers to fall back to the Sleeve
|
||||
overlay, which has a more sophisticated dynamic mechanism for coping
|
||||
with low path MTUs.
|
||||
|
||||
To avoid triggering this fallback in typical deployments, the datapath
|
||||
interface is statically configured with an MTU of 1410 bytes allowing
|
||||
it to work with most underlay network provider MTUs, including GCE at
|
||||
1460 bytes (the fifty byte difference accomodates the vxlan overhead).
|
||||
This value can be overriden by setting `WEAVE_MTU` at launch if
|
||||
necessary.
|
||||
|
||||
# Virtual Ports
|
||||
|
||||
There are three kinds of virtual port associated with the weave
|
||||
datapath:
|
||||
|
||||
* `internal` - one of these is created automatically, named after the
|
||||
datapath. It corresponds to the network interface of the same name
|
||||
that appears on the host when the datapath is created; it is the
|
||||
ingress/egress port for `weave expose`
|
||||
* `netdev` - one for each application container, corresponding to the
|
||||
host end of the veth pair
|
||||
* `vxlan` - one for each UDP port on which we are listening for vxlan
|
||||
encapsulated packets. Typically there is only one of these, but
|
||||
there may be more in a network in which peers do not all use the same
|
||||
port configuration
|
||||
|
||||
# Misses and Flow Creation
|
||||
|
||||
As mentioned above, the datapath has no inherent behaviour - any
|
||||
ingressing packet which does not match a flow is passed to a userspace
|
||||
miss handler. The miss handler is then responsible for a) instructing
|
||||
the datapath to take actions for the packet that caused the miss (for
|
||||
example by copying it to one or more ports) and optionally b)
|
||||
installing flow rules which will allow the datapath to act on similar
|
||||
packets in future without invoking the miss handler.
|
||||
|
||||
At a high level the fast datapath implementation can be viewed as a
|
||||
set of miss handlers that determine what actions the OVS datapath
|
||||
should take based on router topology information, together with some
|
||||
additional machinery that manages the expiry of resulting flows when
|
||||
that state changes.
|
||||
|
||||
When a miss handler is invoked, it has two pieces of context: a byte
|
||||
array containing the packet that triggered the miss, and a set of
|
||||
'flow keys' that have been extracted from the packet by the kernel
|
||||
module. The following flow keys are of interest to weave:
|
||||
|
||||
* `InPort` - the identifier of the ingress virtual port
|
||||
* `Ethernet` - the source and destination MAC addresses of the
|
||||
ethernet frame
|
||||
* `Tunnel` - tunnel identifier (see section on short peer IDs above),
|
||||
and source/dest IPv4 addresses. Only present for ingress via a vxlan
|
||||
vport
|
||||
|
||||
Crucially, the router must use this information alone to determine
|
||||
which actions to take - this allows the specification of a flow which
|
||||
matches these keys and instructs the kernel to take action
|
||||
automatically in future without further userspace involvement.
|
||||
|
||||
Two actions are of interest:
|
||||
|
||||
* `Output` - output the packet to the specified vport
|
||||
* `SetTunnel` - update the effective vxlan tunnel parameters. Only
|
||||
required prior to output from a vxlan vport
|
||||
|
||||
It is possible to have multiple actions in a flow, so the router can
|
||||
for example create a single rule that copies broadcast traffic to all
|
||||
'local' vports (save the ingress vport obviously) as well as to vxlan
|
||||
vports for onward peers as dictated by the routing topology.
|
||||
|
||||
Every flow specified by the router has the following characteristics:
|
||||
|
||||
* An `InPort` key matching the ingress vport
|
||||
* A `Tunnel` key if the ingress vport is of type vxlan
|
||||
* An `Ethernet` key matching the source and destination MAC
|
||||
* A list of `Output` (for internal and netdev vports) and
|
||||
`SetTunnel`+`Output` (for vxlan vports) actions
|
||||
|
||||
Under certain circumstances the miss handler will instruct the
|
||||
datapath to execute a set of actions for a packet without creating a
|
||||
corresponding flow:
|
||||
|
||||
* When broadcasting packets to discover the path to unknown
|
||||
destination MACs. This condition is transient in the overwhelming
|
||||
majority of cases and so the benefit of creating a flow is
|
||||
outweighed by the need to invalidate it once the MAC is learned
|
||||
* When the packet also needs to be forwarded to one or more peers via
|
||||
Sleeve. In this case the router needs to handle all subsequent
|
||||
matching packets as there is no OVS flow action to do it without
|
||||
userspace involvement
|
||||
|
||||
# Flow Invalidation
|
||||
|
||||
Once a flow has been created for a particular combination of keys, the
|
||||
miss handler will never again be invoked for matching packets. It is
|
||||
therefore extremely important that we detect events which invalidate
|
||||
existing flow actions:
|
||||
|
||||
* Addition of netdev vports to the datapath
|
||||
* Route invalidation (topology change)
|
||||
* Short peer ID collision
|
||||
|
||||
The response in each case is the same - delete all flows from the
|
||||
datapath, allowing them to be recreated taking into account the
|
||||
updated state.
|
||||
|
||||
In addition to these event based invalidations there is an expiry
|
||||
process that executes every five minutes. This process enumerates all
|
||||
flows in the datapath, removing any which have not been used since the
|
||||
last check; this cleans up:
|
||||
|
||||
* Flows referring to netdev vports which have been removed
|
||||
* Flows created by forwarders which have been stopped
|
||||
* Flows related to MAC addresses which have not communicated recently
|
||||
|
||||
Flows keyed on tunnel IPv4 address do not need to be cleared when a
|
||||
peer appears to change IP address due to NAT; this will cause a miss
|
||||
resulting in a new flow, and the old flow will expire naturally via
|
||||
the timer mechanism.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 43 KiB |
@@ -38,6 +38,11 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
alloc.establishRing()
|
||||
|
||||
if ok, addr := alloc.space.Allocate(g.r); ok {
|
||||
// If caller hasn't supplied a unique ID, file it under the IP address
|
||||
// which lets the caller then release the address using DELETE /ip/address
|
||||
if g.ident == "_" {
|
||||
g.ident = addr.String()
|
||||
}
|
||||
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
|
||||
alloc.addOwned(g.ident, addr)
|
||||
g.resultChan <- allocateResult{addr, nil}
|
||||
|
||||
+98
-64
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/ipam/ring"
|
||||
"github.com/weaveworks/weave/ipam/space"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
// Kinds of message we can unicast to other peers
|
||||
@@ -43,37 +43,39 @@ type operation interface {
|
||||
// are used around data structures.
|
||||
type Allocator struct {
|
||||
actionChan chan<- func()
|
||||
ourName router.PeerName
|
||||
ourName mesh.PeerName
|
||||
universe address.Range // superset of all ranges
|
||||
ring *ring.Ring // information on ranges owned by all peers
|
||||
space space.Space // more detail on ranges owned by us
|
||||
owned map[string][]address.Address // who owns what addresses, indexed by container-ID
|
||||
nicknames map[router.PeerName]string // so we can map nicknames for rmpeer
|
||||
nicknames map[mesh.PeerName]string // so we can map nicknames for rmpeer
|
||||
pendingAllocates []operation // held until we get some free space
|
||||
pendingClaims []operation // held until we know who owns the space
|
||||
gossip router.Gossip // our link to the outside world for sending messages
|
||||
gossip mesh.Gossip // our link to the outside world for sending messages
|
||||
paxos *paxos.Node
|
||||
paxosTicker *time.Ticker
|
||||
shuttingDown bool // to avoid doing any requests while trying to shut down
|
||||
isKnownPeer func(mesh.PeerName) bool
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewAllocator creates and initialises a new Allocator
|
||||
func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname string, universe address.Range, quorum uint) *Allocator {
|
||||
func NewAllocator(ourName mesh.PeerName, ourUID mesh.PeerUID, ourNickname string, universe address.Range, quorum uint, isKnownPeer func(name mesh.PeerName) bool) *Allocator {
|
||||
return &Allocator{
|
||||
ourName: ourName,
|
||||
universe: universe,
|
||||
ring: ring.New(universe.Start, universe.End, ourName),
|
||||
owned: make(map[string][]address.Address),
|
||||
paxos: paxos.NewNode(ourName, ourUID, quorum),
|
||||
nicknames: map[router.PeerName]string{ourName: ourNickname},
|
||||
now: time.Now,
|
||||
ourName: ourName,
|
||||
universe: universe,
|
||||
ring: ring.New(universe.Start, universe.End, ourName),
|
||||
owned: make(map[string][]address.Address),
|
||||
paxos: paxos.NewNode(ourName, ourUID, quorum),
|
||||
nicknames: map[mesh.PeerName]string{ourName: ourNickname},
|
||||
isKnownPeer: isKnownPeer,
|
||||
now: time.Now,
|
||||
}
|
||||
}
|
||||
|
||||
// Start runs the allocator goroutine
|
||||
func (alloc *Allocator) Start() {
|
||||
actionChan := make(chan func(), router.ChannelSize)
|
||||
actionChan := make(chan func(), mesh.ChannelSize)
|
||||
alloc.actionChan = actionChan
|
||||
go alloc.actorLoop(actionChan)
|
||||
}
|
||||
@@ -163,7 +165,7 @@ func (alloc *Allocator) tryPendingOps() {
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) spaceRequestDenied(sender router.PeerName, r address.Range) {
|
||||
func (alloc *Allocator) spaceRequestDenied(sender mesh.PeerName, r address.Range) {
|
||||
for i := 0; i < len(alloc.pendingClaims); {
|
||||
claim := alloc.pendingClaims[i].(*claim)
|
||||
if r.Contains(claim.addr) {
|
||||
@@ -175,17 +177,6 @@ func (alloc *Allocator) spaceRequestDenied(sender router.PeerName, r address.Ran
|
||||
}
|
||||
}
|
||||
|
||||
func hasBeenCancelled(cancelChan <-chan bool) func() bool {
|
||||
return func() bool {
|
||||
select {
|
||||
case <-cancelChan:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type errorCancelled struct {
|
||||
kind string
|
||||
ident string
|
||||
@@ -199,10 +190,9 @@ func (e *errorCancelled) Error() string {
|
||||
|
||||
// Allocate (Sync) - get new IP address for container with given name in range
|
||||
// if there isn't any space in that range we block indefinitely
|
||||
func (alloc *Allocator) Allocate(ident string, r address.Range, cancelChan <-chan bool) (address.Address, error) {
|
||||
func (alloc *Allocator) Allocate(ident string, r address.Range, hasBeenCancelled func() bool) (address.Address, error) {
|
||||
resultChan := make(chan allocateResult)
|
||||
op := &allocate{resultChan: resultChan, ident: ident, r: r,
|
||||
hasBeenCancelled: hasBeenCancelled(cancelChan)}
|
||||
op := &allocate{resultChan: resultChan, ident: ident, r: r, hasBeenCancelled: hasBeenCancelled}
|
||||
alloc.doOperation(op, &alloc.pendingAllocates)
|
||||
result := <-resultChan
|
||||
return result.addr, result.err
|
||||
@@ -237,6 +227,8 @@ func (alloc *Allocator) ContainerDied(ident string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) ContainerStarted(ident string) {}
|
||||
|
||||
// Delete (Sync) - release all IP addresses for container with given name
|
||||
func (alloc *Allocator) Delete(ident string) error {
|
||||
errChan := make(chan error)
|
||||
@@ -284,6 +276,33 @@ func (alloc *Allocator) Free(ident string, addrToFree address.Address) error {
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func (alloc *Allocator) pickPeerFromNicknames(isValid func(mesh.PeerName) bool) mesh.PeerName {
|
||||
for name := range alloc.nicknames {
|
||||
if name != alloc.ourName && isValid(name) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return mesh.UnknownPeerName
|
||||
}
|
||||
|
||||
func (alloc *Allocator) pickPeerForTransfer() mesh.PeerName {
|
||||
// first try alive peers that actively participate in IPAM (i.e. have entries)
|
||||
if heir := alloc.ring.PickPeerForTransfer(alloc.isKnownPeer); heir != mesh.UnknownPeerName {
|
||||
return heir
|
||||
}
|
||||
// next try alive peers that have IPAM enabled but have no entries
|
||||
if heir := alloc.pickPeerFromNicknames(alloc.isKnownPeer); heir != mesh.UnknownPeerName {
|
||||
return heir
|
||||
}
|
||||
// next try disappeared peers that still have entries
|
||||
t := func(mesh.PeerName) bool { return true }
|
||||
if heir := alloc.ring.PickPeerForTransfer(t); heir != mesh.UnknownPeerName {
|
||||
return heir
|
||||
}
|
||||
// finally, disappeared peers that that passively participated in IPAM
|
||||
return alloc.pickPeerFromNicknames(t)
|
||||
}
|
||||
|
||||
// Shutdown (Sync)
|
||||
func (alloc *Allocator) Shutdown() {
|
||||
alloc.infof("Shutdown")
|
||||
@@ -292,7 +311,7 @@ func (alloc *Allocator) Shutdown() {
|
||||
alloc.shuttingDown = true
|
||||
alloc.cancelOps(&alloc.pendingClaims)
|
||||
alloc.cancelOps(&alloc.pendingAllocates)
|
||||
if heir := alloc.ring.PickPeerForTransfer(); heir != router.UnknownPeerName {
|
||||
if heir := alloc.pickPeerForTransfer(); heir != mesh.UnknownPeerName {
|
||||
alloc.ring.Transfer(alloc.ourName, heir)
|
||||
alloc.space.Clear()
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
@@ -320,7 +339,6 @@ func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) error {
|
||||
return
|
||||
}
|
||||
|
||||
delete(alloc.nicknames, peername)
|
||||
newRanges, err := alloc.ring.Transfer(peername, alloc.ourName)
|
||||
alloc.space.AddRanges(newRanges)
|
||||
resultChan <- err
|
||||
@@ -332,33 +350,45 @@ func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) error {
|
||||
// call into the router for this because we are interested in peers
|
||||
// that have gone away but are still in the ring, which is why we
|
||||
// maintain our own nicknames map.
|
||||
func (alloc *Allocator) lookupPeername(name string) (router.PeerName, error) {
|
||||
func (alloc *Allocator) lookupPeername(name string) (mesh.PeerName, error) {
|
||||
for peername, nickname := range alloc.nicknames {
|
||||
if nickname == name {
|
||||
return peername, nil
|
||||
}
|
||||
}
|
||||
|
||||
return router.PeerNameFromString(name)
|
||||
return mesh.PeerNameFromString(name)
|
||||
}
|
||||
|
||||
// Restrict the peers in "nicknames" to those in the ring and our own
|
||||
// Restrict the peers in "nicknames" to those in the ring plus peers known to the router
|
||||
func (alloc *Allocator) pruneNicknames() {
|
||||
ringPeers := alloc.ring.PeerNames()
|
||||
for name := range alloc.nicknames {
|
||||
if _, ok := ringPeers[name]; !ok && name != alloc.ourName {
|
||||
if _, ok := ringPeers[name]; !ok && !alloc.isKnownPeer(name) {
|
||||
delete(alloc.nicknames, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) annotatePeernames(names []mesh.PeerName) []string {
|
||||
var res []string
|
||||
for _, name := range names {
|
||||
if nickname, found := alloc.nicknames[name]; found {
|
||||
res = append(res, fmt.Sprint(name, "(", nickname, ")"))
|
||||
} else {
|
||||
res = append(res, name.String())
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func decodeRange(msg []byte) (r address.Range, err error) {
|
||||
decoder := gob.NewDecoder(bytes.NewReader(msg))
|
||||
return r, decoder.Decode(&r)
|
||||
}
|
||||
|
||||
// OnGossipUnicast (Sync)
|
||||
func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) error {
|
||||
func (alloc *Allocator) OnGossipUnicast(sender mesh.PeerName, msg []byte) error {
|
||||
alloc.debugln("OnGossipUnicast from", sender, ": ", len(msg), "bytes")
|
||||
resultChan := make(chan error)
|
||||
alloc.actionChan <- func() {
|
||||
@@ -384,7 +414,7 @@ func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) erro
|
||||
}
|
||||
|
||||
// OnGossipBroadcast (Sync)
|
||||
func (alloc *Allocator) OnGossipBroadcast(sender router.PeerName, msg []byte) (router.GossipData, error) {
|
||||
func (alloc *Allocator) OnGossipBroadcast(sender mesh.PeerName, msg []byte) (mesh.GossipData, error) {
|
||||
alloc.debugln("OnGossipBroadcast from", sender, ":", len(msg), "bytes")
|
||||
resultChan := make(chan error)
|
||||
alloc.actionChan <- func() {
|
||||
@@ -397,7 +427,7 @@ type gossipState struct {
|
||||
// We send a timstamp along with the information to be
|
||||
// gossipped in order to detect skewed clocks
|
||||
Now int64
|
||||
Nicknames map[router.PeerName]string
|
||||
Nicknames map[mesh.PeerName]string
|
||||
|
||||
Paxos paxos.GossipState
|
||||
Ring *ring.Ring
|
||||
@@ -433,11 +463,11 @@ func (alloc *Allocator) Encode() []byte {
|
||||
}
|
||||
|
||||
// OnGossip (Sync)
|
||||
func (alloc *Allocator) OnGossip(msg []byte) (router.GossipData, error) {
|
||||
func (alloc *Allocator) OnGossip(msg []byte) (mesh.GossipData, error) {
|
||||
alloc.debugln("Allocator.OnGossip:", len(msg), "bytes")
|
||||
resultChan := make(chan error)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.update(router.UnknownPeerName, msg)
|
||||
resultChan <- alloc.update(mesh.UnknownPeerName, msg)
|
||||
}
|
||||
return nil, <-resultChan // for now, we never propagate updates. TBD
|
||||
}
|
||||
@@ -448,8 +478,8 @@ type ipamGossipData struct {
|
||||
alloc *Allocator
|
||||
}
|
||||
|
||||
func (d *ipamGossipData) Merge(other router.GossipData) {
|
||||
// no-op
|
||||
func (d *ipamGossipData) Merge(other mesh.GossipData) mesh.GossipData {
|
||||
return d // no-op
|
||||
}
|
||||
|
||||
func (d *ipamGossipData) Encode() [][]byte {
|
||||
@@ -458,12 +488,12 @@ func (d *ipamGossipData) Encode() [][]byte {
|
||||
|
||||
// Gossip returns a GossipData implementation, which in this case always
|
||||
// returns the latest ring state (and does nothing on merge)
|
||||
func (alloc *Allocator) Gossip() router.GossipData {
|
||||
func (alloc *Allocator) Gossip() mesh.GossipData {
|
||||
return &ipamGossipData{alloc}
|
||||
}
|
||||
|
||||
// SetInterfaces gives the allocator two interfaces for talking to the outside world
|
||||
func (alloc *Allocator) SetInterfaces(gossip router.Gossip) {
|
||||
func (alloc *Allocator) SetInterfaces(gossip mesh.Gossip) {
|
||||
alloc.gossip = gossip
|
||||
}
|
||||
|
||||
@@ -510,7 +540,7 @@ func (alloc *Allocator) establishRing() {
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) createRing(peers []router.PeerName) {
|
||||
func (alloc *Allocator) createRing(peers []mesh.PeerName) {
|
||||
alloc.debugln("Paxos consensus:", peers)
|
||||
alloc.ring.ClaimForPeers(normalizeConsensus(peers))
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
@@ -533,7 +563,7 @@ func (alloc *Allocator) ringUpdated() {
|
||||
}
|
||||
|
||||
// For compatibility with sort.Interface
|
||||
type peerNames []router.PeerName
|
||||
type peerNames []mesh.PeerName
|
||||
|
||||
func (a peerNames) Len() int { return len(a) }
|
||||
func (a peerNames) Less(i, j int) bool { return a[i] < a[j] }
|
||||
@@ -542,7 +572,7 @@ func (a peerNames) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
// When we get a consensus from Paxos, the peer names are not in a
|
||||
// defined order and may contain duplicates. This function sorts them
|
||||
// and de-dupes.
|
||||
func normalizeConsensus(consensus []router.PeerName) []router.PeerName {
|
||||
func normalizeConsensus(consensus []mesh.PeerName) []mesh.PeerName {
|
||||
if len(consensus) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -577,22 +607,22 @@ func encodeRange(r address.Range) []byte {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) error {
|
||||
msg := router.Concat([]byte{msgSpaceRequest}, encodeRange(r))
|
||||
func (alloc *Allocator) sendSpaceRequest(dest mesh.PeerName, r address.Range) error {
|
||||
msg := append([]byte{msgSpaceRequest}, encodeRange(r)...)
|
||||
return alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendSpaceRequestDenied(dest router.PeerName, r address.Range) error {
|
||||
msg := router.Concat([]byte{msgSpaceRequestDenied}, encodeRange(r))
|
||||
func (alloc *Allocator) sendSpaceRequestDenied(dest mesh.PeerName, r address.Range) error {
|
||||
msg := append([]byte{msgSpaceRequestDenied}, encodeRange(r)...)
|
||||
return alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendRingUpdate(dest router.PeerName) {
|
||||
msg := router.Concat([]byte{msgRingUpdate}, alloc.encode())
|
||||
func (alloc *Allocator) sendRingUpdate(dest mesh.PeerName) {
|
||||
msg := append([]byte{msgRingUpdate}, alloc.encode()...)
|
||||
alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) update(sender router.PeerName, msg []byte) error {
|
||||
func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error {
|
||||
reader := bytes.NewReader(msg)
|
||||
decoder := gob.NewDecoder(reader)
|
||||
var data gossipState
|
||||
@@ -616,16 +646,20 @@ func (alloc *Allocator) update(sender router.PeerName, msg []byte) error {
|
||||
// shouldn't get updates for a empty Ring. But tolerate
|
||||
// them just in case.
|
||||
if data.Ring != nil {
|
||||
if data.Ring.Range() != alloc.universe {
|
||||
return fmt.Errorf("Incompatible IP allocation range %s; ours is %s",
|
||||
data.Ring.Range().AsCIDRString(), alloc.universe.AsCIDRString())
|
||||
switch err = alloc.ring.Merge(*data.Ring); err {
|
||||
case ring.ErrDifferentSeeds:
|
||||
return fmt.Errorf("IP allocation was seeded by different peers (received: %v, ours: %v)",
|
||||
alloc.annotatePeernames(data.Ring.Seeds), alloc.annotatePeernames(alloc.ring.Seeds))
|
||||
case ring.ErrDifferentRange:
|
||||
return fmt.Errorf("Incompatible IP allocation ranges (received: %s, ours: %s)",
|
||||
data.Ring.Range().AsCIDRString(), alloc.ring.Range().AsCIDRString())
|
||||
default:
|
||||
if err == nil && !alloc.ring.Empty() {
|
||||
alloc.pruneNicknames()
|
||||
alloc.ringUpdated()
|
||||
}
|
||||
return err
|
||||
}
|
||||
err = alloc.ring.Merge(*data.Ring)
|
||||
if !alloc.ring.Empty() {
|
||||
alloc.pruneNicknames()
|
||||
alloc.ringUpdated()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if data.Paxos != nil {
|
||||
@@ -640,7 +674,7 @@ func (alloc *Allocator) update(sender router.PeerName, msg []byte) error {
|
||||
alloc.createRing(cons.Value)
|
||||
}
|
||||
}
|
||||
} else if sender != router.UnknownPeerName {
|
||||
} else if sender != mesh.UnknownPeerName {
|
||||
// Sender is trying to initialize a ring, but we have one
|
||||
// already - send it straight back
|
||||
alloc.sendRingUpdate(sender)
|
||||
@@ -650,7 +684,7 @@ func (alloc *Allocator) update(sender router.PeerName, msg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (alloc *Allocator) donateSpace(r address.Range, to router.PeerName) {
|
||||
func (alloc *Allocator) donateSpace(r address.Range, to mesh.PeerName) {
|
||||
// No matter what we do, we'll send a unicast gossip
|
||||
// of our ring back to tha chap who asked for space.
|
||||
// This serves to both tell him of any space we might
|
||||
|
||||
+34
-24
@@ -20,6 +20,8 @@ const (
|
||||
testStart3 = "10.0.3.0"
|
||||
)
|
||||
|
||||
func returnFalse() bool { return false }
|
||||
|
||||
func TestAllocFree(t *testing.T) {
|
||||
const (
|
||||
container1 = "abcdef"
|
||||
@@ -39,31 +41,31 @@ func TestAllocFree(t *testing.T) {
|
||||
_, cidr2, _ := address.ParseCIDR(subnet2)
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), nil)
|
||||
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), returnFalse)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
|
||||
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), nil)
|
||||
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), returnFalse)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr2, addr2.String(), "address")
|
||||
|
||||
// Ask for another address for a different container and check it's different
|
||||
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), nil)
|
||||
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), returnFalse)
|
||||
if addr1b.String() == testAddr1 {
|
||||
t.Fatalf("Expected different address but got %s", addr1b.String())
|
||||
}
|
||||
|
||||
// Ask for the first container again and we should get the same addresses again
|
||||
addr1a, _ := alloc.Allocate(container1, cidr1.HostRange(), nil)
|
||||
addr1a, _ := alloc.Allocate(container1, cidr1.HostRange(), returnFalse)
|
||||
require.Equal(t, testAddr1, addr1a.String(), "address")
|
||||
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), nil)
|
||||
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), returnFalse)
|
||||
require.Equal(t, testAddr2, addr2a.String(), "address")
|
||||
|
||||
// Now delete the first container, and we should get its addresses back
|
||||
require.NoError(t, alloc.Delete(container1))
|
||||
addr3, _ := alloc.Allocate(container3, cidr1.HostRange(), nil)
|
||||
addr3, _ := alloc.Allocate(container3, cidr1.HostRange(), returnFalse)
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), nil)
|
||||
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), returnFalse)
|
||||
require.Equal(t, testAddr2, addr4.String(), "address")
|
||||
|
||||
alloc.ContainerDied(container2)
|
||||
@@ -93,7 +95,7 @@ func TestBootstrap(t *testing.T) {
|
||||
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
alloc1.Allocate("somecontainer", subnet, nil)
|
||||
alloc1.Allocate("somecontainer", subnet, returnFalse)
|
||||
done <- true
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -147,15 +149,15 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Do one allocate to ensure paxos is all done
|
||||
alloc.Allocate("unused", subnet, nil)
|
||||
alloc.Allocate("unused", subnet, returnFalse)
|
||||
// Do an allocate on the other peer, which we will try to claim later
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, nil)
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, returnFalse)
|
||||
|
||||
// Now try the claim again
|
||||
err = alloc.Claim(container3, addr1, true)
|
||||
require.NoError(t, err)
|
||||
// Check we get this address back if we try an allocate
|
||||
addr3, _ := alloc.Allocate(container3, subnet, nil)
|
||||
addr3, _ := alloc.Allocate(container3, subnet, returnFalse)
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
// one more claim should still work
|
||||
err = alloc.Claim(container3, addr1, true)
|
||||
@@ -206,9 +208,9 @@ func TestCancel(t *testing.T) {
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
// Get some IPs, so each allocator has some space
|
||||
res1, _ := alloc1.Allocate("foo", subnet, nil)
|
||||
res1, _ := alloc1.Allocate("foo", subnet, returnFalse)
|
||||
common.Log.Debugf("res1 = %s", res1.String())
|
||||
res2, _ := alloc2.Allocate("bar", subnet, nil)
|
||||
res2, _ := alloc2.Allocate("bar", subnet, returnFalse)
|
||||
common.Log.Debugf("res2 = %s", res2.String())
|
||||
if res1 == res2 {
|
||||
require.FailNow(t, "Error: got same ips!")
|
||||
@@ -220,12 +222,20 @@ func TestCancel(t *testing.T) {
|
||||
|
||||
// Use up all the IPs that alloc1 owns, so the allocation after this will prompt a request to alloc2
|
||||
for i := 0; alloc1.NumFreeAddresses(subnet) > 0; i++ {
|
||||
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, nil)
|
||||
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, returnFalse)
|
||||
}
|
||||
cancelChan := make(chan bool, 1)
|
||||
doneChan := make(chan bool)
|
||||
go func() {
|
||||
_, ok := alloc1.Allocate("baz", subnet, cancelChan)
|
||||
_, ok := alloc1.Allocate("baz", subnet,
|
||||
func() bool {
|
||||
select {
|
||||
case <-cancelChan:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
doneChan <- ok == nil
|
||||
}()
|
||||
|
||||
@@ -252,7 +262,7 @@ func TestCancelOnDied(t *testing.T) {
|
||||
|
||||
doneChan := make(chan bool)
|
||||
f := func() {
|
||||
_, ok := alloc1.Allocate(container1, subnet, nil)
|
||||
_, ok := alloc1.Allocate(container1, subnet, returnFalse)
|
||||
doneChan <- ok == nil
|
||||
}
|
||||
|
||||
@@ -283,11 +293,11 @@ func TestGossipShutdown(t *testing.T) {
|
||||
defer alloc.Stop()
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
alloc.Allocate(container1, subnet, nil)
|
||||
alloc.Allocate(container1, subnet, returnFalse)
|
||||
|
||||
alloc.Shutdown()
|
||||
|
||||
_, err := alloc.Allocate(container2, subnet, nil) // trying to allocate after shutdown should fail
|
||||
_, err := alloc.Allocate(container2, subnet, returnFalse) // trying to allocate after shutdown should fail
|
||||
require.False(t, err == nil, "no address")
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc)
|
||||
@@ -302,10 +312,10 @@ func TestTransfer(t *testing.T) {
|
||||
alloc2 := allocs[1]
|
||||
alloc3 := allocs[2] // This will be 'master' and get the first range
|
||||
|
||||
_, err := alloc2.Allocate("foo", subnet, nil)
|
||||
_, err := alloc2.Allocate("foo", subnet, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
|
||||
_, err = alloc3.Allocate("bar", subnet, nil)
|
||||
_, err = alloc3.Allocate("bar", subnet, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
|
||||
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
|
||||
@@ -323,7 +333,7 @@ func TestTransfer(t *testing.T) {
|
||||
|
||||
require.Equal(t, address.Offset(1022), alloc1.NumFreeAddresses(subnet))
|
||||
|
||||
_, err = alloc1.Allocate("foo", subnet, nil)
|
||||
_, err = alloc1.Allocate("foo", subnet, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
alloc1.Stop()
|
||||
}
|
||||
@@ -338,7 +348,7 @@ func TestFakeRouterSimple(t *testing.T) {
|
||||
alloc1 := allocs[0]
|
||||
//alloc2 := allocs[1]
|
||||
|
||||
_, err := alloc1.Allocate("foo", subnet, nil)
|
||||
_, err := alloc1.Allocate("foo", subnet, returnFalse)
|
||||
require.NoError(t, err, "Failed to get address")
|
||||
}
|
||||
|
||||
@@ -419,7 +429,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
alloc := allocs[allocIndex]
|
||||
//common.Log.Infof("Allocate: asking allocator %d", allocIndex)
|
||||
addr, err := alloc.Allocate(name, subnet, nil)
|
||||
addr, err := alloc.Allocate(name, subnet, returnFalse)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not allocate addr"))
|
||||
@@ -476,7 +486,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
|
||||
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
|
||||
|
||||
newAddr, _ := alloc.Allocate(res.name, subnet, nil)
|
||||
newAddr, _ := alloc.Allocate(res.name, subnet, returnFalse)
|
||||
oldAddr, _ := address.ParseIP(addr)
|
||||
if newAddr != oldAddr {
|
||||
panic(fmt.Sprintf("Got different address for repeat request for %s: %s != %s", res.name, newAddr, oldAddr))
|
||||
|
||||
+3
-3
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
@@ -43,7 +43,7 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
switch owner := alloc.ring.Owner(c.addr); owner {
|
||||
case alloc.ourName:
|
||||
// success
|
||||
case router.UnknownPeerName:
|
||||
case mesh.UnknownPeerName:
|
||||
// If our ring doesn't know, it must be empty.
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: address allocator still initializing; will try later.", c.addr, c.ident)
|
||||
@@ -86,7 +86,7 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *claim) deniedBy(alloc *Allocator, owner router.PeerName) {
|
||||
func (c *claim) deniedBy(alloc *Allocator, owner mesh.PeerName) {
|
||||
name, found := alloc.nicknames[owner]
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
|
||||
+15
-7
@@ -31,7 +31,17 @@ func parseCIDR(w http.ResponseWriter, cidrStr string) (address.CIDR, bool) {
|
||||
|
||||
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
|
||||
closedChan := w.(http.CloseNotifier).CloseNotify()
|
||||
addr, err := alloc.Allocate(ident, subnet.HostRange(), closedChan)
|
||||
addr, err := alloc.Allocate(ident, subnet.HostRange(),
|
||||
func() bool {
|
||||
select {
|
||||
case <-closedChan:
|
||||
return true
|
||||
default:
|
||||
res := checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident)
|
||||
checkAlive = false // we check only once; if the container dies later we learn about that through events
|
||||
return res
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
if _, ok := err.(*errorCancelled); ok { // cancellation is not really an error
|
||||
common.Log.Infoln("[allocator]:", err.Error())
|
||||
@@ -41,18 +51,16 @@ func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.Resp
|
||||
badRequest(w, err)
|
||||
return
|
||||
}
|
||||
if checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
|
||||
common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, addr)
|
||||
alloc.Free(ident, addr)
|
||||
fmt.Fprint(w, "cancelled")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
|
||||
}
|
||||
|
||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
|
||||
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "%s", defaultSubnet)
|
||||
})
|
||||
|
||||
router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
ident := vars["id"]
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func HTTPPost(t *testing.T, url string) string {
|
||||
@@ -145,12 +144,6 @@ func TestBadHttp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHTTPCancel(t *testing.T) {
|
||||
wt.RunWithTimeout(t, 2*time.Second, func() {
|
||||
impTestHTTPCancel(t)
|
||||
})
|
||||
}
|
||||
|
||||
func impTestHTTPCancel(t *testing.T) {
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
testCIDR1 = "10.0.3.0/29"
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
package paxos
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/router"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
)
|
||||
|
||||
// The node identifier. The use of the UID here is important: Paxos
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
// node does not restart and lose its Paxos state but claim to have
|
||||
// the same ID.
|
||||
type NodeID struct {
|
||||
Name router.PeerName
|
||||
UID router.PeerUID
|
||||
Name mesh.PeerName
|
||||
UID mesh.PeerUID
|
||||
}
|
||||
|
||||
// note all fields exported in structs so we can Gob them
|
||||
@@ -39,7 +39,7 @@ func (a ProposalID) valid() bool {
|
||||
}
|
||||
|
||||
// For seeding IPAM, the value we want consensus on is a set of peer names
|
||||
type Value []router.PeerName
|
||||
type Value []mesh.PeerName
|
||||
|
||||
// An AcceptedValue is a Value plus the proposal which originated that
|
||||
// Value. The origin is not essential, but makes comparing
|
||||
@@ -72,7 +72,7 @@ type Node struct {
|
||||
knows GossipState
|
||||
}
|
||||
|
||||
func NewNode(name router.PeerName, uid router.PeerUID, quorum uint) *Node {
|
||||
func NewNode(name mesh.PeerName, uid mesh.PeerUID, quorum uint) *Node {
|
||||
return &Node{
|
||||
id: NodeID{name, uid},
|
||||
quorum: quorum,
|
||||
@@ -223,7 +223,7 @@ func (node *Node) Think() bool {
|
||||
// about. This is not necessarily all peer names, but it is at least
|
||||
// a quorum, and so good enough for seeding the ring.
|
||||
func (node *Node) pickValue() Value {
|
||||
val := make([]router.PeerName, len(node.knows))
|
||||
val := make([]mesh.PeerName, len(node.knows))
|
||||
i := 0
|
||||
for id := range node.knows {
|
||||
val[i] = id.Name
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/router"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
)
|
||||
|
||||
type TestNode struct {
|
||||
@@ -97,8 +97,8 @@ func makeRandomModel(params *TestParams, r *rand.Rand, t *testing.T) *Model {
|
||||
}
|
||||
|
||||
for i := range m.nodes {
|
||||
m.nodes[i].Node = NewNode(router.PeerName(i/2+1),
|
||||
router.PeerUID(r.Int63()), m.quorum)
|
||||
m.nodes[i].Node = NewNode(mesh.PeerName(i/2+1),
|
||||
mesh.PeerUID(r.Int63()), m.quorum)
|
||||
m.nodes[i].Propose()
|
||||
}
|
||||
|
||||
@@ -172,8 +172,8 @@ func (m *Model) isolateNode(node *TestNode) {
|
||||
|
||||
// Restart a node
|
||||
func (m *Model) restart(node *TestNode) {
|
||||
node.Node = NewNode(router.PeerName(m.nextID),
|
||||
router.PeerUID(m.r.Int63()), m.quorum)
|
||||
node.Node = NewNode(mesh.PeerName(m.nextID),
|
||||
mesh.PeerUID(m.r.Int63()), m.quorum)
|
||||
m.nextID++
|
||||
node.Propose()
|
||||
|
||||
|
||||
+3
-3
@@ -4,14 +4,14 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
// Entry represents entries around the ring
|
||||
type entry struct {
|
||||
Token address.Address // The start of this range
|
||||
Peer router.PeerName // Who owns this range
|
||||
Peer mesh.PeerName // Who owns this range
|
||||
Version uint32 // Version of this range
|
||||
Free address.Offset // Number of free IPs in this range
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func (e *entry) Equal(e2 *entry) bool {
|
||||
e.Version == e2.Version
|
||||
}
|
||||
|
||||
func (e *entry) update(peername router.PeerName, free address.Offset) {
|
||||
func (e *entry) update(peername mesh.PeerName, free address.Offset) {
|
||||
e.Peer = peername
|
||||
e.Version++
|
||||
e.Free = free
|
||||
|
||||
+57
-20
@@ -13,15 +13,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
// Ring represents the ring itself
|
||||
type Ring struct {
|
||||
Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively)
|
||||
Peer router.PeerName // name of peer owning this ring instance
|
||||
Peer mesh.PeerName // name of peer owning this ring instance
|
||||
Entries entries // list of entries sorted by token
|
||||
Seeds []mesh.PeerName // peers with which the ring was seeded
|
||||
}
|
||||
|
||||
func (r *Ring) assertInvariants() {
|
||||
@@ -36,6 +37,7 @@ var (
|
||||
ErrNotSorted = errors.New("Ring not sorted")
|
||||
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
||||
ErrTokenOutOfRange = errors.New("Token is out of range")
|
||||
ErrDifferentSeeds = errors.New("Received ring was seeded differently from ours!")
|
||||
ErrDifferentRange = errors.New("Received range differs from ours!")
|
||||
ErrNewerVersion = errors.New("Received new version for entry I own!")
|
||||
ErrInvalidEntry = errors.New("Received invalid state update!")
|
||||
@@ -82,7 +84,7 @@ func (r *Ring) checkInvariants() error {
|
||||
}
|
||||
|
||||
// New creates an empty ring belonging to peer.
|
||||
func New(start, end address.Address, peer router.PeerName) *Ring {
|
||||
func New(start, end address.Address, peer mesh.PeerName) *Ring {
|
||||
common.Assert(start < end)
|
||||
|
||||
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0)}
|
||||
@@ -109,7 +111,7 @@ func (r *Ring) distance(start, end address.Address) address.Offset {
|
||||
// Preconditions:
|
||||
// - start < end
|
||||
// - [start, end) must be owned by the calling peer
|
||||
func (r *Ring) GrantRangeToHost(start, end address.Address, peer router.PeerName) {
|
||||
func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName) {
|
||||
//fmt.Printf("%s GrantRangeToHost [%v,%v) -> %s\n", r.Peer, start, end, peer)
|
||||
|
||||
r.assertInvariants()
|
||||
@@ -188,6 +190,17 @@ func (r *Ring) Merge(gossip Ring) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(gossip.Seeds) > 0 && len(r.Seeds) > 0 {
|
||||
if len(gossip.Seeds) != len(r.Seeds) {
|
||||
return ErrDifferentSeeds
|
||||
}
|
||||
for i, seed := range gossip.Seeds {
|
||||
if seed != r.Seeds[i] {
|
||||
return ErrDifferentSeeds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Start != gossip.Start || r.End != gossip.End {
|
||||
return ErrDifferentRange
|
||||
}
|
||||
@@ -197,7 +210,7 @@ func (r *Ring) Merge(gossip Ring) error {
|
||||
addToResult := func(e entry) { result = append(result, &e) }
|
||||
|
||||
var mine, theirs *entry
|
||||
var previousOwner *router.PeerName
|
||||
var previousOwner *mesh.PeerName
|
||||
// i is index into r.Entries; j is index into gossip.Entries
|
||||
var i, j int
|
||||
for i < len(r.Entries) && j < len(gossip.Entries) {
|
||||
@@ -254,6 +267,9 @@ func (r *Ring) Merge(gossip Ring) error {
|
||||
previousOwner = nil
|
||||
}
|
||||
|
||||
if len(r.Seeds) == 0 {
|
||||
r.Seeds = gossip.Seeds
|
||||
}
|
||||
r.Entries = result
|
||||
return nil
|
||||
}
|
||||
@@ -300,9 +316,28 @@ func (r *Ring) OwnedRanges() (result []address.Range) {
|
||||
return r.splitRangesOverZero(result)
|
||||
}
|
||||
|
||||
// For printing status
|
||||
type RangeInfo struct {
|
||||
Peer mesh.PeerName
|
||||
address.Range
|
||||
Version uint32
|
||||
}
|
||||
|
||||
func (r *Ring) AllRangeInfo() (result []RangeInfo) {
|
||||
for i, entry := range r.Entries {
|
||||
nextEntry := r.Entries.entry(i + 1)
|
||||
ranges := []address.Range{{Start: entry.Token, End: nextEntry.Token}}
|
||||
ranges = r.splitRangesOverZero(ranges)
|
||||
for _, r := range ranges {
|
||||
result = append(result, RangeInfo{entry.Peer, r, entry.Version})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClaimForPeers claims the entire ring for the array of peers passed
|
||||
// in. Only works for empty rings.
|
||||
func (r *Ring) ClaimForPeers(peers []router.PeerName) {
|
||||
func (r *Ring) ClaimForPeers(peers []mesh.PeerName) {
|
||||
common.Assert(r.Empty())
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
@@ -330,9 +365,11 @@ func (r *Ring) ClaimForPeers(peers []router.PeerName) {
|
||||
}
|
||||
|
||||
common.Assert(pos == r.End)
|
||||
|
||||
r.Seeds = peers
|
||||
}
|
||||
|
||||
func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) {
|
||||
func (r *Ring) FprintWithNicknames(w io.Writer, m map[mesh.PeerName]string) {
|
||||
for _, entry := range r.Entries {
|
||||
nickname, found := m[entry.Peer]
|
||||
if found {
|
||||
@@ -347,7 +384,7 @@ func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) {
|
||||
func (r *Ring) String() string {
|
||||
var buffer bytes.Buffer
|
||||
fmt.Fprintf(&buffer, "Ring [%s, %s)", r.Start, r.End)
|
||||
r.FprintWithNicknames(&buffer, make(map[router.PeerName]string))
|
||||
r.FprintWithNicknames(&buffer, make(map[mesh.PeerName]string))
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
@@ -397,7 +434,7 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
|
||||
|
||||
type weightedPeer struct {
|
||||
weight float64
|
||||
peername router.PeerName
|
||||
peername mesh.PeerName
|
||||
}
|
||||
type weightedPeers []weightedPeer
|
||||
|
||||
@@ -408,10 +445,10 @@ func (ws weightedPeers) Swap(i, j int) { ws[i], ws[j] = ws[j], ws[i] }
|
||||
|
||||
// ChoosePeersToAskForSpace returns all peers we can ask for space in
|
||||
// the range [start, end), in weighted-random order. Assumes start<end.
|
||||
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.PeerName {
|
||||
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []mesh.PeerName {
|
||||
var (
|
||||
sum address.Offset
|
||||
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
|
||||
totalSpacePerPeer = make(map[mesh.PeerName]address.Offset) // Compute total free space per peer
|
||||
)
|
||||
|
||||
// iterate through tokens
|
||||
@@ -445,25 +482,25 @@ func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.Pee
|
||||
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
|
||||
}
|
||||
sort.Sort(ws)
|
||||
result := make([]router.PeerName, len(ws))
|
||||
result := make([]mesh.PeerName, len(ws))
|
||||
for i, wp := range ws {
|
||||
result[i] = wp.peername
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Ring) PickPeerForTransfer() router.PeerName {
|
||||
func (r *Ring) PickPeerForTransfer(isValid func(mesh.PeerName) bool) mesh.PeerName {
|
||||
for _, entry := range r.Entries {
|
||||
if entry.Peer != r.Peer {
|
||||
if entry.Peer != r.Peer && isValid(entry.Peer) {
|
||||
return entry.Peer
|
||||
}
|
||||
}
|
||||
return router.UnknownPeerName
|
||||
return mesh.UnknownPeerName
|
||||
}
|
||||
|
||||
// Transfer will mark all entries associated with 'from' peer as owned by 'to' peer
|
||||
// and return ranges indicating the new space we picked up
|
||||
func (r *Ring) Transfer(from, to router.PeerName) ([]address.Range, error) {
|
||||
func (r *Ring) Transfer(from, to mesh.PeerName) ([]address.Range, error) {
|
||||
r.assertInvariants()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
@@ -493,13 +530,13 @@ func (r *Ring) Contains(addr address.Address) bool {
|
||||
}
|
||||
|
||||
// Owner returns the peername which owns the range containing addr
|
||||
func (r *Ring) Owner(token address.Address) router.PeerName {
|
||||
func (r *Ring) Owner(token address.Address) mesh.PeerName {
|
||||
common.Assert(r.Start <= token && token < r.End)
|
||||
|
||||
r.assertInvariants()
|
||||
// There can be no owners on an empty ring
|
||||
if r.Empty() {
|
||||
return router.UnknownPeerName
|
||||
return mesh.UnknownPeerName
|
||||
}
|
||||
|
||||
// Look for the right-most entry, less than or equal to token
|
||||
@@ -512,8 +549,8 @@ func (r *Ring) Owner(token address.Address) router.PeerName {
|
||||
}
|
||||
|
||||
// Get the set of PeerNames mentioned in the ring
|
||||
func (r *Ring) PeerNames() map[router.PeerName]struct{} {
|
||||
res := make(map[router.PeerName]struct{})
|
||||
func (r *Ring) PeerNames() map[mesh.PeerName]struct{} {
|
||||
res := make(map[mesh.PeerName]struct{})
|
||||
|
||||
for _, entry := range r.Entries {
|
||||
res[entry.Peer] = struct{}{}
|
||||
|
||||
+13
-13
@@ -9,14 +9,14 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
var (
|
||||
peer1name, _ = router.PeerNameFromString("01:00:00:00:02:00")
|
||||
peer2name, _ = router.PeerNameFromString("02:00:00:00:02:00")
|
||||
peer3name, _ = router.PeerNameFromString("03:00:00:00:02:00")
|
||||
peer1name, _ = mesh.PeerNameFromString("01:00:00:00:02:00")
|
||||
peer2name, _ = mesh.PeerNameFromString("02:00:00:00:02:00")
|
||||
peer3name, _ = mesh.PeerNameFromString("03:00:00:00:02:00")
|
||||
|
||||
start, end = ParseIP("10.0.0.0"), ParseIP("10.0.0.255")
|
||||
dot10, dot245 = ParseIP("10.0.0.10"), ParseIP("10.0.0.245")
|
||||
@@ -351,7 +351,7 @@ func TestGossip(t *testing.T) {
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
}
|
||||
|
||||
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []router.PeerName {
|
||||
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []mesh.PeerName {
|
||||
peers := ring.ChoosePeersToAskForSpace(start, end)
|
||||
require.Equal(t, expected, len(peers))
|
||||
return peers
|
||||
@@ -375,7 +375,7 @@ func TestFindFree(t *testing.T) {
|
||||
ring1.assertInvariants()
|
||||
|
||||
// We should return others
|
||||
var peers []router.PeerName
|
||||
var peers []mesh.PeerName
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
|
||||
peers = assertPeersWithSpace(t, ring1, start, end, 1)
|
||||
@@ -493,7 +493,7 @@ func TestOwner(t *testing.T) {
|
||||
require.True(t, ring1.Contains(start), "start should be in ring")
|
||||
require.False(t, ring1.Contains(end), "end should not be in ring")
|
||||
|
||||
require.Equal(t, router.UnknownPeerName, ring1.Owner(start))
|
||||
require.Equal(t, mesh.UnknownPeerName, ring1.Owner(start))
|
||||
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
@@ -517,9 +517,9 @@ func TestFuzzRing(t *testing.T) {
|
||||
iterations = 1000
|
||||
)
|
||||
|
||||
peers := make([]router.PeerName, numPeers)
|
||||
peers := make([]mesh.PeerName, numPeers)
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
|
||||
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
|
||||
peers[i] = peer
|
||||
}
|
||||
|
||||
@@ -601,13 +601,13 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
var (
|
||||
numPeers = 100
|
||||
iterations = 3000
|
||||
peers []router.PeerName
|
||||
peers []mesh.PeerName
|
||||
rings []*Ring
|
||||
nextPeerID = 0
|
||||
)
|
||||
|
||||
addPeer := func() {
|
||||
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
|
||||
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
|
||||
common.Log.Debugf("%s: Adding peer", peer)
|
||||
nextPeerID++
|
||||
peers = append(peers, peer)
|
||||
@@ -620,7 +620,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
|
||||
rings[0].ClaimItAll()
|
||||
|
||||
randomPeer := func(exclude int) (int, router.PeerName, *Ring) {
|
||||
randomPeer := func(exclude int) (int, mesh.PeerName, *Ring) {
|
||||
var peerIndex int
|
||||
if exclude >= 0 {
|
||||
peerIndex = rand.Intn(len(peers) - 1)
|
||||
@@ -729,7 +729,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
}
|
||||
|
||||
func (r *Ring) ClaimItAll() {
|
||||
r.ClaimForPeers([]router.PeerName{r.Peer})
|
||||
r.ClaimForPeers([]mesh.PeerName{r.Peer})
|
||||
}
|
||||
|
||||
func (es entries) String() string {
|
||||
|
||||
+19
-6
@@ -1,6 +1,7 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
type Status struct {
|
||||
Paxos *paxos.Status
|
||||
Range string
|
||||
RangeNumIPs int
|
||||
DefaultSubnet string
|
||||
Entries []EntryStatus
|
||||
PendingClaims []ClaimStatus
|
||||
@@ -15,9 +17,12 @@ type Status struct {
|
||||
}
|
||||
|
||||
type EntryStatus struct {
|
||||
Token string
|
||||
Peer string
|
||||
Version uint32
|
||||
Token string
|
||||
Size uint32
|
||||
Peer string
|
||||
Nickname string
|
||||
IsKnownPeer bool
|
||||
Version uint32
|
||||
}
|
||||
|
||||
type ClaimStatus struct {
|
||||
@@ -40,6 +45,7 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
resultChan <- &Status{
|
||||
paxosStatus,
|
||||
allocator.universe.String(),
|
||||
int(allocator.universe.Size()),
|
||||
defaultSubnet.String(),
|
||||
newEntryStatusSlice(allocator),
|
||||
newClaimStatusSlice(allocator),
|
||||
@@ -56,8 +62,15 @@ func newEntryStatusSlice(allocator *Allocator) []EntryStatus {
|
||||
return slice
|
||||
}
|
||||
|
||||
for _, entry := range allocator.ring.Entries {
|
||||
slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version})
|
||||
for _, r := range allocator.ring.AllRangeInfo() {
|
||||
slice = append(slice, EntryStatus{
|
||||
Token: r.Start.String(),
|
||||
Size: uint32(r.Size()),
|
||||
Peer: r.Peer.String(),
|
||||
Nickname: allocator.nicknames[r.Peer],
|
||||
IsKnownPeer: allocator.isKnownPeer(r.Peer),
|
||||
Version: r.Version,
|
||||
})
|
||||
}
|
||||
|
||||
return slice
|
||||
@@ -76,7 +89,7 @@ func newAllocateIdentSlice(allocator *Allocator) []string {
|
||||
var slice []string
|
||||
for _, op := range allocator.pendingAllocates {
|
||||
allocate := op.(*allocate)
|
||||
slice = append(slice, allocate.ident)
|
||||
slice = append(slice, fmt.Sprintf("%s %s", allocate.ident, allocate.r.String()))
|
||||
}
|
||||
return slice
|
||||
}
|
||||
+12
-12
@@ -8,13 +8,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
|
||||
type mockMessage struct {
|
||||
dst router.PeerName
|
||||
dst mesh.PeerName
|
||||
msgType byte
|
||||
buf []byte
|
||||
}
|
||||
@@ -48,13 +48,13 @@ func (m *mockGossipComms) String() string {
|
||||
// that the contents of messages are never re-ordered. Which, for instance,
|
||||
// requires they are not based off iterating through a map.
|
||||
|
||||
func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error {
|
||||
func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
buf := []byte{}
|
||||
if len(m.messages) == 0 {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf))
|
||||
} else if msg := m.messages[0]; msg.dst != router.UnknownPeerName {
|
||||
} else if msg := m.messages[0]; msg.dst != mesh.UnknownPeerName {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst))
|
||||
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf))
|
||||
@@ -77,12 +77,12 @@ func equalByteBuffer(a, b []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
||||
func (m *mockGossipComms) GossipUnicast(dstPeerName mesh.PeerName, buf []byte) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
if len(m.messages) == 0 {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf))
|
||||
} else if msg := m.messages[0]; msg.dst == router.UnknownPeerName {
|
||||
} else if msg := m.messages[0]; msg.dst == mesh.UnknownPeerName {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName))
|
||||
} else if msg.dst != dstPeerName {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName))
|
||||
@@ -99,7 +99,7 @@ func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte)
|
||||
|
||||
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
|
||||
m := alloc.gossip.(*mockGossipComms)
|
||||
dstPeerName, _ := router.PeerNameFromString(dst)
|
||||
dstPeerName, _ := mesh.PeerNameFromString(dst)
|
||||
m.Lock()
|
||||
m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf})
|
||||
m.Unlock()
|
||||
@@ -108,7 +108,7 @@ func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
|
||||
func ExpectBroadcastMessage(alloc *Allocator, buf []byte) {
|
||||
m := alloc.gossip.(*mockGossipComms)
|
||||
m.Lock()
|
||||
m.messages = append(m.messages, mockMessage{router.UnknownPeerName, 0, buf})
|
||||
m.messages = append(m.messages, mockMessage{mesh.UnknownPeerName, 0, buf})
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
|
||||
}
|
||||
|
||||
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) {
|
||||
peername, err := router.PeerNameFromString(name)
|
||||
peername, err := mesh.PeerNameFromString(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -134,8 +134,8 @@ func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, addres
|
||||
panic(err)
|
||||
}
|
||||
|
||||
alloc := NewAllocator(peername, router.PeerUID(rand.Int63()),
|
||||
"nick-"+name, cidr.Range(), quorum)
|
||||
alloc := NewAllocator(peername, mesh.PeerUID(rand.Int63()),
|
||||
"nick-"+name, cidr.Range(), quorum, func(mesh.PeerName) bool { return true })
|
||||
|
||||
return alloc, cidr.HostRange()
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string,
|
||||
}
|
||||
|
||||
func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
||||
peers := []router.PeerName{alloc.ourName}
|
||||
peers := []mesh.PeerName{alloc.ourName}
|
||||
for _, alloc2 := range allocs {
|
||||
peers = append(peers, alloc2.ourName)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Connection interface {
|
||||
Local() *Peer
|
||||
Remote() *Peer
|
||||
RemoteTCPAddr() string
|
||||
Outbound() bool
|
||||
Established() bool
|
||||
BreakTie(Connection) ConnectionTieBreak
|
||||
Shutdown(error)
|
||||
Log(args ...interface{})
|
||||
}
|
||||
|
||||
type ConnectionTieBreak int
|
||||
|
||||
const (
|
||||
TieBreakWon ConnectionTieBreak = iota
|
||||
TieBreakLost
|
||||
TieBreakTied
|
||||
)
|
||||
|
||||
var ErrConnectToSelf = fmt.Errorf("Cannot connect to ourself")
|
||||
|
||||
type RemoteConnection struct {
|
||||
local *Peer
|
||||
remote *Peer
|
||||
remoteTCPAddr string
|
||||
outbound bool
|
||||
established bool
|
||||
}
|
||||
|
||||
type LocalConnection struct {
|
||||
sync.RWMutex
|
||||
RemoteConnection
|
||||
TCPConn *net.TCPConn
|
||||
TrustRemote bool // is remote on a trusted subnet?
|
||||
TrustedByRemote bool // does remote trust us?
|
||||
version byte
|
||||
tcpSender TCPSender
|
||||
SessionKey *[32]byte
|
||||
heartbeatTCP *time.Ticker
|
||||
Router *Router
|
||||
uid uint64
|
||||
actionChan chan<- ConnectionAction
|
||||
errorChan chan<- error
|
||||
finished <-chan struct{} // closed to signal that actorLoop has finished
|
||||
OverlayConn OverlayConnection
|
||||
}
|
||||
|
||||
type ConnectionAction func() error
|
||||
|
||||
func NewRemoteConnection(from, to *Peer, tcpAddr string, outbound bool, established bool) *RemoteConnection {
|
||||
return &RemoteConnection{
|
||||
local: from,
|
||||
remote: to,
|
||||
remoteTCPAddr: tcpAddr,
|
||||
outbound: outbound,
|
||||
established: established,
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *RemoteConnection) Local() *Peer { return conn.local }
|
||||
func (conn *RemoteConnection) Remote() *Peer { return conn.remote }
|
||||
func (conn *RemoteConnection) RemoteTCPAddr() string { return conn.remoteTCPAddr }
|
||||
func (conn *RemoteConnection) Outbound() bool { return conn.outbound }
|
||||
func (conn *RemoteConnection) Established() bool { return conn.established }
|
||||
func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return TieBreakTied }
|
||||
func (conn *RemoteConnection) Shutdown(error) {}
|
||||
|
||||
func (conn *RemoteConnection) Log(args ...interface{}) {
|
||||
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
|
||||
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
// Does not return anything. If the connection is successful, it will
|
||||
// end up in the local peer's connections map.
|
||||
func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, router *Router, acceptNewPeer bool) {
|
||||
if connRemote.local != router.Ourself.Peer {
|
||||
log.Fatal("Attempt to create local connection from a peer which is not ourself")
|
||||
}
|
||||
actionChan := make(chan ConnectionAction, ChannelSize)
|
||||
errorChan := make(chan error, 1)
|
||||
finished := make(chan struct{})
|
||||
conn := &LocalConnection{
|
||||
RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here.
|
||||
Router: router,
|
||||
TCPConn: tcpConn,
|
||||
TrustRemote: router.Trusts(connRemote),
|
||||
uid: randUint64(),
|
||||
actionChan: actionChan,
|
||||
errorChan: errorChan,
|
||||
finished: finished}
|
||||
go conn.run(actionChan, errorChan, finished, acceptNewPeer)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
|
||||
dupConnLocal := dupConn.(*LocalConnection)
|
||||
// conn.uid is used as the tie breaker here, in the knowledge that
|
||||
// both sides will make the same decision.
|
||||
if conn.uid < dupConnLocal.uid {
|
||||
return TieBreakWon
|
||||
} else if dupConnLocal.uid < conn.uid {
|
||||
return TieBreakLost
|
||||
} else {
|
||||
return TieBreakTied
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) Established() bool {
|
||||
return conn.established
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
|
||||
if err := conn.sendProtocolMsg(m); err != nil {
|
||||
conn.Shutdown(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR methods
|
||||
|
||||
// NB: The conn.* fields are only written by the connection actor
|
||||
// process, which is the caller of the ConnectionAction funs. Hence we
|
||||
// do not need locks for reading, and only need write locks for fields
|
||||
// read by other processes.
|
||||
|
||||
// Non-blocking.
|
||||
func (conn *LocalConnection) Shutdown(err error) {
|
||||
// err should always be a real error, even if only io.EOF
|
||||
if err == nil {
|
||||
panic("nil error")
|
||||
}
|
||||
|
||||
select {
|
||||
case conn.errorChan <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Send an actor request to the actorLoop, but don't block if
|
||||
// actorLoop has exited - see http://blog.golang.org/pipelines for
|
||||
// pattern
|
||||
func (conn *LocalConnection) sendAction(action ConnectionAction) {
|
||||
select {
|
||||
case conn.actionChan <- action:
|
||||
case <-conn.finished:
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR server
|
||||
|
||||
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, errorChan <-chan error, finished chan<- struct{}, acceptNewPeer bool) {
|
||||
var err error // important to use this var and not create another one with 'err :='
|
||||
defer func() { conn.shutdown(err) }()
|
||||
defer close(finished)
|
||||
|
||||
conn.TCPConn.SetLinger(0)
|
||||
intro, err := ProtocolIntroParams{
|
||||
MinVersion: conn.Router.ProtocolMinVersion,
|
||||
MaxVersion: ProtocolMaxVersion,
|
||||
Features: conn.makeFeatures(),
|
||||
Conn: conn.TCPConn,
|
||||
Password: conn.Router.Password,
|
||||
Outbound: conn.outbound,
|
||||
}.DoIntro()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn.SessionKey = intro.SessionKey
|
||||
conn.tcpSender = intro.Sender
|
||||
conn.version = intro.Version
|
||||
|
||||
remote, err := conn.parseFeatures(intro.Features)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn.Log("connection ready; using protocol version", conn.version)
|
||||
|
||||
// only use negotiated session key for untrusted connections
|
||||
var sessionKey *[32]byte
|
||||
if conn.Untrusted() {
|
||||
sessionKey = conn.SessionKey
|
||||
}
|
||||
|
||||
params := OverlayConnectionParams{
|
||||
RemotePeer: conn.remote,
|
||||
LocalAddr: conn.TCPConn.LocalAddr().(*net.TCPAddr),
|
||||
RemoteAddr: conn.TCPConn.RemoteAddr().(*net.TCPAddr),
|
||||
Outbound: conn.outbound,
|
||||
ConnUID: conn.uid,
|
||||
SessionKey: sessionKey,
|
||||
SendControlMessage: conn.sendOverlayControlMessage,
|
||||
Features: intro.Features,
|
||||
}
|
||||
if conn.OverlayConn, err = conn.Router.Overlay.PrepareConnection(params); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// As soon as we do AddConnection, the new connection becomes
|
||||
// visible to the packet routing logic. So AddConnection must
|
||||
// come after PrepareConnection
|
||||
if err = conn.Router.Ourself.AddConnection(conn); err != nil {
|
||||
return
|
||||
}
|
||||
conn.Router.ConnectionMaker.ConnectionCreated(conn)
|
||||
|
||||
// OverlayConnection confirmation comes after AddConnection,
|
||||
// because only after that completes do we know the connection is
|
||||
// valid: in particular that it is not a duplicate connection to
|
||||
// the same peer. Overlay communication on a duplicate connection
|
||||
// can cause problems such as tripping up overlay crypto at the
|
||||
// other end due to data being decoded by the other connection. It
|
||||
// is also generally wasteful to engage in any interaction with
|
||||
// the remote on a connection that turns out to be invalid.
|
||||
conn.OverlayConn.Confirm()
|
||||
|
||||
// receiveTCP must follow also AddConnection. In the absence
|
||||
// of any indirect connectivity to the remote peer, the first
|
||||
// we hear about it (and any peers reachable from it) is
|
||||
// through topology gossip it sends us on the connection. We
|
||||
// must ensure that the connection has been added to Ourself
|
||||
// prior to processing any such gossip, otherwise we risk
|
||||
// immediately gc'ing part of that newly received portion of
|
||||
// the topology (though not the remote peer itself, since that
|
||||
// will have a positive ref count), leaving behind dangling
|
||||
// references to peers. Hence we must invoke AddConnection,
|
||||
// which is *synchronous*, first.
|
||||
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
|
||||
go conn.receiveTCP(intro.Receiver)
|
||||
|
||||
// AddConnection must precede actorLoop. More precisely, it
|
||||
// must precede shutdown, since that invokes DeleteConnection
|
||||
// and is invoked on termination of this entire
|
||||
// function. Essentially this boils down to a prohibition on
|
||||
// running AddConnection in a separate goroutine, at least not
|
||||
// without some synchronisation. Which in turn requires the
|
||||
// launching of the receiveTCP goroutine to precede actorLoop.
|
||||
err = conn.actorLoop(actionChan, errorChan)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) makeFeatures() map[string]string {
|
||||
features := map[string]string{
|
||||
"PeerNameFlavour": PeerNameFlavour,
|
||||
"Name": conn.local.Name.String(),
|
||||
"NickName": conn.local.NickName,
|
||||
"ShortID": fmt.Sprint(conn.local.ShortID),
|
||||
"UID": fmt.Sprint(conn.local.UID),
|
||||
"ConnID": fmt.Sprint(conn.uid),
|
||||
"Trusted": fmt.Sprint(conn.TrustRemote),
|
||||
}
|
||||
conn.Router.Overlay.AddFeaturesTo(features)
|
||||
return features
|
||||
}
|
||||
|
||||
type features map[string]string
|
||||
|
||||
func (features features) MustHave(keys []string) error {
|
||||
for _, key := range keys {
|
||||
if _, ok := features[key]; !ok {
|
||||
return fmt.Errorf("Field %s is missing", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (features features) Get(key string) string {
|
||||
return features[key]
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
|
||||
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "UID", "ConnID"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remotePeerNameFlavour := features.Get("PeerNameFlavour")
|
||||
if remotePeerNameFlavour != PeerNameFlavour {
|
||||
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
|
||||
}
|
||||
|
||||
name, err := PeerNameFromString(features.Get("Name"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nickName := features.Get("NickName")
|
||||
|
||||
var shortID uint64
|
||||
var hasShortID bool
|
||||
if shortIDStr, present := features["ShortID"]; present {
|
||||
hasShortID = true
|
||||
shortID, err = strconv.ParseUint(shortIDStr, 10, PeerShortIDBits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var trusted bool
|
||||
if trustedStr, present := features["Trusted"]; present {
|
||||
trusted, err = strconv.ParseBool(trustedStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
conn.TrustedByRemote = trusted
|
||||
|
||||
uid, err := ParsePeerUID(features.Get("UID"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.uid ^= remoteConnID
|
||||
peer := NewPeer(name, nickName, uid, 0, PeerShortID(shortID))
|
||||
peer.HasShortID = hasShortID
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
|
||||
if acceptNewPeer {
|
||||
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
|
||||
} else {
|
||||
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
|
||||
if conn.remote == nil {
|
||||
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if conn.remote == conn.local {
|
||||
return ErrConnectToSelf
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction, errorChan <-chan error) (err error) {
|
||||
fwdErrorChan := conn.OverlayConn.ErrorChannel()
|
||||
fwdEstablishedChan := conn.OverlayConn.EstablishedChannel()
|
||||
|
||||
for err == nil {
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
err = action()
|
||||
|
||||
case err = <-errorChan:
|
||||
|
||||
case <-conn.heartbeatTCP.C:
|
||||
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
|
||||
|
||||
case <-fwdEstablishedChan:
|
||||
conn.established = true
|
||||
fwdEstablishedChan = nil
|
||||
conn.Router.Ourself.ConnectionEstablished(conn)
|
||||
|
||||
case err = <-fwdErrorChan:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) shutdown(err error) {
|
||||
if conn.remote == nil {
|
||||
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
|
||||
} else {
|
||||
conn.ErrorLog("connection shutting down due to error:", err)
|
||||
}
|
||||
|
||||
if conn.TCPConn != nil {
|
||||
checkWarn(conn.TCPConn.Close())
|
||||
}
|
||||
|
||||
if conn.remote != nil {
|
||||
conn.Router.Peers.Dereference(conn.remote)
|
||||
conn.Router.Ourself.DeleteConnection(conn)
|
||||
}
|
||||
|
||||
if conn.heartbeatTCP != nil {
|
||||
conn.heartbeatTCP.Stop()
|
||||
}
|
||||
|
||||
if conn.OverlayConn != nil {
|
||||
conn.OverlayConn.Stop()
|
||||
}
|
||||
|
||||
conn.Router.ConnectionMaker.ConnectionTerminated(conn, err)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) sendOverlayControlMessage(tag byte, msg []byte) error {
|
||||
return conn.sendProtocolMsg(ProtocolMsg{ProtocolTag(tag), msg})
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error {
|
||||
return conn.sendProtocolMsg(ProtocolMsg{tag: tag})
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
|
||||
return conn.tcpSender.Send(append([]byte{byte(m.tag)}, m.msg...))
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
|
||||
var err error
|
||||
for {
|
||||
conn.extendReadDeadline()
|
||||
|
||||
var msg []byte
|
||||
if msg, err = receiver.Receive(); err != nil {
|
||||
break
|
||||
}
|
||||
if len(msg) < 1 {
|
||||
conn.Log("ignoring blank msg")
|
||||
continue
|
||||
}
|
||||
if err = conn.handleProtocolMsg(ProtocolTag(msg[0]), msg[1:]); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
conn.Shutdown(err)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error {
|
||||
switch tag {
|
||||
case ProtocolHeartbeat:
|
||||
case ProtocolReserved1, ProtocolReserved2, ProtocolReserved3, ProtocolOverlayControlMsg:
|
||||
conn.OverlayConn.ControlMessage(byte(tag), payload)
|
||||
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
|
||||
return conn.Router.handleGossip(tag, payload)
|
||||
default:
|
||||
conn.Log("ignoring unknown protocol tag:", tag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) extendReadDeadline() {
|
||||
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) Untrusted() bool {
|
||||
return !conn.TrustRemote || !conn.TrustedByRemote
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
const (
|
||||
InitialInterval = 2 * time.Second
|
||||
MaxInterval = 6 * time.Minute
|
||||
ResetAfter = 1 * time.Minute
|
||||
)
|
||||
|
||||
type peerAddrs map[string]*net.TCPAddr
|
||||
@@ -20,13 +21,22 @@ type ConnectionMaker struct {
|
||||
port int
|
||||
discovery bool
|
||||
targets map[string]*Target
|
||||
connections map[Connection]struct{}
|
||||
directPeers peerAddrs
|
||||
actionChan chan<- ConnectionMakerAction
|
||||
}
|
||||
|
||||
type TargetState int
|
||||
|
||||
const (
|
||||
TargetWaiting TargetState = iota // we are waiting to connect there
|
||||
TargetAttempting // we are attempting to connect there
|
||||
TargetConnected // we are connected to there
|
||||
)
|
||||
|
||||
// Information about an address where we may find a peer
|
||||
type Target struct {
|
||||
attempting bool // are we currently attempting to connect there?
|
||||
state TargetState
|
||||
lastError error // reason for disconnection last time
|
||||
tryAfter time.Time // next time to try this address
|
||||
tryInterval time.Duration // retry delay on next failure
|
||||
@@ -43,6 +53,7 @@ func NewConnectionMaker(ourself *LocalPeer, peers *Peers, port int, discovery bo
|
||||
discovery: discovery,
|
||||
directPeers: peerAddrs{},
|
||||
targets: make(map[string]*Target),
|
||||
connections: make(map[Connection]struct{}),
|
||||
actionChan: actionChan}
|
||||
go cm.queryLoop(actionChan)
|
||||
return cm
|
||||
@@ -70,8 +81,8 @@ func (cm *ConnectionMaker) InitiateConnections(peers []string, replace bool) []e
|
||||
for peer, addr := range addrs {
|
||||
cm.directPeers[peer] = addr
|
||||
// curtail any existing reconnect interval
|
||||
if target, found := cm.targets[addr.String()]; found {
|
||||
target.tryNow()
|
||||
if target, found := cm.targets[cm.completeAddr(*addr)]; found {
|
||||
target.nextTryNow()
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -88,12 +99,42 @@ func (cm *ConnectionMaker) ForgetConnections(peers []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ConnectionTerminated(address string, err error) {
|
||||
func (cm *ConnectionMaker) ConnectionAborted(address string, err error) {
|
||||
cm.actionChan <- func() bool {
|
||||
if target, found := cm.targets[address]; found {
|
||||
target.attempting = false
|
||||
target := cm.targets[address]
|
||||
target.state = TargetWaiting
|
||||
target.lastError = err
|
||||
target.nextTryLater()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ConnectionCreated(conn Connection) {
|
||||
cm.actionChan <- func() bool {
|
||||
cm.connections[conn] = void
|
||||
if conn.Outbound() {
|
||||
target := cm.targets[conn.RemoteTCPAddr()]
|
||||
target.state = TargetConnected
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ConnectionTerminated(conn Connection, err error) {
|
||||
cm.actionChan <- func() bool {
|
||||
delete(cm.connections, conn)
|
||||
if conn.Outbound() {
|
||||
target := cm.targets[conn.RemoteTCPAddr()]
|
||||
target.state = TargetWaiting
|
||||
target.lastError = err
|
||||
target.retry()
|
||||
switch {
|
||||
case err == ErrConnectToSelf:
|
||||
target.nextTryNever()
|
||||
case time.Now().After(target.tryAfter.Add(ResetAfter)):
|
||||
target.nextTryNow()
|
||||
default:
|
||||
target.nextTryLater()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -118,14 +159,18 @@ func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) completeAddr(addr net.TCPAddr) string {
|
||||
if addr.Port == 0 {
|
||||
addr.Port = cm.port
|
||||
}
|
||||
return addr.String()
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
|
||||
var (
|
||||
validTarget = make(map[string]struct{})
|
||||
directTarget = make(map[string]struct{})
|
||||
)
|
||||
// Copy the set of things we are connected to, so we can access
|
||||
// them without locking. Also clear out any entries in cm.targets
|
||||
// for existing connections.
|
||||
ourConnectedPeers, ourConnectedTargets, ourInboundIPs := cm.ourConnections()
|
||||
|
||||
addTarget := func(address string) {
|
||||
@@ -136,25 +181,23 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
|
||||
if _, found := cm.targets[address]; found {
|
||||
return
|
||||
}
|
||||
target := &Target{}
|
||||
target.tryNow()
|
||||
target := &Target{state: TargetWaiting}
|
||||
target.nextTryNow()
|
||||
cm.targets[address] = target
|
||||
}
|
||||
|
||||
// Add direct targets that are not connected
|
||||
for _, addr := range cm.directPeers {
|
||||
completeAddr := *addr
|
||||
attempt := true
|
||||
if completeAddr.Port == 0 {
|
||||
completeAddr.Port = cm.port
|
||||
if addr.Port == 0 {
|
||||
// If a peer was specified w/o a port, then we do not
|
||||
// attempt to connect to it if we have any inbound
|
||||
// connections from that IP.
|
||||
if _, connected := ourInboundIPs[completeAddr.IP.String()]; connected {
|
||||
if _, connected := ourInboundIPs[addr.IP.String()]; connected {
|
||||
attempt = false
|
||||
}
|
||||
}
|
||||
address := completeAddr.String()
|
||||
address := cm.completeAddr(*addr)
|
||||
directTarget[address] = void
|
||||
if attempt {
|
||||
addTarget(address)
|
||||
@@ -176,9 +219,8 @@ func (cm *ConnectionMaker) ourConnections() (PeerNameSet, map[string]struct{}, m
|
||||
ourConnectedTargets = make(map[string]struct{})
|
||||
ourInboundIPs = make(map[string]struct{})
|
||||
)
|
||||
for conn := range cm.ourself.Connections() {
|
||||
for conn := range cm.connections {
|
||||
address := conn.RemoteTCPAddr()
|
||||
delete(cm.targets, address)
|
||||
ourConnectedPeers[conn.Remote().Name] = void
|
||||
ourConnectedTargets[address] = void
|
||||
if conn.Outbound() {
|
||||
@@ -212,7 +254,7 @@ func (cm *ConnectionMaker) addPeerTargets(ourConnectedPeers PeerNameSet, addTarg
|
||||
} else if ip, _, err := net.SplitHostPort(address); err == nil {
|
||||
// There is no point connecting to the (likely
|
||||
// ephemeral) remote port of an inbound connection
|
||||
// that some peer has. Let's try to connect to on the
|
||||
// that some peer has. Let's try to connect on the
|
||||
// weave port instead.
|
||||
addTarget(fmt.Sprintf("%s:%d", ip, cm.port))
|
||||
}
|
||||
@@ -224,7 +266,7 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, dir
|
||||
now := time.Now() // make sure we catch items just added
|
||||
after := MaxDuration
|
||||
for address, target := range cm.targets {
|
||||
if target.attempting {
|
||||
if target.state != TargetWaiting {
|
||||
continue
|
||||
}
|
||||
if _, valid := validTarget[address]; !valid {
|
||||
@@ -236,7 +278,7 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, dir
|
||||
}
|
||||
switch duration := target.tryAfter.Sub(now); {
|
||||
case duration <= 0:
|
||||
target.attempting = true
|
||||
target.state = TargetAttempting
|
||||
_, isCmdLineTarget := directTarget[address]
|
||||
go cm.attemptConnection(address, isCmdLineTarget)
|
||||
case duration < after:
|
||||
@@ -250,23 +292,23 @@ func (cm *ConnectionMaker) attemptConnection(address string, acceptNewPeer bool)
|
||||
log.Printf("->[%s] attempting connection", address)
|
||||
if err := cm.ourself.CreateConnection(address, acceptNewPeer); err != nil {
|
||||
log.Errorf("->[%s] error during connection attempt: %v", address, err)
|
||||
cm.ConnectionTerminated(address, err)
|
||||
cm.ConnectionAborted(address, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Target) tryNow() {
|
||||
func (t *Target) nextTryNever() {
|
||||
t.tryAfter = time.Time{}
|
||||
t.tryInterval = MaxInterval
|
||||
}
|
||||
|
||||
func (t *Target) nextTryNow() {
|
||||
t.tryAfter = time.Now()
|
||||
t.tryInterval = InitialInterval
|
||||
}
|
||||
|
||||
// The delay at the nth retry is a random value in the range
|
||||
// [i-i/2,i+i/2], where i = InitialInterval * 1.5^(n-1).
|
||||
func (t *Target) retry() {
|
||||
if t.lastError == ErrConnectToSelf {
|
||||
t.tryAfter = time.Time{}
|
||||
t.tryInterval = MaxInterval
|
||||
return
|
||||
}
|
||||
func (t *Target) nextTryLater() {
|
||||
t.tryAfter = time.Now().Add(t.tryInterval/2 + time.Duration(rand.Int63n(int64(t.tryInterval))))
|
||||
t.tryInterval = t.tryInterval * 3 / 2
|
||||
if t.tryInterval > MaxInterval {
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
type GossipData interface {
|
||||
Encode() [][]byte
|
||||
Merge(GossipData)
|
||||
Merge(GossipData) GossipData
|
||||
}
|
||||
|
||||
type Gossip interface {
|
||||
@@ -79,8 +79,7 @@ func (sender *GossipSender) Send(data GossipData) {
|
||||
// NB: this must not be invoked concurrently
|
||||
select {
|
||||
case pending := <-sender.cell:
|
||||
pending.Merge(data)
|
||||
sender.cell <- pending
|
||||
sender.cell <- pending.Merge(data)
|
||||
default:
|
||||
sender.cell <- data
|
||||
}
|
||||
@@ -142,7 +141,7 @@ func (router *Router) SendAllGossip() {
|
||||
func (router *Router) SendAllGossipDown(conn Connection) {
|
||||
for channel := range router.gossipChannelSet() {
|
||||
if gossip := channel.gossiper.Gossip(); gossip != nil {
|
||||
channel.SendDown(conn, channel.gossiper.Gossip())
|
||||
channel.SendDown(conn, gossip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -97,78 +97,6 @@ func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
|
||||
// do this outside the lock below so we avoid lock nesting
|
||||
c.routes.EnsureRecalculated()
|
||||
selectedConnections := make(ConnectionSet)
|
||||
for name := range c.routes.RandomNeighbours(srcName) {
|
||||
if conn, found := c.ourself.ConnectionTo(name); found {
|
||||
selectedConnections[conn] = void
|
||||
}
|
||||
}
|
||||
if len(selectedConnections) == 0 {
|
||||
return
|
||||
}
|
||||
connections := c.ourself.Connections()
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.gcSenders(connections)
|
||||
for conn := range selectedConnections {
|
||||
c.sendDown(conn, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
|
||||
connections := c.ourself.Connections()
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.gcSenders(connections)
|
||||
c.sendDown(conn, data)
|
||||
}
|
||||
|
||||
// GC - randomly (courtesy of go's map iterator) pick some existing
|
||||
// senders and stop&remove them if the associated connection is no
|
||||
// longer active. We stop as soon as we encounter a valid entry; the
|
||||
// idea being that when there is little or no garbage then this
|
||||
// executes close to O(1)[1], whereas when there is lots of garbage we
|
||||
// remove it quickly.
|
||||
//
|
||||
// [1] TODO Unfortunately, due to the desire to avoid nested locks,
|
||||
// instead of simply invoking LocalPeer.ConnectionTo(name), we pass in
|
||||
// the result of LocalPeer.Connections(). That is O(n_our_connections)
|
||||
// at best.
|
||||
func (c *GossipChannel) gcSenders(connections ConnectionSet) {
|
||||
for conn, sender := range c.senders {
|
||||
if _, found := connections[conn]; !found {
|
||||
delete(c.senders, conn)
|
||||
sender.Stop()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have seen a couple of failures which suggest a >128GB slice was encountered.
|
||||
// 100MB should be enough for anyone.
|
||||
const maxFeasibleMessageLen = 100 * 1024 * 1024
|
||||
|
||||
func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
|
||||
sender, found := c.senders[conn]
|
||||
if !found {
|
||||
sender = NewGossipSender(func(pending GossipData) {
|
||||
for _, msg := range pending.Encode() {
|
||||
if len(msg) > maxFeasibleMessageLen {
|
||||
panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending))
|
||||
}
|
||||
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)}
|
||||
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
|
||||
}
|
||||
})
|
||||
c.senders[conn] = sender
|
||||
}
|
||||
sender.Send(data)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
|
||||
return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg))
|
||||
}
|
||||
@@ -236,6 +164,81 @@ func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
|
||||
c.routes.EnsureRecalculated()
|
||||
destinations := c.routes.RandomNeighbours(srcName)
|
||||
if len(destinations) == 0 {
|
||||
return
|
||||
}
|
||||
c.sendDown(c.ourself.ConnectionsTo(destinations), data)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
|
||||
c.sendDown([]Connection{conn}, data)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) sendDown(selectedConnections []Connection, data GossipData) {
|
||||
if len(selectedConnections) == 0 {
|
||||
return
|
||||
}
|
||||
connections := c.ourself.Connections()
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
// GC - randomly (courtesy of go's map iterator) pick some
|
||||
// existing senders and stop&remove them if the associated
|
||||
// connection is no longer active. We stop as soon as we
|
||||
// encounter a valid entry; the idea being that when there is
|
||||
// little or no garbage then this executes close to O(1)[1],
|
||||
// whereas when there is lots of garbage we remove it quickly.
|
||||
//
|
||||
// [1] TODO Unfortunately, due to the desire to avoid nested
|
||||
// locks, instead of simply invoking LocalPeer.ConnectionTo(name),
|
||||
// we operate on LocalPeer.Connections(). That is
|
||||
// O(n_our_connections) at best.
|
||||
for conn, sender := range c.senders {
|
||||
if _, found := connections[conn]; !found {
|
||||
delete(c.senders, conn)
|
||||
sender.Stop()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// start senders, if necessary, and send.
|
||||
for _, conn := range selectedConnections {
|
||||
sender, found := c.senders[conn]
|
||||
if !found {
|
||||
sender = c.makeSender(conn)
|
||||
c.senders[conn] = sender
|
||||
}
|
||||
sender.Send(data)
|
||||
}
|
||||
}
|
||||
|
||||
// We have seen a couple of failures which suggest a >128GB slice was encountered.
|
||||
// 100MB should be enough for anyone.
|
||||
const maxFeasibleMessageLen = 100 * 1024 * 1024
|
||||
|
||||
func (c *GossipChannel) makeSender(conn Connection) *GossipSender {
|
||||
return NewGossipSender(func(pending GossipData) {
|
||||
for _, msg := range pending.Encode() {
|
||||
if len(msg) > maxFeasibleMessageLen {
|
||||
panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending))
|
||||
}
|
||||
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)}
|
||||
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *GossipChannel) log(args ...interface{}) {
|
||||
log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
|
||||
}
|
||||
|
||||
func GobEncode(items ...interface{}) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
for _, i := range items {
|
||||
checkFatal(enc.Encode(i))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
@@ -1,36 +1,40 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// TODO test gossip unicast; atm we only test topology gossip and
|
||||
// surrogates, neither of which employ unicast.
|
||||
|
||||
type mockChannelConnection struct {
|
||||
type mockGossipConnection struct {
|
||||
RemoteConnection
|
||||
dest *Router
|
||||
dest *Router
|
||||
start chan struct{}
|
||||
}
|
||||
|
||||
func NewTestRouter(name string) *Router {
|
||||
peerName, _ := PeerNameFromString(name)
|
||||
router := NewRouter(Config{}, peerName, "nick")
|
||||
router := NewRouter(Config{}, peerName, "nick", nil)
|
||||
router.Start()
|
||||
return router
|
||||
}
|
||||
|
||||
func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
|
||||
func (conn *mockGossipConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
|
||||
<-conn.start
|
||||
if err := conn.dest.handleGossip(protocolMsg.tag, protocolMsg.msg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *mockGossipConnection) Start() {
|
||||
close(conn.start)
|
||||
}
|
||||
|
||||
func sendPendingGossip(routers ...*Router) {
|
||||
// Loop until all routers report they didn't send anything
|
||||
for sentSomething := true; sentSomething; {
|
||||
@@ -41,52 +45,43 @@ func sendPendingGossip(routers ...*Router) {
|
||||
}
|
||||
}
|
||||
|
||||
func (router *Router) AddTestChannelConnection(r *Router) {
|
||||
fromName := router.Ourself.Peer.Name
|
||||
toName := r.Ourself.Peer.Name
|
||||
func AddTestGossipConnection(r1, r2 *Router) {
|
||||
c1 := r1.NewTestGossipConnection(r2)
|
||||
c2 := r2.NewTestGossipConnection(r1)
|
||||
c1.Start()
|
||||
c2.Start()
|
||||
}
|
||||
|
||||
fromPeer := NewPeer(fromName, "", router.Ourself.Peer.UID, 0)
|
||||
toPeer := NewPeer(toName, "", r.Ourself.Peer.UID, 0)
|
||||
func (router *Router) NewTestGossipConnection(r *Router) *mockGossipConnection {
|
||||
to := r.Ourself.Peer
|
||||
toPeer := NewPeer(to.Name, to.NickName, to.UID, 0, to.ShortID)
|
||||
toPeer = router.Peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
|
||||
|
||||
r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount
|
||||
router.Peers.FetchWithDefault(toPeer) //
|
||||
|
||||
conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r}
|
||||
conn := &mockGossipConnection{
|
||||
RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r, make(chan struct{})}
|
||||
router.Ourself.handleAddConnection(conn)
|
||||
router.Ourself.handleConnectionEstablished(conn)
|
||||
return conn
|
||||
}
|
||||
|
||||
func (router *Router) DeleteTestChannelConnection(r *Router) {
|
||||
fromName := router.Ourself.Peer.Name
|
||||
func (router *Router) DeleteTestGossipConnection(r *Router) {
|
||||
toName := r.Ourself.Peer.Name
|
||||
|
||||
fromPeer := r.Peers.Fetch(fromName)
|
||||
toPeer := router.Peers.Fetch(toName)
|
||||
|
||||
r.Peers.Dereference(fromPeer)
|
||||
router.Peers.Dereference(toPeer)
|
||||
|
||||
conn, _ := router.Ourself.ConnectionTo(toName)
|
||||
router.Peers.Dereference(conn.Remote())
|
||||
router.Ourself.handleDeleteConnection(conn)
|
||||
}
|
||||
|
||||
func TestGossipTopology(t *testing.T) {
|
||||
wt.RunWithTimeout(t, 5*time.Second, func() {
|
||||
implTestGossipTopology(t)
|
||||
})
|
||||
}
|
||||
|
||||
// Create a Peer representing the receiver router, with connections to
|
||||
// the routers supplied as arguments, carrying across all UID and
|
||||
// version information.
|
||||
func (router *Router) tp(routers ...*Router) *Peer {
|
||||
peer := NewPeer(router.Ourself.Peer.Name, "", router.Ourself.Peer.UID, 0)
|
||||
peer := NewPeerFrom(router.Ourself.Peer)
|
||||
connections := make(map[PeerName]Connection)
|
||||
for _, r := range routers {
|
||||
p := NewPeer(r.Ourself.Peer.Name, "", r.Ourself.Peer.UID, r.Ourself.Peer.version)
|
||||
p := NewPeerFrom(r.Ourself.Peer)
|
||||
connections[r.Ourself.Peer.Name] = newMockConnection(peer, p)
|
||||
}
|
||||
peer.version = router.Ourself.Peer.version
|
||||
peer.Version = router.Ourself.Peer.Version
|
||||
peer.connections = connections
|
||||
return peer
|
||||
}
|
||||
@@ -98,69 +93,46 @@ func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) {
|
||||
router.Peers.RUnlock()
|
||||
}
|
||||
|
||||
func implTestGossipTopology(t *testing.T) {
|
||||
func flushAndCheckTopology(t *testing.T, routers []*Router, wantedPeers ...*Peer) {
|
||||
sendPendingGossip(routers...)
|
||||
for _, r := range routers {
|
||||
checkTopology(t, r, wantedPeers...)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGossipTopology(t *testing.T) {
|
||||
// Create some peers that will talk to each other
|
||||
r1 := NewTestRouter("01:00:00:01:00:00")
|
||||
r2 := NewTestRouter("02:00:00:02:00:00")
|
||||
r3 := NewTestRouter("03:00:00:03:00:00")
|
||||
|
||||
routers := []*Router{r1, r2, r3}
|
||||
// Check state when they have no connections
|
||||
checkTopology(t, r1, r1.tp())
|
||||
checkTopology(t, r2, r2.tp())
|
||||
|
||||
// Now try adding some connections
|
||||
r1.AddTestChannelConnection(r2)
|
||||
sendPendingGossip(r1, r2)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp())
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp())
|
||||
r2.AddTestChannelConnection(r1)
|
||||
AddTestGossipConnection(r1, r2)
|
||||
sendPendingGossip(r1, r2)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
|
||||
|
||||
// Currently, the connection from 2 to 3 is one-way only
|
||||
r2.AddTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp())
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp())
|
||||
// When r2 gossiped to r3, 1 was unreachable from r3 so it got removed from the
|
||||
// list of peers, but remains referenced in the connection from 1 to 3.
|
||||
checkTopology(t, r3, r2.tp(r1, r3), r3.tp())
|
||||
AddTestGossipConnection(r2, r3)
|
||||
flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
|
||||
// Add a connection from 3 to 1 and now r1 is reachable.
|
||||
r3.AddTestChannelConnection(r1)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r3, r1.tp(), r2.tp(r1, r3), r3.tp(r1))
|
||||
|
||||
r1.AddTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
||||
AddTestGossipConnection(r3, r1)
|
||||
flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1, r2))
|
||||
|
||||
// Drop the connection from 2 to 3
|
||||
r2.DeleteTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1))
|
||||
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
||||
r2.DeleteTestGossipConnection(r3)
|
||||
flushAndCheckTopology(t, routers, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2))
|
||||
|
||||
// Drop the connection from 1 to 3
|
||||
r1.DeleteTestChannelConnection(r3)
|
||||
r1.DeleteTestGossipConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
||||
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
|
||||
// r3 still thinks r1 has a connection to it
|
||||
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
||||
|
||||
// On a timer, r3 will gossip to r1
|
||||
r3.SendAllGossip()
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
||||
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1, r2))
|
||||
}
|
||||
|
||||
func TestGossipSurrogate(t *testing.T) {
|
||||
@@ -168,14 +140,10 @@ func TestGossipSurrogate(t *testing.T) {
|
||||
r1 := NewTestRouter("01:00:00:01:00:00")
|
||||
r2 := NewTestRouter("02:00:00:02:00:00")
|
||||
r3 := NewTestRouter("03:00:00:03:00:00")
|
||||
r1.AddTestChannelConnection(r2)
|
||||
r2.AddTestChannelConnection(r1)
|
||||
r3.AddTestChannelConnection(r2)
|
||||
r2.AddTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
checkTopology(t, r3, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
routers := []*Router{r1, r2, r3}
|
||||
AddTestGossipConnection(r1, r2)
|
||||
AddTestGossipConnection(r3, r2)
|
||||
flushAndCheckTopology(t, routers, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
|
||||
// create a gossiper at either end, but not the middle
|
||||
g1 := newTestGossiper()
|
||||
@@ -241,10 +209,10 @@ func (g *testGossiper) OnGossip(update []byte) (GossipData, error) {
|
||||
g.state[v] = void
|
||||
}
|
||||
}
|
||||
if len(delta) > 0 {
|
||||
return NewSurrogateGossipData(delta), nil
|
||||
if len(delta) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
return NewSurrogateGossipData(delta), nil
|
||||
}
|
||||
|
||||
func (g *testGossiper) checkHas(t *testing.T, vs ...byte) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -19,7 +19,7 @@ type LocalPeerAction func()
|
||||
func NewLocalPeer(name PeerName, nickName string, router *Router) *LocalPeer {
|
||||
actionChan := make(chan LocalPeerAction, ChannelSize)
|
||||
peer := &LocalPeer{
|
||||
Peer: NewPeer(name, nickName, 0, 0),
|
||||
Peer: NewPeer(name, nickName, randomPeerUID(), 0, randomPeerShortID()),
|
||||
router: router,
|
||||
actionChan: actionChan,
|
||||
}
|
||||
@@ -27,56 +27,6 @@ func NewLocalPeer(name PeerName, nickName string, router *Router) *LocalPeer {
|
||||
return peer
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) Forward(dstPeer *Peer, frame []byte, dec *EthernetDecoder) error {
|
||||
return peer.Relay(peer.Peer, dstPeer, frame, dec)
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) Broadcast(frame []byte, dec *EthernetDecoder) {
|
||||
peer.RelayBroadcast(peer.Peer, frame, dec)
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) Relay(srcPeer, dstPeer *Peer, frame []byte, dec *EthernetDecoder) error {
|
||||
relayPeerName, found := peer.router.Routes.Unicast(dstPeer.Name)
|
||||
if !found {
|
||||
// Not necessarily an error as there could be a race with the
|
||||
// dst disappearing whilst the frame is in flight
|
||||
log.Println("Received packet for unknown destination:", dstPeer)
|
||||
return nil
|
||||
}
|
||||
conn, found := peer.ConnectionTo(relayPeerName)
|
||||
if !found {
|
||||
// Again, could just be a race, not necessarily an error
|
||||
log.Println("Unable to find connection to relay peer", relayPeerName)
|
||||
return nil
|
||||
}
|
||||
return conn.(*LocalConnection).Forward(&ForwardedFrame{
|
||||
srcPeer: srcPeer,
|
||||
dstPeer: dstPeer,
|
||||
frame: frame},
|
||||
dec)
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) RelayBroadcast(srcPeer *Peer, frame []byte, dec *EthernetDecoder) {
|
||||
nextHops := peer.router.Routes.Broadcast(srcPeer.Name)
|
||||
if len(nextHops) == 0 {
|
||||
return
|
||||
}
|
||||
for _, conn := range peer.ConnectionsTo(nextHops) {
|
||||
err := conn.(*LocalConnection).Forward(&ForwardedFrame{
|
||||
srcPeer: srcPeer,
|
||||
dstPeer: conn.Remote(),
|
||||
frame: frame},
|
||||
dec)
|
||||
if err != nil {
|
||||
if ftbe, ok := err.(FrameTooBigError); ok {
|
||||
log.Warningf("dropping too big DF broadcast frame (%v -> %v): PMTU=%v", dec.IP.SrcIP, dec.IP.DstIP, ftbe.EPMTU)
|
||||
} else {
|
||||
log.Errorln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) Connections() ConnectionSet {
|
||||
connections := make(ConnectionSet)
|
||||
peer.RLock()
|
||||
@@ -112,21 +62,16 @@ func (peer *LocalPeer) CreateConnection(peerAddr string, acceptNewPeer bool) err
|
||||
if err := peer.checkConnectionLimit(); err != nil {
|
||||
return err
|
||||
}
|
||||
tcpAddr, tcpErr := net.ResolveTCPAddr("tcp4", peerAddr)
|
||||
udpAddr, udpErr := net.ResolveUDPAddr("udp4", peerAddr)
|
||||
if tcpErr != nil || udpErr != nil {
|
||||
// they really should have the same value, but just in case...
|
||||
if tcpErr == nil {
|
||||
return udpErr
|
||||
}
|
||||
return tcpErr
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", peerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcpConn, err := net.DialTCP("tcp4", nil, tcpAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connRemote := NewRemoteConnection(peer.Peer, nil, tcpConn.RemoteAddr().String(), true, false)
|
||||
StartLocalConnection(connRemote, tcpConn, udpAddr, peer.router, acceptNewPeer)
|
||||
StartLocalConnection(connRemote, tcpConn, peer.router, acceptNewPeer)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -210,7 +155,10 @@ func (peer *LocalPeer) handleAddConnection(conn Connection) error {
|
||||
conn.Log("connection added (new peer)")
|
||||
peer.router.SendAllGossipDown(conn)
|
||||
}
|
||||
|
||||
peer.router.Routes.Recalculate()
|
||||
peer.broadcastPeerUpdate(conn.Remote())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -224,6 +172,8 @@ func (peer *LocalPeer) handleConnectionEstablished(conn Connection) {
|
||||
}
|
||||
peer.connectionEstablished(conn)
|
||||
conn.Log("connection fully established")
|
||||
|
||||
peer.router.Routes.Recalculate()
|
||||
peer.broadcastPeerUpdate()
|
||||
}
|
||||
|
||||
@@ -243,14 +193,20 @@ func (peer *LocalPeer) handleDeleteConnection(conn Connection) {
|
||||
// Must do garbage collection first to ensure we don't send out an
|
||||
// update with unreachable peers (can cause looping)
|
||||
peer.router.Peers.GarbageCollect()
|
||||
peer.router.Routes.Recalculate()
|
||||
peer.broadcastPeerUpdate()
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
func (peer *LocalPeer) broadcastPeerUpdate(peers ...*Peer) {
|
||||
peer.router.Routes.Recalculate()
|
||||
peer.router.TopologyGossip.GossipBroadcast(NewTopologyGossipData(peer.router.Peers, append(peers, peer.Peer)...))
|
||||
// Some tests run without a router. This should be fixed so
|
||||
// that the relevant part of Router can be easily run in the
|
||||
// context of a test, but that will involve significant
|
||||
// reworking of tests.
|
||||
if peer.router != nil {
|
||||
peer.router.BroadcastTopologyUpdate(append(peers, peer.Peer))
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) checkConnectionLimit() error {
|
||||
@@ -265,20 +221,20 @@ func (peer *LocalPeer) addConnection(conn Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
peer.connections[conn.Remote().Name] = conn
|
||||
peer.version++
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) deleteConnection(conn Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
delete(peer.connections, conn.Remote().Name)
|
||||
peer.version++
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) connectionEstablished(conn Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
peer.version++
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) connectionCount() int {
|
||||
@@ -286,3 +242,20 @@ func (peer *LocalPeer) connectionCount() int {
|
||||
defer peer.RUnlock()
|
||||
return len(peer.connections)
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) setShortID(shortID PeerShortID) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
peer.ShortID = shortID
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) setVersionBeyond(version uint64) bool {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
if version >= peer.Version {
|
||||
peer.Version = version + 1
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// It supplies some mock implementations to other unit tests, and is
|
||||
// named "...test.go" so it is only compiled under `go test`.
|
||||
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -14,8 +14,10 @@ import (
|
||||
|
||||
// Add to peers a connection from peers.ourself to p
|
||||
func (peers *Peers) AddTestConnection(p *Peer) {
|
||||
toPeer := NewPeer(p.Name, "", p.UID, 0)
|
||||
peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
|
||||
summary := p.PeerSummary
|
||||
summary.Version = 0
|
||||
toPeer := NewPeerFromSummary(summary)
|
||||
toPeer = peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
|
||||
conn := newMockConnection(peers.ourself.Peer, toPeer)
|
||||
peers.ourself.addConnection(conn)
|
||||
peers.ourself.connectionEstablished(conn)
|
||||
@@ -23,11 +25,9 @@ func (peers *Peers) AddTestConnection(p *Peer) {
|
||||
|
||||
// Add to peers a connection from p1 to p2
|
||||
func (peers *Peers) AddTestRemoteConnection(p1, p2 *Peer) {
|
||||
fromName := p1.Name
|
||||
fromPeer := NewPeer(fromName, "", p1.UID, 0)
|
||||
fromPeer := NewPeerFrom(p1)
|
||||
fromPeer = peers.FetchWithDefault(fromPeer)
|
||||
toName := p2.Name
|
||||
toPeer := NewPeer(toName, "", p2.UID, 0)
|
||||
toPeer := NewPeerFrom(p2)
|
||||
toPeer = peers.FetchWithDefault(toPeer)
|
||||
peers.ourself.addConnection(&RemoteConnection{fromPeer, toPeer, "", false, false})
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func checkEqualConns(t *testing.T, ourName PeerName, got, wanted map[PeerName]Co
|
||||
// Get all the peers from a Peers in a slice
|
||||
func (peers *Peers) allPeers() []*Peer {
|
||||
var res []*Peer
|
||||
for _, peer := range peers.table {
|
||||
for _, peer := range peers.byName {
|
||||
res = append(res, peer)
|
||||
}
|
||||
return res
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type Overlay interface {
|
||||
// Enhance a features map with overlay-related features
|
||||
AddFeaturesTo(map[string]string)
|
||||
|
||||
// Prepare on overlay connection. The connection should remain
|
||||
// passive until it has been Confirm()ed.
|
||||
PrepareConnection(OverlayConnectionParams) (OverlayConnection, error)
|
||||
|
||||
// Obtain diagnostic information specific to the overlay
|
||||
Diagnostics() interface{}
|
||||
}
|
||||
|
||||
type OverlayConnectionParams struct {
|
||||
RemotePeer *Peer
|
||||
|
||||
// The local address of the corresponding TCP connection. Used to
|
||||
// derive the local IP address for sending. May differ for
|
||||
// different overlay connections.
|
||||
LocalAddr *net.TCPAddr
|
||||
|
||||
// The remote address of the corresponding TCP connection. Used to
|
||||
// determine the address to send to, but only if the TCP
|
||||
// connection is outbound. Otherwise the Overlay needs to discover
|
||||
// it (e.g. from incoming datagrams).
|
||||
RemoteAddr *net.TCPAddr
|
||||
|
||||
// Is the corresponding TCP connection outbound?
|
||||
Outbound bool
|
||||
|
||||
// Unique identifier for this connection
|
||||
ConnUID uint64
|
||||
|
||||
// Session key, if connection is encrypted; nil otherwise.
|
||||
//
|
||||
// NB: overlay connections must take care not to use nonces which
|
||||
// may collide with those of the main connection. These nonces are
|
||||
// 192 bits, with the top most bit unspecified, the next bit set
|
||||
// to 1, followed by 126 zero bits, and a message sequence number
|
||||
// in the lowest 64 bits.
|
||||
SessionKey *[32]byte
|
||||
|
||||
// Function to send a control message to the counterpart
|
||||
// overlay connection.
|
||||
SendControlMessage func(tag byte, msg []byte) error
|
||||
|
||||
// Features passed at connection initiation
|
||||
Features map[string]string
|
||||
}
|
||||
|
||||
// All of the machinery to manage overlay connectivity to a particular
|
||||
// peer
|
||||
type OverlayConnection interface {
|
||||
// Confirm that the connection is really wanted, and so the
|
||||
// Overlay should begin heartbeats etc. to verify the operation of
|
||||
// the overlay connection.
|
||||
Confirm()
|
||||
|
||||
// A channel indicating that the overlay connection is
|
||||
// established, i.e. its operation has been confirmed.
|
||||
EstablishedChannel() <-chan struct{}
|
||||
|
||||
// A channel indicating an error from the overlay connection. The
|
||||
// overlay connection is not expected to be operational after the
|
||||
// first error, so the channel only needs to buffer a single
|
||||
// error.
|
||||
ErrorChannel() <-chan error
|
||||
|
||||
Stop()
|
||||
|
||||
// Handle a message from the peer. 'tag' exists for
|
||||
// compatibility, and should always be
|
||||
// ProtocolOverlayControlMessage for non-sleeve overlays.
|
||||
ControlMessage(tag byte, msg []byte)
|
||||
|
||||
// User facing overlay name
|
||||
DisplayName() string
|
||||
}
|
||||
|
||||
type NullOverlay struct{}
|
||||
|
||||
func (NullOverlay) AddFeaturesTo(map[string]string) {
|
||||
}
|
||||
|
||||
func (NullOverlay) PrepareConnection(OverlayConnectionParams) (OverlayConnection, error) {
|
||||
return NullOverlay{}, nil
|
||||
}
|
||||
|
||||
func (NullOverlay) Diagnostics() interface{} {
|
||||
return nil
|
||||
}
|
||||
func (NullOverlay) Confirm() {
|
||||
}
|
||||
|
||||
func (NullOverlay) EstablishedChannel() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NullOverlay) ErrorChannel() <-chan error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NullOverlay) Stop() {
|
||||
}
|
||||
|
||||
func (NullOverlay) ControlMessage(byte, []byte) {
|
||||
}
|
||||
|
||||
func (NullOverlay) DisplayName() string {
|
||||
return "null"
|
||||
}
|
||||
@@ -1,41 +1,111 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func randBytes(n int) []byte {
|
||||
buf := make([]byte, n)
|
||||
_, err := rand.Read(buf)
|
||||
checkFatal(err)
|
||||
return buf
|
||||
}
|
||||
|
||||
func randUint64() (r uint64) {
|
||||
return binary.LittleEndian.Uint64(randBytes(8))
|
||||
}
|
||||
|
||||
func randUint16() (r uint16) {
|
||||
return binary.LittleEndian.Uint16(randBytes(2))
|
||||
}
|
||||
|
||||
type PeerUID uint64
|
||||
|
||||
func randomPeerUID() PeerUID {
|
||||
for {
|
||||
uid := randUint64()
|
||||
if uid != 0 { // uid 0 is reserved for peer placeholder
|
||||
return PeerUID(uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ParsePeerUID(s string) (PeerUID, error) {
|
||||
uid, err := strconv.ParseUint(s, 10, 64)
|
||||
return PeerUID(uid), err
|
||||
}
|
||||
|
||||
// Short IDs exist for the sake of fast datapath. They are 12 bits,
|
||||
// randomly assigned, but we detect and recover from collisions. This
|
||||
// does limit us to 4096 peers, but that should be sufficient for a
|
||||
// while.
|
||||
type PeerShortID uint16
|
||||
|
||||
const PeerShortIDBits = 12
|
||||
|
||||
func randomPeerShortID() PeerShortID {
|
||||
return PeerShortID(randUint16() & (1<<PeerShortIDBits - 1))
|
||||
}
|
||||
|
||||
type PeerSummary struct {
|
||||
NameByte []byte
|
||||
NickName string
|
||||
UID PeerUID
|
||||
Version uint64
|
||||
ShortID PeerShortID
|
||||
HasShortID bool
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
Name PeerName
|
||||
NameByte []byte
|
||||
NickName string
|
||||
UID PeerUID
|
||||
version uint64
|
||||
Name PeerName
|
||||
PeerSummary
|
||||
localRefCount uint64 // maintained by Peers
|
||||
connections map[PeerName]Connection
|
||||
}
|
||||
|
||||
type ListOfPeers []*Peer
|
||||
|
||||
func (lop ListOfPeers) Len() int {
|
||||
return len(lop)
|
||||
}
|
||||
func (lop ListOfPeers) Swap(i, j int) {
|
||||
lop[i], lop[j] = lop[j], lop[i]
|
||||
}
|
||||
func (lop ListOfPeers) Less(i, j int) bool {
|
||||
return lop[i].Name < lop[j].Name
|
||||
}
|
||||
|
||||
type ConnectionSet map[Connection]struct{}
|
||||
|
||||
func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64) *Peer {
|
||||
if uid == 0 {
|
||||
uid = PeerUID(randUint64())
|
||||
}
|
||||
func NewPeerFromSummary(summary PeerSummary) *Peer {
|
||||
return &Peer{
|
||||
Name: name,
|
||||
NameByte: name.Bin(),
|
||||
NickName: nickName,
|
||||
UID: uid,
|
||||
version: version,
|
||||
connections: make(map[PeerName]Connection)}
|
||||
Name: PeerNameFromBin(summary.NameByte),
|
||||
PeerSummary: summary,
|
||||
connections: make(map[PeerName]Connection),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64, shortID PeerShortID) *Peer {
|
||||
return NewPeerFromSummary(PeerSummary{
|
||||
NameByte: name.Bin(),
|
||||
NickName: nickName,
|
||||
UID: uid,
|
||||
Version: version,
|
||||
ShortID: shortID,
|
||||
HasShortID: true,
|
||||
})
|
||||
}
|
||||
|
||||
func NewPeerPlaceholder(name PeerName) *Peer {
|
||||
return NewPeerFromSummary(PeerSummary{NameByte: name.Bin()})
|
||||
}
|
||||
|
||||
func NewPeerFrom(peer *Peer) *Peer {
|
||||
return NewPeerFromSummary(peer.PeerSummary)
|
||||
}
|
||||
|
||||
func (peer *Peer) String() string {
|
||||
@@ -66,7 +136,7 @@ func (peer *Peer) String() string {
|
||||
// NB: This function should generally be invoked while holding a read
|
||||
// lock on Peers and LocalPeer.
|
||||
func (peer *Peer) Routes(stopAt *Peer, establishedAndSymmetric bool) (bool, map[PeerName]PeerName) {
|
||||
routes := make(map[PeerName]PeerName)
|
||||
routes := make(unicastRoutes)
|
||||
routes[peer.Name] = UnknownPeerName
|
||||
nextWorklist := []*Peer{peer}
|
||||
for len(nextWorklist) > 0 {
|
||||
@@ -3,7 +3,7 @@
|
||||
// Let peer names be sha256 hashes...of anything (as long as it's
|
||||
// unique).
|
||||
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
@@ -15,7 +15,7 @@
|
||||
// name. In particular it doesn't actually have to be the MAC of, say,
|
||||
// the network interface the peer is sniffing on.
|
||||
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"net"
|
||||
@@ -56,3 +56,20 @@ func (name PeerName) Bin() []byte {
|
||||
func (name PeerName) String() string {
|
||||
return intmac(uint64(name)).String()
|
||||
}
|
||||
|
||||
func macint(mac net.HardwareAddr) (r uint64) {
|
||||
for _, b := range mac {
|
||||
r <<= 8
|
||||
r |= uint64(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func intmac(key uint64) (r net.HardwareAddr) {
|
||||
r = make([]byte, 6)
|
||||
for i := 5; i >= 0; i-- {
|
||||
r[i] = byte(key)
|
||||
key >>= 8
|
||||
}
|
||||
return
|
||||
}
|
||||
+530
@@ -0,0 +1,530 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var void = struct{}{}
|
||||
|
||||
type Peers struct {
|
||||
sync.RWMutex
|
||||
ourself *LocalPeer
|
||||
byName map[PeerName]*Peer
|
||||
byShortID map[PeerShortID]ShortIDPeers
|
||||
onGC []func(*Peer)
|
||||
|
||||
// Called when the mapping from short ids to peers changes
|
||||
onInvalidateShortIDs []func()
|
||||
}
|
||||
|
||||
type ShortIDPeers struct {
|
||||
// If we know about a single peer with the short id, this is
|
||||
// that peer. If there is a collision, this is the peer with
|
||||
// the lowest Name.
|
||||
peer *Peer
|
||||
|
||||
// In case of a collision, this holds the other peers.
|
||||
others []*Peer
|
||||
}
|
||||
|
||||
type PeerNameSet map[PeerName]struct{}
|
||||
|
||||
type ConnectionSummary struct {
|
||||
NameByte []byte
|
||||
RemoteTCPAddr string
|
||||
Outbound bool
|
||||
Established bool
|
||||
}
|
||||
|
||||
// Pending notifications due to changes to Peers that need to be sent
|
||||
// out once the Peers is unlocked.
|
||||
type PeersPendingNotifications struct {
|
||||
// Peers that have been GCed
|
||||
removed []*Peer
|
||||
|
||||
// The mapping from shorts ids to peers changed
|
||||
invalidateShortIDs bool
|
||||
|
||||
// The local short ID needs reassigning due to a collision
|
||||
reassignLocalShortID bool
|
||||
|
||||
// The local peer was modified
|
||||
localPeerModified bool
|
||||
}
|
||||
|
||||
func NewPeers(ourself *LocalPeer) *Peers {
|
||||
peers := &Peers{
|
||||
ourself: ourself,
|
||||
byName: make(map[PeerName]*Peer),
|
||||
byShortID: make(map[PeerShortID]ShortIDPeers),
|
||||
}
|
||||
peers.FetchWithDefault(ourself.Peer)
|
||||
return peers
|
||||
}
|
||||
|
||||
func (peers *Peers) OnGC(callback func(*Peer)) {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
|
||||
// Although the array underlying peers.onGC might be accessed
|
||||
// without holding the lock in unlockAndNotify, we don't
|
||||
// support removing callbacks, so a simple append here is
|
||||
// safe.
|
||||
peers.onGC = append(peers.onGC, callback)
|
||||
}
|
||||
|
||||
func (peers *Peers) OnInvalidateShortIDs(callback func()) {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
|
||||
// Safe, as in OnGC
|
||||
peers.onInvalidateShortIDs = append(peers.onInvalidateShortIDs, callback)
|
||||
}
|
||||
|
||||
func (peers *Peers) unlockAndNotify(pending *PeersPendingNotifications) {
|
||||
broadcastLocalPeer := (pending.reassignLocalShortID && peers.reassignLocalShortID(pending)) ||
|
||||
pending.localPeerModified
|
||||
onGC := peers.onGC
|
||||
onInvalidateShortIDs := peers.onInvalidateShortIDs
|
||||
peers.Unlock()
|
||||
|
||||
if pending.removed != nil {
|
||||
for _, callback := range onGC {
|
||||
for _, peer := range pending.removed {
|
||||
callback(peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pending.invalidateShortIDs {
|
||||
for _, callback := range onInvalidateShortIDs {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
if broadcastLocalPeer {
|
||||
peers.ourself.broadcastPeerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func (peers *Peers) addByShortID(peer *Peer, pending *PeersPendingNotifications) {
|
||||
if !peer.HasShortID {
|
||||
return
|
||||
}
|
||||
|
||||
entry, ok := peers.byShortID[peer.ShortID]
|
||||
if !ok {
|
||||
entry = ShortIDPeers{peer: peer}
|
||||
} else if entry.peer == nil {
|
||||
// This short ID is free, but was used in the past.
|
||||
// Because we are reusing it, it's an invalidation
|
||||
// event.
|
||||
entry.peer = peer
|
||||
pending.invalidateShortIDs = true
|
||||
} else if peer.Name < entry.peer.Name {
|
||||
// Short ID collision, this peer becomes the principal
|
||||
// peer for the short ID, bumping the previous one
|
||||
// into others.
|
||||
|
||||
if entry.peer == peers.ourself.Peer {
|
||||
// The bumped peer is peers.ourself, so we
|
||||
// need to look for a new short id
|
||||
pending.reassignLocalShortID = true
|
||||
}
|
||||
|
||||
entry.others = append(entry.others, entry.peer)
|
||||
entry.peer = peer
|
||||
pending.invalidateShortIDs = true
|
||||
} else {
|
||||
// Short ID collision, this peer is secondary
|
||||
entry.others = append(entry.others, peer)
|
||||
}
|
||||
|
||||
peers.byShortID[peer.ShortID] = entry
|
||||
}
|
||||
|
||||
func (peers *Peers) deleteByShortID(peer *Peer, pending *PeersPendingNotifications) {
|
||||
if !peer.HasShortID {
|
||||
return
|
||||
}
|
||||
|
||||
entry := peers.byShortID[peer.ShortID]
|
||||
var otherIndex int
|
||||
|
||||
if peer != entry.peer {
|
||||
// peer is secondary, find its index in others
|
||||
otherIndex = -1
|
||||
|
||||
for i, other := range entry.others {
|
||||
if peer == other {
|
||||
otherIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if otherIndex < 0 {
|
||||
return
|
||||
}
|
||||
} else if len(entry.others) != 0 {
|
||||
// need to find the peer with the lowest name to
|
||||
// become the new principal one
|
||||
otherIndex = 0
|
||||
minName := entry.others[0].Name
|
||||
|
||||
for i := 1; i < len(entry.others); i++ {
|
||||
otherName := entry.others[i].Name
|
||||
if otherName < minName {
|
||||
minName = otherName
|
||||
otherIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
entry.peer = entry.others[otherIndex]
|
||||
pending.invalidateShortIDs = true
|
||||
} else {
|
||||
// This is the last peer with the short id. We clear
|
||||
// the entry, don't delete it, in order to detect when
|
||||
// it gets re-used.
|
||||
peers.byShortID[peer.ShortID] = ShortIDPeers{}
|
||||
return
|
||||
}
|
||||
|
||||
entry.others[otherIndex] = entry.others[len(entry.others)-1]
|
||||
entry.others = entry.others[:len(entry.others)-1]
|
||||
peers.byShortID[peer.ShortID] = entry
|
||||
}
|
||||
|
||||
func (peers *Peers) reassignLocalShortID(pending *PeersPendingNotifications) bool {
|
||||
newShortID, ok := peers.chooseShortID()
|
||||
if ok {
|
||||
peers.setLocalShortID(newShortID, pending)
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise we'll try again later on in garbageColleect
|
||||
return false
|
||||
}
|
||||
|
||||
func (peers *Peers) setLocalShortID(newShortID PeerShortID, pending *PeersPendingNotifications) {
|
||||
peers.deleteByShortID(peers.ourself.Peer, pending)
|
||||
peers.ourself.setShortID(newShortID)
|
||||
peers.addByShortID(peers.ourself.Peer, pending)
|
||||
}
|
||||
|
||||
// Choose an available short id at random
|
||||
func (peers *Peers) chooseShortID() (PeerShortID, bool) {
|
||||
rng := rand.New(rand.NewSource(int64(randUint64())))
|
||||
|
||||
// First, just try picking some short ids at random, and
|
||||
// seeing if they are available:
|
||||
for i := 0; i < 10; i++ {
|
||||
shortID := PeerShortID(rng.Intn(1 << PeerShortIDBits))
|
||||
if peers.byShortID[shortID].peer == nil {
|
||||
return shortID, true
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like most short ids are used. So count the number of
|
||||
// unused ones, and pick one at random.
|
||||
available := int(1 << PeerShortIDBits)
|
||||
for _, entry := range peers.byShortID {
|
||||
if entry.peer != nil {
|
||||
available--
|
||||
}
|
||||
}
|
||||
|
||||
if available == 0 {
|
||||
// All short ids are used.
|
||||
return 0, false
|
||||
}
|
||||
|
||||
n := rng.Intn(available)
|
||||
var i PeerShortID
|
||||
for {
|
||||
if peers.byShortID[i].peer == nil {
|
||||
if n == 0 {
|
||||
return PeerShortID(i), true
|
||||
}
|
||||
|
||||
n--
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (peers *Peers) FetchWithDefault(peer *Peer) *Peer {
|
||||
peers.Lock()
|
||||
var pending PeersPendingNotifications
|
||||
defer peers.unlockAndNotify(&pending)
|
||||
|
||||
if existingPeer, found := peers.byName[peer.Name]; found {
|
||||
existingPeer.localRefCount++
|
||||
return existingPeer
|
||||
}
|
||||
|
||||
peers.byName[peer.Name] = peer
|
||||
peers.addByShortID(peer, &pending)
|
||||
peer.localRefCount++
|
||||
return peer
|
||||
}
|
||||
|
||||
func (peers *Peers) Fetch(name PeerName) *Peer {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
return peers.byName[name]
|
||||
}
|
||||
|
||||
func (peers *Peers) FetchAndAddRef(name PeerName) *Peer {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
peer := peers.byName[name]
|
||||
if peer != nil {
|
||||
peer.localRefCount++
|
||||
}
|
||||
return peer
|
||||
}
|
||||
|
||||
func (peers *Peers) FetchByShortID(shortID PeerShortID) *Peer {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
return peers.byShortID[shortID].peer
|
||||
}
|
||||
|
||||
func (peers *Peers) Dereference(peer *Peer) {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
peer.localRefCount--
|
||||
}
|
||||
|
||||
func (peers *Peers) ForEach(fun func(*Peer)) {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
for _, peer := range peers.byName {
|
||||
fun(peer)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge an incoming update with our own topology.
|
||||
//
|
||||
// We add peers hitherto unknown to us, and update peers for which the
|
||||
// update contains a more recent version than known to us. The return
|
||||
// value is a) a representation of the received update, and b) an
|
||||
// "improved" update containing just these new/updated elements.
|
||||
func (peers *Peers) ApplyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
|
||||
peers.Lock()
|
||||
var pending PeersPendingNotifications
|
||||
defer peers.unlockAndNotify(&pending)
|
||||
|
||||
newPeers, decodedUpdate, decodedConns, err := peers.decodeUpdate(update)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Add new peers
|
||||
for name, newPeer := range newPeers {
|
||||
peers.byName[name] = newPeer
|
||||
peers.addByShortID(newPeer, &pending)
|
||||
}
|
||||
|
||||
// Now apply the updates
|
||||
newUpdate := peers.applyUpdate(decodedUpdate, decodedConns, &pending)
|
||||
peers.garbageCollect(&pending)
|
||||
for _, peerRemoved := range pending.removed {
|
||||
delete(newUpdate, peerRemoved.Name)
|
||||
}
|
||||
|
||||
updateNames := make(PeerNameSet)
|
||||
for _, peer := range decodedUpdate {
|
||||
updateNames[peer.Name] = void
|
||||
}
|
||||
|
||||
return updateNames, newUpdate, nil
|
||||
}
|
||||
|
||||
func (peers *Peers) Names() PeerNameSet {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
|
||||
names := make(PeerNameSet)
|
||||
for name := range peers.byName {
|
||||
names[name] = void
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (peers *Peers) EncodePeers(names PeerNameSet) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
for name := range names {
|
||||
if peer, found := peers.byName[name]; found {
|
||||
if peer == peers.ourself.Peer {
|
||||
peers.ourself.Encode(enc)
|
||||
} else {
|
||||
peer.Encode(enc)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (peers *Peers) GarbageCollect() {
|
||||
peers.Lock()
|
||||
var pending PeersPendingNotifications
|
||||
defer peers.unlockAndNotify(&pending)
|
||||
|
||||
peers.garbageCollect(&pending)
|
||||
}
|
||||
|
||||
func (peers *Peers) garbageCollect(pending *PeersPendingNotifications) {
|
||||
peers.ourself.RLock()
|
||||
_, reached := peers.ourself.Routes(nil, false)
|
||||
peers.ourself.RUnlock()
|
||||
|
||||
for name, peer := range peers.byName {
|
||||
if _, found := reached[peer.Name]; !found && peer.localRefCount == 0 {
|
||||
delete(peers.byName, name)
|
||||
peers.deleteByShortID(peer, pending)
|
||||
pending.removed = append(pending.removed, peer)
|
||||
}
|
||||
}
|
||||
|
||||
if peers.byShortID[peers.ourself.ShortID].peer != peers.ourself.Peer {
|
||||
// The local peer doesn't own its short id. Garbage
|
||||
// collection might have freed some up, so try to
|
||||
// reassign.
|
||||
pending.reassignLocalShortID = true
|
||||
}
|
||||
}
|
||||
|
||||
func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, decodedUpdate []*Peer, decodedConns [][]ConnectionSummary, err error) {
|
||||
newPeers = make(map[PeerName]*Peer)
|
||||
decodedUpdate = []*Peer{}
|
||||
decodedConns = [][]ConnectionSummary{}
|
||||
|
||||
decoder := gob.NewDecoder(bytes.NewReader(update))
|
||||
|
||||
for {
|
||||
peerSummary, connSummaries, decErr := decodePeer(decoder)
|
||||
if decErr == io.EOF {
|
||||
break
|
||||
} else if decErr != nil {
|
||||
err = decErr
|
||||
return
|
||||
}
|
||||
newPeer := NewPeerFromSummary(peerSummary)
|
||||
decodedUpdate = append(decodedUpdate, newPeer)
|
||||
decodedConns = append(decodedConns, connSummaries)
|
||||
if _, found := peers.byName[newPeer.Name]; !found {
|
||||
newPeers[newPeer.Name] = newPeer
|
||||
}
|
||||
}
|
||||
|
||||
for _, connSummaries := range decodedConns {
|
||||
for _, connSummary := range connSummaries {
|
||||
remoteName := PeerNameFromBin(connSummary.NameByte)
|
||||
if _, found := newPeers[remoteName]; found {
|
||||
continue
|
||||
}
|
||||
if _, found := peers.byName[remoteName]; found {
|
||||
continue
|
||||
}
|
||||
// Update refers to a peer which we have no knowledge of.
|
||||
newPeers[remoteName] = NewPeerPlaceholder(remoteName)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]ConnectionSummary, pending *PeersPendingNotifications) PeerNameSet {
|
||||
newUpdate := make(PeerNameSet)
|
||||
for idx, newPeer := range decodedUpdate {
|
||||
connSummaries := decodedConns[idx]
|
||||
name := newPeer.Name
|
||||
// guaranteed to find peer in the peers.byName
|
||||
switch peer := peers.byName[name]; peer {
|
||||
case peers.ourself.Peer:
|
||||
if newPeer.UID != peer.UID {
|
||||
// The update contains information about an old
|
||||
// incarnation of ourselves. We increase our version
|
||||
// number beyond that which we received, so our
|
||||
// information supersedes the old one when it is
|
||||
// received by other peers.
|
||||
pending.localPeerModified = peers.ourself.setVersionBeyond(newPeer.Version)
|
||||
}
|
||||
case newPeer:
|
||||
peer.connections = makeConnsMap(peer, connSummaries, peers.byName)
|
||||
newUpdate[name] = void
|
||||
default: // existing peer
|
||||
if newPeer.Version < peer.Version ||
|
||||
(newPeer.Version == peer.Version &&
|
||||
(newPeer.UID < peer.UID ||
|
||||
(newPeer.UID == peer.UID &&
|
||||
(!newPeer.HasShortID || peer.HasShortID)))) {
|
||||
continue
|
||||
}
|
||||
peer.Version = newPeer.Version
|
||||
peer.UID = newPeer.UID
|
||||
peer.NickName = newPeer.NickName
|
||||
peer.connections = makeConnsMap(peer, connSummaries, peers.byName)
|
||||
|
||||
if newPeer.ShortID != peer.ShortID || newPeer.HasShortID != peer.HasShortID {
|
||||
peers.deleteByShortID(peer, pending)
|
||||
peer.ShortID = newPeer.ShortID
|
||||
peer.HasShortID = newPeer.HasShortID
|
||||
peers.addByShortID(peer, pending)
|
||||
}
|
||||
newUpdate[name] = void
|
||||
}
|
||||
}
|
||||
return newUpdate
|
||||
}
|
||||
|
||||
func (peer *Peer) Encode(enc *gob.Encoder) {
|
||||
checkFatal(enc.Encode(peer.PeerSummary))
|
||||
|
||||
connSummaries := []ConnectionSummary{}
|
||||
for _, conn := range peer.connections {
|
||||
connSummaries = append(connSummaries, ConnectionSummary{
|
||||
conn.Remote().NameByte,
|
||||
conn.RemoteTCPAddr(),
|
||||
conn.Outbound(),
|
||||
conn.Established(),
|
||||
})
|
||||
}
|
||||
|
||||
checkFatal(enc.Encode(connSummaries))
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) Encode(enc *gob.Encoder) {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
peer.Peer.Encode(enc)
|
||||
}
|
||||
|
||||
func decodePeer(dec *gob.Decoder) (peerSummary PeerSummary, connSummaries []ConnectionSummary, err error) {
|
||||
if err = dec.Decode(&peerSummary); err != nil {
|
||||
return
|
||||
}
|
||||
if err = dec.Decode(&connSummaries); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeConnsMap(peer *Peer, connSummaries []ConnectionSummary, byName map[PeerName]*Peer) map[PeerName]Connection {
|
||||
conns := make(map[PeerName]Connection)
|
||||
for _, connSummary := range connSummaries {
|
||||
name := PeerNameFromBin(connSummary.NameByte)
|
||||
remotePeer := byName[name]
|
||||
conn := NewRemoteConnection(peer, remotePeer, connSummary.RemoteTCPAddr, connSummary.Outbound, connSummary.Established)
|
||||
conns[name] = conn
|
||||
}
|
||||
return conns
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TODO we should also test:
|
||||
//
|
||||
// - applying an incremental update, including the case where that
|
||||
// leads to an UnknownPeerError
|
||||
//
|
||||
// - the "improved update" calculation
|
||||
//
|
||||
// - non-gc of peers that are only referenced locally
|
||||
|
||||
func newNode(name PeerName) (*Peer, *Peers) {
|
||||
peer := NewLocalPeer(name, "", nil)
|
||||
peers := NewPeers(peer)
|
||||
return peer.Peer, peers
|
||||
}
|
||||
|
||||
// Check that ApplyUpdate copies the whole topology from peers
|
||||
func checkApplyUpdate(t *testing.T, peers *Peers) {
|
||||
dummyName, _ := PeerNameFromString("99:00:00:01:00:00")
|
||||
// We need a new node outside of the network, with a connection
|
||||
// into it.
|
||||
_, testBedPeers := newNode(dummyName)
|
||||
testBedPeers.AddTestConnection(peers.ourself.Peer)
|
||||
testBedPeers.ApplyUpdate(peers.EncodePeers(peers.Names()))
|
||||
|
||||
checkTopologyPeers(t, true, testBedPeers.allPeersExcept(dummyName), peers.allPeers()...)
|
||||
}
|
||||
|
||||
func TestPeersEncoding(t *testing.T) {
|
||||
const numNodes = 20
|
||||
const numIters = 1000
|
||||
var peer [numNodes]*Peer
|
||||
var ps [numNodes]*Peers
|
||||
for i := 0; i < numNodes; i++ {
|
||||
name, _ := PeerNameFromString(fmt.Sprintf("%02d:00:00:01:00:00", i))
|
||||
peer[i], ps[i] = newNode(name)
|
||||
}
|
||||
|
||||
var conns []struct{ from, to int }
|
||||
for i := 0; i < numIters; i++ {
|
||||
oper := rand.Intn(2)
|
||||
switch oper {
|
||||
case 0:
|
||||
from, to := rand.Intn(numNodes), rand.Intn(numNodes)
|
||||
if from != to {
|
||||
if _, found := peer[from].connections[peer[to].Name]; !found {
|
||||
ps[from].AddTestConnection(peer[to])
|
||||
conns = append(conns, struct{ from, to int }{from, to})
|
||||
checkApplyUpdate(t, ps[from])
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
if len(conns) > 0 {
|
||||
n := rand.Intn(len(conns))
|
||||
c := conns[n]
|
||||
ps[c.from].DeleteTestConnection(peer[c.to])
|
||||
ps[c.from].GarbageCollect()
|
||||
checkApplyUpdate(t, ps[c.from])
|
||||
conns = append(conns[:n], conns[n+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func garbageCollect(peers *Peers) []*Peer {
|
||||
var removed []*Peer
|
||||
peers.OnGC(func(peer *Peer) { removed = append(removed, peer) })
|
||||
peers.GarbageCollect()
|
||||
return removed
|
||||
}
|
||||
|
||||
func TestPeersGarbageCollection(t *testing.T) {
|
||||
const (
|
||||
peer1NameString = "01:00:00:01:00:00"
|
||||
peer2NameString = "02:00:00:02:00:00"
|
||||
peer3NameString = "03:00:00:03:00:00"
|
||||
)
|
||||
var (
|
||||
peer1Name, _ = PeerNameFromString(peer1NameString)
|
||||
peer2Name, _ = PeerNameFromString(peer2NameString)
|
||||
peer3Name, _ = PeerNameFromString(peer3NameString)
|
||||
)
|
||||
|
||||
// Create some peers with some connections to each other
|
||||
p1, ps1 := newNode(peer1Name)
|
||||
p2, ps2 := newNode(peer2Name)
|
||||
p3, ps3 := newNode(peer3Name)
|
||||
ps1.AddTestConnection(p2)
|
||||
ps2.AddTestRemoteConnection(p1, p2)
|
||||
ps2.AddTestConnection(p1)
|
||||
ps2.AddTestConnection(p3)
|
||||
ps3.AddTestConnection(p1)
|
||||
ps1.AddTestConnection(p3)
|
||||
ps2.AddTestRemoteConnection(p1, p3)
|
||||
ps2.AddTestRemoteConnection(p3, p1)
|
||||
|
||||
// Every peer is referenced, so nothing should be dropped
|
||||
require.Empty(t, garbageCollect(ps1), "peers removed")
|
||||
require.Empty(t, garbageCollect(ps2), "peers removed")
|
||||
require.Empty(t, garbageCollect(ps3), "peers removed")
|
||||
|
||||
// Drop the connection from 2 to 3, and 3 isn't garbage-collected
|
||||
// because 1 has a connection to 3
|
||||
ps2.DeleteTestConnection(p3)
|
||||
require.Empty(t, garbageCollect(ps2), "peers removed")
|
||||
|
||||
// Drop the connection from 1 to 3, and 3 will get removed by
|
||||
// garbage-collection
|
||||
ps1.DeleteTestConnection(p3)
|
||||
checkPeerArray(t, garbageCollect(ps1), p3)
|
||||
}
|
||||
|
||||
func TestShortIDCollisions(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, peers := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// Make enough peers that short id collisions are
|
||||
// overwhelmingly likely
|
||||
ps := make([]*Peer, 1<<PeerShortIDBits)
|
||||
for i := 0; i < 1<<PeerShortIDBits; i++ {
|
||||
ps[i] = NewPeer(PeerName(i), "", PeerUID(i), 0,
|
||||
PeerShortID(rng.Intn(1<<PeerShortIDBits)))
|
||||
}
|
||||
|
||||
shuffle := func() {
|
||||
for i := range ps {
|
||||
j := rng.Intn(i + 1)
|
||||
ps[i], ps[j] = ps[j], ps[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Fill peers
|
||||
shuffle()
|
||||
var pending PeersPendingNotifications
|
||||
for _, p := range ps {
|
||||
peers.addByShortID(p, &pending)
|
||||
}
|
||||
|
||||
// Check invariants
|
||||
counts := make([]int, 1<<PeerShortIDBits)
|
||||
saw := func(p *Peer) {
|
||||
if p != peers.ourself.Peer {
|
||||
counts[p.UID]++
|
||||
}
|
||||
}
|
||||
|
||||
for shortID, entry := range peers.byShortID {
|
||||
if entry.peer == nil {
|
||||
// no principal peer for this short id, so
|
||||
// others must be empty
|
||||
require.Empty(t, entry.others)
|
||||
continue
|
||||
}
|
||||
|
||||
require.Equal(t, shortID, entry.peer.ShortID)
|
||||
saw(entry.peer)
|
||||
|
||||
for _, p := range entry.others {
|
||||
saw(p)
|
||||
require.Equal(t, shortID, p.ShortID)
|
||||
|
||||
// the principal peer should have the lowest name
|
||||
require.True(t, p.Name > entry.peer.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every peer was seen
|
||||
for _, n := range counts {
|
||||
require.Equal(t, 1, n)
|
||||
}
|
||||
|
||||
// Delete all the peers
|
||||
shuffle()
|
||||
for _, p := range ps {
|
||||
peers.deleteByShortID(p, &pending)
|
||||
}
|
||||
|
||||
for _, entry := range peers.byShortID {
|
||||
if entry.peer != peers.ourself.Peer {
|
||||
require.Nil(t, entry.peer)
|
||||
}
|
||||
|
||||
require.Empty(t, entry.others)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the easy case of short id reassignment, when few short ids are taken
|
||||
func TestShortIDReassignmentEasy(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, peers := newNode(PeerName(0))
|
||||
|
||||
for i := 1; i <= 10; i++ {
|
||||
peers.FetchWithDefault(NewPeer(PeerName(i), "", PeerUID(i), 0,
|
||||
PeerShortID(rng.Intn(1<<PeerShortIDBits))))
|
||||
}
|
||||
|
||||
checkShortIDReassignment(t, peers)
|
||||
}
|
||||
|
||||
// Test the hard case of short id reassignment, when most short ids are taken
|
||||
func TestShortIDReassignmentHard(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, peers := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// Take all short ids
|
||||
ps := make([]*Peer, 1<<PeerShortIDBits)
|
||||
var pending PeersPendingNotifications
|
||||
for i := 0; i < 1<<PeerShortIDBits; i++ {
|
||||
ps[i] = NewPeer(PeerName(i), "", PeerUID(i), 0,
|
||||
PeerShortID(i))
|
||||
peers.addByShortID(ps[i], &pending)
|
||||
}
|
||||
|
||||
// As all short ids are taken, an attempted reassigment won't
|
||||
// do anything
|
||||
oldShortID := peers.ourself.ShortID
|
||||
require.False(t, peers.reassignLocalShortID(&pending))
|
||||
require.Equal(t, oldShortID, peers.ourself.ShortID)
|
||||
|
||||
// Free up a few ids
|
||||
for i := 0; i < 10; i++ {
|
||||
x := rng.Intn(len(ps))
|
||||
if ps[x] != nil {
|
||||
peers.deleteByShortID(ps[x], &pending)
|
||||
ps[x] = nil
|
||||
}
|
||||
}
|
||||
|
||||
checkShortIDReassignment(t, peers)
|
||||
}
|
||||
|
||||
func checkShortIDReassignment(t *testing.T, peers *Peers) {
|
||||
oldShortID := peers.ourself.ShortID
|
||||
peers.reassignLocalShortID(&PeersPendingNotifications{})
|
||||
require.NotEqual(t, oldShortID, peers.ourself.ShortID)
|
||||
require.Equal(t, peers.ourself.Peer, peers.byShortID[peers.ourself.ShortID].peer)
|
||||
}
|
||||
|
||||
func TestShortIDInvalidation(t *testing.T) {
|
||||
_, peers := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// need to use a short id that is not the local peer's
|
||||
shortID := peers.ourself.ShortID + 1
|
||||
|
||||
var pending PeersPendingNotifications
|
||||
|
||||
requireInvalidateShortIDs := func(expect bool) {
|
||||
require.Equal(t, expect, pending.invalidateShortIDs)
|
||||
pending.invalidateShortIDs = false
|
||||
}
|
||||
|
||||
// The use of a fresh short id does not cause invalidation
|
||||
a := NewPeer(PeerName(1), "", PeerUID(1), 0, shortID)
|
||||
peers.addByShortID(a, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// An addition which does not change the mapping
|
||||
b := NewPeer(PeerName(2), "", PeerUID(2), 0, shortID)
|
||||
peers.addByShortID(b, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// An addition which does change the mapping
|
||||
c := NewPeer(PeerName(0), "", PeerUID(0), 0, shortID)
|
||||
peers.addByShortID(c, &pending)
|
||||
requireInvalidateShortIDs(true)
|
||||
|
||||
// A deletion which does not change the mapping
|
||||
peers.deleteByShortID(b, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// A deletion which does change the mapping
|
||||
peers.deleteByShortID(c, &pending)
|
||||
requireInvalidateShortIDs(true)
|
||||
|
||||
// Deleting the last peer with a short id does not cause invalidation
|
||||
peers.deleteByShortID(a, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// .. but subsequent reuse of that short id does cause invalidation
|
||||
peers.addByShortID(a, &pending)
|
||||
requireInvalidateShortIDs(true)
|
||||
}
|
||||
|
||||
func TestShortIDPropagation(t *testing.T) {
|
||||
_, peers1 := newNode(PeerName(1))
|
||||
_, peers2 := newNode(PeerName(2))
|
||||
|
||||
peers1.AddTestConnection(peers2.ourself.Peer)
|
||||
peers1.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
|
||||
peers12 := peers1.Fetch(PeerName(2))
|
||||
old := peers12.PeerSummary
|
||||
|
||||
require.True(t,
|
||||
peers2.reassignLocalShortID(&PeersPendingNotifications{}))
|
||||
peers1.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
|
||||
require.NotEqual(t, old.Version, peers12.Version)
|
||||
require.NotEqual(t, old.ShortID, peers12.ShortID)
|
||||
}
|
||||
|
||||
func TestShortIDCollision(t *testing.T) {
|
||||
// Create 3 peers
|
||||
_, peers1 := newNode(PeerName(1))
|
||||
_, peers2 := newNode(PeerName(2))
|
||||
_, peers3 := newNode(PeerName(3))
|
||||
|
||||
var pending PeersPendingNotifications
|
||||
peers1.setLocalShortID(1, &pending)
|
||||
peers2.setLocalShortID(2, &pending)
|
||||
peers3.setLocalShortID(3, &pending)
|
||||
|
||||
peers2.AddTestConnection(peers1.ourself.Peer)
|
||||
peers3.AddTestConnection(peers2.ourself.Peer)
|
||||
|
||||
// Propogate from 1 to 2 to 3
|
||||
peers2.ApplyUpdate(peers1.EncodePeers(peers1.Names()))
|
||||
peers3.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
|
||||
|
||||
// Force the short id of peer 1 to collide with peer 2. Peer
|
||||
// 1 has the lowest name, so it gets to keep the short id
|
||||
peers1.setLocalShortID(2, &pending)
|
||||
|
||||
oldShortID := peers2.ourself.ShortID
|
||||
_, updated, _ := peers2.ApplyUpdate(peers1.EncodePeers(peers1.Names()))
|
||||
|
||||
// peer 2 should have noticed the collision and resolved it
|
||||
require.NotEqual(t, oldShortID, peers2.ourself.ShortID)
|
||||
|
||||
// The Peers do not have a Router, so broadcastPeerUpdate does
|
||||
// nothing in the context of this test. So we fake what it
|
||||
// would do.
|
||||
updated[PeerName(2)] = void
|
||||
|
||||
// the update from peer 2 should include its short id change
|
||||
peers3.ApplyUpdate(peers2.EncodePeers(updated))
|
||||
require.Equal(t, peers2.ourself.ShortID,
|
||||
peers3.Fetch(PeerName(2)).ShortID)
|
||||
}
|
||||
|
||||
// Test the case where all short ids are taken, but then some peers go
|
||||
// away, so the local peer reassigns
|
||||
func TestDeferredShortIDReassignment(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, us := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// Connect us to other peers occupying all short ids
|
||||
others := make([]*Peers, 1<<PeerShortIDBits)
|
||||
var pending PeersPendingNotifications
|
||||
for i := range others {
|
||||
_, others[i] = newNode(PeerName(i))
|
||||
others[i].setLocalShortID(PeerShortID(i), &pending)
|
||||
us.AddTestConnection(others[i].ourself.Peer)
|
||||
}
|
||||
|
||||
// Check that, as expected, the local peer does not own its
|
||||
// short id
|
||||
require.NotEqual(t, us.ourself.Peer,
|
||||
us.byShortID[us.ourself.ShortID].peer)
|
||||
|
||||
// Disconnect one peer, and we should now be able to claim its
|
||||
// short id
|
||||
other := others[rng.Intn(1<<PeerShortIDBits)]
|
||||
us.DeleteTestConnection(other.ourself.Peer)
|
||||
us.GarbageCollect()
|
||||
|
||||
require.Equal(t, us.ourself.Peer, us.byShortID[us.ourself.ShortID].peer)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -328,13 +328,14 @@ func (res *ProtocolIntroResults) setupCrypto(params ProtocolIntroParams, remoteP
|
||||
type ProtocolTag byte
|
||||
|
||||
const (
|
||||
ProtocolHeartbeat ProtocolTag = iota
|
||||
ProtocolConnectionEstablished
|
||||
ProtocolFragmentationReceived
|
||||
ProtocolPMTUVerified
|
||||
ProtocolHeartbeat = iota
|
||||
ProtocolReserved1 // legacy overlay control messages
|
||||
ProtocolReserved2 //
|
||||
ProtocolReserved3 //
|
||||
ProtocolGossip
|
||||
ProtocolGossipUnicast
|
||||
ProtocolGossipBroadcast
|
||||
ProtocolOverlayControlMsg
|
||||
)
|
||||
|
||||
type ProtocolMsg struct {
|
||||
@@ -0,0 +1,180 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
)
|
||||
|
||||
const MaxTCPMsgSize = 10 * 1024 * 1024
|
||||
|
||||
func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) {
|
||||
return box.GenerateKey(rand.Reader)
|
||||
}
|
||||
|
||||
func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte) *[32]byte {
|
||||
var sharedKey [32]byte
|
||||
box.Precompute(&sharedKey, remotePublicKey, localPrivateKey)
|
||||
sharedKeySlice := sharedKey[:]
|
||||
sharedKeySlice = append(sharedKeySlice, secretKey...)
|
||||
sessionKey := sha256.Sum256(sharedKeySlice)
|
||||
return &sessionKey
|
||||
}
|
||||
|
||||
// TCP Senders/Receivers
|
||||
|
||||
// The lowest 64 bits of the nonce contain the message sequence
|
||||
// number. The top most bit indicates the connection polarity at the
|
||||
// sender - '1' for outbound; the next indicates protocol type - '1'
|
||||
// for TCP. The remaining 126 bits are zero. The polarity is needed so
|
||||
// that the two ends of a connection do not use the same nonces; the
|
||||
// protocol type so that the TCP connection nonces are distinct from
|
||||
// nonces used by overlay connections, if they share the session key.
|
||||
// This is a requirement of the NaCl Security Model; see
|
||||
// http://nacl.cr.yp.to/box.html.
|
||||
type TCPCryptoState struct {
|
||||
sessionKey *[32]byte
|
||||
nonce [24]byte
|
||||
seqNo uint64
|
||||
}
|
||||
|
||||
func NewTCPCryptoState(sessionKey *[32]byte, outbound bool) *TCPCryptoState {
|
||||
s := &TCPCryptoState{sessionKey: sessionKey}
|
||||
if outbound {
|
||||
s.nonce[0] |= (1 << 7)
|
||||
}
|
||||
s.nonce[0] |= (1 << 6)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *TCPCryptoState) advance() {
|
||||
s.seqNo++
|
||||
binary.BigEndian.PutUint64(s.nonce[16:24], s.seqNo)
|
||||
}
|
||||
|
||||
type TCPSender interface {
|
||||
Send([]byte) error
|
||||
}
|
||||
|
||||
type GobTCPSender struct {
|
||||
encoder *gob.Encoder
|
||||
}
|
||||
|
||||
type LengthPrefixTCPSender struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
type EncryptedTCPSender struct {
|
||||
sync.RWMutex
|
||||
sender TCPSender
|
||||
state *TCPCryptoState
|
||||
}
|
||||
|
||||
func NewGobTCPSender(encoder *gob.Encoder) *GobTCPSender {
|
||||
return &GobTCPSender{encoder: encoder}
|
||||
}
|
||||
|
||||
func (sender *GobTCPSender) Send(msg []byte) error {
|
||||
return sender.encoder.Encode(msg)
|
||||
}
|
||||
|
||||
func NewLengthPrefixTCPSender(writer io.Writer) *LengthPrefixTCPSender {
|
||||
return &LengthPrefixTCPSender{writer: writer}
|
||||
}
|
||||
|
||||
func (sender *LengthPrefixTCPSender) Send(msg []byte) error {
|
||||
l := len(msg)
|
||||
if l > MaxTCPMsgSize {
|
||||
return fmt.Errorf("outgoing message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
|
||||
}
|
||||
// We copy the message so we can send it in a single Write
|
||||
// operation, thus making this thread-safe without locking.
|
||||
prefixedMsg := make([]byte, 4+l)
|
||||
binary.BigEndian.PutUint32(prefixedMsg, uint32(l))
|
||||
copy(prefixedMsg[4:], msg)
|
||||
_, err := sender.writer.Write(prefixedMsg)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewEncryptedTCPSender(sender TCPSender, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender {
|
||||
return &EncryptedTCPSender{sender: sender, state: NewTCPCryptoState(sessionKey, outbound)}
|
||||
}
|
||||
|
||||
func (sender *EncryptedTCPSender) Send(msg []byte) error {
|
||||
sender.Lock()
|
||||
defer sender.Unlock()
|
||||
encodedMsg := secretbox.Seal(nil, msg, &sender.state.nonce, sender.state.sessionKey)
|
||||
sender.state.advance()
|
||||
return sender.sender.Send(encodedMsg)
|
||||
}
|
||||
|
||||
type TCPReceiver interface {
|
||||
Receive() ([]byte, error)
|
||||
}
|
||||
|
||||
type GobTCPReceiver struct {
|
||||
decoder *gob.Decoder
|
||||
}
|
||||
|
||||
type LengthPrefixTCPReceiver struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
type EncryptedTCPReceiver struct {
|
||||
receiver TCPReceiver
|
||||
state *TCPCryptoState
|
||||
}
|
||||
|
||||
func NewGobTCPReceiver(decoder *gob.Decoder) *GobTCPReceiver {
|
||||
return &GobTCPReceiver{decoder: decoder}
|
||||
}
|
||||
|
||||
func (receiver *GobTCPReceiver) Receive() ([]byte, error) {
|
||||
var msg []byte
|
||||
err := receiver.decoder.Decode(&msg)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func NewLengthPrefixTCPReceiver(reader io.Reader) *LengthPrefixTCPReceiver {
|
||||
return &LengthPrefixTCPReceiver{reader: reader}
|
||||
}
|
||||
|
||||
func (receiver *LengthPrefixTCPReceiver) Receive() ([]byte, error) {
|
||||
lenPrefix := make([]byte, 4)
|
||||
if _, err := io.ReadFull(receiver.reader, lenPrefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := binary.BigEndian.Uint32(lenPrefix)
|
||||
if l > MaxTCPMsgSize {
|
||||
return nil, fmt.Errorf("incoming message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
|
||||
}
|
||||
msg := make([]byte, l)
|
||||
_, err := io.ReadFull(receiver.reader, msg)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func NewEncryptedTCPReceiver(receiver TCPReceiver, sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver {
|
||||
return &EncryptedTCPReceiver{receiver: receiver, state: NewTCPCryptoState(sessionKey, !outbound)}
|
||||
}
|
||||
|
||||
func (receiver *EncryptedTCPReceiver) Receive() ([]byte, error) {
|
||||
msg, err := receiver.receiver.Receive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodedMsg, success := secretbox.Open(nil, msg, &receiver.state.nonce, receiver.state.sessionKey)
|
||||
if !success {
|
||||
return nil, fmt.Errorf("Unable to decrypt TCP msg")
|
||||
}
|
||||
|
||||
receiver.state.advance()
|
||||
return decodedMsg, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"io"
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
Port = 6783
|
||||
ChannelSize = 16
|
||||
TCPHeartbeat = 30 * time.Second
|
||||
GossipInterval = 30 * time.Second
|
||||
MaxDuration = time.Duration(math.MaxInt64)
|
||||
|
||||
acceptMaxTokens = 100 // [1]
|
||||
acceptTokenDelay = 100 * time.Millisecond // [2]
|
||||
)
|
||||
|
||||
// [1] capacity of token bucket for rate limiting accepts
|
||||
|
||||
// [2] control rate at which new tokens are added to the bucket
|
||||
|
||||
var (
|
||||
log = common.Log
|
||||
checkFatal = common.CheckFatal
|
||||
checkWarn = common.CheckWarn
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
ProtocolMinVersion byte
|
||||
Password []byte
|
||||
ConnLimit int
|
||||
PeerDiscovery bool
|
||||
TrustedSubnets []*net.IPNet
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
Config
|
||||
Overlay Overlay
|
||||
Ourself *LocalPeer
|
||||
Peers *Peers
|
||||
Routes *Routes
|
||||
ConnectionMaker *ConnectionMaker
|
||||
gossipLock sync.RWMutex
|
||||
gossipChannels GossipChannels
|
||||
TopologyGossip Gossip
|
||||
acceptLimiter *TokenBucket
|
||||
}
|
||||
|
||||
func NewRouter(config Config, name PeerName, nickName string, overlay Overlay) *Router {
|
||||
router := &Router{Config: config, gossipChannels: make(GossipChannels)}
|
||||
|
||||
if overlay == nil {
|
||||
overlay = NullOverlay{}
|
||||
}
|
||||
|
||||
router.Overlay = overlay
|
||||
router.Ourself = NewLocalPeer(name, nickName, router)
|
||||
router.Peers = NewPeers(router.Ourself)
|
||||
router.Peers.OnGC(func(peer *Peer) {
|
||||
log.Println("Removed unreachable peer", peer)
|
||||
})
|
||||
router.Routes = NewRoutes(router.Ourself, router.Peers)
|
||||
router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port, router.PeerDiscovery)
|
||||
router.TopologyGossip = router.NewGossip("topology", router)
|
||||
router.acceptLimiter = NewTokenBucket(acceptMaxTokens, acceptTokenDelay)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// Start listening for TCP connections. This is separate from
|
||||
// NewRouter so that gossipers can register before we start forming
|
||||
// connections.
|
||||
func (router *Router) Start() {
|
||||
router.listenTCP(router.Port)
|
||||
}
|
||||
|
||||
func (router *Router) Stop() error {
|
||||
// TODO: perform graceful shutdown...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (router *Router) UsingPassword() bool {
|
||||
return router.Password != nil
|
||||
}
|
||||
|
||||
func (router *Router) listenTCP(localPort int) {
|
||||
localAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprint(":", localPort))
|
||||
checkFatal(err)
|
||||
ln, err := net.ListenTCP("tcp4", localAddr)
|
||||
checkFatal(err)
|
||||
go func() {
|
||||
defer ln.Close()
|
||||
for {
|
||||
tcpConn, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
continue
|
||||
}
|
||||
router.acceptTCP(tcpConn)
|
||||
router.acceptLimiter.Wait()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (router *Router) acceptTCP(tcpConn *net.TCPConn) {
|
||||
remoteAddrStr := tcpConn.RemoteAddr().String()
|
||||
log.Printf("->[%s] connection accepted", remoteAddrStr)
|
||||
connRemote := NewRemoteConnection(router.Ourself.Peer, nil, remoteAddrStr, false, false)
|
||||
StartLocalConnection(connRemote, tcpConn, router, true)
|
||||
}
|
||||
|
||||
// Gossiper methods - the Router is the topology Gossiper
|
||||
|
||||
type TopologyGossipData struct {
|
||||
peers *Peers
|
||||
update PeerNameSet
|
||||
}
|
||||
|
||||
func (d *TopologyGossipData) Merge(other GossipData) GossipData {
|
||||
names := make(PeerNameSet)
|
||||
for name := range d.update {
|
||||
names[name] = void
|
||||
}
|
||||
for name := range other.(*TopologyGossipData).update {
|
||||
names[name] = void
|
||||
}
|
||||
return &TopologyGossipData{peers: d.peers, update: names}
|
||||
}
|
||||
|
||||
func (d *TopologyGossipData) Encode() [][]byte {
|
||||
return [][]byte{d.peers.EncodePeers(d.update)}
|
||||
}
|
||||
|
||||
func (router *Router) BroadcastTopologyUpdate(update []*Peer) {
|
||||
names := make(PeerNameSet)
|
||||
for _, p := range update {
|
||||
names[p.Name] = void
|
||||
}
|
||||
router.TopologyGossip.GossipBroadcast(
|
||||
&TopologyGossipData{peers: router.Peers, update: names})
|
||||
}
|
||||
|
||||
func (router *Router) OnGossipUnicast(sender PeerName, msg []byte) error {
|
||||
return fmt.Errorf("unexpected topology gossip unicast: %v", msg)
|
||||
}
|
||||
|
||||
func (router *Router) OnGossipBroadcast(_ PeerName, update []byte) (GossipData, error) {
|
||||
origUpdate, _, err := router.applyTopologyUpdate(update)
|
||||
if err != nil || len(origUpdate) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &TopologyGossipData{peers: router.Peers, update: origUpdate}, nil
|
||||
}
|
||||
|
||||
func (router *Router) Gossip() GossipData {
|
||||
return &TopologyGossipData{peers: router.Peers, update: router.Peers.Names()}
|
||||
}
|
||||
|
||||
func (router *Router) OnGossip(update []byte) (GossipData, error) {
|
||||
_, newUpdate, err := router.applyTopologyUpdate(update)
|
||||
if err != nil || len(newUpdate) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &TopologyGossipData{peers: router.Peers, update: newUpdate}, nil
|
||||
}
|
||||
|
||||
func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
|
||||
origUpdate, newUpdate, err := router.Peers.ApplyUpdate(update)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(newUpdate) > 0 {
|
||||
router.ConnectionMaker.Refresh()
|
||||
router.Routes.Recalculate()
|
||||
}
|
||||
return origUpdate, newUpdate, nil
|
||||
}
|
||||
|
||||
func (router *Router) Trusts(remote *RemoteConnection) bool {
|
||||
if tcpAddr, err := net.ResolveTCPAddr("tcp4", remote.remoteTCPAddr); err == nil {
|
||||
for _, trustedSubnet := range router.TrustedSubnets {
|
||||
if trustedSubnet.Contains(tcpAddr.IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Should not happen as remoteTCPAddr was obtained from TCPConn
|
||||
log.Errorf("Unable to parse remote TCP addr: %s", err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type unicastRoutes map[PeerName]PeerName
|
||||
type broadcastRoutes map[PeerName][]PeerName
|
||||
|
||||
type Routes struct {
|
||||
sync.RWMutex
|
||||
ourself *LocalPeer
|
||||
peers *Peers
|
||||
unicast map[PeerName]PeerName
|
||||
unicastAll map[PeerName]PeerName // [1]
|
||||
broadcast map[PeerName][]PeerName
|
||||
broadcastAll map[PeerName][]PeerName // [1]
|
||||
onChange []func()
|
||||
unicast unicastRoutes
|
||||
unicastAll unicastRoutes // [1]
|
||||
broadcast broadcastRoutes
|
||||
broadcastAll broadcastRoutes // [1]
|
||||
recalculate chan<- *struct{}
|
||||
wait chan<- chan struct{}
|
||||
// [1] based on *all* connections, not just established &
|
||||
@@ -25,10 +29,10 @@ func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes {
|
||||
routes := &Routes{
|
||||
ourself: ourself,
|
||||
peers: peers,
|
||||
unicast: make(map[PeerName]PeerName),
|
||||
unicastAll: make(map[PeerName]PeerName),
|
||||
broadcast: make(map[PeerName][]PeerName),
|
||||
broadcastAll: make(map[PeerName][]PeerName),
|
||||
unicast: make(unicastRoutes),
|
||||
unicastAll: make(unicastRoutes),
|
||||
broadcast: make(broadcastRoutes),
|
||||
broadcastAll: make(broadcastRoutes),
|
||||
recalculate: recalculate,
|
||||
wait: wait}
|
||||
routes.unicast[ourself.Name] = UnknownPeerName
|
||||
@@ -39,6 +43,12 @@ func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes {
|
||||
return routes
|
||||
}
|
||||
|
||||
func (routes *Routes) OnChange(callback func()) {
|
||||
routes.Lock()
|
||||
defer routes.Unlock()
|
||||
routes.onChange = append(routes.onChange, callback)
|
||||
}
|
||||
|
||||
func (routes *Routes) PeerNames() PeerNameSet {
|
||||
return routes.peers.Names()
|
||||
}
|
||||
@@ -60,21 +70,13 @@ func (routes *Routes) UnicastAll(name PeerName) (PeerName, bool) {
|
||||
func (routes *Routes) Broadcast(name PeerName) []PeerName {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
hops, found := routes.broadcast[name]
|
||||
if !found {
|
||||
return []PeerName{}
|
||||
}
|
||||
return hops
|
||||
return routes.broadcast[name]
|
||||
}
|
||||
|
||||
func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
hops, found := routes.broadcastAll[name]
|
||||
if !found {
|
||||
return []PeerName{}
|
||||
}
|
||||
return hops
|
||||
return routes.broadcastAll[name]
|
||||
}
|
||||
|
||||
// Choose min(log2(n_peers), n_neighbouring_peers) neighbours, with a
|
||||
@@ -90,20 +92,24 @@ func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
|
||||
// returns a higher proportion of neighbours than elsewhere. In
|
||||
// extremis, on peers with fewer than log2(n_peers) neighbours, all
|
||||
// neighbours are returned.
|
||||
func (routes *Routes) RandomNeighbours(except PeerName) PeerNameSet {
|
||||
res := make(PeerNameSet)
|
||||
func (routes *Routes) RandomNeighbours(except PeerName) []PeerName {
|
||||
destinations := make(PeerNameSet)
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
count := int(math.Log2(float64(len(routes.unicastAll))))
|
||||
// depends on go's random map iteration
|
||||
for _, dst := range routes.unicastAll {
|
||||
if dst != UnknownPeerName && dst != except {
|
||||
res[dst] = void
|
||||
if len(res) >= count {
|
||||
destinations[dst] = void
|
||||
if len(destinations) >= count {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
res := make([]PeerName, 0, len(destinations))
|
||||
for dst := range destinations {
|
||||
res = append(res, dst)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -147,6 +153,8 @@ func (routes *Routes) calculate() {
|
||||
routes.peers.RLock()
|
||||
routes.ourself.RLock()
|
||||
var (
|
||||
oldUnicast = routes.unicast
|
||||
oldBroadcast = routes.broadcast
|
||||
unicast = routes.calculateUnicast(true)
|
||||
unicastAll = routes.calculateUnicast(false)
|
||||
broadcast = routes.calculateBroadcast(true)
|
||||
@@ -160,7 +168,14 @@ func (routes *Routes) calculate() {
|
||||
routes.unicastAll = unicastAll
|
||||
routes.broadcast = broadcast
|
||||
routes.broadcastAll = broadcastAll
|
||||
onChange := routes.onChange
|
||||
routes.Unlock()
|
||||
|
||||
if !unicast.equals(oldUnicast) || !broadcast.equals(oldBroadcast) {
|
||||
for _, callback := range onChange {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate all the routes for the question: if *we* want to send a
|
||||
@@ -172,7 +187,7 @@ func (routes *Routes) calculate() {
|
||||
// any knowledge of the MAC address at all. Thus there's no need
|
||||
// to exchange knowledge of MAC addresses, nor any constraints on
|
||||
// the routes that we construct.
|
||||
func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) map[PeerName]PeerName {
|
||||
func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) unicastRoutes {
|
||||
_, unicast := routes.ourself.Routes(nil, establishedAndSymmetric)
|
||||
return unicast
|
||||
}
|
||||
@@ -195,9 +210,9 @@ func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) map[PeerNam
|
||||
// Y =/= Z /\ X.Routes(Y) <= X.Routes(Z) =>
|
||||
// X.Routes(Y) u [P | Y.HasSymmetricConnectionTo(P)] <= X.Routes(Z)
|
||||
// where <= is the subset relationship on keys of the returned map.
|
||||
func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) map[PeerName][]PeerName {
|
||||
broadcast := make(map[PeerName][]PeerName)
|
||||
for _, peer := range routes.peers.table {
|
||||
func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) broadcastRoutes {
|
||||
broadcast := make(broadcastRoutes)
|
||||
for _, peer := range routes.peers.byName {
|
||||
hops := []PeerName{}
|
||||
if found, reached := peer.Routes(routes.ourself.Peer, establishedAndSymmetric); found {
|
||||
routes.ourself.ForEachConnectedPeer(establishedAndSymmetric, reached,
|
||||
@@ -207,3 +222,55 @@ func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) map[PeerN
|
||||
}
|
||||
return broadcast
|
||||
}
|
||||
|
||||
func (a unicastRoutes) equals(b unicastRoutes) bool {
|
||||
for key, aval := range a {
|
||||
if bval, ok := b[key]; !ok || bval != aval {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for key := range b {
|
||||
if _, ok := a[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (a broadcastRoutes) equals(b broadcastRoutes) bool {
|
||||
set := make(map[PeerName]struct{})
|
||||
|
||||
for key, aval := range a {
|
||||
bval, ok := b[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, peer := range aval {
|
||||
set[peer] = struct{}{}
|
||||
}
|
||||
|
||||
for _, peer := range bval {
|
||||
if _, ok := set[peer]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
delete(set, peer)
|
||||
}
|
||||
|
||||
if len(set) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for key := range b {
|
||||
if _, ok := a[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"time"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
@@ -13,27 +14,20 @@ type Status struct {
|
||||
Name string
|
||||
NickName string
|
||||
Port int
|
||||
Interface string
|
||||
CaptureStats map[string]int
|
||||
MACs []MACStatus
|
||||
Peers []PeerStatus
|
||||
UnicastRoutes []UnicastRouteStatus
|
||||
BroadcastRoutes []BroadcastRouteStatus
|
||||
Connections []LocalConnectionStatus
|
||||
Targets []string
|
||||
}
|
||||
|
||||
type MACStatus struct {
|
||||
Mac string
|
||||
Name string
|
||||
NickName string
|
||||
LastSeen time.Time
|
||||
OverlayDiagnostics interface{}
|
||||
TrustedSubnets []string
|
||||
}
|
||||
|
||||
type PeerStatus struct {
|
||||
Name string
|
||||
NickName string
|
||||
UID PeerUID
|
||||
ShortID PeerShortID
|
||||
Version uint64
|
||||
Connections []ConnectionStatus
|
||||
}
|
||||
@@ -63,14 +57,6 @@ type LocalConnectionStatus struct {
|
||||
}
|
||||
|
||||
func NewStatus(router *Router) *Status {
|
||||
var ifaceName string
|
||||
if router.Iface != nil {
|
||||
ifaceName = router.Iface.Name
|
||||
}
|
||||
var captureStats map[string]int
|
||||
if router.PacketSource != nil {
|
||||
captureStats = router.PacketSource.Stats()
|
||||
}
|
||||
return &Status{
|
||||
Protocol,
|
||||
ProtocolMinVersion,
|
||||
@@ -80,30 +66,13 @@ func NewStatus(router *Router) *Status {
|
||||
router.Ourself.Name.String(),
|
||||
router.Ourself.NickName,
|
||||
router.Port,
|
||||
ifaceName,
|
||||
captureStats,
|
||||
NewMACStatusSlice(router.Macs),
|
||||
NewPeerStatusSlice(router.Peers),
|
||||
NewUnicastRouteStatusSlice(router.Routes),
|
||||
NewBroadcastRouteStatusSlice(router.Routes),
|
||||
NewLocalConnectionStatusSlice(router.ConnectionMaker),
|
||||
NewTargetSlice(router.ConnectionMaker)}
|
||||
}
|
||||
|
||||
func NewMACStatusSlice(cache *MacCache) []MACStatus {
|
||||
cache.RLock()
|
||||
defer cache.RUnlock()
|
||||
|
||||
var slice []MACStatus
|
||||
for key, entry := range cache.table {
|
||||
slice = append(slice, MACStatus{
|
||||
intmac(key).String(),
|
||||
entry.peer.Name.String(),
|
||||
entry.peer.NickName,
|
||||
entry.lastSeen})
|
||||
}
|
||||
|
||||
return slice
|
||||
NewTargetSlice(router.ConnectionMaker),
|
||||
router.Overlay.Diagnostics(),
|
||||
NewTrustedSubnetsSlice(router.TrustedSubnets)}
|
||||
}
|
||||
|
||||
func NewPeerStatusSlice(peers *Peers) []PeerStatus {
|
||||
@@ -127,7 +96,8 @@ func NewPeerStatusSlice(peers *Peers) []PeerStatus {
|
||||
peer.Name.String(),
|
||||
peer.NickName,
|
||||
peer.UID,
|
||||
peer.version,
|
||||
peer.ShortID,
|
||||
peer.Version,
|
||||
connections})
|
||||
})
|
||||
|
||||
@@ -170,40 +140,48 @@ func NewBroadcastRouteStatusSlice(routes *Routes) []BroadcastRouteStatus {
|
||||
}
|
||||
|
||||
func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus {
|
||||
// We need to Refresh first in order to clear out any 'attempting'
|
||||
// connections from cm.targets that have been established since
|
||||
// the last run of cm.checkStateAndAttemptConnections. These
|
||||
// entries are harmless but do represent stale state that we do
|
||||
// not want to report.
|
||||
cm.Refresh()
|
||||
resultChan := make(chan []LocalConnectionStatus, 0)
|
||||
cm.actionChan <- func() bool {
|
||||
var slice []LocalConnectionStatus
|
||||
for conn := range cm.ourself.Connections() {
|
||||
for conn := range cm.connections {
|
||||
state := "pending"
|
||||
if conn.Established() {
|
||||
state = "established"
|
||||
}
|
||||
slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, conn.Remote().String()})
|
||||
lc, _ := conn.(*LocalConnection)
|
||||
info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote())
|
||||
if lc.Router.UsingPassword() {
|
||||
if lc.Untrusted() {
|
||||
info = fmt.Sprintf("%-11v %v", "encrypted", info)
|
||||
} else {
|
||||
info = fmt.Sprintf("%-11v %v", "unencrypted", info)
|
||||
}
|
||||
}
|
||||
slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, info})
|
||||
}
|
||||
for address, target := range cm.targets {
|
||||
var state, info string
|
||||
switch {
|
||||
case target.lastError == nil:
|
||||
state = "connecting"
|
||||
info = ""
|
||||
case target.attempting:
|
||||
state = "retrying"
|
||||
info = target.lastError.Error()
|
||||
default:
|
||||
state = "failed"
|
||||
retry := "never"
|
||||
add := func(state, info string) {
|
||||
slice = append(slice, LocalConnectionStatus{address, true, state, info})
|
||||
}
|
||||
switch target.state {
|
||||
case TargetWaiting:
|
||||
until := "never"
|
||||
if !target.tryAfter.IsZero() {
|
||||
retry = target.tryAfter.String()
|
||||
until = target.tryAfter.String()
|
||||
}
|
||||
info = target.lastError.Error() + ", retry: " + retry
|
||||
if target.lastError == nil { // shouldn't happen
|
||||
add("waiting", "until: "+until)
|
||||
} else {
|
||||
add("failed", target.lastError.Error()+", retry: "+until)
|
||||
}
|
||||
case TargetAttempting:
|
||||
if target.lastError == nil {
|
||||
add("connecting", "")
|
||||
} else {
|
||||
add("retrying", target.lastError.Error())
|
||||
}
|
||||
case TargetConnected:
|
||||
}
|
||||
slice = append(slice, LocalConnectionStatus{address, true, state, info})
|
||||
}
|
||||
resultChan <- slice
|
||||
return false
|
||||
@@ -223,3 +201,11 @@ func NewTargetSlice(cm *ConnectionMaker) []string {
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
func NewTrustedSubnetsSlice(trustedSubnets []*net.IPNet) []string {
|
||||
trustedSubnetStrs := []string{}
|
||||
for _, trustedSubnet := range trustedSubnets {
|
||||
trustedSubnetStrs = append(trustedSubnetStrs, trustedSubnet.String())
|
||||
}
|
||||
return trustedSubnetStrs
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
type SurrogateGossipData struct {
|
||||
messages [][]byte
|
||||
@@ -12,8 +12,12 @@ func (d *SurrogateGossipData) Encode() [][]byte {
|
||||
return d.messages
|
||||
}
|
||||
|
||||
func (d *SurrogateGossipData) Merge(other GossipData) {
|
||||
d.messages = append(d.messages, other.(*SurrogateGossipData).messages...)
|
||||
func (d *SurrogateGossipData) Merge(other GossipData) GossipData {
|
||||
o := other.(*SurrogateGossipData)
|
||||
messages := make([][]byte, 0, len(d.messages)+len(o.messages))
|
||||
messages = append(messages, d.messages...)
|
||||
messages = append(messages, o.messages...)
|
||||
return &SurrogateGossipData{messages: messages}
|
||||
}
|
||||
|
||||
// SurrogateGossiper ignores unicasts and relays broadcasts and gossips.
|
||||
@@ -1,4 +1,4 @@
|
||||
package router
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"time"
|
||||
+1
-1
@@ -216,7 +216,7 @@ func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
||||
reqCopy := req.Copy()
|
||||
reqCopy.Id = dns.Id()
|
||||
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, h.upstream.Port))
|
||||
if err != nil || response == nil {
|
||||
if (err != nil && err != dns.ErrTruncated) || response == nil {
|
||||
h.ns.debugf("error trying %s: %v", server, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
|
||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
||||
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, nil, nil, "")
|
||||
nameserver := New(peername, "", func(mesh.PeerName) bool { return true })
|
||||
dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", "", 30, 5*time.Second)
|
||||
require.Nil(t, err)
|
||||
udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port
|
||||
@@ -39,20 +39,20 @@ func TestTruncation(t *testing.T) {
|
||||
addrs := []address.Address{}
|
||||
for i := address.Address(0); i < 100; i++ {
|
||||
addrs = append(addrs, i)
|
||||
nameserver.AddEntry("foo.weave.local.", "", router.UnknownPeerName, i)
|
||||
nameserver.AddEntry("foo.weave.local.", "", mesh.UnknownPeerName, i)
|
||||
}
|
||||
|
||||
doRequest := func(client *dns.Client, request *dns.Msg, port int) *dns.Msg {
|
||||
doRequest := func(client *dns.Client, request *dns.Msg, port int, expectedErr error) *dns.Msg {
|
||||
request.SetQuestion("foo.weave.local.", dns.TypeA)
|
||||
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, expectedErr, err)
|
||||
return response
|
||||
}
|
||||
|
||||
// do a udp query, ensure we get a truncated response
|
||||
{
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: minUDPSize}
|
||||
response := doRequest(&udpClient, &dns.Msg{}, udpPort)
|
||||
response := doRequest(&udpClient, &dns.Msg{}, udpPort, dns.ErrTruncated)
|
||||
require.True(t, response.MsgHdr.Truncated)
|
||||
require.True(t, len(response.Answer) < 100)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func TestTruncation(t *testing.T) {
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: 65535}
|
||||
request := &dns.Msg{}
|
||||
request.SetEdns0(65535, false)
|
||||
response := doRequest(&udpClient, request, udpPort)
|
||||
response := doRequest(&udpClient, request, udpPort, nil)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func TestTruncation(t *testing.T) {
|
||||
// do a tcp query, ensure we don't get a truncated response
|
||||
{
|
||||
tcpClient := dns.Client{Net: "tcp"}
|
||||
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort)
|
||||
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort, nil)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
|
||||
+111
-38
@@ -5,26 +5,46 @@ import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
var now = func() int64 { return time.Now().Unix() }
|
||||
|
||||
type Entry struct {
|
||||
ContainerID string
|
||||
Origin router.PeerName
|
||||
Origin mesh.PeerName
|
||||
Addr address.Address
|
||||
Hostname string
|
||||
Hostname string // as supplied
|
||||
lHostname string // lowercased (not exported, so not encoded by gob)
|
||||
Version int
|
||||
Tombstone int64 // timestamp of when it was deleted
|
||||
}
|
||||
|
||||
type Entries []Entry
|
||||
type CaseSensitive Entries
|
||||
type CaseInsensitive Entries
|
||||
type SortableEntries interface {
|
||||
sort.Interface
|
||||
Get(i int) Entry
|
||||
}
|
||||
|
||||
func (e1 *Entry) equal(e2 *Entry) bool {
|
||||
// Gossip messages are sorted in a case sensitive order...
|
||||
func (es CaseSensitive) Len() int { return len(es) }
|
||||
func (es CaseSensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||
func (es CaseSensitive) Get(i int) Entry { return es[i] }
|
||||
func (es CaseSensitive) Less(i, j int) bool { return es[i].less(&es[j]) }
|
||||
|
||||
// ... but we store entries in a case insensitive order.
|
||||
func (es CaseInsensitive) Len() int { return len(es) }
|
||||
func (es CaseInsensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||
func (es CaseInsensitive) Get(i int) Entry { return es[i] }
|
||||
func (es CaseInsensitive) Less(i, j int) bool { return es[i].insensitiveLess(&es[j]) }
|
||||
|
||||
func (e1 Entry) equal(e2 Entry) bool {
|
||||
return e1.ContainerID == e2.ContainerID &&
|
||||
e1.Origin == e2.Origin &&
|
||||
e1.Addr == e2.Addr &&
|
||||
@@ -48,6 +68,24 @@ func (e1 *Entry) less(e2 *Entry) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (e1 *Entry) insensitiveLess(e2 *Entry) bool {
|
||||
// Entries are kept sorted by Hostname, Origin, ContainerID then address
|
||||
e1Hostname, e2Hostname := e1.lHostname, e2.lHostname
|
||||
switch {
|
||||
case e1Hostname != e2Hostname:
|
||||
return e1Hostname < e2Hostname
|
||||
|
||||
case e1.Origin != e2.Origin:
|
||||
return e1.Origin < e2.Origin
|
||||
|
||||
case e1.ContainerID != e2.ContainerID:
|
||||
return e1.ContainerID < e2.ContainerID
|
||||
|
||||
default:
|
||||
return e1.Addr < e2.Addr
|
||||
}
|
||||
}
|
||||
|
||||
// returns true to indicate a change
|
||||
func (e1 *Entry) merge(e2 *Entry) bool {
|
||||
// we know container id, origin, add and hostname are equal
|
||||
@@ -63,37 +101,42 @@ func (e1 *Entry) String() string {
|
||||
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
|
||||
}
|
||||
|
||||
func (es Entries) Len() int { return len(es) }
|
||||
func (es Entries) Swap(i, j int) { panic("Swap") }
|
||||
func (es Entries) Less(i, j int) bool { return es[i].less(&es[j]) }
|
||||
func (e1 *Entry) addLowercase() {
|
||||
e1.lHostname = strings.ToLower(e1.Hostname)
|
||||
}
|
||||
|
||||
func (es Entries) check() error {
|
||||
func check(es SortableEntries) error {
|
||||
if !sort.IsSorted(es) {
|
||||
return fmt.Errorf("Not sorted!")
|
||||
}
|
||||
for i := 1; i < len(es); i++ {
|
||||
if es[i].equal(&es[i-1]) {
|
||||
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es[i-1], i, es[i])
|
||||
for i := 1; i < es.Len(); i++ {
|
||||
if es.Get(i).equal(es.Get(i - 1)) {
|
||||
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es.Get(i-1), i, es.Get(i))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Entries) checkAndPanic() {
|
||||
if err := es.check(); err != nil {
|
||||
func checkAndPanic(es SortableEntries) {
|
||||
if err := check(es); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (es *Entries) add(hostname, containerid string, origin router.PeerName, addr address.Address) Entry {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
func (es *Entries) checkAndPanic() *Entries {
|
||||
checkAndPanic(CaseInsensitive(*es))
|
||||
return es
|
||||
}
|
||||
|
||||
entry := Entry{Hostname: hostname, Origin: origin, ContainerID: containerid, Addr: addr}
|
||||
func (es *Entries) add(hostname, containerid string, origin mesh.PeerName, addr address.Address) Entry {
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
|
||||
entry := Entry{Hostname: hostname, lHostname: strings.ToLower(hostname),
|
||||
Origin: origin, ContainerID: containerid, Addr: addr}
|
||||
i := sort.Search(len(*es), func(i int) bool {
|
||||
return !(*es)[i].less(&entry)
|
||||
return !(*es)[i].insensitiveLess(&entry)
|
||||
})
|
||||
if i < len(*es) && (*es)[i].equal(&entry) {
|
||||
if i < len(*es) && (*es)[i].equal(entry) {
|
||||
if (*es)[i].Tombstone > 0 {
|
||||
(*es)[i].Tombstone = 0
|
||||
(*es)[i].Version++
|
||||
@@ -107,16 +150,17 @@ func (es *Entries) add(hostname, containerid string, origin router.PeerName, add
|
||||
}
|
||||
|
||||
func (es *Entries) merge(incoming Entries) Entries {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
incoming.checkAndPanic()
|
||||
|
||||
newEntries := Entries{}
|
||||
i := 0
|
||||
|
||||
for _, entry := range incoming {
|
||||
for i < len(*es) && (*es)[i].less(&entry) {
|
||||
for i < len(*es) && (*es)[i].insensitiveLess(&entry) {
|
||||
i++
|
||||
}
|
||||
if i < len(*es) && (*es)[i].equal(&entry) {
|
||||
if i < len(*es) && (*es)[i].equal(entry) {
|
||||
if (*es)[i].merge(&entry) {
|
||||
newEntries = append(newEntries, entry)
|
||||
}
|
||||
@@ -132,9 +176,9 @@ func (es *Entries) merge(incoming Entries) Entries {
|
||||
}
|
||||
|
||||
// f returning true means keep the entry.
|
||||
func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entries {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
func (es *Entries) tombstone(ourname mesh.PeerName, f func(*Entry) bool) Entries {
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
|
||||
tombstoned := Entries{}
|
||||
for i, e := range *es {
|
||||
if f(&e) && e.Origin == ourname {
|
||||
@@ -148,8 +192,8 @@ func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entri
|
||||
}
|
||||
|
||||
func (es *Entries) filter(f func(*Entry) bool) {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
|
||||
i := 0
|
||||
for _, e := range *es {
|
||||
if !f(&e) {
|
||||
@@ -163,23 +207,26 @@ func (es *Entries) filter(f func(*Entry) bool) {
|
||||
|
||||
func (es Entries) lookup(hostname string) Entries {
|
||||
es.checkAndPanic()
|
||||
|
||||
lowerHostname := strings.ToLower(hostname)
|
||||
i := sort.Search(len(es), func(i int) bool {
|
||||
return es[i].Hostname >= hostname
|
||||
return es[i].lHostname >= lowerHostname
|
||||
})
|
||||
if i >= len(es) || es[i].Hostname != hostname {
|
||||
if i >= len(es) || es[i].lHostname != lowerHostname {
|
||||
return Entries{}
|
||||
}
|
||||
|
||||
j := sort.Search(len(es)-i, func(j int) bool {
|
||||
return es[i+j].Hostname > hostname
|
||||
return es[i+j].lHostname > lowerHostname
|
||||
})
|
||||
|
||||
return es[i : i+j]
|
||||
}
|
||||
|
||||
func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
|
||||
func (es Entries) first(f func(*Entry) bool) (*Entry, error) {
|
||||
es.checkAndPanic()
|
||||
for _, e := range *es {
|
||||
|
||||
for _, e := range es {
|
||||
if f(&e) {
|
||||
return &e, nil
|
||||
}
|
||||
@@ -187,23 +234,49 @@ func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
|
||||
return nil, fmt.Errorf("Not found")
|
||||
}
|
||||
|
||||
func (es Entries) addLowercase() {
|
||||
for i := range es {
|
||||
es[i].addLowercase()
|
||||
}
|
||||
}
|
||||
|
||||
type GossipData struct {
|
||||
Timestamp int64
|
||||
Entries
|
||||
}
|
||||
|
||||
func (g *GossipData) Merge(o router.GossipData) {
|
||||
func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData {
|
||||
other := o.(*GossipData)
|
||||
g.Entries.merge(other.Entries)
|
||||
if g.Timestamp < other.Timestamp {
|
||||
g.Timestamp = other.Timestamp
|
||||
gossip := g.copy()
|
||||
gossip.Entries.merge(other.Entries)
|
||||
if gossip.Timestamp < other.Timestamp {
|
||||
gossip.Timestamp = other.Timestamp
|
||||
}
|
||||
return gossip
|
||||
}
|
||||
|
||||
func (g *GossipData) Decode(msg []byte) error {
|
||||
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Entries.addLowercase() // lowercase strings not sent on the wire
|
||||
sort.Sort(CaseInsensitive(g.Entries))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GossipData) Encode() [][]byte {
|
||||
g2 := g.copy()
|
||||
sort.Sort(CaseSensitive(g2.Entries))
|
||||
buf := &bytes.Buffer{}
|
||||
if err := gob.NewEncoder(buf).Encode(g); err != nil {
|
||||
if err := gob.NewEncoder(buf).Encode(g2); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return [][]byte{buf.Bytes()}
|
||||
}
|
||||
|
||||
func (g *GossipData) copy() *GossipData {
|
||||
g2 := &GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))}
|
||||
copy(g2.Entries, g.Entries)
|
||||
return g2
|
||||
}
|
||||
+55
-64
@@ -5,79 +5,70 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
func l(es Entries) Entries {
|
||||
es.addLowercase()
|
||||
return es
|
||||
}
|
||||
|
||||
func makeEntries(values string) Entries {
|
||||
entries := make(Entries, len(values))
|
||||
for i, c := range values {
|
||||
entries[i] = Entry{Hostname: string(c)}
|
||||
}
|
||||
return l(entries)
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
oldNow := now
|
||||
defer func() { now = oldNow }()
|
||||
now = func() int64 { return 1234 }
|
||||
|
||||
entries := Entries{}
|
||||
entries.add("A", "", router.UnknownPeerName, address.Address(0))
|
||||
expected := Entries{
|
||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0)},
|
||||
}
|
||||
entries.add("A", "", mesh.UnknownPeerName, address.Address(0))
|
||||
expected := l(Entries{
|
||||
Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0)},
|
||||
})
|
||||
require.Equal(t, entries, expected)
|
||||
|
||||
entries.tombstone(router.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" })
|
||||
expected = Entries{
|
||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234},
|
||||
}
|
||||
entries.tombstone(mesh.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" })
|
||||
expected = l(Entries{
|
||||
Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234},
|
||||
})
|
||||
require.Equal(t, entries, expected)
|
||||
|
||||
entries.add("A", "", router.UnknownPeerName, address.Address(0))
|
||||
expected = Entries{
|
||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 2},
|
||||
}
|
||||
entries.add("A", "", mesh.UnknownPeerName, address.Address(0))
|
||||
expected = l(Entries{
|
||||
Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0), Version: 2},
|
||||
})
|
||||
require.Equal(t, entries, expected)
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
e1 := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "C"},
|
||||
Entry{Hostname: "D"},
|
||||
Entry{Hostname: "F"},
|
||||
}
|
||||
|
||||
e2 := Entries{
|
||||
Entry{Hostname: "B"},
|
||||
Entry{Hostname: "E"},
|
||||
Entry{Hostname: "F"},
|
||||
}
|
||||
e1 := makeEntries("ACDF")
|
||||
e2 := makeEntries("BEF")
|
||||
|
||||
diff := e1.merge(e2)
|
||||
expectedDiff := Entries{
|
||||
Entry{Hostname: "B"},
|
||||
Entry{Hostname: "E"},
|
||||
}
|
||||
require.Equal(t, expectedDiff, diff)
|
||||
|
||||
expected := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B"},
|
||||
Entry{Hostname: "C"},
|
||||
Entry{Hostname: "D"},
|
||||
Entry{Hostname: "E"},
|
||||
Entry{Hostname: "F"},
|
||||
}
|
||||
require.Equal(t, expected, e1)
|
||||
require.Equal(t, makeEntries("BE"), diff)
|
||||
require.Equal(t, makeEntries("ABCDEF"), e1)
|
||||
|
||||
diff = e1.merge(e1)
|
||||
require.Equal(t, Entries{}, diff)
|
||||
}
|
||||
|
||||
func TestOldMerge(t *testing.T) {
|
||||
e1 := Entries{Entry{Hostname: "A", Version: 0}}
|
||||
diff := e1.merge(Entries{Entry{Hostname: "A", Version: 1}})
|
||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, diff)
|
||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
|
||||
e1 := l(Entries{Entry{Hostname: "A", Version: 0}})
|
||||
diff := e1.merge(l(Entries{Entry{Hostname: "A", Version: 1}}))
|
||||
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), diff)
|
||||
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), e1)
|
||||
|
||||
diff = e1.merge(Entries{Entry{Hostname: "A", Version: 0}})
|
||||
diff = e1.merge(l(Entries{Entry{Hostname: "A", Version: 0}}))
|
||||
require.Equal(t, Entries{}, diff)
|
||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
|
||||
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), e1)
|
||||
}
|
||||
|
||||
func TestTombstone(t *testing.T) {
|
||||
@@ -85,48 +76,48 @@ func TestTombstone(t *testing.T) {
|
||||
defer func() { now = oldNow }()
|
||||
now = func() int64 { return 1234 }
|
||||
|
||||
es := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B"},
|
||||
}
|
||||
es := makeEntries("AB")
|
||||
|
||||
es.tombstone(router.UnknownPeerName, func(e *Entry) bool {
|
||||
es.tombstone(mesh.UnknownPeerName, func(e *Entry) bool {
|
||||
return e.Hostname == "B"
|
||||
})
|
||||
expected := Entries{
|
||||
expected := l(Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B", Version: 1, Tombstone: 1234},
|
||||
}
|
||||
})
|
||||
require.Equal(t, expected, es)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
es := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B"},
|
||||
}
|
||||
es := makeEntries("AB")
|
||||
|
||||
es.filter(func(e *Entry) bool {
|
||||
return e.Hostname != "A"
|
||||
})
|
||||
expected := Entries{
|
||||
Entry{Hostname: "B"},
|
||||
}
|
||||
require.Equal(t, expected, es)
|
||||
require.Equal(t, makeEntries("B"), es)
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
es := Entries{
|
||||
es := l(Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B", ContainerID: "bar"},
|
||||
Entry{Hostname: "B", ContainerID: "foo"},
|
||||
Entry{Hostname: "C"},
|
||||
}
|
||||
})
|
||||
|
||||
have := es.lookup("B")
|
||||
want := Entries{
|
||||
want := l(Entries{
|
||||
Entry{Hostname: "B", ContainerID: "bar"},
|
||||
Entry{Hostname: "B", ContainerID: "foo"},
|
||||
}
|
||||
})
|
||||
require.Equal(t, have, want)
|
||||
}
|
||||
|
||||
func TestGossipDataMerge(t *testing.T) {
|
||||
g1 := GossipData{Entries: makeEntries("AcDf")}
|
||||
g2 := GossipData{Entries: makeEntries("BEf")}
|
||||
|
||||
g3 := g1.Merge(&g2).(*GossipData)
|
||||
|
||||
require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, *g3)
|
||||
}
|
||||
+8
-2
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -16,7 +17,7 @@ func (n *Nameserver) badRequest(w http.ResponseWriter, err error) {
|
||||
n.infof("%v", err)
|
||||
}
|
||||
|
||||
func (n *Nameserver) HandleHTTP(router *mux.Router) {
|
||||
func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||
router.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, n.domain)
|
||||
})
|
||||
@@ -34,12 +35,17 @@ func (n *Nameserver) HandleHTTP(router *mux.Router) {
|
||||
return
|
||||
}
|
||||
|
||||
if !dns.IsSubDomain(n.domain, hostname) {
|
||||
n.infof("Ignoring registration %s %s %s (not a subdomain of %s)", hostname, ipStr, container, n.domain)
|
||||
return
|
||||
}
|
||||
|
||||
if err := n.AddEntry(hostname, container, n.ourName, ip); err != nil {
|
||||
n.badRequest(w, fmt.Errorf("Unable to add entry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if r.FormValue("check-alive") == "true" && n.docker.IsContainerNotRunning(container) {
|
||||
if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(container) {
|
||||
n.infof("container '%s' is not running: removing", container)
|
||||
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
|
||||
n.infof("failed to remove: %v", err)
|
||||
|
||||
+36
-52
@@ -1,8 +1,6 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -10,9 +8,8 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -37,30 +34,24 @@ const (
|
||||
// - Update is O(n) for now
|
||||
type Nameserver struct {
|
||||
sync.RWMutex
|
||||
ourName router.PeerName
|
||||
domain string
|
||||
gossip router.Gossip
|
||||
docker *docker.Client
|
||||
entries Entries
|
||||
peers *router.Peers
|
||||
quit chan struct{}
|
||||
ourName mesh.PeerName
|
||||
domain string
|
||||
gossip mesh.Gossip
|
||||
entries Entries
|
||||
isKnownPeer func(mesh.PeerName) bool
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func New(ourName router.PeerName, peers *router.Peers, docker *docker.Client, domain string) *Nameserver {
|
||||
ns := &Nameserver{
|
||||
ourName: ourName,
|
||||
domain: dns.Fqdn(domain),
|
||||
peers: peers,
|
||||
docker: docker,
|
||||
quit: make(chan struct{}),
|
||||
func New(ourName mesh.PeerName, domain string, isKnownPeer func(mesh.PeerName) bool) *Nameserver {
|
||||
return &Nameserver{
|
||||
ourName: ourName,
|
||||
domain: dns.Fqdn(domain),
|
||||
isKnownPeer: isKnownPeer,
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
if peers != nil {
|
||||
peers.OnGC(ns.PeerGone)
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func (n *Nameserver) SetGossip(gossip router.Gossip) {
|
||||
func (n *Nameserver) SetGossip(gossip mesh.Gossip) {
|
||||
n.gossip = gossip
|
||||
}
|
||||
|
||||
@@ -83,16 +74,16 @@ func (n *Nameserver) Stop() {
|
||||
}
|
||||
|
||||
func (n *Nameserver) broadcastEntries(es ...Entry) error {
|
||||
if n.gossip != nil {
|
||||
return n.gossip.GossipBroadcast(&GossipData{
|
||||
Entries: Entries(es),
|
||||
Timestamp: now(),
|
||||
})
|
||||
if n.gossip == nil || len(es) == 0 {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return n.gossip.GossipBroadcast(&GossipData{
|
||||
Entries: Entries(es),
|
||||
Timestamp: now(),
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Nameserver) AddEntry(hostname, containerid string, origin router.PeerName, addr address.Address) error {
|
||||
func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName, addr address.Address) error {
|
||||
n.infof("adding entry %s -> %s", hostname, addr.String())
|
||||
n.Lock()
|
||||
entry := n.entries.add(hostname, containerid, origin, addr)
|
||||
@@ -130,6 +121,8 @@ func (n *Nameserver) ReverseLookup(ip address.Address) (string, error) {
|
||||
return match.Hostname, nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) ContainerStarted(ident string) {}
|
||||
|
||||
func (n *Nameserver) ContainerDied(ident string) {
|
||||
n.Lock()
|
||||
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
||||
@@ -140,19 +133,17 @@ func (n *Nameserver) ContainerDied(ident string) {
|
||||
return false
|
||||
})
|
||||
n.Unlock()
|
||||
if len(entries) > 0 {
|
||||
if err := n.broadcastEntries(entries...); err != nil {
|
||||
n.errorf("failed to broadcast container %s death: %v", ident, err)
|
||||
}
|
||||
if err := n.broadcastEntries(entries...); err != nil {
|
||||
n.errorf("failed to broadcast container %s death: %v", ident, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nameserver) PeerGone(peer *router.Peer) {
|
||||
func (n *Nameserver) PeerGone(peer mesh.PeerName) {
|
||||
n.infof("peer %s gone", peer.String())
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
n.entries.filter(func(e *Entry) bool {
|
||||
return e.Origin != peer.Name
|
||||
return e.Origin != peer
|
||||
})
|
||||
}
|
||||
|
||||
@@ -188,7 +179,7 @@ func (n *Nameserver) deleteTombstones() {
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Nameserver) Gossip() router.GossipData {
|
||||
func (n *Nameserver) Gossip() mesh.GossipData {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
gossip := &GossipData{
|
||||
@@ -199,32 +190,25 @@ func (n *Nameserver) Gossip() router.GossipData {
|
||||
return gossip
|
||||
}
|
||||
|
||||
func (n *Nameserver) OnGossipUnicast(sender router.PeerName, msg []byte) error {
|
||||
func (n *Nameserver) OnGossipUnicast(sender mesh.PeerName, msg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.GossipData, error) {
|
||||
func (n *Nameserver) receiveGossip(msg []byte) (mesh.GossipData, mesh.GossipData, error) {
|
||||
var gossip GossipData
|
||||
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&gossip); err != nil {
|
||||
if err := gossip.Decode(msg); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if delta := gossip.Timestamp - now(); delta > gossipWindow || delta < -gossipWindow {
|
||||
return nil, nil, fmt.Errorf("clock skew of %d detected", delta)
|
||||
}
|
||||
|
||||
if err := gossip.Entries.check(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
if n.peers != nil {
|
||||
gossip.Entries.filter(func(e *Entry) bool {
|
||||
return n.peers.Fetch(e.Origin) != nil
|
||||
})
|
||||
}
|
||||
gossip.Entries.filter(func(e *Entry) bool {
|
||||
return n.isKnownPeer(e.Origin)
|
||||
})
|
||||
|
||||
newEntries := n.entries.merge(gossip.Entries)
|
||||
if len(newEntries) > 0 {
|
||||
@@ -235,14 +219,14 @@ func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.Gossip
|
||||
|
||||
// merge received data into state and return "everything new I've
|
||||
// just learnt", or nil if nothing in the received data was new
|
||||
func (n *Nameserver) OnGossip(msg []byte) (router.GossipData, error) {
|
||||
func (n *Nameserver) OnGossip(msg []byte) (mesh.GossipData, error) {
|
||||
newEntries, _, err := n.receiveGossip(msg)
|
||||
return newEntries, err
|
||||
}
|
||||
|
||||
// merge received data into state and return a representation of
|
||||
// the received data, for further propagation
|
||||
func (n *Nameserver) OnGossipBroadcast(_ router.PeerName, msg []byte) (router.GossipData, error) {
|
||||
func (n *Nameserver) OnGossipBroadcast(_ mesh.PeerName, msg []byte) (mesh.GossipData, error) {
|
||||
_, entries, err := n.receiveGossip(msg)
|
||||
return entries, err
|
||||
}
|
||||
|
||||
@@ -11,19 +11,22 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
|
||||
func makeNameserver(name mesh.PeerName) *Nameserver {
|
||||
return New(name, "", func(mesh.PeerName) bool { return true })
|
||||
}
|
||||
|
||||
func makeNetwork(size int) ([]*Nameserver, *gossip.TestRouter) {
|
||||
gossipRouter := gossip.NewTestRouter(0.0)
|
||||
nameservers := make([]*Nameserver, size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
name, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i))
|
||||
nameserver := New(name, nil, nil, "")
|
||||
name, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i))
|
||||
nameserver := makeNameserver(name)
|
||||
nameserver.SetGossip(gossipRouter.Connect(nameserver.ourName, nameserver))
|
||||
nameserver.Start()
|
||||
nameservers[i] = nameserver
|
||||
@@ -40,7 +43,7 @@ func stopNetwork(nameservers []*Nameserver, grouter *gossip.TestRouter) {
|
||||
}
|
||||
|
||||
type pair struct {
|
||||
origin router.PeerName
|
||||
origin mesh.PeerName
|
||||
addr address.Address
|
||||
}
|
||||
|
||||
@@ -72,18 +75,16 @@ func (m mapping) Addrs() []address.Address {
|
||||
}
|
||||
|
||||
func TestNameservers(t *testing.T) {
|
||||
wt.RunWithTimeout(t, time.Minute, func() {
|
||||
testNameservers(t)
|
||||
})
|
||||
}
|
||||
|
||||
func testNameservers(t *testing.T) {
|
||||
//common.SetLogLevel("debug")
|
||||
|
||||
lookupTimeout := 10 // ms
|
||||
nameservers, grouter := makeNetwork(50)
|
||||
nameservers, grouter := makeNetwork(30)
|
||||
defer stopNetwork(nameservers, grouter)
|
||||
nameserversByName := map[router.PeerName]*Nameserver{}
|
||||
// This subset will sometimes lose touch with some of the others
|
||||
badNameservers := nameservers[25:]
|
||||
// This subset will remain well-connected, and we will deal mainly with them
|
||||
nameservers = nameservers[:25]
|
||||
nameserversByName := map[mesh.PeerName]*Nameserver{}
|
||||
for _, n := range nameservers {
|
||||
nameserversByName[n.ourName] = n
|
||||
}
|
||||
@@ -106,7 +107,13 @@ func testNameservers(t *testing.T) {
|
||||
addMapping := func() {
|
||||
nameserver := nameservers[rand.Intn(len(nameservers))]
|
||||
addr := address.Address(rand.Int31())
|
||||
hostname := fmt.Sprintf("hostname%d", rand.Int63())
|
||||
// Create a hostname which has some upper and lowercase letters,
|
||||
// and a unique number so we don't have to check if we allocated it already
|
||||
randomBits := rand.Int63()
|
||||
firstLetter := 'H' + (randomBits&1)*32
|
||||
secondLetter := 'O' + (randomBits&2)*16
|
||||
randomBits = randomBits >> 2
|
||||
hostname := fmt.Sprintf("%c%cstname%d", firstLetter, secondLetter, randomBits)
|
||||
mapping := mapping{hostname, []pair{{nameserver.ourName, addr}}}
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
@@ -129,6 +136,12 @@ func testNameservers(t *testing.T) {
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
loseConnection := func() {
|
||||
nameserver1 := badNameservers[rand.Intn(len(badNameservers))]
|
||||
nameserver2 := nameservers[rand.Intn(len(nameservers))]
|
||||
nameserver1.PeerGone(nameserver2.ourName)
|
||||
}
|
||||
|
||||
deleteMapping := func() {
|
||||
if len(mappings) <= 0 {
|
||||
return
|
||||
@@ -179,7 +192,7 @@ func testNameservers(t *testing.T) {
|
||||
require.Equal(t, mapping.hostname, hostname)
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := 0; i < 800; i++ {
|
||||
r := rand.Float32()
|
||||
switch {
|
||||
case r < 0.1:
|
||||
@@ -191,7 +204,10 @@ func testNameservers(t *testing.T) {
|
||||
case 0.2 <= r && r < 0.3:
|
||||
deleteMapping()
|
||||
|
||||
case 0.3 <= r && r < 0.9:
|
||||
case 0.3 <= r && r < 0.35:
|
||||
loseConnection()
|
||||
|
||||
case 0.35 <= r && r < 0.9:
|
||||
doLookup()
|
||||
|
||||
case 0.9 <= r:
|
||||
@@ -203,9 +219,9 @@ func testNameservers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestContainerAndPeerDeath(t *testing.T) {
|
||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
||||
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, nil, nil, "")
|
||||
nameserver := makeNameserver(peername)
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
@@ -218,7 +234,7 @@ func TestContainerAndPeerDeath(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
nameserver.PeerGone(&router.Peer{Name: peername})
|
||||
nameserver.PeerGone(peername)
|
||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||
}
|
||||
|
||||
@@ -227,9 +243,9 @@ func TestTombstoneDeletion(t *testing.T) {
|
||||
defer func() { now = oldNow }()
|
||||
now = func() int64 { return 1234 }
|
||||
|
||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
||||
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, nil, nil, "")
|
||||
nameserver := makeNameserver(peername)
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
@@ -241,14 +257,14 @@ func TestTombstoneDeletion(t *testing.T) {
|
||||
err = nameserver.Delete("hostname", "containerid", "", address.Address(0))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||
require.Equal(t, Entries{{
|
||||
require.Equal(t, l(Entries{Entry{
|
||||
ContainerID: "containerid",
|
||||
Origin: peername,
|
||||
Addr: address.Address(0),
|
||||
Hostname: "hostname",
|
||||
Version: 1,
|
||||
Tombstone: 1234,
|
||||
}}, nameserver.entries)
|
||||
}}), nameserver.entries)
|
||||
|
||||
now = func() int64 { return 1234 + int64(tombstoneTimeout/time.Second) + 1 }
|
||||
nameserver.deleteTombstones()
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package nameserver
|
||||
|
||||
type Status struct {
|
||||
Domain string
|
||||
Address string
|
||||
TTL uint32
|
||||
Entries []EntryStatus
|
||||
Domain string
|
||||
Upstream []string
|
||||
Address string
|
||||
TTL uint32
|
||||
Entries []EntryStatus
|
||||
}
|
||||
|
||||
type EntryStatus struct {
|
||||
@@ -37,6 +38,7 @@ func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
|
||||
|
||||
return &Status{
|
||||
dnsServer.domain,
|
||||
dnsServer.upstream.Servers,
|
||||
dnsServer.address,
|
||||
dnsServer.ttl,
|
||||
entryStatusSlice}
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewRange(start Address, size Offset) Range {
|
||||
return Range{Start: start, End: Add(start, size)}
|
||||
}
|
||||
func (r Range) Size() Offset { return Subtract(r.End, r.Start) }
|
||||
func (r Range) String() string { return fmt.Sprintf("[%s-%s)", r.Start, r.End) }
|
||||
func (r Range) String() string { return fmt.Sprintf("%s-%s", r.Start, r.End-1) }
|
||||
func (r Range) Overlaps(or Range) bool { return !(r.Start >= or.End || r.End <= or.Start) }
|
||||
func (r Range) Contains(addr Address) bool { return addr >= r.Start && addr < r.End }
|
||||
|
||||
|
||||
@@ -5,28 +5,34 @@ import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// Wait for an interface to come up.
|
||||
func EnsureInterface(ifaceName string) (*net.Interface, error) {
|
||||
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
iface, err := ensureInterface(s, ifaceName)
|
||||
iface, err := ensureInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iface, err
|
||||
}
|
||||
|
||||
func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) {
|
||||
func ensureInterface(ifaceName string) (*net.Interface, error) {
|
||||
ch := make(chan netlink.LinkUpdate)
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
if err := netlink.LinkSubscribe(ch, done); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check for currently-existing interface after subscribing, to avoid race
|
||||
if iface, err := findInterface(ifaceName); err == nil {
|
||||
return iface, nil
|
||||
}
|
||||
netlinkReceiveUntil(s, matchIfName(ifaceName))
|
||||
for update := range ch {
|
||||
if ifaceName == update.Link.Attrs().Name && update.IfInfomsg.Flags&syscall.IFF_UP != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iface, err := findInterface(ifaceName)
|
||||
return iface, err
|
||||
}
|
||||
@@ -35,21 +41,28 @@ func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, err
|
||||
// This matches the behaviour in 'weave attach', which is the only context in which
|
||||
// we expect this to be called. If you change one, change the other to match.
|
||||
func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
|
||||
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE)
|
||||
iface, err := ensureInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
iface, err := ensureInterface(s, ifaceName)
|
||||
if err != nil {
|
||||
ch := make(chan netlink.RouteUpdate)
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
if err := netlink.RouteSubscribe(ch, done); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dest := net.IPv4(224, 0, 0, 0)
|
||||
// check for currently-existing route after subscribing, to avoid race
|
||||
if CheckRouteExists(ifaceName, dest) {
|
||||
return iface, err
|
||||
}
|
||||
netlinkReceiveUntil(s, matchRoute(ifaceName, dest))
|
||||
return iface, err
|
||||
for update := range ch {
|
||||
link, _ := netlink.LinkByIndex(update.Route.LinkIndex)
|
||||
if link.Attrs().Name == ifaceName && update.Route.Dst.IP.Equal(dest) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
||||
@@ -61,40 +74,3 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func netlinkReceiveUntil(s *nl.NetlinkSocket, f func(syscall.NetlinkMessage) (bool, error)) error {
|
||||
for {
|
||||
msgs, err := s.Receive()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range msgs {
|
||||
if done, err := f(m); done {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchIfName(ifaceName string) func(m syscall.NetlinkMessage) (bool, error) {
|
||||
return func(m syscall.NetlinkMessage) (bool, error) {
|
||||
switch m.Header.Type {
|
||||
case syscall.RTM_NEWLINK: // receive this type for link 'up'
|
||||
ifmsg := nl.DeserializeIfInfomsg(m.Data)
|
||||
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
name := ""
|
||||
for _, attr := range attrs {
|
||||
if attr.Attr.Type == syscall.IFA_LABEL {
|
||||
name = string(attr.Value[:len(attr.Value)-1])
|
||||
}
|
||||
}
|
||||
if ifaceName == name && ifmsg.Flags&syscall.IFF_UP != 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@ package net
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
)
|
||||
|
||||
// A network is considered free if it does not overlap any existing
|
||||
@@ -65,51 +63,3 @@ func CheckRouteExists(ifaceName string, dest net.IP) bool {
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func matchRoute(ifaceName string, dest net.IP) func(m syscall.NetlinkMessage) (bool, error) {
|
||||
return func(m syscall.NetlinkMessage) (bool, error) {
|
||||
switch m.Header.Type {
|
||||
case syscall.RTM_NEWROUTE:
|
||||
route, err := deserializeRoute(m.Data)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
link, _ := netlink.LinkByIndex(route.LinkIndex)
|
||||
if link.Attrs().Name == ifaceName && route.Dst.IP.Equal(dest) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// This code proposed for addition to the vishvananda/netlink library
|
||||
// deserializeRoute decodes a binary netlink message into a Route struct
|
||||
func deserializeRoute(m []byte) (netlink.Route, error) {
|
||||
route := netlink.Route{}
|
||||
msg := nl.DeserializeRtMsg(m)
|
||||
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
|
||||
if err != nil {
|
||||
return route, err
|
||||
}
|
||||
route.Scope = netlink.Scope(msg.Scope)
|
||||
|
||||
native := nl.NativeEndian()
|
||||
for _, attr := range attrs {
|
||||
switch attr.Attr.Type {
|
||||
case syscall.RTA_GATEWAY:
|
||||
route.Gw = net.IP(attr.Value)
|
||||
case syscall.RTA_PREFSRC:
|
||||
route.Src = net.IP(attr.Value)
|
||||
case syscall.RTA_DST:
|
||||
route.Dst = &net.IPNet{
|
||||
IP: attr.Value,
|
||||
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
|
||||
}
|
||||
case syscall.RTA_OIF:
|
||||
routeIndex := int(native.Uint32(attr.Value[0:4]))
|
||||
route.LinkIndex = routeIndex
|
||||
}
|
||||
}
|
||||
return route, nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Weave network driver extension for Docker
|
||||
|
||||
A [plugin](http://docs.docker.com/engine/extend/plugin_api/) to
|
||||
integrate [weave Net](http://weave.works/net/) with Docker.
|
||||
|
||||
The plugin runs automatically when you `weave launch`, provided your
|
||||
Docker daemon is version 1.9 or newer.
|
||||
|
||||
More detail [here](http://docs.weave.works/weave/latest_release/plugin.html).
|
||||
@@ -0,0 +1,58 @@
|
||||
package ipamplugin
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/libnetwork/netlabel"
|
||||
"github.com/weaveworks/weave/api"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
)
|
||||
|
||||
const (
|
||||
WeaveContainer = "weave"
|
||||
)
|
||||
|
||||
type ipam struct {
|
||||
client *docker.Client
|
||||
weave *api.Client
|
||||
}
|
||||
|
||||
func NewIpam(client *docker.Client, version string) (ipamapi.Ipam, error) {
|
||||
resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) }
|
||||
return &ipam{client: client, weave: api.NewClientWithResolver(resolver)}, nil
|
||||
}
|
||||
|
||||
func (i *ipam) GetDefaultAddressSpaces() (string, string, error) {
|
||||
Log.Debugln("GetDefaultAddressSpaces")
|
||||
return "weavelocal", "weaveglobal", nil
|
||||
}
|
||||
|
||||
func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
|
||||
Log.Debugln("RequestPool", addressSpace, pool, subPool, options)
|
||||
cidr, err := i.weave.DefaultSubnet()
|
||||
Log.Debugln("RequestPool returning ", cidr, err)
|
||||
// Pass back a fake "gateway address"; we don't actually use it,
|
||||
// so just give the network address.
|
||||
data := map[string]string{netlabel.Gateway: cidr.String()}
|
||||
return "weavepool", cidr, data, err
|
||||
}
|
||||
|
||||
func (i *ipam) ReleasePool(poolID string) error {
|
||||
Log.Debugln("ReleasePool", poolID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
|
||||
Log.Debugln("RequestAddress", poolID, address, options)
|
||||
// Pass magic string to weave IPAM, which then stores the address under its own string
|
||||
ip, err := i.weave.AllocateIP("_")
|
||||
Log.Debugln("allocateIP returned", ip, err)
|
||||
return ip, nil, err
|
||||
}
|
||||
|
||||
func (i *ipam) ReleaseAddress(poolID string, address net.IP) error {
|
||||
Log.Debugln("ReleaseAddress", poolID, address)
|
||||
return i.weave.ReleaseIP(address.String())
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/libnetwork/drivers/remote/api"
|
||||
"github.com/docker/libnetwork/types"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
"github.com/weaveworks/weave/plugin/skel"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
WeaveContainer = "weave"
|
||||
WeaveBridge = "weave"
|
||||
)
|
||||
|
||||
type driver struct {
|
||||
version string
|
||||
nameserver string
|
||||
scope string
|
||||
noMulticastRoute bool
|
||||
sync.RWMutex
|
||||
endpoints map[string]struct{}
|
||||
}
|
||||
|
||||
func New(client *docker.Client, version string, nameserver string, scope string, noMulticastRoute bool) (skel.Driver, error) {
|
||||
driver := &driver{
|
||||
nameserver: nameserver,
|
||||
noMulticastRoute: noMulticastRoute,
|
||||
version: version,
|
||||
scope: scope,
|
||||
endpoints: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
_, err := NewWatcher(client, driver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func errorf(format string, a ...interface{}) error {
|
||||
Log.Errorf(format, a...)
|
||||
return fmt.Errorf(format, a...)
|
||||
}
|
||||
|
||||
// === protocol handlers
|
||||
|
||||
func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) {
|
||||
var caps = &api.GetCapabilityResponse{
|
||||
Scope: driver.scope,
|
||||
}
|
||||
Log.Debugf("Get capabilities: responded with %+v", caps)
|
||||
return caps, nil
|
||||
}
|
||||
|
||||
func (driver *driver) CreateNetwork(create *api.CreateNetworkRequest) error {
|
||||
Log.Debugf("Create network request %+v", create)
|
||||
Log.Infof("Create network %s", create.NetworkID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) DeleteNetwork(delete *api.DeleteNetworkRequest) error {
|
||||
Log.Debugf("Delete network request: %+v", delete)
|
||||
Log.Infof("Destroy network %s", delete.NetworkID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error) {
|
||||
Log.Debugf("Create endpoint request %+v", create)
|
||||
endID := create.EndpointID
|
||||
|
||||
if create.Interface == nil {
|
||||
return nil, fmt.Errorf("Not supported: creating an interface from within CreateEndpoint")
|
||||
}
|
||||
driver.Lock()
|
||||
driver.endpoints[endID] = struct{}{}
|
||||
driver.Unlock()
|
||||
resp := &api.CreateEndpointResponse{}
|
||||
|
||||
Log.Infof("Create endpoint %s %+v", endID, resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (driver *driver) DeleteEndpoint(deleteReq *api.DeleteEndpointRequest) error {
|
||||
Log.Debugf("Delete endpoint request: %+v", deleteReq)
|
||||
Log.Infof("Delete endpoint %s", deleteReq.EndpointID)
|
||||
driver.Lock()
|
||||
delete(driver.endpoints, deleteReq.EndpointID)
|
||||
driver.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) HasEndpoint(endpointID string) bool {
|
||||
driver.Lock()
|
||||
_, found := driver.endpoints[endpointID]
|
||||
driver.Unlock()
|
||||
return found
|
||||
}
|
||||
|
||||
func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error) {
|
||||
Log.Debugf("Endpoint info request: %+v", req)
|
||||
Log.Infof("Endpoint info %s", req.EndpointID)
|
||||
return &api.EndpointInfoResponse{Value: map[string]interface{}{}}, nil
|
||||
}
|
||||
|
||||
func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) {
|
||||
endID := j.EndpointID
|
||||
|
||||
maybeBridge, err := netlink.LinkByName(WeaveBridge)
|
||||
if err != nil {
|
||||
return nil, errorf(`bridge "%s" not present; did you launch weave?`, WeaveBridge)
|
||||
}
|
||||
|
||||
// create and attach local name to the bridge
|
||||
local := vethPair(endID[:5])
|
||||
local.Attrs().MTU = maybeBridge.Attrs().MTU
|
||||
if err := netlink.LinkAdd(local); err != nil {
|
||||
return nil, errorf("could not create veth pair: %s", err)
|
||||
}
|
||||
|
||||
switch maybeBridge.(type) {
|
||||
case *netlink.Bridge:
|
||||
if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil {
|
||||
return nil, errorf(`unable to set master: %s`, err)
|
||||
}
|
||||
case *netlink.GenericLink:
|
||||
if maybeBridge.Type() != "openvswitch" {
|
||||
Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge)
|
||||
return nil, errorf(`device "%s" is of type "%s"`, WeaveBridge, maybeBridge.Type())
|
||||
}
|
||||
odp.AddDatapathInterface(WeaveBridge, local.Name)
|
||||
case *netlink.Device:
|
||||
Log.Warnf("kernel does not report what kind of device %s is, just %+v", WeaveBridge, maybeBridge)
|
||||
// Assume it's our openvswitch device, and the kernel has not been updated to report the kind.
|
||||
odp.AddDatapathInterface(WeaveBridge, local.Name)
|
||||
default:
|
||||
Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge)
|
||||
return nil, errorf(`device "%s" not a bridge`, WeaveBridge)
|
||||
}
|
||||
if err := netlink.LinkSetUp(local); err != nil {
|
||||
return nil, errorf(`unable to bring veth up: %s`, err)
|
||||
}
|
||||
|
||||
ifname := &api.InterfaceName{
|
||||
SrcName: local.PeerName,
|
||||
DstPrefix: "ethwe",
|
||||
}
|
||||
|
||||
response := &api.JoinResponse{
|
||||
InterfaceName: ifname,
|
||||
}
|
||||
if driver.nameserver != "" {
|
||||
routeToDNS := api.StaticRoute{
|
||||
Destination: driver.nameserver + "/32",
|
||||
RouteType: types.CONNECTED,
|
||||
NextHop: "",
|
||||
}
|
||||
response.StaticRoutes = []api.StaticRoute{routeToDNS}
|
||||
}
|
||||
if !driver.noMulticastRoute {
|
||||
multicastRoute := api.StaticRoute{
|
||||
Destination: "224.0.0.0/4",
|
||||
RouteType: types.CONNECTED,
|
||||
}
|
||||
response.StaticRoutes = append(response.StaticRoutes, multicastRoute)
|
||||
}
|
||||
Log.Infof("Join endpoint %s:%s to %s", j.NetworkID, j.EndpointID, j.SandboxKey)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error {
|
||||
Log.Debugf("Leave request: %+v", leave)
|
||||
|
||||
local := vethPair(leave.EndpointID[:5])
|
||||
if err := netlink.LinkDel(local); err != nil {
|
||||
Log.Warningf("unable to delete veth on leave: %s", err)
|
||||
}
|
||||
Log.Infof("Leave %s:%s", leave.NetworkID, leave.EndpointID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) DiscoverNew(disco *api.DiscoveryNotification) error {
|
||||
Log.Debugf("Dicovery new notification: %+v", disco)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *driver) DiscoverDelete(disco *api.DiscoveryNotification) error {
|
||||
Log.Debugf("Dicovery delete notification: %+v", disco)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ===
|
||||
|
||||
func vethPair(suffix string) *netlink.Veth {
|
||||
return &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{Name: "vethwl" + suffix},
|
||||
PeerName: "vethwg" + suffix,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
weaveapi "github.com/weaveworks/weave/api"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
)
|
||||
|
||||
const (
|
||||
WeaveDomain = "weave.local"
|
||||
)
|
||||
|
||||
type watcher struct {
|
||||
client *docker.Client
|
||||
weave *weaveapi.Client
|
||||
driver *driver
|
||||
}
|
||||
|
||||
type Watcher interface {
|
||||
}
|
||||
|
||||
func NewWatcher(client *docker.Client, driver *driver) (Watcher, error) {
|
||||
resolver := func() (string, error) { return client.GetContainerIP(WeaveContainer) }
|
||||
w := &watcher{client: client, driver: driver,
|
||||
weave: weaveapi.NewClientWithResolver(resolver)}
|
||||
err := client.AddObserver(w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *watcher) ContainerStarted(id string) {
|
||||
Log.Debugf("Container started %s", id)
|
||||
info, err := w.client.InspectContainer(id)
|
||||
if err != nil {
|
||||
Log.Warningf("error inspecting container: %s", err)
|
||||
return
|
||||
}
|
||||
// check that it's on our network, via the endpointID
|
||||
for _, net := range info.NetworkSettings.Networks {
|
||||
if w.driver.HasEndpoint(net.EndpointID) {
|
||||
fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)
|
||||
if err := w.weave.RegisterWithDNS(id, fqdn, net.IPAddress); err != nil {
|
||||
Log.Warningf("unable to register with weaveDNS: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) ContainerDied(id string) {
|
||||
// don't need to do this as WeaveDNS removes names on container died anyway
|
||||
// (note by the time we get this event we can't see the EndpointID)
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package skel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/docker/libnetwork/drivers/remote/api"
|
||||
"github.com/docker/libnetwork/ipamapi"
|
||||
iapi "github.com/docker/libnetwork/ipams/remote/api"
|
||||
)
|
||||
|
||||
const (
|
||||
networkReceiver = "NetworkDriver"
|
||||
ipamReceiver = ipamapi.PluginEndpointType
|
||||
)
|
||||
|
||||
type Driver interface {
|
||||
GetCapabilities() (*api.GetCapabilityResponse, error)
|
||||
CreateNetwork(create *api.CreateNetworkRequest) error
|
||||
DeleteNetwork(delete *api.DeleteNetworkRequest) error
|
||||
CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error)
|
||||
DeleteEndpoint(delete *api.DeleteEndpointRequest) error
|
||||
EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointInfoResponse, error)
|
||||
JoinEndpoint(j *api.JoinRequest) (response *api.JoinResponse, error error)
|
||||
LeaveEndpoint(leave *api.LeaveRequest) error
|
||||
DiscoverNew(discover *api.DiscoveryNotification) error
|
||||
DiscoverDelete(delete *api.DiscoveryNotification) error
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
d Driver
|
||||
i ipamapi.Ipam
|
||||
}
|
||||
|
||||
func Listen(socket net.Listener, driver Driver, ipamDriver ipamapi.Ipam) error {
|
||||
router := mux.NewRouter()
|
||||
router.NotFoundHandler = http.HandlerFunc(notFound)
|
||||
|
||||
listener := &listener{driver, ipamDriver}
|
||||
|
||||
router.Methods("POST").Path("/Plugin.Activate").HandlerFunc(listener.handshake)
|
||||
|
||||
handleMethod := func(receiver, method string, h http.HandlerFunc) {
|
||||
router.Methods("POST").Path(fmt.Sprintf("/%s.%s", receiver, method)).HandlerFunc(h)
|
||||
}
|
||||
|
||||
handleMethod(networkReceiver, "GetCapabilities", listener.getCapabilities)
|
||||
|
||||
if driver != nil {
|
||||
handleMethod(networkReceiver, "CreateNetwork", listener.createNetwork)
|
||||
handleMethod(networkReceiver, "DeleteNetwork", listener.deleteNetwork)
|
||||
handleMethod(networkReceiver, "CreateEndpoint", listener.createEndpoint)
|
||||
handleMethod(networkReceiver, "DeleteEndpoint", listener.deleteEndpoint)
|
||||
handleMethod(networkReceiver, "EndpointOperInfo", listener.infoEndpoint)
|
||||
handleMethod(networkReceiver, "Join", listener.joinEndpoint)
|
||||
handleMethod(networkReceiver, "Leave", listener.leaveEndpoint)
|
||||
}
|
||||
|
||||
if ipamDriver != nil {
|
||||
handleMethod(ipamReceiver, "GetDefaultAddressSpaces", listener.getDefaultAddressSpaces)
|
||||
handleMethod(ipamReceiver, "RequestPool", listener.requestPool)
|
||||
handleMethod(ipamReceiver, "ReleasePool", listener.releasePool)
|
||||
handleMethod(ipamReceiver, "RequestAddress", listener.requestAddress)
|
||||
handleMethod(ipamReceiver, "ReleaseAddress", listener.releaseAddress)
|
||||
}
|
||||
|
||||
return http.Serve(socket, router)
|
||||
}
|
||||
|
||||
func decode(w http.ResponseWriter, r *http.Request, v interface{}) error {
|
||||
err := json.NewDecoder(r.Body).Decode(v)
|
||||
if err != nil {
|
||||
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// === protocol handlers
|
||||
|
||||
type handshakeResp struct {
|
||||
Implements []string
|
||||
}
|
||||
|
||||
func (listener *listener) handshake(w http.ResponseWriter, r *http.Request) {
|
||||
var resp handshakeResp
|
||||
if listener.d != nil {
|
||||
resp.Implements = append(resp.Implements, "NetworkDriver")
|
||||
}
|
||||
if listener.i != nil {
|
||||
resp.Implements = append(resp.Implements, "IpamDriver")
|
||||
}
|
||||
err := json.NewEncoder(w).Encode(&resp)
|
||||
if err != nil {
|
||||
sendError(w, "encode error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (listener *listener) getCapabilities(w http.ResponseWriter, r *http.Request) {
|
||||
caps, err := listener.d.GetCapabilities()
|
||||
objectOrErrorResponse(w, caps, err)
|
||||
}
|
||||
|
||||
func (listener *listener) createNetwork(w http.ResponseWriter, r *http.Request) {
|
||||
var create api.CreateNetworkRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&create)
|
||||
if err != nil {
|
||||
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
emptyOrErrorResponse(w, listener.d.CreateNetwork(&create))
|
||||
}
|
||||
|
||||
func (listener *listener) deleteNetwork(w http.ResponseWriter, r *http.Request) {
|
||||
var delete api.DeleteNetworkRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
|
||||
sendError(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
emptyOrErrorResponse(w, listener.d.DeleteNetwork(&delete))
|
||||
}
|
||||
|
||||
func (listener *listener) createEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
var create api.CreateEndpointRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
|
||||
sendError(w, "unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
res, err := listener.d.CreateEndpoint(&create)
|
||||
objectOrErrorResponse(w, res, err)
|
||||
}
|
||||
|
||||
func (listener *listener) deleteEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
var delete api.DeleteEndpointRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&delete); err != nil {
|
||||
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
emptyOrErrorResponse(w, listener.d.DeleteEndpoint(&delete))
|
||||
}
|
||||
|
||||
func (listener *listener) infoEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
var req api.EndpointInfoRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
info, err := listener.d.EndpointInfo(&req)
|
||||
objectOrErrorResponse(w, info, err)
|
||||
}
|
||||
|
||||
func (listener *listener) joinEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
var join api.JoinRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&join); err != nil {
|
||||
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
res, err := listener.d.JoinEndpoint(&join)
|
||||
objectOrErrorResponse(w, res, err)
|
||||
}
|
||||
|
||||
func (listener *listener) leaveEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
var l api.LeaveRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
|
||||
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
emptyOrErrorResponse(w, listener.d.LeaveEndpoint(&l))
|
||||
}
|
||||
|
||||
func (listener *listener) discoverNew(w http.ResponseWriter, r *http.Request) {
|
||||
var disco api.DiscoveryNotification
|
||||
if err := json.NewDecoder(r.Body).Decode(&disco); err != nil {
|
||||
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
emptyOrErrorResponse(w, listener.d.DiscoverNew(&disco))
|
||||
}
|
||||
|
||||
func (listener *listener) discoverDelete(w http.ResponseWriter, r *http.Request) {
|
||||
var disco api.DiscoveryNotification
|
||||
if err := json.NewDecoder(r.Body).Decode(&disco); err != nil {
|
||||
sendError(w, "Could not decode JSON encode payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
emptyOrErrorResponse(w, listener.d.DiscoverDelete(&disco))
|
||||
}
|
||||
|
||||
// ===
|
||||
|
||||
func (listener *listener) getDefaultAddressSpaces(w http.ResponseWriter, r *http.Request) {
|
||||
local, global, err := listener.i.GetDefaultAddressSpaces()
|
||||
response := &iapi.GetAddressSpacesResponse{
|
||||
LocalDefaultAddressSpace: local,
|
||||
GlobalDefaultAddressSpace: global,
|
||||
}
|
||||
objectOrErrorResponse(w, response, err)
|
||||
}
|
||||
|
||||
func (listener *listener) requestPool(w http.ResponseWriter, r *http.Request) {
|
||||
var rq iapi.RequestPoolRequest
|
||||
if err := decode(w, r, &rq); err != nil {
|
||||
return
|
||||
}
|
||||
poolID, pool, data, err := listener.i.RequestPool(rq.AddressSpace, rq.Pool, rq.SubPool, rq.Options, rq.V6)
|
||||
if err != nil {
|
||||
errorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
response := &iapi.RequestPoolResponse{
|
||||
PoolID: poolID,
|
||||
Pool: pool.String(),
|
||||
Data: data,
|
||||
}
|
||||
objectResponse(w, response)
|
||||
}
|
||||
|
||||
func (listener *listener) releasePool(w http.ResponseWriter, r *http.Request) {
|
||||
var rq iapi.ReleasePoolRequest
|
||||
if err := decode(w, r, &rq); err != nil {
|
||||
return
|
||||
}
|
||||
err := listener.i.ReleasePool(rq.PoolID)
|
||||
emptyOrErrorResponse(w, err)
|
||||
}
|
||||
|
||||
func (listener *listener) requestAddress(w http.ResponseWriter, r *http.Request) {
|
||||
var rq iapi.RequestAddressRequest
|
||||
if err := decode(w, r, &rq); err != nil {
|
||||
return
|
||||
}
|
||||
address, data, err := listener.i.RequestAddress(rq.PoolID, net.ParseIP(rq.Address), rq.Options)
|
||||
if err != nil {
|
||||
errorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
response := &iapi.RequestAddressResponse{
|
||||
Address: address.String(),
|
||||
Data: data,
|
||||
}
|
||||
objectResponse(w, response)
|
||||
}
|
||||
|
||||
func (listener *listener) releaseAddress(w http.ResponseWriter, r *http.Request) {
|
||||
var rq iapi.ReleaseAddressRequest
|
||||
if err := decode(w, r, &rq); err != nil {
|
||||
return
|
||||
}
|
||||
err := listener.i.ReleaseAddress(rq.PoolID, net.ParseIP(rq.Address))
|
||||
emptyOrErrorResponse(w, err)
|
||||
}
|
||||
|
||||
// ===
|
||||
|
||||
func notFound(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
func sendError(w http.ResponseWriter, msg string, code int) {
|
||||
http.Error(w, msg, code)
|
||||
}
|
||||
|
||||
func errorResponse(w http.ResponseWriter, fmtString string, item ...interface{}) {
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"Err": fmt.Sprintf(fmtString, item...),
|
||||
})
|
||||
}
|
||||
|
||||
func objectResponse(w http.ResponseWriter, obj interface{}) {
|
||||
if err := json.NewEncoder(w).Encode(obj); err != nil {
|
||||
sendError(w, "Could not JSON encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func emptyResponse(w http.ResponseWriter) {
|
||||
json.NewEncoder(w).Encode(map[string]string{})
|
||||
}
|
||||
|
||||
func objectOrErrorResponse(w http.ResponseWriter, obj interface{}, err error) {
|
||||
if err != nil {
|
||||
errorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
objectResponse(w, obj)
|
||||
}
|
||||
|
||||
func emptyOrErrorResponse(w http.ResponseWriter, err error) {
|
||||
if err != nil {
|
||||
errorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
emptyResponse(w)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// docker_tls_args: find the docker daemon's tls args
|
||||
// This reimplements pgrep, because pgrep will only look in /proc, but not in
|
||||
// $PROCFS.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
procRoot := os.Getenv("PROCFS")
|
||||
if procRoot == "" {
|
||||
procRoot = "/proc"
|
||||
}
|
||||
|
||||
dirEntries, err := ioutil.ReadDir(procRoot)
|
||||
checkErr(err)
|
||||
|
||||
for _, dirEntry := range dirEntries {
|
||||
dirName := dirEntry.Name()
|
||||
if _, err := strconv.Atoi(dirName); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if comm, err := ioutil.ReadFile(filepath.Join(procRoot, dirName, "comm")); err != nil || string(comm) != "docker\n" {
|
||||
continue
|
||||
}
|
||||
|
||||
cmdline, err := ioutil.ReadFile(filepath.Join(procRoot, dirName, "cmdline"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
isDaemon := false
|
||||
tlsArgs := []string{}
|
||||
args := bytes.Split(cmdline, []byte{'\000'})
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := string(args[i])
|
||||
switch {
|
||||
case arg == "-d" || arg == "daemon":
|
||||
isDaemon = true
|
||||
break
|
||||
case arg == "--tls", arg == "--tlsverify":
|
||||
tlsArgs = append(tlsArgs, arg)
|
||||
case strings.HasPrefix(arg, "--tls"):
|
||||
tlsArgs = append(tlsArgs, arg)
|
||||
if len(args) > i+1 &&
|
||||
!strings.Contains(arg, "=") &&
|
||||
!strings.HasPrefix(string(args[i+1]), "-") {
|
||||
tlsArgs = append(tlsArgs, string(args[i+1]))
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isDaemon {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(tlsArgs, " "))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM weaveworks/weaveexec
|
||||
ADD ./plugin /home/weave/
|
||||
ENTRYPOINT ["/home/weave/plugin"]
|
||||
@@ -0,0 +1,163 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libnetwork/ipamapi"
|
||||
go_docker "github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
ipamplugin "github.com/weaveworks/weave/plugin/ipam"
|
||||
netplugin "github.com/weaveworks/weave/plugin/net"
|
||||
"github.com/weaveworks/weave/plugin/skel"
|
||||
)
|
||||
|
||||
var version = "(unreleased version)"
|
||||
|
||||
func main() {
|
||||
var (
|
||||
justVersion bool
|
||||
address string
|
||||
nameserver string
|
||||
meshAddress string
|
||||
logLevel string
|
||||
meshNetworkName string
|
||||
noMulticastRoute bool
|
||||
removeNetwork bool
|
||||
)
|
||||
|
||||
flag.BoolVar(&justVersion, "version", false, "print version and exit")
|
||||
flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)")
|
||||
flag.StringVar(&address, "socket", "/run/docker/plugins/weave.sock", "socket on which to listen")
|
||||
flag.StringVar(&nameserver, "nameserver", "", "nameserver to provide to containers")
|
||||
flag.StringVar(&meshAddress, "meshsocket", "/run/docker/plugins/weavemesh.sock", "socket on which to listen in mesh mode")
|
||||
flag.StringVar(&meshNetworkName, "mesh-network-name", "weave", "network name to create in mesh mode")
|
||||
flag.BoolVar(&noMulticastRoute, "no-multicast-route", false, "do not add a multicast route to network endpoints")
|
||||
flag.BoolVar(&removeNetwork, "remove-network", false, "remove mesh network and exit")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if justVersion {
|
||||
fmt.Printf("weave plugin %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
SetLogLevel(logLevel)
|
||||
|
||||
// API 1.21 is the first version that supports docker network commands
|
||||
dockerClient, err := docker.NewVersionedClientFromEnv("1.21")
|
||||
if err != nil {
|
||||
Log.Fatalf("unable to connect to docker: %s", err)
|
||||
}
|
||||
|
||||
if removeNetwork {
|
||||
if _, err = dockerClient.Client.NetworkInfo(meshNetworkName); err == nil {
|
||||
err = dockerClient.Client.RemoveNetwork(meshNetworkName)
|
||||
if err != nil {
|
||||
Log.Fatalf("unable to remove network: %s", err)
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
Log.Println("Weave plugin", version, "Command line options:", os.Args[1:])
|
||||
Log.Info(dockerClient.Info())
|
||||
|
||||
var globalListener, meshListener net.Listener
|
||||
endChan := make(chan error, 1)
|
||||
if address != "" {
|
||||
globalListener, err := listenAndServe(dockerClient, address, nameserver, noMulticastRoute, endChan, "global", false)
|
||||
if err != nil {
|
||||
Log.Fatalf("unable to create driver: %s", err)
|
||||
}
|
||||
defer globalListener.Close()
|
||||
}
|
||||
if meshAddress != "" {
|
||||
meshListener, err := listenAndServe(dockerClient, meshAddress, nameserver, noMulticastRoute, endChan, "local", true)
|
||||
if err != nil {
|
||||
Log.Fatalf("unable to create driver: %s", err)
|
||||
}
|
||||
defer meshListener.Close()
|
||||
}
|
||||
|
||||
if meshNetworkName != "" {
|
||||
createNetwork(dockerClient, meshNetworkName, meshAddress)
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
Log.Debugf("Caught signal %s; shutting down", sig)
|
||||
if err := dockerClient.Client.RemoveNetwork(meshNetworkName); err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
case err := <-endChan:
|
||||
if err != nil {
|
||||
Log.Errorf("Error from listener: %s", err)
|
||||
globalListener.Close()
|
||||
meshListener.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listenAndServe(dockerClient *docker.Client, address, nameserver string, noMulticastRoute bool, endChan chan<- error, scope string, withIpam bool) (net.Listener, error) {
|
||||
d, err := netplugin.New(dockerClient, version, nameserver, scope, noMulticastRoute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var i ipamapi.Ipam
|
||||
if withIpam {
|
||||
if i, err = ipamplugin.NewIpam(dockerClient, version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var listener net.Listener
|
||||
|
||||
// remove sockets from last invocation
|
||||
if err := os.Remove(address); err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
listener, err = net.Listen("unix", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Log.Printf("Listening on %s for %s scope", address, scope)
|
||||
|
||||
go func() {
|
||||
endChan <- skel.Listen(listener, d, i)
|
||||
}()
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func createNetwork(dockerClient *docker.Client, networkName, address string) {
|
||||
if _, err := dockerClient.Client.NetworkInfo(networkName); err == nil {
|
||||
Log.Printf("Docker network '%s' already exists", networkName)
|
||||
} else if _, ok := err.(*go_docker.NoSuchNetwork); ok {
|
||||
driverName := strings.TrimSuffix(address, ".sock")
|
||||
if i := strings.LastIndex(driverName, "/"); i >= 0 {
|
||||
driverName = driverName[i+1:]
|
||||
}
|
||||
options := go_docker.CreateNetworkOptions{
|
||||
Name: networkName,
|
||||
CheckDuplicate: true,
|
||||
Driver: driverName,
|
||||
IPAM: go_docker.IPAMOptions{Driver: driverName},
|
||||
}
|
||||
_, err := dockerClient.Client.CreateNetwork(options)
|
||||
if err != nil {
|
||||
Log.Fatalf("Error creating network: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
FROM gliderlabs/alpine
|
||||
FROM alpine
|
||||
|
||||
MAINTAINER Weaveworks Inc <help@weave.works>
|
||||
LABEL works.weave.role=system
|
||||
|
||||
WORKDIR /home/weave
|
||||
VOLUME /w
|
||||
VOLUME /w-noop
|
||||
VOLUME /w-nomcast
|
||||
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
|
||||
|
||||
RUN apk add --update \
|
||||
@@ -17,7 +19,9 @@ RUN apk add --update \
|
||||
bind-tools \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
ADD ./weave ./sigproxy ./weaveproxy /home/weave/
|
||||
ADD ./netcheck ./weavehosts /usr/bin/
|
||||
ADD ./weave ./sigproxy ./weaveproxy ./symlink /home/weave/
|
||||
ADD ./netcheck ./docker_tls_args /usr/bin/
|
||||
ADD ./weavewait /w/w
|
||||
ADD ./weavewait_noop /w-noop/w
|
||||
ADD ./weavewait_nomcast /w-nomcast/w
|
||||
ADD ./docker.tgz /
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# This lets us force-symlink a bunch of stuff all at once, so we can minimize
|
||||
# running containers.
|
||||
|
||||
set -e
|
||||
|
||||
[ -z "$WEAVE_DEBUG" ] || set -x
|
||||
|
||||
target="$1"
|
||||
user="$(stat -c '%u:%g' "$target")"
|
||||
mode="$(stat -c '%a' "$target")"
|
||||
shift 1
|
||||
|
||||
for link in "$@" ; do
|
||||
ln -sfT "$target" "$link"
|
||||
done
|
||||
chown -h "$user" "$@"
|
||||
chmod "$mode" "$@"
|
||||
@@ -1,120 +0,0 @@
|
||||
/* weavehosts: rewrite /etc/hosts as necessary to put the weave address(es) at the top */
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := os.Args
|
||||
if len(args) < 4 {
|
||||
fmt.Fprintln(os.Stderr, "usage: weavehosts hostpath hostname cidr [cidr ...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
checkErr(updateHosts(args[1], args[2], args[3:]))
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func updateHosts(path, hostname string, cidrs []string) error {
|
||||
hosts, err := parseHosts(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove existing ips pointing to our hostname
|
||||
toRemove := []string{}
|
||||
for ip, addrs := range hosts {
|
||||
for _, addr := range addrs {
|
||||
if addr == hostname {
|
||||
toRemove = append(toRemove, ip)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ip := range toRemove {
|
||||
delete(hosts, ip)
|
||||
}
|
||||
|
||||
// Add the weave ip(s)
|
||||
for _, cidr := range cidrs {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
checkErr(err)
|
||||
ipStr := ip.String()
|
||||
hosts[ipStr] = append(hosts[ipStr], hostname)
|
||||
}
|
||||
|
||||
return writeHosts(path, hosts)
|
||||
}
|
||||
|
||||
func parseHosts(path string) (map[string][]string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
ips := map[string][]string{}
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// Remove any comments
|
||||
if i := strings.IndexByte(line, '#'); i != -1 {
|
||||
line = line[:i]
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 0 {
|
||||
ips[fields[0]] = append(ips[fields[0]], fields[1:]...)
|
||||
}
|
||||
}
|
||||
if scanner.Err() != nil {
|
||||
return nil, scanner.Err()
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func writeHosts(path string, contents map[string][]string) error {
|
||||
ips := []string{}
|
||||
for ip := range contents {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
sort.Strings(ips)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
fmt.Fprintln(buf, "# modified by weave")
|
||||
for _, ip := range ips {
|
||||
if addrs := contents[ip]; len(addrs) > 0 {
|
||||
fmt.Fprintf(buf, "%s\t%s\n", ip, strings.Join(uniqueStrs(addrs), " "))
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(path, buf.Bytes(), 644)
|
||||
}
|
||||
|
||||
func uniqueStrs(s []string) []string {
|
||||
m := map[string]struct{}{}
|
||||
result := []string{}
|
||||
for _, str := range s {
|
||||
if _, ok := m[str]; !ok {
|
||||
m[str] = struct{}{}
|
||||
result = append(result, str)
|
||||
}
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
+25
-10
@@ -12,14 +12,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "(unreleased version)"
|
||||
version = "(unreleased version)"
|
||||
defaultDockerHost = "unix:///var/run/docker.sock"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
justVersion bool
|
||||
logLevel = "info"
|
||||
c = proxy.Config{ListenAddrs: []string{}}
|
||||
c = proxy.Config{Image: "weaveworks/weaveexec"}
|
||||
withDNS bool
|
||||
)
|
||||
|
||||
c.Version = version
|
||||
@@ -35,32 +37,45 @@ func main() {
|
||||
mflag.BoolVar(&c.NoRewriteHosts, []string{"-no-rewrite-hosts"}, false, "do not automatically rewrite /etc/hosts. Use if you need the docker IP to remain in /etc/hosts")
|
||||
mflag.StringVar(&c.TLSConfig.CACert, []string{"#tlscacert", "-tlscacert"}, "", "Trust certs signed only by this CA")
|
||||
mflag.StringVar(&c.TLSConfig.Cert, []string{"#tlscert", "-tlscert"}, "", "Path to TLS certificate file")
|
||||
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tls-verify")
|
||||
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tlsverify")
|
||||
mflag.StringVar(&c.TLSConfig.Key, []string{"#tlskey", "-tlskey"}, "", "Path to TLS key file")
|
||||
mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote")
|
||||
mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver")
|
||||
mflag.BoolVar(&withDNS, []string{"#-with-dns", "#w"}, false, "option removed")
|
||||
mflag.BoolVar(&c.WithoutDNS, []string{"-without-dns"}, false, "instruct created containers to never use weaveDNS as their nameserver")
|
||||
mflag.BoolVar(&c.NoMulticastRoute, []string{"-no-multicast-route"}, false, "do not add a multicast route via the weave interface when attaching containers")
|
||||
mflag.Parse()
|
||||
|
||||
if justVersion {
|
||||
fmt.Printf("weave proxy %s\n", version)
|
||||
fmt.Printf("weave proxy %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if c.WithDNS && c.WithoutDNS {
|
||||
Log.Fatalf("Cannot use both '--with-dns' and '--without-dns' flags")
|
||||
}
|
||||
|
||||
SetLogLevel(logLevel)
|
||||
|
||||
if image := os.Getenv("EXEC_IMAGE"); image != "" {
|
||||
c.Image = image
|
||||
}
|
||||
|
||||
Log.Infoln("weave proxy", version)
|
||||
Log.Infoln("Command line arguments:", strings.Join(os.Args[1:], " "))
|
||||
|
||||
if withDNS {
|
||||
Log.Warning("--with-dns option has been removed; DNS is on by default")
|
||||
}
|
||||
|
||||
c.DockerHost = defaultDockerHost
|
||||
if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost != "" {
|
||||
c.DockerHost = dockerHost
|
||||
}
|
||||
|
||||
p, err := proxy.NewProxy(c)
|
||||
if err != nil {
|
||||
Log.Fatalf("Could not start proxy: %s", err)
|
||||
}
|
||||
|
||||
go p.ListenAndServe()
|
||||
listeners := p.Listen()
|
||||
p.AttachExistingContainers()
|
||||
go p.Serve(listeners)
|
||||
go p.ListenAndServeStatus("/home/weave/status.sock")
|
||||
SignalHandlerLoop()
|
||||
}
|
||||
+121
-36
@@ -1,17 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam"
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
weave "github.com/weaveworks/weave/router"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
weave "github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
var rootTemplate = template.New("root").Funcs(map[string]interface{}{
|
||||
@@ -24,14 +27,74 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{
|
||||
}
|
||||
return count
|
||||
},
|
||||
"printConnectionCounts": func(conns []weave.LocalConnectionStatus) string {
|
||||
"printList": func(list []string) string {
|
||||
if len(list) == 0 {
|
||||
return "none"
|
||||
}
|
||||
return strings.Join(list, ", ")
|
||||
},
|
||||
"printIPAMRanges": func(router weave.NetworkRouterStatus, status ipam.Status) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
type stats struct {
|
||||
ips uint32
|
||||
nickname string
|
||||
reachable bool
|
||||
}
|
||||
|
||||
peerStats := make(map[string]*stats)
|
||||
|
||||
for _, entry := range status.Entries {
|
||||
s, found := peerStats[entry.Peer]
|
||||
if !found {
|
||||
s = &stats{nickname: entry.Nickname, reachable: entry.IsKnownPeer}
|
||||
peerStats[entry.Peer] = s
|
||||
}
|
||||
s.ips += entry.Size
|
||||
}
|
||||
|
||||
printOwned := func(name string, nickName string, reachable bool, ips uint32) {
|
||||
reachableStr := ""
|
||||
if !reachable {
|
||||
reachableStr = "- unreachable!"
|
||||
}
|
||||
percentageRanges := float32(ips) * 100.0 / float32(status.RangeNumIPs)
|
||||
|
||||
displayName := name + "(" + nickName + ")"
|
||||
fmt.Fprintf(&buffer, "%-37v %8d IPs (%04.1f%% of total) %s\n",
|
||||
displayName, ips, percentageRanges, reachableStr)
|
||||
}
|
||||
|
||||
// print the local info first
|
||||
if ourStats := peerStats[router.Name]; ourStats != nil {
|
||||
printOwned(router.Name, ourStats.nickname, true, ourStats.ips)
|
||||
}
|
||||
|
||||
// and then the rest
|
||||
for peer, stats := range peerStats {
|
||||
if peer != router.Name {
|
||||
printOwned(peer, stats.nickname, stats.reachable, stats.ips)
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
},
|
||||
"allIPAMOwnersUnreachable": func(status ipam.Status) bool {
|
||||
for _, entry := range status.Entries {
|
||||
if entry.Size > 0 && entry.IsKnownPeer {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
"printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string {
|
||||
counts := make(map[string]int)
|
||||
for _, conn := range conns {
|
||||
counts[conn.State]++
|
||||
}
|
||||
return printCounts(counts, []string{"established", "pending", "retrying", "failed", "connecting"})
|
||||
},
|
||||
"printPeerConnectionCounts": func(peers []weave.PeerStatus) string {
|
||||
"printPeerConnectionCounts": func(peers []mesh.PeerStatus) string {
|
||||
counts := make(map[string]int)
|
||||
for _, peer := range peers {
|
||||
for _, conn := range peer.Connections {
|
||||
@@ -75,40 +138,48 @@ func defTemplate(name string, text string) *template.Template {
|
||||
}
|
||||
|
||||
var statusTemplate = defTemplate("status", `\
|
||||
Version: {{.Version}}
|
||||
Version: {{.Version}}
|
||||
|
||||
Service: router
|
||||
Protocol: {{.Router.Protocol}} \
|
||||
Service: router
|
||||
Protocol: {{.Router.Protocol}} \
|
||||
{{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\
|
||||
{{.Router.ProtocolMaxVersion}}\
|
||||
{{else}}\
|
||||
{{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\
|
||||
{{end}}
|
||||
Name: {{.Router.Name}}({{.Router.NickName}})
|
||||
Encryption: {{printState .Router.Encryption}}
|
||||
PeerDiscovery: {{printState .Router.PeerDiscovery}}
|
||||
Targets: {{len .Router.Targets}}
|
||||
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
|
||||
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections between them){{end}}
|
||||
Name: {{.Router.Name}}({{.Router.NickName}})
|
||||
Encryption: {{printState .Router.Encryption}}
|
||||
PeerDiscovery: {{printState .Router.PeerDiscovery}}
|
||||
Targets: {{len .Router.Targets}}
|
||||
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
|
||||
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}}
|
||||
TrustedSubnets: {{printList .Router.TrustedSubnets}}
|
||||
{{if .IPAM}}\
|
||||
|
||||
Service: ipam
|
||||
Service: ipam
|
||||
{{if .IPAM.Entries}}\
|
||||
Consensus: achieved
|
||||
{{else if .IPAM.Paxos}}\
|
||||
Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
|
||||
{{if allIPAMOwnersUnreachable .IPAM}}\
|
||||
Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead
|
||||
{{else if len .IPAM.PendingAllocates}}\
|
||||
Status: waiting for IP range grant from peers
|
||||
{{else}}\
|
||||
Consensus: deferred
|
||||
Status: ready
|
||||
{{end}}\
|
||||
Range: {{.IPAM.Range}}
|
||||
DefaultSubnet: {{.IPAM.DefaultSubnet}}
|
||||
{{else if .IPAM.Paxos}}\
|
||||
Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
|
||||
{{else}}\
|
||||
Status: idle
|
||||
{{end}}\
|
||||
Range: {{.IPAM.Range}}
|
||||
DefaultSubnet: {{.IPAM.DefaultSubnet}}
|
||||
{{end}}\
|
||||
{{if .DNS}}\
|
||||
|
||||
Service: dns
|
||||
Domain: {{.DNS.Domain}}
|
||||
TTL: {{.DNS.TTL}}
|
||||
Entries: {{countDNSEntries .DNS.Entries}}
|
||||
Service: dns
|
||||
Domain: {{.DNS.Domain}}
|
||||
Upstream: {{printList .DNS.Upstream}}
|
||||
TTL: {{.DNS.TTL}}
|
||||
Entries: {{countDNSEntries .DNS.Entries}}
|
||||
{{end}}\
|
||||
`)
|
||||
|
||||
@@ -128,7 +199,7 @@ var peersTemplate = defTemplate("peers", `\
|
||||
{{.Name}}({{.NickName}})
|
||||
{{range .Connections}}\
|
||||
{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} \
|
||||
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-32v" $nameNickName}} \
|
||||
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-37v" $nameNickName}} \
|
||||
{{if .Established}}established{{else}}pending{{end}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
@@ -144,31 +215,44 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\
|
||||
{{end}}\
|
||||
`)
|
||||
|
||||
var ipamTemplate = defTemplate("ipamTemplate", `{{printIPAMRanges .Router .IPAM}}`)
|
||||
|
||||
type WeaveStatus struct {
|
||||
Version string
|
||||
Router *weave.Status `json:"Router,omitempty"`
|
||||
IPAM *ipam.Status `json:"IPAM,omitempty"`
|
||||
DNS *nameserver.Status `json:"DNS,omitempty"`
|
||||
Router *weave.NetworkRouterStatus `json:"Router,omitempty"`
|
||||
IPAM *ipam.Status `json:"IPAM,omitempty"`
|
||||
DNS *nameserver.Status `json:"DNS,omitempty"`
|
||||
}
|
||||
|
||||
func HandleHTTP(muxRouter *mux.Router, version string, router *weave.Router, allocator *ipam.Allocator, defaultSubnet address.CIDR, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) {
|
||||
func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRouter, allocator *ipam.Allocator, defaultSubnet address.CIDR, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) {
|
||||
status := func() WeaveStatus {
|
||||
return WeaveStatus{
|
||||
version,
|
||||
weave.NewStatus(router),
|
||||
weave.NewNetworkRouterStatus(router),
|
||||
ipam.NewStatus(allocator, defaultSubnet),
|
||||
nameserver.NewStatus(ns, dnsserver)}
|
||||
}
|
||||
muxRouter.Methods("GET").Path("/report").Headers("Accept", "application/json").HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
json, _ := json.MarshalIndent(status(), "", " ")
|
||||
json, err := json.MarshalIndent(status(), "", " ")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
Log.Error("Error during report marshalling: ", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(json)
|
||||
})
|
||||
|
||||
muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
formatTemplate, err := template.New("format").Parse(mux.Vars(r)["format"])
|
||||
funcs := template.FuncMap{
|
||||
"json": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
}
|
||||
formatTemplate, err := template.New("format").Funcs(funcs).Parse(mux.Vars(r)["format"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -194,5 +278,6 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.Router, all
|
||||
defHandler("/status/connections", connectionsTemplate)
|
||||
defHandler("/status/peers", peersTemplate)
|
||||
defHandler("/status/dns", dnsEntriesTemplate)
|
||||
defHandler("/status/ipam", ipamTemplate)
|
||||
|
||||
}
|
||||
+211
-86
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -17,7 +16,9 @@ import (
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
"github.com/weaveworks/weave/ipam"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
@@ -36,36 +37,54 @@ func main() {
|
||||
runtime.GOMAXPROCS(procs)
|
||||
|
||||
var (
|
||||
config weave.Config
|
||||
justVersion bool
|
||||
protocolMinVersion int
|
||||
ifaceName string
|
||||
routerName string
|
||||
nickName string
|
||||
password string
|
||||
pktdebug bool
|
||||
logLevel string
|
||||
prof string
|
||||
bufSzMB int
|
||||
noDiscovery bool
|
||||
httpAddr string
|
||||
iprangeCIDR string
|
||||
ipsubnetCIDR string
|
||||
peerCount int
|
||||
apiPath string
|
||||
peers []string
|
||||
// flags that cause immediate exit
|
||||
justVersion bool
|
||||
createDatapath bool
|
||||
deleteDatapath bool
|
||||
addDatapathInterface string
|
||||
|
||||
config mesh.Config
|
||||
networkConfig weave.NetworkConfig
|
||||
protocolMinVersion int
|
||||
ifaceName string
|
||||
routerName string
|
||||
nickName string
|
||||
password string
|
||||
pktdebug bool
|
||||
logLevel string
|
||||
prof string
|
||||
bufSzMB int
|
||||
noDiscovery bool
|
||||
httpAddr string
|
||||
iprangeCIDR string
|
||||
ipsubnetCIDR string
|
||||
peerCount int
|
||||
dockerAPI string
|
||||
peers []string
|
||||
noDNS bool
|
||||
dnsDomain string
|
||||
dnsListenAddress string
|
||||
dnsTTL int
|
||||
dnsClientTimeout time.Duration
|
||||
dnsEffectiveListenAddress string
|
||||
iface *net.Interface
|
||||
datapathName string
|
||||
trustedSubnetStr string
|
||||
|
||||
defaultDockerHost = "unix:///var/run/docker.sock"
|
||||
)
|
||||
|
||||
if val := os.Getenv("DOCKER_HOST"); val != "" {
|
||||
defaultDockerHost = val
|
||||
}
|
||||
|
||||
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
|
||||
mflag.IntVar(&config.Port, []string{"#port", "-port"}, weave.Port, "router port")
|
||||
mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, weave.ProtocolMinVersion, "minimum weave protocol version")
|
||||
mflag.BoolVar(&createDatapath, []string{"-create-datapath"}, false, "create ODP datapath and exit")
|
||||
mflag.BoolVar(&deleteDatapath, []string{"-delete-datapath"}, false, "delete ODP datapath and exit")
|
||||
mflag.StringVar(&addDatapathInterface, []string{"-add-datapath-iface"}, "", "add a network interface to the ODP datapath and exit")
|
||||
|
||||
mflag.IntVar(&config.Port, []string{"#port", "-port"}, mesh.Port, "router port")
|
||||
mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, mesh.ProtocolMinVersion, "minimum weave protocol version")
|
||||
mflag.StringVar(&ifaceName, []string{"#iface", "-iface"}, "", "name of interface to capture/inject from (disabled if blank)")
|
||||
mflag.StringVar(&routerName, []string{"#name", "-name"}, "", "name of router (defaults to MAC of interface)")
|
||||
mflag.StringVar(&nickName, []string{"#nickname", "-nickname"}, "", "nickname of peer (defaults to hostname)")
|
||||
@@ -76,17 +95,20 @@ func main() {
|
||||
mflag.IntVar(&config.ConnLimit, []string{"#connlimit", "#-connlimit", "-conn-limit"}, 30, "connection limit (0 for unlimited)")
|
||||
mflag.BoolVar(&noDiscovery, []string{"#nodiscovery", "#-nodiscovery", "-no-discovery"}, false, "disable peer discovery")
|
||||
mflag.IntVar(&bufSzMB, []string{"#bufsz", "-bufsz"}, 8, "capture buffer size in MB")
|
||||
mflag.StringVar(&httpAddr, []string{"#httpaddr", "#-httpaddr", "-http-addr"}, fmt.Sprintf(":%d", weave.HTTPPort), "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)")
|
||||
mflag.StringVar(&httpAddr, []string{"#httpaddr", "#-httpaddr", "-http-addr"}, "", "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)")
|
||||
mflag.StringVar(&iprangeCIDR, []string{"#iprange", "#-iprange", "-ipalloc-range"}, "", "IP address range reserved for automatic allocation, in CIDR notation")
|
||||
mflag.StringVar(&ipsubnetCIDR, []string{"#ipsubnet", "#-ipsubnet", "-ipalloc-default-subnet"}, "", "subnet to allocate within by default, in CIDR notation")
|
||||
mflag.IntVar(&peerCount, []string{"#initpeercount", "#-initpeercount", "-init-peer-count"}, 0, "number of peers in network (for IP address allocation)")
|
||||
mflag.StringVar(&apiPath, []string{"#api", "-api"}, "unix:///var/run/docker.sock", "Path to Docker API socket")
|
||||
mflag.StringVar(&dockerAPI, []string{"#api", "#-api", "-docker-api"}, defaultDockerHost, "Docker API endpoint")
|
||||
mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server")
|
||||
mflag.StringVar(&dnsDomain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for")
|
||||
mflag.StringVar(&dnsListenAddress, []string{"-dns-listen-address"}, nameserver.DefaultListenAddress, "address to listen on for DNS requests")
|
||||
mflag.IntVar(&dnsTTL, []string{"-dns-ttl"}, nameserver.DefaultTTL, "TTL for DNS request from our domain")
|
||||
mflag.DurationVar(&dnsClientTimeout, []string{"-dns-fallback-timeout"}, nameserver.DefaultClientTimeout, "timeout for fallback DNS requests")
|
||||
mflag.StringVar(&dnsEffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping")
|
||||
mflag.StringVar(&datapathName, []string{"-datapath"}, "", "ODP datapath name")
|
||||
|
||||
mflag.StringVar(&trustedSubnetStr, []string{"-trusted-subnets"}, "", "Command separated list of trusted subnets in CIDR notation")
|
||||
|
||||
// crude way of detecting that we probably have been started in a
|
||||
// container, with `weave launch` --> suppress misleading paths in
|
||||
@@ -95,59 +117,115 @@ func main() {
|
||||
os.Args[0] = "weave"
|
||||
mflag.CommandLine.Init("weave", mflag.ExitOnError)
|
||||
}
|
||||
|
||||
mflag.Parse()
|
||||
|
||||
peers = mflag.Args()
|
||||
|
||||
SetLogLevel(logLevel)
|
||||
if justVersion {
|
||||
|
||||
switch {
|
||||
case justVersion:
|
||||
fmt.Printf("weave router %s\n", version)
|
||||
os.Exit(0)
|
||||
|
||||
case createDatapath:
|
||||
err, odp_supported := odp.CreateDatapath(datapathName)
|
||||
if !odp_supported {
|
||||
if err != nil {
|
||||
Log.Error(err)
|
||||
}
|
||||
|
||||
// When the kernel lacks ODP support, exit
|
||||
// with a special status to distinguish it for
|
||||
// the weave script.
|
||||
os.Exit(17)
|
||||
}
|
||||
|
||||
checkFatal(err)
|
||||
os.Exit(0)
|
||||
|
||||
case deleteDatapath:
|
||||
checkFatal(odp.DeleteDatapath(datapathName))
|
||||
os.Exit(0)
|
||||
|
||||
case addDatapathInterface != "":
|
||||
checkFatal(odp.AddDatapathInterface(datapathName, addDatapathInterface))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
Log.Println("Command line options:", options())
|
||||
Log.Println("Command line peers:", peers)
|
||||
|
||||
if protocolMinVersion < weave.ProtocolMinVersion || protocolMinVersion > weave.ProtocolMaxVersion {
|
||||
Log.Fatalf("--min-protocol-version must be in range [%d,%d]", weave.ProtocolMinVersion, weave.ProtocolMaxVersion)
|
||||
if protocolMinVersion < mesh.ProtocolMinVersion || protocolMinVersion > mesh.ProtocolMaxVersion {
|
||||
Log.Fatalf("--min-protocol-version must be in range [%d,%d]", mesh.ProtocolMinVersion, mesh.ProtocolMaxVersion)
|
||||
}
|
||||
config.ProtocolMinVersion = byte(protocolMinVersion)
|
||||
|
||||
var err error
|
||||
var fastDPOverlay weave.NetworkOverlay
|
||||
if datapathName != "" {
|
||||
// A datapath name implies that "Bridge" and "Overlay"
|
||||
// packet handling use fast datapath, although other
|
||||
// options can override that below. Even if both
|
||||
// things are overridden, we might need bridging on
|
||||
// the datapath.
|
||||
fastdp, err := weave.NewFastDatapath(weave.FastDatapathConfig{
|
||||
DatapathName: datapathName,
|
||||
Port: config.Port,
|
||||
})
|
||||
|
||||
checkFatal(err)
|
||||
networkConfig.Bridge = fastdp.Bridge()
|
||||
fastDPOverlay = fastdp.Overlay()
|
||||
}
|
||||
|
||||
if ifaceName != "" {
|
||||
config.Iface, err = weavenet.EnsureInterface(ifaceName)
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
}
|
||||
// -iface can coexist with -datapath, because
|
||||
// pcap-based packet capture is a bit more efficient
|
||||
// than capture via ODP misses, even when using an
|
||||
// ODP-based bridge. So when using weave encyption,
|
||||
// it's preferable to use -iface.
|
||||
var err error
|
||||
iface, err = weavenet.EnsureInterface(ifaceName)
|
||||
checkFatal(err)
|
||||
|
||||
if routerName == "" {
|
||||
if config.Iface == nil {
|
||||
Log.Fatal("Either an interface must be specified with --iface or a name with -name")
|
||||
}
|
||||
routerName = config.Iface.HardwareAddr.String()
|
||||
}
|
||||
name, err := weave.PeerNameFromUserInput(routerName)
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
|
||||
if nickName == "" {
|
||||
nickName, err = os.Hostname()
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
// bufsz flag is in MB
|
||||
networkConfig.Bridge, err = weave.NewPcap(iface, bufSzMB*1024*1024)
|
||||
checkFatal(err)
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
password = os.Getenv("WEAVE_PASSWORD")
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
Log.Println("Communication between peers is unencrypted.")
|
||||
} else {
|
||||
config.Password = []byte(password)
|
||||
Log.Println("Communication between peers is encrypted.")
|
||||
Log.Println("Communication between peers via untrusted networks is encrypted.")
|
||||
}
|
||||
|
||||
overlays := weave.NewOverlaySwitch()
|
||||
if fastDPOverlay != nil {
|
||||
overlays.Add("fastdp", fastDPOverlay)
|
||||
}
|
||||
sleeve := weave.NewSleeveOverlay(config.Port)
|
||||
overlays.Add("sleeve", sleeve)
|
||||
overlays.SetCompatOverlay(sleeve)
|
||||
|
||||
if routerName == "" {
|
||||
if iface == nil {
|
||||
Log.Fatal("Either an interface must be specified with --iface or a name with -name")
|
||||
}
|
||||
routerName = iface.HardwareAddr.String()
|
||||
}
|
||||
|
||||
name, err := mesh.PeerNameFromUserInput(routerName)
|
||||
checkFatal(err)
|
||||
|
||||
if nickName == "" {
|
||||
nickName, err = os.Hostname()
|
||||
checkFatal(err)
|
||||
}
|
||||
|
||||
if prof != "" {
|
||||
@@ -157,25 +235,46 @@ func main() {
|
||||
defer profile.Start(&p).Stop()
|
||||
}
|
||||
|
||||
config.BufSz = bufSzMB * 1024 * 1024
|
||||
config.LogFrame = logFrameFunc(pktdebug)
|
||||
config.PeerDiscovery = !noDiscovery
|
||||
|
||||
router := weave.NewRouter(config, name, nickName)
|
||||
Log.Println("Our name is", router.Ourself)
|
||||
|
||||
dockerCli, err := docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
Log.Fatal("Unable to start docker client: ", err)
|
||||
if pktdebug {
|
||||
networkConfig.PacketLogging = packetLogging{}
|
||||
} else {
|
||||
networkConfig.PacketLogging = nopPacketLogging{}
|
||||
}
|
||||
|
||||
if config.TrustedSubnets, err = parseTrustedSubnets(trustedSubnetStr); err != nil {
|
||||
Log.Fatal("Unable to parse trusted subnets: ", err)
|
||||
}
|
||||
|
||||
router := weave.NewNetworkRouter(config, networkConfig, name, nickName, overlays)
|
||||
Log.Println("Our name is", router.Ourself)
|
||||
|
||||
var dockerCli *docker.Client
|
||||
if dockerAPI != "" {
|
||||
dc, err := docker.NewClient(dockerAPI)
|
||||
if err != nil {
|
||||
Log.Fatal("Unable to start docker client: ", err)
|
||||
} else {
|
||||
Log.Info(dc.Info())
|
||||
}
|
||||
dockerCli = dc
|
||||
}
|
||||
observeContainers := func(o docker.ContainerObserver) {
|
||||
if dockerCli != nil {
|
||||
if err = dockerCli.AddObserver(o); err != nil {
|
||||
Log.Fatal("Unable to start watcher", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
isKnownPeer := func(name mesh.PeerName) bool {
|
||||
return router.Peers.Fetch(name) != nil
|
||||
}
|
||||
var allocator *ipam.Allocator
|
||||
var defaultSubnet address.CIDR
|
||||
if iprangeCIDR != "" {
|
||||
allocator, defaultSubnet = createAllocator(router, iprangeCIDR, ipsubnetCIDR, determineQuorum(peerCount, peers))
|
||||
if err = dockerCli.AddObserver(allocator); err != nil {
|
||||
Log.Fatal("Unable to start watcher", err)
|
||||
}
|
||||
allocator, defaultSubnet = createAllocator(router.Router, iprangeCIDR, ipsubnetCIDR, determineQuorum(peerCount, peers), isKnownPeer)
|
||||
observeContainers(allocator)
|
||||
} else if peerCount > 0 {
|
||||
Log.Fatal("--init-peer-count flag specified without --ipalloc-range")
|
||||
}
|
||||
@@ -185,11 +284,10 @@ func main() {
|
||||
dnsserver *nameserver.DNSServer
|
||||
)
|
||||
if !noDNS {
|
||||
ns = nameserver.New(router.Ourself.Peer.Name, router.Peers, dockerCli, dnsDomain)
|
||||
ns = nameserver.New(router.Ourself.Peer.Name, dnsDomain, isKnownPeer)
|
||||
router.Peers.OnGC(func(peer *mesh.Peer) { ns.PeerGone(peer.Name) })
|
||||
ns.SetGossip(router.NewGossip("nameserver", ns))
|
||||
if err = dockerCli.AddObserver(ns); err != nil {
|
||||
Log.Fatal("Unable to start watcher", err)
|
||||
}
|
||||
observeContainers(ns)
|
||||
ns.Start()
|
||||
defer ns.Stop()
|
||||
dnsserver, err = nameserver.NewDNSServer(ns, dnsDomain, dnsListenAddress,
|
||||
@@ -197,6 +295,11 @@ func main() {
|
||||
if err != nil {
|
||||
Log.Fatal("Unable to start dns server: ", err)
|
||||
}
|
||||
listenAddr := dnsListenAddress
|
||||
if dnsEffectiveListenAddress != "" {
|
||||
listenAddr = dnsEffectiveListenAddress
|
||||
}
|
||||
Log.Println("Listening for DNS queries on", listenAddr)
|
||||
dnsserver.ActivateAndServe()
|
||||
defer dnsserver.Stop()
|
||||
}
|
||||
@@ -215,11 +318,12 @@ func main() {
|
||||
allocator.HandleHTTP(muxRouter, defaultSubnet, dockerCli)
|
||||
}
|
||||
if ns != nil {
|
||||
ns.HandleHTTP(muxRouter)
|
||||
ns.HandleHTTP(muxRouter, dockerCli)
|
||||
}
|
||||
router.HandleHTTP(muxRouter)
|
||||
HandleHTTP(muxRouter, version, router, allocator, defaultSubnet, ns, dnsserver)
|
||||
http.Handle("/", muxRouter)
|
||||
Log.Println("Listening for HTTP control messages on", httpAddr)
|
||||
go listenAndServeHTTP(httpAddr, muxRouter)
|
||||
}
|
||||
|
||||
@@ -248,38 +352,35 @@ func canonicalName(f *mflag.Flag) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func logFrameFunc(debug bool) weave.LogFrameFunc {
|
||||
if !debug {
|
||||
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {}
|
||||
}
|
||||
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {
|
||||
h := fmt.Sprintf("%x", sha256.Sum256(frame))
|
||||
parts := []interface{}{prefix, len(frame), "bytes (", h, ")"}
|
||||
type packetLogging struct{}
|
||||
|
||||
if dec != nil {
|
||||
parts = append(parts, dec.Eth.SrcMAC, "->", dec.Eth.DstMAC)
|
||||
func (packetLogging) LogPacket(msg string, key weave.PacketKey) {
|
||||
Log.Println(msg, key.SrcMAC, "->", key.DstMAC)
|
||||
}
|
||||
|
||||
if dec.DF() {
|
||||
parts = append(parts, "(DF)")
|
||||
}
|
||||
}
|
||||
func (packetLogging) LogForwardPacket(msg string, key weave.ForwardPacketKey) {
|
||||
Log.Println(msg, key.SrcPeer, key.SrcMAC, "->", key.DstPeer, key.DstMAC)
|
||||
}
|
||||
|
||||
Log.Println(parts...)
|
||||
}
|
||||
type nopPacketLogging struct{}
|
||||
|
||||
func (nopPacketLogging) LogPacket(string, weave.PacketKey) {
|
||||
}
|
||||
|
||||
func (nopPacketLogging) LogForwardPacket(string, weave.ForwardPacketKey) {
|
||||
}
|
||||
|
||||
func parseAndCheckCIDR(cidrStr string) address.CIDR {
|
||||
_, cidr, err := address.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
checkFatal(err)
|
||||
|
||||
if cidr.Size() < ipam.MinSubnetSize {
|
||||
Log.Fatalf("Allocation range smaller than minimum size %d: %s", ipam.MinSubnetSize, cidrStr)
|
||||
}
|
||||
return cidr
|
||||
}
|
||||
|
||||
func createAllocator(router *weave.Router, ipRangeStr string, defaultSubnetStr string, quorum uint) (*ipam.Allocator, address.CIDR) {
|
||||
func createAllocator(router *mesh.Router, ipRangeStr string, defaultSubnetStr string, quorum uint, isKnownPeer func(mesh.PeerName) bool) (*ipam.Allocator, address.CIDR) {
|
||||
ipRange := parseAndCheckCIDR(ipRangeStr)
|
||||
defaultSubnet := ipRange
|
||||
if defaultSubnetStr != "" {
|
||||
@@ -288,7 +389,7 @@ func createAllocator(router *weave.Router, ipRangeStr string, defaultSubnetStr s
|
||||
Log.Fatalf("IP address allocation default subnet %s does not overlap with allocation range %s", defaultSubnet, ipRange)
|
||||
}
|
||||
}
|
||||
allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange.Range(), quorum)
|
||||
allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange.Range(), quorum, isKnownPeer)
|
||||
|
||||
allocator.SetInterfaces(router.NewGossip("IPallocation", allocator))
|
||||
allocator.Start()
|
||||
@@ -316,6 +417,24 @@ func determineQuorum(initPeerCountFlag int, peers []string) uint {
|
||||
return quorum
|
||||
}
|
||||
|
||||
func parseTrustedSubnets(trustedSubnetStr string) ([]*net.IPNet, error) {
|
||||
trustedSubnets := []*net.IPNet{}
|
||||
|
||||
if trustedSubnetStr == "" {
|
||||
return trustedSubnets, nil
|
||||
}
|
||||
|
||||
for _, subnetStr := range strings.Split(trustedSubnetStr, ",") {
|
||||
_, subnet, err := net.ParseCIDR(subnetStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustedSubnets = append(trustedSubnets, subnet)
|
||||
}
|
||||
|
||||
return trustedSubnets, nil
|
||||
}
|
||||
|
||||
func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
|
||||
protocol := "tcp"
|
||||
if strings.HasPrefix(httpAddr, "/") {
|
||||
@@ -331,3 +450,9 @@ func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
|
||||
Log.Fatal("Unable to create http server", err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkFatal(e error) {
|
||||
if e != nil {
|
||||
Log.Fatal(e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// +build !iface,!mcast
|
||||
|
||||
package main
|
||||
|
||||
func checkNetwork() error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// +build iface,!mcast
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
|
||||
func checkNetwork() error {
|
||||
_, err := weavenet.EnsureInterface("ethwe")
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// +build iface,mcast
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
|
||||
func checkNetwork() error {
|
||||
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
|
||||
return err
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -19,8 +17,7 @@ func main() {
|
||||
args = os.Args[1:]
|
||||
)
|
||||
|
||||
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
|
||||
checkErr(err)
|
||||
checkErr(checkNetwork())
|
||||
|
||||
if len(args) == 0 {
|
||||
checkErr(ErrNoCommandSpecified)
|
||||
|
||||
+45
-4
@@ -20,19 +20,35 @@ import (
|
||||
var (
|
||||
containerIDRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/([^/]*)/.*")
|
||||
weaveWaitEntrypoint = []string{"/w/w"}
|
||||
weaveEntrypoint = "/home/weave/weaver"
|
||||
weaveContainerName = "/weave"
|
||||
)
|
||||
|
||||
func callWeave(args ...string) ([]byte, []byte, error) {
|
||||
args = append([]string{"--local"}, args...)
|
||||
Log.Debug("Calling weave", args)
|
||||
cmd := exec.Command("./weave", args...)
|
||||
cmd.Env = []string{
|
||||
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"PROCFS=/hostproc",
|
||||
}
|
||||
if bridge := os.Getenv("DOCKER_BRIDGE"); bridge != "" {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_BRIDGE=%s", bridge))
|
||||
|
||||
propagateEnv := func(key string) {
|
||||
if val := os.Getenv(key); val != "" {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, val))
|
||||
}
|
||||
}
|
||||
|
||||
propagateEnv("DOCKER_BRIDGE")
|
||||
propagateEnv("DOCKER_HOST")
|
||||
|
||||
// Propogage WEAVE_DEBUG, to make debugging easier.
|
||||
propagateEnv("WEAVE_DEBUG")
|
||||
|
||||
// This prevents the code coverage contortions in our
|
||||
// integration test suite breaking things.
|
||||
propagateEnv("COVERAGE")
|
||||
|
||||
Log.Debug("Calling weave args: ", args, "env: ", cmd.Env)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
@@ -45,6 +61,7 @@ func unmarshalRequestBody(r *http.Request, target interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Log.Debugf("->requestBody: %s", body)
|
||||
if err := r.Body.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,6 +77,7 @@ func marshalRequestBody(r *http.Request, body interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Log.Debugf("<-requestBody: %s", newBody)
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
|
||||
r.ContentLength = int64(len(newBody))
|
||||
return nil
|
||||
@@ -70,6 +88,7 @@ func unmarshalResponseBody(r *http.Response, target interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Log.Debugf("->responseBody: %s", body)
|
||||
if err := r.Body.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -85,12 +104,12 @@ func marshalResponseBody(r *http.Response, body interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Log.Debugf("<-responseBody: %s", newBody)
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
|
||||
r.ContentLength = int64(len(newBody))
|
||||
// Stop it being chunked, because that hangs
|
||||
r.TransferEncoding = nil
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func inspectContainerInPath(client *docker.Client, path string) (*docker.Container, error) {
|
||||
@@ -137,3 +156,25 @@ func weaveContainerIPs(containerID string) (mac string, ips []net.IP, nets []*ne
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func addVolume(hostConfig jsonObject, source, target, mode string) error {
|
||||
configBinds, err := hostConfig.StringArray("Binds")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var binds []string
|
||||
for _, bind := range configBinds {
|
||||
s := strings.Split(bind, ":")
|
||||
if len(s) >= 2 && s[1] == target {
|
||||
continue
|
||||
}
|
||||
binds = append(binds, bind)
|
||||
}
|
||||
bind := source + ":" + target
|
||||
if mode != "" {
|
||||
bind += ":" + mode
|
||||
}
|
||||
hostConfig["Binds"] = append(binds, bind)
|
||||
return nil
|
||||
}
|
||||
@@ -2,16 +2,11 @@ package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
const MaxDockerHostname = 64
|
||||
@@ -22,12 +17,6 @@ var (
|
||||
|
||||
type createContainerInterceptor struct{ proxy *Proxy }
|
||||
|
||||
type createContainerRequestBody struct {
|
||||
*docker.Config
|
||||
HostConfig *docker.HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
|
||||
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
|
||||
}
|
||||
|
||||
// ErrNoSuchImage replaces docker.NoSuchImage, which does not contain the image
|
||||
// name, which in turn breaks docker clients post 1.7.0 since they expect the
|
||||
// image name to be present in errors.
|
||||
@@ -40,35 +29,54 @@ func (err *ErrNoSuchImage) Error() string {
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
container := createContainerRequestBody{}
|
||||
container := jsonObject{}
|
||||
if err := unmarshalRequestBody(r, &container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig); err != nil {
|
||||
hostConfig, err := container.Object("HostConfig")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkMode, err := hostConfig.String("NetworkMode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env, err := container.StringArray("Env")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cidrs, err := i.proxy.weaveCIDRs(networkMode, env); err != nil {
|
||||
Log.Infof("Leaving container alone because %s", err)
|
||||
} else {
|
||||
Log.Infof("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
|
||||
if container.HostConfig == nil {
|
||||
container.HostConfig = &docker.HostConfig{}
|
||||
}
|
||||
if container.Config == nil {
|
||||
container.Config = &docker.Config{}
|
||||
}
|
||||
i.addWeaveWaitVolume(container.HostConfig)
|
||||
if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
hostname := r.URL.Query().Get("name")
|
||||
if i.proxy.Config.HostnameFromLabel != "" {
|
||||
if labelValue, ok := container.Labels[i.proxy.Config.HostnameFromLabel]; ok {
|
||||
hostname = labelValue
|
||||
if i.proxy.NoMulticastRoute {
|
||||
if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement)
|
||||
if err := i.setWeaveDNS(&container, hostname); err != nil {
|
||||
if err := i.setWeaveWaitEntrypoint(container); err != nil {
|
||||
return err
|
||||
}
|
||||
hostname, err := i.containerHostname(r, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dnsDomain := i.proxy.getDNSDomain(); dnsDomain != "" {
|
||||
if err := i.setHostname(container, hostname, dnsDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := i.proxy.setWeaveDNS(hostConfig, hostname, dnsDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return marshalRequestBody(r, container)
|
||||
}
|
||||
@@ -76,105 +84,97 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) addWeaveWaitVolume(hostConfig *docker.HostConfig) {
|
||||
var binds []string
|
||||
for _, bind := range hostConfig.Binds {
|
||||
s := strings.Split(bind, ":")
|
||||
if len(s) >= 2 && s[1] == "/w" {
|
||||
continue
|
||||
}
|
||||
binds = append(binds, bind)
|
||||
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container jsonObject) error {
|
||||
var entrypoint []string
|
||||
entrypoint, err := container.StringArray("Entrypoint")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostConfig.Binds = append(binds, fmt.Sprintf("%s:/w:ro", i.proxy.weaveWaitVolume))
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Config) error {
|
||||
if len(container.Entrypoint) == 0 {
|
||||
image, err := i.proxy.client.InspectImage(container.Image)
|
||||
cmd, err := container.StringArray("Cmd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(entrypoint) == 0 {
|
||||
containerImage, err := container.String("Image")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
image, err := i.proxy.client.InspectImage(containerImage)
|
||||
if err == docker.ErrNoSuchImage {
|
||||
return &ErrNoSuchImage{container.Image}
|
||||
return &ErrNoSuchImage{containerImage}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(container.Cmd) == 0 {
|
||||
container.Cmd = image.Config.Cmd
|
||||
if len(cmd) == 0 {
|
||||
cmd = image.Config.Cmd
|
||||
container["Cmd"] = cmd
|
||||
}
|
||||
|
||||
if container.Entrypoint == nil {
|
||||
container.Entrypoint = image.Config.Entrypoint
|
||||
if entrypoint == nil {
|
||||
entrypoint = image.Config.Entrypoint
|
||||
container["Entrypoint"] = entrypoint
|
||||
}
|
||||
}
|
||||
|
||||
if len(container.Entrypoint) == 0 && len(container.Cmd) == 0 {
|
||||
if len(entrypoint) == 0 && len(cmd) == 0 {
|
||||
return ErrNoCommandSpecified
|
||||
}
|
||||
|
||||
if len(container.Entrypoint) == 0 || container.Entrypoint[0] != weaveWaitEntrypoint[0] {
|
||||
container.Entrypoint = append(weaveWaitEntrypoint, container.Entrypoint...)
|
||||
if len(entrypoint) == 0 || entrypoint[0] != weaveWaitEntrypoint[0] {
|
||||
container["Entrypoint"] = append(weaveWaitEntrypoint, entrypoint...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody, name string) error {
|
||||
if i.proxy.WithoutDNS {
|
||||
return nil
|
||||
func (i *createContainerInterceptor) setHostname(container jsonObject, name, dnsDomain string) error {
|
||||
hostname, err := container.String("Hostname")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dnsDomain, dnsRunning := i.getDNSDomain()
|
||||
if !(dnsRunning || i.proxy.WithDNS) {
|
||||
return nil
|
||||
}
|
||||
|
||||
container.HostConfig.DNS = append(container.HostConfig.DNS, i.proxy.dockerBridgeIP)
|
||||
|
||||
if container.Hostname == "" && name != "" {
|
||||
if hostname == "" && name != "" {
|
||||
// Strip trailing period because it's unusual to see it used on the end of a host name
|
||||
trimmedDNSDomain := strings.TrimSuffix(dnsDomain, ".")
|
||||
if len(name)+1+len(trimmedDNSDomain) > MaxDockerHostname {
|
||||
Log.Warningf("Container name [%s] too long to be used as hostname", name)
|
||||
} else {
|
||||
container.Hostname = name
|
||||
container.Domainname = trimmedDNSDomain
|
||||
}
|
||||
}
|
||||
|
||||
if len(container.HostConfig.DNSSearch) == 0 {
|
||||
if container.Hostname == "" {
|
||||
container.HostConfig.DNSSearch = []string{dnsDomain}
|
||||
} else {
|
||||
container.HostConfig.DNSSearch = []string{"."}
|
||||
container["Hostname"] = name
|
||||
container["Domainname"] = trimmedDNSDomain
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) getDNSDomain() (domain string, running bool) {
|
||||
domain = nameserver.DefaultDomain
|
||||
weaveContainer, err := i.proxy.client.InspectContainer("weave")
|
||||
if err != nil ||
|
||||
weaveContainer.NetworkSettings == nil ||
|
||||
weaveContainer.NetworkSettings.IPAddress == "" {
|
||||
return
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("http://%s:%d/domain", weaveContainer.NetworkSettings.IPAddress, router.HTTPPort)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return string(b), true
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) InterceptResponse(r *http.Response) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) containerHostname(r *http.Request, container jsonObject) (hostname string, err error) {
|
||||
hostname = r.URL.Query().Get("name")
|
||||
if i.proxy.Config.HostnameFromLabel != "" {
|
||||
hostname, err = i.hostnameFromLabel(hostname, container)
|
||||
}
|
||||
hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement)
|
||||
return
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) hostnameFromLabel(hostname string, container jsonObject) (string, error) {
|
||||
labels, err := container.Object("Labels")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
label, err := labels.String(i.proxy.Config.HostnameFromLabel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if label == "" {
|
||||
return hostname, nil
|
||||
}
|
||||
|
||||
return label, nil
|
||||
}
|
||||
@@ -4,14 +4,13 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type createExecInterceptor struct{ proxy *Proxy }
|
||||
|
||||
func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
|
||||
options := docker.CreateExecOptions{}
|
||||
options := jsonObject{}
|
||||
if err := unmarshalRequestBody(r, &options); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -25,14 +24,19 @@ func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
|
||||
cidrs, err := i.proxy.weaveCIDRs(container.HostConfig.NetworkMode, container.Config.Env)
|
||||
if err != nil {
|
||||
Log.Infof("Leaving container %s alone because %s", container.ID, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd, err := options.StringArray("Cmd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Log.Infof("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
|
||||
options.Cmd = append(weaveWaitEntrypoint, options.Cmd...)
|
||||
options["Cmd"] = append(weaveWaitEntrypoint, cmd...)
|
||||
|
||||
return marshalRequestBody(r, options)
|
||||
}
|
||||
|
||||
@@ -17,35 +17,14 @@ func (i *inspectContainerInterceptor) InterceptResponse(r *http.Response) error
|
||||
return nil
|
||||
}
|
||||
|
||||
container := map[string]interface{}{}
|
||||
container := jsonObject{}
|
||||
if err := unmarshalResponseBody(r, &container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateContainerNetworkSettings(container); err != nil {
|
||||
if err := i.proxy.updateContainerNetworkSettings(container); err != nil {
|
||||
Log.Warningf("Inspecting container %s failed: %s", container["Id"], err)
|
||||
}
|
||||
|
||||
return marshalResponseBody(r, container)
|
||||
}
|
||||
|
||||
func updateContainerNetworkSettings(container map[string]interface{}) error {
|
||||
containerID, err := lookupString(container, "Id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mac, ips, nets, err := weaveContainerIPs(containerID)
|
||||
if err != nil || len(ips) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
networkSettings, err := lookupObject(container, "NetworkSettings")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
networkSettings["MacAddress"] = mac
|
||||
networkSettings["IPAddress"] = ips[0].String()
|
||||
networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size()
|
||||
return nil
|
||||
}
|
||||
@@ -17,17 +17,17 @@ func (i *inspectExecInterceptor) InterceptResponse(r *http.Response) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
exec := map[string]interface{}{}
|
||||
exec := jsonObject{}
|
||||
if err := unmarshalResponseBody(r, &exec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container, err := lookupObject(exec, "Container")
|
||||
container, err := exec.Object("Container")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateContainerNetworkSettings(container); err != nil {
|
||||
if err := i.proxy.updateContainerNetworkSettings(container); err != nil {
|
||||
Log.Warningf("Inspecting exec %s failed: %s", exec["Id"], err)
|
||||
}
|
||||
|
||||
|
||||
+35
-9
@@ -13,12 +13,13 @@ func (e *UnmarshalWrongTypeError) Error() string {
|
||||
return fmt.Sprintf("Wrong type for %s field, expected %s, but got %T", e.Field, e.Expected, e.Got)
|
||||
}
|
||||
|
||||
// For parsing json objects. Look up (or create) a child object in the given map.
|
||||
func lookupObject(m map[string]interface{}, key string) (map[string]interface{}, error) {
|
||||
iface, ok := m[key]
|
||||
type jsonObject map[string]interface{}
|
||||
|
||||
func (j jsonObject) Object(key string) (jsonObject, error) {
|
||||
iface, ok := j[key]
|
||||
if !ok || iface == nil {
|
||||
result := map[string]interface{}{}
|
||||
m[key] = result
|
||||
result := jsonObject{}
|
||||
j[key] = result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -27,12 +28,11 @@ func lookupObject(m map[string]interface{}, key string) (map[string]interface{},
|
||||
return nil, &UnmarshalWrongTypeError{key, "object", iface}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return jsonObject(result), nil
|
||||
}
|
||||
|
||||
// For parsing json objects. Look up (or create) a string in the given map.
|
||||
func lookupString(m map[string]interface{}, key string) (string, error) {
|
||||
iface, ok := m[key]
|
||||
func (j jsonObject) String(key string) (string, error) {
|
||||
iface, ok := j[key]
|
||||
if !ok || iface == nil {
|
||||
return "", nil
|
||||
}
|
||||
@@ -44,3 +44,29 @@ func lookupString(m map[string]interface{}, key string) (string, error) {
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (j jsonObject) StringArray(key string) ([]string, error) {
|
||||
iface, ok := j[key]
|
||||
if !ok || iface == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch o := iface.(type) {
|
||||
case string:
|
||||
return []string{o}, nil
|
||||
case []string:
|
||||
return o, nil
|
||||
case []interface{}:
|
||||
result := []string{}
|
||||
for _, s := range o {
|
||||
if s, ok := s.(string); ok {
|
||||
result = append(result, s)
|
||||
} else {
|
||||
return nil, &UnmarshalWrongTypeError{key, "string or array of strings", iface}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return nil, &UnmarshalWrongTypeError{key, "string or array of strings", iface}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLookupObject(t *testing.T) {
|
||||
tests := []struct {
|
||||
root jsonObject
|
||||
key string
|
||||
result jsonObject
|
||||
err error
|
||||
}{
|
||||
{
|
||||
jsonObject{},
|
||||
"a",
|
||||
jsonObject{},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
jsonObject{"a": map[string]interface{}{"b": int(1)}},
|
||||
"a",
|
||||
jsonObject{"b": int(1)},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
jsonObject{"nonObject": int(1)},
|
||||
"nonObject",
|
||||
nil,
|
||||
&UnmarshalWrongTypeError{Field: "nonObject", Expected: "object", Got: 1},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
gotResult, gotErr := test.root.Object(test.key)
|
||||
msg := fmt.Sprintf("%q.Object(%q) => %q, %q", test.root, test.key, gotResult, gotErr)
|
||||
assert.Equal(t, test.result, gotResult, msg)
|
||||
assert.Equal(t, test.err, gotErr, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupString(t *testing.T) {
|
||||
tests := []struct {
|
||||
root jsonObject
|
||||
key string
|
||||
result string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
jsonObject{},
|
||||
"a",
|
||||
"",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
jsonObject{"nonString": int(1)},
|
||||
"nonString",
|
||||
"",
|
||||
&UnmarshalWrongTypeError{Field: "nonString", Expected: "string", Got: 1},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
gotResult, gotErr := test.root.String(test.key)
|
||||
msg := fmt.Sprintf("%q.String(%q) => %q, %q", test.root, test.key, gotResult, gotErr)
|
||||
assert.Equal(t, test.result, gotResult, msg)
|
||||
assert.Equal(t, test.err, gotErr, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupStringArray(t *testing.T) {
|
||||
tests := []struct {
|
||||
root jsonObject
|
||||
key string
|
||||
result []string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
jsonObject{},
|
||||
"a",
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
jsonObject{"a": []string{"foo"}},
|
||||
"a",
|
||||
[]string{"foo"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
jsonObject{"a": []string{}},
|
||||
"a",
|
||||
[]string{},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
jsonObject{"a": "foo"},
|
||||
"a",
|
||||
[]string{"foo"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
jsonObject{"int": 5},
|
||||
"int",
|
||||
nil,
|
||||
&UnmarshalWrongTypeError{Field: "int", Expected: "string or array of strings", Got: 5},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
gotResult, gotErr := test.root.StringArray(test.key)
|
||||
msg := fmt.Sprintf("%q.String(%q) => %q, %q", test.root, test.key, gotResult, gotErr)
|
||||
assert.Equal(t, test.result, gotResult, msg)
|
||||
assert.Equal(t, test.err, gotErr, msg)
|
||||
}
|
||||
}
|
||||
+417
-38
@@ -1,26 +1,32 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
weavedocker "github.com/weaveworks/weave/common/docker"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCaFile = "ca.pem"
|
||||
defaultKeyFile = "key.pem"
|
||||
defaultCertFile = "cert.pem"
|
||||
dockerSock = "/var/run/docker.sock"
|
||||
dockerSockUnix = "unix://" + dockerSock
|
||||
|
||||
weaveSock = "/var/run/weave/weave.sock"
|
||||
weaveSockUnix = "unix://" + weaveSock
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -42,41 +48,56 @@ type Config struct {
|
||||
HostnameFromLabel string
|
||||
HostnameMatch string
|
||||
HostnameReplacement string
|
||||
Image string
|
||||
ListenAddrs []string
|
||||
RewriteInspect bool
|
||||
NoDefaultIPAM bool
|
||||
NoRewriteHosts bool
|
||||
TLSConfig TLSConfig
|
||||
Version string
|
||||
WithDNS bool
|
||||
WithoutDNS bool
|
||||
NoMulticastRoute bool
|
||||
DockerHost string
|
||||
}
|
||||
|
||||
type wait struct {
|
||||
ident string
|
||||
ch chan error
|
||||
done bool
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
sync.Mutex
|
||||
Config
|
||||
client *docker.Client
|
||||
dockerBridgeIP string
|
||||
hostnameMatchRegexp *regexp.Regexp
|
||||
weaveWaitVolume string
|
||||
client *docker.Client
|
||||
dockerBridgeIP string
|
||||
hostnameMatchRegexp *regexp.Regexp
|
||||
weaveWaitVolume string
|
||||
weaveWaitNoopVolume string
|
||||
weaveWaitNomcastVolume string
|
||||
normalisedAddrs []string
|
||||
waiters map[*http.Request]*wait
|
||||
}
|
||||
|
||||
func NewProxy(c Config) (*Proxy, error) {
|
||||
p := &Proxy{Config: c}
|
||||
p := &Proxy{Config: c, waiters: make(map[*http.Request]*wait)}
|
||||
|
||||
if err := p.TLSConfig.loadCerts(); err != nil {
|
||||
if err := p.TLSConfig.LoadCerts(); err != nil {
|
||||
Log.Fatalf("Could not configure tls for proxy: %s", err)
|
||||
}
|
||||
|
||||
// We pin the protocol version to 1.15 (which corresponds to
|
||||
// Docker 1.3.x; the earliest version supported by weave) in order
|
||||
// We pin the protocol version to 1.18 (which corresponds to
|
||||
// Docker 1.6.x; the earliest version supported by weave) in order
|
||||
// to insulate ourselves from breaking changes to the API, as
|
||||
// happened in 1.20 (Docker 1.8.0) when the presentation of
|
||||
// volumes changed in `inspect`.
|
||||
client, err := docker.NewVersionedClient(dockerSockUnix, "1.15")
|
||||
client, err := weavedocker.NewVersionedClient(c.DockerHost, "1.18")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
Log.Info(client.Info())
|
||||
}
|
||||
p.client = client
|
||||
p.client = client.Client
|
||||
|
||||
if !p.WithoutDNS {
|
||||
dockerBridgeIP, stderr, err := callWeave("docker-bridge-ip")
|
||||
@@ -92,34 +113,72 @@ func NewProxy(c Config) (*Proxy, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = p.findWeaveWaitVolume(); err != nil {
|
||||
if err = p.findWeaveWaitVolumes(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.AddObserver(p)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) Dial() (net.Conn, error) {
|
||||
return net.Dial("unix", dockerSock)
|
||||
func (proxy *Proxy) AttachExistingContainers() {
|
||||
containers, _ := proxy.client.ListContainers(docker.ListContainersOptions{})
|
||||
for _, c := range containers {
|
||||
container, err := proxy.client.InspectContainer(c.ID)
|
||||
if err != nil {
|
||||
if _, ok := err.(*docker.NoSuchContainer); !ok {
|
||||
Log.Warningf("unable to attach existing container %s since inspecting it failed: %v", c.ID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if containerShouldAttach(container) && (container.State.Running || container.State.Paused) {
|
||||
proxy.attach(container, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *Proxy) findWeaveWaitVolume() error {
|
||||
func (proxy *Proxy) Dial() (net.Conn, error) {
|
||||
proto := "tcp"
|
||||
addr := proxy.Config.DockerHost
|
||||
switch {
|
||||
case strings.HasPrefix(addr, "unix://"):
|
||||
proto = "unix"
|
||||
addr = strings.TrimPrefix(addr, "unix://")
|
||||
case strings.HasPrefix(addr, "tcp://"):
|
||||
addr = strings.TrimPrefix(addr, "tcp://")
|
||||
}
|
||||
return net.Dial(proto, addr)
|
||||
}
|
||||
|
||||
func (proxy *Proxy) findWeaveWaitVolumes() error {
|
||||
var err error
|
||||
if proxy.weaveWaitVolume, err = proxy.findVolume("/w"); err != nil {
|
||||
return err
|
||||
}
|
||||
if proxy.weaveWaitNoopVolume, err = proxy.findVolume("/w-noop"); err != nil {
|
||||
return err
|
||||
}
|
||||
proxy.weaveWaitNomcastVolume, err = proxy.findVolume("/w-nomcast")
|
||||
return err
|
||||
}
|
||||
|
||||
func (proxy *Proxy) findVolume(v string) (string, error) {
|
||||
container, err := proxy.client.InspectContainer("weaveproxy")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not find the weavewait volume: %s", err)
|
||||
return "", fmt.Errorf("Could not find the weavewait volume: %s", err)
|
||||
}
|
||||
|
||||
if container.Volumes == nil {
|
||||
return fmt.Errorf("Could not find the weavewait volume")
|
||||
return "", fmt.Errorf("Could not find the weavewait volume")
|
||||
}
|
||||
|
||||
volume, ok := container.Volumes["/w"]
|
||||
volume, ok := container.Volumes[v]
|
||||
if !ok {
|
||||
return fmt.Errorf("Could not find the weavewait volume")
|
||||
return "", fmt.Errorf("Could not find the weavewait volume")
|
||||
}
|
||||
|
||||
proxy.weaveWaitVolume = volume
|
||||
return nil
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -143,22 +202,44 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
proxy.Intercept(i, w, r)
|
||||
}
|
||||
|
||||
func (proxy *Proxy) ListenAndServe() {
|
||||
func (proxy *Proxy) Listen() []net.Listener {
|
||||
listeners := []net.Listener{}
|
||||
addrs := []string{}
|
||||
proxy.normalisedAddrs = []string{}
|
||||
unixAddrs := []string{}
|
||||
for _, addr := range proxy.ListenAddrs {
|
||||
if strings.HasPrefix(addr, "unix://") || strings.HasPrefix(addr, "/") {
|
||||
unixAddrs = append(unixAddrs, addr)
|
||||
continue
|
||||
}
|
||||
listener, normalisedAddr, err := proxy.listen(addr)
|
||||
if err != nil {
|
||||
Log.Fatalf("Cannot listen on %s: %s", addr, err)
|
||||
}
|
||||
listeners = append(listeners, listener)
|
||||
addrs = append(addrs, normalisedAddr)
|
||||
proxy.normalisedAddrs = append(proxy.normalisedAddrs, normalisedAddr)
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
if len(unixAddrs) > 0 {
|
||||
listener, _, err := proxy.listen(weaveSockUnix)
|
||||
if err != nil {
|
||||
Log.Fatalf("Cannot listen on %s: %s", weaveSockUnix, err)
|
||||
}
|
||||
listeners = append(listeners, listener)
|
||||
|
||||
if err := proxy.symlink(unixAddrs); err != nil {
|
||||
Log.Fatalf("Cannot listen on unix sockets: %s", err)
|
||||
}
|
||||
|
||||
proxy.normalisedAddrs = append(proxy.normalisedAddrs, weaveSockUnix)
|
||||
}
|
||||
|
||||
for _, addr := range proxy.normalisedAddrs {
|
||||
Log.Infoln("proxy listening on", addr)
|
||||
}
|
||||
return listeners
|
||||
}
|
||||
|
||||
func (proxy *Proxy) Serve(listeners []net.Listener) {
|
||||
errs := make(chan error)
|
||||
for _, listener := range listeners {
|
||||
go func(listener net.Listener) {
|
||||
@@ -173,6 +254,26 @@ func (proxy *Proxy) ListenAndServe() {
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *Proxy) ListenAndServeStatus(socket string) {
|
||||
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
||||
Log.Fatalf("Error removing existing status socket: %s", err)
|
||||
}
|
||||
listener, err := net.Listen("unix", socket)
|
||||
if err != nil {
|
||||
Log.Fatalf("ListenAndServeStatus failed: %s", err)
|
||||
}
|
||||
handler := http.HandlerFunc(proxy.StatusHTTP)
|
||||
if err := (&http.Server{Handler: handler}).Serve(listener); err != nil {
|
||||
Log.Fatalf("ListenAndServeStatus failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *Proxy) StatusHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
for _, addr := range proxy.normalisedAddrs {
|
||||
fmt.Fprintln(w, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func copyOwnerAndPermissions(from, to string) error {
|
||||
stat, err := os.Stat(from)
|
||||
if err != nil {
|
||||
@@ -215,7 +316,7 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if proxy.TLSConfig.enabled() {
|
||||
if proxy.TLSConfig.IsEnabled() {
|
||||
listener = tls.NewListener(listener, proxy.TLSConfig.Config)
|
||||
}
|
||||
|
||||
@@ -228,8 +329,10 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err = copyOwnerAndPermissions(dockerSock, addr); err != nil {
|
||||
return nil, "", err
|
||||
if strings.HasPrefix(proxy.Config.DockerHost, "unix://") {
|
||||
if err = copyOwnerAndPermissions(strings.TrimPrefix(proxy.Config.DockerHost, "unix://"), addr); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -239,15 +342,143 @@ func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
|
||||
return listener, fmt.Sprintf("%s://%s", proto, addr), nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *docker.HostConfig) ([]string, error) {
|
||||
netMode := ""
|
||||
if hostConfig != nil {
|
||||
netMode = hostConfig.NetworkMode
|
||||
// weavedocker.ContainerObserver interface
|
||||
func (proxy *Proxy) ContainerStarted(ident string) {
|
||||
container, err := proxy.client.InspectContainer(ident)
|
||||
if err == nil {
|
||||
if containerShouldAttach(container) {
|
||||
err = proxy.attach(container, true)
|
||||
} else if containerIsWeaveRouter(container) {
|
||||
err = proxy.attachRouter(container)
|
||||
}
|
||||
}
|
||||
if netMode == "host" || strings.HasPrefix(netMode, "container:") {
|
||||
return nil, fmt.Errorf("the container was created with the '--net=%s'", netMode)
|
||||
if _, ok := err.(*docker.NoSuchContainer); err != nil && !ok {
|
||||
Log.Warningf("unable to attach new container %s since inspecting it failed: %v", ident, err)
|
||||
}
|
||||
for _, e := range config.Env {
|
||||
proxy.notifyWaiters(ident, err)
|
||||
}
|
||||
|
||||
func containerShouldAttach(container *docker.Container) bool {
|
||||
return len(container.Config.Entrypoint) > 0 && container.Config.Entrypoint[0] == weaveWaitEntrypoint[0]
|
||||
}
|
||||
|
||||
func containerIsWeaveRouter(container *docker.Container) bool {
|
||||
return container.Name == weaveContainerName &&
|
||||
len(container.Config.Entrypoint) > 0 && container.Config.Entrypoint[0] == weaveEntrypoint
|
||||
}
|
||||
|
||||
func (proxy *Proxy) createWait(r *http.Request, ident string) {
|
||||
proxy.Lock()
|
||||
proxy.waiters[r] = &wait{ident: ident, ch: make(chan error, 1)}
|
||||
proxy.Unlock()
|
||||
}
|
||||
|
||||
func (proxy *Proxy) removeWait(r *http.Request) {
|
||||
proxy.Lock()
|
||||
delete(proxy.waiters, r)
|
||||
proxy.Unlock()
|
||||
}
|
||||
|
||||
func (proxy *Proxy) notifyWaiters(ident string, err error) {
|
||||
proxy.Lock()
|
||||
for _, wait := range proxy.waiters {
|
||||
if ident == wait.ident && !wait.done {
|
||||
wait.ch <- err
|
||||
close(wait.ch)
|
||||
wait.done = true
|
||||
}
|
||||
}
|
||||
proxy.Unlock()
|
||||
}
|
||||
|
||||
func (proxy *Proxy) waitForStart(r *http.Request) error {
|
||||
var ch chan error
|
||||
proxy.Lock()
|
||||
wait, found := proxy.waiters[r]
|
||||
if found {
|
||||
ch = wait.ch
|
||||
}
|
||||
proxy.Unlock()
|
||||
if ch != nil {
|
||||
Log.Debugf("Wait for start of container %s", wait.ident)
|
||||
return <-ch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If some other operation is waiting for a container to start, join in the wait
|
||||
func (proxy *Proxy) waitForStartByIdent(ident string) error {
|
||||
var ch chan error
|
||||
proxy.Lock()
|
||||
for _, wait := range proxy.waiters {
|
||||
if ident == wait.ident && !wait.done {
|
||||
ch = wait.ch
|
||||
break
|
||||
}
|
||||
}
|
||||
proxy.Unlock()
|
||||
if ch != nil {
|
||||
Log.Debugf("Wait for start of container %s", ident)
|
||||
return <-ch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) ContainerDied(ident string) {
|
||||
}
|
||||
|
||||
func (proxy *Proxy) attach(container *docker.Container, orDie bool) error {
|
||||
cidrs, err := proxy.weaveCIDRs(container.HostConfig.NetworkMode, container.Config.Env)
|
||||
if err != nil {
|
||||
Log.Infof("Leaving container %s alone because %s", container.ID, err)
|
||||
return nil
|
||||
}
|
||||
Log.Infof("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
|
||||
args := []string{"attach"}
|
||||
args = append(args, cidrs...)
|
||||
if !proxy.NoRewriteHosts {
|
||||
args = append(args, "--rewrite-hosts")
|
||||
|
||||
if container.HostConfig != nil {
|
||||
for _, eh := range container.HostConfig.ExtraHosts {
|
||||
args = append(args, fmt.Sprintf("--add-host=%s", eh))
|
||||
}
|
||||
}
|
||||
}
|
||||
if proxy.NoMulticastRoute {
|
||||
args = append(args, "--no-multicast-route")
|
||||
}
|
||||
if orDie {
|
||||
args = append(args, "--or-die")
|
||||
}
|
||||
args = append(args, container.ID)
|
||||
if _, stderr, err := callWeave(args...); err != nil {
|
||||
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
|
||||
return errors.New(string(stderr))
|
||||
} else if len(stderr) > 0 {
|
||||
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) attachRouter(container *docker.Container) error {
|
||||
Log.Infof("Attaching weave router container: %s", container.ID)
|
||||
args := []string{"attach-router"}
|
||||
if _, stderr, err := callWeave(args...); err != nil {
|
||||
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
|
||||
return errors.New(string(stderr))
|
||||
} else if len(stderr) > 0 {
|
||||
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) weaveCIDRs(networkMode string, env []string) ([]string, error) {
|
||||
if networkMode == "host" || strings.HasPrefix(networkMode, "container:") {
|
||||
return nil, fmt.Errorf("the container has '--net=%s'", networkMode)
|
||||
}
|
||||
for _, e := range env {
|
||||
if strings.HasPrefix(e, "WEAVE_CIDR=") {
|
||||
if e[11:] == "none" {
|
||||
return nil, ErrWeaveCIDRNone
|
||||
@@ -260,3 +491,151 @@ func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *dock
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) setWeaveDNS(hostConfig jsonObject, hostname, dnsDomain string) error {
|
||||
dns, err := hostConfig.StringArray("Dns")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostConfig["Dns"] = append(dns, proxy.dockerBridgeIP)
|
||||
|
||||
dnsSearch, err := hostConfig.StringArray("DnsSearch")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dnsSearch) == 0 {
|
||||
if hostname == "" {
|
||||
hostConfig["DnsSearch"] = []string{dnsDomain}
|
||||
} else {
|
||||
hostConfig["DnsSearch"] = []string{"."}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) getDNSDomain() (domain string) {
|
||||
if proxy.WithoutDNS {
|
||||
return ""
|
||||
}
|
||||
|
||||
weaveContainer, err := proxy.client.InspectContainer("weave")
|
||||
var weaveIP string
|
||||
if err == nil && weaveContainer.NetworkSettings != nil {
|
||||
weaveIP = weaveContainer.NetworkSettings.IPAddress
|
||||
}
|
||||
if weaveIP == "" {
|
||||
weaveIP = "127.0.0.1"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("http://%s:6784/domain", weaveIP)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (proxy *Proxy) updateContainerNetworkSettings(container jsonObject) error {
|
||||
containerID, err := container.String("Id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := proxy.waitForStartByIdent(containerID); err != nil {
|
||||
return err
|
||||
}
|
||||
mac, ips, nets, err := weaveContainerIPs(containerID)
|
||||
if err != nil || len(ips) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
networkSettings, err := container.Object("NetworkSettings")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
networkSettings["MacAddress"] = mac
|
||||
networkSettings["IPAddress"] = ips[0].String()
|
||||
networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) symlink(unixAddrs []string) (err error) {
|
||||
var container *docker.Container
|
||||
binds := []string{"/var/run/weave:/var/run/weave"}
|
||||
froms := []string{}
|
||||
for _, addr := range unixAddrs {
|
||||
if addr == weaveSockUnix {
|
||||
continue
|
||||
}
|
||||
from := strings.TrimPrefix(addr, "unix://")
|
||||
dir := filepath.Dir(from)
|
||||
binds = append(binds, dir+":"+filepath.Join("/host", dir))
|
||||
froms = append(froms, filepath.Join("/host", from))
|
||||
proxy.normalisedAddrs = append(proxy.normalisedAddrs, addr)
|
||||
}
|
||||
if len(froms) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
env := []string{
|
||||
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
}
|
||||
|
||||
if val := os.Getenv("WEAVE_DEBUG"); val != "" {
|
||||
env = append(env, fmt.Sprintf("%s=%s", "WEAVE_DEBUG", val))
|
||||
}
|
||||
|
||||
container, err = proxy.client.CreateContainer(docker.CreateContainerOptions{
|
||||
Config: &docker.Config{
|
||||
Image: proxy.Image,
|
||||
Entrypoint: []string{"/home/weave/symlink", weaveSock},
|
||||
Cmd: froms,
|
||||
Env: env,
|
||||
},
|
||||
HostConfig: &docker.HostConfig{Binds: binds},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err2 := proxy.client.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID})
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
}()
|
||||
|
||||
err = proxy.client.StartContainer(container.ID, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = proxy.client.AttachToContainer(docker.AttachToContainerOptions{
|
||||
Container: container.ID,
|
||||
ErrorStream: &buf,
|
||||
Logs: true,
|
||||
Stderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var rc int
|
||||
rc, err = proxy.client.WaitContainer(container.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if rc != 0 {
|
||||
err = errors.New(buf.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,47 +1,76 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type startContainerInterceptor struct{ proxy *Proxy }
|
||||
|
||||
func (i *startContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
|
||||
if r.StatusCode < 200 || r.StatusCode >= 300 { // Docker didn't do the start
|
||||
return nil
|
||||
}
|
||||
|
||||
container, err := inspectContainerInPath(i.proxy.client, r.Request.URL.Path)
|
||||
container, err := inspectContainerInPath(i.proxy.client, r.URL.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
|
||||
if err != nil {
|
||||
Log.Infof("Leaving container %s alone because %s", container.ID, err)
|
||||
return nil
|
||||
}
|
||||
Log.Infof("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
|
||||
args := []string{"attach"}
|
||||
args = append(args, cidrs...)
|
||||
if !i.proxy.NoRewriteHosts {
|
||||
args = append(args, "--rewrite-hosts")
|
||||
}
|
||||
args = append(args, "--or-die", container.ID)
|
||||
if _, stderr, err := callWeave(args...); err != nil {
|
||||
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
|
||||
return errors.New(string(stderr))
|
||||
} else if len(stderr) > 0 {
|
||||
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
|
||||
}
|
||||
// If the client has sent some JSON which might be a HostConfig, add our
|
||||
// parameters back into it, otherwise Docker will consider them overwritten
|
||||
if containerShouldAttach(container) && r.Header.Get("Content-Type") == "application/json" {
|
||||
params := map[string]interface{}{}
|
||||
if err := unmarshalRequestBody(r, ¶ms); err != nil {
|
||||
return err
|
||||
}
|
||||
// HostConfig can be sent either as a struct named HostConfig, or unnamed at top level
|
||||
var hostConfig map[string]interface{}
|
||||
if subParam, found := params["HostConfig"]; found {
|
||||
if typecast, ok := subParam.(map[string]interface{}); ok {
|
||||
hostConfig = typecast
|
||||
}
|
||||
// We can't reliably detect what Docker will see as a top-level HostConfig,
|
||||
// so just assume any parameter at top level indicates there is one.
|
||||
} else if len(params) > 0 {
|
||||
hostConfig = params
|
||||
}
|
||||
if hostConfig != nil {
|
||||
networkMode, err := jsonObject(hostConfig).String("NetworkMode")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(networkMode, "container:") || networkMode == "host" {
|
||||
if err := addVolume(hostConfig, i.proxy.weaveWaitNoopVolume, "/w", "ro"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if i.proxy.NoMulticastRoute {
|
||||
if err := addVolume(hostConfig, i.proxy.weaveWaitNomcastVolume, "/w", "ro"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := addVolume(hostConfig, i.proxy.weaveWaitVolume, "/w", "ro"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if dnsDomain := i.proxy.getDNSDomain(); dnsDomain != "" {
|
||||
if err := i.proxy.setWeaveDNS(hostConfig, container.Config.Hostname, dnsDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note we marshal the original top-level dictionary to avoid disturbing anything else
|
||||
if err := marshalRequestBody(r, params); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
i.proxy.createWait(r, container.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
|
||||
defer i.proxy.removeWait(r.Request)
|
||||
if r.StatusCode < 200 || r.StatusCode >= 300 { // Docker didn't do the start
|
||||
return nil
|
||||
}
|
||||
return i.proxy.waitForStart(r.Request)
|
||||
}
|
||||
+5
-3
@@ -17,15 +17,17 @@ type TLSConfig struct {
|
||||
*tls.Config
|
||||
}
|
||||
|
||||
func (c *TLSConfig) enabled() bool {
|
||||
// IsEnabled returns true if TLS is enable, according to the config.
|
||||
func (c *TLSConfig) IsEnabled() bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return c.Enabled || c.Verify
|
||||
}
|
||||
|
||||
func (c *TLSConfig) loadCerts() error {
|
||||
if !c.enabled() {
|
||||
// LoadCerts loads the certificates into c.Config, if TLS is enabled.
|
||||
func (c *TLSConfig) LoadCerts() error {
|
||||
if !c.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package router
|
||||
|
||||
// Interface to packet handling on the local virtual bridge
|
||||
type Bridge interface {
|
||||
// Inject a packet to be delivered locally
|
||||
InjectPacket(PacketKey) FlowOp
|
||||
|
||||
// Start consuming packets from the bridge. Injected packets
|
||||
// should not be included.
|
||||
StartConsumingPackets(BridgeConsumer) error
|
||||
|
||||
String() string
|
||||
Stats() map[string]int
|
||||
}
|
||||
|
||||
// A function that determines how to handle locally captured packets.
|
||||
type BridgeConsumer func(PacketKey) FlowOp
|
||||
|
||||
type NullBridge struct{}
|
||||
|
||||
func (NullBridge) InjectPacket(PacketKey) FlowOp {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NullBridge) StartConsumingPackets(BridgeConsumer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NullBridge) String() string {
|
||||
return "<no bridge networking>"
|
||||
}
|
||||
|
||||
func (NullBridge) Stats() map[string]int {
|
||||
return nil
|
||||
}
|
||||
@@ -1,589 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Connection interface {
|
||||
Local() *Peer
|
||||
Remote() *Peer
|
||||
RemoteTCPAddr() string
|
||||
Outbound() bool
|
||||
Established() bool
|
||||
BreakTie(Connection) ConnectionTieBreak
|
||||
Shutdown(error)
|
||||
Log(args ...interface{})
|
||||
}
|
||||
|
||||
type ConnectionTieBreak int
|
||||
|
||||
const (
|
||||
TieBreakWon ConnectionTieBreak = iota
|
||||
TieBreakLost
|
||||
TieBreakTied
|
||||
)
|
||||
|
||||
var ErrConnectToSelf = errors.New("Cannot connect to ourself")
|
||||
|
||||
type RemoteConnection struct {
|
||||
local *Peer
|
||||
remote *Peer
|
||||
remoteTCPAddr string
|
||||
outbound bool
|
||||
established bool
|
||||
}
|
||||
|
||||
type LocalConnection struct {
|
||||
sync.RWMutex
|
||||
RemoteConnection
|
||||
TCPConn *net.TCPConn
|
||||
version byte
|
||||
tcpSender TCPSender
|
||||
remoteUDPAddr *net.UDPAddr
|
||||
receivedHeartbeat bool
|
||||
stackFrag bool
|
||||
effectivePMTU int
|
||||
SessionKey *[32]byte
|
||||
heartbeatTCP *time.Ticker
|
||||
heartbeatTimeout *time.Timer
|
||||
heartbeatFrame []byte
|
||||
heartbeat *time.Ticker
|
||||
fragTest *time.Ticker
|
||||
forwarder *Forwarder
|
||||
forwarderDF *ForwarderDF
|
||||
Decryptor Decryptor
|
||||
Router *Router
|
||||
uid uint64
|
||||
actionChan chan<- ConnectionAction
|
||||
finished <-chan struct{} // closed to signal that actorLoop has finished
|
||||
}
|
||||
|
||||
type ConnectionAction func() error
|
||||
|
||||
func NewRemoteConnection(from, to *Peer, tcpAddr string, outbound bool, established bool) *RemoteConnection {
|
||||
return &RemoteConnection{
|
||||
local: from,
|
||||
remote: to,
|
||||
remoteTCPAddr: tcpAddr,
|
||||
outbound: outbound,
|
||||
established: established,
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *RemoteConnection) Local() *Peer { return conn.local }
|
||||
func (conn *RemoteConnection) Remote() *Peer { return conn.remote }
|
||||
func (conn *RemoteConnection) RemoteTCPAddr() string { return conn.remoteTCPAddr }
|
||||
func (conn *RemoteConnection) Outbound() bool { return conn.outbound }
|
||||
func (conn *RemoteConnection) Established() bool { return conn.established }
|
||||
func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return TieBreakTied }
|
||||
func (conn *RemoteConnection) Shutdown(error) {}
|
||||
|
||||
func (conn *RemoteConnection) Log(args ...interface{}) {
|
||||
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
|
||||
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
// Does not return anything. If the connection is successful, it will
|
||||
// end up in the local peer's connections map.
|
||||
func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router, acceptNewPeer bool) {
|
||||
if connRemote.local != router.Ourself.Peer {
|
||||
log.Fatal("Attempt to create local connection from a peer which is not ourself")
|
||||
}
|
||||
// NB, we're taking a copy of connRemote here.
|
||||
actionChan := make(chan ConnectionAction, ChannelSize)
|
||||
finished := make(chan struct{})
|
||||
conn := &LocalConnection{
|
||||
RemoteConnection: *connRemote,
|
||||
Router: router,
|
||||
TCPConn: tcpConn,
|
||||
remoteUDPAddr: udpAddr,
|
||||
effectivePMTU: DefaultPMTU,
|
||||
uid: randUint64(),
|
||||
actionChan: actionChan,
|
||||
finished: finished}
|
||||
go conn.run(actionChan, finished, acceptNewPeer)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
|
||||
dupConnLocal := dupConn.(*LocalConnection)
|
||||
// conn.uid is used as the tie breaker here, in the knowledge that
|
||||
// both sides will make the same decision.
|
||||
if conn.uid < dupConnLocal.uid {
|
||||
return TieBreakWon
|
||||
} else if dupConnLocal.uid < conn.uid {
|
||||
return TieBreakLost
|
||||
} else {
|
||||
return TieBreakTied
|
||||
}
|
||||
}
|
||||
|
||||
// Read by the forwarder processes when in the UDP senders
|
||||
func (conn *LocalConnection) RemoteUDPAddr() *net.UDPAddr {
|
||||
conn.RLock()
|
||||
defer conn.RUnlock()
|
||||
return conn.remoteUDPAddr
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) Established() bool {
|
||||
conn.RLock()
|
||||
defer conn.RUnlock()
|
||||
return conn.established
|
||||
}
|
||||
|
||||
// Called by forwarder processes, read in Forward (by sniffer and udp
|
||||
// listener process in router).
|
||||
func (conn *LocalConnection) setEffectivePMTU(pmtu int) {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
if conn.effectivePMTU != pmtu {
|
||||
conn.effectivePMTU = pmtu
|
||||
conn.Log("Effective PMTU set to", pmtu)
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the connection's actor process, and by the connection's
|
||||
// TCP receiver process. StackFrag is read in conn.forward
|
||||
func (conn *LocalConnection) setStackFrag(frag bool) {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
conn.stackFrag = frag
|
||||
}
|
||||
|
||||
// Called by the connection's TCP receiver process.
|
||||
func (conn *LocalConnection) pmtuVerified(pmtu int) {
|
||||
conn.RLock()
|
||||
fwd := conn.forwarderDF
|
||||
conn.RUnlock()
|
||||
if fwd != nil {
|
||||
fwd.PMTUVerified(pmtu)
|
||||
}
|
||||
}
|
||||
|
||||
// Send directly, not via the Actor. If it goes via the Actor we can
|
||||
// get a deadlock where LocalConnection is blocked talking to
|
||||
// LocalPeer and LocalPeer is blocked trying send a ProtocolMsg via
|
||||
// LocalConnection, and the channels are full in both directions so
|
||||
// nothing can proceed.
|
||||
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
|
||||
if err := conn.sendProtocolMsg(m); err != nil {
|
||||
conn.Shutdown(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR methods
|
||||
|
||||
// NB: The conn.* fields are only written by the connection actor
|
||||
// process, which is the caller of the ConnectionAction funs. Hence we
|
||||
// do not need locks for reading, and only need write locks for fields
|
||||
// read by other processes.
|
||||
|
||||
// Async
|
||||
func (conn *LocalConnection) Shutdown(err error) {
|
||||
// err should always be a real error, even if only io.EOF
|
||||
if err == nil {
|
||||
panic("nil error")
|
||||
}
|
||||
|
||||
// Run on its own goroutine in case the channel is backed up
|
||||
go func() { conn.sendAction(func() error { return err }) }()
|
||||
}
|
||||
|
||||
// Async
|
||||
//
|
||||
// Heartbeating serves two purposes: a) keeping NAT paths alive, and
|
||||
// b) updating a remote peer's knowledge of our address, in the event
|
||||
// it changes (e.g. because NAT paths expired).
|
||||
func (conn *LocalConnection) ReceivedHeartbeat(remoteUDPAddr *net.UDPAddr, connUID uint64) {
|
||||
if remoteUDPAddr == nil || connUID != conn.uid {
|
||||
return
|
||||
}
|
||||
conn.sendAction(func() error {
|
||||
oldRemoteUDPAddr := conn.remoteUDPAddr
|
||||
old := conn.receivedHeartbeat
|
||||
conn.Lock()
|
||||
conn.remoteUDPAddr = remoteUDPAddr
|
||||
conn.receivedHeartbeat = true
|
||||
conn.Unlock()
|
||||
conn.heartbeatTimeout.Reset(HeartbeatTimeout)
|
||||
if !old {
|
||||
if err := conn.sendSimpleProtocolMsg(ProtocolConnectionEstablished); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if oldRemoteUDPAddr == nil {
|
||||
return conn.sendFastHeartbeats()
|
||||
} else if oldRemoteUDPAddr.String() != remoteUDPAddr.String() {
|
||||
log.Println("Peer", conn.remote, "moved from", oldRemoteUDPAddr, "to", remoteUDPAddr)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Async
|
||||
func (conn *LocalConnection) SetEstablished() {
|
||||
conn.sendAction(func() error {
|
||||
stopTicker(conn.heartbeat)
|
||||
old := conn.established
|
||||
conn.Lock()
|
||||
conn.established = true
|
||||
conn.Unlock()
|
||||
if old {
|
||||
return nil
|
||||
}
|
||||
conn.Router.Ourself.ConnectionEstablished(conn)
|
||||
if err := conn.ensureForwarders(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Send a large frame down the DF channel in order to prompt
|
||||
// PMTU discovery to start.
|
||||
conn.Send(true, PMTUDiscovery)
|
||||
conn.heartbeat = time.NewTicker(SlowHeartbeat)
|
||||
conn.fragTest = time.NewTicker(FragTestInterval)
|
||||
// avoid initial waits for timers to fire
|
||||
conn.Send(true, conn.heartbeatFrame)
|
||||
conn.performFragTest()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Send an actor request to the actorLoop, but don't block if
|
||||
// actorLoop has exited - see http://blog.golang.org/pipelines for
|
||||
// pattern
|
||||
func (conn *LocalConnection) sendAction(action ConnectionAction) {
|
||||
select {
|
||||
case conn.actionChan <- action:
|
||||
case <-conn.finished:
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR server
|
||||
|
||||
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished chan<- struct{}, acceptNewPeer bool) {
|
||||
var err error // important to use this var and not create another one with 'err :='
|
||||
defer func() { conn.shutdown(err) }()
|
||||
defer close(finished)
|
||||
|
||||
conn.TCPConn.SetLinger(0)
|
||||
intro, err := ProtocolIntroParams{
|
||||
MinVersion: conn.Router.ProtocolMinVersion,
|
||||
MaxVersion: ProtocolMaxVersion,
|
||||
Features: conn.makeFeatures(),
|
||||
Conn: conn.TCPConn,
|
||||
Password: conn.Router.Password,
|
||||
Outbound: conn.outbound,
|
||||
}.DoIntro()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn.SessionKey = intro.SessionKey
|
||||
conn.tcpSender = intro.Sender
|
||||
conn.version = intro.Version
|
||||
|
||||
remote, err := conn.parseFeatures(intro.Features)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if conn.SessionKey == nil {
|
||||
conn.Decryptor = NewNonDecryptor()
|
||||
} else {
|
||||
conn.Decryptor = NewNaClDecryptor(conn.SessionKey, conn.outbound)
|
||||
}
|
||||
|
||||
conn.Log("connection ready; using protocol version", conn.version)
|
||||
|
||||
// The ordering of the following is very important. [1]
|
||||
|
||||
if conn.remoteUDPAddr != nil {
|
||||
if err = conn.ensureForwarders(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = conn.Router.Ourself.AddConnection(conn); err != nil {
|
||||
return
|
||||
}
|
||||
if err = conn.initHeartbeats(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go conn.receiveTCP(intro.Receiver)
|
||||
err = conn.actorLoop(actionChan)
|
||||
}
|
||||
|
||||
// [1] Ordering constraints:
|
||||
//
|
||||
// (a) AddConnections must precede initHeartbeats. It is only after
|
||||
// the former completes that we know the connection is valid, in
|
||||
// particular is not a duplicate connection to the same peer. Sending
|
||||
// heartbeats on a duplicate connection can trip up crypto at the
|
||||
// other end, since the associated UDP packets may get decoded by the
|
||||
// other connection. It is also generally wasteful to engage in any
|
||||
// interaction with the remote on a connection that turns out to be
|
||||
// invald.
|
||||
//
|
||||
// (b) AddConnection must precede receiveTCP. In the absence of any
|
||||
// indirect connectivity to the remote peer, the first we hear about
|
||||
// it (and any peers reachable from it) is through topology gossip it
|
||||
// sends us on the connection. We must ensure that the connection has
|
||||
// been added to Ourself prior to processing any such gossip,
|
||||
// otherwise we risk immediately gc'ing part of that newly received
|
||||
// portion of the topology (though not the remote peer itself, since
|
||||
// that will have a positive ref count), leaving behind dangling
|
||||
// references to peers. Hence we must invoke AddConnection, which is
|
||||
// *synchronous*, first.
|
||||
//
|
||||
// (c) AddConnection must precede actorLoop. More precisely, it must
|
||||
// precede shutdown, since that invokes DeleteConnection and is
|
||||
// invoked on termination of this entire function. Essentially this
|
||||
// boils down to a prohibition on running AddConnection in a separate
|
||||
// goroutine, at least not without some synchronisation. Which in turn
|
||||
// requires us the launching of the receiveTCP goroutine to precede
|
||||
// actorLoop.
|
||||
//
|
||||
// (d) AddConnection should precede receiveTCP. There is no point
|
||||
// starting the latter if the former fails.
|
||||
//
|
||||
// (e) initHeartbeats should precede actorLoop. The former is setting
|
||||
// LocalConnection fields accessed by the latter. Since the latter
|
||||
// runs in a separate goroutine, we'd have to add some synchronisation
|
||||
// if initHeartbeats isn't run first.
|
||||
//
|
||||
// (f) ensureForwarders should precede AddConnection. As soon as a
|
||||
// connection has been added to LocalPeer by the latter, it becomes
|
||||
// visible to the packet routing logic, which will end up dropping
|
||||
// packets if the forwarders haven't been created yet. We cannot
|
||||
// prevent that completely, since, for example, forwarder can only be
|
||||
// created when we know the remote UDP address, but it helps to try.
|
||||
|
||||
func (conn *LocalConnection) makeFeatures() map[string]string {
|
||||
return map[string]string{
|
||||
"PeerNameFlavour": PeerNameFlavour,
|
||||
"Name": conn.local.Name.String(),
|
||||
"NickName": conn.local.NickName,
|
||||
"UID": fmt.Sprint(conn.local.UID),
|
||||
"ConnID": fmt.Sprint(conn.uid),
|
||||
}
|
||||
}
|
||||
|
||||
type features map[string]string
|
||||
|
||||
func (features features) MustHave(keys []string) error {
|
||||
for _, key := range keys {
|
||||
if _, ok := features[key]; !ok {
|
||||
return fmt.Errorf("Field %s is missing", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (features features) Get(key string) string {
|
||||
return features[key]
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
|
||||
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "UID", "ConnID"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remotePeerNameFlavour := features.Get("PeerNameFlavour")
|
||||
if remotePeerNameFlavour != PeerNameFlavour {
|
||||
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
|
||||
}
|
||||
|
||||
name, err := PeerNameFromString(features.Get("Name"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nickName := features.Get("NickName")
|
||||
|
||||
uid, err := ParsePeerUID(features.Get("UID"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.uid ^= remoteConnID
|
||||
return NewPeer(name, nickName, uid, 0), nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
|
||||
if acceptNewPeer {
|
||||
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
|
||||
} else {
|
||||
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
|
||||
if conn.remote == nil {
|
||||
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if conn.remote.UID != remote.UID {
|
||||
return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
|
||||
}
|
||||
|
||||
if conn.remote == conn.local {
|
||||
return ErrConnectToSelf
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) initHeartbeats() error {
|
||||
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
|
||||
conn.heartbeatTimeout = time.NewTimer(HeartbeatTimeout)
|
||||
conn.heartbeatFrame = make([]byte, EthernetOverhead+8)
|
||||
binary.BigEndian.PutUint64(conn.heartbeatFrame[EthernetOverhead:], conn.uid)
|
||||
if conn.remoteUDPAddr == nil {
|
||||
return nil
|
||||
}
|
||||
return conn.sendFastHeartbeats()
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) {
|
||||
for err == nil {
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
err = action()
|
||||
case <-conn.heartbeatTCP.C:
|
||||
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
|
||||
case <-conn.heartbeatTimeout.C:
|
||||
err = fmt.Errorf("timed out waiting for UDP heartbeat")
|
||||
case <-tickerChan(conn.heartbeat):
|
||||
conn.Send(true, conn.heartbeatFrame)
|
||||
case <-tickerChan(conn.fragTest):
|
||||
conn.performFragTest()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) shutdown(err error) {
|
||||
if conn.remote == nil {
|
||||
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
|
||||
} else {
|
||||
conn.ErrorLog("connection shutting down due to error:", err)
|
||||
}
|
||||
|
||||
if conn.TCPConn != nil {
|
||||
checkWarn(conn.TCPConn.Close())
|
||||
}
|
||||
|
||||
if conn.remote != nil {
|
||||
conn.Router.Peers.Dereference(conn.remote)
|
||||
conn.Router.Ourself.DeleteConnection(conn)
|
||||
}
|
||||
|
||||
if conn.heartbeatTimeout != nil {
|
||||
conn.heartbeatTimeout.Stop()
|
||||
}
|
||||
|
||||
stopTicker(conn.heartbeatTCP)
|
||||
stopTicker(conn.heartbeat)
|
||||
stopTicker(conn.fragTest)
|
||||
|
||||
// blank out the forwardChan so that the router processes don't
|
||||
// try to send any more
|
||||
conn.stopForwarders()
|
||||
|
||||
conn.Router.ConnectionMaker.ConnectionTerminated(conn.remoteTCPAddr, err)
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error {
|
||||
return conn.sendProtocolMsg(ProtocolMsg{tag: tag})
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
|
||||
return conn.tcpSender.Send(Concat([]byte{byte(m.tag)}, m.msg))
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
|
||||
var err error
|
||||
for {
|
||||
conn.extendReadDeadline()
|
||||
|
||||
var msg []byte
|
||||
if msg, err = receiver.Receive(); err != nil {
|
||||
break
|
||||
}
|
||||
if len(msg) < 1 {
|
||||
conn.Log("ignoring blank msg")
|
||||
continue
|
||||
}
|
||||
if err = conn.handleProtocolMsg(ProtocolTag(msg[0]), msg[1:]); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
conn.Shutdown(err)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error {
|
||||
switch tag {
|
||||
case ProtocolHeartbeat:
|
||||
case ProtocolConnectionEstablished:
|
||||
// We sent fast heartbeats to the remote peer, which has now
|
||||
// received at least one of them and told us via this message.
|
||||
// We can now consider the connection as established from our
|
||||
// end.
|
||||
conn.SetEstablished()
|
||||
case ProtocolFragmentationReceived:
|
||||
conn.setStackFrag(true)
|
||||
case ProtocolPMTUVerified:
|
||||
conn.pmtuVerified(int(binary.BigEndian.Uint16(payload)))
|
||||
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
|
||||
return conn.Router.handleGossip(tag, payload)
|
||||
default:
|
||||
conn.Log("ignoring unknown protocol tag:", tag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) extendReadDeadline() {
|
||||
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) sendFastHeartbeats() error {
|
||||
err := conn.ensureForwarders()
|
||||
if err == nil {
|
||||
conn.heartbeat = time.NewTicker(FastHeartbeat)
|
||||
conn.Send(true, conn.heartbeatFrame) // avoid initial wait
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) performFragTest() {
|
||||
conn.setStackFrag(false)
|
||||
conn.Send(false, FragTest)
|
||||
}
|
||||
|
||||
func tickerChan(ticker *time.Ticker) <-chan time.Time {
|
||||
if ticker != nil {
|
||||
return ticker.C
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stopTicker(ticker *time.Ticker) {
|
||||
if ticker != nil {
|
||||
ticker.Stop()
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
EthernetOverhead = 14
|
||||
UDPOverhead = 28 // 20 bytes for IPv4, 8 bytes for UDP
|
||||
Port = 6783
|
||||
HTTPPort = Port + 1
|
||||
DefaultPMTU = 65535
|
||||
MaxUDPPacketSize = 65535
|
||||
ChannelSize = 16
|
||||
FragTestSize = 60001
|
||||
PMTUDiscoverySize = 60000
|
||||
TCPHeartbeat = 30 * time.Second
|
||||
FastHeartbeat = 500 * time.Millisecond
|
||||
SlowHeartbeat = 10 * time.Second
|
||||
FragTestInterval = 5 * time.Minute
|
||||
GossipInterval = 30 * time.Second
|
||||
PMTUVerifyAttempts = 8
|
||||
PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt
|
||||
MaxDuration = time.Duration(math.MaxInt64)
|
||||
MaxMissedHeartbeats = 6
|
||||
HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat
|
||||
MaxTCPMsgSize = 10 * 1024 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
FragTest = make([]byte, FragTestSize)
|
||||
PMTUDiscovery = make([]byte, PMTUDiscoverySize)
|
||||
)
|
||||
+10
-10
@@ -29,7 +29,13 @@ func (dec *EthernetDecoder) DecodeLayers(data []byte) {
|
||||
dec.parser.DecodeLayers(data, &dec.decoded)
|
||||
}
|
||||
|
||||
func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) error) error {
|
||||
func (dec *EthernetDecoder) PacketKey() (key PacketKey) {
|
||||
copy(key.SrcMAC[:], dec.Eth.SrcMAC)
|
||||
copy(key.DstMAC[:], dec.Eth.DstMAC)
|
||||
return
|
||||
}
|
||||
|
||||
func (dec *EthernetDecoder) makeICMPFragNeeded(mtu int) ([]byte, error) {
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
@@ -57,23 +63,17 @@ func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) e
|
||||
Seq: uint16(mtu)},
|
||||
&payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU=%v", dec.IP.DstIP, dec.IP.SrcIP, mtu)
|
||||
return sendFrame(buf.Bytes())
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var (
|
||||
// see http://en.wikipedia.org/wiki/Multicast_address#Ethernet
|
||||
stpMACPrefix = []byte{0x01, 0x80, 0xC2, 0x00, 0x00}
|
||||
zeroMAC, _ = net.ParseMAC("00:00:00:00:00:00")
|
||||
zeroMAC, _ = net.ParseMAC("00:00:00:00:00:00")
|
||||
)
|
||||
|
||||
func (dec *EthernetDecoder) DropFrame() bool {
|
||||
return bytes.Equal(stpMACPrefix, dec.Eth.DstMAC[:len(stpMACPrefix)])
|
||||
}
|
||||
|
||||
func (dec *EthernetDecoder) IsSpecial() bool {
|
||||
return dec.Eth.Length == 0 && dec.Eth.EthernetType == layers.EthernetTypeLLC &&
|
||||
bytes.Equal(zeroMAC, dec.Eth.SrcMAC) && bytes.Equal(zeroMAC, dec.Eth.DstMAC)
|
||||
|
||||
+1197
File diff suppressed because it is too large.
Load diff
+105
@@ -0,0 +1,105 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
)
|
||||
|
||||
// Just enough flow machinery for the weave router
|
||||
|
||||
type MAC [6]byte
|
||||
|
||||
func (mac MAC) String() string {
|
||||
return net.HardwareAddr(mac[:]).String()
|
||||
}
|
||||
|
||||
type PacketKey struct {
|
||||
SrcMAC MAC
|
||||
DstMAC MAC
|
||||
}
|
||||
|
||||
type ForwardPacketKey struct {
|
||||
SrcPeer *mesh.Peer
|
||||
DstPeer *mesh.Peer
|
||||
PacketKey
|
||||
}
|
||||
|
||||
type FlowOp interface {
|
||||
// The caller must supply an EthernetDecoder specific to this
|
||||
// thread, which has already been used to decode the frame.
|
||||
// The broadcast parameter is a hint whether the packet is
|
||||
// being broadcast.
|
||||
Process(frame []byte, dec *EthernetDecoder, broadcast bool)
|
||||
|
||||
// Does the FlowOp discard the packet?
|
||||
Discards() bool
|
||||
}
|
||||
|
||||
type DiscardingFlowOp struct{}
|
||||
|
||||
func (DiscardingFlowOp) Process([]byte, *EthernetDecoder, bool) {
|
||||
}
|
||||
|
||||
func (DiscardingFlowOp) Discards() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type NonDiscardingFlowOp struct{}
|
||||
|
||||
func (NonDiscardingFlowOp) Discards() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type MultiFlowOp struct {
|
||||
broadcast bool
|
||||
ops []FlowOp
|
||||
}
|
||||
|
||||
func NewMultiFlowOp(broadcast bool, ops ...FlowOp) *MultiFlowOp {
|
||||
mfop := &MultiFlowOp{broadcast: broadcast}
|
||||
for _, op := range ops {
|
||||
mfop.Add(op)
|
||||
}
|
||||
return mfop
|
||||
}
|
||||
|
||||
func (mfop *MultiFlowOp) Add(op FlowOp) {
|
||||
mfop.ops = append(mfop.ops, op)
|
||||
}
|
||||
|
||||
func (mfop *MultiFlowOp) Process(frame []byte, dec *EthernetDecoder, broadcast bool) {
|
||||
for _, op := range mfop.ops {
|
||||
op.Process(frame, dec, mfop.broadcast)
|
||||
}
|
||||
}
|
||||
|
||||
func (mfop *MultiFlowOp) Discards() bool {
|
||||
for _, op := range mfop.ops {
|
||||
if !op.Discards() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func FlattenFlowOp(fop FlowOp) []FlowOp {
|
||||
return collectFlowOps(nil, fop)
|
||||
}
|
||||
|
||||
func collectFlowOps(into []FlowOp, fop FlowOp) []FlowOp {
|
||||
if fop == nil {
|
||||
return into
|
||||
}
|
||||
|
||||
if mfop, ok := fop.(*MultiFlowOp); ok {
|
||||
for _, op := range mfop.ops {
|
||||
into = collectFlowOps(into, op)
|
||||
}
|
||||
|
||||
return into
|
||||
}
|
||||
|
||||
return append(into, fop)
|
||||
}
|
||||
Loaded 100 of 170 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user