Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
276d0b621b | ||
|
|
6733dac9fc | ||
|
|
2c143612e2 | ||
|
|
86361c5b60 | ||
|
|
35c017a787 | ||
|
|
79d65bc98b | ||
|
|
c16653a396 | ||
|
|
ba16135b3a | ||
|
|
3c53aa66b7 | ||
|
|
c97d4731dc | ||
|
|
a69546c9ee | ||
|
|
49f7e38133 | ||
|
|
b0519af573 | ||
|
|
dc7acd6c70 | ||
|
|
d63a21363e | ||
|
|
a330dd4970 | ||
|
|
3aff5f2467 | ||
|
|
0c63a8d0e5 | ||
|
|
65b45e8dd1 | ||
|
|
03885d4b40 | ||
|
|
40e78fea99 | ||
|
|
ecba88e445 | ||
|
|
29c9b2c064 | ||
|
|
6a871d13a6 | ||
|
|
c2a224b436 | ||
|
|
dd8d4b1d9b | ||
|
|
527bcffe3b | ||
|
|
de1d409c6a | ||
|
|
2555e491e1 | ||
|
|
4b5299d124 | ||
|
|
294c16c186 | ||
|
|
7df458fcdd | ||
|
|
608b8f0a06 | ||
|
|
089a993299 | ||
|
|
bc4c31e48b | ||
|
|
a6cd456f8b | ||
|
|
462033dc99 | ||
|
|
0521bdb3b9 | ||
|
|
1f4165c132 | ||
|
|
a1fb08216e | ||
|
|
381996b225 | ||
|
|
08bca70192 | ||
|
|
10d25056a0 | ||
|
|
f889dda9f3 | ||
|
|
b0cd84c940 | ||
|
|
411fd2d831 | ||
|
|
d18782afd7 | ||
|
|
43dfbdc8e3 | ||
|
|
ea0f156b6f | ||
|
|
98def2f5fc | ||
|
|
c089cb508c | ||
|
|
1797cee84f | ||
|
|
45c97c82e1 | ||
|
|
bccde69a33 | ||
|
|
d9aa1a5abd | ||
|
|
46e1153f92 | ||
|
|
3dc642c007 | ||
|
|
bd9bc9093e | ||
|
|
6f0680cc7b | ||
|
|
911bf289bd | ||
|
|
393b98a463 | ||
|
|
2126d2314e | ||
|
|
d4d9a8fa9a | ||
|
|
6a21e3a2ca | ||
|
|
7119348b31 | ||
|
|
bf291b1863 | ||
|
|
e6a1acddcb | ||
|
|
8a8cd70879 | ||
|
|
b744e41e9e | ||
|
|
c101957fb9 | ||
|
|
a8dc7986af | ||
|
|
d0c9763d0d | ||
|
|
5988fede53 | ||
|
|
25c8b46c70 | ||
|
|
60c3d97c7a | ||
|
|
ee0bd2d747 | ||
|
|
c0e94cabb6 | ||
|
|
867e3231d2 | ||
|
|
00bfa3ae03 | ||
|
|
7ae83d6a5b | ||
|
|
5d6f6456d3 | ||
|
|
69b6077221 | ||
|
|
dcf82f8db7 | ||
|
|
69f83b352a | ||
|
|
2899789147 | ||
|
|
d33f06634f | ||
|
|
1d3bdd6a34 | ||
|
|
5a38e04789 | ||
|
|
1f1b22cee7 | ||
|
|
16ce5eacbb | ||
|
|
69e311d021 | ||
|
|
ea48915007 | ||
|
|
165c8c1d0f | ||
|
|
b237eaf6bf | ||
|
|
4de70e31c6 | ||
|
|
dd29302d57 | ||
|
|
4f3cf40e30 | ||
|
|
9fdb40c9b8 | ||
|
|
3af2a070e3 | ||
|
|
6e5f0a8eea | ||
|
|
d0320dd0c6 | ||
|
|
5f25bba012 | ||
|
|
42e650bba8 | ||
|
|
c6f29d685c | ||
|
|
a45c5e1170 | ||
|
|
683b4c7c18 | ||
|
|
1b5d6033b0 | ||
|
|
c209761689 | ||
|
|
59112209d0 | ||
|
|
28ea18d3a6 | ||
|
|
fac669156e | ||
|
|
102f4ad4a4 | ||
|
|
e10ce2345b | ||
|
|
d06eaf14af | ||
|
|
21623118d3 | ||
|
|
4dfcaa470a | ||
|
|
9cc98b0ee8 | ||
|
|
acf2b4dc7d | ||
|
|
029c6f9caf | ||
|
|
34767f4986 | ||
|
|
fe852d8519 | ||
|
|
0eded20bdf | ||
|
|
9ec9e72ade | ||
|
|
c9559666ef | ||
|
|
81c0d44c09 | ||
|
|
2651fe2ed4 | ||
|
|
e1e40fc68c | ||
|
|
dff0f08cb5 | ||
|
|
5483e81d75 | ||
|
|
905d923880 | ||
|
|
c3d96ca448 | ||
|
|
7ea8761685 | ||
|
|
0e6e5e3a4d | ||
|
|
afbc7aaa14 | ||
|
|
8cc3179a36 | ||
|
|
324d013f88 | ||
|
|
70924f6f3a | ||
|
|
a2602bb712 | ||
|
|
5fc94ca01d | ||
|
|
ddf2c99240 | ||
|
|
f3e83dcdb9 | ||
|
|
773aeda980 | ||
|
|
bea58ef6dc | ||
|
|
35b4fca59f | ||
|
|
afc2f13841 | ||
|
|
4e71e4db86 | ||
|
|
dd1fc02f9f | ||
|
|
760a76cbd0 | ||
|
|
a81720d64c | ||
|
|
d0ad4ad022 | ||
|
|
2f9f500ecb | ||
|
|
1d09a35bcd | ||
|
|
e8310ab1b7 | ||
|
|
3b029896c9 | ||
|
|
61f83cd463 | ||
|
|
0c36b87bcc | ||
|
|
14e97185b3 | ||
|
|
fa512ee22a | ||
|
|
58dc68d112 | ||
|
|
7e265100c3 | ||
|
|
9d0a0253c9 | ||
|
|
e41b5d9a1c | ||
|
|
7af82945d9 | ||
|
|
6e90c56d03 | ||
|
|
887d72e45f | ||
|
|
3086a6821b | ||
|
|
599d5e4863 | ||
|
|
d59aab3f71 | ||
|
|
d356afd6cf | ||
|
|
474ff5815b | ||
|
|
5379826662 | ||
|
|
b551c15f5c | ||
|
|
8554f0f896 | ||
|
|
b8a5d49100 | ||
|
|
05ed63aaf4 | ||
|
|
f37f8308c8 | ||
|
|
136c4521d4 | ||
|
|
7a34d997aa | ||
|
|
6ca9b2c006 | ||
|
|
fdc01a41de | ||
|
|
fb138b54fa | ||
|
|
efde637e1c | ||
|
|
448518d500 | ||
|
|
b562cc9b98 | ||
|
|
7b6a21f612 | ||
|
|
cf1a1116f4 | ||
|
|
cbc3d5b04c | ||
|
|
219b03cb6b | ||
|
|
c33cd2657c | ||
|
|
e26b0663e3 | ||
|
|
7f93c403e9 | ||
|
|
4a1c2c5d09 | ||
|
|
8e445f349b | ||
|
|
09984001fc | ||
|
|
f598338a5f | ||
|
|
650adb47fd | ||
|
|
ad8a943eb2 | ||
|
|
1b7efab40a | ||
|
|
43c608e25c | ||
|
|
6aba9411a6 | ||
|
|
1e70992835 | ||
|
|
38a84e95ad | ||
|
|
5492d046d1 | ||
|
|
16dbd5eea0 | ||
|
|
098a3c59c7 | ||
|
|
16272f0d75 | ||
|
|
4e41e422c5 | ||
|
|
227d45a40c | ||
|
|
6bd529ef66 | ||
|
|
61e798d400 | ||
|
|
d84154326f | ||
|
|
cba793524e | ||
|
|
9ec8cb9bf4 | ||
|
|
be52e08f2d | ||
|
|
4edff2f17f | ||
|
|
ed70e6e344 | ||
|
|
920bba2ffd | ||
|
|
2113498348 | ||
|
|
8e7e1a3b26 | ||
|
|
f638fac152 | ||
|
|
d2ebaf3ab7 | ||
|
|
90575278b0 | ||
|
|
fdbffce93a | ||
|
|
afac7c4d09 | ||
|
|
3fdca717c1 | ||
|
|
3959149077 | ||
|
|
f17053fcee | ||
|
|
fcbbf89083 | ||
|
|
3138602661 | ||
|
|
562f49b3ad | ||
|
|
02328ae43f | ||
|
|
e132f6c385 | ||
|
|
2b8fb49be2 | ||
|
|
51601a3258 | ||
|
|
31267f36ed | ||
|
|
540d1cbf06 | ||
|
|
050f671c65 | ||
|
|
35e2179271 | ||
|
|
52d06513bb | ||
|
|
124ef94500 | ||
|
|
166d5bfc5b | ||
|
|
efd290a3be | ||
|
|
742ef03495 | ||
|
|
92a6694ac8 | ||
|
|
1354ad1884 | ||
|
|
15a4b189f4 | ||
|
|
53cf6c2f8c | ||
|
|
64518c3bd4 | ||
|
|
20711cb8bc | ||
|
|
2914b9d220 | ||
|
|
677c872d5a | ||
|
|
015809c83a | ||
|
|
092ec8d83d | ||
|
|
9d2237cfd4 | ||
|
|
8b615e58e8 | ||
|
|
e351d0c3c0 | ||
|
|
a943f66afd | ||
|
|
3df1a0943c | ||
|
|
b5759c5b6d | ||
|
|
6b4c6c84b4 | ||
|
|
d8222d76d9 | ||
|
|
88a3b41135 | ||
|
|
fb7aa9298d | ||
|
|
b79cc948b3 | ||
|
|
e7092612a9 | ||
|
|
15c512388d | ||
|
|
f37377264b | ||
|
|
bd44b20b4a | ||
|
|
e25da3c619 | ||
|
|
3499dbfe09 | ||
|
|
81c4a0ffce | ||
|
|
a0064f7535 | ||
|
|
79c9eff2b9 | ||
|
|
b8bcf0c87d | ||
|
|
179a560e1d | ||
|
|
2245bd25e9 | ||
|
|
c256daceb0 | ||
|
|
73b29050b9 | ||
|
|
162da63522 | ||
|
|
01194ee267 | ||
|
|
d958cff58e | ||
|
|
d0662b519f | ||
|
|
d75a96e9ba | ||
|
|
bd7ed1b41a | ||
|
|
b5e6d235ba | ||
|
|
3cc537b978 | ||
|
|
ac0c0db942 | ||
|
|
0affbad976 | ||
|
|
ced84955d3 | ||
|
|
b86fc67adf | ||
|
|
59de4c3435 | ||
|
|
8e418b8743 | ||
|
|
f2b003379d | ||
|
|
0d083077ab | ||
|
|
db1a94a02c | ||
|
|
fdbc7dfe04 | ||
|
|
70f004cc62 | ||
|
|
c02cb45c6c | ||
|
|
8dccc88613 | ||
|
|
bb5914dbce | ||
|
|
4698ec1a37 | ||
|
|
e29c98faad | ||
|
|
444d6be6c9 | ||
|
|
28aa181480 | ||
|
|
97e0fcb765 | ||
|
|
052fdb30c0 | ||
|
|
2e2b667864 | ||
|
|
666631134c | ||
|
|
de4cfe2b8b | ||
|
|
a7ebe53cfa | ||
|
|
66a569fc40 | ||
|
|
3a646ffdba | ||
|
|
7cfd71c0bd | ||
|
|
3265d60d52 | ||
|
|
640b417b39 | ||
|
|
b655a3aede | ||
|
|
819b395ae4 | ||
|
|
2584335b11 | ||
|
|
4e5a8f348a | ||
|
|
f91a9c2562 | ||
|
|
ea098ac53e | ||
|
|
a0d0b00938 | ||
|
|
31a4657467 | ||
|
|
2ddb8fcec3 | ||
|
|
21d4fff450 | ||
|
|
00036ec277 | ||
|
|
a5d7fb66f0 | ||
|
|
b01a204c1f | ||
|
|
e9310eba98 | ||
|
|
69285270e5 | ||
|
|
6f96d6d63e | ||
|
|
91f476d2c6 | ||
|
|
7871a0b7a4 | ||
|
|
6bd11a426e | ||
|
|
1abdc3afcf | ||
|
|
b8f98a5387 | ||
|
|
cb3b858df6 | ||
|
|
d7025e7dfa | ||
|
|
b2b9c47a50 | ||
|
|
211003f542 | ||
|
|
afb0d5bd3d | ||
|
|
f8227c3f47 | ||
|
|
258d764af8 | ||
|
|
2d3e070288 | ||
|
|
138141ac9b | ||
|
|
fa56ad8bc3 | ||
|
|
9d29da8d27 | ||
|
|
37526728e0 | ||
|
|
68f2cb9caa | ||
|
|
d13c7ea7ee | ||
|
|
4c7c702b6d | ||
|
|
c05955d598 | ||
|
|
ca64ab54e7 | ||
|
|
7f4ad03a5c | ||
|
|
d60ad1f40a | ||
|
|
5106317e5d | ||
|
|
a5e4a19f06 | ||
|
|
067ce6de9f | ||
|
|
9facab53b7 | ||
|
|
e64442f6de | ||
|
|
4f20f545dc | ||
|
|
4bbf84f551 | ||
|
|
987a1618e9 | ||
|
|
0ab95cdd27 | ||
|
|
c617612794 | ||
|
|
0c89bbb8df | ||
|
|
a7206bee95 | ||
|
|
0e3a749f69 | ||
|
|
b0d0c3d731 | ||
|
|
534dd8e16a | ||
|
|
535bf9b066 | ||
|
|
79e703c8ac | ||
|
|
d4a98a58f4 | ||
|
|
ceb7206117 | ||
|
|
1273157ace | ||
|
|
5a92200dc6 | ||
|
|
d021e61dfa | ||
|
|
3ae505d6c6 | ||
|
|
a1ddb336c1 | ||
|
|
646ac3c74d | ||
|
|
d383a29e00 | ||
|
|
b44e295562 | ||
|
|
8a1f8d549e | ||
|
|
a2b674e094 | ||
|
|
c4796dcdf5 | ||
|
|
992b4a65df | ||
|
|
6928929bc9 | ||
|
|
508b424234 | ||
|
|
7f68a96f75 | ||
|
|
535e22e5f8 | ||
|
|
348a926af4 | ||
|
|
aa84c4788a | ||
|
|
66293e8caa | ||
|
|
1e60cff2ef | ||
|
|
701519a15c | ||
|
|
72532324ed | ||
|
|
3cb6569d55 | ||
|
|
bebaa48520 | ||
|
|
af246225a9 | ||
|
|
582a792c71 | ||
|
|
98c4bd9ee5 | ||
|
|
933b395de4 | ||
|
|
4f485f856b | ||
|
|
280651da9c | ||
|
|
77201e949d | ||
|
|
292a50a891 | ||
|
|
a36fc867d1 | ||
|
|
def9f2d407 | ||
|
|
b0da4f6b91 | ||
|
|
cbf1230f6d | ||
|
|
71d6e881f2 | ||
|
|
da40771739 | ||
|
|
694314cc00 | ||
|
|
3025738377 | ||
|
|
cbe0f4e6e5 | ||
|
|
68106d804e | ||
|
|
e0088e0252 | ||
|
|
efff6d6e63 | ||
|
|
6bbd8d7a4c | ||
|
|
e0bb49aa23 | ||
|
|
81d79a64e6 | ||
|
|
9cbf0289d8 | ||
|
|
4f08879321 | ||
|
|
6d6394c408 | ||
|
|
bf3ecaf48a | ||
|
|
6ba2bf80b8 | ||
|
|
9f4030cdb6 | ||
|
|
cce818995a | ||
|
|
1e2dc43df6 | ||
|
|
06caf03d70 | ||
|
|
7226b26e8e | ||
|
|
7c72d69e2f | ||
|
|
bc1d6c47ba | ||
|
|
8ec0e025d3 | ||
|
|
9f40d913c9 | ||
|
|
3595c71d8b | ||
|
|
9740478151 | ||
|
|
255d087416 | ||
|
|
442a55d539 | ||
|
|
01fce0c6b1 | ||
|
|
0e3ac00ff5 | ||
|
|
2285942384 | ||
|
|
8668f6bb1e | ||
|
|
95ee99d652 | ||
|
|
0273491e6e | ||
|
|
b1917d909a | ||
|
|
edbf85655a | ||
|
|
be917eb86b | ||
|
|
0a2521e7a4 | ||
|
|
1b8d115b63 | ||
|
|
70929cec57 | ||
|
|
30583ec8f2 | ||
|
|
e370ef7007 | ||
|
|
42953faf70 | ||
|
|
7611cd963c | ||
|
|
3e0eb4ac1e | ||
|
|
f8d501ae50 | ||
|
|
cb614f01c3 | ||
|
|
9be11d9cf0 | ||
|
|
8ca83de332 | ||
|
|
89d3fedcd4 | ||
|
|
71ca91db09 | ||
|
|
5cf973f03a | ||
|
|
2c7693b2c5 | ||
|
|
43249a8d39 | ||
|
|
b654ac178c | ||
|
|
ccac18b21c | ||
|
|
8a5fef98ab | ||
|
|
c85fff8f23 | ||
|
|
fc57e72853 | ||
|
|
ce94e69fd5 | ||
|
|
6c29a1f648 | ||
|
|
b46ccc0937 | ||
|
|
fa29b18be7 | ||
|
|
e89fc8f66a | ||
|
|
ad4fe86285 | ||
|
|
40db885b3a | ||
|
|
ae0c16f375 | ||
|
|
34ec7bd1c9 | ||
|
|
909b751d6d | ||
|
|
9419ad16bf | ||
|
|
9a90898c7d | ||
|
|
8e80cd1441 | ||
|
|
2c12c4f67e | ||
|
|
d5d45de5a4 | ||
|
|
2ebd358aeb | ||
|
|
be11db45d8 | ||
|
|
30213bd71e | ||
|
|
bb66b6161a | ||
|
|
b14501f358 | ||
|
|
e93a145bab | ||
|
|
f7130b8c21 | ||
|
|
a497e1ab8a | ||
|
|
8b245a442c | ||
|
|
e1ff2197c0 | ||
|
|
f684ea563d | ||
|
|
041b6fe251 | ||
|
|
88cb871654 | ||
|
|
507e734d1a | ||
|
|
d128488339 | ||
|
|
c8c61adca4 | ||
|
|
7c8f33f525 | ||
|
|
cb8599ebd5 | ||
|
|
7714952574 | ||
|
|
15bf6eab1e | ||
|
|
10333b0f3e | ||
|
|
aaa974353a | ||
|
|
52a5ca8f88 | ||
|
|
1cca9d6d79 | ||
|
|
b19f2df6f8 | ||
|
|
b3ac56e3d2 | ||
|
|
d28b1dcd9f | ||
|
|
e4cab53a11 | ||
|
|
c342bc4188 | ||
|
|
73a0df56b2 | ||
|
|
e0303a0dda | ||
|
|
cc77ad2c65 | ||
|
|
280a0fed69 | ||
|
|
24c2743a58 | ||
|
|
264a2cd2ad | ||
|
|
907ba8f53b | ||
|
|
d2cbb2749e | ||
|
|
eab5ec206a | ||
|
|
b49f5bdb99 | ||
|
|
28a1578d20 | ||
|
|
e78be1fc35 | ||
|
|
fa8753cab3 | ||
|
|
9459d4b739 | ||
|
|
f517a09f0f | ||
|
|
34642a04a4 | ||
|
|
4bb4d943ba | ||
|
|
db855715ab | ||
|
|
c27e4d020d | ||
|
|
109e249aa6 | ||
|
|
a16ad4d6e6 | ||
|
|
33ce8c5060 | ||
|
|
f2568627af | ||
|
|
334974472d | ||
|
|
8227a3f67c | ||
|
|
4f7d476907 | ||
|
|
a731d47d97 | ||
|
|
44beed4027 | ||
|
|
9fd1a43769 | ||
|
|
1c6abbbe41 | ||
|
|
c4a715747a | ||
|
|
3bdea732fc | ||
|
|
bfe8e79653 | ||
|
|
7d812e3087 | ||
|
|
05d9cf8235 | ||
|
|
108f4784a6 | ||
|
|
a7feb41e62 | ||
|
|
fddb3ee06e | ||
|
|
c59c86c660 | ||
|
|
8f02c81f16 | ||
|
|
b3fac8ed6a | ||
|
|
3574da267b | ||
|
|
c929b2d5f9 | ||
|
|
f479e57870 | ||
|
|
ae625a18c8 | ||
|
|
41a261bdcd | ||
|
|
4ad1c22ef6 | ||
|
|
ef9f95f653 | ||
|
|
3b54372eab | ||
|
|
f05bb6c4e0 | ||
|
|
c8e0c12d34 | ||
|
|
ab380ed2c1 | ||
|
|
9494f0655d | ||
|
|
c3a41286fd | ||
|
|
8297652204 | ||
|
|
a26fc67ded | ||
|
|
abadbf883a | ||
|
|
833eb6166f | ||
|
|
e7e3608642 | ||
|
|
dd232775e6 | ||
|
|
a8eb7966b6 | ||
|
|
8051f51d46 | ||
|
|
6fd9f471ff | ||
|
|
2d037d874c | ||
|
|
8e5384ee01 | ||
|
|
7dd37d2a13 | ||
|
|
36521b04a2 | ||
|
|
f5d0a277fb | ||
|
|
986a3f8079 | ||
|
|
de4ec23809 | ||
|
|
1cc0794e75 | ||
|
|
3cca0887c7 | ||
|
|
0e1d39120e | ||
|
|
a072cf2e0a | ||
|
|
0f0f3649a4 | ||
|
|
54c95c04af | ||
|
|
7c5dda353d | ||
|
|
c42400de4f | ||
|
|
9b5cef9167 | ||
|
|
6081c400fb | ||
|
|
cb339523de | ||
|
|
538902b207 | ||
|
|
8961041469 | ||
|
|
cfe26ae346 | ||
|
|
bdfa06dbe9 | ||
|
|
1a3537a8f4 | ||
|
|
09606db9c3 | ||
|
|
4a708c2a82 | ||
|
|
735a7f9154 | ||
|
|
7478e78ab7 | ||
|
|
01bfa602ad | ||
|
|
332762201a | ||
|
|
568290afb7 | ||
|
|
3c11c8aa38 | ||
|
|
2ba360f33a | ||
|
|
7fc56e54b3 | ||
|
|
16062ea62d | ||
|
|
e852ae99ca | ||
|
|
0ff37d3492 | ||
|
|
b3866ebd94 | ||
|
|
3031372442 | ||
|
|
d3d2de5f1f | ||
|
|
d7306c66f7 | ||
|
|
1f42a9d2f9 | ||
|
|
8c917eeb17 | ||
|
|
a81a873703 | ||
|
|
da04d97fb5 | ||
|
|
4f42029228 | ||
|
|
4916badcc4 | ||
|
|
82b56848a9 | ||
|
|
889dc856af | ||
|
|
2ba8764615 | ||
|
|
eb5435d275 | ||
|
|
b048b6deb0 | ||
|
|
0c23e47124 | ||
|
|
0af2111535 | ||
|
|
5bbd886e6f | ||
|
|
402fe094f4 | ||
|
|
b753bcd982 | ||
|
|
d6a5eaec3f | ||
|
|
771f57029e | ||
|
|
c223ff1c7d | ||
|
|
481214776b | ||
|
|
98e6571059 | ||
|
|
19a31a5b82 | ||
|
|
090878c157 | ||
|
|
53cc270f47 | ||
|
|
ceb0186f46 | ||
|
|
46762a40b0 | ||
|
|
c291324d14 | ||
|
|
b307b9872a | ||
|
|
5e290aa11f | ||
|
|
5f6c5f6692 | ||
|
|
1c1b3248c0 | ||
|
|
bc47866880 | ||
|
|
60c5e11871 | ||
|
|
e6a692424b | ||
|
|
f293063538 | ||
|
|
6321512ddb | ||
|
|
0ca125ff2c | ||
|
|
57efe0145b | ||
|
|
bd53462ab2 | ||
|
|
c44d98b798 | ||
|
|
c8adcb2e79 | ||
|
|
768f89c3de | ||
|
|
e2d644b86b | ||
|
|
09977a2e04 | ||
|
|
63a0b964ab | ||
|
|
5cae747b36 | ||
|
|
1b7c386351 | ||
|
|
ef0dae4ed3 | ||
|
|
293f1990bc | ||
|
|
0bb62ae75a | ||
|
|
2686be1b93 | ||
|
|
77b193791a | ||
|
|
74a7bef472 | ||
|
|
fc78bf4d11 | ||
|
|
84cdf54079 | ||
|
|
fa08bb9df6 | ||
|
|
b845360091 | ||
|
|
953d451105 | ||
|
|
75d80c4828 | ||
|
|
3e030f7971 | ||
|
|
877658d22e | ||
|
|
1c797ca901 | ||
|
|
520a72c8e6 | ||
|
|
98fec7e5aa | ||
|
|
d0e0df9bd4 | ||
|
|
f5bdf916f5 | ||
|
|
038aac41fd | ||
|
|
ea36d232ac | ||
|
|
c54fe016b1 | ||
|
|
9c9b46479d | ||
|
|
916ff7aa39 | ||
|
|
28c290074b | ||
|
|
b687484dda | ||
|
|
e74783b987 | ||
|
|
0b4c19a369 | ||
|
|
def9d8d004 | ||
|
|
fd731beb87 | ||
|
|
35cc653737 | ||
|
|
1254d680ed | ||
|
|
23c0b6a04b | ||
|
|
ede1ec970c | ||
|
|
2301e8dbf7 | ||
|
|
fbab5a7280 | ||
|
|
ac430e4c42 | ||
|
|
a92cfc1ffe | ||
|
|
178cf85183 | ||
|
|
272161b00b | ||
|
|
c6457981ab | ||
|
|
56155d0819 | ||
|
|
c711826996 | ||
|
|
eef6965590 | ||
|
|
4926cb6f9a | ||
|
|
d9ca54e777 | ||
|
|
1f6a6349a9 | ||
|
|
8f27f8f442 | ||
|
|
681109ba71 | ||
|
|
99c691745a | ||
|
|
1af5699948 | ||
|
|
ef7571f1ef | ||
|
|
8862d4f76e | ||
|
|
d313ec935c | ||
|
|
40e759733d | ||
|
|
dee57aca37 | ||
|
|
7ac2301247 | ||
|
|
c0726923f5 | ||
|
|
4d9eadde5b | ||
|
|
1d55ee19e4 | ||
|
|
9da5cee8d9 | ||
|
|
ca22ecdf09 | ||
|
|
2e58e92d48 | ||
|
|
fd0c53b424 | ||
|
|
7b1e8ac1cf | ||
|
|
9810a84475 | ||
|
|
31b7e063e7 | ||
|
|
b5172f46c1 | ||
|
|
5a6cb8e708 | ||
|
|
c3c28ea27e | ||
|
|
b128562b41 | ||
|
|
8f44dc0986 | ||
|
|
623abeef4c | ||
|
|
5f30846092 | ||
|
|
6ddbc5cb57 | ||
|
|
f9f3b18ce4 | ||
|
|
dc2925263c | ||
|
|
56fd45f7ea | ||
|
|
21a0fc30e8 | ||
|
|
673d0af97d | ||
|
|
ca523fa335 | ||
|
|
5860332e49 | ||
|
|
c06d4d1b00 | ||
|
|
bec2470ddf | ||
|
|
402400d4cb | ||
|
|
b1a66012bd | ||
|
|
9516b1a91b | ||
|
|
8468183738 | ||
|
|
39563fc142 | ||
|
|
104d2240ec | ||
|
|
9bec84da5b | ||
|
|
85fa976a25 | ||
|
|
e3a37c8d71 | ||
|
|
fd28740108 | ||
|
|
fa74d1b9bb | ||
|
|
11d120bb5b | ||
|
|
e2c96cfa85 | ||
|
|
eee3e8d331 | ||
|
|
0c68ad4acb | ||
|
|
640b487914 | ||
|
|
7c84e24f48 | ||
|
|
26b566b541 | ||
|
|
16bbee2831 | ||
|
|
125f3cf6f6 | ||
|
|
96f7c6ebab | ||
|
|
b2ffe77029 | ||
|
|
3607cf2d7a | ||
|
|
5b06063442 | ||
|
|
a57cf4bc83 | ||
|
|
6036037817 | ||
|
|
242b8907bf | ||
|
|
9db22c3cd6 | ||
|
|
e57d79bd70 | ||
|
|
589ab4f01d | ||
|
|
01fe09065c | ||
|
|
83958561f7 | ||
|
|
cb73cf3f51 | ||
|
|
7a3e8e64b7 | ||
|
|
f7ce16cba8 | ||
|
|
6ac1fef01d | ||
|
|
5c19efae48 | ||
|
|
f7c0da3ae1 | ||
|
|
fe0c833cdf | ||
|
|
58342f7509 | ||
|
|
fad537cad7 | ||
|
|
6d5c2ad0fa | ||
|
|
b585e1f4cb | ||
|
|
7102bf6d9d | ||
|
|
77988012ed | ||
|
|
2989e3cccd | ||
|
|
5a47d1d13f | ||
|
|
d3795d4e74 | ||
|
|
bc97daf5c0 | ||
|
|
08e4c98172 | ||
|
|
ca254e066a | ||
|
|
aea92a3563 | ||
|
|
e9fa655e0e | ||
|
|
7d178ecee9 | ||
|
|
201321f390 | ||
|
|
3afc528c88 | ||
|
|
62c9522bf6 | ||
|
|
1b45e8662e | ||
|
|
3ce8aec039 | ||
|
|
32d44e09a1 | ||
|
|
4e37f5ec42 | ||
|
|
39f471e9d3 | ||
|
|
293abd953e | ||
|
|
fde7dcdde9 | ||
|
|
7075b5d9fd | ||
|
|
50b1aa0c74 | ||
|
|
7f48cc9239 | ||
|
|
8211afe5d5 | ||
|
|
e26dbf11e9 | ||
|
|
39ac1a0037 | ||
|
|
86f716c38c | ||
|
|
d5c7c4476b | ||
|
|
2a02883f07 | ||
|
|
f592e4b70a | ||
|
|
968855e0a4 | ||
|
|
bee031ad70 | ||
|
|
3378e09b6a | ||
|
|
ab37b4a6c4 | ||
|
|
dff5b6d3f1 | ||
|
|
c026579cec | ||
|
|
ef8b576742 | ||
|
|
69398a9d7e | ||
|
|
0a2beedad3 | ||
|
|
29d50bbf09 | ||
|
|
673fc73cd5 | ||
|
|
1185bed370 | ||
|
|
1176d44044 | ||
|
|
77649ab81b | ||
|
|
a51bc6164c | ||
|
|
dd935b7e9e | ||
|
|
67dbdd377c | ||
|
|
e5185e73ce | ||
|
|
17ada094f8 | ||
|
|
3e5593dc4f | ||
|
|
e647fe6331 | ||
|
|
a2679794b3 | ||
|
|
30f59953d2 | ||
|
|
acd16881d9 | ||
|
|
2aaa400d62 | ||
|
|
487ebfb514 | ||
|
|
fde7e88e66 | ||
|
|
9e4ce8c052 | ||
|
|
defa5cc3d3 | ||
|
|
dea2755161 | ||
|
|
cc8785fdcf | ||
|
|
7265c6ea03 | ||
|
|
0792f0717d | ||
|
|
851b58cca8 | ||
|
|
cb48ebc399 | ||
|
|
5278eb7ebc | ||
|
|
418c256689 | ||
|
|
4eeb846507 | ||
|
|
7529f5f9af | ||
|
|
8506e57482 | ||
|
|
b1d09305e4 | ||
|
|
07892bff7d | ||
|
|
50985dee90 | ||
|
|
60323d0960 | ||
|
|
a9cef6327f | ||
|
|
fdeb9010ba | ||
|
|
ee659d4033 | ||
|
|
8f9b2a36c0 | ||
|
|
6a83130685 | ||
|
|
77e6c6f886 | ||
|
|
8a5a15d5e9 | ||
|
|
bcc8c73f59 | ||
|
|
e22c6375c9 | ||
|
|
22ae0d200c | ||
|
|
7231bd00f8 | ||
|
|
590a151bde | ||
|
|
f5dfefef75 | ||
|
|
8e6cb5e24d | ||
|
|
ed629d420d | ||
|
|
fd568682c3 | ||
|
|
dbb9849b8c | ||
|
|
776af44782 | ||
|
|
69dacd0594 | ||
|
|
f17a2a00b1 | ||
|
|
1a64420e01 | ||
|
|
49b9a2c8df | ||
|
|
6c251417d5 | ||
|
|
8c0c38c8b3 | ||
|
|
29eee4e858 | ||
|
|
454c6f713d | ||
|
|
dd715e18ec | ||
|
|
f067c7d44e | ||
|
|
c1023eaf18 | ||
|
|
b091f59311 | ||
|
|
f98ad822e3 | ||
|
|
917c1ddbad | ||
|
|
d81d2f691d | ||
|
|
1ad118a38f | ||
|
|
9d1648cee7 | ||
|
|
929167366a | ||
|
|
353ccf5e6c | ||
|
|
cfc1fa4fae | ||
|
|
d881f47088 | ||
|
|
84013c4452 | ||
|
|
d9dc1a988c | ||
|
|
5fd14348aa | ||
|
|
57a250a20b | ||
|
|
81add75258 | ||
|
|
0145282a10 | ||
|
|
ae0cb5d483 | ||
|
|
e7eeecd762 | ||
|
|
7d308a4ac5 | ||
|
|
ebcb7f6108 | ||
|
|
e03c7b1b7c | ||
|
|
70a20b4d26 | ||
|
|
d143470d77 | ||
|
|
30d6639a98 | ||
|
|
92cfd3b4f9 | ||
|
|
7659ea21c7 | ||
|
|
4e740736e7 | ||
|
|
4f44d9e25a | ||
|
|
2ae2d52289 | ||
|
|
635191970f | ||
|
|
e370443a3f | ||
|
|
738ff2b65f | ||
|
|
cef8c908d0 | ||
|
|
edc0cdbc10 | ||
|
|
89e116961c | ||
|
|
71180f97e7 | ||
|
|
be8d9d88d8 | ||
|
|
35de3720e9 | ||
|
|
4a7e2054db | ||
|
|
fe75047988 | ||
|
|
f73fb5307f | ||
|
|
172597a68d | ||
|
|
f05169e457 | ||
|
|
b171497466 | ||
|
|
61375fb785 | ||
|
|
04cc4edf26 | ||
|
|
0101a12fba | ||
|
|
e094fb1547 | ||
|
|
e0a44a901a | ||
|
|
d4f9abeefd | ||
|
|
0338d4595b | ||
|
|
7328c3fe86 | ||
|
|
bf288ff6f5 | ||
|
|
ef4c4024bc | ||
|
|
6f24cca4b1 | ||
|
|
bf59a003bb | ||
|
|
2298a40327 | ||
|
|
61d22bb8b4 | ||
|
|
eb3bbff883 | ||
|
|
03c1379d1b | ||
|
|
1635093341 | ||
|
|
93dcc4550a | ||
|
|
ab55da47b6 | ||
|
|
17971a5a3a | ||
|
|
e0b90e8f02 | ||
|
|
ea014d1362 | ||
|
|
6ef302b8a1 | ||
|
|
1e7afe3bec | ||
|
|
48508044d6 | ||
|
|
a4ad66f1c8 | ||
|
|
0f85a959bb | ||
|
|
d9a8adda1d | ||
|
|
ef4a8648a9 | ||
|
|
8bb72aa175 | ||
|
|
fa05dcf6ee | ||
|
|
8affa26858 | ||
|
|
856cb48170 | ||
|
|
1239fca567 | ||
|
|
6592a9afc7 | ||
|
|
7ffd96f425 | ||
|
|
906d9a47b1 | ||
|
|
0dff07dc8b | ||
|
|
9261d17dbe | ||
|
|
7d8a18bd20 | ||
|
|
29cb13cba6 | ||
|
|
52f375ac1e | ||
|
|
73f184df7e | ||
|
|
bbd631f08f | ||
|
|
cf315fb6ba | ||
|
|
c03a2caac2 | ||
|
|
9c2bc0b825 | ||
|
|
fcda8985fb | ||
|
|
14082f9332 | ||
|
|
83401eb245 | ||
|
|
022a98d4a4 | ||
|
|
bb1cca713d | ||
|
|
1d80a8d676 | ||
|
|
aa9c032176 | ||
|
|
baa8f229f7 | ||
|
|
ab1b99a4d9 | ||
|
|
11ff1880e5 | ||
|
|
71bf040715 | ||
|
|
6a04c6c7bd | ||
|
|
8530528dd5 | ||
|
|
e1d8992ec5 | ||
|
|
b03311e99d | ||
|
|
0c1138ee60 | ||
|
|
8a45292e24 | ||
|
|
d1712e9589 | ||
|
|
ec21afb426 | ||
|
|
882bff292e | ||
|
|
f3843a6b18 | ||
|
|
597c6c111d | ||
|
|
ac4957e15c | ||
|
|
f3915592b2 | ||
|
|
8a292fd9f3 | ||
|
|
f34936d9be | ||
|
|
d4367457a9 | ||
|
|
b4a43f1b85 | ||
|
|
3674808244 | ||
|
|
44cc978a94 | ||
|
|
f441ec2644 | ||
|
|
0ae13a2650 | ||
|
|
ee6596e21c | ||
|
|
a63147beda | ||
|
|
cdcf5808db | ||
|
|
d501c78795 | ||
|
|
e61ddc59c4 | ||
|
|
46e4f047de | ||
|
|
9cff5f2c5e | ||
|
|
5f031453d3 | ||
|
|
028216021f | ||
|
|
d6544302d3 | ||
|
|
bc529c5d09 | ||
|
|
354d1ddd5c | ||
|
|
28bf4e6e95 | ||
|
|
33b7faa17d | ||
|
|
ac731c0ff7 | ||
|
|
fd34dda6c1 | ||
|
|
16fca913de | ||
|
|
f989a82eb0 | ||
|
|
d410ecd34c | ||
|
|
48569265e2 | ||
|
|
ad43acf843 | ||
|
|
71782a6aa5 | ||
|
|
6df24d1a40 | ||
|
|
e283b0a1ce | ||
|
|
fa15193011 | ||
|
|
dff14c19f8 | ||
|
|
6103595b9c | ||
|
|
01c436ae15 | ||
|
|
c3a2539445 | ||
|
|
664ba8735f | ||
|
|
937036aa64 | ||
|
|
47cdf2b755 | ||
|
|
ef270764f3 | ||
|
|
a6edd40de3 | ||
|
|
4219c2c2ab | ||
|
|
4785de1a34 | ||
|
|
cbec624cd8 | ||
|
|
e7bde18f71 | ||
|
|
e9eeaee520 | ||
|
|
d5ec9fb38d | ||
|
|
233a776c41 | ||
|
|
d5f1c233bf | ||
|
|
af732d0495 | ||
|
|
04a87f951b | ||
|
|
9cc98919fa | ||
|
|
e42600632f | ||
|
|
93aa3fed16 | ||
|
|
49e76d1486 | ||
|
|
965bed7c4f | ||
|
|
317c6ba04c | ||
|
|
41d127934b | ||
|
|
915638d17a | ||
|
|
f73355abcc | ||
|
|
8f9da95e5e | ||
|
|
0935aa9221 | ||
|
|
aaa073afed | ||
|
|
9b606fb823 | ||
|
|
15d91c558b | ||
|
|
ffc5a78139 | ||
|
|
f8693074a3 | ||
|
|
1629f4f3f0 | ||
|
|
10baab38d3 | ||
|
|
e458c11e66 | ||
|
|
a07e3301b1 | ||
|
|
2b4d13c5b1 | ||
|
|
755e42836c | ||
|
|
ad743b0e96 | ||
|
|
0544901357 | ||
|
|
bc94633180 | ||
|
|
16415404c7 | ||
|
|
702d12d801 | ||
|
|
46eb01604c | ||
|
|
af36682a7e | ||
|
|
03d9e3db69 | ||
|
|
b03b3fccc7 | ||
|
|
9414e2979d | ||
|
|
865664425b | ||
|
|
b060cf30f4 | ||
|
|
5352ed837a | ||
|
|
d4d54a1ff5 | ||
|
|
ea82a6715f | ||
|
|
d356e32bdf | ||
|
|
6193d22d2e | ||
|
|
50c9568073 | ||
|
|
9d05ed41b2 | ||
|
|
e3f7e0a23a | ||
|
|
da2f0803ce | ||
|
|
6dde5e5ee9 | ||
|
|
494df21bdf | ||
|
|
e44bbbec63 | ||
|
|
c19f49a284 | ||
|
|
d3f2c110e4 | ||
|
|
09628178e4 | ||
|
|
5630a01a8c | ||
|
|
40b3a49c90 | ||
|
|
431b87b94a | ||
|
|
828c3d21d2 | ||
|
|
f0e75481d5 | ||
|
|
8bb7e1cf24 | ||
|
|
e117a6bd04 | ||
|
|
ac44fa63c8 | ||
|
|
bb639dbf32 | ||
|
|
64c890005a | ||
|
|
19088ba8bc | ||
|
|
ec5f7033a4 | ||
|
|
eb60f68df2 | ||
|
|
d72e671d8b | ||
|
|
3462357481 | ||
|
|
2b4da35b1c | ||
|
|
86d68c9875 | ||
|
|
cfff8ee8be | ||
|
|
af10cef5f0 | ||
|
|
a1a42736ed | ||
|
|
b533a11bee | ||
|
|
afed9e9646 | ||
|
|
74b477ac82 | ||
|
|
bf74c8f108 | ||
|
|
04c7f5283e | ||
|
|
d1d53ba45c | ||
|
|
dfadb8f189 | ||
|
|
5335c133a3 | ||
|
|
8fcb5daedf | ||
|
|
ede70a78d6 | ||
|
|
23056cbb45 | ||
|
|
29e9e70ac9 | ||
|
|
d0b711513e | ||
|
|
7dbee74e85 | ||
|
|
4e7a3b1805 | ||
|
|
3c7b244f18 | ||
|
|
e50f5c52c5 | ||
|
|
baf91658d6 | ||
|
|
fda035d8d6 | ||
|
|
a4a53e1a11 | ||
|
|
aad3e52d72 | ||
|
|
cb778a9af5 | ||
|
|
3af9732c84 | ||
|
|
00e754ed9f | ||
|
|
d893f44833 | ||
|
|
4839f41809 | ||
|
|
c801409c94 | ||
|
|
1c4a01d52c | ||
|
|
880b0a883a | ||
|
|
4b77662e3b | ||
|
|
a5ff7ab8df | ||
|
|
04a6e6d13b | ||
|
|
5c2e878c64 | ||
|
|
f9bada5af8 | ||
|
|
80d66f8f68 | ||
|
|
98b35b87a9 | ||
|
|
b1d6605e8f | ||
|
|
2cd3bde348 | ||
|
|
f2454775b9 | ||
|
|
a53d1e00ee | ||
|
|
523a964ac8 | ||
|
|
f8cf7a1054 | ||
|
|
4d8a40c0b5 | ||
|
|
9754da15d6 | ||
|
|
0458d32089 | ||
|
|
ee4e8eb994 | ||
|
|
b8d619dde7 | ||
|
|
00c86fed42 | ||
|
|
6d5af85eee | ||
|
|
1ae2d33714 | ||
|
|
71d550260f | ||
|
|
1092bae05c | ||
|
|
e611f04f41 | ||
|
|
3492cd93d0 | ||
|
|
947ae11fee | ||
|
|
cdd6ed548c | ||
|
|
fc940d0017 | ||
|
|
b38a55ca7c | ||
|
|
8e739d5240 | ||
|
|
71f8bb7e9a | ||
|
|
ebfaf11f4a | ||
|
|
4a6cec3ecc | ||
|
|
1abce148a3 | ||
|
|
e0cc7f17b2 | ||
|
|
264e5c9979 | ||
|
|
a6a1ce5c9e | ||
|
|
6ac7ab162f | ||
|
|
5e660311d2 | ||
|
|
4a49ff3083 | ||
|
|
7f92c8c4c6 | ||
|
|
85ac8177ad | ||
|
|
03bd1fb91d | ||
|
|
eb5b16614c | ||
|
|
a5873a3970 | ||
|
|
e48ca80dee | ||
|
|
e7c9e22350 | ||
|
|
b8e7d83f19 | ||
|
|
4c304c0013 | ||
|
|
7249350757 | ||
|
|
50a8dc1383 | ||
|
|
71491138aa | ||
|
|
ca87f8ada3 | ||
|
|
835b3f3d67 | ||
|
|
263dffeedc | ||
|
|
e1063944f6 | ||
|
|
2380018457 | ||
|
|
a9db631836 | ||
|
|
b45f138ff1 | ||
|
|
b239694d34 | ||
|
|
0c2774f367 | ||
|
|
ea4647bc74 | ||
|
|
6ec21d3a58 | ||
|
|
44a4a56024 | ||
|
|
2ea027d3a8 | ||
|
|
69b688b357 | ||
|
|
cdeae04238 | ||
|
|
a556deec78 | ||
|
|
06a63db4cf | ||
|
|
06a325a05f | ||
|
|
68e9bbe350 | ||
|
|
3bd9e369cd | ||
|
|
57357a728c | ||
|
|
e263295720 | ||
|
|
0c854f2144 | ||
|
|
bdf4debdfd | ||
|
|
f557835ad0 | ||
|
|
17a7391086 | ||
|
|
e65293eb50 | ||
|
|
caa6c6c931 | ||
|
|
2e825242cd | ||
|
|
027064cbdb | ||
|
|
060f6392c7 | ||
|
|
a466f55d59 | ||
|
|
f686408808 | ||
|
|
b8c57ef10f | ||
|
|
5cc0826ca0 | ||
|
|
e703f2f3f2 | ||
|
|
f544da9e19 | ||
|
|
472eab2acd | ||
|
|
7a5d7fca6c | ||
|
|
589b45dace | ||
|
|
3273d13666 | ||
|
|
5fdbbf3722 | ||
|
|
14350ff463 | ||
|
|
0b49c961a7 | ||
|
|
39b80f26ed | ||
|
|
74b20265e4 | ||
|
|
358fddb958 | ||
|
|
5c8f6175b5 | ||
|
|
83ce5875ee | ||
|
|
f5a30c790e | ||
|
|
34415c3c17 | ||
|
|
6387fa8bc9 | ||
|
|
8ec6b699b1 | ||
|
|
551f164ec3 | ||
|
|
88bf64722f | ||
|
|
d9c998b5c4 | ||
|
|
170e19c283 | ||
|
|
7c47a8ffb7 | ||
|
|
791ab55875 | ||
|
|
bd1a9d020c | ||
|
|
71fd3b6b2b | ||
|
|
f8ac27a6cc | ||
|
|
954cd0b86a | ||
|
|
9dedeadf15 | ||
|
|
6561272f8d | ||
|
|
881e795db5 | ||
|
|
4216231ec7 | ||
|
|
333d5f7511 | ||
|
|
7be7945b29 | ||
|
|
afe882cd94 | ||
|
|
5b8a359475 | ||
|
|
df0af203f8 | ||
|
|
5b2a4ff614 | ||
|
|
ceb5f8c714 | ||
|
|
cc3359dfe5 | ||
|
|
cda6785f1d | ||
|
|
cb030be4ad | ||
|
|
b464ce86f7 | ||
|
|
80b3514e24 | ||
|
|
e33f56f9b0 | ||
|
|
f386548890 | ||
|
|
c80d87554a |
No files matched your search
@@ -0,0 +1,65 @@
|
||||
<!--
|
||||
Hi, thank you for opening an issue!
|
||||
Before hitting the button...
|
||||
|
||||
** Is this a REQUEST FOR HELP? **
|
||||
If so, please have a look at:
|
||||
- our FAQ: https://www.weave.works/docs/net/latest/faq/
|
||||
- our troubleshooting page: https://www.weave.works/docs/net/latest/troubleshooting/
|
||||
- our help page, to choose the best channel (Slack, etc.) to reach out: https://www.weave.works/help/
|
||||
|
||||
** Is this a FEATURE REQUEST? **
|
||||
If so, please search existing feature requests, and if you find a similar one, up-vote it and/or add your comments to it instead.
|
||||
If you did not find a similar one, please describe in details:
|
||||
- why: your use-case, specific constraints you may have, etc.
|
||||
- what: the feature/behaviour/change you would like to see in Weave Net
|
||||
Do not hesitate, when appropriate, to share the exact commands or API you would like, and/or to share a diagram (e.g.: asciiflow.com): "a picture is worth a thousand words".
|
||||
|
||||
** Is this a BUG REPORT? **
|
||||
Please fill in as much of the template below as you can.
|
||||
|
||||
Thank you!
|
||||
-->
|
||||
|
||||
## What you expected to happen?
|
||||
|
||||
## What happened?
|
||||
<!-- Error message, actual behaviour, etc. -->
|
||||
|
||||
## How to reproduce it?
|
||||
<!-- Specific steps, as minimally and precisely as possible. -->
|
||||
|
||||
## Anything else we need to know?
|
||||
<!-- Cloud provider? Hardware? How did you configure your cluster? Kubernetes YAML, KOPS, etc. -->
|
||||
|
||||
## Versions:
|
||||
<!-- Please paste in the output of these commands; 'kubectl' only if using Kubernetes -->
|
||||
```
|
||||
$ weave version
|
||||
$ docker version
|
||||
$ uname -a
|
||||
$ kubectl version
|
||||
```
|
||||
|
||||
## Logs:
|
||||
```
|
||||
$ docker logs weave
|
||||
```
|
||||
or, if using Kubernetes:
|
||||
```
|
||||
$ kubectl logs -n kube-system <weave-net-pod> weave
|
||||
```
|
||||
<!-- (If output is long, please consider a Gist.) -->
|
||||
<!-- Anything interesting or unusual output by the below, potentially relevant, commands?
|
||||
$ journalctl -u docker.service --no-pager
|
||||
$ journalctl -u kubelet --no-pager
|
||||
$ kubectl get events
|
||||
-->
|
||||
|
||||
## Network:
|
||||
<!-- If your problem has anything to do with one network endpoint not being able to contact another, please run the following commands -->
|
||||
```
|
||||
$ ip route
|
||||
$ ip -4 -o addr
|
||||
$ sudo iptables-save
|
||||
```
|
||||
+15
-6
@@ -37,29 +37,38 @@ coverage.html
|
||||
prog/weaver/weaver
|
||||
prog/weavedns/weavedns
|
||||
prog/weavehosts/weavehosts
|
||||
prog/weaveproxy/weaveproxy
|
||||
prog/sigproxy/sigproxy
|
||||
prog/weavewait/weavewait
|
||||
prog/weavewait/weavewait_noop
|
||||
prog/weavewait/weavewait_nomcast
|
||||
prog/weaveutil/weaveutil
|
||||
prog/*/Dockerfile.weaveworks
|
||||
prog/kube-peers/kube-peers
|
||||
prog/weave-kube/kube-peers
|
||||
prog/weave-npc/weave-npc
|
||||
prog/plugin/plugin
|
||||
testing/cover/cover
|
||||
testing/runner/runner
|
||||
releases
|
||||
.*.uptodate
|
||||
weave.tar.gz
|
||||
prog/weaveexec/weave
|
||||
weave*.tar.gz
|
||||
prog/weaver/weave
|
||||
prog/weaveexec/weavehosts
|
||||
prog/weaveexec/weaveproxy
|
||||
prog/weaveexec/weavewait
|
||||
prog/weaveexec/weavewait_noop
|
||||
prog/weaveexec/weavewait_nomcast
|
||||
prog/weaveexec/sigproxy
|
||||
prog/weaveexec/weaveutil
|
||||
prog/weaveexec/docker*.tgz
|
||||
prog/weaver/weaveutil
|
||||
prog/weaveexec/docker*
|
||||
Vagrantfile.local
|
||||
test/tls/tls
|
||||
test/tls/*.pem
|
||||
test/coverage
|
||||
test/coverage.*
|
||||
*qemu-*-static
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
prog/net-plugin/rootfs/
|
||||
|
||||
# Website
|
||||
site-build
|
||||
+60
-3
@@ -61,9 +61,6 @@
|
||||
[submodule "vendor/github.com/hashicorp/go-cleanhttp"]
|
||||
path = vendor/github.com/hashicorp/go-cleanhttp
|
||||
url = http://github.com/hashicorp/go-cleanhttp
|
||||
[submodule "vendor/github.com/appc/cni"]
|
||||
path = vendor/github.com/appc/cni
|
||||
url = https://github.com/appc/cni
|
||||
[submodule "vendor/github.com/coreos/go-iptables"]
|
||||
path = vendor/github.com/coreos/go-iptables
|
||||
url = https://github.com/coreos/go-iptables
|
||||
@@ -76,3 +73,63 @@
|
||||
[submodule "vendor/github.com/aws/aws-sdk-go"]
|
||||
path = vendor/github.com/aws/aws-sdk-go
|
||||
url = https://github.com/aws/aws-sdk-go
|
||||
[submodule "vendor/golang.org/x/sys"]
|
||||
path = vendor/golang.org/x/sys
|
||||
url = https://go.googlesource.com/sys
|
||||
[submodule "vendor/github.com/beorn7/perks"]
|
||||
path = vendor/github.com/beorn7/perks
|
||||
url = https://github.com/beorn7/perks
|
||||
[submodule "vendor/github.com/prometheus/procfs"]
|
||||
path = vendor/github.com/prometheus/procfs
|
||||
url = https://github.com/prometheus/procfs
|
||||
[submodule "vendor/github.com/prometheus/client_golang"]
|
||||
path = vendor/github.com/prometheus/client_golang
|
||||
url = https://github.com/prometheus/client_golang
|
||||
[submodule "vendor/github.com/prometheus/client_model"]
|
||||
path = vendor/github.com/prometheus/client_model
|
||||
url = https://github.com/prometheus/client_model
|
||||
[submodule "vendor/github.com/prometheus/common"]
|
||||
path = vendor/github.com/prometheus/common
|
||||
url = https://github.com/prometheus/common
|
||||
[submodule "vendor/github.com/golang/protobuf"]
|
||||
path = vendor/github.com/golang/protobuf
|
||||
url = https://github.com/golang/protobuf
|
||||
[submodule "vendor/github.com/matttproud/golang_protobuf_extensions"]
|
||||
path = vendor/github.com/matttproud/golang_protobuf_extensions
|
||||
url = https://github.com/matttproud/golang_protobuf_extensions
|
||||
[submodule "vendor/k8s.io/client-go"]
|
||||
path = vendor/k8s.io/client-go
|
||||
url = https://github.com/kubernetes/client-go
|
||||
[submodule "vendor/github.com/pkg/errors"]
|
||||
path = vendor/github.com/pkg/errors
|
||||
url = https://github.com/pkg/errors
|
||||
[submodule "vendor/github.com/spf13/cobra"]
|
||||
path = vendor/github.com/spf13/cobra
|
||||
url = https://github.com/spf13/cobra
|
||||
[submodule "vendor/github.com/spf13/plag"]
|
||||
path = vendor/github.com/spf13/plag
|
||||
url = https://github.com/spf13/pflag
|
||||
[submodule "vendor/github.com/spf13/pflag"]
|
||||
path = vendor/github.com/spf13/pflag
|
||||
url = https://github.com/spf13/pflag
|
||||
[submodule "vendor/github.com/mgutz/ansi"]
|
||||
path = vendor/github.com/mgutz/ansi
|
||||
url = https://github.com/mgutz/ansi
|
||||
[submodule "vendor/github.com/estesp/manifest-tool"]
|
||||
path = vendor/github.com/estesp/manifest-tool
|
||||
url = https://github.com/estesp/manifest-tool
|
||||
[submodule "vendor/github.com/weaveworks/common"]
|
||||
path = vendor/github.com/weaveworks/common
|
||||
url = https://github.com/weaveworks/common
|
||||
[submodule "vendor/github.com/containernetworking/cni"]
|
||||
path = vendor/github.com/containernetworking/cni
|
||||
url = https://github.com/containernetworking/cni
|
||||
[submodule "vendor/google.golang.org/grpc"]
|
||||
path = vendor/google.golang.org/grpc
|
||||
url = https://github.com/grpc/grpc-go
|
||||
[submodule "vendor/github.com/docker/go-units"]
|
||||
path = vendor/github.com/docker/go-units
|
||||
url = https://github.com/docker/go-units
|
||||
[submodule "vendor/golang.org/x/net"]
|
||||
path = vendor/golang.org/x/net
|
||||
url = https://github.com/golang/net
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# IMPORTANT: Please only use GLOBs to ignore files and directories.
|
||||
|
||||
# Do not lint the below bash scripts in Weave Net, in order to
|
||||
# avoid breaking the build on make lint:
|
||||
bin/circle-deploy-issues
|
||||
bin/circle-deploy-master
|
||||
bin/circle-teardown-pre
|
||||
bin/circle-test-teardown
|
||||
bin/circle-test-unit
|
||||
bin/install-wordepress
|
||||
bin/multiweave
|
||||
bin/publish-site
|
||||
bin/release
|
||||
build/build.sh
|
||||
prog/weave-kube/launch.sh
|
||||
prog/weaveexec/symlink
|
||||
test/*_test.sh
|
||||
test/600_proxy_docker_py.sh
|
||||
test/assert.sh
|
||||
test/config.sh
|
||||
test/gce.sh
|
||||
test/gen_coverage_reports.sh
|
||||
test/run_all.sh
|
||||
test/sanity_check.sh
|
||||
test/setup.sh
|
||||
vendor/*.sh
|
||||
weave
|
||||
@@ -0,0 +1 @@
|
||||
2.3.0
|
||||
+419
@@ -1,3 +1,422 @@
|
||||
## Release 2.0.5
|
||||
|
||||
Bug fixes
|
||||
|
||||
* Fix /etc/hosts inside containers so the container's name resolves to
|
||||
its Weave Net address #3136,#3138
|
||||
* New weave-kube config for Kubernetes 1.7 and 1.8 which resolves an
|
||||
intermittent conflict with kube-proxy that could break Weave Net
|
||||
until reboot #2998,#3134
|
||||
* Remove persistence file in `weave reset` when using Docker plugin V2 #3103,#3114
|
||||
|
||||
Build and test
|
||||
|
||||
* Some trivial changes to placate go-lint #3137
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/63?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.4
|
||||
|
||||
Bug fixes
|
||||
|
||||
* weave-npc failed on Centos 7, due to older 'ipset' version in kernel #3099,#3100
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/62?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.3
|
||||
|
||||
Bug fixes
|
||||
|
||||
* Weave-npc would crash on a policy with no 'from' part - regression introduced in 2.0.2 #3095,#3096,#3097
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/61?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fix race condition in weave-npc which would intermittently block all traffic for a namespace #3057,#3059
|
||||
* Ensure Fast Datapath works on machines that need to mount the kernel module dynamically #3080
|
||||
* Regression: weave-npc would block everything if `kubelet --hostname-override` was used #3049,#3051
|
||||
* Fix netfilter rules to block containers from accessing the Weave Net control endpoint #3093
|
||||
* If DNS server is off then disable proxy DNS registration, to avoid spurious errors #3054,#3088
|
||||
* Add comments to each iptables rule and ipset, to help when troubleshooting #3064
|
||||
* Remove code that checked for an outdated fallback address for Kubernetes api-server #3071
|
||||
* Add a label to the weavedb image so it can be filtered out by tools #3066
|
||||
* Fix various build and continuous-integration failures #3061
|
||||
* Print 'help' text faster in the weave script #3056
|
||||
* Add an option to create continuous integration hosts in different ways #3060
|
||||
* Remove remnants of the pre-2.0 proxy and plugin from build and test #3035,#3036
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/60?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fall back to slower data path (`sleeve`), rather than crashing, when the machine lacks VXLAN support (required for “fast data path”, `fastdp`) #3043
|
||||
* Fix bug in processing of arguments when Docker has TLS enabled, rather than crashing with invalid peers list, e.g. `lookup --tlsverify: no such host` #3039
|
||||
* Add `kube-system` namespace back to `weave-kube`'s YAMLs, preventing omissions leading to errors like `error contacting APIServer: the server does not allow access to the requested resource` #3033,#3042
|
||||
* Fix release script to prevent ARM64 binaries to end up in AMD64 `net-plugin`, leading to `Error response from daemon: dial unix /run/docker/plugins/<id>/weave.sock: connect: no such file or directory` when installing `net-plugin` #3045
|
||||
* `weave reset` and `weave rmpeer` now only contact Weave Cloud when Weave Net is configured with a Weave Cloud token, preventing unnecessary requests and potentially confusing `401 Unauthorized` errors in Weave Net’s logs #3044
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/58?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.0
|
||||
|
||||
New Features
|
||||
|
||||
Peer Discovery via Weave Cloud
|
||||
|
||||
You can now get all your Weave Net peers to find each other via the
|
||||
Weave Cloud service, instead of maintaining a list of peers at
|
||||
startup. #2799,#2827
|
||||
|
||||
See the [docs
|
||||
page](https://github.com/weaveworks/weave/blob/master/site/using-weave/weave-cloud.md)
|
||||
for more details
|
||||
|
||||
New Docker Plugin
|
||||
|
||||
Docker has a [new plugin
|
||||
system](https://docs.docker.com/engine/extend/) which improves the
|
||||
installation UX and solves some issues around startup. This means
|
||||
Weave Net 2.0 can now run with Docker in "swarm mode" and supports the
|
||||
`docker service` command. #2396,#2397,#2651,#2727,#2805,#2816,#2905,
|
||||
#2906,#2929,#2932,#2945,#2950,#2956,#2963,#2964,#2966,#3019
|
||||
|
||||
The previous Docker Plugin is still available and can be installed as before.
|
||||
|
||||
All of Weave Net now runs in one container
|
||||
|
||||
Previously we had three separate containers for routing, Docker API
|
||||
proxy and Docker plugin. Running everything in one simplifies start-up
|
||||
and removes the need to detect various error
|
||||
conditions. #1642,#2897,#2936,#2945,#2946,#2951,#2960
|
||||
|
||||
The individual commands ‘weave launch-router’, ‘weave launch-plugin’,
|
||||
etc., have been removed. You can turn off the plugin and proxy with
|
||||
new command-line options. In keeping with Semantic Versioning, we have
|
||||
changed the major version number for this release.
|
||||
|
||||
Other new features
|
||||
|
||||
* Kubernetes configuration now comes from our “Launch Generator” that
|
||||
allows different options to be selected via
|
||||
URL. #2754,#2903,#3000,#3001
|
||||
* `weave-kube` now stores data about IP allocation in `/var/lib/weave`
|
||||
on the host instead of in a Kubernetes volume. This means that the
|
||||
data will persist across pod deletion and re-creation, e.g. during
|
||||
an upgrade of Weave Net, which makes restarts more
|
||||
reliable. #2610,#2967
|
||||
* `weave-kube` turned on rolling updates, so careful manual handling
|
||||
of updates is no longer required. #3024
|
||||
|
||||
Bug fixes
|
||||
|
||||
* Kubernetes Network Policies which allowed a specific set of pods to
|
||||
connect would block all pods on other hosts. Revert the change in
|
||||
v1.9.6 which ignored pods on other hosts #3025,#3028
|
||||
|
||||
Features removed
|
||||
|
||||
* `weave run` has been removed. This was the original method provided
|
||||
to start containers with Weave Net, but it always required care over
|
||||
timing of start-up, and we now provide three alternative, better,
|
||||
ways. You can replicate the effect by calling `docker run` then
|
||||
`weave attach`. Similarly `weave start` and `weave restart` were
|
||||
removed. #2353,#2885
|
||||
* Everything deprecated more than one release ago has been removed, so
|
||||
if you use it now you get an error rather than a warning. This
|
||||
includes the ‘create-bridge’ command and older command-line
|
||||
arguments, e.g. `--iprange` was replaced by `--ipalloc-range`
|
||||
#2901,#2909,#2913,#2942,#2989,#2991
|
||||
|
||||
Functions moved from shell-script to Go code.
|
||||
|
||||
This enables more precise error-checking and runs a bit faster. It has
|
||||
also enabled us to shrink the size of images downloaded: `weave-kube`
|
||||
is 101MB compared to 163MB previously #2953,#2954,#2974
|
||||
|
||||
Specific items that moved from shell-script to Go:
|
||||
* Setting up the `weave` bridge #1958,#2975,#2977,#2978
|
||||
* Container attachment #2947
|
||||
* Creation of the ’weave’ default plugin network #2920
|
||||
|
||||
Minor improvements
|
||||
|
||||
* You can now restart the Weave Net router without requiring the proxy to be enabled #2112
|
||||
* Plugin (legacy version) now respects `--ipalloc-default-subnet` option #2919
|
||||
* The `weave` script now detects and issues an error message if
|
||||
`weave-kube` is running and you attempt to launch again from the script. #2709/#2966
|
||||
* It is now possible to choose the the MAC address of the `weave`
|
||||
bridge using `--name`, in case your hosts have identical unique
|
||||
IDs. #2900
|
||||
* Relaxed Kubernetes tolerations for Weave Net's daemonset in order to
|
||||
match any node (previously, only taints directed at master). #3018
|
||||
* Kubernetes' `seLinuxOptions` configuration is now empty by default,
|
||||
to reduce spurious failures on hosts not using seLinux. #3001
|
||||
* Improved reliability of namespace changes via `nsenter`. #2992
|
||||
* `weave ps` now fetches the list container IDs internally, rather
|
||||
than calling out to `docker ps` #2814,#2898
|
||||
* at startup, actively remove dead containers’ Weave Net IP addresses from IPAM #3013
|
||||
* at startup, only check live containers to see if they have an
|
||||
existing Weave Net IP address #2815,#2829
|
||||
* Weave Net CNI plugin now logs but does not raise an error if
|
||||
anything goes wrong during network interface delete, to be more
|
||||
compatible with Kubernetes 1.6. #2928
|
||||
* Stop running a shell in “privileged” mode when it’s only writing a file #2838
|
||||
* New internal REST endpoint to return all IP address mappings. #1350
|
||||
* Changed the wording where we do not log the password #2833
|
||||
* Fixed typo in plugin error messages #2894
|
||||
|
||||
Build and test
|
||||
|
||||
* Weave Net is now built with Go version 1.8, which has better code
|
||||
generation and garbage collection #2914
|
||||
* During smoke-tests, use a webserver instead of just `ping` so we get
|
||||
a more realistic test that the Weave network is working #2918
|
||||
* When installing dependencies for the build container, use a
|
||||
keyserver port that's better for firewalls #2812
|
||||
* Kubernetes test script now scales up to more hosts, and works with
|
||||
Kubernetes 1.6 #2837,#2853,#2923
|
||||
* Other minor build improvements and refactoring #2760,#2910
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/49?closed=1).
|
||||
|
||||
|
||||
## Release 1.9.8
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fix weave-npc blocking NodePort and any other non-local access #3011,#3014
|
||||
* Fix bug where IPAM would duplicate a fixed IP address assigned via Docker plugin #3003,#3010
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/56?closed=1).
|
||||
|
||||
|
||||
## Release 1.9.7
|
||||
|
||||
This is identical to 1.9.6 with one additional bug-fix:
|
||||
|
||||
* weave-npc would block everything if `kubelet --hostname-override` was used #2995,#2996
|
||||
|
||||
## Release 1.9.6
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Ensure that Kubernetes pods can contact a service implemented within
|
||||
the same pod, by turning on "hairpin mode". This is required because
|
||||
of a quiet change between Kubernetes 1.5 and 1.6. #2993
|
||||
* Network Policy Controller (`weave-npc`) now checks local addresses
|
||||
only, so it doesn't interfere with cross-cluster traffic. It should
|
||||
be more efficient too #2622,#2973,#2979
|
||||
* Stop reporting back to Kubernetes any issues encountered when
|
||||
deleting a pod's network interface. This is required because
|
||||
of a quiet change between Kubernetes 1.5 and 1.6. #2921,#2928
|
||||
* Fixed an issue whereby `weave-npc` couldn't start because one
|
||||
`ipset` was referring to another one and could not be destroyed #2915,#2949
|
||||
* Improved the code which checks whether the kernel supports `ipset` #2934,#2935
|
||||
* `weave-npc` now creates ipsets with only valid xml characters in the
|
||||
name #2958,#2959
|
||||
|
||||
Build and Testing
|
||||
|
||||
* In build container use cross-compilers from debian package
|
||||
repository, so they match other components #2940
|
||||
* Pin the version of the linting tool `shfmt` so the set of things it
|
||||
checks is stable #2987
|
||||
* Fix lint error in script that runs smoke-tests #2962
|
||||
* Moved website publishing from Wordpress to Netlify #2986
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/55?closed=1).
|
||||
|
||||
## Release 1.9.5
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Improve log messages generated if "hairpin" conditions are detected,
|
||||
to make clear which kind is likely to cause problems #2808/#2926
|
||||
* Filter out IPv6 peer addresses from Kubernetes; Weave Net currently
|
||||
only supports IPv4 #2904/#2912
|
||||
* Fix rare crash during initialization of weave-kube #2893/#2892
|
||||
* Include overlay and encryption modes in checkpoint reports, in case
|
||||
this is relevant to a version upgrade #2771/#2907
|
||||
|
||||
Build and Testing
|
||||
|
||||
* Ensure CI build can run gcloud tools #2887
|
||||
* Prevent kubeadm from upgrading Kubernetes if we are trying to test an older version #2886
|
||||
* Upgrade build scripts to support Kubernetes 1.6 #2880
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/54?closed=1).
|
||||
|
||||
## Release 1.9.4
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Support Kubernetes 1.6 by creating a new DaemonSet #2777,#2801
|
||||
* Support Kubernetes 1.6 by allowing CNI callers to send a
|
||||
network-delete request for a container that is not running or has
|
||||
never been attached to the network #2850
|
||||
* Leave non-weave ipsets alone in Network Policy Controller (e.g. when
|
||||
running Weave Net alonside keepalived-vip) #2751,#2846
|
||||
* Fix various small issues revealed by 'staticcheck' tool #2843,#2857
|
||||
* Avoid leaving 'defunct' processes when weave-kube container restarts #2836,#2845
|
||||
* When using the CNI plugin with a non-standard network configuration
|
||||
file, the weave bridge could get the same IP as a container, if
|
||||
'weave expose' hadn't run at that point #2839,#2856
|
||||
|
||||
Build and Testing
|
||||
|
||||
* Check that no defunct processes remain after each test #2852
|
||||
* Update build and test scripts to work with Kubernetes 1.6 beta #2851
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/53?closed=1).
|
||||
|
||||
## Release 1.9.3
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed a race condition in Fast Datapath encrypted connections which could lead Weave Net to crash: #2824, #2825.
|
||||
|
||||
## Release 1.9.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fix a weave-kube bug when `br_netfilter` or `xt_set` module is compiled into
|
||||
kernel #2820/#2821
|
||||
* Detect the absence of the required `xt_set` kernel module #2821
|
||||
|
||||
## Release 1.9.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fix a race condition when the Weave Net container is restarted
|
||||
which could allow a new container to be allocated the same IP
|
||||
address as an existing one #2784,#2787
|
||||
* Handle the message type received when a pod has been deleted during
|
||||
Kubernetes api-server fail-over #2772,#2773
|
||||
* Make weave-kube work with `dockerd --iptables=false` #2726
|
||||
* Ensure we have the right kernel modules loaded for Network Policy in weave-kube #2819
|
||||
* Reference-count addresses in Network Policy Controller, to avoid
|
||||
errors when updates come in an unexpected order #2792,#2795
|
||||
* Allow the soft connection limit to be raised in weave-kube, so
|
||||
larger clusters can be created #2781
|
||||
* WeaveDNS was incorrectly case-sensitive for reverse DNS lookups #2817,#2818
|
||||
|
||||
Build and Testing
|
||||
|
||||
* Scripts to create VMs to run automated tests were rewritten to use
|
||||
Terraform and Ansible, to make it much easier to test with different
|
||||
versions of components such as Docker and Kubernetes #2647,#2694,#2775,#2796
|
||||
* Upgrade to latest Weaveworks common build-tools #2780
|
||||
* Improve encryption tests #2793
|
||||
* Update vishvananda/netlink library to bring in changes we had previously forked #2790
|
||||
* Slight change to the build container to avoid permission errors and slow builds #2761,#2802
|
||||
|
||||
## Release 1.9.0
|
||||
|
||||
Highlights:
|
||||
|
||||
* Encryption is now available for Fast Datapath connections, which
|
||||
greatly improves the performance. #1644,#2687
|
||||
* We now build images for Intel/AMD 64-bit, ARM and ARM 64-bit. #2713
|
||||
* Weave Net Docker images are now labelled with description, vendor, etc. #2712
|
||||
* `weave status connections` now shows the MTU #2389,#2663
|
||||
* CNI plugin is now a stand-alone binary that does not depend on Docker #2594,#2662
|
||||
* Embedded docker client updated to version 1.10.3 #2395
|
||||
|
||||
**NOTE:** The move to multi-architecture required that we update the
|
||||
embedded Docker client, and this has the effect that this release of
|
||||
Weave Net will not work with Docker installations older than
|
||||
1.10.
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/issues?q=milestone%3A1.9.0).
|
||||
|
||||
## Release 1.8.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed a bug where looping flows were installed which caused high CPU
|
||||
usage #2650, #2674
|
||||
* Fixed a bug where Kubernetes master could not contact pods #2673, #2683
|
||||
* Fixed a bug where weave-kube was crashing in a loop due to invalid
|
||||
Weave bridge state #2657
|
||||
* Fixed a bug where iptables NAT rules were not appended due to
|
||||
"temporary unavailable" iptables error #2679
|
||||
* Added a detection of enabling the hairpin mode on the Weave bridge port
|
||||
which caused installation of looping flows #2674
|
||||
* Added a detection of overlaps between Weave and the host IP address
|
||||
ranges when launching weave-kube #2669, #2672
|
||||
* Added logging of connections blocked by weave-npc #2546, #2573
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.8.2).
|
||||
|
||||
## Release 1.8.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed weave-npc crash from Succeeded/Failed pods #2632,#2658
|
||||
* Fixed occasional failure to create Weave bridge on node reboot
|
||||
#2617,#2637
|
||||
* Fixed a bug where weave-kube would fail to install when run with
|
||||
unrelease snapshot builds #2642
|
||||
* Improved conformance to CNI spec by not releasing IP addresses when
|
||||
a container dies #2643
|
||||
* Improved troubleshooting of install failure by creating CNI config
|
||||
after Weave Net is up #2570
|
||||
* "up to date" shown even when the version check was blocked by
|
||||
firewall #2537,#2565,#2645
|
||||
* "Unable to claim" message on re-launching Weave after using CNI
|
||||
#2548,#2577
|
||||
* Eliminated spurious IP reclaim operations when IPAM was disabled
|
||||
#2567,#2644
|
||||
* Include `jq` tool in our build VM configuration #2656
|
||||
|
||||
## Release 1.8.0
|
||||
|
||||
Highlights:
|
||||
|
||||
* Exposed network policy controller Prometheus metrics
|
||||
weaveworks/weave-npc#23, #2595, #2549
|
||||
* Exposed router Prometheus metrics #2535, #2547, #2523, #2579, #2578,
|
||||
#2568, #2560, #2561
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.8.0).
|
||||
|
||||
## Release 1.7.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed an error where the Docker plugin could fail to attach a
|
||||
container with a `bridge "weave" not present` error #2540/#2541
|
||||
* Fixed `cannot connect to itself` panic on weave launch #2527/#2543
|
||||
* Fixed inferred initial peer count when target peers includes self
|
||||
#2481/#2543
|
||||
* Fixed compilation on Raspberry Pi #2506/#2538
|
||||
|
||||
## Release 1.7.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Added utility to recover container addresses without asking Docker #2531
|
||||
* Improved CI #2274
|
||||
|
||||
## Release 1.7.0
|
||||
|
||||
Highlights:
|
||||
|
||||
* weave-kube - Deploy Weave Net to Kubernetes with a single command
|
||||
* weave-npc - Kubernetes network policy controller implementation
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.7.0).
|
||||
|
||||
## Release 1.6.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
DOCKER_VERSION=17.06
|
||||
KUBERNETES_VERSION=1.6.2
|
||||
KUBERNETES_CNI_VERSION=0.5.1
|
||||
@@ -1,7 +1,5 @@
|
||||
PUBLISH=publish_weave publish_weaveexec publish_plugin
|
||||
|
||||
.DEFAULT: all
|
||||
.PHONY: all exes testrunner update tests lint publish $(PUBLISH) clean clean-bin prerequisites build run-smoketests
|
||||
.PHONY: all exes testrunner update tests lint publish-one-arch $(PUBLISH) clean clean-bin clean-work-dir prerequisites build run-smoketests
|
||||
|
||||
# If you can use docker without being root, you can do "make SUDO="
|
||||
SUDO=$(shell docker info >/dev/null 2>&1 || echo "sudo -E")
|
||||
@@ -10,43 +8,136 @@ RM=--rm
|
||||
RUN_FLAGS=-ti
|
||||
COVERAGE=
|
||||
|
||||
DOCKERHUB_USER=weaveworks
|
||||
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
||||
# This specifies the architecture we're building for
|
||||
ARCH?=amd64
|
||||
|
||||
# We're using QEMU to be able to build Docker images for other architectures
|
||||
# from amd64 machines.
|
||||
QEMU_VERSION=v2.7.0
|
||||
|
||||
# A list of all supported architectures here. Should be named as Go is naming platforms
|
||||
# All supported architectures must have an "ifeq" block below that customizes the parameters
|
||||
ALL_ARCHITECTURES=amd64 arm arm64
|
||||
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=x86_64
|
||||
|
||||
# The name of the alpine baseimage to use as the base for weave images
|
||||
ALPINE_BASEIMAGE?=alpine:3.4
|
||||
|
||||
# The extension for the made images
|
||||
# Specifying none means for example weaveworks/weave:latest
|
||||
ARCH_EXT?=
|
||||
|
||||
# The name of the gcc we're using for compiling C code
|
||||
CC=gcc
|
||||
|
||||
# Optional parameters that can be passed when linking C code into the Go binary
|
||||
CGO_LDFLAGS=
|
||||
endif
|
||||
ifeq ($(ARCH),arm)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=armhf/alpine:3.4
|
||||
|
||||
# arm images have the -arm suffix, for instance weaveworks/weave-arm:latest
|
||||
ARCH_EXT?=-arm
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=arm-linux-gnueabihf-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=arm
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
# Use the arm 32-bit docker client variant
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=aarch64/alpine:3.5
|
||||
|
||||
# arm64 images have the -arm64 suffix, for instance weaveworks/weave-arm64:latest
|
||||
ARCH_EXT?=-arm64
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=aarch64-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=aarch64
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for arm64 are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
|
||||
# The name of the user that this Makefile should produce image artifacts for. Can/should be overridden
|
||||
DOCKERHUB_USER?=weaveworks
|
||||
# The default version that's chosen when pushing the images. Can/should be overridden
|
||||
WEAVE_VERSION?=git-$(shell git rev-parse --short=12 HEAD)
|
||||
# Docker Store does not allow the "latest" tag.
|
||||
NET_PLUGIN_LATEST=latest_release
|
||||
|
||||
# Paths to all relevant binaries that should be compiled
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
KUBEPEERS_EXE=prog/kube-peers/kube-peers
|
||||
WEAVENPC_EXE=prog/weave-npc/weave-npc
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
|
||||
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
|
||||
WEAVEUTIL_EXE=prog/weaveutil/weaveutil
|
||||
PLUGIN_EXE=prog/plugin/plugin
|
||||
RUNNER_EXE=tools/runner/runner
|
||||
MANIFEST_TOOL_DIR=vendor/github.com/estesp/manifest-tool
|
||||
MANIFEST_TOOL_EXE=$(MANIFEST_TOOL_DIR)/manifest-tool
|
||||
TEST_TLS_EXE=test/tls/tls
|
||||
NETWORKTESTER_EXE=test/images/network-tester/webserver
|
||||
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(PLUGIN_EXE) $(TEST_TLS_EXE)
|
||||
# All binaries together in a list
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(MANIFEST_TOOL_EXE) $(NETWORKTESTER_EXE)
|
||||
|
||||
# These stamp files are used to mark the current state of the build; whether an image has been built or not
|
||||
BUILD_UPTODATE=.build.uptodate
|
||||
WEAVER_UPTODATE=.weaver.uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
||||
PLUGIN_UPTODATE=.plugin.uptodate
|
||||
WEAVER_UPTODATE=.weaver$(ARCH_EXT).uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.net-plugin$(ARCH_EXT).uptodate
|
||||
WEAVEKUBE_UPTODATE=.weavekube$(ARCH_EXT).uptodate
|
||||
WEAVENPC_UPTODATE=.weavenpc$(ARCH_EXT).uptodate
|
||||
WEAVEDB_UPTODATE=.weavedb.uptodate
|
||||
NETWORKTESTER_UPTODATE=.networktester$(ARCH_EXT).uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(PLUGIN_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE) $(NETWORKTESTER_UPTODATE)
|
||||
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin
|
||||
BUILD_IMAGE=$(DOCKERHUB_USER)/weavebuild
|
||||
# The names of the images. Note that the images for other architectures than amd64 have a suffix in the image name.
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave$(ARCH_EXT)
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec$(ARCH_EXT)
|
||||
WEAVEKUBE_IMAGE=$(DOCKERHUB_USER)/weave-kube$(ARCH_EXT)
|
||||
WEAVENPC_IMAGE=$(DOCKERHUB_USER)/weave-npc$(ARCH_EXT)
|
||||
BUILD_IMAGE=weaveworks/weavebuild
|
||||
WEAVEDB_IMAGE=$(DOCKERHUB_USER)/weavedb
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/net-plugin
|
||||
NETWORKTESTER_IMAGE=$(DOCKERHUB_USER)/network-tester
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(PLUGIN_IMAGE) $(WEAVEDB_IMAGE)
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(WEAVEKUBE_IMAGE) $(WEAVENPC_IMAGE) $(WEAVEDB_IMAGE) $(NETWORKTESTER_IMAGE)
|
||||
|
||||
WEAVE_EXPORT=weave.tar.gz
|
||||
PLUGIN_WORK_DIR="prog/net-plugin/rootfs"
|
||||
PLUGIN_BUILD_IMG="plugin-builder"
|
||||
|
||||
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
|
||||
PUBLISH=publish_weave publish_weaveexec publish_weave-kube publish_weave-npc
|
||||
PUSH_ML=push_ml_weave push_ml_weaveexec push_ml_weave-kube push_ml_weave-npc
|
||||
|
||||
WEAVE_EXPORT=weave$(ARCH_EXT).tar.gz
|
||||
|
||||
DOCKER_VERSION=1.10.3
|
||||
DOCKER_DISTRIB=prog/weaveexec/docker-$(DOCKER_VERSION).tgz
|
||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/$(WEAVEEXEC_DOCKER_ARCH)/docker-$(DOCKER_VERSION).tgz
|
||||
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
rm $@; \
|
||||
echo "\nYour go standard library was built without the 'netgo' build tag."; \
|
||||
@@ -55,42 +146,55 @@ NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
echo " sudo go install -tags netgo std"; \
|
||||
false; \
|
||||
}
|
||||
BUILD_FLAGS=-i -ldflags "-extldflags \"-static\" -X main.version=$(WEAVE_VERSION)" -tags netgo
|
||||
# The flags we are passing to go build. -extldflags -static for making a static binary,
|
||||
# -linkmode external for linking external C libraries into the binary, -X main.version for telling the
|
||||
# Go binary which version it is, -tags netgo for enforcing the native Go DNS resolver
|
||||
BUILD_FLAGS=-i -ldflags "-linkmode external -extldflags -static -X main.version=$(WEAVE_VERSION)" -tags netgo
|
||||
|
||||
PACKAGE_BASE=$(shell go list -e ./)
|
||||
|
||||
all: $(WEAVE_EXPORT)
|
||||
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go
|
||||
$(WEAVER_EXE): api/*.go plugin/*.go plugin/*/*
|
||||
$(WEAVER_EXE): proxy/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
|
||||
$(SIGPROXY_EXE): prog/sigproxy/*.go
|
||||
$(PLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/*.go common/docker/*.go net/*.go
|
||||
$(KUBEPEERS_EXE): prog/kube-peers/*.go
|
||||
$(WEAVENPC_EXE): prog/weave-npc/*.go npc/*.go npc/*/*.go
|
||||
$(TEST_TLS_EXE): test/tls/*.go
|
||||
$(RUNNER_EXE): tools/runner/*.go
|
||||
$(MANIFEST_TOOL_EXE): $(MANIFEST_TOOL_DIR)/*.go
|
||||
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
|
||||
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
|
||||
$(NETWORKTESTER_EXE): test/images/network-tester/*.go
|
||||
tests: tools/.git
|
||||
lint: tools/.git
|
||||
|
||||
ifeq ($(BUILD_IN_CONTAINER),true)
|
||||
|
||||
# This make target compiles all binaries inside of the weaveworks/build container
|
||||
# It bind-mounts the source into the container and passes all important variables
|
||||
exes $(EXES) tests lint: $(BUILD_UPTODATE)
|
||||
git submodule update --init
|
||||
# Containernetworking has another copy of vishvananda/netlink which leads to duplicate definitions
|
||||
-@rm -r vendor/github.com/containernetworking/cni/vendor
|
||||
@mkdir -p $(shell pwd)/.pkg
|
||||
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
|
||||
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
|
||||
-v $(shell pwd)/.pkg:/go/pkg \
|
||||
-e GOARCH -e GOOS -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) $@
|
||||
-e GOARCH=$(ARCH) -e CGO_ENABLED=1 -e GOOS=linux -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW -e DEBUG \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) CC=$(CC) QEMUARCH=$(QEMUARCH) CGO_LDFLAGS=$(CGO_LDFLAGS) $@
|
||||
touch $@
|
||||
|
||||
else
|
||||
|
||||
exes: $(EXES)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(PLUGIN_EXE):
|
||||
$(WEAVER_EXE):
|
||||
ifeq ($(COVERAGE),true)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | grep -v "^$(PACKAGE_BASE)/vendor/" | paste -s -d,))
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
@@ -99,7 +203,7 @@ else
|
||||
endif
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVEUTIL_EXE):
|
||||
$(WEAVEUTIL_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE) $(NETWORKTESTER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
@@ -113,76 +217,177 @@ $(WEAVEWAIT_NOMCAST_EXE):
|
||||
|
||||
# These programs need a separate rule as they fail the netgo check in
|
||||
# the main build stanza due to not importing net package
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE):
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE) $(MANIFEST_TOOL_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
tests:
|
||||
./tools/test -no-go-get
|
||||
./tools/test -no-go-get -netgo -timeout 8m
|
||||
|
||||
lint:
|
||||
./tools/lint -nocomment -notestpackage .
|
||||
./tools/lint -nocomment -notestpackage
|
||||
|
||||
endif
|
||||
|
||||
# This rule makes sure the build image is up-to-date.
|
||||
# It also makes sure the multiarch hooks are reqistered in the kernel so the QEMU emulation works
|
||||
$(BUILD_UPTODATE): build/*
|
||||
$(SUDO) docker build -t $(BUILD_IMAGE) build/
|
||||
$(SUDO) docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
touch $@
|
||||
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVER_IMAGE) prog/weaver
|
||||
# Creates the Dockerfile.your-user-here file from the template
|
||||
# Also replaces all placeholders with real values
|
||||
# If the architecture is amd64, it deletes all CROSS_BUILD lines
|
||||
# but otherwise, it only removes the "CROSS_BUILD_" placeholder and downloads QEMU
|
||||
%/Dockerfile.$(DOCKERHUB_USER): %/Dockerfile.template
|
||||
echo "DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)"
|
||||
sed -e "s|DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)|g" $^ > $@
|
||||
ifeq ($(ARCH),amd64)
|
||||
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
|
||||
sed -i "/CROSS_BUILD_/d" $@
|
||||
else
|
||||
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
|
||||
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMU_VERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(shell dirname $@)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/qemu-$(QEMUARCH)-static.sha256sum
|
||||
sed -i "s/CROSS_BUILD_//g" $@
|
||||
endif
|
||||
|
||||
# The targets below builds the weave images
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) weave $(WEAVEUTIL_EXE)
|
||||
cp $(WEAVEUTIL_EXE) prog/weaver/weaveutil
|
||||
cp weave prog/weaver/weave
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE)
|
||||
cp weave prog/weaveexec/weave
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(DOCKER_DISTRIB) $(SIGPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
cp $(WEAVEUTIL_EXE) prog/weaveexec/weaveutil
|
||||
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
tar -xf $(DOCKER_DISTRIB) usr/local/bin/docker -O > prog/weaveexec/docker
|
||||
chmod +x prog/weaveexec/docker
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
touch $@
|
||||
|
||||
$(PLUGIN_UPTODATE): prog/plugin/Dockerfile $(PLUGIN_EXE) $(WEAVEEXEC_UPTODATE)
|
||||
$(SUDO) docker build -t $(PLUGIN_IMAGE) prog/plugin
|
||||
# Builds Docker plugin.
|
||||
$(PLUGIN_UPTODATE): prog/net-plugin/launch.sh prog/net-plugin/config.json $(WEAVER_UPTODATE)
|
||||
-$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG) 2>/dev/null
|
||||
$(SUDO) docker create --name=$(PLUGIN_BUILD_IMG) $(WEAVER_IMAGE) true
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
mkdir $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker export $(PLUGIN_BUILD_IMG) | tar -x -C $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG)
|
||||
cp prog/net-plugin/launch.sh $(PLUGIN_WORK_DIR)/home/weave/launch.sh
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(WEAVE_VERSION) prog/net-plugin
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) prog/net-plugin
|
||||
touch $@
|
||||
|
||||
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEPEERS_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(KUBEPEERS_EXE) prog/weave-kube/
|
||||
$(SUDO) docker build -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
|
||||
touch $@
|
||||
|
||||
$(WEAVENPC_UPTODATE): prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) $(WEAVENPC_EXE) prog/weave-npc/ulogd.conf
|
||||
$(SUDO) docker build -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
|
||||
touch $@
|
||||
|
||||
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
|
||||
$(SUDO) docker build -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
|
||||
$(NETWORKTESTER_UPTODATE): test/images/network-tester/Dockerfile $(NETWORKTESTER_EXE)
|
||||
$(SUDO) docker build -t $(NETWORKTESTER_IMAGE) test/images/network-tester
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
|
||||
$(DOCKER_DISTRIB):
|
||||
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/docker-tgz-$(WEAVEEXEC_DOCKER_ARCH).sha256sum
|
||||
|
||||
tools/.git:
|
||||
tools/.git $(MANIFEST_TOOL_DIR)/.git:
|
||||
git submodule update --init
|
||||
|
||||
$(RUNNER_EXE): tools/.git
|
||||
make -C tools/runner
|
||||
# CODE FOR PUBLISHING THE IMAGES
|
||||
|
||||
# Push plugin
|
||||
plugin_publish: $(PLUGIN_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(WEAVE_VERSION)
|
||||
# "$(NET_PLUGIN_LATEST)" means "stable release" here, so only push that if explicitly told to
|
||||
ifeq ($(UPDATE_LATEST),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST)
|
||||
endif
|
||||
|
||||
# This target first runs "make publish" for each architecture
|
||||
# Then it pushes the manifest lists
|
||||
publish: $(addprefix sub-publish-,$(ALL_ARCHITECTURES)) $(WEAVEDB_UPTODATE)
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(PUSH_ML)
|
||||
ifeq ($(PUBLISH_WEAVEDB),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/weavedb:latest
|
||||
endif
|
||||
|
||||
sub-publish-%:
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) clean-bin
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) $(IMAGES_UPTODATE)
|
||||
$(MAKE) ARCH=$* DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) publish-one-arch
|
||||
|
||||
publish-one-arch: $(PUBLISH)
|
||||
|
||||
# This rule handles the pushing of the built images
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
# Tag :latest with the real version
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*$(ARCH_EXT) $(DOCKERHUB_USER)/$*$(ARCH_EXT):$(WEAVE_VERSION)
|
||||
# Push the image with the arch suffix for arm and arm64, and without suffix for amd64
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*$(ARCH_EXT)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# If the architecture is amd64, add the -amd64 suffix.
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION)
|
||||
# If the version is latest, tag the -amd64 with latest as well
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*-amd64:$(WEAVE_VERSION) $(DOCKERHUB_USER)/$*-amd64:latest
|
||||
endif
|
||||
# Push the image with the -amd64-suffix so BINARY-ARCH-named images exist for all arches, so manifest lists may be made and ARCH is replaceable in scripts
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*-amd64
|
||||
endif
|
||||
|
||||
# This target pushes an image, and if UPDATE_LATEST is anything but false, also updates the latest tag
|
||||
# It takes one parameter, the image name
|
||||
push_%:
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
publish: $(PUBLISH)
|
||||
ifeq ($(PUBLISH_WEAVEDB),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/weavedb:latest
|
||||
# This target pushes a manifest list; it takes one parameter, the image name.
|
||||
# The variable UPDATE_LATEST controls whether it updates the versioned tag and/or the latest tag
|
||||
$(PUSH_ML): push_ml_%: $(MANIFEST_TOOL_EXE)
|
||||
ifneq ($(UPDATE_LATEST),latest-only)
|
||||
$(MANIFEST_TOOL_EXE) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
endif
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
# Push the manifest list to :latest as well
|
||||
$(MANIFEST_TOOL_EXE) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
clean-work-dir:
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
|
||||
clean-bin:
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT) .pkg
|
||||
find prog -type f -name "Dockerfile.*" -not -name "Dockerfile.template" -print | xargs rm -f
|
||||
find prog -type f -name "*qemu-*" -print | xargs rm -f
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE) weave*.tar.gz $(DOCKER_DISTRIB) prog/weaveexec/docker .pkg
|
||||
|
||||
clean: clean-bin
|
||||
clean: clean-bin clean-work-dir
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE) $(MANIFEST_TOOL_EXE)
|
||||
|
||||
build:
|
||||
$(SUDO) go clean -i net
|
||||
@@ -191,3 +396,31 @@ build:
|
||||
|
||||
run-smoketests: all testrunner
|
||||
cd test && ./setup.sh && ./run_all.sh
|
||||
|
||||
integration-tests: all testrunner
|
||||
# Usage:
|
||||
# $ make \
|
||||
# NAME="<prefix used to name VMs and other resources>" \
|
||||
# PROVIDER="<provider among {vagrant|gcp|aws|do}>" \
|
||||
# NUM_HOSTS="<# test machines>" \
|
||||
# PLAYBOOK="<filename>" \
|
||||
# RUNNER_ARGS="<...>" \
|
||||
# TESTS="<...>" \ # Can be set to only run one or a few tests instead of the full test suite.
|
||||
# DOCKER_VERSION=<...> \
|
||||
# KUBERNETES_VERSION=<...> \
|
||||
# KUBERNETES_CNI_VERSION=<...> \
|
||||
# <...> # See also run-integration-test.sh for all variables and individual functions.
|
||||
# integration-tests
|
||||
#
|
||||
RUNNER_ARGS="-parallel" ./test/run-integration-tests.sh
|
||||
|
||||
# This target is only intended for use in Netlify CI environment for generating preview pages on feature branches and pull requests.
|
||||
# We need to obtain website repository (checked out under `site-build`) and place `site` directory into the context (`site-build/_weave_net_docs`).
|
||||
# We then run make in `site-build` and Netlify will publish the output (`site-build/_site`).
|
||||
netlify-site-preview:
|
||||
@mkdir -p site-build
|
||||
@curl --user $(WEBSITE_GITHUB_USER) --silent 'https://codeload.github.com/weaveworks/website-next/tar.gz/$(WEBSITE_BRANCH)' \
|
||||
| tar --extract --gunzip --directory site-build --strip 1
|
||||
@cp -r site site-build/_weave_net_docs
|
||||
@$(MAKE) -C site-build netlify_ensure_install
|
||||
@$(MAKE) -C site-build BUILD_ENV=netlify
|
||||
@@ -3,7 +3,6 @@
|
||||
[](https://circleci.com/gh/weaveworks/weave)
|
||||
[](https://coveralls.io/r/weaveworks/weave)
|
||||
[](https://goreportcard.com/report/github.com/weaveworks/weave)
|
||||
[](https://slack.weave.works)
|
||||
[](https://hub.docker.com/r/weaveworks/weave/)
|
||||
|
||||
# About Weaveworks
|
||||
@@ -11,9 +10,13 @@
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control
|
||||
Docker containers.
|
||||
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, and with nearly 5 million downloads to date, it enables you to get started with Docker clusters and portable apps in a fraction of the time compared with other solutions.
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, and with over 8 million downloads to date, enables you to get started with Docker clusters and portable apps in a fraction of the time compared with other solutions.
|
||||
|
||||
Other products from Weaveworks include [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container monitoring tool that automatically maps Docker containers and their interactions, and [Weave Flux](https://www.weave.works/products/weave-flux/), a microservice router that automates the access of containers as services.
|
||||
## Weave Cloud:
|
||||
|
||||
[Weave Cloud](https://www.weave.works/product/cloud/) is software-as-a-service that simplifies deployment, monitoring and management for containers and microservices. It extends and complements popular orchestrators, and enables developers and DevOps to realize faster deployments, insightful monitoring, visualization and networking.
|
||||
|
||||
Weave Cloud is built using these Open Source projects: [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container visualization tool that automatically maps Docker containers and their interactions, [Weave Cortex](https://github.com/weaveworks/cortex), a horizontally-scalable version of Prometheus, and [Weave Flux](https://www.weave.works/products/weave-flux/), a continuous deployment tool that works with Kubernetes.
|
||||
|
||||
# Weave Net
|
||||
|
||||
@@ -21,5 +24,20 @@ Weave Net creates a virtual network that connects Docker containers across multi
|
||||
|
||||
Services provided by application containers on the Weave network can be exposed to the outside world, regardless of where they are running. Similarly, existing internal systems can be opened to accept connections from application containers irrespective of their location.
|
||||
|
||||
* [Install Weave Net](https://www.weave.works/docs/net/latest/installing-weave/)
|
||||
* [Find out more about Weave Net](https://www.weave.works/products/weave-net/)
|
||||
## <a name="help"></a>Getting help
|
||||
|
||||
If you have any questions about, feedback for or a problem with Weave Net:
|
||||
|
||||
- Read [the Weave Net docs](https://www.weave.works/docs/net/latest/overview).
|
||||
- Invite yourself to the <a href="https://weaveworks.github.io/community-slack/" target="_blank"> #weave-community </a> slack channel.
|
||||
- Ask a question on the <a href="https://weave-community.slack.com/messages/general/"> #weave-community</a> slack channel.
|
||||
- Join the <a href="https://www.meetup.com/pro/Weave/"> Weave User Group </a> and get invited to online talks, hands-on training and meetups in your area.
|
||||
- Send an email to <a href="mailto:weave-users@weave.works">weave-users@weave.works</a>
|
||||
- <a href="https://github.com/weaveworks/weave/issues/new">File an issue.</a>
|
||||
|
||||
Your feedback is always welcome!
|
||||
|
||||
## Further Information
|
||||
|
||||
* [Installing Weave Net](https://www.weave.works/docs/net/latest/install/installing-weave)
|
||||
* [Weave Net Product Overview](https://www.weave.works/products/weave-net)
|
||||
Vendored
+18
-22
@@ -1,40 +1,36 @@
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
require './vagrant-common.rb'
|
||||
require File.expand_path(File.join(File.dirname(__FILE__), 'vagrant-common.rb'))
|
||||
|
||||
vm_ip = "172.16.0.3" # arbitrary private IP
|
||||
|
||||
pkgs = %w(
|
||||
docker-engine=1.10.2-0~wily
|
||||
aufs-tools
|
||||
build-essential
|
||||
ethtool
|
||||
iputils-arping
|
||||
libpcap-dev
|
||||
git
|
||||
mercurial
|
||||
bc
|
||||
)
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.box = "ubuntu/wily64"
|
||||
config.vm.box = VAGRANT_IMAGE
|
||||
|
||||
config.vm.network "private_network", ip: vm_ip
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
|
||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
|
||||
vb.memory = 2048
|
||||
configure_nat_dns(vb)
|
||||
end
|
||||
|
||||
# Disable default Vagrant shared folder, which we don't need:
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
# Keep Weave Net sources' in sync:
|
||||
config.vm.synced_folder ".", "/home/vagrant/src/github.com/weaveworks/weave"
|
||||
# Create a convenience symlink to $HOME/src/github.com/weaveworks/weave
|
||||
config.vm.provision :shell, :inline => 'ln -sf ~vagrant/src/github.com/weaveworks/weave ~vagrant/'
|
||||
|
||||
install_build_deps config.vm, pkgs
|
||||
install_go_toochain config.vm
|
||||
tweak_user_env config.vm
|
||||
tweak_docker_daemon config.vm
|
||||
# Set SSH keys up to be able to run smoke tests straightaway:
|
||||
config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/src/github.com/weaveworks/weave/test/insecure_private_key"
|
||||
# Grant permissions on sources:
|
||||
config.vm.provision :shell, :inline => 'sudo chown -R vagrant:vagrant ~vagrant/src', :privileged => false
|
||||
cleanup config.vm
|
||||
|
||||
config.vm.provision 'ansible' do |ansible|
|
||||
ansible.playbook = 'tools/config_management/setup_weave-net_dev.yml'
|
||||
ansible.extra_vars = {
|
||||
go_version: GO_VERSION
|
||||
}.merge(ansibleize(get_dependencies_version_from_file_and_env()))
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
|
||||
+28
-10
@@ -3,23 +3,41 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), nil)
|
||||
// Special token used in place of a container identifier when:
|
||||
// - the caller does not know the container ID, or
|
||||
// - the caller is unsure whether IPAM was previously told the container ID.
|
||||
//
|
||||
// While addresses are typically stored under their container IDs, when this
|
||||
// token is used, the address will be stored under its own string, something
|
||||
// which should be kept in mind if the entry needs to be removed at any point.
|
||||
const NoContainerID string = "_"
|
||||
|
||||
func (client *Client) ipamOp(ID string, op string, values url.Values) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), values)
|
||||
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")
|
||||
func ipamValues(checkAlive bool) url.Values {
|
||||
values := make(url.Values)
|
||||
if checkAlive {
|
||||
values.Set("check-alive", "true")
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func (client *Client) AllocateIPInSubnet(ID string, subnet *net.IPNet) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb("POST", fmt.Sprintf("/ip/%s/%s", ID, subnet), nil)
|
||||
// returns an IP for the ID given, allocating a fresh one if necessary
|
||||
func (client *Client) AllocateIP(ID string, checkAlive bool) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "POST", ipamValues(checkAlive))
|
||||
}
|
||||
|
||||
func (client *Client) AllocateIPInSubnet(ID string, subnet *net.IPNet, checkAlive bool) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb("POST", fmt.Sprintf("/ip/%s/%s", ID, subnet), ipamValues(checkAlive))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,12 +47,12 @@ func (client *Client) AllocateIPInSubnet(ID string, subnet *net.IPNet) (*net.IPN
|
||||
// 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")
|
||||
return client.ipamOp(ID, "GET", nil)
|
||||
}
|
||||
|
||||
// Claim a specific IP on behalf of the ID
|
||||
func (client *Client) ClaimIP(ID string, cidr *net.IPNet) error {
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/ip/%s/%s", ID, cidr), nil)
|
||||
func (client *Client) ClaimIP(ID string, cidr *net.IPNet, checkAlive bool) error {
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/ip/%s/%s", ID, cidr), ipamValues(checkAlive))
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -76,6 +77,22 @@ func (client *Client) Connect(remote string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// IsReady returns true if the API server is up and running
|
||||
// (note it returns StatusServiceUnavailable until all parts are ready,
|
||||
// but callers of this function are expected to be ok with a partial service)
|
||||
func (client *Client) IsReady() bool {
|
||||
resp, err := http.Get(client.baseURL + "/status")
|
||||
return err == nil &&
|
||||
(resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusServiceUnavailable)
|
||||
}
|
||||
|
||||
// WaitAPIServer waits until the API server is ready to serve.
|
||||
func (client *Client) WaitAPIServer(n int) {
|
||||
for i := 0; i < n && !client.IsReady(); i++ {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Infof(string, ...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
cd "$SRCDIR"
|
||||
git submodule update --init
|
||||
cd "$SRCDIR/build"
|
||||
../tools/rebuild-image weaveworks/weavebuild . Dockerfile build.sh
|
||||
touch "$SRCDIR/.build.uptodate"
|
||||
sudo chown -R ubuntu /usr/local/go
|
||||
cd "$SRCDIR"
|
||||
make testrunner
|
||||
make COVERAGE=true RM= exes all
|
||||
fi
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "TEST_AND_PUBLISH=1" >"$STATE"
|
||||
|
||||
# Avoid doing a full build if we can. Note if the PR doesn't exist
|
||||
# when the build is kicked off, we can't run this check so we do a
|
||||
# full build. Subsequent pushes to the same branch will have the desired behaviour
|
||||
if [ -n "$CI_PULL_REQUEST" -a "$CI_PULL_REQUEST" = "$CI_PULL_REQUESTS" ]; then
|
||||
if PR=$(echo "$CI_PULL_REQUEST" | grep -oP '(?<=^https://github.com/weaveworks/weave/pull/)[0-9]+$'); then
|
||||
echo "GH_PR=$PR" >>"$STATE"
|
||||
if FILES=$(curl -s "https://api.github.com/repos/weaveworks/weave/pulls/$PR/files" | jq -r '.[]|.filename'); then
|
||||
echo "PR touches [$FILES]"
|
||||
if ! echo "$FILES" | grep -v ^site/; then
|
||||
echo "PR contains site changes only - skipping tests"
|
||||
echo "TEST_AND_PUBLISH=" >>"$STATE"
|
||||
else
|
||||
echo "PR contains non-site changes - running tests"
|
||||
fi
|
||||
else
|
||||
echo "Unable to get files for PR $PR" >&2
|
||||
fi
|
||||
else
|
||||
echo "Unable to extract PR number from $CI_PULL_REQUEST" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
source "$STATE"
|
||||
|
||||
mkdir -p "$(dirname "$SRCDIR")" && cp -r "$(pwd)/" "$SRCDIR"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
cd "$SRCDIR"
|
||||
git submodule update --init
|
||||
fi
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
cd $SRCDIR
|
||||
make clean-bin
|
||||
# CircleCI doesn't let us install the qemu module to do ARM docker builds
|
||||
make publish ALL_ARCHITECTURES=amd64 UPDATE_LATEST=latest-only
|
||||
fi
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
if [ "$CIRCLE_NODE_INDEX" = "0" ] ; then
|
||||
go get github.com/mattn/goveralls
|
||||
goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci
|
||||
cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS
|
||||
fi
|
||||
fi
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Signal failures in lock file, in order to fail fast:
|
||||
function signal_failure() {
|
||||
echo "KO" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
source "$STATE"
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
|
||||
# Upload built binaries to testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh setup >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
echo "OK" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
echo "Test VMs now ready. $(date)." >>"$TEST_VMS_SETUP_OUTPUT_FILE"
|
||||
fi
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_READY_LOCK_FILE"
|
||||
|
||||
# Run integration tests:
|
||||
export COVERAGE=true
|
||||
export WEAVE_NET_SANITY_CHECKS_FILES="$CIRCLE_ARTIFACTS/weave_net_sanity_check_*.log"
|
||||
cd "$SRCDIR/test" # Ensures we generate code coverage files in the right folder, and use the Terraform state of previously provisionned VMs.
|
||||
./run-integration-tests.sh test
|
||||
fi
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR/test
|
||||
test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh)
|
||||
fi
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR
|
||||
make DEBUG=1 lint
|
||||
COVERDIR=test/coverage make RM= tests
|
||||
fi
|
||||
+2
-2
@@ -41,7 +41,7 @@ case "$1" in
|
||||
for i in $(seq $START $FINISH); do
|
||||
IP=$SUBNET.$i
|
||||
sudo ip addr add $IP/24 dev multiweave
|
||||
weave $i launch-router --datapath '' --iface '' --no-dns --host $IP \
|
||||
weave $i launch --datapath '' --iface '' --no-dns --host $IP \
|
||||
--name $(random_mac) --nickname weave$i "$@"
|
||||
done
|
||||
echo $(date -R) connecting
|
||||
@@ -57,7 +57,7 @@ case "$1" in
|
||||
for i in $(seq $FINISH -1 $START); do
|
||||
IP=$SUBNET.$i
|
||||
echo Stopping weave$i
|
||||
weave $i stop-router
|
||||
weave $i stop
|
||||
sudo ip addr del $IP/24 dev multiweave || true
|
||||
done
|
||||
[ -z "$(ip -4 -o addr show multiweave)" ] && sudo ip link del multiweave
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Signal failures in lock file, in order to fail fast:
|
||||
function signal_failure() {
|
||||
echo "KO" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
source "$STATE"
|
||||
|
||||
function install_terraform() {
|
||||
curl -fsS https://releases.hashicorp.com/terraform/0.8.5/terraform_0.8.5_linux_amd64.zip | gunzip >terraform && chmod +x terraform && sudo mv terraform /usr/bin
|
||||
}
|
||||
|
||||
function install_ansible() {
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -qq -y python-pip python-dev libffi-dev libssl-dev && pip install --user -U cffi && pip install --user ansible
|
||||
export PATH="$PATH:$HOME/.local/bin"
|
||||
}
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
install_terraform >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
install_ansible >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
|
||||
# Only attempt to create GCP image in first container, wait for it to be created otherwise:
|
||||
[ "$CIRCLE_NODE_INDEX" != "0" ] && export CREATE_IMAGE=0
|
||||
|
||||
# Provision and configure testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh configure >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
echo "OK" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
echo "Test VMs now provisioned and configured. $(date)." >>"$TEST_VMS_SETUP_OUTPUT_FILE"
|
||||
fi
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
PRODUCT=net
|
||||
|
||||
fatal() {
|
||||
echo "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ ! -d .git ] ; then
|
||||
fatal "Current directory is not a git clone"
|
||||
fi
|
||||
|
||||
if ! BRANCH=$(git symbolic-ref --short HEAD) || [ -z "$BRANCH" ] ; then
|
||||
fatal "Could not determine branch"
|
||||
fi
|
||||
|
||||
case "$BRANCH" in
|
||||
issues/*)
|
||||
VERSION="${BRANCH#issues/}"
|
||||
TAGS="$VERSION"
|
||||
;;
|
||||
*)
|
||||
if echo "$BRANCH" | grep -qE '^[0-9]+\.[0-9]+' ; then
|
||||
DESCRIBE=$(git describe --match 'v*')
|
||||
if ! VERSION=$(echo "$DESCRIBE" | grep -oP '(?<=^v)[0-9]+\.[0-9]+\.[0-9]+') ; then
|
||||
fatal "Could not infer latest $BRANCH version from $DESCRIBE"
|
||||
fi
|
||||
TAGS="latest"
|
||||
else
|
||||
VERSION="$BRANCH"
|
||||
TAGS="$VERSION"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
for TAG in $TAGS ; do
|
||||
echo ">>> Publishing $PRODUCT $VERSION to $1/docs/$PRODUCT/$TAG"
|
||||
wordepress \
|
||||
--url "$1" --user "$2" --password "$3" \
|
||||
--product "$PRODUCT" --version "$VERSION" --tag "$TAG" \
|
||||
publish site
|
||||
done
|
||||
+53
@@ -89,6 +89,9 @@ build() {
|
||||
## Inject the version numbers and build the distributables
|
||||
## (library versions?)
|
||||
sed -i "/SCRIPT_VERSION=/ c\SCRIPT_VERSION=\"$VERSION\"" ./weave
|
||||
sed -i -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset.yaml
|
||||
sed -i -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.6.yaml
|
||||
sed -i -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.7.yaml
|
||||
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER
|
||||
|
||||
if make tests; then
|
||||
@@ -157,6 +160,27 @@ draft() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.6.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.6.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.7.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
|
||||
|
||||
echo "** Draft $TYPE $RELEASE_NAME $VERSION created at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
}
|
||||
@@ -171,6 +195,10 @@ publish() {
|
||||
fi
|
||||
|
||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||
echo "== Building and pushing plugin"
|
||||
make UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER plugin_publish
|
||||
echo "** Plugin built and pushed"
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
@@ -203,6 +231,10 @@ publish() {
|
||||
fi
|
||||
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
|
||||
|
||||
echo "== Building and pushing plugin"
|
||||
make UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER plugin_publish
|
||||
echo "** Plugin built and pushed"
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make PUBLISH_WEAVEDB=$IS_MAINLINE UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
@@ -236,6 +268,27 @@ publish() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.6.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.6.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.7.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
|
||||
|
||||
echo "** Release $RELEASE_NAME $VERSION published at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/latest_release"
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
## This is the shape of unencrypted `do-setup-ci-secrets`
|
||||
##
|
||||
## cat > /tmp/boto_gsutil_cert.pem << EOF
|
||||
## -----BEGIN PRIVATE KEY-----
|
||||
## <...>
|
||||
## -----END PRIVATE KEY-----
|
||||
## EOF
|
||||
##
|
||||
## cat > ~/.boto << EOF
|
||||
## [Credentials]
|
||||
## gs_service_client_id = <...>
|
||||
## gs_service_key_file = /tmp/boto_gsutil_cert.pem
|
||||
##
|
||||
## [Boto]
|
||||
## https_validate_certificates = True
|
||||
##
|
||||
## [GSUtil]
|
||||
## content_language = en
|
||||
## default_api_version = 2
|
||||
## default_project_id = <...>
|
||||
## EOF
|
||||
|
||||
openssl base64 -d << EOF \
|
||||
| openssl aes-256-cbc -d -K $1 -iv $2 \
|
||||
-out bin/do-setup-ci-secrets
|
||||
mOYNzNsAdwva0LbB2YFMY9VgL0SITFu1bPrbOujyLtOrBNjmOYbzR5d/sG+ARMFr
|
||||
7NZ5OKcvMbItllz0n7TtNkzkCOMztMC870Wcx3nGJzbBephdcYwos4tBlp5bihTU
|
||||
AXgxLUiDT1broYAFKnbE1aZC/XyNxuLTN+nXJWIWeijaXTgvhkEEm3tab+z7ioCb
|
||||
tlg71W1BrUuocctAJAuVwH/uTZAaXaJGJj1s2S7F2DrFnG6sFlrp8rnCucPLWKnC
|
||||
4IwJFC5saR5gxK6wECffwmwbpeXVOoQ58OgzxoZTEyAr9nhVbaBt5UZz3tfHlx8W
|
||||
Rn5jHehACw5kg5eMtGPqMEksJ63LX4h3mfSTLJXJHEmOrzCRpFrigVrsbA408U2u
|
||||
8jJTHJmKrGfwBh5ZFzBg6NuV4VplOp+ZquJLgegv2WH/iu/ciopXqB2YDCdx5ZBi
|
||||
nHV2Puth7kjS4EMCKUGfeqpesjL9Djb8cygzv/J3ZBmLjNH9dqlSXWy2DcFZA1Iy
|
||||
pFTTHkVU3w5sNz3nYOuT2UPQvULBW0FUndDKX6olyVa0mlxPwGBP9N3yGg918/rw
|
||||
7rNV/r54lA334F5QMhDrVxar6sl9wfLA+Z+8qemBbNFlLog1uQGhuwWK2nFu1tRM
|
||||
9uyvQIq0oq5NHKpogWc7NEuk7CPe1JyfRIcbMVj8+ilmKjUPB2HPC43IDmKt9/56
|
||||
rY3IXAcxZ/ClGfGpeNHXqop9T/WhfaowponOZFcdGSUddG0OswqkTE4KIv71mgTF
|
||||
jXooMkTQBOG+gBnCe+8bCRdRCG3anxHX74za4dGD58cOJtl6DYWnjWuR91NWnxdT
|
||||
1Zr3iUe41txg9hCNY9LHo2QHGLPxe6pmNnSClQ1mcFhuY6NF8RmCX3HAGKJQkxJb
|
||||
vpLGbXTXjAIFgIYgLNgKP2RLvCif/LmSWgkvE3ZU9GXtCUbm6xgJodwOqXiI9uuO
|
||||
4H4+/vpGHnSFENiclkgMFrLG4yiMu8ArgmnCBzg9E2AoOX5zCrh/2ZRTU8Dk4mt7
|
||||
jUd5SKQLsjB9QtB+y3tRO7zY9RDvR6iBOPRsjx6t8EJMCfPb06KchfMJw+VGxvMM
|
||||
48jtmepREFnhd/8XDRGnw5bznjRuwohGik8BToltjIviMkkpifKWaLwlaOiDXaHh
|
||||
VBFNXnUKvOBwRMU+PZqv4+DP2qff1gSXnGKqA5GIURYrK9P7TqHHNTnmjBdfJLzc
|
||||
0x+W1DTiYhLT2vrLrkEpZ99o6qrg+lwvjat6IsJF8xoQEo4pLD/ZRPJ0WzoDyctT
|
||||
oWZ5rMsN1TzTvJrBrTAWIJyhIzZcuNwChMrU6qogMr0fd6TSxWhWn0sOJ5y8AaTR
|
||||
ej4JHx0qroS/9JPmgFYU61pX3c0ypdef/sSHWG23mcFzLTgwe2d4ty9VakypZy3T
|
||||
sXCl6EHVVXlUFWhY4zQCjdZpYJv/rtoKL1BL4fPE2NV5WTjCL2GNvaO5SfHR7pA0
|
||||
yQJR/iU+cS2n8UXsWNjyV+/qEn6+QXuyVHGLbxyH8EeqlliAbyHsc2rzVI8Xctzo
|
||||
W5DuIqxwHAkNNurhmx27ktIIQ6HhaCp028SWwvLttkbBBgF4v1mmd4u/2bKbKjUd
|
||||
dOaetaIUx1hU9OAeaePvVkeTDcc3mlFtPARCS9Fqj16hgJQ43onZyC0H1K2WAxmU
|
||||
GsmzjCHlTNquzFTuuQihYGZKfsWIh9z6HEOkvNJ3VEQ=
|
||||
EOF
|
||||
|
||||
exec sh bin/do-setup-ci-secrets
|
||||
@@ -1,115 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
# openssl enc -in do-setup-circleci-secrets.orig -out setup-circleci-secrets.orig -e -aes256 -pass stdin
|
||||
# openssl base64 < setup-circleci-secrets.orig
|
||||
|
||||
openssl base64 -d << EOF \
|
||||
| openssl enc \
|
||||
-out bin/do-setup-circleci-secrets \
|
||||
-d -aes256 -pass pass:$1
|
||||
U2FsdGVkX1/3QHnDgpZn0srp+GiEQBTlag0CBdsw6Bp6iaIKy6Jy8gFshrsmvl3+
|
||||
c+7hn1nqsBVpzVH0S5QA3Ex5nx9rl7/dcGoogsySFcwMmBFcauT0UTMK1bvX0Hqx
|
||||
QrshYSndUkww72KA7EsSnjR1BBJWm3AhCDOTIcnO6nQhig1Fc+SBEj5FGS5xTQ5Z
|
||||
/8hDsKf54Ms49vOO6QeE5w5ndZ+k7IvVJfq9Evhb7htD20RntDhR86foAwMnLV7R
|
||||
Wh9qAqI3gnRq0GmbeRKaHmB7pcLq9f0OdScqM/Tn1D+q0556fqJjBziXpZiGbxVs
|
||||
DxEp8zDJ8yNDAi0Wb2NXjoA79SXJ8Q7iTieaST4y2OmXQpY+Vw8RVDXxi6ERyVnH
|
||||
41OeMzyLm0qvCoMl32pe1sSG5DRCUiAm7wOtDTVvNDRqmGJYUgwwexyayecJI1wQ
|
||||
LlhYLJavUvw3UGDZOld8BaMQhfOKBVCdFrjmA4HuVxmOf3029qkgzOCSZiQVNqs+
|
||||
fvM6J05zBuk+GPy8YInvRkzfxum/x00vmUyGjMs/lHDFfrmYDFK3ux41EoVqvWLG
|
||||
S5uYxdZJFdR3Xsrs+sMr51XNBQGMmz6KoEEzFYyHs5fZgtdpRdPAuQS1m6FqgZyi
|
||||
wSz6+EKsALtmGorEp+CF8JN5YzKJCrKzNkg0fILBqsqmJZGP64lmX69950lRLNGM
|
||||
/l3KVR2zH31b4AnnzTcvldVSCKDXjaMw3JA2Ce/MK6XhOWOcpEv4oDt/62lbhUKP
|
||||
QUQTPbfpQRvVfBcoIrfAm3ghi/CZc2TDVEuD7DhasLTZD3r7LoK261BWC9O5Wubi
|
||||
vlXuFIF+uyrpTDf6EJKE/+XywtArppffMP6RkHu1Iqnd8JtsqPaIZsmMmZbjgb6j
|
||||
BX4ZLb5U9R7jYoSgIke4+u6geFcrMQ+Lny4r7MxXaS0Y1UJ6Jo1bwpYw1TBIobZc
|
||||
eQ30+xhbSeRC9vEw1NXI6qM2Ai2gIHtAZvEiBkBBXTG5Ld7N/mBAmDIJEzVRxh6F
|
||||
jRkB3dzESF6eeTBm61OQepwV3aW6lslMaf26drPGv2W2q+K/WOpQOejfggtHUgVs
|
||||
9SWqtn7hHYSpIASe1Ihi14HAWv3kPhQien0zY1DLKJWf4ilbRaICn1QSJjNuKwtl
|
||||
A2ZwEw7FFddFcsF4okHPkxEoCre3shq0DEw9UA1gnwEaEF1Q+i9cvLtQg56zK3+R
|
||||
MfTzFUv2K1z3j1s5tC6Z6LzL8j682EFOll7MkihqiIWh+po1zcNs6Ln0SkjB3v9T
|
||||
OFRYVJis9GqXYO9PUW1mI69udR4nXbKlBl5tWNyAxn9hufJHLSAmbGMwActKLRM5
|
||||
kROapKrN1Ra/sHUmhoSSDUK9AXeYKkqPXVMbhly9xC1Kya4hFaFokjRQIMLPmXdQ
|
||||
lP46RL3HOq0v1BkXg05poSwDm+BtitZkcIhOzcmeucUqn1whsz39y3VoOhJqQu2j
|
||||
0dUcj19Fue1CDZfwzACb9mLZr643h0yCIHQaVT7is/yiN2vEt0S0zynvOA1D+Xgy
|
||||
4bFdjqed9DgBShLwF/lh4/hur5iZPpVr9A8rt3Vq0k5A1tdjsZpXP3qv5jLc7lJv
|
||||
cAQxdVLEuv8WPwwTV/EQsoH7lpoEdjktw/gu0v5PkUgR/WBxEqJq4liTekViQ+8X
|
||||
BAmdm/FGWeIxSSEm0a8I+muiBpIuKjuQoJit1f0k+lLUohoVDqpDDfeYHh1TxXHE
|
||||
xSKAKNmFSc2XOr5Zg2mOInxeH7WoxH6iylXKADBgEgAQ4C55DoAPRjFNtGGz2iJl
|
||||
WkzhQOfgsDss1VxpPZ4SWDQO+xxuuEnYcCYdJ6Tyi5hAwZ0+RZmXx0g9BCK+5qzc
|
||||
Ffei066kCItBcayekwCs0i86r8JvDtsMUUkzEKDQTSWHpP8ypIZoz3P+Skdqcs6p
|
||||
IcZHy2fODIsClctRAsHOjZ9BnWSY2rqf1lysqvRfOkNTViTOUVx2hf+LuL+8rT6g
|
||||
K4GvS39Q+c9FYSMjCNxv1m37NfHJv2W7AlqVV95gMBEFB97M3388T7PfvmSLsyPv
|
||||
qAKDE7genTOycQvEX9oeEACxPo08ai2Mgs/YXCig0g0btZoScoKPelQKtD0HLpZ+
|
||||
j7ruaKFy4/vySDMostB3AKSkCE/SkGu/W9oI37n1guSYC/9yJjtTcwZULXAClfwt
|
||||
LyHvLAw4+7BdGJqDe9Tf8z4zqVLax+BcBHkW7pbP+pvxC6fhIA4PO8k5HwwAVlRw
|
||||
68lG6e3z9+Vst4zrFLewHJvkDRSOTSecs9h9SPOjReTK0ovlDzMHVakfUvqpk9mv
|
||||
nCj8SWaMPF70z138AuRuKN7X4exNXPBFnOcseKp/NpT1DyvgoHm/oIO65mq9vvo+
|
||||
42uSbyyegYiTdVK4SaXBc2hixkhaTteqdEy6GxfWqKCvHtYuRD2LyUSjZn/0Y6CG
|
||||
3wZapylKL1sxfH+b1gogv2wAA8KtZAfiTlMISuymwQr5RbOFma7UVMVecCa7XrNG
|
||||
fZwO8R+6++ru0efDzeC/gtLOItjn+yVfsbk2gfu0FikHIV1sogOjnsv3dl6IpH/s
|
||||
w6WbuK/yZhyrrmhIIueHF30ES512Peogf/Gxp8U/ZAmqBa7p4qsv93XWDR52IPYz
|
||||
yU1MtboLD02J39daBNrYZuO4Yfo/xAvU7Ijg2KIR9HS0bSWNuSFAZaZXlJ2bSag3
|
||||
8l0dZMx2fZa0FALxWvY2+nd1tWcH+Wo99RSxX+4TH3guCpe1x/58yKOKL4d1xRpu
|
||||
02nGaqerV9bHU6WjCUYTrrZurTp6yqivpSFCALDMhAU5Ihd/Qjz69zg4uZ7iqbNd
|
||||
WqIYJ3SAOei2tSfoOJbdh8mohotnMFFbupck3Fv+lXAXYDp0V/sSuK32MSeJxan1
|
||||
q3t0eenAh6zoBP0Tj53N8B9YmrgZ1i1AljQqYsAdb8N5Z3vecuzQa946U8H4axEc
|
||||
BL5fgmqSfsbj1EjJVUrw3cE9hgqeqAYBBvZsyW5zSAsLq/RJKYtBxDGADqr0TOOu
|
||||
TjVqfPObb1u9EAQj15ALBhhPORa2gBAK5gnhJWW5deNgA0UOYoWARFYfATuQvr1T
|
||||
9xTVbqzZ0VJR4n7X5JyxJo2tZ44QYVzA0fqX0lt+uRkarrkbZMgplKp4MJTFCp2N
|
||||
tLciWS0YSHPbKTm7svw5mROazUvEoj1N3SYrJT+Ai9UgSL1cA4FmQ5Jsi+GOeQbH
|
||||
7E4pzjeST4b1aATY9ZZSy4TzK+UNYBcCVqY1dN5Xnk0e5yPMnrkkcYI41s/6oOaW
|
||||
lQkp/yrHkvpzQWZa7vF2yWg54j3eX8YWx/TsLvmuJmSQqiIHfu9TXcAIPDispCpF
|
||||
gp4ZbYPZ8RDQLw3ROc+bZ9E0J8e+FHwgXEELsMLW9n4d3PTj9pLFej1vmBKa03o3
|
||||
g+JViNxb1gUPNmC6ADk4o7TQzBrjV2pmmuCza16kOmq6oAbJPOeCLtVWA5HJTX8d
|
||||
AuOzBLO37tWP457dNqoNDaCMik3LCy8cslZjAz02D6VG/QGOAXP9x8bJ3zg9N7vW
|
||||
EOn1BMtIeMI4yIxpC6HgotNWDNbUBWremAFWYQRgYQqo9L1SdW4hz6X8FqdlBFvK
|
||||
ml7sdUwjEy2tNW+aiZI4cgAlI/VvMWYr5kPfhvaUu1wGYeS1TD1B+DPcjcXpOGWo
|
||||
vB1f/uvGc61zZWD0ph9f3S9OnvEko5qV3VhxOnxYUZTrqlM7LVgy/sHrSaPdR7Eo
|
||||
nb4QGdi5wEGMxVgQlDGwOmEmS5nCaA0RcfHbkQV4SebRleocLB9qcSwj5JzPICAB
|
||||
sn1qeSv45LnGdTzUnCQErw1EzgrK1FXOknujjZ3GX5dM7a6Gag0E6cAxoQeC8Mh0
|
||||
aUWolyBo9aSXltn7Vnywoe4X3v96eVH6tUQtN3wt51LBlfSDTWOQDeXLFyW57oYL
|
||||
/ErbmJLaSHpANBcC9InI+rxNYePTTba7K8rdPDppmMEmdOXjaL+5aPay2Bay9K8T
|
||||
FUyZdoyrbzxFZYT9/YyVJV/ukVcT9ru3HpzLTg9Vgc/CyRfIvCZY/Gzh+pPUbRsR
|
||||
pHYDT09BFS+mjszTkcdbceweYXJvknXpajMiISrPUUkb9rwCTmLS4QR4y7I9zAgJ
|
||||
9U72mCZB9yhhzS17kyKAANihXvOFrMXCO7QTWZbtSkobMBS2SybDxYP3rM5b6824
|
||||
HQaY4MZW5qCndydfz4fa908XbPTDka9Nu3CZfI3fhGiiGDFWViotGEYE4i9BuGZJ
|
||||
vmtzII7zeIoHwM4t5fb5mYDd9H+Rkct1yATGEIFEx7cu1fj4Ht2177EzhdkF3Q6I
|
||||
Fccn43yGDW06nFvEjnjb+Lz4EvtGp6dDefUhNBohdzd4Rm2o+etKobm0lZ1xOX74
|
||||
2gOGBLfXS6PF+msNkWP76mqKE6hG0+R1iPrN06Arti+ZRBhNGJDp1pnOERl7IyB0
|
||||
hiLUqae7xVSMVRDeq4O04EUpMXh5ixs3RJXjthYILt7vX+6qskJC3rMuyThrsvMi
|
||||
Kk0wErDpzR5U7KFnM2fk4jY5by/0b/jHZ/PWFGnaFrXNOiwK/mV1RIJtg5FCrvMv
|
||||
xJN5Qo7o2ZYzUL+IBf5mP5O49FCnz3e++9r4ySY/K57ybhQft1l+cQPWS7xuvVAg
|
||||
wZGM9DPl9AhBus0I3V3XzfkcWWHkMynMRcfTnkBHrmKjMopIOggsq05JPalM9b0V
|
||||
NfwZngVvTFbQejbGjgQQQ+G7spav/icCIA8Iu8JPHaKZt2IX77L1xMcQOouqMrx8
|
||||
JCXnJ7zsuU+8W3GQGm70H2kcen4bf4beA8TSkFmbVsaqDNtGaG+ZopWO8ctZX3CP
|
||||
jFgIYgEGi137IWvs1KD5QNFHZI6l0JMzglmvcfnViRPDHLi+EQXAoL/98j/YzA9t
|
||||
a9L4xMO0oucovKkqYOS2cdFaQKVIUM9L5ibcbW7Tol4ugMo5ibIsTgjZoyFohG6t
|
||||
acg+h2wTTyxtrIapvOOBeAo9V/iQowTAPQf38LKQZ5tApksiCG1qvJerKALTMp1J
|
||||
yUHjTCPCPsaiXVVO0mfa9OPtzVvi/icLmlz7umk82mew13djedgMS3/ghq5aEHbg
|
||||
e7rtI5snFlm/GQnV6IaCDytb+jYxqQ0DZ9Mb0CHcMpnLyiBO6nzHfadpZYlrxrpx
|
||||
dPx9MVqfLavj1UVjKuVj4Mgec9J38IBBRGMJZ4MGhde2hrw/s2jVzG1BzaIzcvFa
|
||||
bWiJOkKKRC4BY9DBDYfae5c/obwzkxLmvkbWAVzoFXc+46K1IK6bbC2a4EerOAxc
|
||||
C7B3X8BucD8OtiPORwXgjctd9AJ7cXq0EEVGoZZ8jkILnklsK6RXsxrT4gSOGFpx
|
||||
cBU7TF4kHpH+o4KGQmjlFA2VYP1tXirjEep57LOHfL+zy+HiSXdsbexf6odkqLYA
|
||||
1RR0TzSkmqJYvP0GfHPMvp2xredx1kP934fhTIUcWvErrG3NPd/Dod4BzQye1nNK
|
||||
8Y8aFYF6fACcD4SFZ11ecK9DJmB+WXGlsxtO3w1LeEysny2lVkkH6HuMRIzPyXAM
|
||||
/sEpwh3hwPJosAh6vGM6JxuL7CXP8hC1oUvK91VR0hxeDcO/LgdcHtDnlNMqt1wC
|
||||
jvYt9Hg+3aY5GehBnQPRppnIMaGhiBrRC6HjBg8Umt6UTUr5PXNY2aia5wRcx4M6
|
||||
Tj0O0dAt8WWZ9ySgzOoq4SRoaAp6m4E7CpRNMgXH031OWEeHqtt8rsYqAcvcQLuW
|
||||
vIwts5hiXZOfgXyR6SBY3ANE1SjDY46na2G4oiG8FI7eAqYgehjHrXqCnGrzjefE
|
||||
V1eVcIr7DBYuM8wq7C9x+qdMir6RNV4MrVD+7rO/drivnd3MaqDHlG0LfMZo6GIK
|
||||
QZGIy0z1W5lOXm9x3RAz7/Ea11ynkZyPDDxBE5NRcV6JfukdjDrvsaQA/Qk2Wo7h
|
||||
PdUZjv17H4syMdap6tUDTc8Cohut6n1UxnDnhaAYEDGR3TgPqI5vEaJ+yoaM9itv
|
||||
b2I/K+0uK6GfWwca7PeTtYn92/5etO4czG99xEBFzw7tiEmBTA8KfseZIUe/i3fr
|
||||
9LO+rKEGalFB4mVTYyCKPYYx4hOIvjdCox/jEBTFyYUEhgGnZvArgw9XabvII7eM
|
||||
c1FmwnN2CM/MHUzM0Kw7cS/oaoRFcG/PqioO/krL2Bd+xThwSH4Zn+Y1UVO7aXNy
|
||||
90MOehHSc2nhN984Y2re3ffVsjPhC3KdWxr8jbho/Az65rUkYg64roiO2ZqdqRvf
|
||||
aYtX9fCe8N8SzgLTlWZt+l8sjY8HP2k9NoajphV1nG4iC/pX0ZJiVQ+VBRNaGhSl
|
||||
6IhM9gM1ZsI86cULJpOTAhNu1fZBv+Mq8IXYD8SqlNVwVtjvshtlzBN4InSGEKiO
|
||||
HM7coo7aDuu+Go5AEiIVMbR09jAa0VINsCVScfSbTFhlGRTrZr2DSO/s8WxbV+Mb
|
||||
EQAfZmyrLF71Z3MfIukg+ps5iuqFhZ+gJSXXSFwumpg=
|
||||
EOF
|
||||
|
||||
exec sh bin/do-setup-circleci-secrets
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=SC2155
|
||||
wait_for() {
|
||||
local timeout=$1
|
||||
local lock_file=$2
|
||||
for i in $(seq 1 "$timeout"); do
|
||||
if [ -f "$lock_file" ]; then
|
||||
local status="$(cat "$lock_file")"
|
||||
if [ "$status" == "OK" ]; then
|
||||
echo "[$i seconds]: $lock_file found and status: $status." && return
|
||||
else
|
||||
echo "[$i seconds]: $lock_file found and status: $status." && return 1
|
||||
fi
|
||||
fi
|
||||
if ! ((i % 10)); then echo "[$i seconds]: Waiting for $lock_file to be created..."; fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Timed out waiting for test VMs to be ready. See details in: $TEST_VMS_SETUP_OUTPUT_FILE" >&2
|
||||
return 1
|
||||
}
|
||||
+85
-6
@@ -1,8 +1,87 @@
|
||||
FROM golang:1.5.2
|
||||
ENV GO15VENDOREXPERIMENT 1
|
||||
RUN apt-get update && apt-get install -y libpcap-dev python-requests time file
|
||||
RUN go get github.com/golang/lint/golint github.com/fzipp/gocyclo github.com/client9/misspell/cmd/misspell
|
||||
RUN go clean -i net && go install -tags netgo std
|
||||
RUN go install -race -tags netgo std
|
||||
FROM golang:1.8.1
|
||||
|
||||
# Support Raspberry Pi 2 and newer
|
||||
ENV GOARM 7
|
||||
|
||||
# The names of the architectures that we're fetching cross-compilers for from the Debian packages
|
||||
ENV DEB_CROSSPLATFORMS \
|
||||
armhf \
|
||||
arm64
|
||||
|
||||
# The go platforms we are supporting to cross-compile
|
||||
ENV GO_CROSSPLATFORMS \
|
||||
linux/arm \
|
||||
linux/arm64
|
||||
|
||||
# The names of the gcc cross-compilers we have installed
|
||||
ENV GCC_CROSSCOMPILERS \
|
||||
arm-linux-gnueabihf \
|
||||
aarch64-linux-gnu
|
||||
|
||||
# Temporarily add the Debian repositories, because we're going to install gcc cross-compilers from there
|
||||
# Install the build-essential and crossbuild-essential-ARCH packages
|
||||
RUN echo "deb http://emdebian.org/tools/debian/ jessie main" > /etc/apt/sources.list.d/cgocrosscompiling.list \
|
||||
&& curl http://emdebian.org/tools/debian/emdebian-toolchain-archive.key | apt-key add - \
|
||||
&& for platform in ${DEB_CROSSPLATFORMS}; do dpkg --add-architecture $platform; done \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y build-essential \
|
||||
&& for platform in ${DEB_CROSSPLATFORMS}; do apt-get install -y crossbuild-essential-${platform}; done \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install some required packages
|
||||
# libpcap is required because we're linking against its C libraries from the prog/weaver binary
|
||||
# flex and bison are required packages for compiling libpcap manually later
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
libpcap-dev \
|
||||
python-requests \
|
||||
time \
|
||||
file \
|
||||
flex \
|
||||
bison
|
||||
|
||||
RUN curl -fsSLo shfmt https://github.com/mvdan/sh/releases/download/v1.3.0/shfmt_v1.3.0_linux_amd64 && \
|
||||
echo "b1925c2c405458811f0c227266402cf1868b4de529f114722c2e3a5af4ac7bb2 shfmt" | sha256sum -c && \
|
||||
chmod +x shfmt && \
|
||||
mv shfmt /usr/bin
|
||||
|
||||
# Install common Go tools
|
||||
RUN go get \
|
||||
github.com/golang/lint/golint \
|
||||
github.com/fzipp/gocyclo \
|
||||
github.com/fatih/hclfmt \
|
||||
github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# First clean, because we're gonna rebuild the std both with -race enabled and disabled
|
||||
# Running go install std twice is intentional and required, as content is written in two different directories
|
||||
# (/usr/local/go/pkg/linux_amd64 vs /usr/local/go/pkg/linux_amd64_race)
|
||||
# The other architectures do not have support for -race
|
||||
RUN go clean -i net \
|
||||
&& go install -tags netgo std \
|
||||
&& go install -race -tags netgo std
|
||||
|
||||
# Prebuild std for the other architectures
|
||||
RUN for platform in ${GO_CROSSPLATFORMS}; do \
|
||||
GOOS=${platform%/*} GOARCH=${platform##*/} \
|
||||
go install -tags netgo std; done
|
||||
|
||||
# Allow full write access to the Go folders for anyone
|
||||
RUN chmod -R a+w /usr/local/go
|
||||
|
||||
# The libpcap version from Debian packages is 1.6.2, matching that version here although newer versions of libpcap have been released
|
||||
# We have to cross-compile the libpcap library for the new architectures, that's what we're doing here
|
||||
ENV LIBPCAP_CROSS_VERSION=1.6.2
|
||||
RUN curl -sSL http://www.tcpdump.org/release/libpcap-${LIBPCAP_CROSS_VERSION}.tar.gz | tar -xz \
|
||||
&& cd libpcap-${LIBPCAP_CROSS_VERSION} \
|
||||
&& for crosscompiler in ${GCC_CROSSCOMPILERS}; do \
|
||||
CC=${crosscompiler}-gcc ac_cv_linux_vers=2 ./configure --host=${crosscompiler} --with-pcap=linux \
|
||||
&& make \
|
||||
&& export LIB_DIR="/usr/local/lib/${crosscompiler}-gcc" VER="$(cat ./VERSION)" MAJOR_VER="$(sed 's/\([0-9][0-9]*\)\..*/\1/' ./VERSION)" \
|
||||
&& mkdir -p ${LIB_DIR} \
|
||||
&& cp -f libpcap.a libpcap.so.${VER} ${LIB_DIR} \
|
||||
&& ln -sf libpcap.so.${VER} /usr/local/lib/libpcap.so.${MAJOR_VER} \
|
||||
&& ln -sf libpcap.so.${MAJOR_VER} /usr/local/lib/libpcap.so \
|
||||
&& make clean; done
|
||||
|
||||
COPY build.sh /
|
||||
ENTRYPOINT ["sh", "/build.sh"]
|
||||
@@ -0,0 +1 @@
|
||||
8f313a9df867c207ae9ceac6e0935d1d2a0360aafce780dd5d1e0687ee98e0c3 docker-1.10.3.tgz
|
||||
@@ -0,0 +1 @@
|
||||
7541b5490cee1183d911f909c0e91d3d9d57b2b775e40f14a1a7a6de216ca4a8 docker-1.10.3.tgz
|
||||
@@ -0,0 +1 @@
|
||||
c02d861383d035ba020b361ef93bf14beb3964a42a6ae645b505b6148493e7ec qemu-aarch64-static
|
||||
@@ -0,0 +1 @@
|
||||
0ff63682b8868620d771aa812d3053dc3c579dc8633ce62d3d7ae7eee83beb06 qemu-arm-static
|
||||
+31
-43
@@ -2,77 +2,65 @@ general:
|
||||
branches:
|
||||
ignore:
|
||||
- gh\-pages
|
||||
- /pull\/.*/
|
||||
|
||||
machine:
|
||||
pre:
|
||||
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
|
||||
services:
|
||||
- docker
|
||||
environment:
|
||||
GOPATH: /home/ubuntu:$GOPATH
|
||||
SRCDIR: /home/ubuntu/src/github.com/weaveworks/weave
|
||||
PATH: $PATH:$HOME/.local/bin
|
||||
PATH: $PATH:$HOME/.local/bin:/opt/google-cloud-sdk/bin
|
||||
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
|
||||
STATE: /home/ubuntu/state.env
|
||||
# Base name of VMs for integration tests:
|
||||
NAME: test-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
|
||||
TEST_VMS_SETUP_OUTPUT_FILE: $CIRCLE_ARTIFACTS/test_vms_setup_output.txt
|
||||
TEST_VMS_PROV_AND_CONF_LOCK_FILE: $CIRCLE_ARTIFACTS/test_vms_provisioned_and_configured.txt
|
||||
TEST_VMS_READY_LOCK_FILE: $CIRCLE_ARTIFACTS/test_vms_ready.txt
|
||||
# Google Cloud Platorm images for integration tests:
|
||||
USE_IMAGE: 1
|
||||
DISK_NAME_PREFIX: test-$CIRCLE_BUILD_NUM-0
|
||||
|
||||
dependencies:
|
||||
cache_directories:
|
||||
- "~/docker"
|
||||
post:
|
||||
- "sudo apt-get update && sudo apt-get install bc jq"
|
||||
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||
- "mkdir -p $(dirname $SRCDIR) && cp -r $(pwd)/ $SRCDIR"
|
||||
- "cd $SRCDIR; git submodule update --init"
|
||||
- cd $SRCDIR/test; ./gce.sh make_template:
|
||||
parallel: false
|
||||
- "cd $SRCDIR/build; ../tools/rebuild-image weaveworks/weavebuild . Dockerfile build.sh && touch $SRCDIR/.build.uptodate"
|
||||
- "sudo chown -R ubuntu /usr/local/go"
|
||||
- bin/circle-dependencies-post-sources
|
||||
# Provision & configure testing VMs:
|
||||
- "while [ ! -d $SRCDIR ]; do sleep 1; done; $SRCDIR/bin/provision_test_vms.sh":
|
||||
parallel: true
|
||||
background: true
|
||||
- bin/circle-dependencies-post
|
||||
|
||||
test:
|
||||
pre:
|
||||
- "$SRCDIR/bin/circle-test-pre":
|
||||
parallel: true
|
||||
background: true
|
||||
override:
|
||||
- cd $SRCDIR; make lint:
|
||||
- "$SRCDIR/bin/circle-test-unit":
|
||||
parallel: true
|
||||
- cd $SRCDIR; COVERDIR=test/coverage make RM= tests:
|
||||
parallel: true
|
||||
- cd $SRCDIR; make COVERAGE=true RM= exes all:
|
||||
parallel: true
|
||||
- cd $SRCDIR; make testrunner:
|
||||
parallel: true
|
||||
- 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:
|
||||
- "$SRCDIR/bin/circle-test-smoke":
|
||||
parallel: true
|
||||
timeout: 300
|
||||
post:
|
||||
- cd $SRCDIR/test; ./gce.sh destroy:
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh):
|
||||
- "$SRCDIR/bin/circle-test-teardown":
|
||||
parallel: true
|
||||
|
||||
teardown:
|
||||
pre:
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (go get github.com/mattn/goveralls && goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci || true):
|
||||
# Destroy testing VMs:
|
||||
- "cd $SRCDIR/test && ./run-integration-tests.sh destroy":
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
|
||||
background: true
|
||||
- "$SRCDIR/bin/circle-teardown-pre":
|
||||
parallel: true
|
||||
|
||||
deployment:
|
||||
release:
|
||||
branch: /[0-9]+\.[0-9]+/
|
||||
owner: weaveworks
|
||||
commands:
|
||||
- go get github.com/weaveworks/wordepress && cd /home/ubuntu/src/github.com/weaveworks/wordepress && git checkout v1.0.0 && cd cmd/wordepress && go get
|
||||
- cd $SRCDIR; bin/publish-site "$WP_LIVE_URL" "$WP_LIVE_USER" "$WP_LIVE_PASSWORD"
|
||||
hub:
|
||||
master:
|
||||
branch: master
|
||||
owner: weaveworks
|
||||
commands:
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- cd $SRCDIR; make clean-bin
|
||||
- cd $SRCDIR; make exes publish
|
||||
# - go get github.com/weaveworks/wordepress && cd /home/ubuntu/src/github.com/weaveworks/wordepress && git checkout v1.0.0 && cd cmd/wordepress && go get
|
||||
# - cd $SRCDIR; bin/publish-site "$WP_DEV_URL" "$WP_DEV_USER" "$WP_DEV_PASSWORD"
|
||||
# issues:
|
||||
# branch: /.*/
|
||||
# owner: weaveworks
|
||||
# commands:
|
||||
# - go get github.com/weaveworks/wordepress && cd /home/ubuntu/src/github.com/weaveworks/wordepress && git checkout v1.0.0 && cd cmd/wordepress && go get
|
||||
# - cd $SRCDIR; bin/publish-site "$WP_DEV_URL" "$WP_DEV_USER" "$WP_DEV_PASSWORD"
|
||||
- "$SRCDIR/bin/circle-deploy-master"
|
||||
@@ -0,0 +1,7 @@
|
||||
## Community Code of Conduct
|
||||
|
||||
Weaveworks follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by contacting a Weaveworks project maintainer, or
|
||||
Alexis Richardson alexis@weave.works.
|
||||
+38
-2
@@ -165,13 +165,25 @@ func (pending pendingStarts) finish(id string) {
|
||||
}
|
||||
}
|
||||
|
||||
// AllContainerIDs returns all the IDs of Docker containers,
|
||||
// whether they are running or not.
|
||||
func (c *Client) AllContainerIDs() ([]string, error) {
|
||||
all, err := c.ListContainers(docker.ListContainersOptions{All: true})
|
||||
return c.containerIDs(true)
|
||||
}
|
||||
|
||||
// RunningContainerIDs returns all the IDs of the running
|
||||
// Docker containers.
|
||||
func (c *Client) RunningContainerIDs() ([]string, error) {
|
||||
return c.containerIDs(false)
|
||||
}
|
||||
|
||||
func (c *Client) containerIDs(all bool) ([]string, error) {
|
||||
containers, err := c.ListContainers(docker.ListContainersOptions{All: all})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids []string
|
||||
for _, c := range all {
|
||||
for _, c := range containers {
|
||||
ids = append(ids, c.ID)
|
||||
}
|
||||
return ids, nil
|
||||
@@ -213,6 +225,30 @@ func (c *Client) GetContainerIP(nameOrID string) (string, error) {
|
||||
return info.NetworkSettings.IPAddress, nil
|
||||
}
|
||||
|
||||
func (c *Client) EnsureNetwork(networkName, driver, subnet string, options map[string]interface{}) error {
|
||||
_, err := c.CreateNetwork(
|
||||
docker.CreateNetworkOptions{
|
||||
Name: networkName,
|
||||
CheckDuplicate: true,
|
||||
Driver: driver,
|
||||
IPAM: docker.IPAMOptions{
|
||||
Driver: driver,
|
||||
Config: []docker.IPAMConfig{{Subnet: subnet}},
|
||||
},
|
||||
Options: options,
|
||||
})
|
||||
if err != docker.ErrNetworkAlreadyExists && err != nil {
|
||||
// Despite appearances to the contrary, CreateNetwork does
|
||||
// sometimes(always?) *not* return ErrNetworkAlreadyExists
|
||||
// when the network already exists. Hence we need to check for
|
||||
// this explicitly.
|
||||
if _, err2 := c.NetworkInfo(networkName); err2 != nil {
|
||||
return fmt.Errorf("unable to create network: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// logging
|
||||
|
||||
func (c *Client) errorf(fmt string, args ...interface{}) {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package mflagext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
type listOpts struct {
|
||||
value *[]string
|
||||
hasBeenSet bool
|
||||
}
|
||||
|
||||
func ListVar(p *[]string, names []string, value []string, usage string) {
|
||||
*p = value
|
||||
mflag.Var(&listOpts{p, false}, names, usage)
|
||||
}
|
||||
|
||||
func (opts *listOpts) Set(value string) error {
|
||||
if opts.hasBeenSet {
|
||||
(*opts.value) = append((*opts.value), value)
|
||||
} else {
|
||||
(*opts.value) = []string{value}
|
||||
opts.hasBeenSet = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts *listOpts) String() string {
|
||||
return fmt.Sprintf("%v", []string(*opts.value))
|
||||
}
|
||||
+7
-2
@@ -21,8 +21,13 @@ func CreateDatapath(dpname string) (supported bool, err error) {
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.CreateDatapath(dpname)
|
||||
if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return true, err
|
||||
if err != nil {
|
||||
if !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return true, err
|
||||
}
|
||||
if dp, err = dpif.LookupDatapath(dpname); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
// Pick an ephemeral port number to use in probing for vxlan
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AllPids(procRoot string) ([]int, error) {
|
||||
fh, err := os.Open(procRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
dirNames, err := fh.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pids := make([]int, len(dirNames))
|
||||
for _, dirName := range dirNames {
|
||||
pid, err := strconv.Atoi(dirName)
|
||||
if err != nil { // Only interested in numeric entries - skip /proc/stat, etc.
|
||||
continue
|
||||
}
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
return pids, err
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// A subsystem/server/... that can be stopped or queried about the status with a signal
|
||||
type SignalReceiver interface {
|
||||
Stop() error
|
||||
}
|
||||
|
||||
func SignalHandlerLoop(ss ...SignalReceiver) {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||
buf := make([]byte, 1<<20)
|
||||
for {
|
||||
switch <-sigs {
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
Log.Infof("=== received SIGINT/SIGTERM ===\n*** exiting")
|
||||
for _, subsystem := range ss {
|
||||
subsystem.Stop()
|
||||
}
|
||||
return
|
||||
case syscall.SIGQUIT:
|
||||
stacklen := runtime.Stack(buf, true)
|
||||
Log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen])
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-168
@@ -1,17 +1,6 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
// Assert test is true, panic otherwise
|
||||
func Assert(test bool) {
|
||||
@@ -27,159 +16,3 @@ func ErrorMessages(errors []error) string {
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
type NetDev struct {
|
||||
Name string
|
||||
MAC net.HardwareAddr
|
||||
CIDRs []*net.IPNet
|
||||
}
|
||||
|
||||
// Search the network namespace of a process for interfaces matching a predicate
|
||||
// Note that the predicate is called while the goroutine is inside the process' netns
|
||||
func FindNetDevs(processID int, match func(link netlink.Link) bool) ([]NetDev, error) {
|
||||
var netDevs []NetDev
|
||||
|
||||
ns, err := netns.GetFromPid(processID)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
err = weavenet.WithNetNSUnsafe(ns, func() error {
|
||||
return forEachLink(func(link netlink.Link) error {
|
||||
if match(link) {
|
||||
netDev, err := linkToNetDev(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netDevs = append(netDevs, netDev)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
return netDevs, err
|
||||
}
|
||||
|
||||
func forEachLink(f func(netlink.Link) error) error {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, link := range links {
|
||||
if err := f(link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkToNetDev(link netlink.Link) (NetDev, error) {
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return NetDev{}, err
|
||||
}
|
||||
|
||||
netDev := NetDev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr}
|
||||
for _, addr := range addrs {
|
||||
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
|
||||
}
|
||||
return netDev, nil
|
||||
}
|
||||
|
||||
// ConnectedToBridgePredicate returns a function which is used to query whether
|
||||
// a given link is a veth interface which one end is connected to a bridge.
|
||||
// The returned function should be called from a container network namespace which
|
||||
// the bridge does NOT belong to.
|
||||
func ConnectedToBridgePredicate(bridgeName string) (func(link netlink.Link) bool, error) {
|
||||
indexes := make(map[int]struct{})
|
||||
|
||||
// Scan devices in root namespace to find those attached to weave bridge
|
||||
err := weavenet.WithNetNSLinkByPidUnsafe(1, bridgeName,
|
||||
func(br netlink.Link) error {
|
||||
return forEachLink(func(link netlink.Link) error {
|
||||
if link.Attrs().MasterIndex == br.Attrs().Index {
|
||||
peerIndex := link.Attrs().ParentIndex
|
||||
if peerIndex == 0 {
|
||||
// perhaps running on an older kernel where ParentIndex doesn't work.
|
||||
// as fall-back, assume the indexes are consecutive
|
||||
peerIndex = link.Attrs().Index - 1
|
||||
}
|
||||
indexes[peerIndex] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(link netlink.Link) bool {
|
||||
_, isveth := link.(*netlink.Veth)
|
||||
_, found := indexes[link.Attrs().Index]
|
||||
return isveth && found
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetNetDevsWithPredicate(processID int, predicate func(link netlink.Link) bool) ([]NetDev, error) {
|
||||
// Bail out if this container is running in the root namespace
|
||||
nsToplevel, err := netns.GetFromPid(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open root namespace: %s", err)
|
||||
}
|
||||
defer nsToplevel.Close()
|
||||
nsContainr, err := netns.GetFromPid(processID)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err)
|
||||
}
|
||||
defer nsContainr.Close()
|
||||
if nsToplevel.Equal(nsContainr) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return FindNetDevs(processID, predicate)
|
||||
}
|
||||
|
||||
// Lookup the weave interface of a container
|
||||
func GetWeaveNetDevs(processID int) ([]NetDev, error) {
|
||||
p, err := ConnectedToBridgePredicate("weave")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetNetDevsWithPredicate(processID, p)
|
||||
}
|
||||
|
||||
// Get the weave bridge interface.
|
||||
// NB: Should be called from the root network namespace.
|
||||
func GetBridgeNetDev(bridgeName string) (NetDev, error) {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return NetDev{}, err
|
||||
}
|
||||
|
||||
return linkToNetDev(link)
|
||||
}
|
||||
|
||||
// Do post-attach configuration of all veths we have created
|
||||
func ConfigureARPforVeths(processID int, prefix string) error {
|
||||
_, err := FindNetDevs(processID, func(link netlink.Link) bool {
|
||||
ifName := link.Attrs().Name
|
||||
if strings.HasPrefix(ifName, prefix) {
|
||||
weavenet.ConfigureARPCache(ifName)
|
||||
if addrs, err := netlink.AddrList(link, netlink.FAMILY_V4); err == nil {
|
||||
for _, addr := range addrs {
|
||||
arping.GratuitousArpOverIfaceByName(addr.IPNet.IP, ifName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type WaitGroup struct {
|
||||
wg sync.WaitGroup
|
||||
count int32
|
||||
}
|
||||
|
||||
// Increase the count of things waiting and return a closure which calls Done
|
||||
func (w *WaitGroup) Add() func() {
|
||||
atomic.AddInt32(&w.count, 1)
|
||||
w.wg.Add(1)
|
||||
return func() {
|
||||
w.wg.Done()
|
||||
atomic.AddInt32(&w.count, -1)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WaitGroup) IsDone() bool {
|
||||
return atomic.LoadInt32(&w.count) == 0
|
||||
}
|
||||
+37
-6
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
@@ -23,14 +24,45 @@ var (
|
||||
topBucket = []byte("top")
|
||||
)
|
||||
|
||||
func NewBoltDB(dbPathname string) (*BoltDB, error) {
|
||||
const (
|
||||
NameIdent = "peername"
|
||||
fileName = "data.db"
|
||||
)
|
||||
|
||||
func Pathname(dbPrefix string) string {
|
||||
return dbPrefix + fileName
|
||||
}
|
||||
|
||||
func NewBoltDB(dbPrefix string) (*BoltDB, error) {
|
||||
dbPathname := Pathname(dbPrefix)
|
||||
db, err := bolt.Open(dbPathname, 0660, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
}
|
||||
// check the persistence version
|
||||
err = db.Update(func(tx *bolt.Tx) error {
|
||||
err = db.Update(checkVersion(false))
|
||||
return &BoltDB{db: db}, err
|
||||
}
|
||||
|
||||
func NewBoltDBReadOnly(dbPrefix string) (*BoltDB, error) {
|
||||
options := bolt.Options{Timeout: time.Millisecond * 50, ReadOnly: true}
|
||||
dbPathname := Pathname(dbPrefix)
|
||||
db, err := bolt.Open(dbPathname, 0660, &options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
}
|
||||
err = db.View(checkVersion(true))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Cannot use persistence file %s: %s", dbPathname, err)
|
||||
}
|
||||
return &BoltDB{db: db}, nil
|
||||
}
|
||||
|
||||
func checkVersion(readOnly bool) func(tx *bolt.Tx) error {
|
||||
return func(tx *bolt.Tx) error {
|
||||
if top := tx.Bucket(topBucket); top == nil {
|
||||
if readOnly {
|
||||
return fmt.Errorf("no top bucket")
|
||||
}
|
||||
top, err := tx.CreateBucket(topBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -41,13 +73,12 @@ func NewBoltDB(dbPathname string) (*BoltDB, error) {
|
||||
} else {
|
||||
if checkVersion := top.Get(versionIdent); checkVersion != nil {
|
||||
if checkVersion[0] != persistenceVersion[0] {
|
||||
return fmt.Errorf("[boltDB] Cannot use persistence file %s - version %x", dbPathname, checkVersion)
|
||||
return fmt.Errorf("mismatched version %x", checkVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &BoltDB{db: db}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (d *BoltDB) Load(ident string, data interface{}) (bool, error) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# Weave Net Benchmarks
|
||||
|
||||
## 2017-02-16: Fast Datapath Encryption
|
||||
|
||||
Results: https://www.weave.works/weave-net-performance-fast-datapath/
|
||||
|
||||
* Two `c3.8xlarge` instances running Ubuntu 16.04 LTS (`ami-d8f4deab`) in
|
||||
`eu-west-1` region of AWS.
|
||||
* 10 Gigabit [Enhanced Networking](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html).
|
||||
* `iperf 3.0.11`.
|
||||
|
||||
### fastdp
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_MTU=8916 weave launch --password=foobar
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_MTU=8916 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_MTU=8950 weave launch
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_MTU=8950 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
### sleeve
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_NO_FASTDP=1 weave launch --password=foobar
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_NO_FASTDP=1 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_NO_FASTDP=1 weave launch
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_NO_FASTDP=1 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
### host
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ iperf -s
|
||||
host2$ iperf -c host1
|
||||
```
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ export KEY1="0x466454f2b1a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host1$ export KEY2="0x3531151241a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host1# ip xfrm state add src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc replay-window 256 flag esn reqid 0 mode transport aead 'rfc4106(gcm(aes))' ${KEY1} 128
|
||||
host1# ip xfrm state add src ${IP2} dst ${IP1} proto esp spi 0x9b0830bc replay-window 256 flag esn reqid 0 mode transport aead 'rfc4106(gcm(aes))' ${KEY2} 128
|
||||
host1# ip xfrm policy add src ${IP1}/32 dst ${IP2}/32 dir out tmpl src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc reqid 0 mode transport
|
||||
host1$ iperf -s
|
||||
|
||||
host2$ export KEY1="0x466454f2b1a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host2$ export KEY2="0x3531151241a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host2# ip xfrm state add src ${IP2} dst ${IP1} proto esp spi 0x9b0830bc reqid 0 replay-window 256 flag esn mode transport aead 'rfc4106(gcm(aes))' ${KEY2} 128
|
||||
host2# ip xfrm state add src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc reqid 0 replay-window 256 flag esn mode transport aead 'rfc4106(gcm(aes))' ${KEY1} 128
|
||||
host2# ip xfrm policy add src ${IP2}/32 dst ${IP1}/32 dir out tmpl src ${IP1} dst ${IP2} proto esp spi 0x9b0830bc reqid 0 mode transport
|
||||
host2$ iperf -c host1
|
||||
```
|
||||
@@ -0,0 +1,70 @@
|
||||
# Entrypoints
|
||||
|
||||
This document describes the various ways that weave as a binary gets launched, and where and how it processes options.
|
||||
|
||||
The goal is to enable maintainers to modify functionality without having to reverse engineer the code to find all of the places you can start a weave binary and what it depends upon.
|
||||
|
||||
## Components
|
||||
### weaver
|
||||
|
||||
`weaver` is the primary binary: it sets up the weave bridge to which containers are attached, manages IP address allocation, serves DNS requests and does many more things.
|
||||
|
||||
Since version 2.0, `weaver` also bundles the Docker Plugin (legacy version) and Docker API Proxy which used to be shipped as separate containers.
|
||||
|
||||
### weaveutil
|
||||
weaveutil is a binary which provides a number of functions for managing an existing weave network. It gets information about the network, attaches and detaches containers, etc.
|
||||
|
||||
For the majority of interactions with an existing weave network, you will launch `weaveutil` in some manner or another.
|
||||
|
||||
Almost all options can be passed as `--option` to `weaveutil`. These options are created in `prog/weaveutil/main.go` with a table mapping each command to a dedicated golang function.
|
||||
|
||||
In addition to operating in normal mode, `weaveutil` has several additional operating modes. Before processing any commands, `weaveutil` checks the filename with which it was called.
|
||||
|
||||
* If it was `weave-ipam` it delegates responsibility to the `cniIPAM()` function in `prog/weaveutil/cni.go`, which, in turn, calls the standard CNI plugin function `cni.PluginMain()`, passing it weave's IPAM implementation from `plugin/ipam`.
|
||||
* If it was `weave-net` it delegates responsibility to the `cniNet()` function in `prog/weaveutil/cni.go`, which, in turn, calls the standard CNI plugin function `cni.PluginMain()`, passing it weave's net plugin implementation from `plugin/net`.
|
||||
|
||||
Finally, `weaveutil` is called by `weaver` when it needs to operate in a different network namespace.
|
||||
Go programs cannot safely switch namespaces; see this [issue](http://github.com/vishvananda/netns/issues/17).
|
||||
The call chain is `weaver`->`nsenter`->`weaveutil`.
|
||||
|
||||
### weave
|
||||
Wrapping `weaver` and `weaveutil` is `weave`, a `sh` script that provides help information and calls `weaveutil` as relevant.
|
||||
|
||||
### weave-kube
|
||||
weave-kube is an image with the weave binaries and a wrapper script installed. It is responsible for:
|
||||
|
||||
1. Setting up the weave network
|
||||
2. Connecting to peers
|
||||
3. Copy the weave CNI-compatible binary plugin `weaveutil` to `/opt/cni/bin/weave-net` and weave config in `/etc/cni/net.d/10-weave.conf`
|
||||
4. Running the weave network policy controller (weave-npc) to implement kubernetes' `NetworkPolicy`
|
||||
|
||||
Once installation is complete, each network-relevant change in a container leads `kubelet` to:
|
||||
|
||||
1. Set certain environment variables to configure the CNI plugin, mostly related to the container ID
|
||||
2. Launch `/opt/cni/bin/weave-net`
|
||||
3. Pass the CNI config - the contents of `/etc/cni/net.d/10-weave.conf` to `weave-net`
|
||||
|
||||
Thus, when each container is changed, and `kubelet` calls weave as a CNI plugin, it really just is launching `weaveutil` as `weave-net`.
|
||||
|
||||
The installation and setup of all of the above - and therefore the entrypoint to the weave-kube image - is the script `prog/weave-kube/launch.sh`. `launch.sh` does the following:
|
||||
|
||||
1. Read configuration variables from the environment. When the documentation for `weave-kube` describes configuring the weave network by changing the environment variables in the daemonset in the `.yml` file, `launch.sh` reads these environment variables.
|
||||
2. Set up the config file at `/etc/cni/net.d/10-weave.conf`
|
||||
3. Run the `weave` initialization script
|
||||
4. Run `weaver` with the correct configuration passed as command-line options
|
||||
|
||||
|
||||
## Adding Options
|
||||
To add new options to how weave should run with each invocation, you would do the following:
|
||||
|
||||
1. determine to which `weave` command you want to add the option(s). `weave` normally is launched as `weave <command> <options> ...`.
|
||||
2. Add the option to `weave` script help for each `weave` command you wish to make it available. As of this writing, all of the commands and their options are listed in the function `usage_no_exist()`.
|
||||
3. Add the option to `weave` script option processing for the `weave` command, under the `case $COMMAND in` in the main `weave` script.
|
||||
4. In the function for the `weave` command, determine if the command should be passed on to `weaveutil` or `weaver`. Pass the option on to `weaveutil` or `weaver`, as appropriate, in the format `--option`, in the function for the `weave` command.
|
||||
5. Add a command-line option `--option` to `weaveutil` or `weaver` as appropriate.
|
||||
6. If the option can be configured for CNI:
|
||||
* have `prog/weave-kube/launch.sh` read it as an environment variable and set inside
|
||||
* set a default for the environment variable in `prog/weave-kube/weave-daemonset-k8s-1.6.yaml` and `weave-daemonset.yaml`
|
||||
* if it should be set via `weaver` globally on its one-time initialization invocation, pass it on in `launch.sh`
|
||||
* if it needs to be set via `weaveutil` on each invocation of `weave-net`, have `launch.sh` save it as an option in the CNI config file `/etc/cni/net.d/10-weave.conf` and then have the CNI code in `plugin/net/cni.go` in `weaveutil` read it and use where appropriate
|
||||
7. Document it!
|
||||
@@ -0,0 +1,155 @@
|
||||
This document describes implementation details of the fast datapath encryption.
|
||||
|
||||
# Overview
|
||||
|
||||
At the high level, we use the ESP protocol ([RFC 2406][esp]) in the Transport
|
||||
mode. Each packet is encrypted with AES in GCM mode ([RFC 4106][aesgcm]), with
|
||||
32 byte key and 4 byte salt. This combo provides the following security
|
||||
properties:
|
||||
|
||||
* Data confidentiality.
|
||||
* Data origin authentication.
|
||||
* Integrity.
|
||||
* Anti-replay.
|
||||
* Limited traffic flow confidentiality as fast datapath VXLAN packets are fully
|
||||
encrypted.
|
||||
|
||||
## Notation
|
||||
|
||||
* `SAin`: IPsec security association for inbound connections.
|
||||
* `SAout`: IPsec security association for outbound connections.
|
||||
* `SPout`: IPsec security policy for outbound connections. Used to match
|
||||
outbound flows to SAout.
|
||||
|
||||
## SA Key Derivation
|
||||
|
||||
For each connection direction, a different AES-GCM key and salt is used.
|
||||
The pairs are derived with HKDF ([RFC 5869][hkdf]):
|
||||
|
||||
```
|
||||
SAin[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceIn, info=localPeerName)
|
||||
SAout[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceOut, info=remotePeerName)
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
* the mutual `sessionKey` is derived by the [Mesh][mesh] library during
|
||||
the control plane connection establishment between the local peer and
|
||||
the remote peer.
|
||||
* `nonceIn` and `nonceOut` are randomly generated 32byte nonces which
|
||||
are exchanged over the encrypted control plane channel.
|
||||
|
||||
## SPI
|
||||
|
||||
A directional secure connection between two peers is identified with SPI.
|
||||
|
||||
The kernel requires the pair of SPI and dst IP to be unique among security
|
||||
associations. Thus, to avoid collisions, we generate outbound SPI on a remote
|
||||
peer.
|
||||
|
||||
## Connection Establishment
|
||||
|
||||
```
|
||||
Peer A Peer B
|
||||
-----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
fastdp.fwd.Confirm():
|
||||
install iptables blocking rules, fastdp.fwd.Confirm():
|
||||
nonce_BA = rand(), install iptables blocking rules,
|
||||
{key,salt}_BA = hkdf(sessionKey, nonce_BA, A), nonce_AB = rand(),
|
||||
spi_BA = allocspi(), {key,salt}_AB = hkdf(sessionKey, nonce_AB, B),
|
||||
create SA_BA(B<-A, spi_BA, key_BA, salt_BA), spi_AB = allocspi(),
|
||||
send InitSARemote(spi_BA, nonce_BA). --> create SA_AB(A<-B, spi_AB, key_AB, salt_AB),
|
||||
<-- send InitSARemote(spi_AB, nonce_AB).
|
||||
|
||||
|
||||
--> recv InitSARemote(spi_BA, nonce_BA):
|
||||
recv InitSARemote(spi_AB, nonce_AB): <-- {key,salt}_BA = hkdf(sessionKey, nonce_BA, A),
|
||||
{key,salt}_AB = hkdf(sessionKey, nonce_AB, B), create SA_BA(B<-A, spi_BA, key_BA, salt_BA),
|
||||
create SA_AB(A<-B, spi_AB, key_AB, salt_AB), create SP_BA(B<-A, spi_BA),
|
||||
create SP_AB(A<-B, spi_AB), install marking rule.
|
||||
install marking rule.
|
||||
```
|
||||
|
||||
# Implementation Details
|
||||
|
||||
## XFRM
|
||||
|
||||
The implementation is based on the kernel IP packet transformation framework
|
||||
called XFRM. Unfortunately, docs are barely existing and the complexity of
|
||||
the framework is high. The best resource I found is Chapter 10 in
|
||||
"Linux Kernel Networking: Implementation and Theory" by Rami Rosen.
|
||||
|
||||
## iptables Rules
|
||||
|
||||
The kernel VXLAN driver does not set a dst port of a tunnel in the ip flow
|
||||
descriptor, thus XFRM policy lookup cannot match a policy (SPout) which includes
|
||||
the port. This makes impossible to encrypt only tunneled traffic between
|
||||
peers. To work around, we mark such outgoing packets with iptables and set
|
||||
the same mark in the policy selector (funnily enough, `iptables_mangle` module
|
||||
eventually sets the missing dst port in the flow descriptor). The challenge
|
||||
here is to pick a mark that it would not interfere with other networking
|
||||
applications before OUTPUT'ing a packet. For example, Kubernetes by default
|
||||
uses 1<<14 and 1<<15 marks and we choose 1<<17 (0x20000). Additionally,
|
||||
such workaround brings the requirement for at least the 4.2 kernel.
|
||||
|
||||
The marking rules are the following:
|
||||
|
||||
```
|
||||
iptables -t mangle -A OUTPUT -j WEAVE-IPSEC-OUT
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT -s ${LOCAL_PEER_IP} -d ${REMOTE_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -j WEAVE-IPSEC-OUT-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT-MARK --set-xmark ${MARK} -j MARK
|
||||
```
|
||||
|
||||
As Linux does not implement [IP Security Levels][ipseclevel], we install
|
||||
additional iptables rules to prevent from accidentally sending unencrypted
|
||||
traffic between peers which have previously established the secure connection.
|
||||
|
||||
For inbound traffic, we mark each ESP packet with the mark and drop non-marked
|
||||
tunnel traffic:
|
||||
|
||||
```
|
||||
iptables -t mangle -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-m esp --espspi ${SPIin} -j WEAVE-IPSEC-IN-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN-MARK --set-xmark ${MARK} -j MARK
|
||||
|
||||
iptables -t filter -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t filter -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -m mark ! --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
For outbound traffic, we drop marked traffic which does not match any SPout:
|
||||
|
||||
```
|
||||
iptables -t filter -A OUTPUT ! -p esp -m policy --dir out --pol none \
|
||||
-m mark --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
## ESN
|
||||
|
||||
To prevent from cycling SeqNo which makes replay attacks possible, we use
|
||||
64-bit extended sequence numbers known as [ESN](esn).
|
||||
|
||||
## MTU
|
||||
|
||||
In addition to the VXLAN overhead, the MTU calculation should take into account
|
||||
the ESP overhead which is 34-37 bytes (encrypted Payload is 4 bytes aligned) and
|
||||
consists of:
|
||||
|
||||
* 4 bytes (SPI).
|
||||
* 4 bytes (Sequence Number).
|
||||
* 8 bytes (ESP IV).
|
||||
* 1 byte (Pad Length).
|
||||
* 1 byte (NextHeader).
|
||||
* 16 bytes (ICV).
|
||||
* 0-3 bytes (Padding).
|
||||
|
||||
|
||||
[esp]: https://tools.ietf.org/html/rfc2406
|
||||
[aesgcm]: https://tools.ietf.org/html/rfc4106
|
||||
[hkdf]: https://tools.ietf.org/html/rfc5869
|
||||
[esn]: https://tools.ietf.org/html/rfc4304
|
||||
[ipseclevel]: https://tools.ietf.org/html/draft-mcdonald-simple-ipsec-api-01)
|
||||
[mesh]: https://github.com/weaveworks/mesh
|
||||
+2
-2
@@ -52,9 +52,9 @@ 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
|
||||
interface is statically configured with an MTU of 1376 bytes allowing
|
||||
it to work with most underlay network provider MTUs, including GCE at
|
||||
1460 bytes (the fifty byte difference accommodates the vxlan overhead).
|
||||
1460 bytes (the eighty four byte difference accommodates the encrypted vxlan overhead).
|
||||
This value can be overridden by setting `WEAVE_MTU` at launch if
|
||||
necessary.
|
||||
|
||||
|
||||
+22
-6
@@ -65,14 +65,30 @@ peer for space:
|
||||
- it will continue to ask for space until it receives some, or its
|
||||
copy of the ring tells it all peers are full in that subnet.
|
||||
|
||||
### Data persistence
|
||||
|
||||
Key IPAM data is saved to disk, in a [BoltDB](https://github.com/boltdb/bolt)
|
||||
file, stored within a [data volume container](https://docs.docker.com/engine/userguide/containers/dockervolumes/#creating-and-mounting-a-data-volume-container)
|
||||
named `weavedb`.
|
||||
|
||||
This file is used to persist data for various Weave Net components; for IPAM it contains:
|
||||
|
||||
* the division of the IP allocation range amongst peers, and
|
||||
* allocation of addresses to containers on the local peer,
|
||||
|
||||
so that it is immediately available when the peer restarts.
|
||||
|
||||
### Claiming an address
|
||||
|
||||
If a Weave process is restarted, in most cases it will hear from
|
||||
another peer which ranges it used to own, but it needs to know which
|
||||
individual IP addresses are assigned to containers in order to avoid
|
||||
giving the same address out in subsequent allocation requests. The
|
||||
weave script invokes the `claim` command in `populate_ipam` to do
|
||||
this.
|
||||
If a Weave process is restarted,
|
||||
|
||||
- if `weavedb` is present, then it loads persisted IPAM data from there, as
|
||||
described, in the previous section;
|
||||
- else, it learns from other peers which ranges it used to own.
|
||||
|
||||
Weave Net then consults the allocator to know which individual IP addresses
|
||||
are assigned to containers, and therefore avoid giving the same address out in
|
||||
subsequent allocation requests.
|
||||
|
||||
When the Allocator is told to claim an address, there are four
|
||||
possibilities:
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
* Up-to-date versions of `git`, `go` etc
|
||||
* Install GitHub release tool `go get github.com/weaveworks/github-release`
|
||||
* Create a [github token for
|
||||
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/);
|
||||
set and export `$GITHUB_TOKEN` with this value
|
||||
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/); select the `repo` OAuth scope; set and export `$GITHUB_TOKEN` with this value
|
||||
|
||||
## Release Types
|
||||
|
||||
@@ -138,6 +137,8 @@ Finally, for **Mainline** releases only:
|
||||
published to https://www.weave.works
|
||||
* Add the new version of `weave-net` to the checkpoint system at
|
||||
https://checkpoint-api.weave.works/admin
|
||||
* File a PR to update the version of the daemonset at
|
||||
https://github.com/kubernetes/kops/tree/master/upup/models/cloudup/resources/addons/networking.weave
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Overview
|
||||
|
||||
# ipsets
|
||||
|
||||
The policy controller maintains a number of ipsets which are
|
||||
subsequently referred to by the iptables rules used to effect network
|
||||
policy specifications. These ipsets are created, modified and
|
||||
destroyed automatically in response to Pod, Namespace and
|
||||
NetworkPolicy object updates from the k8s API server:
|
||||
|
||||
* A `hash:ip` set per namespace, containing the IP addresses of all
|
||||
pods in that namespace
|
||||
* A `list:set` per distinct (across all network policies in all
|
||||
namespaces) namespace selector mentioned in a network policy,
|
||||
containing the names of any of the above hash:ip sets whose
|
||||
corresponding namespace labels match the selector
|
||||
* A `hash:ip` set for each distinct (within the scope of the
|
||||
containing network policy's namespace) pod selector mentioned in a
|
||||
network policy, containing the IP addresses of all pods in the
|
||||
namespace whose labels match that selector
|
||||
|
||||
IPsets are implemented by the kernel module `xt_set`, without which
|
||||
weave-npc will not work.
|
||||
|
||||
ipset names are generated deterministically from a string
|
||||
representation of the corresponding label selector. Because ipset
|
||||
names are limited to 31 characters in length, this is done by taking a
|
||||
SHA hash of the selector string and then printing that out as a base
|
||||
85 string with a "weave-" prefix e.g.:
|
||||
|
||||
weave-k?Z;25^M}|1s7P3|H9i;*;MhG
|
||||
|
||||
Because pod selectors are scoped to a namespace, we need to make sure
|
||||
that if the same selector definition is used in different namespaces
|
||||
that we maintain distinct ipsets. Consequently, for such selectors the
|
||||
namespace name is prepended to the label selector string before
|
||||
hashing to avoid clashes.
|
||||
|
||||
# iptables chains
|
||||
|
||||
The policy controller maintains two iptables chains in response to
|
||||
changes to pods, namespaces and network policies. One chain contains
|
||||
the ingress rules that implement the network policy specifications,
|
||||
and the other is used to bypass the ingress rules for namespaces which
|
||||
have an ingress isolation policy of `DefaultAllow`.
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-DEFAULT` chain
|
||||
|
||||
The policy controller maintains a rule in this chain for every
|
||||
namespace whose ingress isolation policy is `DefaultAllow`. The
|
||||
purpose of this rule is simply to ACCEPT any traffic destined for such
|
||||
namespaces before it reaches the ingress chain.
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-DEFAULT -m set --match-set $NSIPSET dst -j ACCEPT
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-INGRESS` chain
|
||||
|
||||
For each namespace network policy ingress rule peer/port combination:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-INGRESS -p $PROTO [-m set --match-set $SRCSET] -m set --match-set $DSTSET --dport $DPORT -j ACCEPT
|
||||
```
|
||||
|
||||
## Static `WEAVE-NPC` chain
|
||||
|
||||
Static configuration:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
iptables -A WEAVE-NPC -m state --state NEW -j WEAVE-NPC-DEFAULT
|
||||
iptables -A WEAVE-NPC -m state --state NEW -j WEAVE-NPC-INGRESS
|
||||
```
|
||||
|
||||
# Steering traffic into the policy engine
|
||||
|
||||
To direct traffic into the policy engine:
|
||||
|
||||
```
|
||||
iptables -A FORWARD -o weave -m physdev ! --physdev-out vethwe-bridge -j WEAVE-NPC
|
||||
iptables -A FORWARD -o weave -m physdev ! --physdev-out vethwe-bridge -j DROP
|
||||
```
|
||||
|
||||
Note this only affects traffic which egresses the bridge on a physical
|
||||
port which is not the Weave Net router - in other words, it is
|
||||
destined for an application container veth. The following traffic is
|
||||
affected:
|
||||
|
||||
* Traffic bridged between local application containers
|
||||
* Traffic bridged from the router to a local application container
|
||||
* Traffic originating from the internet destined for nodeports - this
|
||||
is routed via the FORWARD chain to a container pod IP after DNAT
|
||||
|
||||
The following traffic is NOT affected:
|
||||
|
||||
* Traffic bridged from a local application container to the router
|
||||
* Traffic originating from processes in the host network namespace
|
||||
(e.g. kubelet health checks)
|
||||
* Traffic routed from an application container to the internet
|
||||
|
||||
The above mechanism relies on the kernel module `br_netfilter` being
|
||||
loaded and enabled via `/proc/sys/net/bridge/bridge-nf-call-iptables`.
|
||||
|
||||
See these resources for helpful context:
|
||||
|
||||
* http://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html
|
||||
* https://commons.wikimedia.org/wiki/File:Netfilter-packet-flow.svg
|
||||
+4
-3
@@ -3,6 +3,7 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -13,9 +14,9 @@ type allocateResult struct {
|
||||
|
||||
type allocate struct {
|
||||
resultChan chan<- allocateResult
|
||||
ident string
|
||||
ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
r address.CIDR // Subnet we are trying to allocate within
|
||||
isContainer bool
|
||||
isContainer bool // true if ident is a container ID
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
if ok, addr := alloc.space.Allocate(g.r.HostRange()); 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 == "_" {
|
||||
if g.ident == api.NoContainerID {
|
||||
g.ident = addr.String()
|
||||
}
|
||||
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
|
||||
|
||||
+36
-12
@@ -76,6 +76,13 @@ type Allocator struct {
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// PreClaims are IP addresses discovered before we could initialize IPAM
|
||||
type PreClaim struct {
|
||||
Ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
IsContainer bool // true if Ident is a container ID
|
||||
Cidr address.CIDR
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
OurName mesh.PeerName
|
||||
OurUID mesh.PeerUID
|
||||
@@ -83,6 +90,7 @@ type Config struct {
|
||||
Seed []mesh.PeerName
|
||||
Universe address.CIDR
|
||||
IsObserver bool
|
||||
PreClaims []PreClaim
|
||||
Quorum func() uint
|
||||
Db db.DB
|
||||
IsKnownPeer func(name mesh.PeerName) bool
|
||||
@@ -123,6 +131,12 @@ func NewAllocator(config Config) *Allocator {
|
||||
dead: make(map[string]time.Time),
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
alloc.pendingClaims = make([]operation, len(config.PreClaims))
|
||||
for i, c := range config.PreClaims {
|
||||
alloc.pendingClaims[i] = &claim{ident: c.Ident, cidr: c.Cidr}
|
||||
}
|
||||
|
||||
return alloc
|
||||
}
|
||||
|
||||
@@ -156,6 +170,9 @@ func (alloc *Allocator) Start() {
|
||||
default:
|
||||
alloc.infof("Initialising as observer - awaiting IPAM data from another peer")
|
||||
}
|
||||
if loadedPersistedData { // do any pre-claims right away
|
||||
alloc.tryOps(&alloc.pendingClaims)
|
||||
}
|
||||
actionChan := make(chan func(), mesh.ChannelSize)
|
||||
stopChan := make(chan struct{})
|
||||
alloc.actionChan = actionChan
|
||||
@@ -193,9 +210,9 @@ func (alloc *Allocator) doOperation(op operation, ops *[]operation) {
|
||||
// already succeeded. If it is on the queue, we call
|
||||
// cancel on it, allowing callers waiting for the resultChans
|
||||
// to unblock.
|
||||
func (alloc *Allocator) cancelOp(op operation, ops *[]operation) {
|
||||
func (alloc *Allocator) cancelOp(opToCancel operation, ops *[]operation) {
|
||||
for i, op := range *ops {
|
||||
if op == op {
|
||||
if op == opToCancel {
|
||||
*ops = append((*ops)[:i], (*ops)[i+1:]...)
|
||||
op.Cancel()
|
||||
break
|
||||
@@ -248,6 +265,10 @@ func (alloc *Allocator) tryPendingOps() {
|
||||
alloc.tryOps(&alloc.pendingAllocates)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) havePendingOps() bool {
|
||||
return len(alloc.pendingPrimes)+len(alloc.pendingClaims)+len(alloc.pendingAllocates) > 0
|
||||
}
|
||||
|
||||
func (alloc *Allocator) spaceRequestDenied(sender mesh.PeerName, r address.Range) {
|
||||
for i := 0; i < len(alloc.pendingClaims); {
|
||||
claim := alloc.pendingClaims[i].(*claim)
|
||||
@@ -322,7 +343,7 @@ func (alloc *Allocator) Claim(ident string, cidr address.CIDR, isContainer, noEr
|
||||
// ContainerDied called from the updater interface. Async.
|
||||
func (alloc *Allocator) ContainerDied(ident string) {
|
||||
alloc.actionChan <- func() {
|
||||
if alloc.hasOwned(ident) {
|
||||
if alloc.hasOwnedByContainer(ident) {
|
||||
alloc.debugln("Container", ident, "died; noting to remove later")
|
||||
alloc.dead[ident] = alloc.now()
|
||||
}
|
||||
@@ -335,7 +356,7 @@ func (alloc *Allocator) ContainerDied(ident string) {
|
||||
// ContainerDestroyed called from the updater interface. Async.
|
||||
func (alloc *Allocator) ContainerDestroyed(ident string) {
|
||||
alloc.actionChan <- func() {
|
||||
if alloc.hasOwned(ident) {
|
||||
if alloc.hasOwnedByContainer(ident) {
|
||||
alloc.debugln("Container", ident, "destroyed; removing addresses")
|
||||
alloc.delete(ident)
|
||||
delete(alloc.dead, ident)
|
||||
@@ -679,8 +700,12 @@ func (alloc *Allocator) actorLoop(actionChan <-chan func(), stopChan <-chan stru
|
||||
// Retry things in case messages got lost between here and recipients
|
||||
if alloc.awaitingConsensus {
|
||||
alloc.propose()
|
||||
} else {
|
||||
alloc.tryPendingOps()
|
||||
} else if alloc.havePendingOps() {
|
||||
if alloc.ring.Empty() {
|
||||
alloc.establishRing()
|
||||
} else {
|
||||
alloc.tryPendingOps()
|
||||
}
|
||||
}
|
||||
alloc.removeDeadContainers()
|
||||
}
|
||||
@@ -920,13 +945,12 @@ func (alloc *Allocator) reportFreeSpace() {
|
||||
// Persistent data
|
||||
const (
|
||||
ringIdent = "ring"
|
||||
nameIdent = "peername"
|
||||
ownedIdent = "ownedAddresses"
|
||||
)
|
||||
|
||||
func (alloc *Allocator) persistRing() {
|
||||
// It would be better if these two Save operations happened in the same transaction
|
||||
if err := alloc.db.Save(nameIdent, alloc.ourName); err != nil {
|
||||
if err := alloc.db.Save(db.NameIdent, alloc.ourName); err != nil {
|
||||
alloc.fatalf("Error persisting ring data: %s", err)
|
||||
return
|
||||
}
|
||||
@@ -938,7 +962,7 @@ func (alloc *Allocator) persistRing() {
|
||||
// Returns true if persisted data is to be used, otherwise false
|
||||
func (alloc *Allocator) loadPersistedData() bool {
|
||||
var checkPeerName mesh.PeerName
|
||||
nameFound, err := alloc.db.Load(nameIdent, &checkPeerName)
|
||||
nameFound, err := alloc.db.Load(db.NameIdent, &checkPeerName)
|
||||
if err != nil {
|
||||
alloc.fatalf("Error loading persisted peer name: %s", err)
|
||||
}
|
||||
@@ -996,9 +1020,9 @@ func (alloc *Allocator) persistOwned() {
|
||||
|
||||
// Owned addresses
|
||||
|
||||
func (alloc *Allocator) hasOwned(ident string) bool {
|
||||
_, b := alloc.owned[ident]
|
||||
return b
|
||||
func (alloc *Allocator) hasOwnedByContainer(ident string) bool {
|
||||
d, b := alloc.owned[ident]
|
||||
return b && d.IsContainer
|
||||
}
|
||||
|
||||
// NB: addr must not be owned by ident already
|
||||
|
||||
+31
-2
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
@@ -30,6 +31,10 @@ func (alloc *Allocator) SimplyClaim(ident string, cidr address.CIDR) error {
|
||||
return alloc.Claim(ident, cidr, true, true, returnFalse)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) AnonymousClaim(cidr address.CIDR) error {
|
||||
return alloc.Claim(api.NoContainerID, cidr, false, true, returnFalse)
|
||||
}
|
||||
|
||||
func TestAllocFree(t *testing.T) {
|
||||
const (
|
||||
container1 = "abcdef"
|
||||
@@ -168,11 +173,15 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
container1 = "abcdef"
|
||||
container3 = "b01df00d"
|
||||
universe = "10.0.3.0/24"
|
||||
testAddr1 = "10.0.3.2/24"
|
||||
testAddr1 = "10.0.3.5/24"
|
||||
testAddr2 = "10.0.4.2/24"
|
||||
testAddr4 = "10.0.2.3/23"
|
||||
subnet4 = "10.0.2.0/23"
|
||||
testPre = "10.0.3.1/24"
|
||||
)
|
||||
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, universe)
|
||||
preAddr, _ := address.ParseCIDR(testPre)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, universe, []PreClaim{{container1, true, preAddr}})
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc := allocs[1]
|
||||
addr1, _ := address.ParseCIDR(testAddr1)
|
||||
@@ -184,6 +193,9 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
alloc.Prime()
|
||||
// Do an allocate on the other peer, which we will try to claim later
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse)
|
||||
require.NoError(t, err)
|
||||
// Should not get the address we pre-claimed
|
||||
require.NotEqual(t, addrx, preAddr)
|
||||
router.Flush()
|
||||
|
||||
// Now try the claim again
|
||||
@@ -198,13 +210,30 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
// claim for a different container should fail
|
||||
err = alloc.SimplyClaim(container1, addr1)
|
||||
require.Error(t, err)
|
||||
// claim for an anonymous container, will assume it's the same one
|
||||
err = alloc.AnonymousClaim(addr1)
|
||||
require.NoError(t, err)
|
||||
// claiming the address allocated on the other peer should fail
|
||||
err = alloc.SimplyClaim(container1, address.MakeCIDR(subnet, addrx))
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// claiming the pre-claimed address should fail on both peers
|
||||
err = alloc.SimplyClaim(container3, preAddr)
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
err = allocs[0].SimplyClaim(container3, preAddr)
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// Check an address outside of our universe
|
||||
addr2, _ := address.ParseCIDR(testAddr2)
|
||||
err = alloc.SimplyClaim(container1, addr2)
|
||||
require.NoError(t, err)
|
||||
err = alloc.SimplyClaim(container3, addr2)
|
||||
require.Error(t, err)
|
||||
// Anonymous claim outside of our universe
|
||||
addr4, _ := address.ParseCIDR(testAddr4)
|
||||
subnet4cidr, _ := address.ParseCIDR(subnet4)
|
||||
require.NoError(t, alloc.AnonymousClaim(addr4))
|
||||
addr5, err := alloc.Allocate(api.NoContainerID, subnet4cidr, false, returnFalse)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, addr4.Start(), addr5)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) pause() func() {
|
||||
|
||||
+40
-16
@@ -5,16 +5,17 @@ import (
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
resultChan chan<- error
|
||||
ident string
|
||||
cidr address.CIDR
|
||||
isContainer bool
|
||||
noErrorOnUnknown bool
|
||||
ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
cidr address.CIDR // single address being claimed
|
||||
isContainer bool // true if ident is a container ID
|
||||
noErrorOnUnknown bool // if false, error or block if we don't know; if true return ok but keep trying
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
@@ -34,15 +35,34 @@ func (c *claim) sendResult(result error) {
|
||||
|
||||
// Try returns true for success (or failure), false if we need to try again later
|
||||
func (c *claim) Try(alloc *Allocator) bool {
|
||||
if c.hasBeenCancelled() {
|
||||
if c.hasBeenCancelled != nil && c.hasBeenCancelled() {
|
||||
c.Cancel()
|
||||
return true
|
||||
}
|
||||
|
||||
addOwned := func() {
|
||||
if c.ident == api.NoContainerID {
|
||||
alloc.addOwned(c.cidr.Addr.String(), c.cidr, c.isContainer)
|
||||
} else {
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
}
|
||||
}
|
||||
|
||||
if !alloc.ring.Contains(c.cidr.Addr) {
|
||||
// Address not within our universe; assume user knows what they are doing
|
||||
alloc.infof("Address %s claimed by %s - not in our range", c.cidr, c.ident)
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
previousOwner := alloc.findOwner(c.cidr.Addr)
|
||||
switch {
|
||||
case previousOwner == "":
|
||||
addOwned()
|
||||
case previousOwner == c.ident: // already owned by this ID
|
||||
case c.ident == api.NoContainerID: // already owned by anonymous container
|
||||
// do nothing (no automatic fall-through in Go)
|
||||
case !alloc.dead[previousOwner].IsZero(): // already owned by dead container
|
||||
alloc.removeOwned(previousOwner, c.cidr.Addr)
|
||||
addOwned()
|
||||
default:
|
||||
c.sendResult(fmt.Errorf("address %s already in use by %s", c.cidr, previousOwner))
|
||||
}
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
@@ -77,26 +97,30 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.ident == "_" { // Special "I don't have a unique ID" identifier
|
||||
c.ident = c.cidr.Addr.String()
|
||||
}
|
||||
// We are the owner, check we haven't given it to another container
|
||||
switch existingIdent := alloc.findOwner(c.cidr.Addr); existingIdent {
|
||||
case "":
|
||||
existingIdent := alloc.findOwner(c.cidr.Addr)
|
||||
switch {
|
||||
case existingIdent == "":
|
||||
// Unused address, we try to claim it:
|
||||
if err := alloc.space.Claim(c.cidr.Addr); err == nil {
|
||||
alloc.debugln("Claimed", c.cidr, "for", c.ident)
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
addOwned()
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(err)
|
||||
}
|
||||
case c.ident:
|
||||
case (existingIdent == c.ident) || (c.ident == api.NoContainerID && existingIdent == c.cidr.Addr.String()):
|
||||
// same identifier is claiming same address; that's OK
|
||||
alloc.debugln("Re-Claimed", c.cidr, "for", c.ident)
|
||||
c.sendResult(nil)
|
||||
case c.cidr.Addr.String():
|
||||
// Address already allocated via "_" name
|
||||
case existingIdent == c.cidr.Addr.String():
|
||||
// Address already allocated via api.NoContainerID name and current ID is a real container ID:
|
||||
c.sendResult(fmt.Errorf("address %s already in use", c.cidr))
|
||||
case c.ident == api.NoContainerID:
|
||||
// We do not know whether this is the same container or another one,
|
||||
// but we also cannot prove otherwise, so we let it reclaim the address:
|
||||
alloc.debugln("Re-Claimed", c.cidr, "for ID", c.ident, "having existing ID as", existingIdent)
|
||||
c.sendResult(nil)
|
||||
default:
|
||||
// Addr already owned by container on this machine
|
||||
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.cidr.String(), existingIdent))
|
||||
|
||||
+34
-11
@@ -1,6 +1,7 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -127,6 +128,39 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
writeAddresses(w, addrs)
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ip").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
type mapping struct {
|
||||
ContainerID string `json:"containerid"`
|
||||
Addrs []string `json:"addrs"`
|
||||
}
|
||||
|
||||
type mappings struct {
|
||||
Owned []mapping `json:"owned"`
|
||||
}
|
||||
|
||||
resultChan := make(chan mappings)
|
||||
alloc.actionChan <- func() {
|
||||
ms := mappings{}
|
||||
for containerid, d := range alloc.owned {
|
||||
m := mapping{
|
||||
ContainerID: containerid,
|
||||
Addrs: []string{},
|
||||
}
|
||||
for _, addr := range d.Cidrs {
|
||||
m.Addrs = append(m.Addrs, addr.String())
|
||||
}
|
||||
ms.Owned = append(ms.Owned, m)
|
||||
}
|
||||
resultChan <- ms
|
||||
}
|
||||
ms := <-resultChan
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(ms); err != nil {
|
||||
common.Log.Warningln("[allocator]:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("POST").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], true); ok {
|
||||
@@ -164,17 +198,6 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
router.Methods("DELETE").Path("/peer").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
alloc.Shutdown()
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
router.Methods("DELETE").Path("/peer/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ident := mux.Vars(r)["id"]
|
||||
transferred := alloc.AdminTakeoverRanges(ident)
|
||||
fmt.Fprintf(w, "%d IPs taken over from %s\n", transferred, ident)
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ipinfo/tracker").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, tracker)
|
||||
})
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package ring
|
||||
|
||||
/* Exported variables for monitoring and management */
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
expRingSize = expvar.NewMap("ipam.ringSize")
|
||||
expRingEntries = expvar.NewMap("ipam.ringEntries")
|
||||
)
|
||||
|
||||
type _int int
|
||||
|
||||
func (i _int) String() string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
type _uint32 uint32
|
||||
|
||||
func (i _uint32) String() string {
|
||||
return strconv.FormatUint(uint64(i), 10)
|
||||
}
|
||||
|
||||
func (r *Ring) updateExportedVariables() {
|
||||
ringName := r.Start.String()
|
||||
expRingSize.Set(ringName, _uint32(r.End-r.Start))
|
||||
expRingEntries.Set(ringName, _int(len(r.Entries)))
|
||||
}
|
||||
+2
-8
@@ -41,8 +41,8 @@ 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!")
|
||||
ErrDifferentSeeds = errors.New("Received ring was seeded differently from ours")
|
||||
ErrDifferentRange = errors.New("Received range differs from ours")
|
||||
)
|
||||
|
||||
func errInconsistentEntry(mine, theirs *entry) error {
|
||||
@@ -114,7 +114,6 @@ func New(start, end address.Address, peer mesh.PeerName, f OnUpdate) *Ring {
|
||||
common.Assert(start < end)
|
||||
|
||||
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0), onUpdate: f}
|
||||
ring.updateExportedVariables()
|
||||
return ring
|
||||
}
|
||||
|
||||
@@ -151,7 +150,6 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName)
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
// ----------------- Start of Checks -----------------
|
||||
|
||||
@@ -218,7 +216,6 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName)
|
||||
func (r *Ring) Merge(gossip Ring) (bool, error) {
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
// Don't panic when checking the gossiped in ring.
|
||||
// In this case just return any error found.
|
||||
@@ -404,7 +401,6 @@ func (r *Ring) ClaimForPeers(peers []mesh.PeerName) {
|
||||
|
||||
defer r.trackUpdates()()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
defer func() {
|
||||
e := r.Entries[len(r.Entries)-1]
|
||||
common.Assert(address.Add(e.Token, address.Offset(e.Free)) == r.End)
|
||||
@@ -455,7 +451,6 @@ func (r *Ring) String() string {
|
||||
func (r *Ring) ReportFree(freespace map[address.Address]address.Count) (updated bool) {
|
||||
r.assertInvariants()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
common.Assert(!r.Empty())
|
||||
entries := r.Entries
|
||||
@@ -564,7 +559,6 @@ func (r *Ring) Transfer(from, to mesh.PeerName) []address.Range {
|
||||
defer r.trackUpdates()()
|
||||
defer r.trackUpdatesOfPeer(from)()
|
||||
defer r.assertInvariants()
|
||||
defer r.updateExportedVariables()
|
||||
|
||||
var newRanges []address.Range
|
||||
|
||||
|
||||
@@ -494,7 +494,7 @@ func TestTransfer(t *testing.T) {
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
require.Equal(t, []address.Range{{start, middle}, {middle, end}}, ring1.OwnedRanges())
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}, {Start: middle, End: end}}, ring1.OwnedRanges())
|
||||
|
||||
// Second test is what happens when a token exists at the end of a range but is transferred
|
||||
// - does it get resurrected correctly?
|
||||
@@ -503,7 +503,7 @@ func TestTransfer(t *testing.T) {
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
ring1.GrantRangeToHost(dot10, middle, peer2name)
|
||||
require.Equal(t, []address.Range{{start, dot10}, {middle, end}}, ring1.OwnedRanges())
|
||||
require.Equal(t, []address.Range{{Start: start, End: dot10}, {Start: middle, End: end}}, ring1.OwnedRanges())
|
||||
}
|
||||
|
||||
func TestOwner(t *testing.T) {
|
||||
|
||||
@@ -88,6 +88,14 @@ func (s *Space) Claim(addr address.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Space) NumOwnedAddresses() address.Count {
|
||||
res := address.Count(0)
|
||||
for i := 0; i < len(s.ours); i += 2 {
|
||||
res += address.Length(s.ours[i+1], s.ours[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Space) NumFreeAddresses() address.Count {
|
||||
res := address.Count(0)
|
||||
for i := 0; i < len(s.free); i += 2 {
|
||||
|
||||
@@ -52,12 +52,12 @@ func TestLowlevel(t *testing.T) {
|
||||
|
||||
s := New()
|
||||
require.Equal(t, address.Count(0), s.NumFreeAddresses())
|
||||
ok, got := s.Allocate(address.NewRange(0, 1000))
|
||||
ok, _ := s.Allocate(address.NewRange(0, 1000))
|
||||
require.False(t, ok, "allocate in empty space should fail")
|
||||
|
||||
s.Add(100, 100)
|
||||
require.Equal(t, address.Count(100), s.NumFreeAddresses())
|
||||
ok, got = s.Allocate(address.NewRange(0, 1000))
|
||||
ok, got := s.Allocate(address.NewRange(0, 1000))
|
||||
require.True(t, ok && got == 100, "allocate")
|
||||
require.Equal(t, address.Count(99), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Claim(150))
|
||||
@@ -68,6 +68,7 @@ func TestLowlevel(t *testing.T) {
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
||||
|
||||
r, ok := s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok, "donate")
|
||||
require.Equal(t, address.NewRange(0xa0, 0x20), r, "donate")
|
||||
|
||||
// test Donate when addresses are scarce
|
||||
@@ -78,6 +79,7 @@ func TestLowlevel(t *testing.T) {
|
||||
require.NoError(t, s.Claim(0))
|
||||
require.NoError(t, s.Claim(2))
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok, "donate")
|
||||
require.Equal(t, address.NewRange(1, 1), r, "donate")
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, !ok, "donate should fail")
|
||||
@@ -98,16 +100,19 @@ func TestSpaceAllocate(t *testing.T) {
|
||||
|
||||
space1 := makeSpace(start, size)
|
||||
require.Equal(t, address.Count(20), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(0), space1.NumOwnedAddresses())
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr1 := space1.Allocate(address.NewRange(start, size))
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
require.Equal(t, address.Count(19), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(1), space1.NumOwnedAddresses())
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr2 := space1.Allocate(address.NewRange(start, size))
|
||||
require.False(t, addr2.String() == testAddr1, "address")
|
||||
require.Equal(t, address.Count(18), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(2), space1.NumOwnedAddresses())
|
||||
require.Equal(t, address.Count(13), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}))
|
||||
require.Equal(t, address.Count(18), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}))
|
||||
space1.assertInvariants()
|
||||
|
||||
@@ -10,6 +10,7 @@ type Status struct {
|
||||
Paxos *paxos.Status
|
||||
Range string
|
||||
RangeNumIPs int
|
||||
ActiveIPs int
|
||||
DefaultSubnet string
|
||||
Entries []EntryStatus
|
||||
PendingClaims []ClaimStatus
|
||||
@@ -52,6 +53,7 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
paxosStatus,
|
||||
allocator.universe.String(),
|
||||
int(allocator.universe.Size()),
|
||||
int(allocator.space.NumOwnedAddresses()),
|
||||
defaultSubnet.String(),
|
||||
newEntryStatusSlice(allocator),
|
||||
newClaimStatusSlice(allocator),
|
||||
|
||||
@@ -128,7 +128,7 @@ type mockDB struct{}
|
||||
func (d *mockDB) Load(_ string, _ interface{}) (bool, error) { return false, nil }
|
||||
func (d *mockDB) Save(_ string, _ interface{}) error { return nil }
|
||||
|
||||
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.CIDR) {
|
||||
func makeAllocator(name string, cidrStr string, quorum uint, preClaims ...PreClaim) (*Allocator, address.CIDR) {
|
||||
peername, err := mesh.PeerNameFromString(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -145,6 +145,7 @@ func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, addres
|
||||
OurNickname: "nick-" + name,
|
||||
Universe: cidr,
|
||||
IsObserver: quorum == 0,
|
||||
PreClaims: preClaims,
|
||||
Quorum: func() uint { return quorum },
|
||||
Db: new(mockDB),
|
||||
IsKnownPeer: func(mesh.PeerName) bool { return true },
|
||||
@@ -213,15 +214,19 @@ func AssertNothingSentErr(t *testing.T, ch <-chan error) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestRouter, address.CIDR) {
|
||||
func makeNetworkOfAllocators(size int, cidr string, preClaims ...[]PreClaim) ([]*Allocator, *gossip.TestRouter, address.CIDR) {
|
||||
gossipRouter := gossip.NewTestRouter(0.0)
|
||||
allocs := make([]*Allocator, size)
|
||||
var subnet address.CIDR
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
var alloc *Allocator
|
||||
preClaim := []PreClaim{}
|
||||
if i < len(preClaims) {
|
||||
preClaim = preClaims[i]
|
||||
}
|
||||
alloc, subnet = makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
|
||||
cidr, uint(size/2+1))
|
||||
cidr, uint(size/2+1), preClaim...)
|
||||
alloc.SetInterfaces(gossipRouter.Connect(alloc.ourName, alloc))
|
||||
alloc.Start()
|
||||
allocs[i] = alloc
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
wnet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -42,7 +41,7 @@ type AWSVPCTracker struct {
|
||||
}
|
||||
|
||||
// NewAWSVPCTracker creates and initialises AWS VPC based tracker.
|
||||
func NewAWSVPCTracker() (*AWSVPCTracker, error) {
|
||||
func NewAWSVPCTracker(bridgeName string) (*AWSVPCTracker, error) {
|
||||
var (
|
||||
err error
|
||||
session = session.New()
|
||||
@@ -69,9 +68,9 @@ func NewAWSVPCTracker() (*AWSVPCTracker, error) {
|
||||
t.routeTableID = *routeTableID
|
||||
|
||||
// Detect Weave bridge link index
|
||||
link, err := netlink.LinkByName(wnet.WeaveBridgeName)
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find \"%s\" interface: %s", wnet.WeaveBridgeName, err)
|
||||
return nil, fmt.Errorf("cannot find \"%s\" interface: %s", bridgeName, err)
|
||||
}
|
||||
t.linkIndex = link.Attrs().Index
|
||||
|
||||
|
||||
+14
-7
@@ -28,7 +28,7 @@ const (
|
||||
)
|
||||
|
||||
type Upstream interface {
|
||||
Config() *dns.ClientConfig
|
||||
Config() (*dns.ClientConfig, error)
|
||||
}
|
||||
|
||||
func NewUpstream(resolvConf, filterAddress string) Upstream {
|
||||
@@ -48,27 +48,31 @@ type upstream struct {
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
func (u *upstream) Config() *dns.ClientConfig {
|
||||
func (u *upstream) Config() (*dns.ClientConfig, error) {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
// If we checked less than five seconds ago return what we already have
|
||||
now := time.Now()
|
||||
if now.Sub(u.lastStat) < 5*time.Second {
|
||||
return u.cachedConfig
|
||||
return u.cachedConfig, nil
|
||||
}
|
||||
u.lastStat = now
|
||||
|
||||
// Reread file if it has changed
|
||||
if fi, err := os.Stat(u.resolvConf); err == nil && fi.ModTime() != u.lastModified {
|
||||
if fi, err := os.Stat(u.resolvConf); err != nil {
|
||||
return u.cachedConfig, err
|
||||
} else if fi.ModTime() != u.lastModified {
|
||||
if config, err := dns.ClientConfigFromFile(u.resolvConf); err == nil {
|
||||
config.Servers = filter(config.Servers, u.filterAddress)
|
||||
u.lastModified = fi.ModTime()
|
||||
u.cachedConfig = config
|
||||
} else {
|
||||
return u.cachedConfig, err
|
||||
}
|
||||
}
|
||||
|
||||
return u.cachedConfig
|
||||
return u.cachedConfig, nil
|
||||
}
|
||||
|
||||
func filter(ss []string, s string) []string {
|
||||
@@ -218,7 +222,7 @@ func (h *handler) handleReverse(w dns.ResponseWriter, req *dns.Msg) {
|
||||
return
|
||||
}
|
||||
|
||||
ipStr := strings.TrimSuffix(req.Question[0].Name, "."+reverseDNSdomain)
|
||||
ipStr := strings.TrimSuffix(strings.ToLower(req.Question[0].Name), "."+reverseDNSdomain)
|
||||
ip, err := address.ParseIP(ipStr)
|
||||
if err != nil {
|
||||
h.nameError(w, req)
|
||||
@@ -257,7 +261,10 @@ func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
||||
}
|
||||
}
|
||||
|
||||
upstreamConfig := h.upstream.Config()
|
||||
upstreamConfig, err := h.upstream.Config()
|
||||
if err != nil {
|
||||
h.ns.errorf("unable to read upstream config: %s", err)
|
||||
}
|
||||
for _, server := range upstreamConfig.Servers {
|
||||
reqCopy := req.Copy()
|
||||
reqCopy.Id = dns.Id()
|
||||
|
||||
@@ -19,8 +19,8 @@ type mockUpstream struct {
|
||||
config *dns.ClientConfig
|
||||
}
|
||||
|
||||
func (mu *mockUpstream) Config() *dns.ClientConfig {
|
||||
return mu.config
|
||||
func (mu *mockUpstream) Config() (*dns.ClientConfig, error) {
|
||||
return mu.config, nil
|
||||
}
|
||||
|
||||
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ func (e1 *Entry) tombstone() bool {
|
||||
|
||||
func check(es SortableEntries) error {
|
||||
if !sort.IsSorted(es) {
|
||||
return fmt.Errorf("Not sorted!")
|
||||
return fmt.Errorf("Not sorted")
|
||||
}
|
||||
for i := 1; i < es.Len(); i++ {
|
||||
if es.Get(i).equal(es.Get(i - 1)) {
|
||||
|
||||
+3
-8
@@ -27,7 +27,7 @@ func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||
vars = mux.Vars(r)
|
||||
container = vars["container"]
|
||||
ipStr = vars["ip"]
|
||||
hostname = dns.Fqdn(r.FormValue("fqdn"))
|
||||
fqdn = r.FormValue("fqdn")
|
||||
ip, err = address.ParseIP(ipStr)
|
||||
)
|
||||
if err != nil {
|
||||
@@ -35,16 +35,11 @@ func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||
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
|
||||
}
|
||||
|
||||
n.AddEntry(hostname, container, n.ourName, ip)
|
||||
n.AddEntryFQDN(fqdn, container, n.ourName, ip)
|
||||
|
||||
if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(container) {
|
||||
n.infof("container '%s' is not running: removing", container)
|
||||
n.Delete(hostname, container, ipStr, ip)
|
||||
n.Delete(dns.Fqdn(fqdn), container, ipStr, ip)
|
||||
}
|
||||
|
||||
w.WriteHeader(204)
|
||||
|
||||
@@ -91,6 +91,15 @@ func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName
|
||||
n.broadcastEntries(entry)
|
||||
}
|
||||
|
||||
func (n *Nameserver) AddEntryFQDN(fqdn, containerid string, origin mesh.PeerName, addr address.Address) {
|
||||
hostname := dns.Fqdn(fqdn)
|
||||
if !dns.IsSubDomain(n.domain, hostname) {
|
||||
n.infof("Ignoring registration %s %s %s (not a subdomain of %s)", hostname, addr.String(), containerid, n.domain)
|
||||
return
|
||||
}
|
||||
n.AddEntry(hostname, containerid, origin, addr)
|
||||
}
|
||||
|
||||
func (n *Nameserver) Lookup(hostname string) []address.Address {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
|
||||
@@ -36,9 +36,10 @@ func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
|
||||
entry.Tombstone})
|
||||
}
|
||||
|
||||
upstreamConfig, _ := dnsServer.upstream.Config()
|
||||
return &Status{
|
||||
dnsServer.domain,
|
||||
dnsServer.upstream.Config().Servers,
|
||||
upstreamConfig.Servers,
|
||||
dnsServer.address,
|
||||
dnsServer.ttl,
|
||||
entryStatusSlice}
|
||||
|
||||
@@ -160,6 +160,11 @@ func (cidr CIDR) String() string {
|
||||
return fmt.Sprintf("%s/%d", cidr.Addr.String(), cidr.PrefixLen)
|
||||
}
|
||||
|
||||
func (cidr CIDR) IPNet() (r *net.IPNet) {
|
||||
mask := net.CIDRMask(cidr.PrefixLen, 8*net.IPv4len)
|
||||
return &net.IPNet{IP: cidr.Addr.IP4(), Mask: mask}
|
||||
}
|
||||
|
||||
// FromIP4 converts an ipv4 address to our integer address type
|
||||
func FromIP4(ip4 net.IP) (r Address) {
|
||||
for _, b := range ip4.To4() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
@@ -67,3 +68,15 @@ func TestCIDRStartAndEnd(t *testing.T) {
|
||||
require.Equal(t, ip("10.0.0.0"), cidr.Start(), "")
|
||||
require.Equal(t, ip("10.0.1.0"), cidr.End(), "")
|
||||
}
|
||||
|
||||
func TestConversions(t *testing.T) {
|
||||
addrStr := "10.2.3.0"
|
||||
cidrStr := addrStr + "/24"
|
||||
addr := ip(addrStr)
|
||||
require.Equal(t, addr.String(), addrStr)
|
||||
require.Equal(t, addr.IP4(), net.ParseIP(addrStr).To4())
|
||||
cidr, _ := ParseCIDR(cidrStr)
|
||||
require.Equal(t, cidr.String(), cidrStr)
|
||||
_, ipnet, _ := net.ParseCIDR(cidrStr)
|
||||
require.Equal(t, cidr.IPNet(), ipnet)
|
||||
}
|
||||
+12
-12
@@ -1,27 +1,27 @@
|
||||
package net
|
||||
|
||||
import "fmt"
|
||||
import "io"
|
||||
import "os"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Configure the ARP cache parameters for the given interface. This
|
||||
// makes containers react more quickly to a change in the MAC address
|
||||
// associated with an IP address.
|
||||
func ConfigureARPCache(name string) error {
|
||||
if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/base_reachable_time", name), "5"); err != nil {
|
||||
func ConfigureARPCache(procPath, name string) error {
|
||||
if err := sysctl(procPath, fmt.Sprintf("net/ipv4/neigh/%s/base_reachable_time", name), "5"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/delay_first_probe_time", name), "2"); err != nil {
|
||||
if err := sysctl(procPath, fmt.Sprintf("net/ipv4/neigh/%s/delay_first_probe_time", name), "2"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/ucast_solicit", name), "1"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return sysctl(procPath, fmt.Sprintf("net/ipv4/neigh/%s/ucast_solicit", name), "1")
|
||||
}
|
||||
|
||||
func sysctl(variable, value string) error {
|
||||
f, err := os.OpenFile(fmt.Sprintf("/proc/sys/%s", variable), os.O_WRONLY, 0)
|
||||
func sysctl(procPath, variable, value string) error {
|
||||
f, err := os.OpenFile(filepath.Join(procPath, "/sys/", variable), os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+473
-13
@@ -1,40 +1,138 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
)
|
||||
|
||||
type BridgeType int
|
||||
/* This code implements three possible configurations to connect
|
||||
containers to the Weave Net overlay:
|
||||
|
||||
1. Bridge
|
||||
+-------+
|
||||
(container-veth)-+ weave +-(vethwe-bridge)--(vethwe-pcap)
|
||||
+-------+
|
||||
|
||||
"weave" is a Linux bridge. "vethwe-pcap" (end of veth pair) is used
|
||||
to capture and inject packets, by router/pcap.go.
|
||||
|
||||
2. BridgedFastdp
|
||||
|
||||
+-------+ /----------\
|
||||
(container-veth)-+ weave +-(vethwe-bridge)--(vethwe-datapath)-+ datapath +
|
||||
+-------+ \----------/
|
||||
|
||||
"weave" is a Linux bridge and "datapath" is an Open vSwitch datapath;
|
||||
they are connected via a veth pair. Packet capture and injection use
|
||||
the "datapath" device, via "router/fastdp.go:fastDatapathBridge"
|
||||
|
||||
3. Fastdp
|
||||
|
||||
/-------\
|
||||
(container-veth)-+ weave +
|
||||
\-------/
|
||||
|
||||
"weave" is an Open vSwitch datapath, and capture/injection are as in
|
||||
BridgedFastdp. Not used by default due to missing conntrack support in
|
||||
datapath of old kernel versions (https://github.com/weaveworks/weave/issues/1577).
|
||||
*/
|
||||
|
||||
const (
|
||||
WeaveBridgeName = "weave"
|
||||
DatapathName = "datapath"
|
||||
|
||||
None BridgeType = iota
|
||||
Bridge
|
||||
Fastdp
|
||||
BridgedFastdp
|
||||
Inconsistent
|
||||
DatapathIfName = "vethwe-datapath"
|
||||
BridgeIfName = "vethwe-bridge"
|
||||
PcapIfName = "vethwe-pcap"
|
||||
)
|
||||
|
||||
func DetectBridgeType(weaveBridgeName, datapathName string) BridgeType {
|
||||
type Bridge interface {
|
||||
init(config *BridgeConfig) error // create and initialise bridge device(s)
|
||||
attach(veth *netlink.Veth) error // attach veth to bridge
|
||||
IsFastdp() bool // does this bridge use fastdp?
|
||||
String() string // human-readable type string
|
||||
}
|
||||
|
||||
// Used to indicate a fallback to the Bridge type
|
||||
var errBridgeNotSupported = errors.New("bridge not supported")
|
||||
|
||||
type bridgeImpl struct{ bridge netlink.Link }
|
||||
type fastdpImpl struct{ datapathName string }
|
||||
type bridgedFastdpImpl struct {
|
||||
bridgeImpl
|
||||
fastdpImpl
|
||||
}
|
||||
|
||||
// Returns a string that is consistent with the weave script
|
||||
func (bridgeImpl) String() string { return "bridge" }
|
||||
func (fastdpImpl) String() string { return "fastdp" }
|
||||
func (bridgedFastdpImpl) String() string { return "bridged_fastdp" }
|
||||
|
||||
// Used to decide whether to manage ODP tunnels
|
||||
func (bridgeImpl) IsFastdp() bool { return false }
|
||||
func (fastdpImpl) IsFastdp() bool { return true }
|
||||
func (bridgedFastdpImpl) IsFastdp() bool { return true }
|
||||
|
||||
func ExistingBridgeType(weaveBridgeName, datapathName string) (Bridge, error) {
|
||||
bridge, _ := netlink.LinkByName(weaveBridgeName)
|
||||
datapath, _ := netlink.LinkByName(datapathName)
|
||||
|
||||
switch {
|
||||
case bridge == nil && datapath == nil:
|
||||
return None
|
||||
return nil, nil
|
||||
case isBridge(bridge) && datapath == nil:
|
||||
return Bridge
|
||||
return bridgeImpl{bridge: bridge}, nil
|
||||
case isDatapath(bridge) && datapath == nil:
|
||||
return Fastdp
|
||||
return fastdpImpl{datapathName: datapathName}, nil
|
||||
case isDatapath(datapath) && isBridge(bridge):
|
||||
return BridgedFastdp
|
||||
return bridgedFastdpImpl{bridgeImpl{bridge: bridge}, fastdpImpl{datapathName: datapathName}}, nil
|
||||
default:
|
||||
return Inconsistent
|
||||
return nil, errors.New("Inconsistent bridge state detected. Please do 'weave reset' and try again")
|
||||
}
|
||||
}
|
||||
|
||||
func EnforceAddrAssignType(bridgeName string) (setAddr bool, err error) {
|
||||
sysctlFilename := filepath.Join("/sys/class/net/", bridgeName, "/addr_assign_type")
|
||||
addrAssignType, err := ioutil.ReadFile(sysctlFilename)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "reading %q", sysctlFilename)
|
||||
}
|
||||
|
||||
// From include/uapi/linux/netdevice.h
|
||||
// #define NET_ADDR_PERM 0 /* address is permanent (default) */
|
||||
// #define NET_ADDR_RANDOM 1 /* address is generated randomly */
|
||||
// #define NET_ADDR_STOLEN 2 /* address is stolen from other device */
|
||||
// #define NET_ADDR_SET 3 /* address is set using dev_set_mac_address() */
|
||||
// Note the file typically has a newline at the end, so we just look at the first char
|
||||
if addrAssignType[0] != '3' {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "EnforceAddrAssignType finding bridge %s", bridgeName)
|
||||
}
|
||||
|
||||
mac, err := RandomMAC()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "creating random MAC")
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetHardwareAddr(link, mac); err != nil {
|
||||
return false, errors.Wrapf(err, "setting bridge %s address to %v", bridgeName, mac)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func isBridge(link netlink.Link) bool {
|
||||
_, isBridge := link.(*netlink.Bridge)
|
||||
return isBridge
|
||||
@@ -51,3 +149,365 @@ func isDatapath(link netlink.Link) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func DetectHairpin(portIfName string, log *logrus.Logger) error {
|
||||
link, err := netlink.LinkByName(portIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find link %q: %s", portIfName, err)
|
||||
}
|
||||
|
||||
ch := make(chan netlink.LinkUpdate)
|
||||
// See EnsureInterface for why done channel is not passed
|
||||
if err := netlink.LinkSubscribe(ch, nil); err != nil {
|
||||
return fmt.Errorf("Unable to subscribe to netlink updates: %s", err)
|
||||
}
|
||||
|
||||
pi, err := netlink.LinkGetProtinfo(link)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get link protinfo %q: %s", portIfName, err)
|
||||
}
|
||||
if pi.Hairpin {
|
||||
return fmt.Errorf("Hairpin mode enabled on %q", portIfName)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for up := range ch {
|
||||
if up.Attrs().Name == portIfName && up.Attrs().Protinfo != nil &&
|
||||
up.Attrs().Protinfo.Hairpin {
|
||||
log.Errorf("Hairpin mode enabled on %q", portIfName)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrBridgeNoIP = fmt.Errorf("Bridge has no IP address")
|
||||
|
||||
func FindBridgeIP(bridgeName string, subnet *net.IPNet) (net.IP, error) {
|
||||
netdev, err := GetBridgeNetDev(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get netdev for %q bridge: %s", bridgeName, err)
|
||||
}
|
||||
if len(netdev.CIDRs) == 0 {
|
||||
return nil, ErrBridgeNoIP
|
||||
}
|
||||
if subnet != nil {
|
||||
for _, cidr := range netdev.CIDRs {
|
||||
if subnet.Contains(cidr.IP) {
|
||||
return cidr.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// No subnet, or none in the required subnet; just return the first one
|
||||
return netdev.CIDRs[0].IP, nil
|
||||
}
|
||||
|
||||
type BridgeConfig struct {
|
||||
DockerBridgeName string
|
||||
WeaveBridgeName string
|
||||
DatapathName string
|
||||
NoFastdp bool
|
||||
NoBridgedFastdp bool
|
||||
AWSVPC bool
|
||||
NPC bool
|
||||
MTU int
|
||||
Mac string
|
||||
Port int
|
||||
}
|
||||
|
||||
func (config *BridgeConfig) configuredBridgeType() Bridge {
|
||||
switch {
|
||||
case config.NoFastdp:
|
||||
return bridgeImpl{}
|
||||
case config.NoBridgedFastdp:
|
||||
return fastdpImpl{datapathName: config.WeaveBridgeName}
|
||||
default:
|
||||
return bridgedFastdpImpl{fastdpImpl: fastdpImpl{datapathName: config.DatapathName}}
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger) (Bridge, error) {
|
||||
bridgeType, err := ExistingBridgeType(config.WeaveBridgeName, config.DatapathName)
|
||||
if bridgeType != nil || err != nil {
|
||||
return bridgeType, err
|
||||
}
|
||||
|
||||
bridgeType = config.configuredBridgeType()
|
||||
for {
|
||||
if err := bridgeType.init(config); err != nil {
|
||||
if errors.Cause(err) == errBridgeNotSupported {
|
||||
log.Warnf("Skipping bridge creation of %q due to: %s", bridgeType, err)
|
||||
bridgeType = bridgeImpl{}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err := configureIPTables(config); err != nil {
|
||||
return bridgeType, errors.Wrap(err, "configuring iptables")
|
||||
}
|
||||
|
||||
if config.AWSVPC {
|
||||
// Set proxy_arp on the bridge, so that it could accept packets destined
|
||||
// to containers within the same subnet but running on remote hosts.
|
||||
// Without it, exact routes on each container are required.
|
||||
if err := sysctl(procPath, "net/ipv4/conf/"+config.WeaveBridgeName+"/proxy_arp", "1"); err != nil {
|
||||
return bridgeType, errors.Wrap(err, "setting proxy_arp")
|
||||
}
|
||||
// Avoid delaying the first ARP request. Also, setting it to 0 avoids
|
||||
// placing the request into a bounded queue as it can be seen:
|
||||
// https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/net/ipv4/arp.c?id=refs/tags/v4.6.1#n819
|
||||
if err := sysctl(procPath, "net/ipv4/neigh/"+config.WeaveBridgeName+"/proxy_delay", "0"); err != nil {
|
||||
return bridgeType, errors.Wrap(err, "setting proxy_arp")
|
||||
}
|
||||
}
|
||||
|
||||
if err := linkSetUpByName(config.WeaveBridgeName); err != nil {
|
||||
return bridgeType, err
|
||||
}
|
||||
|
||||
if err := ConfigureARPCache(procPath, config.WeaveBridgeName); err != nil {
|
||||
return bridgeType, errors.Wrapf(err, "configuring ARP cache on bridge %q", config.WeaveBridgeName)
|
||||
}
|
||||
|
||||
return bridgeType, nil
|
||||
}
|
||||
|
||||
func (b bridgeImpl) initPrep(config *BridgeConfig) error {
|
||||
mac, err := net.ParseMAC(config.Mac)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parsing bridge MAC %q", config.Mac)
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = config.WeaveBridgeName
|
||||
linkAttrs.HardwareAddr = mac
|
||||
if config.MTU == 0 {
|
||||
config.MTU = 65535
|
||||
}
|
||||
b.bridge = &netlink.Bridge{LinkAttrs: linkAttrs}
|
||||
if err = netlink.LinkAdd(b.bridge); err != nil {
|
||||
return errors.Wrapf(err, "creating bridge %q", config.WeaveBridgeName)
|
||||
}
|
||||
if err := netlink.LinkSetHardwareAddr(b.bridge, mac); err != nil {
|
||||
return errors.Wrapf(err, "setting bridge %q mac %v", config.WeaveBridgeName, mac)
|
||||
}
|
||||
// Attempting to set the bridge MTU to a high value directly
|
||||
// fails. Bridges take the lowest MTU of their interfaces. So
|
||||
// instead we create a temporary interface with the desired MTU,
|
||||
// attach that to the bridge, and then remove it again.
|
||||
dummy := &netlink.Dummy{LinkAttrs: netlink.NewLinkAttrs()}
|
||||
dummy.LinkAttrs.Name = "vethwedu"
|
||||
if err = netlink.LinkAdd(dummy); err != nil {
|
||||
return errors.Wrap(err, "creating dummy interface")
|
||||
}
|
||||
if err := netlink.LinkSetMTU(dummy, config.MTU); err != nil {
|
||||
return errors.Wrapf(err, "setting dummy interface mtu to %d", config.MTU)
|
||||
}
|
||||
if err := netlink.LinkSetMasterByIndex(dummy, b.bridge.Attrs().Index); err != nil {
|
||||
return errors.Wrap(err, "setting dummy interface master")
|
||||
}
|
||||
if err := netlink.LinkDel(dummy); err != nil {
|
||||
return errors.Wrap(err, "deleting dummy interface")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b bridgeImpl) init(config *BridgeConfig) error {
|
||||
if err := b.initPrep(config); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := CreateAndAttachVeth(BridgeIfName, PcapIfName, config.WeaveBridgeName, config.MTU, true, func(veth netlink.Link) error {
|
||||
return netlink.LinkSetUp(veth)
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "creating pcap veth pair")
|
||||
}
|
||||
if err := EthtoolTXOff(config.WeaveBridgeName); err != nil {
|
||||
return errors.Wrap(err, "setting tx off")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fastdpImpl) init(config *BridgeConfig) error {
|
||||
odpSupported, err := odp.CreateDatapath(f.datapathName)
|
||||
if !odpSupported {
|
||||
msg := ""
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
}
|
||||
return errors.Wrap(errBridgeNotSupported, msg)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating datapath %q", f.datapathName)
|
||||
}
|
||||
datapath, err := netlink.LinkByName(f.datapathName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "finding datapath %q", f.datapathName)
|
||||
}
|
||||
if config.MTU == 0 {
|
||||
/* GCE has the lowest underlay network MTU we're likely to encounter on
|
||||
a local network, at 1460 bytes. To get the overlay MTU from that we
|
||||
subtract 20 bytes for the outer IPv4 header, 8 bytes for the outer
|
||||
UDP header, 8 bytes for the vxlan header, and 14 bytes for the inner
|
||||
ethernet header. In addition, we subtract 34 bytes for the ESP overhead
|
||||
which is needed for the vxlan encryption. */
|
||||
config.MTU = 1376
|
||||
}
|
||||
if err := netlink.LinkSetMTU(datapath, config.MTU); err != nil {
|
||||
return errors.Wrapf(err, "setting datapath %q mtu %d", f.datapathName, config.MTU)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bf bridgedFastdpImpl) init(config *BridgeConfig) error {
|
||||
if err := bf.fastdpImpl.init(config); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bf.bridgeImpl.initPrep(config); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := CreateAndAttachVeth(BridgeIfName, DatapathIfName, config.WeaveBridgeName, config.MTU, true, func(veth netlink.Link) error {
|
||||
if err := netlink.LinkSetUp(veth); err != nil {
|
||||
return errors.Wrapf(err, "setting link up on %q", veth.Attrs().Name)
|
||||
}
|
||||
if err := odp.AddDatapathInterface(bf.datapathName, veth.Attrs().Name); err != nil {
|
||||
return errors.Wrapf(err, "adding interface %q to datapath %q", veth.Attrs().Name, bf.datapathName)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "creating bridged fastdp veth pair")
|
||||
}
|
||||
|
||||
return linkSetUpByName(bf.datapathName)
|
||||
}
|
||||
|
||||
func (b bridgeImpl) attach(veth *netlink.Veth) error {
|
||||
return netlink.LinkSetMasterByIndex(veth, b.bridge.Attrs().Index)
|
||||
}
|
||||
|
||||
func (bf bridgedFastdpImpl) attach(veth *netlink.Veth) error {
|
||||
return bf.bridgeImpl.attach(veth)
|
||||
}
|
||||
|
||||
func (f fastdpImpl) attach(veth *netlink.Veth) error {
|
||||
return odp.AddDatapathInterface(f.datapathName, veth.Attrs().Name)
|
||||
}
|
||||
|
||||
func configureIPTables(config *BridgeConfig) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating iptables object")
|
||||
}
|
||||
if config.DockerBridgeName != "" {
|
||||
if config.WeaveBridgeName != config.DockerBridgeName {
|
||||
err := ipt.Insert("filter", "FORWARD", 1, "-i", config.DockerBridgeName, "-o", config.WeaveBridgeName, "-j", "DROP")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dockerBridgeIP, err := FindBridgeIP(config.DockerBridgeName, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// forbid traffic to the Weave port from other containers
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "tcp", "--dst", dockerBridgeIP.String(), "--dport", fmt.Sprint(config.Port), "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "udp", "--dst", dockerBridgeIP.String(), "--dport", fmt.Sprint(config.Port), "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "udp", "--dst", dockerBridgeIP.String(), "--dport", fmt.Sprint(config.Port+1), "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// let DNS traffic to weaveDNS, since otherwise it might get blocked by the likes of UFW
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "udp", "--dport", "53", "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if config.NPC {
|
||||
// Steer traffic via the NPC
|
||||
_ = ipt.NewChain("filter", "WEAVE-NPC") // ignore error because it might already exist
|
||||
// If WEAVE-NPC chain doesn't exist then next line will fail
|
||||
if err = ipt.AppendUnique("filter", "FORWARD", "-o", config.WeaveBridgeName, "-j", "WEAVE-NPC"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "FORWARD", "-o", config.WeaveBridgeName, "-m", "state", "--state", "NEW", "-j", "NFLOG", "--nflog-group", "86"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "FORWARD", "-o", config.WeaveBridgeName, "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Work around the situation where there are no rules allowing traffic
|
||||
// across our bridge. E.g. ufw
|
||||
if err = ipt.AppendUnique("filter", "FORWARD", "-i", config.WeaveBridgeName, "-o", config.WeaveBridgeName, "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Forward from weave to the rest of the world
|
||||
if err = ipt.AppendUnique("filter", "FORWARD", "-i", config.WeaveBridgeName, "!", "-o", config.WeaveBridgeName, "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
// and allow replies back
|
||||
if err = ipt.AppendUnique("filter", "FORWARD", "-o", config.WeaveBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create a chain for masquerading
|
||||
if err = ipt.ClearChain("nat", "WEAVE"); err != nil {
|
||||
return errors.Wrap(err, "clearing WEAVE chain")
|
||||
}
|
||||
return ipt.AppendUnique("nat", "POSTROUTING", "-j", "WEAVE")
|
||||
}
|
||||
|
||||
func linkSetUpByName(linkName string) error {
|
||||
link, err := netlink.LinkByName(linkName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "setting link up on %q", linkName)
|
||||
}
|
||||
return netlink.LinkSetUp(link)
|
||||
}
|
||||
|
||||
func AddBridgeAddr(bridgeName string, addr *net.IPNet, removeDefaultRoute bool) error {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "AddBridgeAddr finding bridge %q", bridgeName)
|
||||
}
|
||||
newAddresses, err := AddAddresses(link, []*net.IPNet{addr})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "adding address %v to %q", addr, bridgeName)
|
||||
}
|
||||
for _, ipnet := range newAddresses {
|
||||
arping.GratuitousArpOverIfaceByName(ipnet.IP, bridgeName)
|
||||
}
|
||||
if removeDefaultRoute {
|
||||
routeFilter := &netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Dst: &net.IPNet{IP: addr.IP.Mask(addr.Mask), Mask: addr.Mask},
|
||||
Protocol: 2, // RTPROT_KERNEL
|
||||
}
|
||||
filterMask := netlink.RT_FILTER_OIF | netlink.RT_FILTER_DST | netlink.RT_FILTER_PROTOCOL
|
||||
routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, routeFilter, filterMask)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get route list for bridge %q", bridgeName)
|
||||
}
|
||||
for _, r := range routes {
|
||||
if err = netlink.RouteDel(&r); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete default route %+v", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -82,3 +82,25 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func LocalAddresses() ([]*net.IPNet, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retval := []*net.IPNet{}
|
||||
for _, link := range links {
|
||||
if _, isBridge := link.(*netlink.Bridge); isBridge || (link.Attrs().Flags&net.FlagLoopback) != 0 {
|
||||
continue
|
||||
}
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
retval = append(retval, addr.IPNet)
|
||||
}
|
||||
}
|
||||
return retval, nil
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
// package IPsec provides primitives for establishing IPsec in the fastdp mode.
|
||||
package ipsec
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
)
|
||||
|
||||
const (
|
||||
keySize = 36 // AES-GCM key 32 bytes + 4 bytes salt
|
||||
nonceSize = 32 // HKDF nonce size
|
||||
|
||||
mark = uint32(0x1) << 17 // iptables marks
|
||||
markStr = "0x20000/0x20000" // update if the above mark changes
|
||||
|
||||
tableMangle = "mangle"
|
||||
tableFilter = "filter"
|
||||
chainIn = "WEAVE-IPSEC-IN"
|
||||
chainInMark = "WEAVE-IPSEC-IN-MARK"
|
||||
chainOut = "WEAVE-IPSEC-OUT"
|
||||
chainOutMark = "WEAVE-IPSEC-OUT-MARK"
|
||||
)
|
||||
|
||||
type SPI uint32
|
||||
|
||||
// Used to identify:
|
||||
// - directional SPIs,
|
||||
// - ipsec establishments.
|
||||
type spiID [24]byte
|
||||
|
||||
func getSPIId(srcPeer, dstPeer mesh.PeerName, connUID uint64) (id spiID) {
|
||||
binary.BigEndian.PutUint64(id[:], uint64(srcPeer))
|
||||
binary.BigEndian.PutUint64(id[8:], uint64(dstPeer))
|
||||
binary.BigEndian.PutUint64(id[16:], connUID)
|
||||
return
|
||||
}
|
||||
|
||||
type spiInfo struct {
|
||||
spi SPI
|
||||
isDirOut bool
|
||||
}
|
||||
|
||||
// IPSec
|
||||
|
||||
type IPSec struct {
|
||||
sync.RWMutex
|
||||
ipt *iptables.IPTables
|
||||
log *logrus.Logger
|
||||
|
||||
spiInfo map[spiID]spiInfo
|
||||
// A reference to spiInfo; spiInfo might be of an expired SPI.
|
||||
spis map[SPI]*spiInfo
|
||||
}
|
||||
|
||||
func New(log *logrus.Logger) (*IPSec, error) {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "iptables new")
|
||||
}
|
||||
|
||||
ipsec := &IPSec{
|
||||
ipt: ipt,
|
||||
log: log,
|
||||
spiInfo: make(map[spiID]spiInfo),
|
||||
spis: make(map[SPI]*spiInfo),
|
||||
}
|
||||
|
||||
return ipsec, nil
|
||||
}
|
||||
|
||||
// InitSALocal initializes inbound ipsec from remotePeer and triggers
|
||||
// the initialization on remotePeer.
|
||||
func (ipsec *IPSec) InitSALocal(localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int, sessionKey *[32]byte, initRemote func([]byte) error) error {
|
||||
// ID of inbound SPI
|
||||
spiID := getSPIId(remotePeer, localPeer, connUID)
|
||||
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
// Derive SA key
|
||||
nonce, err := genNonce()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generate nonce")
|
||||
}
|
||||
key, err := deriveKey(sessionKey[:], nonce, localPeer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "derive key")
|
||||
}
|
||||
|
||||
// Allocate SA
|
||||
sa, err := netlink.XfrmStateAllocSpi(xfrmAllocSpiState(remoteIP, localIP))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("ip xfrm state allocspi (in, %s, %s)", remoteIP, localIP))
|
||||
}
|
||||
|
||||
// Use SPI generated by the kernel. The kernel ensures {dstIP, spi} to
|
||||
// be unique.
|
||||
spi := SPI(sa.Spi)
|
||||
|
||||
ipsec.log.Infof("ipsec: InitSALocal: %s -> %s :%d 0x%x", remoteIP, localIP, udpPort, spi)
|
||||
|
||||
// Create SA
|
||||
if sa, err := xfrmState(remoteIP, localIP, spi, false, key); err == nil {
|
||||
if err := netlink.XfrmStateUpdate(sa); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm state update (in, %s, %s, 0x%x)", sa.Src, sa.Dst, sa.Spi))
|
||||
}
|
||||
} else {
|
||||
return errors.Wrap(err, "new xfrm state (in)")
|
||||
}
|
||||
|
||||
// Install iptables rules
|
||||
if err := ipsec.installDropNonEncrypted(localIP, remoteIP, udpPort, spi); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("install protecting rules (%s, %s, %d, 0x%x)", localIP, remoteIP, udpPort, spi))
|
||||
}
|
||||
|
||||
si := spiInfo{spi: spi, isDirOut: false}
|
||||
ipsec.spiInfo[spiID] = si
|
||||
ipsec.spis[spi] = &si
|
||||
|
||||
// Trigger the initialization on the remote peer
|
||||
msg := &msgInitSARemote{nonce, spi}
|
||||
if err := initRemote(msg.serialize()); err != nil {
|
||||
return errors.Wrap(err, "send InitSARemote")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitSARemote initializes outbound ipsec to remotePeer.
|
||||
// Triggered by remotePeer.
|
||||
func (ipsec *IPSec) InitSARemote(msgInitSARemote []byte, localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int, sessionKey *[32]byte) error {
|
||||
// ID of outbound SPI
|
||||
spiID := getSPIId(localPeer, remotePeer, connUID)
|
||||
|
||||
msg, err := deserializeMsgInitSARemote(msgInitSARemote)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "deserialize InitSARemote")
|
||||
}
|
||||
spi := msg.spi
|
||||
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
ipsec.log.Infof("ipsec: InitSARemote: %s -> %s :%d 0x%x", localIP, remoteIP, udpPort, spi)
|
||||
|
||||
// Derive SA key by using the received nonce
|
||||
key, err := deriveKey(sessionKey[:], msg.nonce, remotePeer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "derive key")
|
||||
}
|
||||
|
||||
// Create SA
|
||||
if sa, err := xfrmState(localIP, remoteIP, spi, true, key); err == nil {
|
||||
if err := netlink.XfrmStateAdd(sa); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm state update (out, %s, %s, 0x%x)", sa.Src, sa.Dst, sa.Spi))
|
||||
}
|
||||
} else {
|
||||
return errors.Wrap(err, "new xfrm state (out)")
|
||||
}
|
||||
|
||||
// Create or update SP
|
||||
sp := xfrmPolicy(localIP, remoteIP, spi)
|
||||
if err := netlink.XfrmPolicyUpdate(sp); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm policy update (%s, %s, 0x%x)", localIP, remoteIP, spi))
|
||||
}
|
||||
|
||||
si := spiInfo{spi: spi, isDirOut: true}
|
||||
ipsec.spiInfo[spiID] = si
|
||||
ipsec.spis[spi] = &si
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy destroys any (inbound / outbound) ipsec establishment between the peers.
|
||||
func (ipsec *IPSec) Destroy(localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int) error {
|
||||
outSPIID := getSPIId(localPeer, remotePeer, connUID)
|
||||
inSPIID := getSPIId(remotePeer, localPeer, connUID)
|
||||
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
// Destroy inbound
|
||||
|
||||
if inSPIInfo, ok := ipsec.spiInfo[inSPIID]; ok {
|
||||
ipsec.log.Infof("ipsec: destroy: in %s -> %s 0x%x", remoteIP, localIP, inSPIInfo.spi)
|
||||
|
||||
inSPI := inSPIInfo.spi
|
||||
|
||||
inSA := &netlink.XfrmState{
|
||||
Src: remoteIP,
|
||||
Dst: localIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Spi: int(inSPI),
|
||||
}
|
||||
if err := netlink.XfrmStateDel(inSA); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm state del (in, %s, %s, 0x%x) failed: %s", inSA.Src, inSA.Dst, inSA.Spi, err)
|
||||
}
|
||||
|
||||
if err := ipsec.removeDropNonEncrypted(localIP, remoteIP, udpPort, inSPI); err != nil {
|
||||
ipsec.log.Warnf("ipsec: remove protecting rules (%s, %s, %d, 0x%x) failed: %s", localIP, remoteIP, udpPort, inSPI, err)
|
||||
}
|
||||
|
||||
delete(ipsec.spiInfo, inSPIID)
|
||||
delete(ipsec.spis, inSPI)
|
||||
}
|
||||
|
||||
// Destroy outbound
|
||||
|
||||
if outSPIInfo, ok := ipsec.spiInfo[outSPIID]; ok {
|
||||
ipsec.log.Infof("ipsec: destroy: out %s -> %s 0x%x", localIP, remoteIP, outSPIInfo.spi)
|
||||
|
||||
if err := netlink.XfrmPolicyDel(xfrmPolicy(localIP, remoteIP, outSPIInfo.spi)); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm policy del (%s, %s, 0x%x) failed: %s", localIP, remoteIP, outSPIInfo.spi, err)
|
||||
}
|
||||
|
||||
outSA := &netlink.XfrmState{
|
||||
Src: localIP,
|
||||
Dst: remoteIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Spi: int(outSPIInfo.spi),
|
||||
}
|
||||
if err := netlink.XfrmStateDel(outSA); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm state del (out, %s, %s, 0x%x) failed: %s", outSA.Src, outSA.Dst, outSA.Spi, err)
|
||||
}
|
||||
|
||||
delete(ipsec.spiInfo, outSPIID)
|
||||
delete(ipsec.spis, outSPIInfo.spi)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush removes all policies/SAs established by us. Also, it removes chains and
|
||||
// rules of iptables.
|
||||
//
|
||||
// If destroy is true, the chains and the rules won't be re-created.
|
||||
func (ipsec *IPSec) Flush(destroy bool) error {
|
||||
ipsec.Lock()
|
||||
defer ipsec.Unlock()
|
||||
|
||||
policies, err := netlink.XfrmPolicyList(syscall.AF_INET)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "xfrm policy list")
|
||||
}
|
||||
for _, p := range policies {
|
||||
if p.Mark != nil && p.Mark.Value == mark && len(p.Tmpls) != 0 {
|
||||
spi := SPI(p.Tmpls[0].Spi)
|
||||
if err := netlink.XfrmPolicyDel(&p); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm policy del (%s, %s, 0x%x)", p.Src, p.Dst, spi))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states, err := netlink.XfrmStateList(syscall.AF_INET)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "xfrm state list")
|
||||
}
|
||||
for _, s := range states {
|
||||
if _, ok := ipsec.spis[SPI(s.Spi)]; ok {
|
||||
if err := netlink.XfrmStateDel(&s); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm state list (%s, %s, 0x%x)", s.Src, s.Dst, s.Spi))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ipsec.resetIPTables(destroy); err != nil {
|
||||
return errors.Wrap(err, "reset ip tables")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// iptables
|
||||
|
||||
type chain struct {
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
type rule struct {
|
||||
table string
|
||||
chain string
|
||||
rulespec []string
|
||||
unique bool
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) clearChains(chains []chain) error {
|
||||
for _, c := range chains {
|
||||
if err := ipsec.ipt.ClearChain(c.table, c.chain); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables clear chain (%s, %s)", c.table, c.chain))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) deleteChains(chains []chain) error {
|
||||
for _, c := range chains {
|
||||
if err := ipsec.ipt.DeleteChain(c.table, c.chain); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables delete chain (%s, %s)", c.table, c.chain))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) resetRules(rules []rule, destroy bool) error {
|
||||
for _, r := range rules {
|
||||
ok, err := ipsec.ipt.Exists(r.table, r.chain, r.rulespec...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables exists rule (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
switch {
|
||||
case !destroy && !ok:
|
||||
if err := ipsec.ipt.Append(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables append rule (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
case destroy && ok:
|
||||
if err := ipsec.ipt.Delete(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables delete rule (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) resetIPTables(destroy bool) error {
|
||||
chains := []chain{
|
||||
{tableMangle, chainIn},
|
||||
{tableMangle, chainInMark},
|
||||
{tableFilter, chainIn},
|
||||
{tableMangle, chainOut},
|
||||
{tableMangle, chainOutMark},
|
||||
}
|
||||
rules := []rule{
|
||||
{tableMangle, "INPUT", []string{"-j", chainIn}, true},
|
||||
{tableMangle, chainInMark, []string{"-j", "MARK", "--set-xmark", markStr}, true},
|
||||
{tableFilter, "INPUT", []string{"-j", chainIn}, true},
|
||||
{tableMangle, "OUTPUT", []string{"-j", chainOut}, true},
|
||||
{tableMangle, chainOutMark, []string{"-j", "MARK", "--set-xmark", markStr}, true},
|
||||
{tableFilter, "OUTPUT",
|
||||
[]string{
|
||||
"!", "-p", "esp",
|
||||
"-m", "policy", "--dir", "out", "--pol", "none",
|
||||
"-m", "mark", "--mark", markStr,
|
||||
"-j", "DROP"}, true},
|
||||
}
|
||||
|
||||
if err := ipsec.clearChains(chains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ipsec.resetRules(rules, destroy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if destroy {
|
||||
if err := ipsec.deleteChains(chains); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ruleMarkInboundESP(srcIP, dstIP net.IP, inSPI SPI) rule {
|
||||
return rule{tableMangle, chainIn,
|
||||
[]string{
|
||||
"-s", dstIP.String(), "-d", srcIP.String(),
|
||||
"-p", "esp",
|
||||
"-m", "esp", "--espspi", "0x" + strconv.FormatUint(uint64(inSPI), 16),
|
||||
"-j", chainInMark,
|
||||
}, true}
|
||||
}
|
||||
|
||||
func rulesDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, inSPI SPI) []rule {
|
||||
udpPortStr := strconv.FormatUint(uint64(udpPort), 10)
|
||||
return []rule{
|
||||
ruleMarkInboundESP(srcIP, dstIP, inSPI),
|
||||
{tableFilter, chainIn,
|
||||
[]string{
|
||||
"-s", dstIP.String(), "-d", srcIP.String(),
|
||||
"-p", "udp", "--dport", udpPortStr,
|
||||
"-m", "mark", "!", "--mark", markStr,
|
||||
"-j", "DROP",
|
||||
}, false},
|
||||
{tableMangle, chainOut,
|
||||
[]string{
|
||||
"-s", srcIP.String(), "-d", dstIP.String(),
|
||||
"-p", "udp", "--dport", udpPortStr,
|
||||
"-j", chainOutMark,
|
||||
}, false},
|
||||
}
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) installDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, inSPI SPI) error {
|
||||
rules := rulesDropNonEncrypted(srcIP, dstIP, udpPort, inSPI)
|
||||
for _, r := range rules {
|
||||
appendFunc := ipsec.ipt.Append
|
||||
if r.unique {
|
||||
appendFunc = ipsec.ipt.AppendUnique
|
||||
}
|
||||
if err := appendFunc(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables append (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) removeDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, inSPI SPI) error {
|
||||
rules := rulesDropNonEncrypted(srcIP, dstIP, udpPort, inSPI)
|
||||
return ipsec.resetRules(rules, true)
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) removeDropNonEncryptedInbound(srcIP, dstIP net.IP, inSPI SPI) error {
|
||||
r := ruleMarkInboundESP(srcIP, dstIP, inSPI)
|
||||
if err := ipsec.ipt.Delete(r.table, r.chain, r.rulespec...); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("iptables delete unique (%s, %s, %s)", r.table, r.chain, r.rulespec))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// xfrm
|
||||
|
||||
func xfrmAllocSpiState(srcIP, dstIP net.IP) *netlink.XfrmState {
|
||||
return &netlink.XfrmState{
|
||||
Src: srcIP,
|
||||
Dst: dstIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Mode: netlink.XFRM_MODE_TRANSPORT,
|
||||
ReplayWindow: 256,
|
||||
ESN: true,
|
||||
}
|
||||
}
|
||||
|
||||
func xfrmState(srcIP, dstIP net.IP, spi SPI, isDirOut bool, key []byte) (*netlink.XfrmState, error) {
|
||||
if len(key) != keySize {
|
||||
return nil, fmt.Errorf("key should be %d bytes long", keySize)
|
||||
}
|
||||
|
||||
state := xfrmAllocSpiState(srcIP, dstIP)
|
||||
|
||||
state.Spi = int(spi)
|
||||
state.Aead = &netlink.XfrmStateAlgo{
|
||||
Name: "rfc4106(gcm(aes))",
|
||||
Key: key,
|
||||
ICVLen: 128,
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func xfrmPolicy(srcIP, dstIP net.IP, spi SPI) *netlink.XfrmPolicy {
|
||||
ipMask := []byte{0xff, 0xff, 0xff, 0xff} // /32
|
||||
|
||||
return &netlink.XfrmPolicy{
|
||||
Src: &net.IPNet{IP: srcIP, Mask: ipMask},
|
||||
Dst: &net.IPNet{IP: dstIP, Mask: ipMask},
|
||||
Proto: syscall.IPPROTO_UDP,
|
||||
Dir: netlink.XFRM_DIR_OUT,
|
||||
Mark: &netlink.XfrmMark{
|
||||
Value: mark,
|
||||
Mask: mark,
|
||||
},
|
||||
Tmpls: []netlink.XfrmPolicyTmpl{
|
||||
{
|
||||
Src: srcIP,
|
||||
Dst: dstIP,
|
||||
Proto: netlink.XFRM_PROTO_ESP,
|
||||
Mode: netlink.XFRM_MODE_TRANSPORT,
|
||||
Spi: int(spi),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Key derivation
|
||||
|
||||
func genNonce() ([]byte, error) {
|
||||
buf := make([]byte, nonceSize)
|
||||
n, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("crypto rand failed: %s", err)
|
||||
}
|
||||
if n != nonceSize {
|
||||
return nil, fmt.Errorf("not enough of random data: %d", n)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func deriveKey(sessionKey []byte, nonce []byte, peerName mesh.PeerName) ([]byte, error) {
|
||||
key := make([]byte, keySize)
|
||||
|
||||
info := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(info, uint64(peerName))
|
||||
|
||||
hkdf := hkdf.New(sha256.New, sessionKey, nonce, info)
|
||||
|
||||
n, err := io.ReadFull(hkdf, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != keySize {
|
||||
return nil, fmt.Errorf("derived too short key: %d", n)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Protocol Messages
|
||||
|
||||
type msgInitSARemote struct {
|
||||
nonce []byte
|
||||
spi SPI
|
||||
}
|
||||
|
||||
func deserializeMsgInitSARemote(b []byte) (*msgInitSARemote, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, fmt.Errorf("empty msg")
|
||||
}
|
||||
|
||||
msg := &msgInitSARemote{}
|
||||
if len(b) != msg.size() {
|
||||
return nil, fmt.Errorf("invalid payload size: %d", len(b))
|
||||
}
|
||||
|
||||
msg.nonce = make([]byte, nonceSize)
|
||||
copy(msg.nonce, b[:nonceSize])
|
||||
b = b[nonceSize:]
|
||||
|
||||
msg.spi = SPI(binary.BigEndian.Uint32(b))
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (msg *msgInitSARemote) size() int {
|
||||
return nonceSize + 32 // SPI
|
||||
}
|
||||
|
||||
func (msg *msgInitSARemote) serialize() []byte {
|
||||
b := make([]byte, msg.size())
|
||||
|
||||
copy(b[:nonceSize], msg.nonce)
|
||||
binary.BigEndian.PutUint32(b[nonceSize:], uint32(msg.spi))
|
||||
|
||||
return b
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"net"
|
||||
)
|
||||
|
||||
func RandomMAC() (net.HardwareAddr, error) {
|
||||
mac := make([]byte, 6)
|
||||
if _, err := rand.Read(mac); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setUnicastAndLocal(mac)
|
||||
|
||||
return net.HardwareAddr(mac), nil
|
||||
}
|
||||
|
||||
func MACfromUUID(uuid []byte) net.HardwareAddr {
|
||||
hash := sha256.New()
|
||||
// We salt the input just as a precaution to avoid clashes with
|
||||
// other applications who might have had the bright idea of
|
||||
// generating MACs in the same way.
|
||||
hash.Write([]byte("9oBJ0Jmip-"))
|
||||
hash.Write(uuid)
|
||||
sum := hash.Sum(nil)
|
||||
|
||||
setUnicastAndLocal(sum)
|
||||
|
||||
return net.HardwareAddr(sum[:6])
|
||||
}
|
||||
|
||||
// In the first byte of the MAC, the 'multicast' bit should be
|
||||
// clear and 'locally administered' bit should be set.
|
||||
func setUnicastAndLocal(mac []byte) {
|
||||
mac[0] = (mac[0] & 0xFE) | 0x02
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
type Dev struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
MAC net.HardwareAddr `json:"MAC,omitempty"`
|
||||
CIDRs []*net.IPNet `json:"CIDRs,omitempty"`
|
||||
}
|
||||
|
||||
func LinkToNetDev(link netlink.Link) (Dev, error) {
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return Dev{}, err
|
||||
}
|
||||
|
||||
netDev := Dev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr}
|
||||
for _, addr := range addrs {
|
||||
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
|
||||
}
|
||||
return netDev, nil
|
||||
}
|
||||
|
||||
// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to
|
||||
// the given bridge. The peer index is used to query from a container netns
|
||||
// whether the container is connected to the bridge.
|
||||
func ConnectedToBridgeVethPeerIds(bridgeName string) ([]int, error) {
|
||||
var ids []int
|
||||
|
||||
br, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
if _, isveth := link.(*netlink.Veth); isveth && link.Attrs().MasterIndex == br.Attrs().Index {
|
||||
peerID := link.Attrs().ParentIndex
|
||||
if peerID == 0 {
|
||||
// perhaps running on an older kernel where ParentIndex doesn't work.
|
||||
// as fall-back, assume the peers are consecutive
|
||||
peerID = link.Attrs().Index - 1
|
||||
}
|
||||
ids = append(ids, peerID)
|
||||
}
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Lookup the weave interface of a container
|
||||
func GetWeaveNetDevs(processID int) ([]Dev, error) {
|
||||
peerIDs, err := ConnectedToBridgeVethPeerIds("weave")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetNetDevsByVethPeerIds(processID, peerIDs)
|
||||
}
|
||||
|
||||
func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) {
|
||||
// Bail out if this container is running in the root namespace
|
||||
netnsRoot, err := netns.GetFromPid(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open root namespace: %s", err)
|
||||
}
|
||||
defer netnsRoot.Close()
|
||||
netnsContainer, err := netns.GetFromPid(processID)
|
||||
if err != nil {
|
||||
// Unable to find a namespace for this process - just return nothing
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err)
|
||||
}
|
||||
defer netnsContainer.Close()
|
||||
if netnsRoot.Equal(netnsContainer) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var netdevs []Dev
|
||||
peersStr := make([]string, len(peerIDs))
|
||||
for i, id := range peerIDs {
|
||||
peersStr[i] = strconv.Itoa(id)
|
||||
}
|
||||
nds, err := WithNetNSByPid(processID, "list-netdevs", peersStr...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list-netdevs failed: %s", err)
|
||||
}
|
||||
err = json.Unmarshal(nds, &netdevs)
|
||||
|
||||
return netdevs, err
|
||||
}
|
||||
|
||||
// Get the weave bridge interface.
|
||||
// NB: Should be called from the root network namespace.
|
||||
func GetBridgeNetDev(bridgeName string) (Dev, error) {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return Dev{}, err
|
||||
}
|
||||
|
||||
return LinkToNetDev(link)
|
||||
}
|
||||
+31
-7
@@ -1,7 +1,11 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
@@ -54,12 +58,32 @@ func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlin
|
||||
})
|
||||
}
|
||||
|
||||
func WithNetNSLinkByPidUnsafe(pid int, ifName string, work func(link netlink.Link) error) error {
|
||||
ns, err := netns.GetFromPid(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
var WeaveUtilCmd = "weaveutil"
|
||||
|
||||
return WithNetNSLinkUnsafe(ns, ifName, work)
|
||||
// A safe version of WithNetNS* which creates a process executing
|
||||
// "nsenter --net=<ns-path> weaveutil <cmd> [args]".
|
||||
func WithNetNS(nsPath string, cmd string, args ...string) ([]byte, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
args = append([]string{"--net=" + nsPath, WeaveUtilCmd, cmd}, args...)
|
||||
c := exec.Command("nsenter", args...)
|
||||
c.Stdout = &stdout
|
||||
c.Stderr = &stderr
|
||||
if err := c.Run(); err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", string(stderr.Bytes()), err)
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
func WithNetNSByPid(pid int, cmd string, args ...string) ([]byte, error) {
|
||||
return WithNetNS(NSPathByPid(pid), cmd, args...)
|
||||
}
|
||||
|
||||
func NSPathByPid(pid int) string {
|
||||
return NSPathByPidWithRoot("/", pid)
|
||||
}
|
||||
|
||||
func NSPathByPidWithRoot(root string, pid int) string {
|
||||
return filepath.Join(root, fmt.Sprintf("/proc/%d/ns/net", pid))
|
||||
}
|
||||
+2
-2
@@ -13,7 +13,7 @@ import (
|
||||
func CheckNetworkFree(subnet *net.IPNet, ignoreIfaceNames map[string]struct{}) error {
|
||||
return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error {
|
||||
if route.Dst != nil && overlaps(route.Dst, subnet) {
|
||||
return fmt.Errorf("Network %s overlaps with existing route %s on host.", subnet, route.Dst)
|
||||
return fmt.Errorf("Network %s overlaps with existing route %s on host", subnet, route.Dst)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -29,7 +29,7 @@ func overlaps(n1, n2 *net.IPNet) bool {
|
||||
func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) error {
|
||||
return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error {
|
||||
if route.Dst != nil && route.Dst.Contains(addr) {
|
||||
return fmt.Errorf("Address %s overlaps with existing route %s on host.", addr, route.Dst)
|
||||
return fmt.Errorf("Address %s overlaps with existing route %s on host", addr, route.Dst)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
"github.com/weaveworks/weave/db"
|
||||
)
|
||||
|
||||
func getOldStyleSystemUUID() ([]byte, error) {
|
||||
uuid, err := ioutil.ReadFile("/sys/class/dmi/id/product_uuid")
|
||||
if os.IsNotExist(err) {
|
||||
uuid, err = ioutil.ReadFile("/sys/hypervisor/uuid")
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
func getSystemUUID(hostRoot string) ([]byte, error) {
|
||||
uuid, err := getOldStyleSystemUUID()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
machineid, err := ioutil.ReadFile(hostRoot + "/etc/machine-id")
|
||||
if os.IsNotExist(err) {
|
||||
machineid, err = ioutil.ReadFile(hostRoot + "/var/lib/dbus/machine-id")
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if len(uuid) == 0 && len(machineid) == 0 {
|
||||
return nil, errors.New("All system IDs are blank")
|
||||
}
|
||||
return append(machineid, uuid...), nil
|
||||
}
|
||||
|
||||
func getPersistedPeerName(dbPrefix string) (mesh.PeerName, error) {
|
||||
d, err := db.NewBoltDBReadOnly(dbPrefix)
|
||||
if err != nil {
|
||||
return mesh.UnknownPeerName, err
|
||||
}
|
||||
defer d.Close()
|
||||
var peerName mesh.PeerName
|
||||
nameFound, err := d.Load(db.NameIdent, &peerName)
|
||||
if err != nil || !nameFound {
|
||||
return mesh.UnknownPeerName, err
|
||||
}
|
||||
return peerName, nil
|
||||
}
|
||||
|
||||
// GetSystemPeerName returns an ID derived from concatenated machine-id
|
||||
// (either systemd or dbus), the system (aka bios) UUID and the
|
||||
// hypervisor UUID. It is tweaked and formatted to be usable as a mac address
|
||||
func GetSystemPeerName(dbPrefix, hostRoot string) (string, error) {
|
||||
// Check if we have a persisted name that matches the old-style ID for this host
|
||||
if oldUUID, err := getOldStyleSystemUUID(); err == nil {
|
||||
if _, err := os.Stat(db.Pathname(dbPrefix)); err == nil {
|
||||
persistedPeerName, err := getPersistedPeerName(dbPrefix)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
if persistedPeerName == mesh.PeerNameFromBin(MACfromUUID(oldUUID)) {
|
||||
return persistedPeerName.String(), nil
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
var mac net.HardwareAddr
|
||||
if uuid, err := getSystemUUID(hostRoot); err == nil {
|
||||
mac = MACfromUUID(uuid)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
} else {
|
||||
mac, err = RandomMAC()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return mac.String(), nil
|
||||
}
|
||||
+195
-67
@@ -3,13 +3,14 @@ package net
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
)
|
||||
|
||||
// create and attach a veth to the Weave bridge
|
||||
@@ -37,22 +38,17 @@ func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bo
|
||||
return nil, fmt.Errorf(format, a...)
|
||||
}
|
||||
|
||||
switch bridgeType := DetectBridgeType(bridgeName, DatapathName); bridgeType {
|
||||
case Bridge, BridgedFastdp:
|
||||
if err := netlink.LinkSetMasterByIndex(veth, bridge.Attrs().Index); err != nil {
|
||||
return cleanup(`unable to set master of %s: %s`, name, err)
|
||||
bridgeType, err := ExistingBridgeType(bridgeName, DatapathName)
|
||||
if err != nil {
|
||||
return cleanup("detect bridge type: %s", err)
|
||||
}
|
||||
if err := bridgeType.attach(veth); err != nil {
|
||||
return cleanup("attaching veth %q to %q: %s", name, bridgeName, err)
|
||||
}
|
||||
if !bridgeType.IsFastdp() && !keepTXOn {
|
||||
if err := EthtoolTXOff(veth.PeerName); err != nil {
|
||||
return cleanup(`unable to set tx off on %q: %s`, peerName, err)
|
||||
}
|
||||
if bridgeType == Bridge && !keepTXOn {
|
||||
if err := EthtoolTXOff(peerName); err != nil {
|
||||
return cleanup(`unable to set tx off on %q: %s`, peerName, err)
|
||||
}
|
||||
}
|
||||
case Fastdp:
|
||||
if err := odp.AddDatapathInterface(bridgeName, name); err != nil {
|
||||
return cleanup(`failed to attach %s to device "%s": %s`, name, bridgeName, err)
|
||||
}
|
||||
default:
|
||||
return cleanup(`invalid bridge configuration`)
|
||||
}
|
||||
|
||||
if init != nil {
|
||||
@@ -103,34 +99,29 @@ const (
|
||||
vethPrefix = "v" + VethName // starts with "veth" to suppress UI notifications
|
||||
)
|
||||
|
||||
func interfaceExistsInNamespace(ns netns.NsHandle, ifName string) bool {
|
||||
err := WithNetNSUnsafe(ns, func() error {
|
||||
_, err := netlink.LinkByName(ifName)
|
||||
return err
|
||||
})
|
||||
func interfaceExistsInNamespace(netNSPath string, ifName string) bool {
|
||||
_, err := WithNetNS(netNSPath, "check-iface", ifName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool) error {
|
||||
if !interfaceExistsInNamespace(ns, ifName) {
|
||||
func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool, hairpinMode bool) error {
|
||||
ns, err := netns.GetFromPath(netNSPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
if !interfaceExistsInNamespace(netNSPath, ifName) {
|
||||
maxIDLen := IFNAMSIZ - 1 - len(vethPrefix+"pl")
|
||||
if len(id) > maxIDLen {
|
||||
id = id[:maxIDLen] // trim passed ID if too long
|
||||
}
|
||||
name, peerName := vethPrefix+"pl"+id, vethPrefix+"pg"+id
|
||||
_, err := CreateAndAttachVeth(name, peerName, bridgeName, mtu, keepTXOn, func(veth netlink.Link) error {
|
||||
veth, err := CreateAndAttachVeth(name, peerName, bridgeName, mtu, keepTXOn, func(veth netlink.Link) error {
|
||||
if err := netlink.LinkSetNsFd(veth, int(ns)); err != nil {
|
||||
return fmt.Errorf("failed to move veth to container netns: %s", err)
|
||||
}
|
||||
if err := WithNetNSUnsafe(ns, func() error {
|
||||
if err := netlink.LinkSetName(veth, ifName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ConfigureARPCache(ifName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
if _, err := WithNetNS(netNSPath, "setup-iface", peerName, ifName); err != nil {
|
||||
return fmt.Errorf("error setting up interface: %s", err)
|
||||
}
|
||||
return nil
|
||||
@@ -138,45 +129,112 @@ func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = netlink.LinkSetHairpin(veth, hairpinMode); err != nil {
|
||||
return fmt.Errorf("unable to set hairpin mode to %t for bridge side of veth %s: %s", hairpinMode, name, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := WithNetNSLinkUnsafe(ns, ifName, func(veth netlink.Link) error {
|
||||
newAddresses, err := AddAddresses(veth, cidrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := netlink.LinkSetUp(veth); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ipnet := range newAddresses {
|
||||
// If we don't wait for a bit here, we see the arp fail to reach the bridge.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
arping.GratuitousArpOverIfaceByName(ipnet.IP, ifName)
|
||||
}
|
||||
if withMulticastRoute {
|
||||
/* Route multicast packets across the weave network.
|
||||
This must come last in 'attach'. If you change this, change weavewait to match.
|
||||
|
||||
TODO: Add the MTU lock to prevent PMTU discovery for multicast
|
||||
destinations. Without that, the kernel sets the DF flag on
|
||||
multicast packets. Since RFC1122 prohibits sending of ICMP
|
||||
errors for packets with multicast destinations, that causes
|
||||
packets larger than the PMTU to be dropped silently. */
|
||||
|
||||
_, multicast, _ := net.ParseCIDR("224.0.0.0/4")
|
||||
if err := AddRoute(veth, netlink.SCOPE_LINK, multicast, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
args := []string{ifName, fmt.Sprintf("%t", withMulticastRoute)}
|
||||
for _, cidr := range cidrs {
|
||||
args = append(args, cidr.String())
|
||||
}
|
||||
if _, err := WithNetNS(netNSPath, "setup-iface-addrs", args...); err != nil {
|
||||
return fmt.Errorf("error setting up interface addresses: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DetachContainer(ns netns.NsHandle, id, ifName string, cidrs []*net.IPNet) error {
|
||||
// SetupIfaceAddrs is the implementation of the 'setup-iface-addrs' call above,
|
||||
// running in another process in the container's netns
|
||||
func SetupIfaceAddrs(veth netlink.Link, withMulticastRoute bool, cidrs []*net.IPNet) error {
|
||||
newAddresses, err := AddAddresses(veth, cidrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ifName := veth.Attrs().Name
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add multicast ACCEPT rules for new subnets
|
||||
for _, ipnet := range newAddresses {
|
||||
acceptRule := []string{"-i", ifName, "-s", subnet(ipnet), "-d", "224.0.0.0/4", "-j", "ACCEPT"}
|
||||
exists, err := ipt.Exists("filter", "INPUT", acceptRule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := ipt.Insert("filter", "INPUT", 1, acceptRule...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(veth); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ipnet := range newAddresses {
|
||||
// If we don't wait for a bit here, we see the arp fail to reach the bridge.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
arping.GratuitousArpOverIfaceByName(ipnet.IP, ifName)
|
||||
}
|
||||
if withMulticastRoute {
|
||||
/* Route multicast packets across the weave network.
|
||||
This must come last in 'attach'. If you change this, change weavewait to match.
|
||||
|
||||
TODO: Add the MTU lock to prevent PMTU discovery for multicast
|
||||
destinations. Without that, the kernel sets the DF flag on
|
||||
multicast packets. Since RFC1122 prohibits sending of ICMP
|
||||
errors for packets with multicast destinations, that causes
|
||||
packets larger than the PMTU to be dropped silently. */
|
||||
|
||||
_, multicast, _ := net.ParseCIDR("224.0.0.0/4")
|
||||
if err := AddRoute(veth, netlink.SCOPE_LINK, multicast, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupIface is the implementation of the 'setup-iface' call above,
|
||||
// running in another process in the container's netns
|
||||
func SetupIface(ifaceName, newIfName string) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := netlink.LinkSetName(link, newIfName); err != nil {
|
||||
return err
|
||||
}
|
||||
// This is only called by AttachContainer which is only called in host pid namespace
|
||||
if err := ConfigureARPCache("/proc", newIfName); err != nil {
|
||||
return err
|
||||
}
|
||||
return ipt.Append("filter", "INPUT", "-i", newIfName, "-d", "224.0.0.0/4", "-j", "DROP")
|
||||
}
|
||||
|
||||
// NB: This function can be used only by a process that terminates immediately
|
||||
// after calling the function as it changes netns via WithNetNSLinkUnsafe.
|
||||
func DetachContainer(netNSPath, id, ifName string, cidrs []*net.IPNet) error {
|
||||
ns, err := netns.GetFromPath(netNSPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WithNetNSLinkUnsafe(ns, ifName, func(veth netlink.Link) error {
|
||||
existingAddrs, err := netlink.AddrList(veth, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
@@ -194,7 +252,31 @@ func DetachContainer(ns netns.NsHandle, id, ifName string, cidrs []*net.IPNet) e
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get IP address for %q: %v", veth.Attrs().Name, err)
|
||||
}
|
||||
|
||||
// Remove multicast ACCEPT rules for subnets we no longer have addresses in
|
||||
subnets := subnets(addrs)
|
||||
rules, err := ipt.List("filter", "INPUT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
ps := strings.Split(rule, " ")
|
||||
if len(ps) == 10 &&
|
||||
ps[0] == "-A" && ps[2] == "-s" && ps[4] == "-d" && ps[5] == "224.0.0.0/4" &&
|
||||
ps[6] == "-i" && ps[7] == ifName && ps[8] == "-j" && ps[9] == "ACCEPT" {
|
||||
|
||||
if _, found := subnets[ps[3]]; !found {
|
||||
if err := ipt.Delete("filter", "INPUT", ps[2:]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(addrs) == 0 { // all addresses gone: remove the interface
|
||||
if err := ipt.Delete("filter", "INPUT", "-i", ifName, "-d", "224.0.0.0/4", "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := netlink.LinkDel(veth); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -202,3 +284,49 @@ func DetachContainer(ns netns.NsHandle, id, ifName string, cidrs []*net.IPNet) e
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func subnet(ipn *net.IPNet) string {
|
||||
ones, _ := ipn.Mask.Size()
|
||||
return fmt.Sprintf("%s/%d", ipn.IP.Mask(ipn.Mask).String(), ones)
|
||||
}
|
||||
|
||||
func subnets(addrs []netlink.Addr) map[string]struct{} {
|
||||
subnets := make(map[string]struct{})
|
||||
for _, addr := range addrs {
|
||||
subnets[subnet(addr.IPNet)] = struct{}{}
|
||||
}
|
||||
return subnets
|
||||
}
|
||||
|
||||
func addNatRule(ipt *iptables.IPTables, rulespec ...string) error {
|
||||
// Loop until we get an exit code other than "temporarily unavailable"
|
||||
for {
|
||||
if err := ipt.AppendUnique("nat", "WEAVE", rulespec...); err != nil {
|
||||
if ierr, ok := err.(*iptables.Error); ok {
|
||||
if status, ok := ierr.ExitError.Sys().(syscall.WaitStatus); ok {
|
||||
// (magic exit code 4 found in iptables source code; undocumented)
|
||||
if status.ExitStatus() == 4 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ExposeNAT(ipnet net.IPNet) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cidr := ipnet.String()
|
||||
if err := addNatRule(ipt, "-s", cidr, "-d", "224.0.0.0/4", "-j", "RETURN"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addNatRule(ipt, "-d", cidr, "!", "-s", cidr, "-j", "MASQUERADE"); err != nil {
|
||||
return err
|
||||
}
|
||||
return addNatRule(ipt, "-s", cidr, "!", "-d", cidr, "-j", "MASQUERADE")
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/pkg/api"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/util/intstr"
|
||||
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
)
|
||||
|
||||
func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
|
||||
rules map[string]*ruleSpec,
|
||||
nsSelectors, podSelectors map[string]*selectorSpec,
|
||||
err error) {
|
||||
|
||||
nsSelectors = make(map[string]*selectorSpec)
|
||||
podSelectors = make(map[string]*selectorSpec)
|
||||
rules = make(map[string]*ruleSpec)
|
||||
|
||||
dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, ns.name, ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
podSelectors[dstSelector.key] = dstSelector
|
||||
|
||||
for _, ingressRule := range policy.Spec.Ingress {
|
||||
if ingressRule.Ports != nil && len(ingressRule.Ports) == 0 {
|
||||
// Ports is empty, this rule matches no ports (no traffic matches).
|
||||
continue
|
||||
}
|
||||
|
||||
if ingressRule.From != nil && len(ingressRule.From) == 0 {
|
||||
// From is empty, this rule matches no sources (no traffic matches).
|
||||
continue
|
||||
}
|
||||
|
||||
if ingressRule.From == nil {
|
||||
// From is not provided, this rule matches all sources (traffic not restricted by source).
|
||||
if ingressRule.Ports == nil {
|
||||
// Ports is not provided, this rule matches all ports (traffic not restricted by port).
|
||||
rule := newRuleSpec(nil, nil, dstSelector, nil)
|
||||
rules[rule.key] = rule
|
||||
} else {
|
||||
// Ports is present and contains at least one item, then this rule allows traffic
|
||||
// only if the traffic matches at least one port in the ports list.
|
||||
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
|
||||
rule := newRuleSpec(&proto, nil, dstSelector, &port)
|
||||
rules[rule.key] = rule
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// From is present and contains at least on item, this rule allows traffic only if the
|
||||
// traffic matches at least one item in the from list.
|
||||
for _, peer := range ingressRule.From {
|
||||
var srcSelector *selectorSpec
|
||||
if peer.PodSelector != nil {
|
||||
srcSelector, err = newSelectorSpec(peer.PodSelector, ns.name, ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
podSelectors[srcSelector.key] = srcSelector
|
||||
}
|
||||
if peer.NamespaceSelector != nil {
|
||||
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, "", ipset.ListSet)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
nsSelectors[srcSelector.key] = srcSelector
|
||||
}
|
||||
|
||||
if ingressRule.Ports == nil {
|
||||
// Ports is not provided, this rule matches all ports (traffic not restricted by port).
|
||||
rule := newRuleSpec(nil, srcSelector, dstSelector, nil)
|
||||
rules[rule.key] = rule
|
||||
} else {
|
||||
// Ports is present and contains at least one item, then this rule allows traffic
|
||||
// only if the traffic matches at least one port in the ports list.
|
||||
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
|
||||
rule := newRuleSpec(&proto, srcSelector, dstSelector, &port)
|
||||
rules[rule.key] = rule
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nsSelectors, podSelectors, nil
|
||||
}
|
||||
|
||||
func withNormalisedProtoAndPort(npps []extnapi.NetworkPolicyPort, f func(proto, port string)) {
|
||||
for _, npp := range npps {
|
||||
// If no proto is specified, default to TCP
|
||||
proto := string(api.ProtocolTCP)
|
||||
if npp.Protocol != nil {
|
||||
proto = string(*npp.Protocol)
|
||||
}
|
||||
|
||||
// If no port is specified, match any port. Let iptables executable handle
|
||||
// service name resolution
|
||||
port := "0:65535"
|
||||
if npp.Port != nil {
|
||||
switch npp.Port.Type {
|
||||
case intstr.Int:
|
||||
port = fmt.Sprintf("%d", npp.Port.IntVal)
|
||||
case intstr.String:
|
||||
port = npp.Port.StrVal
|
||||
}
|
||||
}
|
||||
|
||||
f(proto, port)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package npc
|
||||
|
||||
// These types are defined in https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/network-policy.md
|
||||
// but not in the k8s API yet. Copies are included here for decoding the `net.beta.kubernetes.io/network-policy`
|
||||
// annotation specified in the above document.
|
||||
|
||||
type IngressIsolationPolicy string
|
||||
|
||||
const (
|
||||
// Deny all ingress traffic to pods in this namespace. Ingress means
|
||||
// any incoming traffic to pods, whether that be from other pods within this namespace
|
||||
// or any source outside of this namespace.
|
||||
DefaultDeny IngressIsolationPolicy = "DefaultDeny"
|
||||
)
|
||||
|
||||
// Standard NamespaceSpec object, modified to include a new
|
||||
// NamespaceNetworkPolicy field.
|
||||
type NamespaceSpec struct {
|
||||
// This is a pointer so that it can be left undefined.
|
||||
NetworkPolicy *NamespaceNetworkPolicy `json:"networkPolicy,omitempty"`
|
||||
}
|
||||
|
||||
type NamespaceNetworkPolicy struct {
|
||||
// Ingress configuration for this namespace. This config is
|
||||
// applied to all pods within this namespace. For now, only
|
||||
// ingress is supported. This field is optional - if not
|
||||
// defined, then the cluster default for ingress is applied.
|
||||
Ingress *NamespaceIngressPolicy `json:"ingress,omitempty"`
|
||||
}
|
||||
|
||||
// Configuration for ingress to pods within this namespace.
|
||||
// For now, this only supports specifying an isolation policy.
|
||||
type NamespaceIngressPolicy struct {
|
||||
// The isolation policy to apply to pods in this namespace.
|
||||
// Currently this field only supports "DefaultDeny", but could
|
||||
// be extended to support other policies in the future. When set to DefaultDeny,
|
||||
// pods in this namespace are denied ingress traffic by default. When not defined,
|
||||
// the cluster default ingress isolation policy is applied (currently allow all).
|
||||
Isolation *IngressIsolationPolicy `json:"isolation,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package npc
|
||||
|
||||
const (
|
||||
TableFilter = "filter"
|
||||
|
||||
MainChain = "WEAVE-NPC"
|
||||
DefaultChain = "WEAVE-NPC-DEFAULT"
|
||||
IngressChain = "WEAVE-NPC-INGRESS"
|
||||
|
||||
IpsetNamePrefix = "weave-"
|
||||
|
||||
LocalIpset = IpsetNamePrefix + "local-pods"
|
||||
)
|
||||
@@ -0,0 +1,176 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
)
|
||||
|
||||
type NetworkPolicyController interface {
|
||||
AddNamespace(ns *coreapi.Namespace) error
|
||||
UpdateNamespace(oldObj, newObj *coreapi.Namespace) error
|
||||
DeleteNamespace(ns *coreapi.Namespace) error
|
||||
|
||||
AddPod(obj *coreapi.Pod) error
|
||||
UpdatePod(oldObj, newObj *coreapi.Pod) error
|
||||
DeletePod(obj *coreapi.Pod) error
|
||||
|
||||
AddNetworkPolicy(obj *extnapi.NetworkPolicy) error
|
||||
UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error
|
||||
DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
sync.Mutex
|
||||
|
||||
nodeName string // my node name
|
||||
|
||||
ipt iptables.Interface
|
||||
ips ipset.Interface
|
||||
|
||||
nss map[string]*ns // ns name -> ns struct
|
||||
nsSelectors *selectorSet // selector string -> nsSelector
|
||||
}
|
||||
|
||||
func New(nodeName string, ipt iptables.Interface, ips ipset.Interface) NetworkPolicyController {
|
||||
c := &controller{
|
||||
nodeName: nodeName,
|
||||
ipt: ipt,
|
||||
ips: ips,
|
||||
nss: make(map[string]*ns)}
|
||||
|
||||
c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (npc *controller) onNewNsSelector(selector *selector) error {
|
||||
for _, ns := range npc.nss {
|
||||
if ns.namespace != nil {
|
||||
if selector.matches(ns.namespace.ObjectMeta.Labels) {
|
||||
if err := selector.addEntry(string(ns.allPods.ipsetName), namespaceComment(ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (npc *controller) withNS(name string, f func(ns *ns) error) error {
|
||||
ns, found := npc.nss[name]
|
||||
if !found {
|
||||
newNs, err := newNS(name, npc.nodeName, npc.ipt, npc.ips, npc.nsSelectors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
npc.nss[name] = newNs
|
||||
ns = newNs
|
||||
}
|
||||
if err := f(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
if ns.empty() {
|
||||
if err := ns.destroy(); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(npc.nss, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (npc *controller) AddPod(obj *coreapi.Pod) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Debugf("EVENT AddPod %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.addPod(obj), "add pod")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) UpdatePod(oldObj, newObj *coreapi.Pod) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Debugf("EVENT UpdatePod %s %s", js(oldObj), js(newObj))
|
||||
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.updatePod(oldObj, newObj), "update pod")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) DeletePod(obj *coreapi.Pod) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Debugf("EVENT DeletePod %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.deletePod(obj), "delete pod")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) AddNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT AddNetworkPolicy %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.addNetworkPolicy(obj), "add network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT UpdateNetworkPolicy %s %s", js(oldObj), js(newObj))
|
||||
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.updateNetworkPolicy(oldObj, newObj), "update network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT DeleteNetworkPolicy %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return errors.Wrap(ns.deleteNetworkPolicy(obj), "delete network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) AddNamespace(obj *coreapi.Namespace) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT AddNamespace %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Name, func(ns *ns) error {
|
||||
return errors.Wrap(ns.addNamespace(obj), "add namespace")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) UpdateNamespace(oldObj, newObj *coreapi.Namespace) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT UpdateNamespace %s %s", js(oldObj), js(newObj))
|
||||
return npc.withNS(oldObj.ObjectMeta.Name, func(ns *ns) error {
|
||||
return errors.Wrap(ns.updateNamespace(oldObj, newObj), "update namespace")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) DeleteNamespace(obj *coreapi.Namespace) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
common.Log.Infof("EVENT DeleteNamespace %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Name, func(ns *ns) error {
|
||||
return errors.Wrap(ns.deleteNamespace(obj), "delete namespace")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
unversionedapi "k8s.io/client-go/pkg/api/unversioned"
|
||||
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/util/intstr"
|
||||
)
|
||||
|
||||
type mockSet struct {
|
||||
name ipset.Name
|
||||
setType ipset.Type
|
||||
subSets map[string]bool
|
||||
}
|
||||
|
||||
type mockIPSet struct {
|
||||
sets map[string]mockSet
|
||||
}
|
||||
|
||||
func newMockIPSet() mockIPSet {
|
||||
i := mockIPSet{
|
||||
sets: make(map[string]mockSet),
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *mockIPSet) Create(ipsetName ipset.Name, ipsetType ipset.Type) error {
|
||||
if _, ok := i.sets[string(ipsetName)]; ok {
|
||||
return errors.Errorf("ipset %s already exists", ipsetName)
|
||||
}
|
||||
i.sets[string(ipsetName)] = mockSet{name: ipsetName, setType: ipsetType, subSets: make(map[string]bool)}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) AddEntry(ipsetName ipset.Name, entry string, comment string) error {
|
||||
log.Printf("adding entry %s to %s", entry, ipsetName)
|
||||
if _, ok := i.sets[entry]; !ok {
|
||||
return errors.Errorf("ipset %s does not exist", entry)
|
||||
}
|
||||
if _, ok := i.sets[string(ipsetName)].subSets[entry]; ok {
|
||||
return errors.Errorf("ipset %s is already a member of %s", entry, ipsetName)
|
||||
}
|
||||
i.sets[string(ipsetName)].subSets[entry] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) DelEntry(ipsetName ipset.Name, entry string) error {
|
||||
log.Printf("deleting entry %s from %s", entry, ipsetName)
|
||||
if _, ok := i.sets[string(ipsetName)]; !ok {
|
||||
return errors.Errorf("ipset %s does not exist", ipsetName)
|
||||
}
|
||||
if _, ok := i.sets[string(ipsetName)].subSets[entry]; !ok {
|
||||
return errors.Errorf("ipset %s is not a member of %s", entry, ipsetName)
|
||||
}
|
||||
delete(i.sets[string(ipsetName)].subSets, entry)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) Flush(ipsetName ipset.Name) error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func (i *mockIPSet) FlushAll() error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func (i *mockIPSet) Destroy(ipsetName ipset.Name) error {
|
||||
if _, ok := i.sets[string(ipsetName)]; !ok {
|
||||
return errors.Errorf("ipset %s does not exist", ipsetName)
|
||||
}
|
||||
delete(i.sets, string(ipsetName))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) DestroyAll() error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func (i *mockIPSet) List(prefix string) ([]ipset.Name, error) {
|
||||
return []ipset.Name{}, errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
type mockIPTables struct {
|
||||
}
|
||||
|
||||
func (ipt *mockIPTables) Append(table, chain string, rulespec ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipt *mockIPTables) Delete(table, chain string, rulespec ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipt *mockIPTables) Insert(table, chain string, pos int, rulespec ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
|
||||
// Test for race condition between namespace and networkpolicy events
|
||||
// https://github.com/weaveworks/weave/issues/3059
|
||||
|
||||
sourceNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: coreapi.ObjectMeta{
|
||||
Name: "source",
|
||||
Labels: map[string]string{
|
||||
"app": "source",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
destinationNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: coreapi.ObjectMeta{
|
||||
Name: "destination",
|
||||
},
|
||||
}
|
||||
|
||||
port := intstr.FromInt(12345)
|
||||
|
||||
networkPolicy := &extnapi.NetworkPolicy{
|
||||
ObjectMeta: coreapi.ObjectMeta{
|
||||
Name: "network-policy",
|
||||
Namespace: "destination",
|
||||
},
|
||||
Spec: extnapi.NetworkPolicySpec{
|
||||
Ingress: []extnapi.NetworkPolicyIngressRule{
|
||||
{
|
||||
From: []extnapi.NetworkPolicyPeer{
|
||||
{
|
||||
NamespaceSelector: &unversionedapi.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "source",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: []extnapi.NetworkPolicyPort{
|
||||
{
|
||||
Port: &port,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Namespaces first
|
||||
m := newMockIPSet()
|
||||
controller := New("foo", &mockIPTables{}, &m)
|
||||
|
||||
const (
|
||||
selectorIPSetName = "weave-I239Zp%sCvoVt*D6u=A!2]YEk"
|
||||
sourceIPSetName = "weave-HboJG1fGgG]/SR%k9H#hv5e96"
|
||||
)
|
||||
|
||||
controller.AddNamespace(sourceNamespace)
|
||||
controller.AddNamespace(destinationNamespace)
|
||||
|
||||
controller.AddNetworkPolicy(networkPolicy)
|
||||
|
||||
require.Equal(t, true, m.sets[selectorIPSetName].subSets[sourceIPSetName])
|
||||
|
||||
// NetworkPolicy first
|
||||
m = newMockIPSet()
|
||||
controller = New("foo", &mockIPTables{}, &m)
|
||||
|
||||
controller.AddNetworkPolicy(networkPolicy)
|
||||
|
||||
controller.AddNamespace(sourceNamespace)
|
||||
controller.AddNamespace(destinationNamespace)
|
||||
|
||||
require.Equal(t, true, m.sets[selectorIPSetName].subSets[sourceIPSetName])
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Name string
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
ListSet = Type("list:set")
|
||||
HashIP = Type("hash:ip")
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Create(ipsetName Name, ipsetType Type) error
|
||||
AddEntry(ipsetName Name, entry string, comment string) error
|
||||
DelEntry(ipsetName Name, entry string) error
|
||||
Flush(ipsetName Name) error
|
||||
Destroy(ipsetName Name) error
|
||||
|
||||
List(prefix string) ([]Name, error)
|
||||
|
||||
FlushAll() error
|
||||
DestroyAll() error
|
||||
}
|
||||
|
||||
type ipset struct {
|
||||
refCount
|
||||
*log.Logger
|
||||
enableComments bool
|
||||
}
|
||||
|
||||
func New(logger *log.Logger) Interface {
|
||||
ips := &ipset{refCount: newRefCount(), Logger: logger, enableComments: true}
|
||||
|
||||
// Check for comment support
|
||||
testIpsetName := Name("weave-test-comment")
|
||||
// Clear it out if it already exists
|
||||
_ = ips.Destroy(testIpsetName)
|
||||
// Test for comment support
|
||||
if err := ips.Create(testIpsetName, HashIP); err != nil {
|
||||
ips.Logger.Printf("failed to create %s; disabling comment support", testIpsetName)
|
||||
ips.enableComments = false
|
||||
}
|
||||
// If it was created, destroy it
|
||||
_ = ips.Destroy(testIpsetName)
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
func (i *ipset) Create(ipsetName Name, ipsetType Type) error {
|
||||
args := []string{"create", string(ipsetName), string(ipsetType)}
|
||||
if i.enableComments {
|
||||
args = append(args, "comment")
|
||||
}
|
||||
return doExec(args...)
|
||||
}
|
||||
|
||||
func (i *ipset) AddEntry(ipsetName Name, entry string, comment string) error {
|
||||
i.Logger.Printf("adding entry %s to %s", entry, ipsetName)
|
||||
if i.inc(ipsetName, entry) > 1 { // already in the set
|
||||
return nil
|
||||
}
|
||||
args := []string{"add", string(ipsetName), entry}
|
||||
if i.enableComments {
|
||||
args = append(args, "comment", comment)
|
||||
}
|
||||
return doExec(args...)
|
||||
}
|
||||
|
||||
func (i *ipset) DelEntry(ipsetName Name, entry string) error {
|
||||
i.Logger.Printf("deleting entry %s from %s", entry, ipsetName)
|
||||
if i.dec(ipsetName, entry) > 0 { // still needed
|
||||
return nil
|
||||
}
|
||||
return doExec("del", string(ipsetName), entry)
|
||||
}
|
||||
|
||||
func (i *ipset) Flush(ipsetName Name) error {
|
||||
i.removeSet(ipsetName)
|
||||
return doExec("flush", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) FlushAll() error {
|
||||
i.refCount = newRefCount()
|
||||
return doExec("flush")
|
||||
}
|
||||
|
||||
func (i *ipset) Destroy(ipsetName Name) error {
|
||||
i.removeSet(ipsetName)
|
||||
return doExec("destroy", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) DestroyAll() error {
|
||||
i.refCount = newRefCount()
|
||||
return doExec("destroy")
|
||||
}
|
||||
|
||||
// Fetch a list of all existing sets with a given prefix
|
||||
func (i *ipset) List(prefix string) ([]Name, error) {
|
||||
output, err := exec.Command("ipset", "list", "-name", "-output", "plain").Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var selected []Name
|
||||
sets := strings.Split(string(output), "\n")
|
||||
for _, v := range sets {
|
||||
if strings.HasPrefix(v, prefix) {
|
||||
selected = append(selected, Name(v))
|
||||
}
|
||||
}
|
||||
|
||||
return selected, err
|
||||
}
|
||||
|
||||
func doExec(args ...string) error {
|
||||
if output, err := exec.Command("ipset", args...).CombinedOutput(); err != nil {
|
||||
return errors.Wrapf(err, "ipset %v failed: %s", args, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reference-counting
|
||||
type key struct {
|
||||
ipsetName Name
|
||||
entry string
|
||||
}
|
||||
|
||||
// note no locking is required as all operations are serialised in the controller
|
||||
type refCount struct {
|
||||
ref map[key]int
|
||||
}
|
||||
|
||||
func newRefCount() refCount {
|
||||
return refCount{ref: make(map[key]int)}
|
||||
}
|
||||
|
||||
func (rc *refCount) inc(ipsetName Name, entry string) int {
|
||||
k := key{ipsetName, entry}
|
||||
rc.ref[k]++
|
||||
return rc.ref[k]
|
||||
}
|
||||
|
||||
func (rc *refCount) dec(ipsetName Name, entry string) int {
|
||||
k := key{ipsetName, entry}
|
||||
rc.ref[k]--
|
||||
return rc.ref[k]
|
||||
}
|
||||
|
||||
func (rc *refCount) removeSet(ipsetName Name) {
|
||||
for k := range rc.ref {
|
||||
if k.ipsetName == ipsetName {
|
||||
delete(rc.ref, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package iptables
|
||||
|
||||
type Interface interface {
|
||||
Append(table, chain string, rulespec ...string) error
|
||||
Delete(table, chain string, rulespec ...string) error
|
||||
Insert(table, chain string, pos int, rulespec ...string) error
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func js(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcapgo"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
var (
|
||||
blockedConnections = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "weavenpc_blocked_connections_total",
|
||||
Help: "Connection attempts blocked by policy controller.",
|
||||
},
|
||||
[]string{"protocol", "dport"},
|
||||
)
|
||||
)
|
||||
|
||||
func gatherMetrics() {
|
||||
pipe, err := os.Open("/var/log/ulogd.pcap")
|
||||
if err != nil {
|
||||
common.Log.Fatalf("Failed to open pcap: %v", err)
|
||||
}
|
||||
|
||||
reader, err := pcapgo.NewReader(pipe)
|
||||
if err != nil {
|
||||
common.Log.Fatalf("Failed to read pcap header: %v", err)
|
||||
}
|
||||
|
||||
for {
|
||||
data, _, err := reader.ReadPacketData()
|
||||
if err != nil {
|
||||
common.Log.Fatalf("Failed to read pcap packet: %v", err)
|
||||
}
|
||||
|
||||
packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
||||
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
if tcp.SYN && !tcp.ACK { // Only plain SYN constitutes a NEW TCP connection
|
||||
blockedConnections.With(prometheus.Labels{"protocol": "tcp", "dport": strconv.Itoa(int(tcp.DstPort))}).Inc()
|
||||
common.Log.Warnf("TCP connection from %v:%d to %v:%d blocked by Weave NPC.", srcIP(packet), tcp.SrcPort, dstIP(packet), tcp.DstPort)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil {
|
||||
udp, _ := udpLayer.(*layers.UDP)
|
||||
blockedConnections.With(prometheus.Labels{"protocol": "udp", "dport": strconv.Itoa(int(udp.DstPort))}).Inc()
|
||||
common.Log.Warnf("UDP connection from %v:%d to %v:%d blocked by Weave NPC.", srcIP(packet), udp.SrcPort, dstIP(packet), udp.DstPort)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unknownIP string = "<unknown IP>"
|
||||
|
||||
func srcIP(packet gopacket.Packet) string {
|
||||
if layer := packet.Layer(layers.LayerTypeIPv4); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv4); ok {
|
||||
return ip.SrcIP.String()
|
||||
}
|
||||
}
|
||||
if layer := packet.Layer(layers.LayerTypeIPv6); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv6); ok {
|
||||
return ip.SrcIP.String()
|
||||
}
|
||||
}
|
||||
return unknownIP
|
||||
}
|
||||
|
||||
func dstIP(packet gopacket.Packet) string {
|
||||
if layer := packet.Layer(layers.LayerTypeIPv4); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv4); ok {
|
||||
return ip.DstIP.String()
|
||||
}
|
||||
}
|
||||
if layer := packet.Layer(layers.LayerTypeIPv6); layer != nil {
|
||||
if ip, ok := layer.(*layers.IPv6); ok {
|
||||
return ip.DstIP.String()
|
||||
}
|
||||
}
|
||||
return unknownIP
|
||||
}
|
||||
|
||||
func Start(addr string) error {
|
||||
if err := prometheus.Register(blockedConnections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
go func() {
|
||||
common.Log.Infof("Serving /metrics on %s", addr)
|
||||
if err := http.ListenAndServe(addr, nil); err != nil {
|
||||
common.Log.Fatalf("Failed to bind metrics server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go gatherMetrics()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/pkg/api/unversioned"
|
||||
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/types"
|
||||
"k8s.io/client-go/pkg/util/uuid"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
)
|
||||
|
||||
type ns struct {
|
||||
ipt iptables.Interface // interface to iptables
|
||||
ips ipset.Interface // interface to ipset
|
||||
|
||||
name string // k8s Namespace name
|
||||
nodeName string // my node name
|
||||
namespace *coreapi.Namespace // k8s Namespace object
|
||||
pods map[types.UID]*coreapi.Pod // k8s Pod objects by UID
|
||||
policies map[types.UID]*extnapi.NetworkPolicy // k8s NetworkPolicy objects by UID
|
||||
|
||||
uid types.UID // surrogate UID to own allPods selector
|
||||
allPods *selectorSpec // hash:ip ipset of all pod IPs in this namespace
|
||||
|
||||
nsSelectors *selectorSet
|
||||
podSelectors *selectorSet
|
||||
rules *ruleSet
|
||||
}
|
||||
|
||||
func newNS(name, nodeName string, ipt iptables.Interface, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
|
||||
allPods, err := newSelectorSpec(&unversioned.LabelSelector{}, name, ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns := &ns{
|
||||
ipt: ipt,
|
||||
ips: ips,
|
||||
name: name,
|
||||
nodeName: nodeName,
|
||||
pods: make(map[types.UID]*coreapi.Pod),
|
||||
policies: make(map[types.UID]*extnapi.NetworkPolicy),
|
||||
uid: uuid.NewUUID(),
|
||||
allPods: allPods,
|
||||
nsSelectors: nsSelectors,
|
||||
rules: newRuleSet(ipt)}
|
||||
|
||||
ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector)
|
||||
|
||||
if err := ns.podSelectors.provision(ns.uid, nil, map[string]*selectorSpec{ns.allPods.key: ns.allPods}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (ns *ns) empty() bool {
|
||||
return len(ns.pods) == 0 && len(ns.policies) == 0 && ns.namespace == nil
|
||||
}
|
||||
|
||||
func (ns *ns) destroy() error {
|
||||
return ns.podSelectors.deprovision(ns.uid, map[string]*selectorSpec{ns.allPods.key: ns.allPods}, nil)
|
||||
}
|
||||
|
||||
func (ns *ns) onNewPodSelector(selector *selector) error {
|
||||
for _, pod := range ns.pods {
|
||||
if hasIP(pod) {
|
||||
if selector.matches(pod.ObjectMeta.Labels) {
|
||||
if err := selector.addEntry(pod.Status.PodIP, podComment(pod)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) checkLocalPod(obj *coreapi.Pod) bool {
|
||||
if obj.Spec.NodeName != ns.nodeName {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ns *ns) addPod(obj *coreapi.Pod) error {
|
||||
ns.pods[obj.ObjectMeta.UID] = obj
|
||||
|
||||
if !hasIP(obj) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ns.checkLocalPod(obj) {
|
||||
ns.ips.AddEntry(LocalIpset, obj.Status.PodIP, podComment(obj))
|
||||
}
|
||||
return ns.podSelectors.addToMatching(obj.ObjectMeta.Labels, obj.Status.PodIP, podComment(obj))
|
||||
}
|
||||
|
||||
func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
|
||||
delete(ns.pods, oldObj.ObjectMeta.UID)
|
||||
ns.pods[newObj.ObjectMeta.UID] = newObj
|
||||
|
||||
if !hasIP(oldObj) && !hasIP(newObj) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasIP(oldObj) && !hasIP(newObj) {
|
||||
if ns.checkLocalPod(oldObj) {
|
||||
ns.ips.DelEntry(LocalIpset, oldObj.Status.PodIP)
|
||||
}
|
||||
return ns.podSelectors.delFromMatching(oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
|
||||
}
|
||||
|
||||
if !hasIP(oldObj) && hasIP(newObj) {
|
||||
if ns.checkLocalPod(newObj) {
|
||||
ns.ips.AddEntry(LocalIpset, newObj.Status.PodIP, podComment(newObj))
|
||||
}
|
||||
return ns.podSelectors.addToMatching(newObj.ObjectMeta.Labels, newObj.Status.PodIP, podComment(newObj))
|
||||
}
|
||||
|
||||
if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) ||
|
||||
oldObj.Status.PodIP != newObj.Status.PodIP {
|
||||
|
||||
for _, ps := range ns.podSelectors.entries {
|
||||
oldMatch := ps.matches(oldObj.ObjectMeta.Labels)
|
||||
newMatch := ps.matches(newObj.ObjectMeta.Labels)
|
||||
if oldMatch == newMatch && oldObj.Status.PodIP == newObj.Status.PodIP {
|
||||
continue
|
||||
}
|
||||
if oldMatch {
|
||||
if err := ps.delEntry(oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newMatch {
|
||||
if err := ps.addEntry(newObj.Status.PodIP, podComment(newObj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) deletePod(obj *coreapi.Pod) error {
|
||||
delete(ns.pods, obj.ObjectMeta.UID)
|
||||
|
||||
if !hasIP(obj) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ns.checkLocalPod(obj) {
|
||||
ns.ips.DelEntry(LocalIpset, obj.Status.PodIP)
|
||||
}
|
||||
return ns.podSelectors.delFromMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
|
||||
}
|
||||
|
||||
func (ns *ns) addNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
ns.policies[obj.ObjectMeta.UID] = obj
|
||||
|
||||
// Analyse policy, determine which rules and ipsets are required
|
||||
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Provision required resources in dependency order
|
||||
if err := ns.nsSelectors.provision(obj.ObjectMeta.UID, nil, nsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.provision(obj.ObjectMeta.UID, nil, podSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.rules.provision(obj.ObjectMeta.UID, nil, rules)
|
||||
}
|
||||
|
||||
func (ns *ns) updateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||
delete(ns.policies, oldObj.ObjectMeta.UID)
|
||||
ns.policies[newObj.ObjectMeta.UID] = newObj
|
||||
|
||||
// Analyse the old and the new policy so we can determine differences
|
||||
oldRules, oldNsSelectors, oldPodSelectors, err := ns.analysePolicy(oldObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRules, newNsSelectors, newPodSelectors, err := ns.analysePolicy(newObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deprovision unused and provision newly required resources in dependency order
|
||||
if err := ns.rules.deprovision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.deprovision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.deprovision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.provision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.provision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.rules.provision(oldObj.ObjectMeta.UID, oldRules, newRules)
|
||||
}
|
||||
|
||||
func (ns *ns) deleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
delete(ns.policies, obj.ObjectMeta.UID)
|
||||
|
||||
// Analyse network policy to free resources
|
||||
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deprovision unused resources in dependency order
|
||||
if err := ns.rules.deprovision(obj.ObjectMeta.UID, rules, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.deprovision(obj.ObjectMeta.UID, nsSelectors, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.podSelectors.deprovision(obj.ObjectMeta.UID, podSelectors, nil)
|
||||
}
|
||||
|
||||
func bypassRule(nsIpsetName ipset.Name, namespace string) []string {
|
||||
return []string{"-m", "set", "--match-set", string(nsIpsetName), "dst", "-j", "ACCEPT", "-m", "comment", "--comment", "DefaultAllow isolation for namespace: " + namespace}
|
||||
}
|
||||
|
||||
func (ns *ns) ensureBypassRule(nsIpsetName ipset.Name) error {
|
||||
common.Log.Debugf("ensuring rule for DefaultAllow in namespace: %s, set %s", ns.name, nsIpsetName)
|
||||
return ns.ipt.Append(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName, ns.name)...)
|
||||
}
|
||||
|
||||
func (ns *ns) deleteBypassRule(nsIpsetName ipset.Name) error {
|
||||
common.Log.Debugf("removing default rule in namespace: %s, set %s", ns.name, nsIpsetName)
|
||||
return ns.ipt.Delete(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName, ns.name)...)
|
||||
}
|
||||
|
||||
func (ns *ns) addNamespace(obj *coreapi.Namespace) error {
|
||||
ns.namespace = obj
|
||||
|
||||
// Insert a rule to bypass policies if namespace is DefaultAllow
|
||||
if !isDefaultDeny(obj) {
|
||||
if err := ns.ensureBypassRule(ns.allPods.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add namespace ipset to matching namespace selectors
|
||||
return ns.nsSelectors.addToMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName), namespaceComment(ns))
|
||||
}
|
||||
|
||||
func (ns *ns) updateNamespace(oldObj, newObj *coreapi.Namespace) error {
|
||||
ns.namespace = newObj
|
||||
|
||||
// Update bypass rule if ingress default has changed
|
||||
oldDefaultDeny := isDefaultDeny(oldObj)
|
||||
newDefaultDeny := isDefaultDeny(newObj)
|
||||
|
||||
if oldDefaultDeny != newDefaultDeny {
|
||||
common.Log.Infof("namespace DefaultDeny changed from %t to %t", oldDefaultDeny, newDefaultDeny)
|
||||
if oldDefaultDeny {
|
||||
if err := ns.ensureBypassRule(ns.allPods.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newDefaultDeny {
|
||||
if err := ns.deleteBypassRule(ns.allPods.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-evaluate namespace selector membership if labels have changed
|
||||
if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) {
|
||||
for _, selector := range ns.nsSelectors.entries {
|
||||
oldMatch := selector.matches(oldObj.ObjectMeta.Labels)
|
||||
newMatch := selector.matches(newObj.ObjectMeta.Labels)
|
||||
if oldMatch == newMatch {
|
||||
continue
|
||||
}
|
||||
if oldMatch {
|
||||
if err := selector.delEntry(string(ns.allPods.ipsetName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newMatch {
|
||||
if err := selector.addEntry(string(ns.allPods.ipsetName), namespaceComment(ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) deleteNamespace(obj *coreapi.Namespace) error {
|
||||
ns.namespace = nil
|
||||
|
||||
// Remove bypass rule
|
||||
if !isDefaultDeny(obj) {
|
||||
if err := ns.deleteBypassRule(ns.allPods.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove namespace ipset from any matching namespace selectors
|
||||
return ns.nsSelectors.delFromMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||
}
|
||||
|
||||
func hasIP(pod *coreapi.Pod) bool {
|
||||
// Ensure pod isn't dead, has an IP address and isn't sharing the host network namespace
|
||||
return pod.Status.Phase != "Succeeded" && pod.Status.Phase != "Failed" &&
|
||||
len(pod.Status.PodIP) > 0 && !pod.Spec.HostNetwork
|
||||
}
|
||||
|
||||
func equals(a, b map[string]string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for ak, av := range a {
|
||||
if b[ak] != av {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isDefaultDeny(namespace *coreapi.Namespace) bool {
|
||||
nnpJSON, found := namespace.ObjectMeta.Annotations["net.beta.kubernetes.io/network-policy"]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
var nnp NamespaceNetworkPolicy
|
||||
if err := json.Unmarshal([]byte(nnpJSON), &nnp); err != nil {
|
||||
common.Log.Warn("Ignoring network policy annotation: unmarshal failed:", err)
|
||||
// If we can't understand the annotation, behave as if it isn't present
|
||||
return false
|
||||
}
|
||||
|
||||
return nnp.Ingress != nil &&
|
||||
nnp.Ingress.Isolation != nil &&
|
||||
*(nnp.Ingress.Isolation) == DefaultDeny
|
||||
}
|
||||
|
||||
func namespaceComment(namespace *ns) string {
|
||||
return "namespace: " + namespace.name
|
||||
}
|
||||
|
||||
func podComment(pod *coreapi.Pod) string {
|
||||
return fmt.Sprintf("namespace: %s, pod: %s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/client-go/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
)
|
||||
|
||||
type ruleSpec struct {
|
||||
key string
|
||||
args []string
|
||||
}
|
||||
|
||||
func newRuleSpec(proto *string, srcHost *selectorSpec, dstHost *selectorSpec, dstPort *string) *ruleSpec {
|
||||
args := []string{}
|
||||
if proto != nil {
|
||||
args = append(args, "-p", *proto)
|
||||
}
|
||||
srcComment := "anywhere"
|
||||
if srcHost != nil {
|
||||
args = append(args, "-m", "set", "--match-set", string(srcHost.ipsetName), "src")
|
||||
if srcHost.nsName != "" {
|
||||
srcComment = fmt.Sprintf("pods: namespace: %s, selector: %s", srcHost.nsName, srcHost.key)
|
||||
} else {
|
||||
srcComment = fmt.Sprintf("namespaces: selector: %s", srcHost.key)
|
||||
}
|
||||
}
|
||||
dstComment := "anywhere"
|
||||
if dstHost != nil {
|
||||
args = append(args, "-m", "set", "--match-set", string(dstHost.ipsetName), "dst")
|
||||
dstComment = fmt.Sprintf("pods: namespace: %s, selector: %s", dstHost.nsName, dstHost.key)
|
||||
}
|
||||
if dstPort != nil {
|
||||
args = append(args, "--dport", *dstPort)
|
||||
}
|
||||
args = append(args, "-j", "ACCEPT")
|
||||
args = append(args, "-m", "comment", "--comment", fmt.Sprintf("%s -> %s", srcComment, dstComment))
|
||||
key := strings.Join(args, " ")
|
||||
|
||||
return &ruleSpec{key, args}
|
||||
}
|
||||
|
||||
type ruleSet struct {
|
||||
ipt iptables.Interface
|
||||
users map[string]map[types.UID]struct{}
|
||||
}
|
||||
|
||||
func newRuleSet(ipt iptables.Interface) *ruleSet {
|
||||
return &ruleSet{ipt, make(map[string]map[types.UID]struct{})}
|
||||
}
|
||||
|
||||
func (rs *ruleSet) deprovision(user types.UID, current, desired map[string]*ruleSpec) error {
|
||||
for key, spec := range current {
|
||||
if _, found := desired[key]; !found {
|
||||
delete(rs.users[key], user)
|
||||
if len(rs.users[key]) == 0 {
|
||||
common.Log.Infof("deleting rule: %v", spec.args)
|
||||
if err := rs.ipt.Delete(TableFilter, IngressChain, spec.args...); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(rs.users, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *ruleSet) provision(user types.UID, current, desired map[string]*ruleSpec) error {
|
||||
for key, spec := range desired {
|
||||
if _, found := current[key]; !found {
|
||||
if _, found := rs.users[key]; !found {
|
||||
common.Log.Infof("adding rule: %v", spec.args)
|
||||
if err := rs.ipt.Append(TableFilter, IngressChain, spec.args...); err != nil {
|
||||
return err
|
||||
}
|
||||
rs.users[key] = make(map[types.UID]struct{})
|
||||
}
|
||||
rs.users[key][user] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegressionHandleNil3095(t *testing.T) {
|
||||
// Test for handling nil values
|
||||
// https://github.com/weaveworks/weave/issues/3095
|
||||
|
||||
rs := newRuleSpec(nil, nil, nil, nil)
|
||||
|
||||
require.NotEqual(t, rs.key, "")
|
||||
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/pkg/api/unversioned"
|
||||
"k8s.io/client-go/pkg/labels"
|
||||
"k8s.io/client-go/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
)
|
||||
|
||||
type selectorSpec struct {
|
||||
key string // string representation (for hash keying/equality comparison)
|
||||
selector labels.Selector // k8s Selector object (for matching)
|
||||
|
||||
ipsetType ipset.Type // type of ipset to provision
|
||||
ipsetName ipset.Name // generated ipset name
|
||||
nsName string // Namespace name
|
||||
}
|
||||
|
||||
func newSelectorSpec(json *unversioned.LabelSelector, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
|
||||
selector, err := unversioned.LabelSelectorAsSelector(json)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := selector.String()
|
||||
return &selectorSpec{
|
||||
key: key,
|
||||
selector: selector,
|
||||
// We prefix the selector string with the namespace name when generating
|
||||
// the shortname because you can specify the same selector in multiple
|
||||
// namespaces - we need those to map to distinct ipsets
|
||||
ipsetName: ipset.Name(IpsetNamePrefix + shortName(nsName+":"+key)),
|
||||
ipsetType: ipsetType,
|
||||
nsName: nsName}, nil
|
||||
}
|
||||
|
||||
type selector struct {
|
||||
ips ipset.Interface
|
||||
spec *selectorSpec
|
||||
}
|
||||
|
||||
func (s *selector) matches(labelMap map[string]string) bool {
|
||||
return s.spec.selector.Matches(labels.Set(labelMap))
|
||||
}
|
||||
|
||||
func (s *selector) addEntry(entry string, comment string) error {
|
||||
return s.ips.AddEntry(s.spec.ipsetName, entry, comment)
|
||||
}
|
||||
|
||||
func (s *selector) delEntry(entry string) error {
|
||||
return s.ips.DelEntry(s.spec.ipsetName, entry)
|
||||
}
|
||||
|
||||
type selectorFn func(selector *selector) error
|
||||
|
||||
type selectorSet struct {
|
||||
ips ipset.Interface
|
||||
onNewSelector selectorFn
|
||||
users map[string]map[types.UID]struct{} // list of users per selector
|
||||
entries map[string]*selector
|
||||
}
|
||||
|
||||
func newSelectorSet(ips ipset.Interface, onNewSelector selectorFn) *selectorSet {
|
||||
return &selectorSet{
|
||||
ips: ips,
|
||||
onNewSelector: onNewSelector,
|
||||
users: make(map[string]map[types.UID]struct{}),
|
||||
entries: make(map[string]*selector)}
|
||||
}
|
||||
|
||||
func (ss *selectorSet) addToMatching(labelMap map[string]string, entry string, comment string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matches(labelMap) {
|
||||
if err := s.addEntry(entry, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) delFromMatching(labelMap map[string]string, entry string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matches(labelMap) {
|
||||
if err := s.delEntry(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||
for key, spec := range current {
|
||||
if _, found := desired[key]; !found {
|
||||
delete(ss.users[key], user)
|
||||
if len(ss.users[key]) == 0 {
|
||||
common.Log.Infof("destroying ipset: %#v", spec)
|
||||
if err := ss.ips.Destroy(spec.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(ss.entries, key)
|
||||
delete(ss.users, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) provision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||
for key, spec := range desired {
|
||||
if _, found := current[key]; !found {
|
||||
if _, found := ss.users[key]; !found {
|
||||
common.Log.Infof("creating ipset: %#v", spec)
|
||||
if err := ss.ips.Create(spec.ipsetName, spec.ipsetType); err != nil {
|
||||
return err
|
||||
}
|
||||
selector := &selector{ss.ips, spec}
|
||||
if err := ss.onNewSelector(selector); err != nil {
|
||||
return err
|
||||
}
|
||||
ss.users[key] = make(map[types.UID]struct{})
|
||||
ss.entries[key] = selector
|
||||
}
|
||||
ss.users[key][user] = struct{}{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const (
|
||||
// This array:
|
||||
// * Must include only printable UTF8 characters that are represented with a single byte (http://www.utf8-chartable.de/)
|
||||
// * Must be at least of length 85 (`len("weave-") + l(2^160)/l(85)` equals 31, the maximum ipset name length)
|
||||
// * Must not include commas as those are treated specially by `ipset add` when adding a named set to a list:set
|
||||
// * Should not include space for readability
|
||||
// * Should not include invalid chars for XML `<>&"'`
|
||||
// * Should not include single quote or backslash to be nice to shell users
|
||||
ShortNameSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(){}[]+=_$%^!*|/?.;:@#~"
|
||||
)
|
||||
|
||||
// sha1 hash an arbitrary string and represent it using the full range of
|
||||
// printable ascii characters
|
||||
func shortName(arbitrary string) string {
|
||||
symbols := []byte(ShortNameSymbols)
|
||||
sum := sha1.Sum([]byte(arbitrary))
|
||||
i := big.NewInt(0).SetBytes(sum[:])
|
||||
base := big.NewInt(int64(len(symbols)))
|
||||
zero := big.NewInt(0)
|
||||
result := make([]byte, 0)
|
||||
|
||||
for i.Cmp(zero) > 0 {
|
||||
remainder := new(big.Int).Mod(i, base)
|
||||
i.Sub(i, remainder)
|
||||
i.Div(i, base)
|
||||
result = append(result, symbols[remainder.Int64()])
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ulogd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
func waitForExit(cmd *exec.Cmd) {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
common.Log.Fatalf("ulogd terminated: %v", err)
|
||||
}
|
||||
common.Log.Fatal("ulogd terminated normally")
|
||||
}
|
||||
|
||||
func Start() error {
|
||||
cmd := exec.Command("/usr/sbin/ulogd", "-v")
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Pdeathsig: syscall.SIGKILL,
|
||||
}
|
||||
stdout, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(os.Stdout, stdout)
|
||||
go waitForExit(cmd)
|
||||
return nil
|
||||
}
|
||||
+20
-14
@@ -5,19 +5,24 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/appc/cni/pkg/skel"
|
||||
"github.com/appc/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
)
|
||||
|
||||
func (i *Ipam) CmdAdd(args *skel.CmdArgs) error {
|
||||
var conf types.NetConf
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
result, err := i.Allocate(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return result.Print()
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func (i *Ipam) Allocate(args *skel.CmdArgs) (*types.Result, error) {
|
||||
func (i *Ipam) Allocate(args *skel.CmdArgs) (types.Result, error) {
|
||||
// extract the things we care about
|
||||
conf, err := loadIPAMConf(args.StdinData)
|
||||
if err != nil {
|
||||
@@ -33,25 +38,26 @@ func (i *Ipam) Allocate(args *skel.CmdArgs) (*types.Result, error) {
|
||||
var ipnet *net.IPNet
|
||||
|
||||
if conf.Subnet == "" {
|
||||
ipnet, err = i.weave.AllocateIP(containerID)
|
||||
ipnet, err = i.weave.AllocateIP(containerID, false)
|
||||
} else {
|
||||
var subnet *net.IPNet
|
||||
subnet, err = types.ParseCIDR(conf.Subnet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("subnet given in config, but not parseable: %s", err)
|
||||
}
|
||||
ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet)
|
||||
ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &types.Result{
|
||||
IP4: &types.IPConfig{
|
||||
IP: *ipnet,
|
||||
result := ¤t.Result{
|
||||
IPs: []*current.IPConfig{{
|
||||
Version: "4",
|
||||
Address: *ipnet,
|
||||
Gateway: conf.Gateway,
|
||||
Routes: conf.Routes,
|
||||
},
|
||||
}},
|
||||
Routes: conf.Routes,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -65,9 +71,9 @@ func (i *Ipam) Release(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
type ipamConf struct {
|
||||
Subnet string `json:"subnet,omitempty"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
Routes []types.Route `json:"routes"`
|
||||
Subnet string `json:"subnet,omitempty"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
Routes []*types.Route `json:"routes"`
|
||||
}
|
||||
|
||||
type netConf struct {
|
||||
|
||||
@@ -19,6 +19,11 @@ func NewIpam(weave *api.Client) *Ipam {
|
||||
return &Ipam{weave: weave}
|
||||
}
|
||||
|
||||
func (i *Ipam) GetCapabilities() (RequiresMACAddress bool, RequiresRequestReplay bool, err error) {
|
||||
i.logReq("GetCapabilities")
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
func (i *Ipam) GetDefaultAddressSpaces() (string, string, error) {
|
||||
i.logReq("GetDefaultAddressSpaces")
|
||||
return "weavelocal", "weaveglobal", nil
|
||||
@@ -72,9 +77,8 @@ func splitPoolID(poolID string) (subnet, iprange *net.IPNet, err error) {
|
||||
func (i *Ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (ip *net.IPNet, _ map[string]string, err error) {
|
||||
i.logReq("RequestAddress", poolID, address, options)
|
||||
defer func() { i.logRes("RequestAddress", err, ip) }()
|
||||
// If we pass magic string "_" to weave IPAM it stores the address under its own string
|
||||
if poolID == "weavepool" { // old-style
|
||||
ip, err = i.weave.AllocateIP("_")
|
||||
ip, err = i.weave.AllocateIP(api.NoContainerID, false)
|
||||
return
|
||||
}
|
||||
subnet, iprange, err := splitPoolID(poolID)
|
||||
@@ -83,12 +87,12 @@ func (i *Ipam) RequestAddress(poolID string, address net.IP, options map[string]
|
||||
}
|
||||
if address != nil { // try to claim specific address requested
|
||||
ip = &net.IPNet{IP: address, Mask: subnet.Mask}
|
||||
if err = i.weave.ClaimIP("_", ip); err != nil {
|
||||
if err = i.weave.ClaimIP(api.NoContainerID, ip, false); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// We are lying slightly to IPAM here: the range is not a subnet
|
||||
if ip, err = i.weave.AllocateIPInSubnet("_", iprange); err != nil {
|
||||
if ip, err = i.weave.AllocateIPInSubnet(api.NoContainerID, iprange, false); err != nil {
|
||||
return
|
||||
}
|
||||
ip.Mask = subnet.Mask // fix up the subnet we lied about
|
||||
|
||||
+69
-60
@@ -5,14 +5,16 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/appc/cni/pkg/ipam"
|
||||
"github.com/appc/cni/pkg/skel"
|
||||
"github.com/appc/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/ipam"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
weaveapi "github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
ipamplugin "github.com/weaveworks/weave/plugin/ipam"
|
||||
)
|
||||
@@ -32,7 +34,8 @@ func NewCNIPlugin(weave *weaveapi.Client) *CNIPlugin {
|
||||
|
||||
func loadNetConf(bytes []byte) (*NetConf, error) {
|
||||
n := &NetConf{
|
||||
BrName: weavenet.WeaveBridgeName,
|
||||
BrName: weavenet.WeaveBridgeName,
|
||||
HairpinMode: true,
|
||||
}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
@@ -40,17 +43,26 @@ func loadNetConf(bytes []byte) (*NetConf, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *CNIPlugin) getIP(ipamType string, args *skel.CmdArgs) (result *types.Result, err error) {
|
||||
func (c *CNIPlugin) getIP(ipamType string, args *skel.CmdArgs) (newResult *current.Result, err error) {
|
||||
var result types.Result
|
||||
// Default IPAM is Weave's own
|
||||
if ipamType == "" {
|
||||
result, err = ipamplugin.NewIpam(c.weave).Allocate(args)
|
||||
} else {
|
||||
result, err = ipam.ExecAdd(ipamType, args.StdinData)
|
||||
}
|
||||
if err == nil && result.IP4 == nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("Received no usable result from IPAM plugin")
|
||||
}
|
||||
newResult, err = current.NewResultFromResult(result)
|
||||
// Check if ipam returned no results without error
|
||||
if err == nil && len(newResult.IPs) == 0 {
|
||||
return nil, fmt.Errorf("IPAM plugin failed to allocate IP address")
|
||||
}
|
||||
return result, err
|
||||
return newResult, err
|
||||
}
|
||||
|
||||
func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
|
||||
@@ -70,30 +82,40 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to allocate IP address: %s", err)
|
||||
}
|
||||
// Only expecting one address
|
||||
ip := result.IPs[0]
|
||||
|
||||
// If config says nothing about routes or gateway, default one will be via the bridge
|
||||
if result.IP4.Routes == nil && result.IP4.Gateway == nil {
|
||||
bridgeIP, err := findBridgeIP(conf.BrName, result.IP4.IP)
|
||||
if err == errBridgeNoIP {
|
||||
if len(result.Routes) == 0 && ip.Gateway == nil {
|
||||
bridgeIP, err := weavenet.FindBridgeIP(conf.BrName, &ip.Address)
|
||||
if err == weavenet.ErrBridgeNoIP {
|
||||
bridgeArgs := *args
|
||||
bridgeArgs.ContainerID = "weave:expose"
|
||||
// It would be better if libcni let us send just the desired parameters,
|
||||
// but there is a bug: https://github.com/containernetworking/cni/issues/410
|
||||
// so just blank out the one we want to change
|
||||
os.Setenv("CNI_CONTAINERID", bridgeArgs.ContainerID)
|
||||
bridgeIPResult, err := c.getIP(conf.IPAM.Type, &bridgeArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to allocate IP address for bridge: %s", err)
|
||||
}
|
||||
if err := assignBridgeIP(conf.BrName, bridgeIPResult.IP4.IP); err != nil {
|
||||
bridgeCIDR := bridgeIPResult.IPs[0].Address
|
||||
if err := assignBridgeIP(conf.BrName, bridgeCIDR); err != nil {
|
||||
return fmt.Errorf("unable to assign IP address to bridge: %s", err)
|
||||
}
|
||||
bridgeIP = bridgeIPResult.IP4.IP.IP
|
||||
if err := weavenet.ExposeNAT(bridgeCIDR); err != nil {
|
||||
return fmt.Errorf("unable to create NAT rules: %s", err)
|
||||
}
|
||||
bridgeIP = bridgeCIDR.IP
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
result.IP4.Gateway = bridgeIP
|
||||
result.IPs[0].Gateway = bridgeIP
|
||||
}
|
||||
|
||||
ns, err := netns.GetFromPath(args.Netns)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error accessing namespace %q: %s", args.Netns, err)
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
@@ -107,20 +129,20 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error {
|
||||
id = fmt.Sprintf("%x", data)
|
||||
}
|
||||
|
||||
if err := weavenet.AttachContainer(ns, id, args.IfName, conf.BrName, conf.MTU, false, []*net.IPNet{&result.IP4.IP}, false); err != nil {
|
||||
if err := weavenet.AttachContainer(args.Netns, id, args.IfName, conf.BrName, conf.MTU, false, []*net.IPNet{&ip.Address}, false, conf.HairpinMode); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := weavenet.WithNetNSLinkUnsafe(ns, args.IfName, func(link netlink.Link) error {
|
||||
return setupRoutes(link, args.IfName, result.IP4.IP, result.IP4.Gateway, result.IP4.Routes)
|
||||
return setupRoutes(link, args.IfName, ip.Address, ip.Gateway, result.Routes)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error setting up routes: %s", err)
|
||||
}
|
||||
|
||||
result.DNS = conf.DNS
|
||||
return result.Print()
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func setupRoutes(link netlink.Link, name string, ipnet net.IPNet, gw net.IP, routes []types.Route) error {
|
||||
func setupRoutes(link netlink.Link, name string, ipnet net.IPNet, gw net.IP, routes []*types.Route) error {
|
||||
var err error
|
||||
if routes == nil { // If config says nothing about routes, add a default one
|
||||
if !ipnet.Contains(gw) {
|
||||
@@ -130,7 +152,7 @@ func setupRoutes(link netlink.Link, name string, ipnet net.IPNet, gw net.IP, rou
|
||||
return err
|
||||
}
|
||||
}
|
||||
routes = []types.Route{{Dst: zeroNetwork}}
|
||||
routes = []*types.Route{{Dst: zeroNetwork}}
|
||||
}
|
||||
for _, r := range routes {
|
||||
if r.GW != nil {
|
||||
@@ -151,50 +173,32 @@ func assignBridgeIP(bridgeName string, ipnet net.IPNet) error {
|
||||
return err
|
||||
}
|
||||
if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: &ipnet}); err != nil {
|
||||
// Treat as non-error if this address is already there
|
||||
// - maybe another copy of this program just added it
|
||||
if err == syscall.Errno(syscall.EEXIST) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to add IP address to %q: %v", bridgeName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var errBridgeNoIP = fmt.Errorf("Bridge has no IP address")
|
||||
|
||||
func findBridgeIP(bridgeName string, subnet net.IPNet) (net.IP, error) {
|
||||
netdev, err := common.GetBridgeNetDev(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get netdev for %q bridge: %s", bridgeName, err)
|
||||
}
|
||||
if len(netdev.CIDRs) == 0 {
|
||||
return nil, errBridgeNoIP
|
||||
}
|
||||
for _, cidr := range netdev.CIDRs {
|
||||
if subnet.Contains(cidr.IP) {
|
||||
return cidr.IP, nil
|
||||
}
|
||||
}
|
||||
// None in the required subnet; just return the first one
|
||||
return netdev.CIDRs[0].IP, nil
|
||||
}
|
||||
|
||||
// As of CNI 0.5 spec:
|
||||
// "Plugins should generally complete a DEL action without error even if some resources are missing"
|
||||
// this method should therefore return nil in most, if not all, cases.
|
||||
func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error {
|
||||
conf, err := loadNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
logOnStderr(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
ns, err := netns.GetFromPath(args.Netns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
err = weavenet.WithNetNSUnsafe(ns, func() error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
// As of CNI 0.3 spec, runtimes can send blank if they just want the address deallocated
|
||||
if args.Netns != "" {
|
||||
if _, err = weavenet.WithNetNS(args.Netns, "del-iface", args.IfName); err != nil {
|
||||
// We log the error and carry on instead of returning nil, as there may still be resources to free in IPAM.
|
||||
logOnStderr(fmt.Errorf("error removing interface %q: %s", args.IfName, err))
|
||||
}
|
||||
return netlink.LinkDel(link)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing interface: %s", err)
|
||||
}
|
||||
|
||||
// Default IPAM is Weave's own
|
||||
@@ -203,16 +207,21 @@ func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error {
|
||||
} else {
|
||||
err = ipam.ExecDel(conf.IPAM.Type, args.StdinData)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to release IP address: %s", err)
|
||||
}
|
||||
logOnStderr(fmt.Errorf("unable to release IP address: %s", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Standard error is inherited from the caller (e.g. Kubelet, when running Weave Net's CNI plugin
|
||||
// under Kubernetes) hence provided errors should eventually appear in the caller's log files.
|
||||
func logOnStderr(err error) {
|
||||
fmt.Fprintln(os.Stderr, "weave-cni:", err)
|
||||
}
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
BrName string `json:"bridge"`
|
||||
IsGW bool `json:"isGateway"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
MTU int `json:"mtu"`
|
||||
BrName string `json:"bridge"`
|
||||
IsGW bool `json:"isGateway"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
MTU int `json:"mtu"`
|
||||
HairpinMode bool `json:"hairpinMode"`
|
||||
}
|
||||
+86
-25
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/docker/libnetwork/drivers/remote/api"
|
||||
"github.com/docker/libnetwork/netlabel"
|
||||
"github.com/docker/libnetwork/types"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
weaveapi "github.com/weaveworks/weave/api"
|
||||
@@ -27,25 +28,40 @@ type network struct {
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
name string
|
||||
scope string
|
||||
docker *docker.Client
|
||||
sync.RWMutex
|
||||
networks map[string]network
|
||||
name string
|
||||
scope string
|
||||
// Docker API is not available for plugin-v2
|
||||
docker *docker.Client
|
||||
dns bool
|
||||
isPluginV2 bool
|
||||
// Enable multicast regardless whether multicast opt is passed to Docker;
|
||||
// used only by plugin-v2
|
||||
forceMulticast bool
|
||||
networks map[string]network
|
||||
}
|
||||
|
||||
func New(client *docker.Client, weave *weaveapi.Client, name, scope string) (skel.Driver, error) {
|
||||
func New(client *docker.Client, weave *weaveapi.Client, name, scope string, dns, isPluginV2, forceMulticast bool) (skel.Driver, error) {
|
||||
driver := &driver{
|
||||
name: name,
|
||||
scope: scope,
|
||||
docker: client,
|
||||
networks: make(map[string]network),
|
||||
name: name,
|
||||
scope: scope,
|
||||
docker: client,
|
||||
dns: dns,
|
||||
isPluginV2: isPluginV2,
|
||||
// make sure that it's used only by plugin-v2
|
||||
forceMulticast: isPluginV2 && forceMulticast,
|
||||
networks: make(map[string]network),
|
||||
}
|
||||
|
||||
_, err := NewWatcher(client, weave, driver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Do not start watcher in the case of plugin v2, which prevents us from
|
||||
// configuring arp settings of containers.
|
||||
if client != nil {
|
||||
_, err := NewWatcher(client, weave, driver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
@@ -60,12 +76,27 @@ func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) {
|
||||
return caps, nil
|
||||
}
|
||||
|
||||
// In Swarm mode, CreateNetwork is called on each Swarm node when a new service
|
||||
// is created.
|
||||
func (driver *driver) CreateNetwork(create *api.CreateNetworkRequest) error {
|
||||
driver.logReq("CreateNetwork", create, create.NetworkID)
|
||||
_, err := driver.setupNetworkInfo(create.NetworkID, true, stringOptions(create))
|
||||
return err
|
||||
}
|
||||
|
||||
// NetworkAllocate is called on a Swarm node (master) which creates the network.
|
||||
// The returned options are passed to CreateNetwork.
|
||||
func (driver *driver) NetworkAllocate(alloc *api.AllocateNetworkRequest) (*api.AllocateNetworkResponse, error) {
|
||||
driver.logReq("NetworkAllocate", alloc, alloc.NetworkID)
|
||||
return &api.AllocateNetworkResponse{Options: alloc.Options}, nil
|
||||
}
|
||||
|
||||
// NetworkFree is called on a Swarm master node which created the network.
|
||||
func (driver *driver) NetworkFree(free *api.FreeNetworkRequest) (*api.FreeNetworkResponse, error) {
|
||||
driver.logReq("NetworkFree", free, free.NetworkID)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Deal with excessively-generic way the options get decoded from JSON
|
||||
func stringOptions(create *api.CreateNetworkRequest) map[string]string {
|
||||
if create.Options != nil {
|
||||
@@ -84,6 +115,7 @@ func stringOptions(create *api.CreateNetworkRequest) map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In Swarm mode, DeleteNetwork is called after a service has been removed.
|
||||
func (driver *driver) DeleteNetwork(delreq *api.DeleteNetworkRequest) error {
|
||||
driver.logReq("DeleteNetwork", delreq, delreq.NetworkID)
|
||||
driver.Lock()
|
||||
@@ -94,11 +126,21 @@ func (driver *driver) DeleteNetwork(delreq *api.DeleteNetworkRequest) error {
|
||||
|
||||
func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.CreateEndpointResponse, error) {
|
||||
driver.logReq("CreateEndpoint", create, create.EndpointID)
|
||||
common.Log.Debugf("interface %+v", create.Interface)
|
||||
|
||||
if create.Interface == nil {
|
||||
return nil, driver.error("CreateEndpoint", "Not supported: creating an interface from within CreateEndpoint")
|
||||
}
|
||||
resp := &api.CreateEndpointResponse{}
|
||||
|
||||
// create veths. note we assume endpoint IDs are unique in the first 9 chars
|
||||
name, peerName := vethPair(create.EndpointID)
|
||||
if _, err := weavenet.CreateAndAttachVeth(name, peerName, weavenet.WeaveBridgeName, 0, false, nil); err != nil {
|
||||
return nil, driver.error("JoinEndpoint", "%s", err)
|
||||
}
|
||||
|
||||
// Send back the MAC address
|
||||
link, _ := netlink.LinkByName(peerName)
|
||||
resp := &api.CreateEndpointResponse{Interface: &api.EndpointInterface{MacAddress: link.Attrs().HardwareAddr.String()}}
|
||||
|
||||
driver.logRes("CreateEndpoint", resp)
|
||||
return resp, nil
|
||||
@@ -106,6 +148,17 @@ func (driver *driver) CreateEndpoint(create *api.CreateEndpointRequest) (*api.Cr
|
||||
|
||||
func (driver *driver) DeleteEndpoint(deleteReq *api.DeleteEndpointRequest) error {
|
||||
driver.logReq("DeleteEndpoint", deleteReq, deleteReq.EndpointID)
|
||||
name, _ := vethPair(deleteReq.EndpointID)
|
||||
veth := &netlink.Veth{LinkAttrs: netlink.LinkAttrs{Name: name}}
|
||||
if err := netlink.LinkDel(veth); err != nil {
|
||||
// Try again using the name construction from earlier plugin version,
|
||||
// in case user has upgraded with endpoints still extant
|
||||
veth.Name = "vethwl" + deleteReq.EndpointID[:5]
|
||||
if err2 := netlink.LinkDel(veth); err2 != nil {
|
||||
// Note we report the first error
|
||||
driver.warn("LeaveEndpoint", "unable to delete veth %q: %s", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -122,11 +175,7 @@ func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error
|
||||
return nil, driver.error("JoinEndpoint", "unable to find network info: %s", err)
|
||||
}
|
||||
|
||||
name, peerName := vethPair(j.EndpointID)
|
||||
if _, err := weavenet.CreateAndAttachVeth(name, peerName, weavenet.WeaveBridgeName, 0, false, nil); err != nil {
|
||||
return nil, driver.error("JoinEndpoint", "%s", err)
|
||||
}
|
||||
|
||||
_, peerName := vethPair(j.EndpointID)
|
||||
response := &api.JoinResponse{
|
||||
InterfaceName: &api.InterfaceName{
|
||||
SrcName: peerName,
|
||||
@@ -145,12 +194,29 @@ func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error
|
||||
}
|
||||
|
||||
func (driver *driver) findNetworkInfo(id string) (network, error) {
|
||||
var network network
|
||||
|
||||
// plugin-v2 does not have access to docker.sock, so we cannot call Docker
|
||||
// API for the network info.
|
||||
if driver.isPluginV2 {
|
||||
// safe to set, as isOurs used only by the watcher which is disabled for plugin-v2
|
||||
network.isOurs = true
|
||||
network.hasMulticastRoute = driver.forceMulticast
|
||||
|
||||
return network, nil
|
||||
}
|
||||
|
||||
driver.Lock()
|
||||
network, found := driver.networks[id]
|
||||
driver.Unlock()
|
||||
if found {
|
||||
return network, nil
|
||||
}
|
||||
|
||||
if driver.docker == nil {
|
||||
return network, fmt.Errorf("Docker client disabled; unable to get network info")
|
||||
}
|
||||
|
||||
info, err := driver.docker.NetworkInfo(id)
|
||||
if err != nil {
|
||||
return network, err
|
||||
@@ -186,12 +252,6 @@ func (driver *driver) setupNetworkInfo(id string, isOurs bool, options map[strin
|
||||
|
||||
func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error {
|
||||
driver.logReq("LeaveEndpoint", leave, fmt.Sprintf("%s:%s", leave.NetworkID, leave.EndpointID))
|
||||
|
||||
name, _ := vethPair(leave.EndpointID)
|
||||
veth := &netlink.Veth{LinkAttrs: netlink.LinkAttrs{Name: name}}
|
||||
if err := netlink.LinkDel(veth); err != nil {
|
||||
driver.warn("LeaveEndpoint", "unable to delete veth: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -206,7 +266,8 @@ func (driver *driver) DiscoverDelete(disco *api.DiscoveryNotification) error {
|
||||
}
|
||||
|
||||
func vethPair(id string) (string, string) {
|
||||
return "vethwl" + id[:5], "vethwg" + id[:5]
|
||||
// IFNAMSIZ is buffer length; subtract 6 for "vethwl" and 1 for terminating nul
|
||||
return "vethwl" + id[:unix.IFNAMSIZ-7], "vethwg" + id[:unix.IFNAMSIZ-7]
|
||||
}
|
||||
|
||||
// logging
|
||||
|
||||
+12
-5
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
weaveapi "github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
@@ -42,11 +41,19 @@ func (w *watcher) ContainerStarted(id string) {
|
||||
continue
|
||||
}
|
||||
if network.isOurs {
|
||||
fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)
|
||||
if err := w.weave.RegisterWithDNS(id, fqdn, net.IPAddress); err != nil {
|
||||
w.driver.warn("ContainerStarted", "unable to register %s with weaveDNS: %s", id, err)
|
||||
if w.driver.dns {
|
||||
fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)
|
||||
if err := w.weave.RegisterWithDNS(id, fqdn, net.IPAddress); err != nil {
|
||||
w.driver.warn("ContainerStarted", "unable to register %s with weaveDNS: %s", id, err)
|
||||
}
|
||||
}
|
||||
if err := common.ConfigureARPforVeths(info.State.Pid, weavenet.VethName); err != nil {
|
||||
rootDir := "/"
|
||||
if w.driver.isPluginV2 {
|
||||
// We bind mount host's /proc to /host/proc for plugin-v2
|
||||
rootDir = "/host"
|
||||
}
|
||||
netNSPath := weavenet.NSPathByPidWithRoot(rootDir, info.State.Pid)
|
||||
if _, err := weavenet.WithNetNS(netNSPath, "configure-arp", weavenet.VethName, rootDir); err != nil {
|
||||
w.driver.warn("ContainerStarted", "unable to configure interfaces: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 351 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user