Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64e3cf04cc | ||
|
|
4f9185e3fe | ||
|
|
4dbccea5df | ||
|
|
a412268154 | ||
|
|
d79ac3c12c | ||
|
|
85c309eee0 | ||
|
|
f782fb569f | ||
|
|
c50968a0a4 | ||
|
|
99b482b363 | ||
|
|
ce5dd847e9 | ||
|
|
5c0383059e | ||
|
|
6f672f9bc1 | ||
|
|
32a82f2d0e | ||
|
|
633df0cdaf | ||
|
|
32a12578f3 | ||
|
|
11b655154c | ||
|
|
19ceb424c3 | ||
|
|
e21a204eed | ||
|
|
145a527626 | ||
|
|
b33bbdb3f8 | ||
|
|
704e6a8676 | ||
|
|
4c6a8f29c0 | ||
|
|
5744ed8994 | ||
|
|
c71d9b126f | ||
|
|
5b437d2199 | ||
|
|
0125e67b35 | ||
|
|
1472e35011 | ||
|
|
58ea422ea2 | ||
|
|
3097565664 | ||
|
|
6a5df80bda | ||
|
|
b13baccbd2 | ||
|
|
82f2f7b2b9 | ||
|
|
f48a4373d1 | ||
|
|
ab9d1777a1 | ||
|
|
6a34995796 | ||
|
|
df25f84011 | ||
|
|
cead9579be | ||
|
|
a23c0237c3 | ||
|
|
5c3efbdca0 | ||
|
|
176ee73efe | ||
|
|
54833195ed | ||
|
|
8960a3a898 | ||
|
|
6291a89665 | ||
|
|
a45e5ac152 | ||
|
|
244a679f90 | ||
|
|
1a9909e3e2 | ||
|
|
6655974ef3 | ||
|
|
cdf4fb605a | ||
|
|
6fce798164 | ||
|
|
7801dcd849 | ||
|
|
2eed101a93 | ||
|
|
3d25620d13 | ||
|
|
6e89b036e2 | ||
|
|
8b5992e43f | ||
|
|
42b64f826b | ||
|
|
95379b0c5a | ||
|
|
f5918a3c14 | ||
|
|
a1f5d90323 | ||
|
|
5289a380f2 | ||
|
|
914c88454d | ||
|
|
46363f4129 | ||
|
|
bd19b86259 | ||
|
|
db2e7e4ba1 | ||
|
|
760f419e8f | ||
|
|
58ae0cd113 | ||
|
|
4809c113b3 | ||
|
|
6d0db90f43 | ||
|
|
75fb8f6ebf | ||
|
|
26739463a4 | ||
|
|
130cf6eb2b | ||
|
|
5054805ac6 | ||
|
|
ca94cfffe7 | ||
|
|
396df6bcda | ||
|
|
58dafbfa9d | ||
|
|
8516ab7c69 | ||
|
|
2284bc2120 | ||
|
|
31a490bfe6 | ||
|
|
0b5f5fd74e | ||
|
|
9a7abe68fd | ||
|
|
2ea27ec08f | ||
|
|
322441d5c8 | ||
|
|
7c7c302920 | ||
|
|
69986e77dd | ||
|
|
8182022d52 | ||
|
|
b075e93319 | ||
|
|
fd6d15e712 | ||
|
|
f399c3a888 | ||
|
|
1e83e7a95c | ||
|
|
8a7821a15b | ||
|
|
dac219b64a | ||
|
|
790a1757e0 | ||
|
|
aafa0f6783 | ||
|
|
df5426dd8b | ||
|
|
3803d79835 | ||
|
|
ceff3bed28 | ||
|
|
eebdbe226b | ||
|
|
8ab639ddaa | ||
|
|
812579a139 | ||
|
|
08f31886d6 | ||
|
|
f080e5aa5c | ||
|
|
d61c719e45 | ||
|
|
4d49bf3ab9 | ||
|
|
f34f837106 | ||
|
|
99ab0d3abc | ||
|
|
29c64b1f40 | ||
|
|
db51b772b4 | ||
|
|
a93a3e98b9 | ||
|
|
be94dcc8a3 | ||
|
|
a34b31ad12 | ||
|
|
e1d07aa264 | ||
|
|
0d79a8fc43 | ||
|
|
5d1411830b | ||
|
|
c1c5450caf | ||
|
|
0b4e8b8754 | ||
|
|
f73dcba306 | ||
|
|
acde6cb0ee | ||
|
|
4ecaa0f508 | ||
|
|
86b7e4a6db | ||
|
|
59859c4aed | ||
|
|
0e85783257 | ||
|
|
d8e8cd6e51 | ||
|
|
bbc91c1273 | ||
|
|
67f974923b | ||
|
|
4f00dab4c4 | ||
|
|
74e29c8990 | ||
|
|
74883c5da8 | ||
|
|
375af0a7b9 | ||
|
|
8c9679b19c | ||
|
|
0a80fdcc1d | ||
|
|
5fbcb3f905 | ||
|
|
067f96023a | ||
|
|
9de7c9ed0d | ||
|
|
0de292c0d4 | ||
|
|
eaa5385069 | ||
|
|
fa2aa1f791 | ||
|
|
78df99e7e7 | ||
|
|
26f4a41900 | ||
|
|
686500b181 | ||
|
|
d5de0b41f9 | ||
|
|
2bb84ae28b | ||
|
|
e319f54d3f | ||
|
|
cdab6a71b7 | ||
|
|
45293fdcac | ||
|
|
7656752d87 | ||
|
|
d0ed225184 | ||
|
|
c2147f80b7 | ||
|
|
f1b9cf4fc0 | ||
|
|
c1d799a06c | ||
|
|
6a5044fc70 | ||
|
|
7fa568872c | ||
|
|
f3ad81d554 | ||
|
|
fc9a1c8664 | ||
|
|
00ecff09f7 | ||
|
|
278807a199 | ||
|
|
8bce900170 | ||
|
|
cf6d6d0cd3 | ||
|
|
94af02fbf4 | ||
|
|
755a949f6d | ||
|
|
42debde746 | ||
|
|
c2d5b35cd6 | ||
|
|
073836905a | ||
|
|
fd2a59417d | ||
|
|
6d7e784c25 | ||
|
|
b2a24d1cbe | ||
|
|
0a2701bbbc | ||
|
|
1a11b989e7 | ||
|
|
010792f714 | ||
|
|
536369bc12 | ||
|
|
f55655216f | ||
|
|
43ef3b8515 | ||
|
|
3180c67151 | ||
|
|
457e162d0c | ||
|
|
f9127e90db | ||
|
|
94db0beb25 | ||
|
|
77996158ef | ||
|
|
6cc7749cf2 | ||
|
|
c47e57cc29 | ||
|
|
de6f5a93e8 | ||
|
|
157910ec9a | ||
|
|
b85b90ef4e | ||
|
|
1d1ebc103d | ||
|
|
9c22066bdb | ||
|
|
f241e15ec1 | ||
|
|
881ece3553 | ||
|
|
281bf02871 | ||
|
|
778038fdd3 | ||
|
|
e7ee72836d | ||
|
|
31c0506b85 | ||
|
|
7d6f85424c | ||
|
|
ca89cfee3d | ||
|
|
5a37f652c2 | ||
|
|
cc7b781bb3 | ||
|
|
55051f0194 | ||
|
|
b7e8938ab4 | ||
|
|
f8327c3c6c | ||
|
|
e3851e6e55 | ||
|
|
6a136eebb2 | ||
|
|
9396c046cd | ||
|
|
dccd199369 | ||
|
|
a87be89095 | ||
|
|
5c9a13d343 | ||
|
|
bfe07fc71a | ||
|
|
fa95b67c92 | ||
|
|
728203912e | ||
|
|
27289e7b7b | ||
|
|
669a0db528 | ||
|
|
4da999ba86 | ||
|
|
3bffd871b8 | ||
|
|
2d450ac39d | ||
|
|
6fe69590c4 | ||
|
|
4df3d7f150 | ||
|
|
51128a6705 | ||
|
|
e7676979ee | ||
|
|
da36c78942 | ||
|
|
77358140d9 | ||
|
|
1701bc3e92 | ||
|
|
db6ee9954c | ||
|
|
792c4c8de4 | ||
|
|
820307dd4e | ||
|
|
88144ac7f8 | ||
|
|
da7ac78694 | ||
|
|
10dbd9ad4c | ||
|
|
9779e06f66 | ||
|
|
f861d22593 | ||
|
|
e3f0565517 | ||
|
|
9a91b3ce9c | ||
|
|
c88361473c | ||
|
|
a2e1a2d375 | ||
|
|
536ce992fc | ||
|
|
8d57b85a3a | ||
|
|
ce37b622eb | ||
|
|
ed4fb14f52 | ||
|
|
32cce0c636 | ||
|
|
34bb7ae84a | ||
|
|
5389e31190 | ||
|
|
f0915221d1 | ||
|
|
aa4efd63ef | ||
|
|
a12d63bb12 | ||
|
|
062a25a032 | ||
|
|
9e6d08c606 | ||
|
|
976641c3b6 | ||
|
|
2f80f12001 | ||
|
|
647b755b64 | ||
|
|
26dc83dbc2 | ||
|
|
3c107d675a | ||
|
|
2980586e76 | ||
|
|
dba116bae1 | ||
|
|
0b7d462adf | ||
|
|
6f8622a1a0 | ||
|
|
af049efb40 | ||
|
|
ad4e4ce6b8 | ||
|
|
4c9b0be117 | ||
|
|
a9dae46110 | ||
|
|
e34093c328 | ||
|
|
9131b46263 | ||
|
|
2a9c3fc64a | ||
|
|
c44c61dc83 | ||
|
|
11663dd29f | ||
|
|
90b7c3625d | ||
|
|
4e29dba5cd | ||
|
|
f1b5a00cb3 | ||
|
|
1a4b38a10c | ||
|
|
9ab2da8deb | ||
|
|
66473e6021 | ||
|
|
a9229b9197 | ||
|
|
7b9e59833e | ||
|
|
5baddb83cb | ||
|
|
4e56247ed9 | ||
|
|
ae2348875b | ||
|
|
fafaeef07d | ||
|
|
5b62b8c1e3 | ||
|
|
7c17c8aec3 | ||
|
|
64408b126e | ||
|
|
2a1f090a88 | ||
|
|
e2a2a8f18f | ||
|
|
a3d32068d2 | ||
|
|
68afbe31b0 | ||
|
|
ba3451e9ca | ||
|
|
25c9180fdf | ||
|
|
e03a4bb176 | ||
|
|
4d594458d2 | ||
|
|
854d83f294 | ||
|
|
358ae3a936 | ||
|
|
2dbf83889b | ||
|
|
9b2c5b8466 | ||
|
|
1f4b943454 | ||
|
|
2ca0fb93c9 | ||
|
|
47273d424f | ||
|
|
f4d6e028d4 | ||
|
|
f10e0e4add | ||
|
|
66e58937ca | ||
|
|
62c1cad676 | ||
|
|
ca523c716c | ||
|
|
e2deff09a6 | ||
|
|
19ddb65036 | ||
|
|
61c7c034e7 | ||
|
|
4c5920a50a | ||
|
|
c3c40b15f1 | ||
|
|
2fa65f0a22 | ||
|
|
af7ca99dc4 | ||
|
|
2d224d423d | ||
|
|
e7088e5daf | ||
|
|
d65409e97e | ||
|
|
70110e483c | ||
|
|
a0876becbc | ||
|
|
aa3f35e38c | ||
|
|
db77570779 | ||
|
|
6e5995964d | ||
|
|
da13238368 | ||
|
|
0865f4c207 | ||
|
|
a4a31e632b | ||
|
|
23ad547db4 | ||
|
|
4d887c8a55 | ||
|
|
73210b6a70 | ||
|
|
9df7358ded | ||
|
|
9507ea93dd | ||
|
|
52d3bcfe8b | ||
|
|
b3979c9505 | ||
|
|
273a0fba47 | ||
|
|
7e34ec61f6 | ||
|
|
acf13dc157 | ||
|
|
91395d6728 | ||
|
|
ef4a227ae9 | ||
|
|
25589562cd | ||
|
|
7063ca51aa | ||
|
|
97498d26f7 | ||
|
|
36292216fd | ||
|
|
86448a1025 | ||
|
|
b4f53edd91 | ||
|
|
3a22d2e6a1 | ||
|
|
9a4bbb55da | ||
|
|
fa73240812 | ||
|
|
a3dc2d0482 | ||
|
|
41834478d7 | ||
|
|
6832ae20b0 | ||
|
|
7ae06ef00d | ||
|
|
a1ac0313e5 | ||
|
|
d9c5d2f940 | ||
|
|
361ed1b4a6 | ||
|
|
dfcc9678f3 | ||
|
|
8b061fd555 | ||
|
|
01f7201a9e | ||
|
|
ded3d6b40b | ||
|
|
f7739ef04c | ||
|
|
4f82dc97fd | ||
|
|
16471d0a28 | ||
|
|
0abd134a0d | ||
|
|
18032015b9 | ||
|
|
39a1f2163f | ||
|
|
4c519fc761 | ||
|
|
bbacaa42b8 | ||
|
|
2f35ad1d42 | ||
|
|
6d17fe5df5 | ||
|
|
907de67a40 | ||
|
|
499d65e715 | ||
|
|
efb0ead9ad | ||
|
|
85b4a05c9a | ||
|
|
4853a1da05 | ||
|
|
db8016112e | ||
|
|
4eb6d147b5 | ||
|
|
1e39b2bdcf | ||
|
|
d6443809bd | ||
|
|
80e56d67db | ||
|
|
24a4d6501e | ||
|
|
5e43f0fd4c | ||
|
|
13f1239a3e | ||
|
|
9463ceb873 | ||
|
|
7aab5cc5b4 | ||
|
|
2b61bc91df | ||
|
|
57a4371e9f | ||
|
|
1d2e6b4c56 | ||
|
|
2a0bc91f86 | ||
|
|
210b926064 | ||
|
|
9a089a111f | ||
|
|
710eabeff3 | ||
|
|
603f34e944 | ||
|
|
a3dff7e1e9 | ||
|
|
25486f7b4d | ||
|
|
c22d824db8 | ||
|
|
a44a3a4158 | ||
|
|
982c9d7650 | ||
|
|
7042c1506a | ||
|
|
76943f0b17 | ||
|
|
c4f516655d | ||
|
|
aaba681db1 | ||
|
|
56a169b50f | ||
|
|
32b2ec4038 | ||
|
|
ce5052305d | ||
|
|
0e94e3e07b | ||
|
|
3d72670105 | ||
|
|
f8718dcc5d | ||
|
|
fa6777004f | ||
|
|
e3212d462d | ||
|
|
f5186e74c0 | ||
|
|
bf814739ba | ||
|
|
00a7490a1d | ||
|
|
da41604229 | ||
|
|
d5a4326b9e | ||
|
|
e4458372e8 | ||
|
|
a05c65c73b | ||
|
|
14d346db8b | ||
|
|
882f14d50c | ||
|
|
75646d4786 | ||
|
|
cbcd5da680 | ||
|
|
501f40d9c1 | ||
|
|
08463ea403 | ||
|
|
89ad7e1ba2 | ||
|
|
e6828e1aa5 | ||
|
|
c8718d85dc | ||
|
|
56863c3d0e | ||
|
|
e50b1f1fe7 | ||
|
|
5e10b901a3 | ||
|
|
a6a74001bd | ||
|
|
0b032386c1 | ||
|
|
8eb092c754 | ||
|
|
33921c2cfe | ||
|
|
6cc566fd3e | ||
|
|
491a4369d7 | ||
|
|
6666fd14ac | ||
|
|
e621aca77e | ||
|
|
3e7c20b4ee | ||
|
|
22446ffc19 | ||
|
|
f39791616d | ||
|
|
d39aaa86ee | ||
|
|
4fdb14293b | ||
|
|
4ede33468e | ||
|
|
ebbb5d379d | ||
|
|
379b8c4e6b | ||
|
|
41b0c2a03f | ||
|
|
646cf84b4a | ||
|
|
f593c2fbf2 | ||
|
|
bd24307c2b | ||
|
|
e0c7054f9b | ||
|
|
59550e0fcb | ||
|
|
af64c8e2af | ||
|
|
03ac7a86c6 | ||
|
|
80a232f44c | ||
|
|
d1041c17d3 | ||
|
|
cf7fff3da2 | ||
|
|
373d663b5e | ||
|
|
e86d255a47 | ||
|
|
5c44f74475 | ||
|
|
d649bdddc7 | ||
|
|
5c7763b075 | ||
|
|
c8bd47e948 | ||
|
|
098fc81825 | ||
|
|
cab4a7e1ee | ||
|
|
6596c8b1a7 | ||
|
|
5559f1df2e | ||
|
|
156597a965 | ||
|
|
d0641062c0 | ||
|
|
cf4fffa394 | ||
|
|
b30de63f18 | ||
|
|
ff2d7451e2 | ||
|
|
910b57731d | ||
|
|
b8aa1d473b | ||
|
|
ce26f88ee2 | ||
|
|
1524796408 | ||
|
|
7821412bf7 | ||
|
|
b72b7a9ed5 | ||
|
|
8b0089565f | ||
|
|
8c6c692926 | ||
|
|
7554bd3913 | ||
|
|
b81fce32f6 | ||
|
|
3ea1448c52 | ||
|
|
f31a3c1261 | ||
|
|
fcea6fcbf8 | ||
|
|
07ee2df822 | ||
|
|
5b79e274e8 | ||
|
|
e25d1b8365 | ||
|
|
3fd146557a | ||
|
|
f6ffd604dd | ||
|
|
18a4c14670 | ||
|
|
0f67dbd8bd | ||
|
|
ecfc5d091d | ||
|
|
84238a0262 | ||
|
|
edf3ec9f09 | ||
|
|
2fee0f0184 | ||
|
|
a5ddb63fca | ||
|
|
013380c7e7 | ||
|
|
13c0b62f96 | ||
|
|
7276fc1d4b | ||
|
|
67a6961b4b | ||
|
|
d82578d895 | ||
|
|
c786e7eb5a | ||
|
|
91099694c9 | ||
|
|
c9a20ae10c | ||
|
|
f05225da22 | ||
|
|
badfdee5e4 | ||
|
|
6403e7a11c | ||
|
|
312122e1be | ||
|
|
bb1da63bfc | ||
|
|
d1b2e74985 | ||
|
|
c0b2fe3e3b | ||
|
|
864461918f | ||
|
|
9127eb8acf | ||
|
|
8e629d9471 | ||
|
|
ab943d938a | ||
|
|
d6fedc1e6e | ||
|
|
3222478449 | ||
|
|
26cb65c357 | ||
|
|
1ef3bd3a8b | ||
|
|
51ecf05091 | ||
|
|
565454d3cb | ||
|
|
906ff210fe | ||
|
|
c4565f6ed3 | ||
|
|
8dd9020803 | ||
|
|
55fbffef75 | ||
|
|
039a2030db | ||
|
|
33d85aaee5 | ||
|
|
d4c1766532 | ||
|
|
39a6d1c87e | ||
|
|
ee2a66323e | ||
|
|
635a3e5b26 | ||
|
|
82da45a638 | ||
|
|
64ac7745fe | ||
|
|
5690902173 | ||
|
|
4162dcafd3 | ||
|
|
ea59c6c065 | ||
|
|
7c741d6f7e | ||
|
|
99dc70ea7a | ||
|
|
b78d4f05d0 | ||
|
|
a9e3320e7c | ||
|
|
08f3d5b8e8 | ||
|
|
0d5aad3e90 | ||
|
|
fcd79a78af | ||
|
|
657bbbffbc | ||
|
|
7780ac1078 | ||
|
|
76a12edf57 | ||
|
|
7387caa2eb | ||
|
|
576dd69b0b | ||
|
|
5e6b107d01 | ||
|
|
f444ae9224 | ||
|
|
8f5d1eec11 | ||
|
|
9a6afcd236 | ||
|
|
94a8338be5 | ||
|
|
12c17d39cc | ||
|
|
409961fff3 | ||
|
|
159d47a934 | ||
|
|
4b11874390 | ||
|
|
3c7cff1c9c | ||
|
|
e3574f243f | ||
|
|
bdda9d03ae | ||
|
|
ae1805de76 | ||
|
|
0ce123e63a | ||
|
|
215d96fdc3 | ||
|
|
eb9e450cf1 | ||
|
|
72039bbe5a | ||
|
|
d6bceb139e | ||
|
|
a311623bb9 | ||
|
|
95d72a57e8 | ||
|
|
aa8d9eab53 | ||
|
|
43b8840dd2 | ||
|
|
59e667801d | ||
|
|
ae8172ec97 | ||
|
|
d6ab78ff03 | ||
|
|
dde5782625 | ||
|
|
369bd0f4dc | ||
|
|
3b4a47a961 | ||
|
|
0a287217b6 | ||
|
|
2c4cebe9d9 | ||
|
|
cedf9bb82d | ||
|
|
2a9f8a3d75 | ||
|
|
acd5b91eb8 | ||
|
|
bda245bfdb | ||
|
|
d1eb2917e8 | ||
|
|
9f0cd93859 | ||
|
|
7cf0d59003 | ||
|
|
74f8c07674 | ||
|
|
6d89867a74 | ||
|
|
ebf725d869 | ||
|
|
257774a093 | ||
|
|
5fbda6b094 | ||
|
|
b483be8c4b | ||
|
|
a751237164 | ||
|
|
0c87dcd656 | ||
|
|
e276f3e026 | ||
|
|
84e5659090 | ||
|
|
bbeb7966b4 | ||
|
|
edb4911bf6 | ||
|
|
e3917121fa | ||
|
|
ede288ad38 | ||
|
|
58f14551dc | ||
|
|
97816d6c54 | ||
|
|
939a095bf4 | ||
|
|
ad0d53f7fe | ||
|
|
e748ddc20d | ||
|
|
e51ecd73b3 | ||
|
|
5479d0a54d | ||
|
|
6830b27317 | ||
|
|
4399be7ad2 | ||
|
|
42d2a860eb | ||
|
|
3ec0200067 | ||
|
|
d1e4a8ec8a | ||
|
|
54b3061c50 | ||
|
|
19fe808dff | ||
|
|
aea1a2b08c | ||
|
|
cdee7a5d6f | ||
|
|
21e07c0db9 | ||
|
|
06101d134b |
No files matched your search
+12
-2
@@ -29,20 +29,28 @@ coverage.html
|
||||
# Emacs backup files
|
||||
*~
|
||||
|
||||
# Vim backup files
|
||||
*.swp
|
||||
|
||||
# Project specific
|
||||
prog/weaver/weaver
|
||||
prog/weavedns/weavedns
|
||||
prog/weavehosts/weavehosts
|
||||
prog/weaveproxy/weaveproxy
|
||||
prog/sigproxy/sigproxy
|
||||
prog/weavewait/weavewait
|
||||
prog/netcheck/netcheck
|
||||
testing/cover/cover
|
||||
testing/runner/runner
|
||||
tools/bin
|
||||
tools/build
|
||||
releases
|
||||
.weaver.uptodate
|
||||
.weavedns.uptodate
|
||||
.weaveexec.uptodate
|
||||
weave.tar
|
||||
weavedns.tar
|
||||
weaveexec.tar
|
||||
prog/weaveexec/weave
|
||||
prog/weaveexec/weavehosts
|
||||
prog/weaveexec/weaveproxy
|
||||
prog/weaveexec/weavewait
|
||||
prog/weaveexec/sigproxy
|
||||
@@ -50,3 +58,5 @@ prog/weaveexec/netcheck
|
||||
prog/weaveexec/docker*.tgz
|
||||
Vagrantfile.local
|
||||
test/tls/*.pem
|
||||
test/coverage
|
||||
test/coverage.*
|
||||
+1
-1
@@ -18,6 +18,7 @@ install:
|
||||
- pip install --user --upgrade gsutil
|
||||
- bin/setup-ci-secrets $encrypted_5ba036e89377_key $encrypted_5ba036e89377_iv
|
||||
- make travis
|
||||
- go get ./...
|
||||
|
||||
script:
|
||||
- bin/lint .
|
||||
@@ -25,5 +26,4 @@ script:
|
||||
|
||||
after_success:
|
||||
- go get github.com/mattn/goveralls
|
||||
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
- test "${TRAVIS_PULL_REQUEST}" = "false" && site/build-and-publish.sh
|
||||
+25
-3
@@ -1,3 +1,25 @@
|
||||
## Release v1.1.0
|
||||
|
||||
**Highlights**:
|
||||
|
||||
- `weave launch` now launches all weave components, simplifying
|
||||
startup.
|
||||
- `weave status` has been completely revamped, with a much improved
|
||||
presentation of the information, and the option to select and output
|
||||
data in JSON.
|
||||
- weaveDNS has been rewritten and embedded in the router. The new
|
||||
implementation simplifies configuration, improves performance, and
|
||||
provides fault resilience for services.
|
||||
- the weave Docker API proxy now provides an even more seamless user
|
||||
experience, and enables easier integration of weave with other
|
||||
systems such as kubernetes.
|
||||
- many usability improvements
|
||||
- a few minor bug fixes, including a couple of security
|
||||
vulnerabilities
|
||||
|
||||
More details in the
|
||||
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.1.0).
|
||||
|
||||
## Release 1.0.3
|
||||
|
||||
This release contains a weaveDNS feature enhancement as well as minor fixes for
|
||||
@@ -45,14 +67,14 @@ This is a bug fix release, addressing the following issue:
|
||||
root@d11e9287f65b:/# ping pingme
|
||||
````
|
||||
- Containers can now be
|
||||
[load-balanced](http://docs.weave.works/weave/master/head/weavedns.html#load-balancing)
|
||||
[load-balanced](http://docs.weave.works/weave/latest_release/weavedns.html#load-balancing)
|
||||
easily.
|
||||
- IP address allocation is now available across [multiple
|
||||
subnets](http://docs.weave.works/weave/master/head/ipam.html#range),
|
||||
subnets](http://docs.weave.works/weave/latest_release/ipam.html#range),
|
||||
and hence can be employed when running multiple, isolated
|
||||
applications.
|
||||
- The proxy now supports [TLS
|
||||
connections](http://docs.weave.works/weave/master/head/proxy.html#tls),
|
||||
connections](http://docs.weave.works/weave/latest_release/proxy.html#tls),
|
||||
enabling its deployment when the communication between docker
|
||||
clients and the server must be secured.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PUBLISH=publish_weave publish_weavedns publish_weaveexec
|
||||
PUBLISH=publish_weave publish_weaveexec
|
||||
|
||||
.DEFAULT: all
|
||||
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
|
||||
@@ -10,110 +10,118 @@ DOCKERHUB_USER=weaveworks
|
||||
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
||||
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
WEAVEDNS_EXE=prog/weavedns/weavedns
|
||||
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
WEAVEHOSTS_EXE=prog/weavehosts/weavehosts
|
||||
NETCHECK_EXE=prog/netcheck/netcheck
|
||||
COVER_EXE=testing/cover/cover
|
||||
RUNNER_EXE=testing/runner/runner
|
||||
|
||||
EXES=$(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(NETCHECK_EXE)
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE) $(COVER_EXE) $(RUNNER_EXE)
|
||||
|
||||
WEAVER_UPTODATE=.weaver.uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE)
|
||||
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||
WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE)
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE)
|
||||
|
||||
WEAVER_EXPORT=weave.tar
|
||||
WEAVEDNS_EXPORT=weavedns.tar
|
||||
WEAVEEXEC_EXPORT=weaveexec.tar
|
||||
|
||||
EXPORTS=$(WEAVER_EXPORT) $(WEAVEDNS_EXPORT) $(WEAVEEXEC_EXPORT)
|
||||
WEAVE_EXPORT=weave.tar
|
||||
|
||||
WEAVEEXEC_DOCKER_VERSION=1.3.1
|
||||
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
rm $@; \
|
||||
echo "\nYour go standard library was built without the 'netgo' build tag."; \
|
||||
echo "To fix that, run"; \
|
||||
echo " sudo go clean -i net"; \
|
||||
echo " sudo go install -tags netgo std"; \
|
||||
false; \
|
||||
}
|
||||
BUILD_FLAGS=-ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo
|
||||
|
||||
all: $(EXPORTS)
|
||||
all: $(WEAVE_EXPORT) $(COVER_EXE) $(RUNNER_EXE)
|
||||
|
||||
travis: $(EXES)
|
||||
|
||||
update:
|
||||
go get -u -f -v -tags -netgo $(addprefix ./,$(dir $(EXES)))
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE): common/*.go common/*/*.go net/*.go
|
||||
ifeq ($(COVERAGE),true)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "weaveworks") | paste -s -d,))
|
||||
go get -t -tags netgo ./$(@D)
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
else
|
||||
go get -tags netgo ./$(@D)
|
||||
go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D)
|
||||
@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
rm $@; \
|
||||
echo "\nYour go standard library was built without the 'netgo' build tag."; \
|
||||
echo "To fix that, run"; \
|
||||
echo " sudo go clean -i net"; \
|
||||
echo " sudo go install -tags netgo std"; \
|
||||
false; \
|
||||
}
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
endif
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go prog/weaver/main.go
|
||||
$(WEAVEDNS_EXE): nameserver/*.go prog/weavedns/main.go
|
||||
$(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go
|
||||
$(NETCHECK_EXE): prog/netcheck/netcheck.go
|
||||
|
||||
# Sigproxy and weavewait need separate rules as they fail the netgo check in
|
||||
# the main build stanza due to not importing net package
|
||||
|
||||
$(SIGPROXY_EXE): prog/sigproxy/main.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/main.go net/*.go
|
||||
$(WEAVEHOSTS_EXE): prog/weavehosts/weavehosts.go
|
||||
$(COVER_EXE): testing/cover/cover.go
|
||||
$(RUNNER_EXE): testing/runner/runner.go
|
||||
|
||||
$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(WEAVEHOSTS_EXE) $(COVER_EXE) $(RUNNER_EXE):
|
||||
go get ./$(@D)
|
||||
go build -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/main.go
|
||||
go build -o $@ ./$(@D)
|
||||
|
||||
$(WEAVER_EXPORT): prog/weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
|
||||
$(SUDO) docker save $(WEAVER_IMAGE):latest > $@
|
||||
touch $@
|
||||
|
||||
$(WEAVEDNS_EXPORT): prog/weavedns/Dockerfile $(WEAVEDNS_EXE)
|
||||
$(SUDO) docker build -t $(WEAVEDNS_IMAGE) prog/weavedns
|
||||
$(SUDO) docker save $(WEAVEDNS_IMAGE):latest > $@
|
||||
|
||||
$(WEAVEEXEC_EXPORT): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(NETCHECK_EXE)
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE)
|
||||
cp weave prog/weaveexec/weave
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEHOSTS_EXE) prog/weaveexec/weavehosts
|
||||
cp $(NETCHECK_EXE) prog/weaveexec/netcheck
|
||||
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
|
||||
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
$(SUDO) docker save $(WEAVEEXEC_IMAGE):latest > $@
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
|
||||
$(SUDO) docker save $(addsuffix :latest,$(IMAGES)) > $@
|
||||
|
||||
$(DOCKER_DISTRIB):
|
||||
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
||||
|
||||
tests:
|
||||
echo "mode: count" > profile.cov
|
||||
fail=0 ; \
|
||||
for dir in $$(find . -type f -name '*_test.go' | xargs -n1 dirname | sort -u); do \
|
||||
output=$$(mktemp cover.XXXXXXXXXX) ; \
|
||||
if ! go test -cpu 4 -tags netgo -covermode=count -coverprofile=$$output $$dir ; then \
|
||||
fail=1 ; \
|
||||
fi ; \
|
||||
if [ -f $$output ]; then \
|
||||
tail -n +2 <$$output >>profile.cov; \
|
||||
rm $$output; \
|
||||
fi \
|
||||
done ; \
|
||||
exit $$fail
|
||||
go tool cover -html=profile.cov -o=coverage.html
|
||||
tests: $(COVER_EXE)
|
||||
@test/units.sh
|
||||
|
||||
$(PUBLISH): publish_%:
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
$(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
publish: $(PUBLISH)
|
||||
|
||||
clean:
|
||||
-$(SUDO) docker rmi $(IMAGES)
|
||||
rm -f $(EXES) $(EXPORTS)
|
||||
rm -f test/tls/*.pem
|
||||
go clean -r ./...
|
||||
rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage
|
||||
|
||||
build:
|
||||
$(SUDO) go clean -i net
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
|
||||
[](https://travis-ci.org/weaveworks/weave) [](https://circleci.com/gh/weaveworks/weave) [](https://coveralls.io/r/weaveworks/weave)
|
||||
|
||||
# About Weaveworks
|
||||
|
||||
[Weaveworks](http://weave.works) is the company that develops Weave -
|
||||
the most productive way for developers to connect, observe and control
|
||||
Docker containers. To learn about our products, including getting
|
||||
started tutorials, visit our [website](http://weave.works), read about
|
||||
the
|
||||
[1.0 release of Weave](http://blog.weave.works/2015/06/18/weave-1-0-is-out-and-it-is-good/),
|
||||
visit our [documentation](http://docs.weave.works) or continue to read
|
||||
about some of the more technical aspects of Weave in this readme.
|
||||
|
||||
# Weave
|
||||
|
||||
Weave creates a virtual network that connects Docker containers
|
||||
deployed across multiple hosts and enables their automatic discovery.
|
||||
|
||||
@@ -42,7 +55,7 @@ it locally to control a weave network on a remote host.
|
||||
|
||||
## Quick Start Screencast
|
||||
|
||||
<a href="http://youtu.be/k6r7yuSr0hE" alt="Click to watch the screencast" target="_blank">
|
||||
<a href="https://youtu.be/BF9YJSPQUcY" alt="Click to watch the screencast" target="_blank">
|
||||
<img src="/docs/hello-screencast.png" />
|
||||
</a>
|
||||
|
||||
@@ -54,8 +67,8 @@ two containers, one on each host.
|
||||
|
||||
On $HOST1 we run:
|
||||
|
||||
host1$ weave launch && weave launch-dns && weave launch-proxy
|
||||
host1$ eval $(weave proxy-env)
|
||||
host1$ weave launch
|
||||
host1$ eval $(weave env)
|
||||
host1$ docker run --name a1 -ti ubuntu
|
||||
|
||||
> NB: If the first command results in an error like
|
||||
@@ -68,21 +81,18 @@ On $HOST1 we run:
|
||||
> `sudo`, since some commands modify environment entries and hence
|
||||
> they all need to be executed from the same shell.
|
||||
|
||||
The first line runs the weave router, DNS and Docker API proxy, each
|
||||
in their own container. The second line sets the `DOCKER_HOST`
|
||||
environment variable to point to the proxy, so that containers
|
||||
launched via the docker command line are automatically attached to the
|
||||
weave network. Finally, we run our application container; this happens
|
||||
via the proxy so it is automatically allocated an IP address and
|
||||
registered in DNS.
|
||||
The first line runs weave. The second line configures our environment
|
||||
so that containers launched via the docker command line are
|
||||
automatically attached to the weave network. Finally, we run our
|
||||
application container.
|
||||
|
||||
That's it! If our application consists of more than one container on
|
||||
this host we simply launch them with `docker run` as appropriate.
|
||||
|
||||
Next we repeat similar steps on `$HOST2`...
|
||||
|
||||
host2$ weave launch $HOST1 && weave launch-dns && weave launch-proxy
|
||||
host2$ eval $(weave proxy-env)
|
||||
host2$ weave launch $HOST1
|
||||
host2$ eval $(weave env)
|
||||
host2$ docker run --name a2 -ti ubuntu
|
||||
|
||||
The only difference, apart from the name of the application container,
|
||||
@@ -100,11 +110,10 @@ available. Also, we can tell weave to connect to multiple peers by
|
||||
supplying multiple addresses, separated by spaces. And we can
|
||||
[add peers dynamically](http://docs.weave.works/weave/latest_release/features.html#dynamic-topologies).
|
||||
|
||||
The router, DNS and Docker API proxy need to be started once per
|
||||
host. The relevant container images are pulled down on demand, but if
|
||||
you wish you can preload them by running `weave setup` - this is
|
||||
particularly useful for automated deployments, and ensures that there
|
||||
are no delays during later operations.
|
||||
Weave must be started once per host. The relevant container images are
|
||||
pulled down on demand, but if you wish you can preload them by running
|
||||
`weave setup` - this is particularly useful for automated deployments,
|
||||
and ensures that there are no delays during later operations.
|
||||
|
||||
Now that we've got everything set up, let's see whether our containers
|
||||
can talk to each other...
|
||||
@@ -112,7 +121,7 @@ can talk to each other...
|
||||
In the container started on `$HOST1`...
|
||||
|
||||
root@a1:/# ping -c 1 -q a2
|
||||
PING a2.weave.local (10.160.0.2) 56(84) bytes of data.
|
||||
PING a2.weave.local (10.40.0.2) 56(84) bytes of data.
|
||||
--- a2.weave.local ping statistics ---
|
||||
1 packets transmitted, 1 received, 0% packet loss, time 0ms
|
||||
rtt min/avg/max/mdev = 0.341/0.341/0.341/0.000 ms
|
||||
@@ -120,7 +129,7 @@ In the container started on `$HOST1`...
|
||||
Similarly, in the container started on `$HOST2`...
|
||||
|
||||
root@a2:/# ping -c 1 -q a1
|
||||
PING a1.weave.local (10.128.0.2) 56(84) bytes of data.
|
||||
PING a1.weave.local (10.32.0.2) 56(84) bytes of data.
|
||||
--- a1.weave.local ping statistics ---
|
||||
1 packets transmitted, 1 received, 0% packet loss, time 0ms
|
||||
rtt min/avg/max/mdev = 0.366/0.366/0.366/0.000 ms
|
||||
|
||||
Vendored
+2
-2
@@ -18,8 +18,8 @@ pkgs = %w(
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.box = "ubuntu/ubuntu-14.10-amd64"
|
||||
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/utopic/current/utopic-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
config.vm.box = "ubuntu/ubuntu-15.04-amd64"
|
||||
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/vivid/current/vivid-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
|
||||
config.vm.network "private_network", ip: vm_ip
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
|
||||
@@ -56,6 +56,11 @@ function lint {
|
||||
return
|
||||
fi
|
||||
|
||||
# Don't lint files that don't exist
|
||||
if ! [ -a "${filename}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
case "$ext" in
|
||||
go) lint_go "${filename}"
|
||||
;;
|
||||
|
||||
+3
-3
@@ -23,14 +23,14 @@ random_mac() {
|
||||
# In the first byte of the MAC, the 'multicast' bit should be
|
||||
# clear and 'locally administered' bit should be set. All other
|
||||
# bits should be random.
|
||||
od -txC -An -N6 /dev/urandom | sed 's|^ ||;s| |:|;s|[0-3]:|2:|;s|[4-7]:|6:|;s|[89ab]:|a:|;s|[c-f]:|e:|;s| |:|g'
|
||||
od -txC -An -N6 /dev/urandom | ( read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2)) )
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
launch)
|
||||
shift 1
|
||||
for i in $(seq $START $FINISH); do
|
||||
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch -iface '' -name $(random_mac) -nickname weave$i -port $PORT "$@"
|
||||
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch-router --iface '' --name $(random_mac) --nickname weave$i --port $PORT --no-dns "$@"
|
||||
done
|
||||
if [ $START -ne 1 ] ; then
|
||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
|
||||
@@ -48,7 +48,7 @@ case "$1" in
|
||||
stop)
|
||||
for i in $(seq $START $FINISH); do
|
||||
echo Stopping weave$i
|
||||
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave stop
|
||||
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave stop-router
|
||||
done
|
||||
;;
|
||||
esac
|
||||
+152
-65
@@ -10,15 +10,49 @@ RELEASE_DESCRIPTION=${RELEASE_DESCRIPTION:-"Weaving Containers into Applications
|
||||
|
||||
PWD=`pwd`
|
||||
|
||||
setup() {
|
||||
## Get the new version number from the most recent tag
|
||||
if ! LATEST_TAG=$(git describe --abbrev=0 --match='v*' 2>/dev/null) ; then
|
||||
echo "Could not find an annotated 'v*' version tag." >&2
|
||||
exit 1
|
||||
infer_release_type() {
|
||||
if echo $1 | grep -qE '^v[0-9]+\.[0-9]+\.0+$' ; then
|
||||
echo MAINLINE
|
||||
elif echo $1 | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$' ; then
|
||||
echo BRANCH
|
||||
else
|
||||
echo PRERELEASE
|
||||
fi
|
||||
}
|
||||
|
||||
setup() {
|
||||
# Ensure we have exactly one annotated tag pointing at HEAD
|
||||
HEAD_TAGS=$(git tag --points-at HEAD)
|
||||
case $(echo $HEAD_TAGS | wc -w) in
|
||||
1)
|
||||
if [ $HEAD_TAGS != "latest_release" ] ; then
|
||||
LATEST_TAG=$HEAD_TAGS
|
||||
else
|
||||
echo "Cannot determine version - latest_release points at HEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
0)
|
||||
echo "Cannot determine version - no tags point at HEAD" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Cannot determine version - multiple tags point at HEAD:" >&2
|
||||
for TAG in $HEAD_TAGS ; do
|
||||
echo -e "\t$TAG" >&2
|
||||
done
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
RELEASE_TYPE=$(infer_release_type $LATEST_TAG)
|
||||
echo "== Inferred release type $RELEASE_TYPE from tag $LATEST_TAG"
|
||||
|
||||
LATEST_TAG_SHA=$(git rev-parse $LATEST_TAG)
|
||||
LATEST_TAG_COMMIT_SHA=$(git rev-list -1 $LATEST_TAG)
|
||||
LATEST_RELEASE_SHA=$(git rev-parse latest_release)
|
||||
VERSION=${LATEST_TAG#v}
|
||||
LATEST_RELEASE_COMMIT_SHA=$(git rev-list -1 latest_release)
|
||||
VERSION=${LATEST_TAG}
|
||||
# NB does not check that this tag is on master
|
||||
RELEASE_DIR=./releases/$LATEST_TAG
|
||||
}
|
||||
@@ -39,8 +73,8 @@ build() {
|
||||
cd $RELEASE_DIR
|
||||
|
||||
## Check that the top changelog entry is this version
|
||||
if ! latest_changelog=$(grep -o "## Release [0-9].*" -m1 ./CHANGELOG.md) || \
|
||||
! [ `echo "$latest_changelog" | grep -o '[0-9][^ ]*'` == "$VERSION" ]; then
|
||||
if ! latest_changelog=$(grep -oP '(?<=^## Release ).*' -m1 ./CHANGELOG.md) || \
|
||||
! [ `echo "$latest_changelog" = "$VERSION"` ]; then
|
||||
echo -e "\u2757 Latest changelog entry \"$latest_changelog\" does not match the release version $VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -61,7 +95,7 @@ build() {
|
||||
fi
|
||||
|
||||
## Run tests with the distributables, including version check
|
||||
v=$(./prog/weaver/weaver --version | grep -o '[0-9].*')
|
||||
v=$(./prog/weaver/weaver --version | grep -oP '(?<=^weave router ).*')
|
||||
if ! [ "$v" == "$VERSION" ]; then
|
||||
echo "Version of distributable "$v" does not match release version $VERSION" >&2
|
||||
exit 1
|
||||
@@ -71,24 +105,13 @@ build() {
|
||||
echo '** Release artefacts in' $RELEASE_DIR
|
||||
}
|
||||
|
||||
publish() {
|
||||
draft() {
|
||||
setup
|
||||
|
||||
cd $PWD/$RELEASE_DIR
|
||||
|
||||
echo "== Sanity checks"
|
||||
|
||||
if ! which github-release >/dev/null; then
|
||||
echo "Please install git-release:" >&2
|
||||
echo -e "\tgo get github.com/aktau/github-release" >&2
|
||||
echo "and create a git token per https://github.com/aktau/github-release" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ $(git rev-list -1 $LATEST_TAG) == $(git rev-list -1 latest_release) ]; then
|
||||
echo -e "\u2757 The tag latest_release does not point to the same commit as $LATEST_TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Check that the tag exists by looking at github
|
||||
if ! curl -sSf https://api.github.com/repos/$GITHUB_USER/weave/git/tags/$LATEST_TAG_SHA >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Tag $LATEST_TAG is not on GitHub, or is not the same as the local tag" >&2
|
||||
@@ -98,19 +121,9 @@ publish() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Check that the 'latest_release' tag exists by looking at github
|
||||
if ! curl -sSf https://api.github.com/repos/$GITHUB_USER/weave/git/tags/$LATEST_RELEASE_SHA >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Tag latest_release is not on GitHub, or is not the same as the local tag" >&2
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/tags" >&2
|
||||
echo "You may need to" >&2
|
||||
echo -e "\tgit tag -af latest_release"
|
||||
echo -e "\tgit push -f git@github.com:$GITHUB_USER/weave latest_release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "\u2713 Tag $LATEST_TAG exists in GitHub repo $GITHUB_USER/weave"
|
||||
|
||||
## Check that the version doesn't already exist by looking at github
|
||||
## Check that the version does not already exist by looking at github
|
||||
## releases
|
||||
if github-release info --user $GITHUB_USER --repo weave --tag $LATEST_TAG >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Release $LATEST_TAG already exists on GitHub" >&2
|
||||
@@ -120,13 +133,13 @@ publish() {
|
||||
|
||||
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
|
||||
|
||||
echo "== Tagging and publishing images on docker hub as user $DOCKERHUB_USER"
|
||||
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
RELEASE_ARGS="--draft"
|
||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||
RELEASE_ARGS="$RELEASE_ARGS --pre-release"
|
||||
fi
|
||||
|
||||
echo "== Creating GitHub release $RELEASE_NAME $VERSION"
|
||||
# This cannot be done as a draft because of a bug in
|
||||
# github-release: https://github.com/aktau/github-release/issues/7
|
||||
github-release release \
|
||||
echo "== Creating GitHub release $RELEASE_ARGS $RELEASE_NAME $VERSION"
|
||||
github-release release $RELEASE_ARGS \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
@@ -140,52 +153,126 @@ publish() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
echo "** Published release $RELEASE_NAME $VERSION"
|
||||
echo "** Draft $TYPE $RELEASE_NAME $VERSION created at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
}
|
||||
|
||||
if github-release info --user $GITHUB_USER --repo weave \
|
||||
--tag latest_release >/dev/null 2>&1; then
|
||||
github-release delete \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release
|
||||
publish() {
|
||||
setup
|
||||
cd $PWD/$RELEASE_DIR
|
||||
|
||||
UPDATE_LATEST=false
|
||||
if [ "$RELEASE_TYPE" = 'MAINLINE' ] ; then
|
||||
UPDATE_LATEST=true
|
||||
fi
|
||||
|
||||
github-release release \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "$RELEASE_NAME latest ($VERSION)" \
|
||||
--description "$RELEASE_DESCRIPTION"
|
||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make UPDATE_LATEST=$UPDATE_LATEST SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
echo "== Publishing pre-release on GitHub"
|
||||
|
||||
echo "** Published $RELEASE_NAME as latest_release at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/latest_release"
|
||||
github-release publish \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG
|
||||
|
||||
echo "** Pre-release $RELEASE_NAME $VERSION published at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
else
|
||||
echo "== Sanity checks"
|
||||
if ! [ "$LATEST_TAG_COMMIT_SHA" == "$LATEST_RELEASE_COMMIT_SHA" ] ; then
|
||||
echo -e "\u2757 The tag latest_release does not point to the same commit as $LATEST_TAG" >&2
|
||||
echo "You may need to" >&2
|
||||
echo -e "\tgit tag -af latest_release $LATEST_TAG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Check that the 'latest_release' tag exists by looking at github
|
||||
if ! curl -sSf https://api.github.com/repos/$GITHUB_USER/weave/git/tags/$LATEST_RELEASE_SHA >/dev/null 2>&1; then
|
||||
echo -e "\u2757 Tag latest_release is not on GitHub, or is not the same as the local tag" >&2
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/tags" >&2
|
||||
echo "You may need to" >&2
|
||||
echo -e "\tgit push -f git@github.com:$GITHUB_USER/weave latest_release" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make UPDATE_LATEST=$UPDATE_LATEST SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
|
||||
echo "== Publishing release on GitHub"
|
||||
|
||||
github-release publish \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG
|
||||
|
||||
if github-release info --user $GITHUB_USER --repo weave \
|
||||
--tag latest_release >/dev/null 2>&1; then
|
||||
github-release delete \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release
|
||||
fi
|
||||
|
||||
github-release release \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "$RELEASE_NAME latest ($VERSION)" \
|
||||
--description "$RELEASE_DESCRIPTION"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
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"
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage:"
|
||||
echo -e "\t./bin/release build"
|
||||
echo "-- Build artefacts for the latest version tag"
|
||||
echo -e "\t./bin/release draft"
|
||||
echo "-- Create draft release with artefacts in GitHub"
|
||||
echo -e "\t./bin/release publish"
|
||||
echo "-- Publish the artefacts for the latest tag to GitHub and DockerHub"
|
||||
echo "-- Publish the GitHub release and update DockerHub"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
# Ensure required tooling is installed
|
||||
if ! which github-release >/dev/null; then
|
||||
echo "Please install git-release:" >&2
|
||||
echo -e "\tgo get github.com/aktau/github-release" >&2
|
||||
echo "and create a git token per https://github.com/aktau/github-release" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ $# -eq 0 ] && usage
|
||||
COMMAND=$1
|
||||
shift
|
||||
|
||||
case "$COMMAND" in
|
||||
build)
|
||||
build
|
||||
build "$@"
|
||||
;;
|
||||
draft)
|
||||
draft "$@"
|
||||
;;
|
||||
publish)
|
||||
publish
|
||||
publish "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command \"$1\""
|
||||
echo "Unknown command \"$COMMAND\""
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
+4
-1
@@ -1,6 +1,9 @@
|
||||
FROM ubuntu
|
||||
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git ca-certificates docker.io mercurial libpcap-dev curl make pkg-config gcc bison flex
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
|
||||
RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.3.1 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
|
||||
|
||||
# When doing a build in a container, "apt-get update" happens twice,
|
||||
# which can be a very significant overhead for incremental builds.
|
||||
|
||||
+7
-2
@@ -6,7 +6,7 @@ export GOPATH
|
||||
|
||||
WEAVE_SRC=$GOPATH/src/github.com/weaveworks/weave
|
||||
|
||||
if [ $# -eq 0 ] ; then
|
||||
if [ $# -eq 0 -o "$1" = "tests" ] ; then
|
||||
# No arguments. Expect that the weave repo will be bind-mounted
|
||||
# into $GOPATH
|
||||
if ! [ -e $WEAVE_SRC ] ; then
|
||||
@@ -35,7 +35,12 @@ EOF
|
||||
echo "weave:x:$uid:$gid::$WEAVE_SRC:/bin/sh" >>/etc/passwd
|
||||
echo "weave:*:::::::" >>/etc/shadow
|
||||
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
|
||||
|
||||
if [ "$1" = "tests" ] ; then
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC tests"
|
||||
else
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
|
||||
fi
|
||||
else
|
||||
# There are arguments to pass to git-clone
|
||||
mkdir -p ${WEAVE_SRC%/*}
|
||||
|
||||
+15
-4
@@ -27,7 +27,7 @@ dependencies:
|
||||
fi
|
||||
post:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install bc
|
||||
- sudo apt-get install bc jq
|
||||
- pip install requests
|
||||
- curl https://sdk.cloud.google.com | bash
|
||||
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||
@@ -36,15 +36,26 @@ dependencies:
|
||||
|
||||
test:
|
||||
override:
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go weaveworks/weave-build:
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-build tests:
|
||||
parallel: true
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERAGE=true weaveworks/weave-build:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; ./gce.sh setup:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; . ./gce.sh hosts; ./setup.sh:
|
||||
- cd $SRCDIR/test; eval $(./gce.sh hosts); ./setup.sh:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; . ./gce.sh hosts; ./run_all.sh:
|
||||
- cd $SRCDIR/test; eval $(./gce.sh hosts); export COVERAGE=true; ./run_all.sh:
|
||||
parallel: true
|
||||
timeout: 300
|
||||
post:
|
||||
- cd $SRCDIR/test; ./gce.sh destroy:
|
||||
parallel: true
|
||||
|
||||
teardown:
|
||||
pre:
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh):
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (go get github.com/mattn/goveralls && goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci || true):
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
|
||||
parallel: true
|
||||
+4
-20
@@ -1,23 +1,13 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// Regexp that matches container IDs
|
||||
var containerIDregexp = regexp.MustCompile("^[a-f0-9]+$")
|
||||
|
||||
// IsContainerID returns True if the string provided is a valid container id
|
||||
func IsContainerID(idStr string) bool {
|
||||
return containerIDregexp.MatchString(idStr)
|
||||
}
|
||||
|
||||
// An observer for container events
|
||||
type ContainerObserver interface {
|
||||
ContainerDied(ident string) error
|
||||
ContainerDied(ident string)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
@@ -36,7 +26,7 @@ func NewClient(apiPath string) (*Client, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Info.Printf("[docker] Using Docker API on %s: %v", apiPath, env)
|
||||
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -44,7 +34,7 @@ func NewClient(apiPath string) (*Client, error) {
|
||||
func (c *Client) AddObserver(ob ContainerObserver) error {
|
||||
events := make(chan *docker.APIEvents)
|
||||
if err := c.AddEventListener(events); err != nil {
|
||||
Error.Printf("[docker] Unable to add listener to Docker API: %s", err)
|
||||
Log.Errorf("[docker] Unable to add listener to Docker API: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -62,12 +52,6 @@ func (c *Client) AddObserver(ob ContainerObserver) error {
|
||||
|
||||
// IsContainerNotRunning returns true if we have checked with Docker that the ID is not running
|
||||
func (c *Client) IsContainerNotRunning(idStr string) bool {
|
||||
if !IsContainerID(idStr) {
|
||||
Debug.Printf("[docker] '%s' does not seem to be a container id", idStr)
|
||||
return false
|
||||
}
|
||||
|
||||
// check with Docker whether the container is really running
|
||||
container, err := c.InspectContainer(idStr)
|
||||
if err == nil {
|
||||
return !container.State.Running
|
||||
@@ -75,6 +59,6 @@ func (c *Client) IsContainerNotRunning(idStr string) bool {
|
||||
if _, notThere := err.(*docker.NoSuchContainer); notThere {
|
||||
return true
|
||||
}
|
||||
Error.Printf("[docker] Could not check container status: %s", err)
|
||||
Log.Errorf("[docker] Could not check container status: %s", err)
|
||||
return false
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package common
|
||||
|
||||
func CheckFatal(e error) {
|
||||
if e != nil {
|
||||
Error.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckWarn(e error) {
|
||||
if e != nil {
|
||||
Warning.Println(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Assert test is true, panic otherwise
|
||||
func Assert(test bool) {
|
||||
if !test {
|
||||
panic("Assertion failure")
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
InitLogging(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr)
|
||||
}
|
||||
+39
-31
@@ -1,46 +1,54 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
standardLogFlags = log.Ldate | log.Ltime | log.Lmicroseconds
|
||||
)
|
||||
type textFormatter struct {
|
||||
}
|
||||
|
||||
// Largely taken from
|
||||
// http://www.goinggo.net/2013/11/using-log-package-in-go.html
|
||||
// Based off logrus.TextFormatter, which behaves completely
|
||||
// differently when you don't want colored output
|
||||
func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||
timeStamp := entry.Time.Format("2006/01/02 15:04:05.000000")
|
||||
if len(entry.Data) > 0 {
|
||||
fmt.Fprintf(b, "%s: %s %-44s ", levelText, timeStamp, entry.Message)
|
||||
for k, v := range entry.Data {
|
||||
fmt.Fprintf(b, " %s=%v", k, v)
|
||||
}
|
||||
} else {
|
||||
// No padding when there's no fields
|
||||
fmt.Fprintf(b, "%s: %s %s", levelText, timeStamp, entry.Message)
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
var (
|
||||
Debug *log.Logger
|
||||
Info *log.Logger
|
||||
Warning *log.Logger
|
||||
Error *log.Logger
|
||||
debugF bool
|
||||
standardTextFormatter = &textFormatter{}
|
||||
)
|
||||
|
||||
func InitLogging(debugHandle io.Writer,
|
||||
infoHandle io.Writer,
|
||||
warningHandle io.Writer,
|
||||
errorHandle io.Writer) {
|
||||
var (
|
||||
Log *logrus.Logger
|
||||
)
|
||||
|
||||
Debug = log.New(debugHandle, "DEBUG: ", standardLogFlags)
|
||||
Info = log.New(infoHandle, "INFO: ", standardLogFlags)
|
||||
Warning = log.New(warningHandle, "WARNING: ", standardLogFlags)
|
||||
Error = log.New(errorHandle, "ERROR: ", standardLogFlags)
|
||||
func init() {
|
||||
Log = logrus.New()
|
||||
Log.Formatter = standardTextFormatter
|
||||
}
|
||||
|
||||
func InitDefaultLogging(debug bool) {
|
||||
if debug == debugF {
|
||||
return
|
||||
func SetLogLevel(levelname string) {
|
||||
level, err := logrus.ParseLevel(levelname)
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
debugF = debug
|
||||
debugOut := ioutil.Discard
|
||||
if debug {
|
||||
debugOut = os.Stderr
|
||||
}
|
||||
InitLogging(debugOut, os.Stdout, os.Stdout, os.Stderr)
|
||||
Log.Level = level
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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))
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
)
|
||||
|
||||
// A callable is a function that is called and return an (optional) next scheduled time
|
||||
type callable func() time.Time
|
||||
|
||||
type schedCall struct {
|
||||
t time.Time
|
||||
c callable
|
||||
}
|
||||
|
||||
func (sc *schedCall) FromNow(now time.Time) time.Duration {
|
||||
if sc.t.After(now) {
|
||||
return time.Duration(sc.t.Sub(now).Nanoseconds())
|
||||
}
|
||||
return time.Duration(0)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// A min-heap of scheduled callables
|
||||
type schedCallsHeap []*schedCall
|
||||
|
||||
func (ch *schedCallsHeap) Len() int { return len(*ch) }
|
||||
func (ch *schedCallsHeap) Less(i, j int) bool { return (*ch)[i].t.Before((*ch)[j].t) }
|
||||
func (ch *schedCallsHeap) Swap(i, j int) {
|
||||
(*ch)[i], (*ch)[j] = (*ch)[j], (*ch)[i]
|
||||
}
|
||||
|
||||
func (ch *schedCallsHeap) Push(x interface{}) {
|
||||
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||
// not just its contents.
|
||||
entry := x.(*schedCall)
|
||||
*ch = append(*ch, entry)
|
||||
}
|
||||
|
||||
func (ch *schedCallsHeap) Pop() interface{} {
|
||||
old := *ch
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
*ch = old[0 : n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// SchedQueue is queue of scheduled callables
|
||||
type SchedQueue struct {
|
||||
clock clock.Clock
|
||||
callablesH schedCallsHeap
|
||||
schedChan chan *schedCall
|
||||
closeChan chan bool
|
||||
counter uint64 // number of calls invoked so far (used for stats). Note: it will wrap.
|
||||
}
|
||||
|
||||
// NewSchedQueue creates a new scheduled queue
|
||||
func NewSchedQueue(clock clock.Clock) *SchedQueue {
|
||||
cq := SchedQueue{
|
||||
clock: clock,
|
||||
schedChan: make(chan *schedCall),
|
||||
closeChan: make(chan bool),
|
||||
}
|
||||
heap.Init(&cq.callablesH)
|
||||
return &cq
|
||||
}
|
||||
|
||||
// Start starts the scheduled queue
|
||||
func (cq *SchedQueue) Start() {
|
||||
go func() {
|
||||
defer func() { Debug.Printf("[sched-q] Quitting (%d calls pending)", len(cq.callablesH)) }()
|
||||
|
||||
var now time.Time
|
||||
timer := cq.clock.Timer(time.Duration(math.MaxInt64))
|
||||
|
||||
for {
|
||||
// Wait until an insertion, until the next callable or until we are Stop()ed
|
||||
select {
|
||||
case ce := <-cq.schedChan:
|
||||
heap.Push(&cq.callablesH, ce)
|
||||
now = cq.clock.Now()
|
||||
case <-cq.closeChan:
|
||||
return
|
||||
case now = <-timer.C:
|
||||
}
|
||||
|
||||
timer.Stop()
|
||||
durationUntilNext := cq.durationUntilNext(now)
|
||||
for durationUntilNext == 0 {
|
||||
sched := heap.Pop(&cq.callablesH).(*schedCall)
|
||||
|
||||
atomic.AddUint64(&cq.counter, 1)
|
||||
schedNextTime := sched.c()
|
||||
|
||||
if !schedNextTime.IsZero() {
|
||||
sched.t = schedNextTime
|
||||
heap.Push(&cq.callablesH, sched) // TODO: use a Fix() instead of Pop()&Push()
|
||||
}
|
||||
|
||||
now = cq.clock.Now()
|
||||
durationUntilNext = cq.durationUntilNext(now)
|
||||
}
|
||||
|
||||
timer = cq.clock.Timer(durationUntilNext)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops the scheduled queue
|
||||
func (cq *SchedQueue) Stop() {
|
||||
Debug.Printf("[sched-q] Stopping...")
|
||||
cq.closeChan <- true
|
||||
}
|
||||
|
||||
// Add schedules a call.
|
||||
// The callable should not modify the scheduled queue in any way.
|
||||
func (cq *SchedQueue) Add(c callable, t time.Time) {
|
||||
Debug.Printf("[sched-q] Adding call at %s", t)
|
||||
ce := schedCall{c: c, t: t}
|
||||
cq.schedChan <- &ce
|
||||
}
|
||||
|
||||
// Counter returns the number of calls invoked in the queued
|
||||
// Note: the result will wrap over time.
|
||||
func (cq *SchedQueue) Count() uint64 {
|
||||
return atomic.LoadUint64(&cq.counter)
|
||||
}
|
||||
|
||||
func (cq *SchedQueue) durationUntilNext(now time.Time) time.Duration {
|
||||
if len(cq.callablesH) > 0 {
|
||||
firstSched := cq.callablesH[0]
|
||||
return firstSched.FromNow(now)
|
||||
}
|
||||
return time.Duration(math.MaxInt64)
|
||||
}
|
||||
|
||||
// ensure that the background goroutine has executed any pending callbacks
|
||||
func (cq *SchedQueue) Flush() {
|
||||
ch := make(chan struct{})
|
||||
flushFunc := func() time.Time {
|
||||
atomic.AddUint64(&cq.counter, ^uint64(0)) // decrement so nobody sees this
|
||||
close(ch)
|
||||
return time.Time{}
|
||||
}
|
||||
cq.schedChan <- &schedCall{c: flushFunc, t: time.Time{}}
|
||||
<-ch
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// Ensure we can add new calls while forwarding the clock
|
||||
func TestSchedCallsBasic(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestSchedCallsBasic starting")
|
||||
|
||||
const testSecs = 1000
|
||||
clk := clock.NewMock()
|
||||
schedQueue := NewSchedQueue(clk)
|
||||
schedQueue.Start()
|
||||
defer schedQueue.Stop()
|
||||
|
||||
c := func() time.Time {
|
||||
return clk.Now().Add(time.Second)
|
||||
}
|
||||
|
||||
schedQueue.Add(c, clk.Now().Add(time.Second))
|
||||
schedQueue.Flush()
|
||||
for i := 0; i < testSecs; i++ {
|
||||
clk.Add(time.Second)
|
||||
schedQueue.Flush()
|
||||
}
|
||||
|
||||
t.Logf("Now: %s - calls: %d", clk.Now(), schedQueue.Count())
|
||||
wt.AssertEqualInt(t, (int)(schedQueue.Count()), testSecs, "Number of calls")
|
||||
}
|
||||
|
||||
// Ensure we can create a 100 seconds gap in the middle of the time travel
|
||||
func TestSchedCallsGap(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestSchedCallsGap starting")
|
||||
|
||||
const testSecs = 1000
|
||||
clk := clock.NewMock()
|
||||
schedQueue := NewSchedQueue(clk)
|
||||
schedQueue.Start()
|
||||
defer schedQueue.Stop()
|
||||
|
||||
c2 := func() time.Time {
|
||||
if schedQueue.Count() == testSecs/2 {
|
||||
return clk.Now().Add(time.Duration(100) * time.Second)
|
||||
}
|
||||
return clk.Now().Add(time.Second)
|
||||
}
|
||||
|
||||
schedQueue.Add(c2, clk.Now().Add(time.Second))
|
||||
schedQueue.Flush()
|
||||
for i := 0; i < testSecs; i++ {
|
||||
clk.Add(time.Second)
|
||||
schedQueue.Flush()
|
||||
}
|
||||
|
||||
t.Logf("Now: %s - calls: %d", clk.Now(), schedQueue.Count())
|
||||
wt.AssertEqualInt(t, (int)(schedQueue.Count()), testSecs-100+1, "Number of calls")
|
||||
}
|
||||
|
||||
func TestSchedCallsStop(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestSchedCallsStop starting")
|
||||
|
||||
const testSecs = 1000
|
||||
clk := clock.NewMock()
|
||||
schedQueue := NewSchedQueue(clk)
|
||||
schedQueue.Start()
|
||||
defer schedQueue.Stop()
|
||||
|
||||
c2 := func() time.Time {
|
||||
if schedQueue.Count() == testSecs/2 {
|
||||
return time.Time{}
|
||||
}
|
||||
return clk.Now().Add(time.Second)
|
||||
}
|
||||
|
||||
schedQueue.Add(c2, clk.Now().Add(time.Second))
|
||||
schedQueue.Flush()
|
||||
for i := 0; i < testSecs; i++ {
|
||||
clk.Add(time.Second)
|
||||
schedQueue.Flush()
|
||||
}
|
||||
|
||||
t.Logf("Now: %s - calls: %d", clk.Now(), schedQueue.Count())
|
||||
wt.AssertEqualInt(t, (int)(schedQueue.Count()), testSecs/2, "Number of calls")
|
||||
}
|
||||
+4
-9
@@ -10,28 +10,23 @@ import (
|
||||
// A subsystem/server/... that can be stopped or queried about the status with a signal
|
||||
type SignalReceiver interface {
|
||||
Stop() error
|
||||
Status() string
|
||||
}
|
||||
|
||||
func SignalHandlerLoop(ss ...SignalReceiver) {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGTERM)
|
||||
buf := make([]byte, 1<<20)
|
||||
for {
|
||||
switch <-sigs {
|
||||
case syscall.SIGINT:
|
||||
Info.Printf("=== received SIGINT ===\n*** exiting\n")
|
||||
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)
|
||||
Info.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen])
|
||||
case syscall.SIGUSR1:
|
||||
for _, subsystem := range ss {
|
||||
Info.Printf("=== received SIGUSR1 ===\n*** status...\n%s\n*** end\n", subsystem.Status())
|
||||
}
|
||||
Log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen])
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-4
@@ -1,8 +1,20 @@
|
||||
package common
|
||||
|
||||
func OnOff(b bool) string {
|
||||
if b {
|
||||
return "on"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Assert test is true, panic otherwise
|
||||
func Assert(test bool) {
|
||||
if !test {
|
||||
panic("Assertion failure")
|
||||
}
|
||||
return "off"
|
||||
}
|
||||
|
||||
func ErrorMessages(errors []error) string {
|
||||
var result []string
|
||||
for _, err := range errors {
|
||||
result = append(result, err.Error())
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
+2
-2
@@ -125,8 +125,8 @@ VM2> nc 192.168.122.2 6783
|
||||
If all is well, you should now be able to launch weave on VM1
|
||||
then on VM2:
|
||||
|
||||
VM1> weaver -iface veth6 -name vm1
|
||||
VM2> weaver -iface veth6 -name vm2 192.168.122.2
|
||||
VM1> weaver --iface veth6 --name vm1
|
||||
VM2> weaver --iface veth6 --name vm2 192.168.122.2
|
||||
|
||||
Once the connection is up and running, you should be able to connect
|
||||
from VM1/ns2 to VM2/ns2 and vice versa:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,145 @@
|
||||
# Release Process
|
||||
## Prerequisites
|
||||
|
||||
* 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
|
||||
* Update all dependencies with `make update`
|
||||
|
||||
## Release Types
|
||||
|
||||
The release script behaves differently depending on the kind of
|
||||
release you are doing. There are three types:
|
||||
|
||||
* **Mainline** - a release (typically from master) with version tag
|
||||
`vX.Y.Z` where Z is zero (e.g. `v2.1.0`)
|
||||
* **Branch** - a bugfix release (typically from a branch) with version tag
|
||||
`vX.Y.Z` where Z is non-zero (e.g `v2.1.1`)
|
||||
* **Prerelease** - a release from an arbitrary branch with an arbitrary
|
||||
version tag (e.g. `feature-preview-20150904`)
|
||||
|
||||
N.B. the release script _infers the release type from the format of the
|
||||
version tag_. Ensure your tag is in the correct format and the desired
|
||||
behaviours for that type of release will be obtained from the script.
|
||||
|
||||
## Build Phase
|
||||
### Update CHANGELOG.md
|
||||
|
||||
* Checkout the branch from which you wish to release
|
||||
* Choose a version tag (see above) henceforth referred to as `$TAG`.
|
||||
* Add a changelog entry for the new tag at the top of `CHANGELOG.md`.
|
||||
The first line must be a markdown header of the form `## Release
|
||||
$TAG`
|
||||
|
||||
Commit the changelog update:
|
||||
|
||||
git commit -m "Add release $TAG" CHANGELOG.md
|
||||
|
||||
### Create Version Tag
|
||||
|
||||
Next you must tag the changelog commit with `$TAG`
|
||||
|
||||
git tag -a -m "Release $TAG" $TAG
|
||||
|
||||
### Execute Build
|
||||
|
||||
You are now ready to perform the build. If you have skipped the
|
||||
previous steps (e.g. because you're doing a rebuild), you must ensure
|
||||
that `HEAD` points to the tagged commit. You may then execute
|
||||
|
||||
bin/release build
|
||||
|
||||
This has the following effects:
|
||||
|
||||
* `git tag --points-at HEAD` is used to determine `$TAG` (hence the
|
||||
`HEAD` requirement)
|
||||
* Your *local* repository is cloned into `releases/$TAG`
|
||||
* `CHANGELOG.md` is checked to ensure it has an entry for `$TAG`
|
||||
* Distributables injected with `$TAG` are built
|
||||
* Tests are executed
|
||||
|
||||
## Draft Phase
|
||||
### Push Version Tag Upstream
|
||||
|
||||
First you must push your version tag upstream, so that an associated
|
||||
GitHub release may be created:
|
||||
|
||||
git push git@github.com:weaveworks/weave $TAG
|
||||
|
||||
N.B. if you're testing the release process, push to your fork
|
||||
instead!
|
||||
|
||||
### Create Draft Release
|
||||
|
||||
You're now ready to draft your release notes:
|
||||
|
||||
bin/release draft
|
||||
|
||||
This has the following effects:
|
||||
|
||||
* A [release](https://help.github.com/articles/about-releases) is
|
||||
created in GitHub for `$TAG`. This release is in the draft state, so
|
||||
it is only visible to contributors; for **Prerelease** builds the
|
||||
pre-release attribute will also be set
|
||||
* The `weave` script is uploaded as an attachment to the release
|
||||
|
||||
Navigate to https://github.com/weaveworks/weave/releases, 'Edit' the
|
||||
draft and input the release notes. When you are done make sure you
|
||||
'Save draft' (and not 'Publish release'!).
|
||||
|
||||
Once the release notes have passed review, proceed to the publish
|
||||
phase.
|
||||
|
||||
## Publish Phase
|
||||
### Move/Force Push `latest_release` Tag
|
||||
|
||||
This step must only be performed for **Mainline** and **Branch**
|
||||
releases:
|
||||
|
||||
git tag -af -m "Release $TAG" latest_release $TAG
|
||||
git push -f git@github.com:weaveworks/weave latest_release
|
||||
|
||||
The `latest_release` tag *must* point at `$TAG`, *not* at `HEAD` -
|
||||
the build script will complain otherwise.
|
||||
|
||||
N.B. if you're testing the release process, push to your fork
|
||||
instead!
|
||||
|
||||
### Publish Release & Distributable Artefacts
|
||||
|
||||
You can now publish the release and upload the remaining
|
||||
distributables to DockerHub:
|
||||
|
||||
bin/release publish
|
||||
|
||||
The effects of this step depend on the inferred release type. The
|
||||
following occurs for all types:
|
||||
|
||||
* Docker images are tagged `$TAG` and pushed to DockerHub
|
||||
* GitHub release moves from draft to published state
|
||||
|
||||
Additionally, for **Mainline** and **Branch** types:
|
||||
|
||||
* Release named `latest_release` is updated on GitHub
|
||||
|
||||
Finally, for **Mainline** releases only:
|
||||
|
||||
* Images tagged `latest` are updated on DockerHub
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
There's a few things that can go wrong.
|
||||
|
||||
* If the build is wonky, e.g., the tests don't pass, you can delete
|
||||
the directory in `./releases/`, fix whatever it is, move the
|
||||
version tag (which should still be only local) and have another go.
|
||||
* If the DockerHub pushes fail (which sadly seems to happen a lot),
|
||||
you can just run `./bin/release publish` again.
|
||||
* If you need to overwrite a release you can do so by manually
|
||||
deleting the GitHub version release and re-running the process.
|
||||
Please note that the DockerHub `latest` images, GitHub
|
||||
`latest_release` and download links may be in an inconsistent state
|
||||
until the overwrite is completed.
|
||||
+3
-5
@@ -3,7 +3,7 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type allocateResult struct {
|
||||
@@ -35,6 +35,8 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
alloc.establishRing()
|
||||
|
||||
if ok, addr := alloc.space.Allocate(g.r); ok {
|
||||
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
|
||||
alloc.addOwned(g.ident, addr)
|
||||
@@ -60,10 +62,6 @@ func (g *allocate) Cancel() {
|
||||
g.resultChan <- allocateResult{0, fmt.Errorf("Allocate request for %s cancelled", g.ident)}
|
||||
}
|
||||
|
||||
func (g *allocate) String() string {
|
||||
return fmt.Sprintf("Allocate for %s", g.ident)
|
||||
}
|
||||
|
||||
func (g *allocate) ForContainer(ident string) bool {
|
||||
return g.ident == ident
|
||||
}
|
||||
+76
-70
@@ -8,10 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/ipam/ring"
|
||||
"github.com/weaveworks/weave/ipam/space"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
const (
|
||||
msgSpaceRequest = iota
|
||||
msgRingUpdate
|
||||
msgSpaceRequestDenied
|
||||
|
||||
paxosInterval = time.Second * 5
|
||||
MinSubnetSize = 4 // first and last addresses are excluded, so 2 would be too small
|
||||
@@ -32,8 +33,6 @@ type operation interface {
|
||||
|
||||
Cancel()
|
||||
|
||||
String() string
|
||||
|
||||
// Does this operation pertain to the given container id?
|
||||
// Used for tidying up pending operations when containers die.
|
||||
ForContainer(ident string) bool
|
||||
@@ -64,7 +63,7 @@ func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname st
|
||||
return &Allocator{
|
||||
ourName: ourName,
|
||||
universe: universe,
|
||||
ring: ring.New(universe.Start, address.Add(universe.Start, universe.Size()), ourName),
|
||||
ring: ring.New(universe.Start, universe.End, ourName),
|
||||
owned: make(map[string][]address.Address),
|
||||
paxos: paxos.NewNode(ourName, ourUID, quorum),
|
||||
nicknames: map[router.PeerName]string{ourName: ourNickname},
|
||||
@@ -94,7 +93,6 @@ func (alloc *Allocator) doOperation(op operation, ops *[]operation) {
|
||||
op.Cancel()
|
||||
return
|
||||
}
|
||||
alloc.establishRing()
|
||||
if !op.Try(alloc) {
|
||||
*ops = append(*ops, op)
|
||||
}
|
||||
@@ -163,6 +161,18 @@ func (alloc *Allocator) tryPendingOps() {
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) spaceRequestDenied(sender router.PeerName, r address.Range) {
|
||||
for i := 0; i < len(alloc.pendingClaims); {
|
||||
claim := alloc.pendingClaims[i].(*claim)
|
||||
if r.Contains(claim.addr) {
|
||||
claim.deniedBy(alloc, sender)
|
||||
alloc.pendingClaims = append(alloc.pendingClaims[:i], alloc.pendingClaims[i+1:]...)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func hasBeenCancelled(cancelChan <-chan bool) func() bool {
|
||||
return func() bool {
|
||||
select {
|
||||
@@ -202,20 +212,18 @@ func (alloc *Allocator) Lookup(ident string, r address.Range) (address.Address,
|
||||
}
|
||||
|
||||
// Claim an address that we think we should own (Sync)
|
||||
func (alloc *Allocator) Claim(ident string, addr address.Address) error {
|
||||
func (alloc *Allocator) Claim(ident string, addr address.Address, noErrorOnUnknown bool) error {
|
||||
resultChan := make(chan error)
|
||||
op := &claim{resultChan: resultChan, ident: ident, addr: addr}
|
||||
op := &claim{resultChan: resultChan, ident: ident, addr: addr, noErrorOnUnknown: noErrorOnUnknown}
|
||||
alloc.doOperation(op, &alloc.pendingClaims)
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
// ContainerDied is provided to satisfy the updater interface; does a 'Delete' underneath. Sync.
|
||||
func (alloc *Allocator) ContainerDied(ident string) error {
|
||||
err := alloc.Delete(ident)
|
||||
if err == nil {
|
||||
func (alloc *Allocator) ContainerDied(ident string) {
|
||||
if err := alloc.Delete(ident); err == nil {
|
||||
alloc.debugln("Container", ident, "died; released addresses")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete (Sync) - release all IP addresses for container with given name
|
||||
@@ -265,15 +273,6 @@ func (alloc *Allocator) Free(ident string, addrToFree address.Address) error {
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// Sync.
|
||||
func (alloc *Allocator) String() string {
|
||||
resultChan := make(chan string)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.string()
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
// Shutdown (Sync)
|
||||
func (alloc *Allocator) Shutdown() {
|
||||
alloc.infof("Shutdown")
|
||||
@@ -342,6 +341,11 @@ func (alloc *Allocator) pruneNicknames() {
|
||||
}
|
||||
}
|
||||
|
||||
func decodeRange(msg []byte) (r address.Range, err error) {
|
||||
decoder := gob.NewDecoder(bytes.NewReader(msg))
|
||||
return r, decoder.Decode(&r)
|
||||
}
|
||||
|
||||
// OnGossipUnicast (Sync)
|
||||
func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) error {
|
||||
alloc.debugln("OnGossipUnicast from", sender, ": ", len(msg), "bytes")
|
||||
@@ -350,27 +354,30 @@ func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) erro
|
||||
switch msg[0] {
|
||||
case msgSpaceRequest:
|
||||
// some other peer asked us for space
|
||||
decoder := gob.NewDecoder(bytes.NewReader(msg[1:]))
|
||||
var r address.Range
|
||||
if err := decoder.Decode(&r); err != nil {
|
||||
resultChan <- err
|
||||
return
|
||||
r, err := decodeRange(msg[1:])
|
||||
if err == nil {
|
||||
alloc.donateSpace(r, sender)
|
||||
}
|
||||
alloc.donateSpace(r, sender)
|
||||
resultChan <- nil
|
||||
resultChan <- err
|
||||
case msgSpaceRequestDenied:
|
||||
r, err := decodeRange(msg[1:])
|
||||
if err == nil {
|
||||
alloc.spaceRequestDenied(sender, r)
|
||||
}
|
||||
resultChan <- err
|
||||
case msgRingUpdate:
|
||||
resultChan <- alloc.update(msg[1:])
|
||||
resultChan <- alloc.update(sender, msg[1:])
|
||||
}
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
// OnGossipBroadcast (Sync)
|
||||
func (alloc *Allocator) OnGossipBroadcast(msg []byte) (router.GossipData, error) {
|
||||
alloc.debugln("OnGossipBroadcast:", len(msg), "bytes")
|
||||
func (alloc *Allocator) OnGossipBroadcast(sender router.PeerName, msg []byte) (router.GossipData, error) {
|
||||
alloc.debugln("OnGossipBroadcast from", sender, ":", len(msg), "bytes")
|
||||
resultChan := make(chan error)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.update(msg)
|
||||
resultChan <- alloc.update(sender, msg)
|
||||
}
|
||||
return alloc.Gossip(), <-resultChan
|
||||
}
|
||||
@@ -419,7 +426,7 @@ func (alloc *Allocator) OnGossip(msg []byte) (router.GossipData, error) {
|
||||
alloc.debugln("Allocator.OnGossip:", len(msg), "bytes")
|
||||
resultChan := make(chan error)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.update(msg)
|
||||
resultChan <- alloc.update(router.UnknownPeerName, msg)
|
||||
}
|
||||
return nil, <-resultChan // for now, we never propagate updates. TBD
|
||||
}
|
||||
@@ -475,30 +482,6 @@ func (alloc *Allocator) actorLoop(actionChan <-chan func()) {
|
||||
|
||||
// Helper functions
|
||||
|
||||
func (alloc *Allocator) string() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "Allocator range %s", alloc.universe)
|
||||
|
||||
if alloc.ring.Empty() {
|
||||
if alloc.paxosTicker != nil {
|
||||
fmt.Fprintf(&buf, " awaiting consensus: %s", alloc.paxos.String())
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(&buf, "\nOwned Ranges:")
|
||||
alloc.ring.FprintWithNicknames(&buf, alloc.nicknames)
|
||||
}
|
||||
if len(alloc.pendingAllocates)+len(alloc.pendingClaims) > 0 {
|
||||
fmt.Fprintf(&buf, "\nPending requests:")
|
||||
for _, op := range alloc.pendingAllocates {
|
||||
fmt.Fprintf(&buf, "\n %s", op.String())
|
||||
}
|
||||
for _, op := range alloc.pendingClaims {
|
||||
fmt.Fprintf(&buf, "\n %s", op.String())
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Ensure we are making progress towards an established ring
|
||||
func (alloc *Allocator) establishRing() {
|
||||
if !alloc.ring.Empty() || alloc.paxosTicker != nil {
|
||||
@@ -574,13 +557,22 @@ func (alloc *Allocator) propose() {
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) error {
|
||||
func encodeRange(r address.Range) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
if err := enc.Encode(r); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
msg := router.Concat([]byte{msgSpaceRequest}, buf.Bytes())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) error {
|
||||
msg := router.Concat([]byte{msgSpaceRequest}, encodeRange(r))
|
||||
return alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) sendSpaceRequestDenied(dest router.PeerName, r address.Range) error {
|
||||
msg := router.Concat([]byte{msgSpaceRequestDenied}, encodeRange(r))
|
||||
return alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
@@ -589,7 +581,7 @@ func (alloc *Allocator) sendRingUpdate(dest router.PeerName) {
|
||||
alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) update(msg []byte) error {
|
||||
func (alloc *Allocator) update(sender router.PeerName, msg []byte) error {
|
||||
reader := bytes.NewReader(msg)
|
||||
decoder := gob.NewDecoder(reader)
|
||||
var data gossipState
|
||||
@@ -613,6 +605,10 @@ func (alloc *Allocator) update(msg []byte) error {
|
||||
// shouldn't get updates for a empty Ring. But tolerate
|
||||
// them just in case.
|
||||
if data.Ring != nil {
|
||||
if data.Ring.Range() != alloc.universe {
|
||||
return fmt.Errorf("Incompatible IP allocation range %s; ours is %s",
|
||||
data.Ring.Range().AsCIDRString(), alloc.universe.AsCIDRString())
|
||||
}
|
||||
err = alloc.ring.Merge(*data.Ring)
|
||||
if !alloc.ring.Empty() {
|
||||
alloc.pruneNicknames()
|
||||
@@ -621,16 +617,22 @@ func (alloc *Allocator) update(msg []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if data.Paxos != nil && alloc.ring.Empty() {
|
||||
if alloc.paxos.Update(data.Paxos) {
|
||||
if alloc.paxos.Think() {
|
||||
// If something important changed, broadcast
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
if data.Paxos != nil {
|
||||
if alloc.ring.Empty() {
|
||||
if alloc.paxos.Update(data.Paxos) {
|
||||
if alloc.paxos.Think() {
|
||||
// If something important changed, broadcast
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
|
||||
if ok, cons := alloc.paxos.Consensus(); ok {
|
||||
alloc.createRing(cons.Value)
|
||||
if ok, cons := alloc.paxos.Consensus(); ok {
|
||||
alloc.createRing(cons.Value)
|
||||
}
|
||||
}
|
||||
} else if sender != router.UnknownPeerName {
|
||||
// Sender is trying to initialize a ring, but we have one
|
||||
// already - send it straight back
|
||||
alloc.sendRingUpdate(sender)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,10 +653,14 @@ func (alloc *Allocator) donateSpace(r address.Range, to router.PeerName) {
|
||||
free := alloc.space.NumFreeAddressesInRange(r)
|
||||
common.Assert(free == 0)
|
||||
alloc.debugln("No space to give to peer", to)
|
||||
// separate message maintains backwards-compatibility:
|
||||
// down-level peers will ignore this and still get the ring update.
|
||||
alloc.sendSpaceRequestDenied(to, r)
|
||||
return
|
||||
}
|
||||
alloc.debugln("Giving range", chunk, "to", to)
|
||||
alloc.ring.GrantRangeToHost(chunk.Start, chunk.End, to)
|
||||
alloc.sendRingUpdate(to)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) assertInvariants() {
|
||||
@@ -717,11 +723,11 @@ func (alloc *Allocator) findOwner(addr address.Address) string {
|
||||
// Logging
|
||||
|
||||
func (alloc *Allocator) infof(fmt string, args ...interface{}) {
|
||||
common.Info.Printf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
common.Log.Infof("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
}
|
||||
func (alloc *Allocator) debugln(args ...interface{}) {
|
||||
common.Debug.Println(append([]interface{}{fmt.Sprintf("[allocator %s]:", alloc.ourName)}, args...)...)
|
||||
common.Log.Debugln(append([]interface{}{fmt.Sprintf("[allocator %s]:", alloc.ourName)}, args...)...)
|
||||
}
|
||||
func (alloc *Allocator) debugf(fmt string, args ...interface{}) {
|
||||
common.Debug.Printf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
common.Log.Debugf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
}
|
||||
+127
-78
@@ -7,10 +7,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,12 +40,12 @@ func TestAllocFree(t *testing.T) {
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
|
||||
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertEqualString(t, addr2.String(), testAddr2, "address")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr2, addr2.String(), "address")
|
||||
|
||||
// Ask for another address for a different container and check it's different
|
||||
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), nil)
|
||||
@@ -54,24 +55,23 @@ func TestAllocFree(t *testing.T) {
|
||||
|
||||
// Ask for the first container again and we should get the same addresses again
|
||||
addr1a, _ := alloc.Allocate(container1, cidr1.HostRange(), nil)
|
||||
wt.AssertEqualString(t, addr1a.String(), testAddr1, "address")
|
||||
require.Equal(t, testAddr1, addr1a.String(), "address")
|
||||
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), nil)
|
||||
wt.AssertEqualString(t, addr2a.String(), testAddr2, "address")
|
||||
require.Equal(t, testAddr2, addr2a.String(), "address")
|
||||
|
||||
// Now delete the first container, and we should get its addresses back
|
||||
wt.AssertSuccess(t, alloc.Delete(container1))
|
||||
require.NoError(t, alloc.Delete(container1))
|
||||
addr3, _ := alloc.Allocate(container3, cidr1.HostRange(), nil)
|
||||
wt.AssertEqualString(t, addr3.String(), testAddr1, "address")
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), nil)
|
||||
wt.AssertEqualString(t, addr4.String(), testAddr2, "address")
|
||||
require.Equal(t, testAddr2, addr4.String(), "address")
|
||||
|
||||
alloc.ContainerDied(container2)
|
||||
alloc.ContainerDied(container3)
|
||||
wt.AssertEquals(t, alloc.NumFreeAddresses(subnet), address.Offset(spaceSize))
|
||||
require.Equal(t, address.Offset(spaceSize), alloc.NumFreeAddresses(subnet))
|
||||
}
|
||||
|
||||
func TestBootstrap(t *testing.T) {
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
donateSize = 5
|
||||
donateStart = "10.0.1.7"
|
||||
@@ -86,7 +86,7 @@ func TestBootstrap(t *testing.T) {
|
||||
alloc2, _ := makeAllocatorWithMockGossip(t, peerNameString, testStart1+"/22", 2)
|
||||
defer alloc2.Stop()
|
||||
|
||||
alloc1.OnGossipBroadcast(alloc2.Encode())
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
|
||||
|
||||
@@ -108,19 +108,19 @@ func TestBootstrap(t *testing.T) {
|
||||
|
||||
// alloc2 receives paxos update and broadcasts its reply
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
alloc2.OnGossipBroadcast(alloc1.Encode())
|
||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
||||
|
||||
ExpectBroadcastMessage(alloc1, nil)
|
||||
alloc1.OnGossipBroadcast(alloc2.Encode())
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
// both nodes will get consensus now so initialize the ring
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
alloc2.OnGossipBroadcast(alloc1.Encode())
|
||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
alloc1.OnGossipBroadcast(alloc2.Encode())
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
// now alloc1 should have space
|
||||
|
||||
AssertSent(t, done)
|
||||
@@ -131,23 +131,45 @@ func TestBootstrap(t *testing.T) {
|
||||
func TestAllocatorClaim(t *testing.T) {
|
||||
const (
|
||||
container1 = "abcdef"
|
||||
container2 = "baddf00d"
|
||||
container3 = "b01df00d"
|
||||
universe = "10.0.3.0/30"
|
||||
testAddr1 = "10.0.3.1" // first address allocated should be .1 because .0 is network addr
|
||||
universe = "10.0.3.0/24"
|
||||
testAddr1 = "10.0.3.2"
|
||||
testAddr2 = "10.0.4.2"
|
||||
)
|
||||
|
||||
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||
allocs, _, subnet := makeNetworkOfAllocators(2, universe)
|
||||
alloc := allocs[1]
|
||||
defer alloc.Stop()
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
addr1, _ := address.ParseIP(testAddr1)
|
||||
|
||||
err := alloc.Claim(container3, addr1)
|
||||
wt.AssertNoErr(t, err)
|
||||
// First claim should trigger "dunno, I'm going to wait"
|
||||
err := alloc.Claim(container3, addr1, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Do one allocate to ensure paxos is all done
|
||||
alloc.Allocate("unused", subnet, nil)
|
||||
// Do an allocate on the other peer, which we will try to claim later
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, nil)
|
||||
|
||||
// Now try the claim again
|
||||
err = alloc.Claim(container3, addr1, true)
|
||||
require.NoError(t, err)
|
||||
// Check we get this address back if we try an allocate
|
||||
addr3, _ := alloc.Allocate(container3, subnet, nil)
|
||||
wt.AssertEqualString(t, addr3.String(), testAddr1, "address")
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
// one more claim should still work
|
||||
err = alloc.Claim(container3, addr1, true)
|
||||
require.NoError(t, err)
|
||||
// claim for a different container should fail
|
||||
err = alloc.Claim(container1, addr1, true)
|
||||
require.Error(t, err)
|
||||
// claiming the address allocated on the other peer should fail
|
||||
err = alloc.Claim(container1, addrx, true)
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// Check an address outside of our universe
|
||||
addr2, _ := address.ParseIP(testAddr2)
|
||||
err = alloc.Claim(container1, addr2, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) pause() func() {
|
||||
@@ -163,18 +185,17 @@ func (alloc *Allocator) pause() func() {
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
CIDR = "10.0.1.7/26"
|
||||
)
|
||||
|
||||
router := TestGossipRouter{make(map[router.PeerName]chan interface{}), 0.0}
|
||||
router := gossip.NewTestRouter(0.0)
|
||||
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", CIDR, 2)
|
||||
alloc1.SetInterfaces(router.connect(alloc1.ourName, alloc1))
|
||||
alloc1.SetInterfaces(router.Connect(alloc1.ourName, alloc1))
|
||||
|
||||
alloc2, _ := makeAllocator("02:00:00:02:00:00", CIDR, 2)
|
||||
alloc2.SetInterfaces(router.connect(alloc2.ourName, alloc2))
|
||||
alloc2.SetInterfaces(router.Connect(alloc2.ourName, alloc2))
|
||||
alloc1.claimRingForTesting(alloc1, alloc2)
|
||||
alloc2.claimRingForTesting(alloc1, alloc2)
|
||||
|
||||
@@ -182,15 +203,15 @@ func TestCancel(t *testing.T) {
|
||||
alloc2.Start()
|
||||
|
||||
// tell peers about each other
|
||||
alloc1.OnGossipBroadcast(alloc2.Encode())
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
// Get some IPs, so each allocator has some space
|
||||
res1, _ := alloc1.Allocate("foo", subnet, nil)
|
||||
common.Debug.Printf("res1 = %s", res1.String())
|
||||
common.Log.Debugf("res1 = %s", res1.String())
|
||||
res2, _ := alloc2.Allocate("bar", subnet, nil)
|
||||
common.Debug.Printf("res2 = %s", res2.String())
|
||||
common.Log.Debugf("res2 = %s", res2.String())
|
||||
if res1 == res2 {
|
||||
wt.Fatalf(t, "Error: got same ips!")
|
||||
require.FailNow(t, "Error: got same ips!")
|
||||
}
|
||||
|
||||
// Now we're going to pause alloc2 and ask alloc1
|
||||
@@ -215,7 +236,7 @@ func TestCancel(t *testing.T) {
|
||||
cancelChan <- true
|
||||
unpause()
|
||||
if <-doneChan {
|
||||
wt.Fatalf(t, "Error: got result from Allocate")
|
||||
require.FailNow(t, "Error: got result from Allocate")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +256,7 @@ func TestGossipShutdown(t *testing.T) {
|
||||
alloc.Shutdown()
|
||||
|
||||
_, err := alloc.Allocate(container2, subnet, nil) // trying to allocate after shutdown should fail
|
||||
wt.AssertFalse(t, err == nil, "no address")
|
||||
require.False(t, err == nil, "no address")
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc)
|
||||
}
|
||||
@@ -250,33 +271,32 @@ func TestTransfer(t *testing.T) {
|
||||
alloc3 := allocs[2] // This will be 'master' and get the first range
|
||||
|
||||
_, err := alloc2.Allocate("foo", subnet, nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
|
||||
_, err = alloc3.Allocate("bar", subnet, nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
|
||||
router.GossipBroadcast(alloc2.Gossip())
|
||||
router.flush()
|
||||
router.GossipBroadcast(alloc3.Gossip())
|
||||
router.flush()
|
||||
router.removePeer(alloc2.ourName)
|
||||
router.removePeer(alloc3.ourName)
|
||||
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
|
||||
router.Flush()
|
||||
alloc2.gossip.GossipBroadcast(alloc3.Gossip())
|
||||
router.Flush()
|
||||
router.RemovePeer(alloc2.ourName)
|
||||
router.RemovePeer(alloc3.ourName)
|
||||
alloc2.Stop()
|
||||
alloc3.Stop()
|
||||
router.flush()
|
||||
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
|
||||
wt.AssertSuccess(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
|
||||
router.flush()
|
||||
router.Flush()
|
||||
require.NoError(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
|
||||
require.NoError(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
|
||||
router.Flush()
|
||||
|
||||
wt.AssertEquals(t, alloc1.NumFreeAddresses(subnet), address.Offset(1022))
|
||||
require.Equal(t, address.Offset(1022), alloc1.NumFreeAddresses(subnet))
|
||||
|
||||
_, err = alloc1.Allocate("foo", subnet, nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
alloc1.Stop()
|
||||
}
|
||||
|
||||
func TestFakeRouterSimple(t *testing.T) {
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
cidr = "10.0.1.7/22"
|
||||
)
|
||||
@@ -287,17 +307,16 @@ func TestFakeRouterSimple(t *testing.T) {
|
||||
//alloc2 := allocs[1]
|
||||
|
||||
_, err := alloc1.Allocate("foo", subnet, nil)
|
||||
wt.AssertTrue(t, err == nil, "Failed to get address")
|
||||
require.NoError(t, err, "Failed to get address")
|
||||
}
|
||||
|
||||
func TestAllocatorFuzz(t *testing.T) {
|
||||
common.InitDefaultLogging(false)
|
||||
const (
|
||||
firstpass = 1000
|
||||
secondpass = 20000
|
||||
nodes = 10
|
||||
secondpass = 10000
|
||||
nodes = 5
|
||||
maxAddresses = 1000
|
||||
concurrency = 30
|
||||
concurrency = 5
|
||||
cidr = "10.0.1.7/22"
|
||||
)
|
||||
allocs, _, subnet := makeNetworkOfAllocators(nodes, cidr)
|
||||
@@ -329,28 +348,19 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
return xs[:ls]
|
||||
}
|
||||
|
||||
// Do a Allocate and check the address
|
||||
// is unique. Needs a unique container
|
||||
// name.
|
||||
allocate := func(name string) {
|
||||
bumpPending := func() bool {
|
||||
stateLock.Lock()
|
||||
if len(addrs)+numPending >= maxAddresses {
|
||||
stateLock.Unlock()
|
||||
return
|
||||
return false
|
||||
}
|
||||
numPending++
|
||||
stateLock.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
alloc := allocs[allocIndex]
|
||||
//common.Info.Printf("Allocate: asking allocator %d", allocIndex)
|
||||
addr, err := alloc.Allocate(name, subnet, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not allocate addr"))
|
||||
}
|
||||
|
||||
//common.Info.Printf("Allocate: got address %s for name %s", addr, name)
|
||||
noteAllocation := func(allocIndex int32, name string, addr address.Address) {
|
||||
//common.Log.Infof("Allocate: got address %s for name %s", addr, name)
|
||||
addrStr := addr.String()
|
||||
|
||||
stateLock.Lock()
|
||||
@@ -366,6 +376,26 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
numPending--
|
||||
}
|
||||
|
||||
// Do a Allocate and check the address
|
||||
// is unique. Needs a unique container
|
||||
// name.
|
||||
allocate := func(name string) {
|
||||
if !bumpPending() {
|
||||
return
|
||||
}
|
||||
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
alloc := allocs[allocIndex]
|
||||
//common.Log.Infof("Allocate: asking allocator %d", allocIndex)
|
||||
addr, err := alloc.Allocate(name, subnet, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not allocate addr"))
|
||||
}
|
||||
|
||||
noteAllocation(allocIndex, name, addr)
|
||||
}
|
||||
|
||||
// Free a random address.
|
||||
free := func() {
|
||||
stateLock.Lock()
|
||||
@@ -387,13 +417,13 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
stateLock.Unlock()
|
||||
|
||||
alloc := allocs[res.alloc]
|
||||
//common.Info.Printf("Freeing %s (%s) on allocator %d", res.name, addr, res.alloc)
|
||||
//common.Log.Infof("Freeing %s (%s) on allocator %d", res.name, addr, res.alloc)
|
||||
|
||||
oldAddr, err := address.ParseIP(addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wt.AssertSuccess(t, alloc.Free(res.name, oldAddr))
|
||||
require.NoError(t, alloc.Free(res.name, oldAddr))
|
||||
}
|
||||
|
||||
// Do a Allocate on an existing container & allocator
|
||||
@@ -412,7 +442,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
stateLock.Unlock()
|
||||
alloc := allocs[res.alloc]
|
||||
|
||||
//common.Info.Printf("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
|
||||
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
|
||||
|
||||
newAddr, _ := alloc.Allocate(res.name, subnet, nil)
|
||||
oldAddr, _ := address.ParseIP(addr)
|
||||
@@ -426,6 +456,21 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
stateLock.Unlock()
|
||||
}
|
||||
|
||||
// Claim a random address for a unique container name - may not succeed
|
||||
claim := func(name string) {
|
||||
if !bumpPending() {
|
||||
return
|
||||
}
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
addressIndex := rand.Int31n(int32(subnet.Size()))
|
||||
alloc := allocs[allocIndex]
|
||||
addr := address.Add(subnet.Start, address.Offset(addressIndex))
|
||||
err := alloc.Claim(name, addr, true)
|
||||
if err == nil {
|
||||
noteAllocation(allocIndex, name, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Run function _f_ _iterations_ times, in _concurrency_
|
||||
// number of goroutines
|
||||
doConcurrentIterations := func(iterations int, f func(int)) {
|
||||
@@ -465,9 +510,13 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
// free a random addr
|
||||
free()
|
||||
|
||||
case 0.8 <= r && r < 1.0:
|
||||
case 0.8 <= r && r < 0.95:
|
||||
// ask for an existing name again, check we get same ip
|
||||
allocateAgain()
|
||||
|
||||
case 0.95 <= r && r < 1.0:
|
||||
name := fmt.Sprintf("second%d", iteration)
|
||||
claim(name)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -479,7 +528,7 @@ func TestGossipSkew(t *testing.T) {
|
||||
alloc2.now = func() time.Time { return time.Now().Add(time.Hour * 2) }
|
||||
defer alloc2.Stop()
|
||||
|
||||
if _, err := alloc1.OnGossipBroadcast(alloc2.Encode()); err == nil {
|
||||
if _, err := alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode()); err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
+56
-41
@@ -4,25 +4,28 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
resultChan chan<- error
|
||||
ident string
|
||||
addr address.Address
|
||||
resultChan chan<- error
|
||||
ident string
|
||||
addr address.Address
|
||||
noErrorOnUnknown bool
|
||||
}
|
||||
|
||||
// Send an error (or nil for success) back to caller listening on resultChan
|
||||
func (c *claim) sendResult(result error) {
|
||||
// Make sure we only send a result once, since listener stops listening after that
|
||||
if c.resultChan != nil {
|
||||
c.resultChan <- result
|
||||
close(c.resultChan)
|
||||
c.resultChan = nil
|
||||
return
|
||||
}
|
||||
if result != nil {
|
||||
common.Error.Println("[allocator] " + result.Error())
|
||||
common.Log.Errorln("[allocator] " + result.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,59 +33,71 @@ func (c *claim) sendResult(result error) {
|
||||
func (c *claim) Try(alloc *Allocator) bool {
|
||||
if !alloc.ring.Contains(c.addr) {
|
||||
// Address not within our universe; assume user knows what they are doing
|
||||
alloc.infof("Ignored address %s claimed by %s - not in our universe\n", c.addr, c.ident)
|
||||
alloc.infof("Ignored address %s claimed by %s - not in our universe", c.addr, c.ident)
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
|
||||
// If our ring doesn't know, it must be empty. We will have initiated the
|
||||
// bootstrap of the ring, so wait until we find some owner for this
|
||||
// range (might be us).
|
||||
owner := alloc.ring.Owner(c.addr)
|
||||
if owner == router.UnknownPeerName {
|
||||
alloc.infof("Ring is empty; will try later.\n", c.addr, owner)
|
||||
c.sendResult(nil) // don't make the caller wait
|
||||
alloc.establishRing()
|
||||
|
||||
switch owner := alloc.ring.Owner(c.addr); owner {
|
||||
case alloc.ourName:
|
||||
// success
|
||||
case router.UnknownPeerName:
|
||||
// If our ring doesn't know, it must be empty.
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: address allocator still initializing; will try later.", c.addr, c.ident)
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(fmt.Errorf("%s is in the range %s, but the allocator is not initialized yet", c.addr, alloc.universe.AsCIDRString()))
|
||||
}
|
||||
return false
|
||||
default:
|
||||
alloc.debugf("requesting address %s from other peer %s", c.addr, owner)
|
||||
err := alloc.sendSpaceRequest(owner, address.NewRange(c.addr, 1))
|
||||
if err != nil { // can't speak to owner right now
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: %s; will try later.", c.addr, c.ident, err)
|
||||
c.sendResult(nil)
|
||||
} else { // just tell the user they can't do this.
|
||||
c.deniedBy(alloc, owner)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if owner != alloc.ourName {
|
||||
name, found := alloc.nicknames[owner]
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
}
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name))
|
||||
return true
|
||||
}
|
||||
|
||||
// We are the owner, check we haven't given it to another container
|
||||
existingIdent := alloc.findOwner(c.addr)
|
||||
if existingIdent == c.ident {
|
||||
switch existingIdent := alloc.findOwner(c.addr); existingIdent {
|
||||
case "":
|
||||
if err := alloc.space.Claim(c.addr); err == nil {
|
||||
alloc.debugln("Claimed", c.addr, "for", c.ident)
|
||||
alloc.addOwned(c.ident, c.addr)
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(err)
|
||||
}
|
||||
case c.ident:
|
||||
// same identifier is claiming same address; that's OK
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
default:
|
||||
// Addr already owned by container on this machine
|
||||
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.addr.String(), existingIdent))
|
||||
}
|
||||
if existingIdent == "" {
|
||||
err := alloc.space.Claim(c.addr)
|
||||
if err != nil {
|
||||
c.sendResult(err)
|
||||
return true
|
||||
}
|
||||
alloc.debugln("Claimed", c.addr, "for", c.ident)
|
||||
alloc.addOwned(c.ident, c.addr)
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
// Addr already owned by container on this machine
|
||||
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.addr.String(), existingIdent))
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *claim) deniedBy(alloc *Allocator, owner router.PeerName) {
|
||||
name, found := alloc.nicknames[owner]
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
}
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name))
|
||||
}
|
||||
|
||||
func (c *claim) Cancel() {
|
||||
c.sendResult(fmt.Errorf("Operation cancelled."))
|
||||
}
|
||||
|
||||
func (c *claim) String() string {
|
||||
return fmt.Sprintf("Claim %s -> %s", c.ident, c.addr.String())
|
||||
}
|
||||
|
||||
func (c *claim) ForContainer(ident string) bool {
|
||||
return c.ident == ident
|
||||
}
|
||||
+8
-7
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
func badRequest(w http.ResponseWriter, err error) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
common.Warning.Println("[allocator]:", err.Error())
|
||||
common.Log.Warningln("[allocator]:", err.Error())
|
||||
}
|
||||
|
||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
||||
@@ -22,10 +22,11 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
vars := mux.Vars(r)
|
||||
ident := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true"
|
||||
if ip, err := address.ParseIP(ipStr); err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
} else if err := alloc.Claim(ident, ip); err != nil {
|
||||
} else if err := alloc.Claim(ident, ip, noErrorOnUnknown); err != nil {
|
||||
badRequest(w, fmt.Errorf("Unable to claim: %s", err))
|
||||
return
|
||||
}
|
||||
@@ -77,8 +78,8 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
badRequest(w, fmt.Errorf("Unable to allocate: %s", err))
|
||||
return
|
||||
}
|
||||
if dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
|
||||
common.Info.Printf("[allocator] '%s' is not running: freeing %s", ident, addr)
|
||||
if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
|
||||
common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, addr)
|
||||
alloc.Free(ident, addr)
|
||||
return
|
||||
}
|
||||
@@ -94,8 +95,8 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
badRequest(w, err)
|
||||
return
|
||||
}
|
||||
if dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
|
||||
common.Info.Printf("[allocator] '%s' is not running: freeing %s", ident, newAddr)
|
||||
if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
|
||||
common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, newAddr)
|
||||
alloc.Free(ident, newAddr)
|
||||
return
|
||||
}
|
||||
|
||||
+22
-23
@@ -11,16 +11,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func HTTPPost(t *testing.T, url string) string {
|
||||
resp, err := http.Post(url, "", nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http response")
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(body)
|
||||
@@ -28,7 +28,7 @@ func HTTPPost(t *testing.T, url string) string {
|
||||
|
||||
func HTTPGet(t *testing.T, url string) string {
|
||||
resp, err := http.Get(url)
|
||||
wt.AssertNoErr(t, err)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(body)
|
||||
@@ -48,13 +48,13 @@ func listenHTTP(alloc *Allocator, subnet address.CIDR) int {
|
||||
|
||||
httpListener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
common.Error.Fatal("Unable to create http listener: ", err)
|
||||
common.Log.Fatal("Unable to create http listener: ", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
srv := &http.Server{Handler: router}
|
||||
if err := srv.Serve(httpListener); err != nil {
|
||||
common.Error.Fatal("Unable to serve http: ", err)
|
||||
common.Log.Fatal("Unable to serve http: ", err)
|
||||
}
|
||||
}()
|
||||
return httpListener.Addr().(*net.TCPAddr).Port
|
||||
@@ -87,28 +87,28 @@ func TestHttp(t *testing.T) {
|
||||
|
||||
// Allocate an address in each subnet, and check we got what we expected
|
||||
cidr1a := HTTPPost(t, allocURL(port, testCIDR1, containerID))
|
||||
wt.AssertEqualString(t, cidr1a, testAddr1, "address")
|
||||
require.Equal(t, testAddr1, cidr1a, "address")
|
||||
cidr2a := HTTPPost(t, allocURL(port, testCIDR2, containerID))
|
||||
wt.AssertEqualString(t, cidr2a, testAddr2, "address")
|
||||
require.Equal(t, testAddr2, cidr2a, "address")
|
||||
// Now, make the same requests again to check the operation is idempotent
|
||||
check := HTTPGet(t, allocURL(port, testCIDR1, containerID))
|
||||
wt.AssertEqualString(t, check, cidr1a, "address")
|
||||
require.Equal(t, cidr1a, check, "address")
|
||||
check = HTTPGet(t, allocURL(port, testCIDR2, containerID))
|
||||
wt.AssertEqualString(t, check, cidr2a, "address")
|
||||
require.Equal(t, cidr2a, check, "address")
|
||||
|
||||
// Ask the http server for a pair of addresses for another container and check they're different
|
||||
cidr1b := HTTPPost(t, allocURL(port, testCIDR1, container2))
|
||||
wt.AssertFalse(t, cidr1b == testAddr1, "address")
|
||||
require.False(t, cidr1b == testAddr1, "address")
|
||||
cidr2b := HTTPPost(t, allocURL(port, testCIDR2, container2))
|
||||
wt.AssertFalse(t, cidr2b == testAddr2, "address")
|
||||
require.False(t, cidr2b == testAddr2, "address")
|
||||
|
||||
// Now free the first container, and we should get its addresses back when we ask
|
||||
doHTTP("DELETE", identURL(port, containerID))
|
||||
|
||||
cidr1c := HTTPPost(t, allocURL(port, testCIDR1, container3))
|
||||
wt.AssertEqualString(t, cidr1c, testAddr1, "address")
|
||||
require.Equal(t, testAddr1, cidr1c, "address")
|
||||
cidr2c := HTTPPost(t, allocURL(port, testCIDR2, container3))
|
||||
wt.AssertEqualString(t, cidr2c, testAddr2, "address")
|
||||
require.Equal(t, testAddr2, cidr2c, "address")
|
||||
|
||||
// Would like to shut down the http server at the end of this test
|
||||
// but it's complicated.
|
||||
@@ -132,16 +132,16 @@ func TestBadHttp(t *testing.T) {
|
||||
testAddr1 := parts[0]
|
||||
// Verb that's not handled
|
||||
resp, err := doHTTP("HEAD", fmt.Sprintf("http://localhost:%d/ip/%s/%s", port, containerID, testAddr1))
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
|
||||
// Mis-spelled URL
|
||||
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/xip/%s/", port, containerID))
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
|
||||
// Malformed URL
|
||||
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/ip/%s/foo/bar/baz", port, containerID))
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusNotFound, "http response")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
|
||||
}
|
||||
|
||||
func TestHTTPCancel(t *testing.T) {
|
||||
@@ -151,7 +151,6 @@ func TestHTTPCancel(t *testing.T) {
|
||||
}
|
||||
|
||||
func impTestHTTPCancel(t *testing.T) {
|
||||
common.InitDefaultLogging(true)
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
testCIDR1 = "10.0.3.0/29"
|
||||
@@ -180,6 +179,6 @@ func impTestHTTPCancel(t *testing.T) {
|
||||
unpause()
|
||||
res := <-done
|
||||
if res != nil {
|
||||
wt.Fatalf(t, "Error: Allocate returned non-nil")
|
||||
require.FailNow(t, "Error: Allocate returned non-nil")
|
||||
}
|
||||
}
|
||||
+9
-6
@@ -1,8 +1,6 @@
|
||||
package paxos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
@@ -252,9 +250,14 @@ func (node *Node) Consensus() (bool, AcceptedValue) {
|
||||
return false, AcceptedValue{}
|
||||
}
|
||||
|
||||
func (node *Node) String() string {
|
||||
if ok, val := node.Consensus(); ok {
|
||||
return fmt.Sprintf("Consensus reached with size %d", len(val.Value))
|
||||
type Status struct {
|
||||
KnownNodes int
|
||||
Quorum uint
|
||||
}
|
||||
|
||||
func NewStatus(node *Node) *Status {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Sprintf("Nodes known: %d, Quorum size: %d", len(node.knows), node.quorum)
|
||||
return &Status{len(node.knows), node.quorum}
|
||||
}
|
||||
+1
-5
@@ -4,7 +4,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
@@ -68,10 +68,6 @@ func (es entries) get(token address.Address) (*entry, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (es *entries) remove(i int) {
|
||||
*es = (*es)[:i+copy((*es)[i:], (*es)[i+1:])]
|
||||
}
|
||||
|
||||
// Is token between entries at i and j?
|
||||
// NB i and j can overflow and will wrap
|
||||
// NBB this function does not work very well if there is only one
|
||||
|
||||
+20
-13
@@ -10,9 +10,10 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
@@ -32,16 +33,14 @@ func (r *Ring) assertInvariants() {
|
||||
|
||||
// Errors returned by Merge
|
||||
var (
|
||||
ErrNotSorted = errors.New("Ring not sorted")
|
||||
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
||||
ErrTokenOutOfRange = errors.New("Token is out of range")
|
||||
ErrDifferentSubnets = errors.New("IP Allocator with different subnet detected")
|
||||
ErrNewerVersion = errors.New("Received new version for entry I own!")
|
||||
ErrInvalidEntry = errors.New("Received invalid state update!")
|
||||
ErrEntryInMyRange = errors.New("Received new entry in my range!")
|
||||
ErrNoFreeSpace = errors.New("No free space found!")
|
||||
ErrInvalidTimeout = errors.New("dt must be greater than 0")
|
||||
ErrNotFound = errors.New("No entries for peer found")
|
||||
ErrNotSorted = errors.New("Ring not sorted")
|
||||
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
||||
ErrTokenOutOfRange = errors.New("Token is out of range")
|
||||
ErrDifferentRange = errors.New("Received range differs from ours!")
|
||||
ErrNewerVersion = errors.New("Received new version for entry I own!")
|
||||
ErrInvalidEntry = errors.New("Received invalid state update!")
|
||||
ErrEntryInMyRange = errors.New("Received new entry in my range!")
|
||||
ErrNotFound = errors.New("No entries for peer found")
|
||||
)
|
||||
|
||||
func (r *Ring) checkInvariants() error {
|
||||
@@ -91,6 +90,10 @@ func New(start, end address.Address, peer router.PeerName) *Ring {
|
||||
return ring
|
||||
}
|
||||
|
||||
func (r *Ring) Range() address.Range {
|
||||
return address.Range{Start: r.Start, End: r.End}
|
||||
}
|
||||
|
||||
// Returns the distance between two tokens on this ring, dealing
|
||||
// with ranges which cross the origin
|
||||
func (r *Ring) distance(start, end address.Address) address.Offset {
|
||||
@@ -186,7 +189,7 @@ func (r *Ring) Merge(gossip Ring) error {
|
||||
}
|
||||
|
||||
if r.Start != gossip.Start || r.End != gossip.End {
|
||||
return ErrDifferentSubnets
|
||||
return ErrDifferentRange
|
||||
}
|
||||
|
||||
// Now merge their ring with yours, in a temporary ring.
|
||||
@@ -217,7 +220,7 @@ func (r *Ring) Merge(gossip Ring) error {
|
||||
switch {
|
||||
case mine.Version >= theirs.Version:
|
||||
if mine.Version == theirs.Version && !mine.Equal(theirs) {
|
||||
common.Debug.Printf("Error merging entries at %s - %v != %v\n", mine.Token, mine, theirs)
|
||||
common.Log.Debugf("Error merging entries at %s - %v != %v", mine.Token, mine, theirs)
|
||||
return ErrInvalidEntry
|
||||
}
|
||||
addToResult(*mine)
|
||||
@@ -518,3 +521,7 @@ func (r *Ring) PeerNames() map[router.PeerName]struct{} {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
+125
-116
@@ -7,10 +7,10 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -34,26 +34,26 @@ func TestInvariants(t *testing.T) {
|
||||
|
||||
// Check ring is sorted
|
||||
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot10, Peer: peer2name}}
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrNotSorted, "Expected error")
|
||||
require.True(t, ring.checkInvariants() == ErrNotSorted, "Expected error")
|
||||
|
||||
// Check tokens don't appear twice
|
||||
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot245, Peer: peer2name}}
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
|
||||
require.True(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
|
||||
|
||||
// Check tokens are in bounds
|
||||
ring = New(dot10, dot245, peer1name)
|
||||
ring.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
|
||||
ring.Entries = []*entry{{Token: end, Peer: peer1name}}
|
||||
wt.AssertTrue(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
ring := New(start, end, peer1name)
|
||||
ring.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
|
||||
wt.AssertPanic(t, func() {
|
||||
require.Panics(t, func() {
|
||||
ring.Entries.insert(entry{Token: start, Peer: peer1name})
|
||||
})
|
||||
|
||||
@@ -61,11 +61,11 @@ func TestInsert(t *testing.T) {
|
||||
ring.Entries.insert(entry{Token: dot245, Peer: peer1name})
|
||||
ring2 := New(start, end, peer1name)
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot245, Peer: peer1name}}
|
||||
wt.AssertEquals(t, ring, ring2)
|
||||
require.Equal(t, ring2, ring)
|
||||
|
||||
ring.Entries.insert(entry{Token: dot10, Peer: peer1name})
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot10, Peer: peer1name}, {Token: dot245, Peer: peer1name}}
|
||||
wt.AssertEquals(t, ring, ring2)
|
||||
require.Equal(t, ring2, ring)
|
||||
}
|
||||
|
||||
func TestBetween(t *testing.T) {
|
||||
@@ -76,7 +76,7 @@ func TestBetween(t *testing.T) {
|
||||
// between should return true for everything
|
||||
for i := 1; i <= 255; i++ {
|
||||
ip := ParseIP(fmt.Sprintf("10.0.0.%d", i))
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1), "between should be true!")
|
||||
require.True(t, ring1.Entries.between(ip, 0, 1), "between should be true!")
|
||||
}
|
||||
|
||||
// Now, construct a ring with entries at +10 and -10
|
||||
@@ -87,25 +87,25 @@ func TestBetween(t *testing.T) {
|
||||
for i := 10; i <= 244; i++ {
|
||||
ipStr := fmt.Sprintf("10.0.0.%d", i)
|
||||
ip := ParseIP(ipStr)
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 0, 1),
|
||||
require.True(t, ring1.Entries.between(ip, 0, 1),
|
||||
fmt.Sprintf("Between should be true for %s!", ipStr))
|
||||
wt.AssertFalse(t, ring1.Entries.between(ip, 1, 2),
|
||||
require.False(t, ring1.Entries.between(ip, 1, 2),
|
||||
fmt.Sprintf("Between should be false for %s!", ipStr))
|
||||
}
|
||||
for i := 0; i <= 9; i++ {
|
||||
ipStr := fmt.Sprintf("10.0.0.%d", i)
|
||||
ip := ParseIP(ipStr)
|
||||
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
|
||||
require.False(t, ring1.Entries.between(ip, 0, 1),
|
||||
fmt.Sprintf("Between should be false for %s!", ipStr))
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
|
||||
require.True(t, ring1.Entries.between(ip, 1, 2),
|
||||
fmt.Sprintf("Between should be true for %s!", ipStr))
|
||||
}
|
||||
for i := 245; i <= 255; i++ {
|
||||
ipStr := fmt.Sprintf("10.0.0.%d", i)
|
||||
ip := ParseIP(ipStr)
|
||||
wt.AssertFalse(t, ring1.Entries.between(ip, 0, 1),
|
||||
require.False(t, ring1.Entries.between(ip, 0, 1),
|
||||
fmt.Sprintf("Between should be false for %s!", ipStr))
|
||||
wt.AssertTrue(t, ring1.Entries.between(ip, 1, 2),
|
||||
require.True(t, ring1.Entries.between(ip, 1, 2),
|
||||
fmt.Sprintf("Between should be true for %s!", ipStr))
|
||||
}
|
||||
}
|
||||
@@ -116,32 +116,33 @@ func TestGrantSimple(t *testing.T) {
|
||||
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 255}})
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 255}}, ring1.Entries)
|
||||
|
||||
// Now grant everything to peer2
|
||||
ring1.GrantRangeToHost(start, end, peer2name)
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}}
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now spint back to peer 1
|
||||
ring2.GrantRangeToHost(dot10, end, peer1name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 245}}
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// And spint back to peer 2 again
|
||||
ring1.GrantRangeToHost(dot245, end, peer2name)
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 235, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}})
|
||||
{Token: dot245, Peer: peer2name, Free: 10}}, ring1.Entries)
|
||||
|
||||
// Grant range spanning a live token
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 235}, {Token: dot245, Peer: peer1name, Free: 10}}
|
||||
ring1.GrantRangeToHost(dot10, end, peer2name)
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer2name, Free: 235, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 10, Version: 1}})
|
||||
{Token: dot245, Peer: peer2name, Free: 10, Version: 1}}, ring1.Entries)
|
||||
|
||||
}
|
||||
|
||||
func TestGrantSplit(t *testing.T) {
|
||||
@@ -151,23 +152,25 @@ func TestGrantSplit(t *testing.T) {
|
||||
// Claim everything for peer1
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||
ring2.Merge(*ring1)
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now grant a split range to peer2
|
||||
ring1.GrantRangeToHost(dot10, dot245, peer2name)
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer2name, Free: 235},
|
||||
{Token: dot245, Peer: peer1name, Free: 10}})
|
||||
{Token: dot245, Peer: peer1name, Free: 10}}, ring1.Entries)
|
||||
|
||||
ring1.assertInvariants()
|
||||
|
||||
// Grant range spanning a live token, and inserting a new token
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer1name, Free: 118}, {Token: middle, Peer: peer1name, Free: 127}}
|
||||
ring1.GrantRangeToHost(dot10, dot245, peer2name)
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||
{Token: dot10, Peer: peer2name, Free: 118, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 117, Version: 1},
|
||||
{Token: dot245, Peer: peer1name, Free: 10, Version: 0}})
|
||||
{Token: dot245, Peer: peer1name, Free: 10, Version: 0}}, ring1.Entries)
|
||||
|
||||
ring1.assertInvariants()
|
||||
}
|
||||
|
||||
@@ -178,23 +181,25 @@ func TestMergeSimple(t *testing.T) {
|
||||
// Claim everything for peer1
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer1name, Free: 128, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 127}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 128, Version: 1},
|
||||
{Token: middle, Peer: peer2name, Free: 127}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now to two different operations on either side,
|
||||
// check we can Merge again
|
||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||
ring2.GrantRangeToHost(middle, end, peer1name)
|
||||
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127, Version: 1}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127, Version: 1}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
}
|
||||
|
||||
func TestMergeErrors(t *testing.T) {
|
||||
@@ -202,28 +207,28 @@ func TestMergeErrors(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}, {Token: start, Peer: peer2name}}
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrNotSorted, "Expected ErrNotSorted")
|
||||
require.True(t, ring1.Merge(*ring2) == ErrNotSorted, "Expected ErrNotSorted")
|
||||
|
||||
// Should Merge two rings for different ranges
|
||||
ring2 = New(start, middle, peer2name)
|
||||
ring2.Entries = []*entry{}
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrDifferentSubnets, "Expected ErrDifferentSubnets")
|
||||
require.True(t, ring1.Merge(*ring2) == ErrDifferentRange, "Expected ErrDifferentRange")
|
||||
|
||||
// Cannot Merge newer version of entry I own
|
||||
ring2 = New(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrNewerVersion, "Expected ErrNewerVersion")
|
||||
require.True(t, ring1.Merge(*ring2) == ErrNewerVersion, "Expected ErrNewerVersion")
|
||||
|
||||
// Cannot Merge two entries with same version but different hosts
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name}}
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrInvalidEntry, "Expected ErrInvalidEntry")
|
||||
require.True(t, ring1.Merge(*ring2) == ErrInvalidEntry, "Expected ErrInvalidEntry")
|
||||
|
||||
// Cannot Merge an entry into a range I own
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
||||
wt.AssertTrue(t, ring1.Merge(*ring2) == ErrEntryInMyRange, "Expected ErrEntryInMyRange")
|
||||
require.True(t, ring1.Merge(*ring2) == ErrEntryInMyRange, "Expected ErrEntryInMyRange")
|
||||
}
|
||||
|
||||
func TestMergeMore(t *testing.T) {
|
||||
@@ -231,7 +236,7 @@ func TestMergeMore(t *testing.T) {
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
assertRing := func(ring *Ring, entries entries) {
|
||||
wt.AssertEquals(t, ring.Entries, entries)
|
||||
require.Equal(t, entries, ring.Entries)
|
||||
}
|
||||
|
||||
assertRing(ring1, []*entry{})
|
||||
@@ -243,7 +248,7 @@ func TestMergeMore(t *testing.T) {
|
||||
assertRing(ring2, []*entry{})
|
||||
|
||||
// Check the Merge sends it to the other ring
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
|
||||
@@ -252,7 +257,7 @@ func TestMergeMore(t *testing.T) {
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
|
||||
@@ -263,14 +268,14 @@ func TestMergeMore(t *testing.T) {
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
||||
|
||||
// And Merge back
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
|
||||
// This should be a no-op
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
{Token: middle, Peer: peer1name, Free: 127}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||
@@ -283,19 +288,21 @@ func TestMergeSplit(t *testing.T) {
|
||||
|
||||
// Claim everything for peer2
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255}}
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now grant a split range to peer1
|
||||
ring2.GrantRangeToHost(dot10, dot245, peer1name)
|
||||
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}})
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}}, ring2.Entries)
|
||||
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 10}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 10}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
}
|
||||
|
||||
func TestMergeSplit2(t *testing.T) {
|
||||
@@ -304,19 +311,21 @@ func TestMergeSplit2(t *testing.T) {
|
||||
|
||||
// Claim everything for peer2
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 250}, {Token: dot250, Peer: peer2name, Free: 5}}
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
|
||||
// Now grant a split range to peer1
|
||||
ring2.GrantRangeToHost(dot10, dot245, peer1name)
|
||||
wt.AssertEquals(t, ring2.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}})
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
wt.AssertEquals(t, ring1.Entries, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring2.Entries)
|
||||
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||
{Token: dot10, Peer: peer1name, Free: 235},
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}})
|
||||
wt.AssertEquals(t, ring1.Entries, ring2.Entries)
|
||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring1.Entries)
|
||||
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
}
|
||||
|
||||
// A simple test, very similar to above, but using the marshalling to byte[]s
|
||||
@@ -325,7 +334,7 @@ func TestGossip(t *testing.T) {
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
assertRing := func(ring *Ring, entries entries) {
|
||||
wt.AssertEquals(t, ring.Entries, entries)
|
||||
require.Equal(t, entries, ring.Entries)
|
||||
}
|
||||
|
||||
assertRing(ring1, []*entry{})
|
||||
@@ -337,50 +346,50 @@ func TestGossip(t *testing.T) {
|
||||
assertRing(ring2, []*entry{})
|
||||
|
||||
// Check the Merge sends it to the other ring
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
||||
}
|
||||
|
||||
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []router.PeerName {
|
||||
peers := ring.ChoosePeersToAskForSpace(start, end)
|
||||
require.Equal(t, expected, len(peers))
|
||||
return peers
|
||||
}
|
||||
|
||||
func TestFindFree(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
|
||||
peers := ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
|
||||
// We shouldn't return outselves
|
||||
ring1.ReportFree(map[address.Address]address.Offset{start: 10})
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
|
||||
{Token: middle, Peer: peer1name, Free: 1}}
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 0, "Expected no peers")
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
ring1.assertInvariants()
|
||||
|
||||
// We should return others
|
||||
var peers []router.PeerName
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
|
||||
wt.AssertEquals(t, peers[0], peer2name)
|
||||
peers = assertPeersWithSpace(t, ring1, start, end, 1)
|
||||
require.Equal(t, peer2name, peers[0])
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1},
|
||||
{Token: middle, Peer: peer3name, Free: 1}}
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, middle)
|
||||
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
|
||||
wt.AssertEquals(t, peers[0], peer2name)
|
||||
peers = assertPeersWithSpace(t, ring1, start, middle, 1)
|
||||
require.Equal(t, peer2name, peers[0])
|
||||
|
||||
peers = ring1.ChoosePeersToAskForSpace(middle, end)
|
||||
wt.AssertTrue(t, len(peers) == 1, "Expected one peer")
|
||||
wt.AssertEquals(t, peers[0], peer3name)
|
||||
peers = assertPeersWithSpace(t, ring1, middle, end, 1)
|
||||
require.Equal(t, peer3name, peers[0])
|
||||
|
||||
peers = ring1.ChoosePeersToAskForSpace(start, end)
|
||||
wt.AssertTrue(t, len(peers) == 2, "Expected two peers")
|
||||
assertPeersWithSpace(t, ring1, start, end, 2)
|
||||
ring1.assertInvariants()
|
||||
}
|
||||
|
||||
@@ -390,7 +399,7 @@ func TestReportFree(t *testing.T) {
|
||||
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
|
||||
freespace := make(map[address.Address]address.Offset)
|
||||
for _, r := range ring2.OwnedRanges() {
|
||||
@@ -402,7 +411,7 @@ func TestReportFree(t *testing.T) {
|
||||
func TestMisc(t *testing.T) {
|
||||
ring := New(start, end, peer1name)
|
||||
|
||||
wt.AssertTrue(t, ring.Empty(), "empty")
|
||||
require.True(t, ring.Empty(), "empty")
|
||||
|
||||
ring.ClaimItAll()
|
||||
println(ring.String())
|
||||
@@ -414,7 +423,7 @@ func TestEmptyGossip(t *testing.T) {
|
||||
|
||||
ring1.ClaimItAll()
|
||||
// This used to panic, and it shouldn't
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
}
|
||||
|
||||
func TestMergeOldMessage(t *testing.T) {
|
||||
@@ -422,10 +431,10 @@ func TestMergeOldMessage(t *testing.T) {
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
|
||||
ring1.GrantRangeToHost(middle, end, peer1name)
|
||||
wt.AssertSuccess(t, ring1.Merge(*ring2))
|
||||
require.NoError(t, ring1.Merge(*ring2))
|
||||
}
|
||||
|
||||
func TestSplitRangeAtBeginning(t *testing.T) {
|
||||
@@ -433,33 +442,32 @@ func TestSplitRangeAtBeginning(t *testing.T) {
|
||||
ring2 := New(start, end, peer2name)
|
||||
|
||||
ring1.ClaimItAll()
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
|
||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
}
|
||||
|
||||
func TestOwnedRange(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
ring1.ClaimItAll()
|
||||
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: end}})
|
||||
require.Equal(t, []address.Range{{Start: start, End: end}}, ring1.OwnedRanges())
|
||||
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{Start: start, End: middle}})
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}}, ring1.OwnedRanges())
|
||||
|
||||
ring2 := New(start, end, peer2name)
|
||||
ring2.Merge(*ring1)
|
||||
wt.AssertEquals(t, ring2.OwnedRanges(), []address.Range{{Start: middle, End: end}})
|
||||
require.Equal(t, []address.Range{{Start: middle, End: end}}, ring2.OwnedRanges())
|
||||
|
||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
||||
wt.AssertEquals(t, ring2.OwnedRanges(),
|
||||
[]address.Range{{Start: start, End: middle}, {Start: middle, End: end}})
|
||||
require.Equal(t, []address.Range{{Start: start, End: middle}, {Start: middle, End: end}}, ring2.OwnedRanges())
|
||||
|
||||
ring2.Entries = []*entry{{Token: dot10, Peer: peer2name}, {Token: middle, Peer: peer2name}}
|
||||
wt.AssertEquals(t, ring2.OwnedRanges(),
|
||||
[]address.Range{{Start: start, End: dot10}, {Start: dot10, End: middle},
|
||||
{Start: middle, End: end}})
|
||||
require.Equal(t, []address.Range{{Start: start, End: dot10}, {Start: dot10, End: middle},
|
||||
{Start: middle, End: end}}, ring2.OwnedRanges())
|
||||
|
||||
}
|
||||
|
||||
func TestTransfer(t *testing.T) {
|
||||
@@ -468,7 +476,7 @@ func TestTransfer(t *testing.T) {
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, middle}, {middle, end}})
|
||||
require.Equal(t, []address.Range{{start, middle}, {middle, 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?
|
||||
@@ -477,23 +485,24 @@ func TestTransfer(t *testing.T) {
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
ring1.Transfer(peer2name, peer1name)
|
||||
ring1.GrantRangeToHost(dot10, middle, peer2name)
|
||||
wt.AssertEquals(t, ring1.OwnedRanges(), []address.Range{{start, dot10}, {middle, end}})
|
||||
require.Equal(t, []address.Range{{start, dot10}, {middle, end}}, ring1.OwnedRanges())
|
||||
}
|
||||
|
||||
func TestOwner(t *testing.T) {
|
||||
ring1 := New(start, end, peer1name)
|
||||
wt.AssertTrue(t, ring1.Contains(start), "start should be in ring")
|
||||
wt.AssertFalse(t, ring1.Contains(end), "end should not be in ring")
|
||||
require.True(t, ring1.Contains(start), "start should be in ring")
|
||||
require.False(t, ring1.Contains(end), "end should not be in ring")
|
||||
|
||||
wt.AssertEquals(t, ring1.Owner(start), router.UnknownPeerName)
|
||||
require.Equal(t, router.UnknownPeerName, ring1.Owner(start))
|
||||
|
||||
ring1.ClaimItAll()
|
||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||
wt.AssertEquals(t, ring1.Owner(start), peer1name)
|
||||
wt.AssertEquals(t, ring1.Owner(middle), peer2name)
|
||||
wt.AssertPanic(t, func() {
|
||||
require.Equal(t, peer1name, ring1.Owner(start))
|
||||
require.Equal(t, peer2name, ring1.Owner(middle))
|
||||
require.Panics(t, func() {
|
||||
ring1.Owner(end)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
type addressSlice []address.Address
|
||||
@@ -599,7 +608,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
|
||||
addPeer := func() {
|
||||
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
|
||||
common.Debug.Printf("%s: Adding peer", peer)
|
||||
common.Log.Debugf("%s: Adding peer", peer)
|
||||
nextPeerID++
|
||||
peers = append(peers, peer)
|
||||
rings = append(rings, New(start, end, peer))
|
||||
@@ -641,10 +650,10 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
|
||||
// We need to be in a ~converged ring to rmpeer
|
||||
for _, ring := range rings {
|
||||
wt.AssertSuccess(t, otherRing.Merge(*ring))
|
||||
require.NoError(t, otherRing.Merge(*ring))
|
||||
}
|
||||
|
||||
common.Debug.Printf("%s: transferring from peer %s", otherPeername, peername)
|
||||
common.Log.Debugf("%s: transferring from peer %s", otherPeername, peername)
|
||||
otherRing.Transfer(peername, otherPeername)
|
||||
|
||||
// Remove peer from our state
|
||||
@@ -655,7 +664,7 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
// And now tell everyone about the transfer - rmpeer is
|
||||
// not partition safe
|
||||
for i, ring := range rings {
|
||||
wt.AssertSuccess(t, ring.Merge(*otherRing))
|
||||
require.NoError(t, ring.Merge(*otherRing))
|
||||
theRanges[i] = ring.OwnedRanges()
|
||||
}
|
||||
}
|
||||
@@ -678,13 +687,13 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
size := address.Subtract(rangeToSplit.End, rangeToSplit.Start)
|
||||
ipInRange := address.Add(rangeToSplit.Start, address.Offset(rand.Intn(int(size))))
|
||||
_, peerToGiveTo, _ := randomPeer(-1)
|
||||
common.Debug.Printf("%s: Granting [%v, %v) to %s", ring.Peer, ipInRange, rangeToSplit.End, peerToGiveTo)
|
||||
common.Log.Debugf("%s: Granting [%v, %v) to %s", ring.Peer, ipInRange, rangeToSplit.End, peerToGiveTo)
|
||||
ring.GrantRangeToHost(ipInRange, rangeToSplit.End, peerToGiveTo)
|
||||
|
||||
// Now 'gossip' this to a random host (note, note could be same host as above)
|
||||
otherIndex, _, otherRing := randomPeer(-1)
|
||||
common.Debug.Printf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
|
||||
wt.AssertSuccess(t, otherRing.Merge(*ring))
|
||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
|
||||
require.NoError(t, otherRing.Merge(*ring))
|
||||
|
||||
theRanges[indexWithRanges] = ring.OwnedRanges()
|
||||
theRanges[otherIndex] = otherRing.OwnedRanges()
|
||||
@@ -702,8 +711,8 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
}
|
||||
ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
|
||||
ring2index, _, ring2 := randomPeer(-1)
|
||||
common.Debug.Printf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
|
||||
wt.AssertSuccess(t, ring2.Merge(*ring1))
|
||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
|
||||
require.NoError(t, ring2.Merge(*ring1))
|
||||
theRanges[ring2index] = ring2.OwnedRanges()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type Space struct {
|
||||
|
||||
+77
-76
@@ -3,7 +3,8 @@ package space
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
@@ -31,65 +32,65 @@ func (s *Space) NumFreeAddresses() address.Offset {
|
||||
func TestLowlevel(t *testing.T) {
|
||||
a := []address.Address{}
|
||||
a = add(a, 100, 200)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200})
|
||||
wt.AssertTrue(t, !contains(a, 99), "")
|
||||
wt.AssertTrue(t, contains(a, 100), "")
|
||||
wt.AssertTrue(t, contains(a, 199), "")
|
||||
wt.AssertTrue(t, !contains(a, 200), "")
|
||||
require.Equal(t, []address.Address{100, 200}, a)
|
||||
require.True(t, !contains(a, 99), "")
|
||||
require.True(t, contains(a, 100), "")
|
||||
require.True(t, contains(a, 199), "")
|
||||
require.True(t, !contains(a, 200), "")
|
||||
a = add(a, 700, 800)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 700, 800}, a)
|
||||
a = add(a, 300, 400)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 300, 400, 700, 800}, a)
|
||||
a = add(a, 400, 500)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 700, 800}, a)
|
||||
a = add(a, 600, 700)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 600, 800}, a)
|
||||
a = add(a, 500, 600)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 300, 800}, a)
|
||||
a = subtract(a, 500, 600)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 600, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 600, 800}, a)
|
||||
a = subtract(a, 600, 700)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 500, 700, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 300, 500, 700, 800}, a)
|
||||
a = subtract(a, 400, 500)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 300, 400, 700, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 300, 400, 700, 800}, a)
|
||||
a = subtract(a, 300, 400)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200, 700, 800})
|
||||
require.Equal(t, []address.Address{100, 200, 700, 800}, a)
|
||||
a = subtract(a, 700, 800)
|
||||
wt.AssertEquals(t, a, []address.Address{100, 200})
|
||||
require.Equal(t, []address.Address{100, 200}, a)
|
||||
a = subtract(a, 100, 200)
|
||||
wt.AssertEquals(t, a, []address.Address{})
|
||||
require.Equal(t, []address.Address{}, a)
|
||||
|
||||
s := New()
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(0))
|
||||
require.Equal(t, address.Offset(0), s.NumFreeAddresses())
|
||||
ok, got := s.Allocate(address.NewRange(0, 1000))
|
||||
wt.AssertFalse(t, ok, "allocate in empty space should fail")
|
||||
require.False(t, ok, "allocate in empty space should fail")
|
||||
|
||||
s.Add(100, 100)
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(100))
|
||||
require.Equal(t, address.Offset(100), s.NumFreeAddresses())
|
||||
ok, got = s.Allocate(address.NewRange(0, 1000))
|
||||
wt.AssertTrue(t, ok && got == 100, "allocate")
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(99))
|
||||
wt.AssertNoErr(t, s.Claim(150))
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(98))
|
||||
wt.AssertNoErr(t, s.Free(100))
|
||||
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(99))
|
||||
wt.AssertErrorInterface(t, s.Free(0), (*error)(nil), "free not allocated")
|
||||
wt.AssertErrorInterface(t, s.Free(100), (*error)(nil), "double free")
|
||||
require.True(t, ok && got == 100, "allocate")
|
||||
require.Equal(t, address.Offset(99), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Claim(150))
|
||||
require.Equal(t, address.Offset(98), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Free(100))
|
||||
require.Equal(t, address.Offset(99), s.NumFreeAddresses())
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(0), "free not allocated")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
||||
|
||||
r, ok := s.Donate(address.NewRange(0, 1000))
|
||||
wt.AssertTrue(t, ok && r.Start == 125 && r.Size() == 25, "donate")
|
||||
require.True(t, ok && r.Start == 125 && r.Size() == 25, "donate")
|
||||
|
||||
// test Donate when addresses are scarce
|
||||
s = New()
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
wt.AssertTrue(t, !ok, "donate on empty space should fail")
|
||||
require.True(t, !ok, "donate on empty space should fail")
|
||||
s.Add(0, 3)
|
||||
wt.AssertNoErr(t, s.Claim(0))
|
||||
wt.AssertNoErr(t, s.Claim(2))
|
||||
require.NoError(t, s.Claim(0))
|
||||
require.NoError(t, s.Claim(2))
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
wt.AssertTrue(t, ok && r.Start == 1 && r.End == 2, "donate")
|
||||
require.True(t, ok && r.Start == 1 && r.End == 2, "donate")
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
wt.AssertTrue(t, !ok, "donate should fail")
|
||||
require.True(t, !ok, "donate should fail")
|
||||
}
|
||||
|
||||
func TestSpaceAllocate(t *testing.T) {
|
||||
@@ -106,27 +107,27 @@ func TestSpaceAllocate(t *testing.T) {
|
||||
)
|
||||
|
||||
space1 := makeSpace(start, size)
|
||||
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(20))
|
||||
require.Equal(t, address.Offset(20), space1.NumFreeAddresses())
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr1 := space1.Allocate(address.NewRange(start, size))
|
||||
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
|
||||
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(19))
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
require.Equal(t, address.Offset(19), space1.NumFreeAddresses())
|
||||
space1.assertInvariants()
|
||||
|
||||
_, addr2 := space1.Allocate(address.NewRange(start, size))
|
||||
wt.AssertFalse(t, addr2.String() == testAddr1, "address")
|
||||
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(18))
|
||||
wt.AssertEquals(t, space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}), address.Offset(13))
|
||||
wt.AssertEquals(t, space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}), address.Offset(18))
|
||||
require.False(t, addr2.String() == testAddr1, "address")
|
||||
require.Equal(t, address.Offset(18), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Offset(13), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}))
|
||||
require.Equal(t, address.Offset(18), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}))
|
||||
space1.assertInvariants()
|
||||
|
||||
space1.Free(addr2)
|
||||
space1.assertInvariants()
|
||||
|
||||
wt.AssertErrorInterface(t, space1.Free(addr2), (*error)(nil), "double free")
|
||||
wt.AssertErrorInterface(t, space1.Free(ip(testAddrx)), (*error)(nil), "address not allocated")
|
||||
wt.AssertErrorInterface(t, space1.Free(ip(testAddry)), (*error)(nil), "wrong out of range")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), space1.Free(addr2), "double free")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), space1.Free(ip(testAddrx)), "address not allocated")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), space1.Free(ip(testAddry)), "wrong out of range")
|
||||
|
||||
space1.assertInvariants()
|
||||
}
|
||||
@@ -144,51 +145,51 @@ func TestSpaceFree(t *testing.T) {
|
||||
|
||||
// Check we are prepared to give up the entire space
|
||||
r := space.biggestFreeRange(entireRange)
|
||||
wt.AssertTrue(t, r.Start == ip(testAddr1) && r.Size() == 20, "Wrong space")
|
||||
require.True(t, r.Start == ip(testAddr1) && r.Size() == 20, "Wrong space")
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
ok, _ := space.Allocate(entireRange)
|
||||
wt.AssertTrue(t, ok, "Failed to get address")
|
||||
require.True(t, ok, "Failed to get address")
|
||||
}
|
||||
|
||||
// Check we are full
|
||||
ok, _ := space.Allocate(entireRange)
|
||||
wt.AssertTrue(t, !ok, "Should have failed to get address")
|
||||
require.True(t, !ok, "Should have failed to get address")
|
||||
r, ok = space.Donate(entireRange)
|
||||
wt.AssertTrue(t, r.Size() == 0, "Wrong space")
|
||||
require.True(t, r.Size() == 0, "Wrong space")
|
||||
|
||||
// Free in the middle
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.13")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.13")))
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
wt.AssertTrue(t, r.Start == ip("10.0.3.13") && r.Size() == 1, "Wrong space")
|
||||
require.True(t, r.Start == ip("10.0.3.13") && r.Size() == 1, "Wrong space")
|
||||
|
||||
// Free one at the end
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.23")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.23")))
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
wt.AssertTrue(t, r.Start == ip("10.0.3.23") && r.Size() == 1, "Wrong space")
|
||||
require.True(t, r.Start == ip("10.0.3.23") && r.Size() == 1, "Wrong space")
|
||||
|
||||
// Now free a few at the end
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.22")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.21")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.22")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.21")))
|
||||
|
||||
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(4))
|
||||
require.Equal(t, address.Offset(4), space.NumFreeAddresses())
|
||||
|
||||
// Now get the biggest free space; should be 3.21
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
wt.AssertTrue(t, r.Start == ip("10.0.3.21") && r.Size() == 3, "Wrong space")
|
||||
require.True(t, r.Start == ip("10.0.3.21") && r.Size() == 3, "Wrong space")
|
||||
|
||||
// Now free a few in the middle
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.12")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.11")))
|
||||
wt.AssertSuccess(t, space.Free(ip("10.0.3.10")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.12")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.11")))
|
||||
require.NoError(t, space.Free(ip("10.0.3.10")))
|
||||
|
||||
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(7))
|
||||
require.Equal(t, address.Offset(7), space.NumFreeAddresses())
|
||||
|
||||
// Now get the biggest free space; should be 3.21
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
wt.AssertTrue(t, r.Start == ip("10.0.3.10") && r.Size() == 4, "Wrong space")
|
||||
require.True(t, r.Start == ip("10.0.3.10") && r.Size() == 4, "Wrong space")
|
||||
|
||||
wt.AssertEquals(t, space.OwnedRanges(), []address.Range{address.Range{Start: ip("10.0.3.4"), End: ip("10.0.3.24")}})
|
||||
require.Equal(t, []address.Range{{Start: ip("10.0.3.4"), End: ip("10.0.3.24")}}, space.OwnedRanges())
|
||||
}
|
||||
|
||||
func TestDonateSimple(t *testing.T) {
|
||||
@@ -207,10 +208,10 @@ func TestDonateSimple(t *testing.T) {
|
||||
// Empty space set should split in two and give me the second half
|
||||
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
|
||||
numGivenUp := r.Size()
|
||||
wt.AssertTrue(t, ok, "Donate result")
|
||||
wt.AssertEqualString(t, r.Start.String(), "10.0.1.24", "Invalid start")
|
||||
wt.AssertEquals(t, numGivenUp, address.Offset(size/2))
|
||||
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(size/2))
|
||||
require.True(t, ok, "Donate result")
|
||||
require.Equal(t, "10.0.1.24", r.Start.String(), "Invalid start")
|
||||
require.Equal(t, address.Offset(size/2), numGivenUp)
|
||||
require.Equal(t, address.Offset(size/2), ps1.NumFreeAddresses())
|
||||
|
||||
// Now check we can give the rest up.
|
||||
count := 0 // count to avoid infinite loop
|
||||
@@ -221,8 +222,8 @@ func TestDonateSimple(t *testing.T) {
|
||||
}
|
||||
numGivenUp += r.Size()
|
||||
}
|
||||
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(0))
|
||||
wt.AssertEquals(t, numGivenUp, address.Offset(size))
|
||||
require.Equal(t, address.Offset(0), ps1.NumFreeAddresses())
|
||||
require.Equal(t, address.Offset(size), numGivenUp)
|
||||
}
|
||||
|
||||
func TestDonateHard(t *testing.T) {
|
||||
@@ -236,27 +237,27 @@ func TestDonateHard(t *testing.T) {
|
||||
spaceset := makeSpace(start, size)
|
||||
for i := address.Offset(0); i < size; i++ {
|
||||
ok, _ := spaceset.Allocate(address.NewRange(start, size))
|
||||
wt.AssertTrue(t, ok, "Failed to get IP!")
|
||||
require.True(t, ok, "Failed to get IP!")
|
||||
}
|
||||
|
||||
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(0))
|
||||
require.Equal(t, address.Offset(0), spaceset.NumFreeAddresses())
|
||||
|
||||
// Now free all but the last address
|
||||
// this will force us to split the free list
|
||||
for i := address.Offset(0); i < size-1; i++ {
|
||||
wt.AssertSuccess(t, spaceset.Free(address.Add(start, i)))
|
||||
require.NoError(t, spaceset.Free(address.Add(start, i)))
|
||||
}
|
||||
|
||||
// Now split
|
||||
newRange, ok := spaceset.Donate(address.NewRange(start, size))
|
||||
wt.AssertTrue(t, ok, "GiveUpSpace result")
|
||||
wt.AssertEquals(t, newRange.Start, ip("10.0.1.23"))
|
||||
wt.AssertEquals(t, newRange.Size(), address.Offset(24))
|
||||
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(23))
|
||||
require.True(t, ok, "GiveUpSpace result")
|
||||
require.Equal(t, ip("10.0.1.23"), newRange.Start)
|
||||
require.Equal(t, address.Offset(24), newRange.Size())
|
||||
require.Equal(t, address.Offset(23), spaceset.NumFreeAddresses())
|
||||
|
||||
//Space set should now have 2 spaces
|
||||
expected := New()
|
||||
expected.Add(start, 23)
|
||||
expected.ours = add(nil, ip("10.0.1.47"), ip("10.0.1.48"))
|
||||
wt.AssertEquals(t, spaceset, expected)
|
||||
require.Equal(t, expected, spaceset)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Paxos *paxos.Status
|
||||
Range string
|
||||
DefaultSubnet string
|
||||
Entries []EntryStatus
|
||||
PendingClaims []ClaimStatus
|
||||
PendingAllocates []string
|
||||
}
|
||||
|
||||
type EntryStatus struct {
|
||||
Token string
|
||||
Peer string
|
||||
Version uint32
|
||||
}
|
||||
|
||||
type ClaimStatus struct {
|
||||
Ident string
|
||||
Address address.Address
|
||||
}
|
||||
|
||||
func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
if allocator == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var paxosStatus *paxos.Status
|
||||
if allocator.paxosTicker != nil {
|
||||
paxosStatus = paxos.NewStatus(allocator.paxos)
|
||||
}
|
||||
|
||||
resultChan := make(chan *Status)
|
||||
allocator.actionChan <- func() {
|
||||
resultChan <- &Status{
|
||||
paxosStatus,
|
||||
allocator.universe.String(),
|
||||
defaultSubnet.String(),
|
||||
newEntryStatusSlice(allocator),
|
||||
newClaimStatusSlice(allocator),
|
||||
newAllocateIdentSlice(allocator)}
|
||||
}
|
||||
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
func newEntryStatusSlice(allocator *Allocator) []EntryStatus {
|
||||
var slice []EntryStatus
|
||||
|
||||
if allocator.ring.Empty() {
|
||||
return slice
|
||||
}
|
||||
|
||||
for _, entry := range allocator.ring.Entries {
|
||||
slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version})
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
func newClaimStatusSlice(allocator *Allocator) []ClaimStatus {
|
||||
var slice []ClaimStatus
|
||||
for _, op := range allocator.pendingClaims {
|
||||
claim := op.(*claim)
|
||||
slice = append(slice, ClaimStatus{claim.ident, claim.addr})
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func newAllocateIdentSlice(allocator *Allocator) []string {
|
||||
var slice []string
|
||||
for _, op := range allocator.pendingAllocates {
|
||||
allocate := op.(*allocate)
|
||||
slice = append(slice, allocate.ident)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
+22
-138
@@ -7,10 +7,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
|
||||
type mockMessage struct {
|
||||
@@ -33,7 +33,7 @@ func toStringArray(messages []mockMessage) []string {
|
||||
|
||||
type mockGossipComms struct {
|
||||
sync.RWMutex
|
||||
t *testing.T
|
||||
*testing.T
|
||||
name string
|
||||
messages []mockMessage
|
||||
}
|
||||
@@ -53,11 +53,11 @@ func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error {
|
||||
defer m.Unlock()
|
||||
buf := []byte{}
|
||||
if len(m.messages) == 0 {
|
||||
m.Fatalf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf)
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf))
|
||||
} else if msg := m.messages[0]; msg.dst != router.UnknownPeerName {
|
||||
m.Fatalf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst)
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst))
|
||||
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
|
||||
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf)
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf))
|
||||
} else {
|
||||
// Swallow this message
|
||||
m.messages = m.messages[1:]
|
||||
@@ -77,24 +77,19 @@ func equalByteBuffer(a, b []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) Fatalf(format string, args ...interface{}) {
|
||||
// this sometimes hangs: wt.Fatalf(m.t, args...)
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
if len(m.messages) == 0 {
|
||||
m.Fatalf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf)
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf))
|
||||
} else if msg := m.messages[0]; msg.dst == router.UnknownPeerName {
|
||||
m.Fatalf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName)
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName))
|
||||
} else if msg.dst != dstPeerName {
|
||||
m.Fatalf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName)
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName))
|
||||
} else if buf[0] != msg.msgType {
|
||||
m.Fatalf("%s: Expected Gossip message of type %d but got type %d", m.name, msg.msgType, buf[0])
|
||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message of type %d but got type %d", m.name, msg.msgType, buf[0]))
|
||||
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf[1:]) {
|
||||
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf[1:])
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf[1:]))
|
||||
} else {
|
||||
// Swallow this message
|
||||
m.messages = m.messages[1:]
|
||||
@@ -122,7 +117,7 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
|
||||
m := alloc.gossip.(*mockGossipComms)
|
||||
m.RLock()
|
||||
if len(m.messages) > 0 {
|
||||
wt.Fatalf(m.t, "%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages)
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages))
|
||||
}
|
||||
m.RUnlock()
|
||||
}
|
||||
@@ -147,7 +142,7 @@ func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, addres
|
||||
|
||||
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) (*Allocator, address.Range) {
|
||||
alloc, subnet := makeAllocator(name, universeCIDR, quorum)
|
||||
gossip := &mockGossipComms{t: t, name: name}
|
||||
gossip := &mockGossipComms{T: t, name: name}
|
||||
alloc.SetInterfaces(gossip)
|
||||
alloc.Start()
|
||||
return alloc, subnet
|
||||
@@ -177,14 +172,14 @@ func AssertSent(t *testing.T, ch <-chan bool) {
|
||||
case <-ch:
|
||||
// This case is ok
|
||||
case <-timeout:
|
||||
wt.Fatalf(t, "Nothing sent on channel")
|
||||
require.FailNow(t, "Nothing sent on channel")
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNothingSent(t *testing.T, ch <-chan bool) {
|
||||
select {
|
||||
case val := <-ch:
|
||||
wt.Fatalf(t, "Unexpected value on channel: %t", val)
|
||||
require.FailNow(t, fmt.Sprintf("Unexpected value on channel: %v", val))
|
||||
default:
|
||||
// no message received
|
||||
}
|
||||
@@ -193,125 +188,14 @@ func AssertNothingSent(t *testing.T, ch <-chan bool) {
|
||||
func AssertNothingSentErr(t *testing.T, ch <-chan error) {
|
||||
select {
|
||||
case val := <-ch:
|
||||
wt.Fatalf(t, "Unexpected value on channel: %t", val)
|
||||
require.FailNow(t, fmt.Sprintf("Unexpected value on channel: %v", val))
|
||||
default:
|
||||
// no message received
|
||||
}
|
||||
}
|
||||
|
||||
// Router to convey gossip from one gossiper to another, for testing
|
||||
type unicastMessage struct {
|
||||
sender router.PeerName
|
||||
buf []byte
|
||||
}
|
||||
type broadcastMessage struct {
|
||||
data router.GossipData
|
||||
}
|
||||
type exitMessage struct {
|
||||
exitChan chan struct{}
|
||||
}
|
||||
type flushMessage struct {
|
||||
flushChan chan struct{}
|
||||
}
|
||||
|
||||
type TestGossipRouter struct {
|
||||
gossipChans map[router.PeerName]chan interface{}
|
||||
loss float32 // 0.0 means no loss
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) GossipBroadcast(update router.GossipData) error {
|
||||
for _, gossipChan := range grouter.gossipChans {
|
||||
select {
|
||||
case gossipChan <- broadcastMessage{data: update}:
|
||||
default: // drop the message if we cannot send it
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) flush() {
|
||||
for _, gossipChan := range grouter.gossipChans {
|
||||
flushChan := make(chan struct{})
|
||||
gossipChan <- flushMessage{flushChan: flushChan}
|
||||
<-flushChan
|
||||
}
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) removePeer(peer router.PeerName) {
|
||||
gossipChan := grouter.gossipChans[peer]
|
||||
resultChan := make(chan struct{})
|
||||
gossipChan <- exitMessage{exitChan: resultChan}
|
||||
<-resultChan
|
||||
delete(grouter.gossipChans, peer)
|
||||
}
|
||||
|
||||
type TestGossipRouterClient struct {
|
||||
router *TestGossipRouter
|
||||
sender router.PeerName
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) run(gossiper router.Gossiper, gossipChan chan interface{}) {
|
||||
gossipTimer := time.Tick(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case gossip := <-gossipChan:
|
||||
switch message := gossip.(type) {
|
||||
case exitMessage:
|
||||
close(message.exitChan)
|
||||
return
|
||||
|
||||
case flushMessage:
|
||||
close(message.flushChan)
|
||||
|
||||
case unicastMessage:
|
||||
if rand.Float32() > (1.0 - grouter.loss) {
|
||||
continue
|
||||
}
|
||||
if err := gossiper.OnGossipUnicast(message.sender, message.buf); err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", message.sender, err))
|
||||
}
|
||||
|
||||
case broadcastMessage:
|
||||
if rand.Float32() > (1.0 - grouter.loss) {
|
||||
continue
|
||||
}
|
||||
for _, msg := range message.data.Encode() {
|
||||
if _, err := gossiper.OnGossipBroadcast(msg); err != nil {
|
||||
panic(fmt.Sprintf("Error doing gossip broadcast: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-gossipTimer:
|
||||
grouter.GossipBroadcast(gossiper.Gossip())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
|
||||
gossipChan := make(chan interface{}, 100)
|
||||
|
||||
go grouter.run(gossiper, gossipChan)
|
||||
|
||||
grouter.gossipChans[sender] = gossipChan
|
||||
return TestGossipRouterClient{grouter, sender}
|
||||
}
|
||||
|
||||
func (client TestGossipRouterClient) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
||||
select {
|
||||
case client.router.gossipChans[dstPeerName] <- unicastMessage{sender: client.sender, buf: buf}:
|
||||
default: // drop the message if we cannot send it
|
||||
common.Error.Printf("Dropping message")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client TestGossipRouterClient) GossipBroadcast(update router.GossipData) error {
|
||||
return client.router.GossipBroadcast(update)
|
||||
}
|
||||
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRouter, address.Range) {
|
||||
|
||||
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan interface{}), 0.0}
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestRouter, address.Range) {
|
||||
gossipRouter := gossip.NewTestRouter(0.0)
|
||||
allocs := make([]*Allocator, size)
|
||||
var subnet address.Range
|
||||
|
||||
@@ -319,13 +203,13 @@ func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRou
|
||||
var alloc *Allocator
|
||||
alloc, subnet = makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
|
||||
cidr, uint(size/2+1))
|
||||
alloc.SetInterfaces(gossipRouter.connect(alloc.ourName, alloc))
|
||||
alloc.SetInterfaces(gossipRouter.Connect(alloc.ourName, alloc))
|
||||
alloc.Start()
|
||||
allocs[i] = alloc
|
||||
}
|
||||
|
||||
gossipRouter.GossipBroadcast(allocs[size-1].Gossip())
|
||||
gossipRouter.flush()
|
||||
allocs[size-1].gossip.GossipBroadcast(allocs[size-1].Gossip())
|
||||
gossipRouter.Flush()
|
||||
return allocs, gossipRouter, subnet
|
||||
}
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// A zone record
|
||||
// Note: we will try to keep all the zone records information is concentrated here. Maybe not all queries will
|
||||
// use things like priorities or weights, but we do not want to create a hierarchy for dealing with all
|
||||
// possible queries...
|
||||
type ZoneRecord interface {
|
||||
Name() string // The name for this IP
|
||||
IP() net.IP // The IP (v4) address
|
||||
Priority() int // The priority
|
||||
Weight() int // The weight
|
||||
TTL() int // TTL
|
||||
}
|
||||
|
||||
type ZoneLookup interface {
|
||||
// Lookup for a name
|
||||
LookupName(name string) ([]ZoneRecord, error)
|
||||
// Lookup for an address
|
||||
LookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
}
|
||||
|
||||
type ZoneLookupFunc func(target string) ([]ZoneRecord, error)
|
||||
|
||||
type ZoneObservable interface {
|
||||
// Observe anything that affects a particular name in the zone
|
||||
ObserveName(name string, observer ZoneRecordObserver) error
|
||||
// Observe anything that affects a particular IP in the zone
|
||||
ObserveInaddr(inaddr string, observer ZoneRecordObserver) error
|
||||
}
|
||||
|
||||
type ZoneObserverFunc func(string, ZoneRecordObserver) error
|
||||
type ZoneRecordObserver func()
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
// A basic record struct (satisfying the ZoneRecord interface)
|
||||
type Record struct {
|
||||
name string
|
||||
ip net.IP
|
||||
priority int
|
||||
weight int
|
||||
ttl int
|
||||
}
|
||||
|
||||
func (r Record) Name() string { return r.name }
|
||||
func (r Record) IP() net.IP { return r.ip }
|
||||
func (r Record) Priority() int { return r.priority }
|
||||
func (r Record) Weight() int { return r.weight }
|
||||
func (r Record) TTL() int { return r.ttl }
|
||||
|
||||
func (r Record) String() string {
|
||||
var buf bytes.Buffer
|
||||
if len(r.Name()) > 0 {
|
||||
fmt.Fprintf(&buf, "%s", r.Name())
|
||||
}
|
||||
if !r.IP().IsUnspecified() {
|
||||
fmt.Fprintf(&buf, "[%s]", r.IP())
|
||||
}
|
||||
if r.Priority() > 0 {
|
||||
fmt.Fprintf(&buf, "/P:%d", r.Priority())
|
||||
}
|
||||
if r.Weight() > 0 {
|
||||
fmt.Fprintf(&buf, "/W:%d", r.Weight())
|
||||
}
|
||||
if r.TTL() > 0 {
|
||||
fmt.Fprintf(&buf, "/TTL:%d", r.TTL())
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
func newParseError(reason string, addr string) *net.ParseError {
|
||||
return &net.ParseError{Type: reason, Text: addr}
|
||||
}
|
||||
|
||||
// simple IPv4 type that can be used as a key in a map (in contrast with net.IP), used for sets of IPs, etc...
|
||||
type IPv4 [4]byte
|
||||
|
||||
func (ip IPv4) toNetIP() net.IP { return net.IPv4(ip[0], ip[1], ip[2], ip[3]) }
|
||||
func (ip IPv4) String() string { return ip.toNetIP().String() }
|
||||
func ipToIPv4(nip net.IP) IPv4 { ip := nip.To4(); return IPv4([4]byte{ip[0], ip[1], ip[2], ip[3]}) }
|
||||
|
||||
// Parse an address (eg, "10.13.12.11") and return the corresponding IPv4 (eg, "10.13.12.11")
|
||||
func addrToIPv4(addr string) (IPv4, error) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
return IPv4{}, newParseError("IP address", addr)
|
||||
}
|
||||
return ipToIPv4(ip), nil
|
||||
}
|
||||
|
||||
// Parse a reverse address (eg, "11.12.13.10.in-addr.arpa.") and return the corresponding IPv4 (eg, "10.13.12.11")
|
||||
func raddrToIPv4(addr string) (IPv4, error) {
|
||||
l := len(addr)
|
||||
if l < rdnsDomainLen+1 {
|
||||
return IPv4{}, newParseError("too short reverse IP address", addr)
|
||||
}
|
||||
|
||||
suffixLen := l - rdnsDomainLen
|
||||
suffix := addr[suffixLen+1:]
|
||||
if suffix != RDNSDomain {
|
||||
return IPv4{}, newParseError("suffix of reverse IP address", addr)
|
||||
}
|
||||
ipStr := addr[:suffixLen]
|
||||
revIP := net.ParseIP(ipStr)
|
||||
if revIP == nil {
|
||||
return IPv4{}, newParseError("reverse IP address", addr)
|
||||
}
|
||||
|
||||
revIP4 := revIP.To4()
|
||||
return IPv4([4]byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}), nil
|
||||
}
|
||||
|
||||
func raddrToIP(addr string) (net.IP, error) {
|
||||
r, err := raddrToIPv4(addr)
|
||||
if err != nil {
|
||||
return net.IP{}, err
|
||||
}
|
||||
return r.toNetIP(), nil
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func TestAddrs(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestAddrs starting")
|
||||
|
||||
ip, err := addrToIPv4("10.13.12.11")
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
|
||||
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-addr.arpa.")
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
|
||||
|
||||
// some malformed addresses
|
||||
ip, err = addrToIPv4("10.13.12")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
ip, err = addrToIPv4("10.13.AA.12")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-axxx.arpa.")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-addr")
|
||||
wt.AssertTrue(t, err != nil, "when parsing malformed address")
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/heap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidCapacity = errors.New("Invalid cache capacity")
|
||||
errCouldNotResolve = errors.New("Could not resolve")
|
||||
errTimeout = errors.New("Timeout while waiting for resolution")
|
||||
errNoLocalReplies = errors.New("No local replies")
|
||||
)
|
||||
|
||||
const (
|
||||
defPendingTimeout int = 5 // timeout for a resolution
|
||||
)
|
||||
|
||||
const nullTTL = 0 // a null TTL
|
||||
|
||||
type entryStatus uint8
|
||||
|
||||
const (
|
||||
stPending entryStatus = iota // someone is waiting for the resolution
|
||||
stResolved entryStatus = iota // resolved
|
||||
)
|
||||
|
||||
var statusToString = map[entryStatus]string{
|
||||
stPending: "pending",
|
||||
stResolved: "resolved",
|
||||
}
|
||||
|
||||
const (
|
||||
CacheNoLocalReplies uint8 = 1 << iota // not found in local network (stored in the cache so we skip another local lookup or some time)
|
||||
)
|
||||
|
||||
// a cache entry
|
||||
type cacheEntry struct {
|
||||
Status entryStatus // status of the entry
|
||||
Flags uint8 // some extra flags
|
||||
ReplyLen int
|
||||
|
||||
question dns.Question
|
||||
protocol dnsProtocol
|
||||
reply dns.Msg
|
||||
|
||||
validUntil time.Time // obtained from the reply and stored here for convenience/speed
|
||||
putTime time.Time
|
||||
|
||||
index int // for fast lookups in the heap
|
||||
}
|
||||
|
||||
func newCacheEntry(question *dns.Question, now time.Time) *cacheEntry {
|
||||
e := &cacheEntry{
|
||||
Status: stPending,
|
||||
validUntil: now.Add(time.Second * time.Duration(defPendingTimeout)),
|
||||
question: *question,
|
||||
index: -1,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// Get a copy of the reply stored in the entry, but with some values adjusted like the TTL
|
||||
func (e *cacheEntry) getReply(request *dns.Msg, maxLen int, now time.Time) (*dns.Msg, error) {
|
||||
if e.Status != stResolved {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if the reply has expired or is invalid, force the caller to start a new resolution
|
||||
if e.hasExpired(now) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
return nil, errNoLocalReplies
|
||||
}
|
||||
|
||||
if e.ReplyLen >= maxLen {
|
||||
Debug.Printf("[cache msgid %d] returning truncated reponse: %d > %d", request.MsgHdr.Id, e.ReplyLen, maxLen)
|
||||
return makeTruncatedReply(request), nil
|
||||
}
|
||||
|
||||
// create a copy of the reply, with values for this particular query
|
||||
reply := e.reply.Copy()
|
||||
reply.SetReply(request)
|
||||
|
||||
// adjust the TTLs
|
||||
passedSecs := uint32(now.Sub(e.putTime).Seconds())
|
||||
for _, rr := range reply.Answer {
|
||||
hdr := rr.Header()
|
||||
ttl := hdr.Ttl
|
||||
if passedSecs < ttl {
|
||||
hdr.Ttl = ttl - passedSecs
|
||||
} else {
|
||||
return nil, nil // it is expired: do not spend more time and return nil...
|
||||
}
|
||||
}
|
||||
|
||||
reply.Rcode = e.reply.Rcode
|
||||
reply.Authoritative = true
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (e cacheEntry) hasExpired(now time.Time) bool {
|
||||
return e.validUntil.Before(now) || e.validUntil == now
|
||||
}
|
||||
|
||||
// set the reply for the entry
|
||||
// returns True if the entry has changed the validUntil time
|
||||
func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Time) bool {
|
||||
var prevValidUntil time.Time
|
||||
if e.Status == stResolved {
|
||||
if reply != nil {
|
||||
Debug.Printf("[cache msgid %d] replacing response in cache", reply.MsgHdr.Id)
|
||||
}
|
||||
prevValidUntil = e.validUntil
|
||||
}
|
||||
|
||||
// make sure we do not overwrite noLocalReplies entries
|
||||
if flags&CacheNoLocalReplies != 0 {
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if ttl != nullTTL {
|
||||
e.validUntil = now.Add(time.Second * time.Duration(ttl))
|
||||
} else if reply != nil {
|
||||
// calculate the validUntil from the reply TTL
|
||||
var minTTL uint32 = math.MaxUint32
|
||||
for _, rr := range reply.Answer {
|
||||
ttl := rr.Header().Ttl
|
||||
if ttl < minTTL {
|
||||
minTTL = ttl // TODO: improve the minTTL calculation (maybe we should skip some RRs)
|
||||
}
|
||||
}
|
||||
e.validUntil = now.Add(time.Second * time.Duration(minTTL))
|
||||
} else {
|
||||
Warning.Printf("[cache] no valid TTL could be calculated")
|
||||
}
|
||||
|
||||
e.Status = stResolved
|
||||
e.Flags = flags
|
||||
e.putTime = now
|
||||
|
||||
if reply != nil {
|
||||
e.reply = *reply.Copy()
|
||||
e.ReplyLen = reply.Len()
|
||||
}
|
||||
|
||||
return (prevValidUntil != e.validUntil)
|
||||
}
|
||||
|
||||
func (e *cacheEntry) String() string {
|
||||
var buf bytes.Buffer
|
||||
q := e.question
|
||||
fmt.Fprintf(&buf, "'%s'[%s]: ", q.Name, dns.TypeToString[q.Qtype])
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
fmt.Fprintf(&buf, "neg-local")
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%s", statusToString[e.Status])
|
||||
}
|
||||
fmt.Fprintf(&buf, "(%d bytes)", e.ReplyLen)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// An entriesPtrHeap is a min-heap of cache entries.
|
||||
type entriesPtrsHeap []*cacheEntry
|
||||
|
||||
func (h entriesPtrsHeap) Len() int { return len(h) }
|
||||
func (h entriesPtrsHeap) Less(i, j int) bool { return h[i].validUntil.Before(h[j].validUntil) }
|
||||
func (h entriesPtrsHeap) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
h[i].index = i
|
||||
h[j].index = j
|
||||
}
|
||||
|
||||
func (h *entriesPtrsHeap) Push(x interface{}) {
|
||||
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||
// not just its contents.
|
||||
n := len(*h)
|
||||
entry := x.(*cacheEntry)
|
||||
entry.index = n
|
||||
*h = append(*h, entry)
|
||||
}
|
||||
|
||||
func (h *entriesPtrsHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
item.index = -1 // for safety
|
||||
*h = old[0 : n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The cache interface
|
||||
type ZoneCache interface {
|
||||
// Get from the cache
|
||||
Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error)
|
||||
// Put in the cache
|
||||
Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int
|
||||
// Remove from the cache
|
||||
Remove(question *dns.Question)
|
||||
// Purge all expired entries from the cache
|
||||
Purge()
|
||||
// Remove all entries
|
||||
Clear()
|
||||
// Return the cache length
|
||||
Len() int
|
||||
// Return the max capacity
|
||||
Capacity() int
|
||||
}
|
||||
|
||||
type cacheKey dns.Question
|
||||
type entries map[cacheKey]*cacheEntry
|
||||
|
||||
// Cache is a thread-safe fixed capacity LRU cache.
|
||||
type Cache struct {
|
||||
capacity int
|
||||
entries entries
|
||||
entriesH entriesPtrsHeap // len(entriesH) <= len(entries), as pending entries can be in entries but not in entriesH
|
||||
clock clock.Clock
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCache creates a cache of the given capacity
|
||||
func NewCache(capacity int, clk clock.Clock) (*Cache, error) {
|
||||
if capacity <= 0 {
|
||||
return nil, errInvalidCapacity
|
||||
}
|
||||
c := &Cache{
|
||||
capacity: capacity,
|
||||
entries: make(entries, capacity),
|
||||
clock: clk,
|
||||
}
|
||||
|
||||
if c.clock == nil {
|
||||
c.clock = clock.New()
|
||||
}
|
||||
|
||||
heap.Init(&c.entriesH)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Clear removes all the entries in the cache
|
||||
func (c *Cache) Clear() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.entries = make(entries, c.capacity)
|
||||
heap.Init(&c.entriesH)
|
||||
}
|
||||
|
||||
// Purge removes the old elements in the cache
|
||||
func (c *Cache) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
for i, entry := range c.entriesH {
|
||||
if entry.hasExpired(now) {
|
||||
heap.Remove(&c.entriesH, i)
|
||||
delete(c.entries, cacheKey(entry.question))
|
||||
} else {
|
||||
return // all remaining entries must be still valid...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a reply to the cache.
|
||||
// When `ttl` is equal to `nullTTL`, the cache entry will be valid until the closest TTL in the `reply`
|
||||
func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
question := request.Question[0]
|
||||
key := cacheKey(question)
|
||||
ent, found := c.entries[key]
|
||||
if found {
|
||||
updated := ent.setReply(reply, ttl, flags, now)
|
||||
if updated {
|
||||
heap.Fix(&c.entriesH, ent.index)
|
||||
}
|
||||
} else {
|
||||
// If we will add a new item and the capacity has been exceeded, make some room...
|
||||
if len(c.entriesH) >= c.capacity {
|
||||
lowestEntry := heap.Pop(&c.entriesH).(*cacheEntry)
|
||||
delete(c.entries, cacheKey(lowestEntry.question))
|
||||
}
|
||||
ent = newCacheEntry(&question, now)
|
||||
ent.setReply(reply, ttl, flags, now)
|
||||
heap.Push(&c.entriesH, ent)
|
||||
c.entries[key] = ent
|
||||
}
|
||||
return ent.ReplyLen
|
||||
}
|
||||
|
||||
// Look up for a question's reply from the cache.
|
||||
// If no reply is stored in the cache, it returns a `nil` reply and no error.
|
||||
func (c *Cache) Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
question := request.Question[0]
|
||||
key := cacheKey(question)
|
||||
if ent, found := c.entries[key]; found {
|
||||
reply, err = ent.getReply(request, maxLen, now)
|
||||
if ent.hasExpired(now) {
|
||||
Debug.Printf("[cache msgid %d] expired: removing", request.MsgHdr.Id)
|
||||
if ent.index > 0 {
|
||||
heap.Remove(&c.entriesH, ent.index)
|
||||
}
|
||||
delete(c.entries, key)
|
||||
reply = nil
|
||||
}
|
||||
} else {
|
||||
// we are the first asking for this name: create an entry with no reply... the caller must wait
|
||||
Debug.Printf("[cache msgid %d] addind in pending state", request.MsgHdr.Id)
|
||||
c.entries[key] = newCacheEntry(&question, now)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the provided question from the cache.
|
||||
func (c *Cache) Remove(question *dns.Question) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
key := cacheKey(*question)
|
||||
if entry, found := c.entries[key]; found {
|
||||
Debug.Printf("[cache] removing %s-response for '%s'", dns.TypeToString[question.Qtype], question.Name)
|
||||
if entry.index > 0 {
|
||||
heap.Remove(&c.entriesH, entry.index)
|
||||
}
|
||||
delete(c.entries, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of entries in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return len(c.entries)
|
||||
}
|
||||
|
||||
// Return the max capacity
|
||||
func (c *Cache) Capacity() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return c.capacity
|
||||
}
|
||||
|
||||
// Return the max capacity
|
||||
func (c *Cache) String() string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, entry := range c.entries {
|
||||
fmt.Fprintf(&buf, "%s\n", entry)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// Check that the cache keeps its intended capacity constant
|
||||
func TestCacheLength(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestCacheLength starting")
|
||||
|
||||
const cacheLen = 128
|
||||
|
||||
l, err := NewCache(cacheLen, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
insTime := time.Now()
|
||||
|
||||
t.Logf("Inserting 256 questions in the cache at '%s', with TTL from 0 to 255", insTime)
|
||||
for i := 0; i < cacheLen*2; i++ {
|
||||
name := fmt.Sprintf("name%d", i)
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion(name, dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
|
||||
question := &questionMsg.Question[0]
|
||||
|
||||
ip := net.ParseIP(fmt.Sprintf("10.0.1.%d", i))
|
||||
records := []ZoneRecord{Record{name, ip, 0, 0, 0}}
|
||||
|
||||
reply := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
reply.Answer[0].Header().Ttl = uint32(i)
|
||||
|
||||
l.Put(questionMsg, reply, 0, 0)
|
||||
}
|
||||
|
||||
wt.AssertEqualInt(t, l.Len(), cacheLen, "cache length")
|
||||
|
||||
minExpectedTime := insTime.Add(time.Duration(cacheLen) * time.Second)
|
||||
t.Logf("Checking all remaining entries expire after insert_time + %d secs='%s'", cacheLen, minExpectedTime)
|
||||
for _, entry := range l.entries {
|
||||
if entry.validUntil.Before(minExpectedTime) {
|
||||
t.Fatalf("Entry valid until %s", entry.validUntil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the cache entries are ok
|
||||
func TestCacheEntries(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestCacheEntries starting")
|
||||
|
||||
Info.Println("Checking cache consistency")
|
||||
|
||||
const cacheLen = 128
|
||||
clk := clock.NewMock()
|
||||
|
||||
l, err := NewCache(cacheLen, clk)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion("some.name", dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
|
||||
question := &questionMsg.Question[0]
|
||||
|
||||
t.Logf("Trying to get a name")
|
||||
resp, err := l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did not expect a reponse from Get() yet")
|
||||
}
|
||||
t.Logf("Trying to get it again")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did not expect a reponse from Get() yet")
|
||||
}
|
||||
|
||||
t.Logf("Inserting the reply")
|
||||
records := []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.1"), 0, 0, 0}}
|
||||
reply1 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, reply1, nullTTL, 0)
|
||||
|
||||
t.Logf("Checking we can Get() the reply now")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
ttlGet1 := resp.Answer[0].Header().Ttl
|
||||
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
t.Logf("Checking that a second Get(), after 1 second, gets the same result, but with reduced TTL")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from a second Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
ttlGet2 := resp.Answer[0].Header().Ttl
|
||||
wt.AssertEqualInt(t, int(ttlGet1-ttlGet2), 1, "TTL difference")
|
||||
|
||||
clk.Add(time.Duration(DefaultLocalTTL) * time.Second)
|
||||
t.Logf("Checking that a third Get(), after %d second, gets no result", DefaultLocalTTL)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did NOT expect a reponse from the second Get()")
|
||||
}
|
||||
|
||||
t.Logf("Checking that an Remove() results in Get() returning nothing")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
|
||||
replyTemp := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, replyTemp, nullTTL, 0)
|
||||
lenBefore := l.Len()
|
||||
l.Remove(question)
|
||||
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length")
|
||||
l.Remove(question) // do it again: should have no effect...
|
||||
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length")
|
||||
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp == nil, "reponse from the Get() after a Remove()")
|
||||
|
||||
t.Logf("Inserting a two replies for the same query")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.2"), 0, 0, 0}}
|
||||
reply2 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, reply2, nullTTL, 0)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.3"), 0, 0, 0}}
|
||||
reply3 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, reply3, nullTTL, 0)
|
||||
|
||||
t.Logf("Checking we get the last one...")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from the Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
wt.AssertEqualString(t, resp.Answer[0].(*dns.A).A.String(), "10.0.1.3", "IP address")
|
||||
wt.AssertEqualInt(t, int(resp.Answer[0].Header().Ttl), int(DefaultLocalTTL), "TTL")
|
||||
|
||||
clk.Add(time.Duration(DefaultLocalTTL-1) * time.Second)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, resp != nil, "reponse from the Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
wt.AssertEqualString(t, resp.Answer[0].(*dns.A).A.String(), "10.0.1.3", "IP address")
|
||||
wt.AssertEqualInt(t, int(resp.Answer[0].Header().Ttl), 1, "TTL")
|
||||
|
||||
t.Logf("Checking we get empty replies when they are expired...")
|
||||
lenBefore = l.Len()
|
||||
clk.Add(time.Duration(DefaultLocalTTL) * time.Second)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp.Answer[0])
|
||||
t.Fatalf("ERROR: Did NOT expect a reponse from the Get()")
|
||||
}
|
||||
wt.AssertEqualInt(t, l.Len(), lenBefore-1, "cache length (after getting an expired entry)")
|
||||
|
||||
questionMsg2 := new(dns.Msg)
|
||||
questionMsg2.SetQuestion("some.other.name", dns.TypeA)
|
||||
questionMsg2.RecursionDesired = true
|
||||
question2 := &questionMsg2.Question[0]
|
||||
|
||||
t.Logf("Trying to Get() a name")
|
||||
resp, err = l.Get(questionMsg2, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNil(t, resp, "reponse from Get() yet")
|
||||
|
||||
t.Logf("Checking that an Remove() between Get() and Put() does not break things")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
|
||||
replyTemp2 := makeAddressReply(questionMsg2, question2, records, DefaultLocalTTL)
|
||||
l.Remove(question2)
|
||||
l.Put(questionMsg2, replyTemp2, nullTTL, 0)
|
||||
resp, err = l.Get(questionMsg2, minUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotNil(t, resp, "reponse from Get()")
|
||||
|
||||
questionMsg3 := new(dns.Msg)
|
||||
questionMsg3.SetQuestion("some.other.name", dns.TypeA)
|
||||
questionMsg3.RecursionDesired = true
|
||||
question3 := &questionMsg3.Question[0]
|
||||
|
||||
t.Logf("Checking that a entry with CacheNoLocalReplies return an error")
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies")
|
||||
wt.AssertTrue(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
|
||||
|
||||
t.Logf("Checking that more Put's do not make the negative TTL longer")
|
||||
clk.Add(time.Duration(DefaultLocalTTL/2) * time.Second)
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies")
|
||||
wt.AssertTrue(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
|
||||
|
||||
clk.Add(time.Duration(DefaultLocalTTL/2) * time.Second)
|
||||
t.Logf("Checking that we get an expired response after %f seconds", DefaultLocalTTL)
|
||||
clk.Add(time.Second * time.Duration(DefaultLocalTTL+1))
|
||||
t.Logf("Checking that we get an expired response after %d seconds", DefaultLocalTTL)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "expired Get() response with CacheNoLocalReplies")
|
||||
wt.AssertNil(t, err, "expired Get() error with CacheNoLocalReplies")
|
||||
|
||||
l.Remove(question3)
|
||||
t.Logf("Checking that Put&Get with CacheNoLocalReplies with a Remove in the middle returns nothing")
|
||||
l.Put(questionMsg3, nil, nullTTL, CacheNoLocalReplies)
|
||||
l.Remove(question3)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
wt.AssertNil(t, resp, "Get() reponse with CacheNoLocalReplies")
|
||||
wt.AssertNil(t, err, "Get() error with CacheNoLocalReplies")
|
||||
}
|
||||
+267
-86
@@ -1,117 +1,298 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
const (
|
||||
minUDPSize = 512
|
||||
maxUDPSize = 65535
|
||||
topDomain = "."
|
||||
reverseDNSdomain = "in-addr.arpa."
|
||||
etcResolvConf = "/etc/resolv.conf"
|
||||
udpBuffSize = uint16(4096)
|
||||
minUDPSize = 512
|
||||
|
||||
DefaultListenAddress = "0.0.0.0:53"
|
||||
DefaultTTL = 1
|
||||
DefaultClientTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
func makeHeader(r *dns.Msg, q *dns.Question, ttl int) *dns.RR_Header {
|
||||
return &dns.RR_Header{
|
||||
Name: q.Name,
|
||||
Rrtype: q.Qtype,
|
||||
type DNSServer struct {
|
||||
ns *Nameserver
|
||||
domain string
|
||||
ttl uint32
|
||||
address string
|
||||
|
||||
servers []*dns.Server
|
||||
upstream *dns.ClientConfig
|
||||
tcpClient *dns.Client
|
||||
udpClient *dns.Client
|
||||
}
|
||||
|
||||
func filter(ss []string, s string) []string {
|
||||
for i := 0; i < len(ss); {
|
||||
if ss[i] == s {
|
||||
ss = append(ss[:i], ss[i+1:]...)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func NewDNSServer(ns *Nameserver, domain, address, effectiveAddress string, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {
|
||||
s := &DNSServer{
|
||||
ns: ns,
|
||||
domain: dns.Fqdn(domain),
|
||||
ttl: ttl,
|
||||
address: address,
|
||||
tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
|
||||
udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
|
||||
}
|
||||
var err error
|
||||
if s.upstream, err = dns.ClientConfigFromFile(etcResolvConf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.upstream != nil {
|
||||
s.upstream.Servers = filter(s.upstream.Servers, effectiveAddress)
|
||||
}
|
||||
|
||||
err = s.listen(address)
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (d *DNSServer) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "WeaveDNS (%s)\n", d.ns.ourName)
|
||||
fmt.Fprintf(&buf, " listening on %s, for domain %s\n", d.address, d.domain)
|
||||
fmt.Fprintf(&buf, " response ttl %d\n", d.ttl)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (d *DNSServer) listen(address string) error {
|
||||
udpListener, err := net.ListenPacket("udp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
udpServer := &dns.Server{PacketConn: udpListener, Handler: d.createMux(d.udpClient, minUDPSize)}
|
||||
|
||||
tcpListener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
udpServer.Shutdown()
|
||||
return err
|
||||
}
|
||||
tcpServer := &dns.Server{Listener: tcpListener, Handler: d.createMux(d.tcpClient, -1)}
|
||||
|
||||
d.servers = []*dns.Server{udpServer, tcpServer}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSServer) ActivateAndServe() {
|
||||
for _, server := range d.servers {
|
||||
go func(server *dns.Server) {
|
||||
server.ActivateAndServe()
|
||||
}(server)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSServer) Stop() error {
|
||||
for _, server := range d.servers {
|
||||
if err := server.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
*DNSServer
|
||||
maxResponseSize int
|
||||
client *dns.Client
|
||||
}
|
||||
|
||||
func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *dns.ServeMux {
|
||||
m := dns.NewServeMux()
|
||||
h := &handler{
|
||||
DNSServer: d,
|
||||
maxResponseSize: defaultMaxResponseSize,
|
||||
client: client,
|
||||
}
|
||||
m.HandleFunc(d.domain, h.handleLocal)
|
||||
m.HandleFunc(reverseDNSdomain, h.handleReverse)
|
||||
m.HandleFunc(topDomain, h.handleRecursive)
|
||||
return m
|
||||
}
|
||||
|
||||
func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("local request: %+v", *req)
|
||||
if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypeA {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
hostname := dns.Fqdn(req.Question[0].Name)
|
||||
if strings.Count(hostname, ".") == 1 {
|
||||
hostname = hostname + h.domain
|
||||
}
|
||||
|
||||
addrs := h.ns.Lookup(hostname)
|
||||
if len(addrs) == 0 {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
header := dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(ttl),
|
||||
Ttl: h.ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func makeReply(r *dns.Msg, as []dns.RR) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.RecursionAvailable = true
|
||||
m.Answer = as
|
||||
return m
|
||||
}
|
||||
|
||||
func makeTruncatedReply(r *dns.Msg) *dns.Msg {
|
||||
// for truncated response, we create a minimal reply with the Truncated bit set
|
||||
reply := new(dns.Msg)
|
||||
reply.SetReply(r)
|
||||
reply.Truncated = true
|
||||
return reply
|
||||
}
|
||||
|
||||
type DNSResponseBuilder func(r *dns.Msg, q *dns.Question, addrs []ZoneRecord, ttl int) *dns.Msg
|
||||
|
||||
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord, ttl int) *dns.Msg {
|
||||
answers := make([]dns.RR, len(addrs))
|
||||
header := makeHeader(r, q, ttl)
|
||||
count := 0
|
||||
for _, addr := range addrs {
|
||||
ip := addr.IP()
|
||||
ip4 := ip.To4()
|
||||
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
if ip4 != nil {
|
||||
answers[count] = &dns.A{Hdr: *header, A: ip}
|
||||
count++
|
||||
}
|
||||
case dns.TypeAAAA:
|
||||
if ip4 == nil {
|
||||
answers[count] = &dns.AAAA{Hdr: *header, AAAA: ip}
|
||||
count++
|
||||
}
|
||||
}
|
||||
for i, addr := range addrs {
|
||||
ip := addr.IP4()
|
||||
answers[i] = &dns.A{Hdr: header, A: ip}
|
||||
}
|
||||
return makeReply(r, answers[:count])
|
||||
shuffleAnswers(&answers)
|
||||
|
||||
h.respond(w, h.makeResponse(req, answers))
|
||||
}
|
||||
|
||||
func makePTRReply(r *dns.Msg, q *dns.Question, names []ZoneRecord, ttl int) *dns.Msg {
|
||||
answers := make([]dns.RR, len(names))
|
||||
header := makeHeader(r, q, ttl)
|
||||
for i, name := range names {
|
||||
answers[i] = &dns.PTR{Hdr: *header, Ptr: name.Name()}
|
||||
func (h *handler) handleReverse(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("reverse request: %+v", *req)
|
||||
if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypePTR {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
return makeReply(r, answers)
|
||||
}
|
||||
|
||||
func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.RecursionAvailable = true
|
||||
m.Rcode = dns.RcodeNameError
|
||||
return m
|
||||
}
|
||||
|
||||
// get the maximum UDP-reply length
|
||||
func getMaxReplyLen(r *dns.Msg, proto dnsProtocol) int {
|
||||
maxLen := minUDPSize
|
||||
if proto == protTCP {
|
||||
maxLen = maxUDPSize
|
||||
} else if opt := r.IsEdns0(); opt != nil {
|
||||
maxLen = int(opt.UDPSize())
|
||||
ipStr := strings.TrimSuffix(req.Question[0].Name, "."+reverseDNSdomain)
|
||||
ip, err := address.ParseIP(ipStr)
|
||||
if err != nil {
|
||||
h.nameError(w, req)
|
||||
return
|
||||
}
|
||||
return maxLen
|
||||
|
||||
hostname, err := h.ns.ReverseLookup(ip.Reverse())
|
||||
if err != nil {
|
||||
h.handleRecursive(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
header := dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: h.ttl,
|
||||
}
|
||||
answers := []dns.RR{&dns.PTR{
|
||||
Hdr: header,
|
||||
Ptr: hostname,
|
||||
}}
|
||||
|
||||
h.respond(w, h.makeResponse(req, answers))
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.ns.debugf("recursive request: %+v", *req)
|
||||
|
||||
// shuffleAnswers reorders answers for very basic load balancing
|
||||
func shuffleAnswers(answers []dns.RR) []dns.RR {
|
||||
if len(answers) > 1 {
|
||||
for i := range answers {
|
||||
j := rand.Intn(i + 1)
|
||||
answers[i], answers[j] = answers[j], answers[i]
|
||||
// Resolve unqualified names locally
|
||||
if len(req.Question) == 1 && req.Question[0].Qtype == dns.TypeA {
|
||||
hostname := dns.Fqdn(req.Question[0].Name)
|
||||
if strings.Count(hostname, ".") == 1 {
|
||||
h.handleLocal(w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return answers
|
||||
for _, server := range h.upstream.Servers {
|
||||
reqCopy := req.Copy()
|
||||
reqCopy.Id = dns.Id()
|
||||
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, h.upstream.Port))
|
||||
if err != nil || response == nil {
|
||||
h.ns.debugf("error trying %s: %v", server, err)
|
||||
continue
|
||||
}
|
||||
response.Id = req.Id
|
||||
if h.responseTooBig(req, response) {
|
||||
response.Compress = true
|
||||
}
|
||||
h.respond(w, response)
|
||||
return
|
||||
}
|
||||
|
||||
h.respond(w, h.makeErrorResponse(req, dns.RcodeServerFailure))
|
||||
}
|
||||
|
||||
// only take the first `num` answers
|
||||
func pruneAnswers(answers []dns.RR, num int) []dns.RR {
|
||||
if num > 0 && len(answers) > num {
|
||||
// TODO: we should have some prefer locally-introduced answers, etc...
|
||||
return answers[:num]
|
||||
func (h *handler) makeResponse(req *dns.Msg, answers []dns.RR) *dns.Msg {
|
||||
response := &dns.Msg{}
|
||||
response.SetReply(req)
|
||||
response.RecursionAvailable = true
|
||||
response.Authoritative = true
|
||||
response.Answer = answers
|
||||
if !h.responseTooBig(req, response) {
|
||||
return response
|
||||
}
|
||||
|
||||
// search for smallest i that is too big
|
||||
maxSize := h.getMaxResponseSize(req)
|
||||
i := sort.Search(len(answers), func(i int) bool {
|
||||
// return true if too big
|
||||
response.Answer = answers[:i+1]
|
||||
return response.Len() > maxSize
|
||||
})
|
||||
|
||||
response.Answer = answers[:i]
|
||||
if i < len(answers) {
|
||||
response.Truncated = true
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func (h *handler) makeErrorResponse(req *dns.Msg, code int) *dns.Msg {
|
||||
response := &dns.Msg{}
|
||||
response.SetReply(req)
|
||||
response.RecursionAvailable = true
|
||||
response.Rcode = code
|
||||
return response
|
||||
}
|
||||
|
||||
func (h *handler) responseTooBig(req, response *dns.Msg) bool {
|
||||
return len(response.Answer) > 1 && h.maxResponseSize > 0 && response.Len() > h.getMaxResponseSize(req)
|
||||
}
|
||||
|
||||
func (h *handler) respond(w dns.ResponseWriter, response *dns.Msg) {
|
||||
h.ns.debugf("response: %+v", response)
|
||||
if err := w.WriteMsg(response); err != nil {
|
||||
h.ns.infof("error responding: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) nameError(w dns.ResponseWriter, req *dns.Msg) {
|
||||
h.respond(w, h.makeErrorResponse(req, dns.RcodeNameError))
|
||||
}
|
||||
|
||||
func (h *handler) getMaxResponseSize(req *dns.Msg) int {
|
||||
if opt := req.IsEdns0(); opt != nil {
|
||||
return int(opt.UDPSize())
|
||||
}
|
||||
return h.maxResponseSize
|
||||
}
|
||||
|
||||
func shuffleAnswers(answers *[]dns.RR) {
|
||||
if len(*answers) <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range *answers {
|
||||
j := rand.Intn(i + 1)
|
||||
(*answers)[i], (*answers)[j] = (*answers)[j], (*answers)[i]
|
||||
}
|
||||
return answers
|
||||
}
|
||||
+154
-23
@@ -1,39 +1,170 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
// Check that we can prune an answer
|
||||
func TestPrune(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestPrune starting")
|
||||
func startServer(t *testing.T) (*DNSServer, *Nameserver, int, int) {
|
||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, nil, nil, "")
|
||||
dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", "", 30, 5*time.Second)
|
||||
require.Nil(t, err)
|
||||
udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port
|
||||
tcpPort := dnsserver.servers[1].Listener.Addr().(*net.TCPAddr).Port
|
||||
go dnsserver.ActivateAndServe()
|
||||
return dnsserver, nameserver, udpPort, tcpPort
|
||||
}
|
||||
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion("name", dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
question := &questionMsg.Question[0]
|
||||
records := []ZoneRecord{
|
||||
Record{"name", net.ParseIP("10.0.1.1"), 0, 0, 0},
|
||||
Record{"name", net.ParseIP("10.0.1.2"), 0, 0, 0},
|
||||
Record{"name", net.ParseIP("10.0.1.3"), 0, 0, 0},
|
||||
Record{"name", net.ParseIP("10.0.1.4"), 0, 0, 0},
|
||||
func TestTruncation(t *testing.T) {
|
||||
//common.SetLogLevel("debug")
|
||||
dnsserver, nameserver, udpPort, tcpPort := startServer(t)
|
||||
defer dnsserver.Stop()
|
||||
|
||||
// Add 100 mappings to nameserver
|
||||
addrs := []address.Address{}
|
||||
for i := address.Address(0); i < 100; i++ {
|
||||
addrs = append(addrs, i)
|
||||
nameserver.AddEntry("foo.weave.local.", "", router.UnknownPeerName, i)
|
||||
}
|
||||
|
||||
reply := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
reply.Answer[0].Header().Ttl = DefaultLocalTTL
|
||||
doRequest := func(client *dns.Client, request *dns.Msg, port int) *dns.Msg {
|
||||
request.SetQuestion("foo.weave.local.", dns.TypeA)
|
||||
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
|
||||
require.Nil(t, err)
|
||||
return response
|
||||
}
|
||||
|
||||
pruned := pruneAnswers(reply.Answer, 1)
|
||||
wt.AssertEqualInt(t, len(pruned), 1, "wrong number of answers")
|
||||
// do a udp query, ensure we get a truncated response
|
||||
{
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: minUDPSize}
|
||||
response := doRequest(&udpClient, &dns.Msg{}, udpPort)
|
||||
require.True(t, response.MsgHdr.Truncated)
|
||||
require.True(t, len(response.Answer) < 100)
|
||||
}
|
||||
|
||||
pruned = pruneAnswers(reply.Answer, 2)
|
||||
wt.AssertEqualInt(t, len(pruned), 2, "wrong number of answers")
|
||||
// do a udp query with big size, ensure we don't get a truncated response
|
||||
{
|
||||
udpClient := dns.Client{Net: "udp", UDPSize: 65535}
|
||||
request := &dns.Msg{}
|
||||
request.SetEdns0(65535, false)
|
||||
response := doRequest(&udpClient, request, udpPort)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
|
||||
pruned = pruneAnswers(reply.Answer, 0)
|
||||
wt.AssertEqualInt(t, len(pruned), len(records), "wrong number of answers")
|
||||
// do a tcp query, ensure we don't get a truncated response
|
||||
{
|
||||
tcpClient := dns.Client{Net: "tcp"}
|
||||
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort)
|
||||
require.False(t, response.MsgHdr.Truncated)
|
||||
require.Equal(t, len(response.Answer), 100)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateResponse(t *testing.T) {
|
||||
|
||||
header := dns.RR_Header{
|
||||
Name: "host.domain.com",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 30,
|
||||
}
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
// generate a random answer set
|
||||
numAnswers := 40 + rand.Intn(200)
|
||||
answers := make([]dns.RR, numAnswers)
|
||||
for j := 0; j < numAnswers; j++ {
|
||||
answers[j] = &dns.A{Hdr: header, A: address.Address(j).IP4()}
|
||||
}
|
||||
|
||||
// pick a random max size, truncate response to that, check it
|
||||
maxSize := 512 + rand.Intn(2*512)
|
||||
h := handler{maxResponseSize: maxSize}
|
||||
response := h.makeResponse(&dns.Msg{}, answers)
|
||||
require.True(t, response.Len() <= maxSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecursiveCompress(t *testing.T) {
|
||||
const (
|
||||
hostname = "foo.example."
|
||||
maxSize = 512
|
||||
)
|
||||
|
||||
// Construct a response that is >512 when uncompressed, <512 when compressed
|
||||
response := dns.Msg{}
|
||||
response.Authoritative = true
|
||||
response.Answer = []dns.RR{}
|
||||
header := dns.RR_Header{
|
||||
Name: hostname,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 10,
|
||||
}
|
||||
for response.Len() <= maxSize {
|
||||
ip := address.Address(rand.Uint32()).IP4()
|
||||
response.Answer = append(response.Answer, &dns.A{Hdr: header, A: ip})
|
||||
}
|
||||
response.Compress = true
|
||||
require.True(t, response.Len() <= maxSize)
|
||||
|
||||
// A dns server that returns the above response
|
||||
var gotRequest = false
|
||||
handleRecursive := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
gotRequest = true
|
||||
require.Equal(t, req.Question[0].Name, hostname)
|
||||
response.SetReply(req)
|
||||
err := w.WriteMsg(&response)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
mux := dns.NewServeMux()
|
||||
mux.HandleFunc(topDomain, handleRecursive)
|
||||
udpListener, err := net.ListenPacket("udp", "0.0.0.0:0")
|
||||
require.Nil(t, err)
|
||||
udpServer := &dns.Server{PacketConn: udpListener, Handler: mux}
|
||||
udpServerPort := udpListener.LocalAddr().(*net.UDPAddr).Port
|
||||
go udpServer.ActivateAndServe()
|
||||
defer udpServer.Shutdown()
|
||||
|
||||
// The weavedns server, pointed at the above server
|
||||
dnsserver, _, udpPort, _ := startServer(t)
|
||||
dnsserver.upstream = &dns.ClientConfig{
|
||||
Servers: []string{"127.0.0.1"},
|
||||
Port: strconv.Itoa(udpServerPort),
|
||||
Ndots: 1,
|
||||
Timeout: 5,
|
||||
Attempts: 2,
|
||||
}
|
||||
defer dnsserver.Stop()
|
||||
|
||||
// Now do lookup, check its what we expected.
|
||||
// NB this doesn't really test golang's resolver behaves correctly, as I can't see
|
||||
// a way to point golangs resolver at a specific hosts.
|
||||
req := new(dns.Msg)
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
req.Question = make([]dns.Question, 1)
|
||||
req.Question[0] = dns.Question{
|
||||
Name: hostname,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}
|
||||
c := new(dns.Client)
|
||||
res, _, err := c.Exchange(req, fmt.Sprintf("127.0.0.1:%d", udpPort))
|
||||
require.Nil(t, err)
|
||||
require.True(t, gotRequest)
|
||||
require.True(t, res.Len() > maxSize)
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
var now = func() int64 { return time.Now().Unix() }
|
||||
|
||||
type Entry struct {
|
||||
ContainerID string
|
||||
Origin router.PeerName
|
||||
Addr address.Address
|
||||
Hostname string
|
||||
Version int
|
||||
Tombstone int64 // timestamp of when it was deleted
|
||||
}
|
||||
|
||||
type Entries []Entry
|
||||
|
||||
func (e1 *Entry) equal(e2 *Entry) bool {
|
||||
return e1.ContainerID == e2.ContainerID &&
|
||||
e1.Origin == e2.Origin &&
|
||||
e1.Addr == e2.Addr &&
|
||||
e1.Hostname == e2.Hostname
|
||||
}
|
||||
|
||||
func (e1 *Entry) less(e2 *Entry) bool {
|
||||
// Entries are kept sorted by Hostname, Origin, ContainerID then address
|
||||
switch {
|
||||
case e1.Hostname != e2.Hostname:
|
||||
return e1.Hostname < e2.Hostname
|
||||
|
||||
case e1.Origin != e2.Origin:
|
||||
return e1.Origin < e2.Origin
|
||||
|
||||
case e1.ContainerID != e2.ContainerID:
|
||||
return e1.ContainerID < e2.ContainerID
|
||||
|
||||
default:
|
||||
return e1.Addr < e2.Addr
|
||||
}
|
||||
}
|
||||
|
||||
// returns true to indicate a change
|
||||
func (e1 *Entry) merge(e2 *Entry) bool {
|
||||
// we know container id, origin, add and hostname are equal
|
||||
if e2.Version > e1.Version {
|
||||
e1.Version = e2.Version
|
||||
e1.Tombstone = e2.Tombstone
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e1 *Entry) String() string {
|
||||
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
|
||||
}
|
||||
|
||||
func (es Entries) Len() int { return len(es) }
|
||||
func (es Entries) Swap(i, j int) { panic("Swap") }
|
||||
func (es Entries) Less(i, j int) bool { return es[i].less(&es[j]) }
|
||||
|
||||
func (es Entries) check() error {
|
||||
if !sort.IsSorted(es) {
|
||||
return fmt.Errorf("Not sorted!")
|
||||
}
|
||||
for i := 1; i < len(es); i++ {
|
||||
if es[i].equal(&es[i-1]) {
|
||||
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es[i-1], i, es[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Entries) checkAndPanic() {
|
||||
if err := es.check(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (es *Entries) add(hostname, containerid string, origin router.PeerName, addr address.Address) Entry {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
|
||||
entry := Entry{Hostname: hostname, Origin: origin, ContainerID: containerid, Addr: addr}
|
||||
i := sort.Search(len(*es), func(i int) bool {
|
||||
return !(*es)[i].less(&entry)
|
||||
})
|
||||
if i < len(*es) && (*es)[i].equal(&entry) {
|
||||
if (*es)[i].Tombstone > 0 {
|
||||
(*es)[i].Tombstone = 0
|
||||
(*es)[i].Version++
|
||||
}
|
||||
} else {
|
||||
*es = append(*es, Entry{})
|
||||
copy((*es)[i+1:], (*es)[i:])
|
||||
(*es)[i] = entry
|
||||
}
|
||||
return (*es)[i]
|
||||
}
|
||||
|
||||
func (es *Entries) merge(incoming Entries) Entries {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
newEntries := Entries{}
|
||||
i := 0
|
||||
|
||||
for _, entry := range incoming {
|
||||
for i < len(*es) && (*es)[i].less(&entry) {
|
||||
i++
|
||||
}
|
||||
if i < len(*es) && (*es)[i].equal(&entry) {
|
||||
if (*es)[i].merge(&entry) {
|
||||
newEntries = append(newEntries, entry)
|
||||
}
|
||||
} else {
|
||||
*es = append(*es, Entry{})
|
||||
copy((*es)[i+1:], (*es)[i:])
|
||||
(*es)[i] = entry
|
||||
newEntries = append(newEntries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return newEntries
|
||||
}
|
||||
|
||||
// f returning true means keep the entry.
|
||||
func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entries {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
tombstoned := Entries{}
|
||||
for i, e := range *es {
|
||||
if f(&e) && e.Origin == ourname {
|
||||
e.Version++
|
||||
e.Tombstone = now()
|
||||
(*es)[i] = e
|
||||
tombstoned = append(tombstoned, e)
|
||||
}
|
||||
}
|
||||
return tombstoned
|
||||
}
|
||||
|
||||
func (es *Entries) filter(f func(*Entry) bool) {
|
||||
es.checkAndPanic()
|
||||
defer es.checkAndPanic()
|
||||
i := 0
|
||||
for _, e := range *es {
|
||||
if !f(&e) {
|
||||
continue
|
||||
}
|
||||
(*es)[i] = e
|
||||
i++
|
||||
}
|
||||
*es = (*es)[:i]
|
||||
}
|
||||
|
||||
func (es Entries) lookup(hostname string) Entries {
|
||||
es.checkAndPanic()
|
||||
i := sort.Search(len(es), func(i int) bool {
|
||||
return es[i].Hostname >= hostname
|
||||
})
|
||||
if i >= len(es) || es[i].Hostname != hostname {
|
||||
return Entries{}
|
||||
}
|
||||
|
||||
j := sort.Search(len(es)-i, func(j int) bool {
|
||||
return es[i+j].Hostname > hostname
|
||||
})
|
||||
|
||||
return es[i : i+j]
|
||||
}
|
||||
|
||||
func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
|
||||
es.checkAndPanic()
|
||||
for _, e := range *es {
|
||||
if f(&e) {
|
||||
return &e, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Not found")
|
||||
}
|
||||
|
||||
type GossipData struct {
|
||||
Timestamp int64
|
||||
Entries
|
||||
}
|
||||
|
||||
func (g *GossipData) Merge(o router.GossipData) {
|
||||
other := o.(*GossipData)
|
||||
g.Entries.merge(other.Entries)
|
||||
if g.Timestamp < other.Timestamp {
|
||||
g.Timestamp = other.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GossipData) Encode() [][]byte {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := gob.NewEncoder(buf).Encode(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return [][]byte{buf.Bytes()}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
oldNow := now
|
||||
defer func() { now = oldNow }()
|
||||
now = func() int64 { return 1234 }
|
||||
|
||||
entries := Entries{}
|
||||
entries.add("A", "", router.UnknownPeerName, address.Address(0))
|
||||
expected := Entries{
|
||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0)},
|
||||
}
|
||||
require.Equal(t, entries, expected)
|
||||
|
||||
entries.tombstone(router.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" })
|
||||
expected = Entries{
|
||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234},
|
||||
}
|
||||
require.Equal(t, entries, expected)
|
||||
|
||||
entries.add("A", "", router.UnknownPeerName, address.Address(0))
|
||||
expected = Entries{
|
||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 2},
|
||||
}
|
||||
require.Equal(t, entries, expected)
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
e1 := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "C"},
|
||||
Entry{Hostname: "D"},
|
||||
Entry{Hostname: "F"},
|
||||
}
|
||||
|
||||
e2 := Entries{
|
||||
Entry{Hostname: "B"},
|
||||
Entry{Hostname: "E"},
|
||||
Entry{Hostname: "F"},
|
||||
}
|
||||
|
||||
diff := e1.merge(e2)
|
||||
expectedDiff := Entries{
|
||||
Entry{Hostname: "B"},
|
||||
Entry{Hostname: "E"},
|
||||
}
|
||||
require.Equal(t, expectedDiff, diff)
|
||||
|
||||
expected := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B"},
|
||||
Entry{Hostname: "C"},
|
||||
Entry{Hostname: "D"},
|
||||
Entry{Hostname: "E"},
|
||||
Entry{Hostname: "F"},
|
||||
}
|
||||
require.Equal(t, expected, e1)
|
||||
|
||||
diff = e1.merge(e1)
|
||||
require.Equal(t, Entries{}, diff)
|
||||
}
|
||||
|
||||
func TestOldMerge(t *testing.T) {
|
||||
e1 := Entries{Entry{Hostname: "A", Version: 0}}
|
||||
diff := e1.merge(Entries{Entry{Hostname: "A", Version: 1}})
|
||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, diff)
|
||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
|
||||
|
||||
diff = e1.merge(Entries{Entry{Hostname: "A", Version: 0}})
|
||||
require.Equal(t, Entries{}, diff)
|
||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
|
||||
}
|
||||
|
||||
func TestTombstone(t *testing.T) {
|
||||
oldNow := now
|
||||
defer func() { now = oldNow }()
|
||||
now = func() int64 { return 1234 }
|
||||
|
||||
es := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B"},
|
||||
}
|
||||
|
||||
es.tombstone(router.UnknownPeerName, func(e *Entry) bool {
|
||||
return e.Hostname == "B"
|
||||
})
|
||||
expected := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B", Version: 1, Tombstone: 1234},
|
||||
}
|
||||
require.Equal(t, expected, es)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
es := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B"},
|
||||
}
|
||||
|
||||
es.filter(func(e *Entry) bool {
|
||||
return e.Hostname != "A"
|
||||
})
|
||||
expected := Entries{
|
||||
Entry{Hostname: "B"},
|
||||
}
|
||||
require.Equal(t, expected, es)
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
es := Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B", ContainerID: "bar"},
|
||||
Entry{Hostname: "B", ContainerID: "foo"},
|
||||
Entry{Hostname: "C"},
|
||||
}
|
||||
|
||||
have := es.lookup("B")
|
||||
want := Entries{
|
||||
Entry{Hostname: "B", ContainerID: "bar"},
|
||||
Entry{Hostname: "B", ContainerID: "foo"},
|
||||
}
|
||||
require.Equal(t, have, want)
|
||||
}
|
||||
+61
-93
@@ -1,125 +1,93 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
func httpErrorAndLog(level *log.Logger, w http.ResponseWriter, msg string,
|
||||
status int, logmsg string, logargs ...interface{}) {
|
||||
http.Error(w, msg, status)
|
||||
level.Printf("[http] "+logmsg, logargs...)
|
||||
func (n *Nameserver) badRequest(w http.ResponseWriter, err error) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
n.infof("%v", err)
|
||||
}
|
||||
|
||||
func extractFQDN(r *http.Request) (string, string) {
|
||||
if fqdnStr := r.FormValue("fqdn"); fqdnStr != "" {
|
||||
return fqdnStr, fqdnStr
|
||||
}
|
||||
return "*", ""
|
||||
}
|
||||
|
||||
func ServeHTTP(listener net.Listener, version string, server *DNSServer, dockerCli *docker.Client) {
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
|
||||
muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "weave DNS", version)
|
||||
fmt.Fprintln(w, server.Status())
|
||||
fmt.Fprintln(w, server.Zone.Status())
|
||||
func (n *Nameserver) HandleHTTP(router *mux.Router) {
|
||||
router.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, n.domain)
|
||||
})
|
||||
|
||||
muxRouter.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, server.Zone.Domain())
|
||||
})
|
||||
|
||||
muxRouter.Methods("PUT").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
reqError := func(msg string, logmsg string, logargs ...interface{}) {
|
||||
httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
name := r.FormValue("fqdn")
|
||||
|
||||
if name == "" {
|
||||
reqError("Invalid FQDN", "Invalid FQDN in request: %s, %s", r.URL, r.Form)
|
||||
router.Methods("PUT").Path("/name/{container}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
container = vars["container"]
|
||||
ipStr = vars["ip"]
|
||||
hostname = dns.Fqdn(r.FormValue("fqdn"))
|
||||
ip, err = address.ParseIP(ipStr)
|
||||
)
|
||||
if err != nil {
|
||||
n.badRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
reqError("Invalid IP", "Invalid IP in request: %s", ipStr)
|
||||
if err := n.AddEntry(hostname, container, n.ourName, ip); err != nil {
|
||||
n.badRequest(w, fmt.Errorf("Unable to add entry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
domain := server.Zone.Domain()
|
||||
if !dns.IsSubDomain(domain, name) {
|
||||
Info.Printf("[http] Ignoring name %s, not in %s", name, domain)
|
||||
return
|
||||
}
|
||||
Info.Printf("[http] Adding %s -> %s", name, ipStr)
|
||||
if err := server.Zone.AddRecord(idStr, name, ip); err != nil {
|
||||
if _, ok := err.(DuplicateError); !ok {
|
||||
httpErrorAndLog(
|
||||
Error, w, "Internal error", http.StatusInternalServerError,
|
||||
"Unexpected error from DB: %s", err)
|
||||
return
|
||||
} // oh, I already know this. whatever.
|
||||
if r.FormValue("check-alive") == "true" && n.docker.IsContainerNotRunning(container) {
|
||||
n.infof("container '%s' is not running: removing", container)
|
||||
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
|
||||
n.infof("failed to remove: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if dockerCli != nil && dockerCli.IsContainerNotRunning(idStr) {
|
||||
Info.Printf("[http] '%s' is not running: removing", idStr)
|
||||
server.Zone.DeleteRecords(idStr, name, ip)
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
muxRouter.Methods("DELETE").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
deleteHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
httpErrorAndLog(
|
||||
Warning, w, "Invalid IP in request", http.StatusBadRequest,
|
||||
"Invalid IP in request: %s", ipStr)
|
||||
return
|
||||
hostname := r.FormValue("fqdn")
|
||||
if hostname == "" {
|
||||
hostname = "*"
|
||||
} else {
|
||||
hostname = dns.Fqdn(hostname)
|
||||
}
|
||||
|
||||
fqdnStr, fqdn := extractFQDN(r)
|
||||
|
||||
Info.Printf("[http] Deleting ID %s, IP %s, FQDN %s", idStr, ipStr, fqdnStr)
|
||||
server.Zone.DeleteRecords(idStr, fqdn, ip)
|
||||
})
|
||||
|
||||
muxRouter.Methods("DELETE").Path("/name/{id:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars["id"]
|
||||
|
||||
fqdnStr, fqdn := extractFQDN(r)
|
||||
|
||||
Info.Printf("[http] Deleting ID %s, IP *, FQDN %s", idStr, fqdnStr)
|
||||
server.Zone.DeleteRecords(idStr, fqdn, net.IP{})
|
||||
})
|
||||
|
||||
muxRouter.Methods("DELETE").Path("/name").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fqdnStr, fqdn := extractFQDN(r)
|
||||
|
||||
Info.Printf("[http] Deleting ID *, IP *, FQDN %s", fqdnStr)
|
||||
server.Zone.DeleteRecords("", fqdn, net.IP{})
|
||||
})
|
||||
container, ok := vars["container"]
|
||||
if !ok {
|
||||
container = "*"
|
||||
}
|
||||
|
||||
http.Handle("/", muxRouter)
|
||||
ipStr, ok := vars["ip"]
|
||||
ip, err := address.ParseIP(ipStr)
|
||||
if ok && err != nil {
|
||||
n.badRequest(w, err)
|
||||
return
|
||||
} else if !ok {
|
||||
ipStr = "*"
|
||||
}
|
||||
|
||||
if err := http.Serve(listener, nil); err != nil {
|
||||
Error.Fatal("[http] Unable to serve http: ", err)
|
||||
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
|
||||
n.badRequest(w, fmt.Errorf("Unable to delete entries: %v", err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
}
|
||||
router.Methods("DELETE").Path("/name/{container}/{ip}").HandlerFunc(deleteHandler)
|
||||
router.Methods("DELETE").Path("/name/{container}").HandlerFunc(deleteHandler)
|
||||
router.Methods("DELETE").Path("/name").HandlerFunc(deleteHandler)
|
||||
|
||||
router.Methods("GET").Path("/name").Headers("Accept", "application/json").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
if err := json.NewEncoder(w).Encode(n.entries); err != nil {
|
||||
n.badRequest(w, fmt.Errorf("Error marshalling response: %v", err))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func genForm(method string, url string, data url.Values) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest(method, url, strings.NewReader(data.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func TestHttp(t *testing.T) {
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
testDomain = "weave.local."
|
||||
successTestName = "test1." + testDomain
|
||||
testAddr1 = "10.2.2.1/24"
|
||||
testAddr2 = "10.2.2.2/24"
|
||||
dockerIP = "9.8.7.6"
|
||||
)
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{Domain: testDomain})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
httpListener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create http listener: ", err)
|
||||
}
|
||||
|
||||
// Create a useless server... it will not even be started
|
||||
srv, _ := NewDNSServer(DNSServerConfig{Zone: zone})
|
||||
|
||||
port := httpListener.Addr().(*net.TCPAddr).Port
|
||||
go ServeHTTP(httpListener, "", srv, nil)
|
||||
|
||||
// Ask the http server to add our test address into the database
|
||||
addrParts := strings.Split(testAddr1, "/")
|
||||
addrURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, containerID, addrParts[0])
|
||||
resp, err := genForm("PUT", addrURL,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
|
||||
|
||||
// Check that the address is now there.
|
||||
ip, _, _ := net.ParseCIDR(testAddr1)
|
||||
foundIP, err := zone.LookupName(successTestName)
|
||||
wt.AssertNoErr(t, err)
|
||||
if !foundIP[0].IP().Equal(ip) {
|
||||
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
|
||||
}
|
||||
|
||||
// Adding exactly the same address should be OK
|
||||
resp, err = genForm("PUT", addrURL,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http success response for duplicate add")
|
||||
|
||||
// Now try adding the same address again with a different ident --
|
||||
// again should be fine
|
||||
otherURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, "other", addrParts[0])
|
||||
resp, err = genForm("PUT", otherURL,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
|
||||
|
||||
// Adding a new IP for the same name should be OK
|
||||
addrParts2 := strings.Split(testAddr2, "/")
|
||||
addrURL2 := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, containerID, addrParts2[0])
|
||||
resp, err = genForm("PUT", addrURL2,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts2[1]}})
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http success response for second IP")
|
||||
|
||||
// Check that we can get two IPs for that name
|
||||
ip2, _, _ := net.ParseCIDR(testAddr2)
|
||||
foundIP, err = zone.LookupName(successTestName)
|
||||
wt.AssertNoErr(t, err)
|
||||
if len(foundIP) != 2 {
|
||||
t.Logf("IPs found: %s", foundIP)
|
||||
t.Fatalf("Unexpected result length: received %d responses", len(foundIP))
|
||||
}
|
||||
if !(foundIP[0].IP().Equal(ip) || foundIP[0].IP().Equal(ip2)) {
|
||||
t.Logf("IPs found: %s", foundIP)
|
||||
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
|
||||
}
|
||||
if !(foundIP[1].IP().Equal(ip) || foundIP[1].IP().Equal(ip2)) {
|
||||
t.Logf("IPs found: %s", foundIP)
|
||||
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
|
||||
}
|
||||
|
||||
// Delete the address
|
||||
resp, err = genForm("DELETE", addrURL, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
|
||||
|
||||
// Check that the address is still resolvable.
|
||||
x, err := zone.LookupName(successTestName)
|
||||
t.Logf("Got %s", x)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
// Delete the address record mentioning the other container
|
||||
resp, err = genForm("DELETE", otherURL, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
|
||||
|
||||
// Delete the second IP
|
||||
resp, err = genForm("DELETE", addrURL2, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
|
||||
|
||||
// Check that the address is gone
|
||||
x, err = zone.LookupName(successTestName)
|
||||
t.Logf("Got %s", x)
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), "fully-removed address")
|
||||
|
||||
// Would like to shut down the http server at the end of this test
|
||||
// but it's complicated.
|
||||
// See https://groups.google.com/forum/#!topic/golang-nuts/vLHWa5sHnCE
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import "net"
|
||||
|
||||
// A basic mDNS service
|
||||
type ZoneMDNS interface {
|
||||
// Start the service
|
||||
Start(*net.Interface) error
|
||||
// Stop the service
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// A mDNS server
|
||||
type ZoneMDNSServer interface {
|
||||
ZoneMDNS
|
||||
// Return the Zone database used by the server
|
||||
Zone() Zone
|
||||
}
|
||||
|
||||
// A mDNS client interface
|
||||
type ZoneMDNSClient interface {
|
||||
ZoneMDNS
|
||||
ZoneLookup
|
||||
// Perform an insistent lookup for a name
|
||||
InsistentLookupName(name string) ([]ZoneRecord, error)
|
||||
// Perform an insistent lookup for a reverse address
|
||||
InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Portions of this code taken from github.com/armon/mdns
|
||||
|
||||
const (
|
||||
ipv4mdns = "224.0.0.251" // link-local multicast address
|
||||
mdnsPort = 5353 // mDNS assigned port
|
||||
// We wait this long to hear a response from other mDNS servers on
|
||||
// the network.
|
||||
mDNSTimeout = 500 * time.Millisecond
|
||||
MaxDuration = time.Duration(math.MaxInt64)
|
||||
MailboxSize = 16
|
||||
)
|
||||
|
||||
var (
|
||||
ipv4Addr = &net.UDPAddr{
|
||||
IP: net.ParseIP(ipv4mdns),
|
||||
Port: mdnsPort,
|
||||
}
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
name string
|
||||
addr net.IP
|
||||
ttl int
|
||||
err error
|
||||
}
|
||||
|
||||
func (r Response) Name() string { return r.name }
|
||||
func (r Response) IP() net.IP { return r.addr }
|
||||
func (r Response) Priority() int { return 0 }
|
||||
func (r Response) Weight() int { return 0 }
|
||||
func (r Response) TTL() int { return r.ttl }
|
||||
|
||||
func (r Response) Equal(r2 *Response) bool {
|
||||
if r.name != r2.name {
|
||||
return false
|
||||
}
|
||||
if !r.addr.Equal(r2.addr) {
|
||||
return false
|
||||
}
|
||||
if r.err != r2.err {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r Response) String() string {
|
||||
var buf bytes.Buffer
|
||||
if r.err != nil {
|
||||
fmt.Fprintf(&buf, "%s", r.err)
|
||||
} else {
|
||||
if len(r.Name()) > 0 {
|
||||
fmt.Fprintf(&buf, "%s", r.Name())
|
||||
}
|
||||
if !r.IP().IsUnspecified() {
|
||||
fmt.Fprintf(&buf, "[%s]", r.IP())
|
||||
}
|
||||
if r.ttl > 0 {
|
||||
fmt.Fprintf(&buf, "(TTL:%d)", r.TTL())
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type responseInfo struct {
|
||||
timeout time.Time // if no answer by this time, give up
|
||||
insistent bool // insistent queries are not removed on the first reply
|
||||
ch chan<- *Response
|
||||
}
|
||||
|
||||
// Represents one query that we have sent for one name.
|
||||
// If we, internally, get several requests for the same name while we have
|
||||
// a query in flight, then we don't want to send more queries out.
|
||||
// Invariant on responseInfos: they are in ascending order of timeout.
|
||||
type inflightQuery struct {
|
||||
name string
|
||||
id uint16 // the DNS message ID
|
||||
responseInfos []*responseInfo
|
||||
}
|
||||
|
||||
type MDNSClient struct {
|
||||
// note unorthodox use of 'Server' class in client logic - using it to
|
||||
// listen on a multicast socket and call callback whenever a message comes in.
|
||||
listener *dns.Server
|
||||
conn *net.UDPConn
|
||||
addr *net.UDPAddr
|
||||
inflight map[string]*inflightQuery
|
||||
actionChan chan<- MDNSAction
|
||||
}
|
||||
|
||||
type mDNSQueryInfo struct {
|
||||
name string
|
||||
querytype uint16
|
||||
responseCh chan<- *Response
|
||||
}
|
||||
|
||||
func NewMDNSClient() (*MDNSClient, error) {
|
||||
return &MDNSClient{
|
||||
addr: ipv4Addr,
|
||||
inflight: make(map[string]*inflightQuery)}, nil
|
||||
}
|
||||
|
||||
func (c *MDNSClient) Start(ifi *net.Interface) (err error) {
|
||||
if c.conn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
multicast, err := LinkLocalMulticastListener(ifi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// Don't want to handle queries here, so filter anything out that isn't a response
|
||||
if len(r.Answer) > 0 {
|
||||
c.ResponseCallback(r)
|
||||
}
|
||||
}
|
||||
|
||||
actionChan := make(chan MDNSAction, MailboxSize)
|
||||
c.actionChan = actionChan
|
||||
go c.actorLoop(actionChan)
|
||||
|
||||
c.listener = &dns.Server{Unsafe: true, PacketConn: multicast, Handler: dns.HandlerFunc(handleMDNS)}
|
||||
go c.listener.ActivateAndServe()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MDNSClient) Stop() error {
|
||||
c.actionChan <- nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func LinkLocalMulticastListener(ifi *net.Interface) (net.PacketConn, error) {
|
||||
return net.ListenMulticastUDP("udp", ifi, ipv4Addr)
|
||||
}
|
||||
|
||||
// ACTOR client API
|
||||
|
||||
type MDNSAction func()
|
||||
|
||||
// Async
|
||||
func (c *MDNSClient) SendQuery(name string, querytype uint16, insistent bool, responseCh chan<- *Response) {
|
||||
c.actionChan <- func() {
|
||||
query, found := c.inflight[name]
|
||||
if !found {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(name, querytype)
|
||||
m.RecursionDesired = false
|
||||
buf, err := m.Pack()
|
||||
if err != nil {
|
||||
responseCh <- &Response{err: err}
|
||||
close(responseCh)
|
||||
return
|
||||
}
|
||||
query = &inflightQuery{
|
||||
name: name,
|
||||
id: m.Id,
|
||||
}
|
||||
if _, err = c.conn.WriteTo(buf, c.addr); err != nil {
|
||||
responseCh <- &Response{err: err}
|
||||
close(responseCh)
|
||||
return
|
||||
}
|
||||
c.inflight[name] = query
|
||||
}
|
||||
info := &responseInfo{
|
||||
ch: responseCh,
|
||||
timeout: time.Now().Add(mDNSTimeout),
|
||||
insistent: insistent,
|
||||
}
|
||||
// Invariant on responseInfos: they are in ascending order of
|
||||
// timeout. Since we use a fixed interval from Now(), this
|
||||
// must be after all existing timeouts.
|
||||
query.responseInfos = append(query.responseInfos, info)
|
||||
}
|
||||
}
|
||||
|
||||
// Async - called from dns library multiplexer
|
||||
func (c *MDNSClient) ResponseCallback(r *dns.Msg) {
|
||||
c.actionChan <- func() {
|
||||
for _, answer := range r.Answer {
|
||||
var name string
|
||||
var res *Response
|
||||
|
||||
switch rr := answer.(type) {
|
||||
case *dns.A:
|
||||
name = rr.Hdr.Name
|
||||
res = &Response{name: name, addr: rr.A, ttl: int(rr.Hdr.Ttl)}
|
||||
case *dns.PTR:
|
||||
name = rr.Hdr.Name
|
||||
raddr, _ := raddrToIP(name)
|
||||
res = &Response{name: rr.Ptr, addr: raddr, ttl: int(rr.Hdr.Ttl)}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if query, found := c.inflight[name]; found {
|
||||
var newResponseInfos []*responseInfo
|
||||
for _, resp := range query.responseInfos {
|
||||
resp.ch <- res
|
||||
// insistent queries are not removed on the first reply, but on the timeout
|
||||
if resp.insistent {
|
||||
newResponseInfos = append(newResponseInfos, resp)
|
||||
} else {
|
||||
close(resp.ch)
|
||||
}
|
||||
}
|
||||
if len(newResponseInfos) == 0 {
|
||||
delete(c.inflight, name)
|
||||
} else {
|
||||
query.responseInfos = newResponseInfos
|
||||
}
|
||||
} else {
|
||||
// We've received a response that didn't match a query
|
||||
// Do we want to cache it?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR server
|
||||
|
||||
// Check all in-flight queries, close all that have already timed out,
|
||||
// and return the duration until the next timeout
|
||||
func (c *MDNSClient) checkInFlightQueries() time.Duration {
|
||||
now := time.Now()
|
||||
after := MaxDuration
|
||||
for name, query := range c.inflight {
|
||||
// Invariant on responseInfos: they are in ascending order of timeout.
|
||||
numClosed := 0
|
||||
for _, item := range query.responseInfos {
|
||||
duration := item.timeout.Sub(now)
|
||||
if duration <= 0 { // timed out
|
||||
close(item.ch)
|
||||
numClosed++
|
||||
} else {
|
||||
if duration < after {
|
||||
after = duration
|
||||
}
|
||||
break // don't need to look at any more for this query
|
||||
}
|
||||
}
|
||||
// Remove timed-out items from the slice
|
||||
query.responseInfos = query.responseInfos[numClosed:]
|
||||
if len(query.responseInfos) == 0 {
|
||||
delete(c.inflight, name)
|
||||
}
|
||||
}
|
||||
return after
|
||||
}
|
||||
|
||||
func (c *MDNSClient) actorLoop(actionChan <-chan MDNSAction) {
|
||||
timer := time.NewTimer(MaxDuration)
|
||||
run := func() { timer.Reset(c.checkInFlightQueries()) }
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
if action == nil {
|
||||
c.listener.Shutdown()
|
||||
break loop
|
||||
} else {
|
||||
action()
|
||||
run()
|
||||
}
|
||||
case <-timer.C:
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
||||
// Close all response channels at termination
|
||||
for _, query := range c.inflight {
|
||||
for _, item := range query.responseInfos {
|
||||
close(item.ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
successTestName = "test1.weave."
|
||||
failTestName = "test2.weave."
|
||||
testAddr = net.ParseIP("9.8.7.6")
|
||||
testInAddr = "6.7.8.9.in-addr.arpa."
|
||||
)
|
||||
|
||||
func minimalServer(w dns.ResponseWriter, req *dns.Msg) {
|
||||
|
||||
sendAnswer := func(m *dns.Msg) {
|
||||
buf, err := m.Pack()
|
||||
CheckFatal(err)
|
||||
if buf == nil {
|
||||
log.Fatal("Nil buffer")
|
||||
}
|
||||
//log.Println("minimalServer sending:", buf)
|
||||
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
|
||||
sendconn, err := net.DialUDP("udp", nil, ipv4Addr)
|
||||
CheckFatal(err)
|
||||
|
||||
_, err = sendconn.Write(buf)
|
||||
sendconn.Close()
|
||||
CheckFatal(err)
|
||||
}
|
||||
|
||||
//log.Println("minimalServer received:", req)
|
||||
if len(req.Answer) > 0 {
|
||||
return // Only interested in questions.
|
||||
}
|
||||
if len(req.Question) != 1 {
|
||||
return // We only handle single-question messages
|
||||
}
|
||||
|
||||
if req.Question[0].Qtype == dns.TypeA && req.Question[0].Name == successTestName {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET, Ttl: 3600}
|
||||
a := &dns.A{Hdr: hdr, A: testAddr}
|
||||
m.Answer = append(m.Answer, a)
|
||||
sendAnswer(m)
|
||||
} else if req.Question[0].Qtype == dns.TypePTR && req.Question[0].Name == testInAddr {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET, Ttl: 3600}
|
||||
a := &dns.PTR{Hdr: hdr, Ptr: successTestName}
|
||||
m.Answer = append(m.Answer, a)
|
||||
sendAnswer(m)
|
||||
}
|
||||
}
|
||||
|
||||
func RunLocalMulticastServer() (*dns.Server, error) {
|
||||
multicast, err := LinkLocalMulticastListener(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := &dns.Server{
|
||||
PacketConn: multicast,
|
||||
Handler: dns.HandlerFunc(minimalServer),
|
||||
ReadTimeout: 100 * time.Millisecond}
|
||||
go server.ActivateAndServe()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func setup(t *testing.T) (*MDNSClient, *dns.Server, error) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
|
||||
server, err := RunLocalMulticastServer()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to run test server: %s. No default multicast interface?", err)
|
||||
}
|
||||
|
||||
mdnsClient, err := NewMDNSClient()
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsClient.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
return mdnsClient, server, err
|
||||
}
|
||||
|
||||
func (c *MDNSClient) Shutdown() {
|
||||
c.actionChan <- nil
|
||||
}
|
||||
|
||||
type testContext struct {
|
||||
receivedAddr net.IP
|
||||
receivedCount int
|
||||
channel chan *Response
|
||||
}
|
||||
|
||||
func newTestContext() *testContext {
|
||||
return &testContext{channel: make(chan *Response, 4)}
|
||||
}
|
||||
|
||||
func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *Response) {
|
||||
if !channelOk {
|
||||
c.channel = nil
|
||||
return
|
||||
}
|
||||
wt.AssertNoErr(t, resp.err)
|
||||
log.Printf("Got address response %s addr %s", resp.Name(), resp.IP())
|
||||
c.receivedAddr = resp.IP()
|
||||
c.receivedCount++
|
||||
}
|
||||
|
||||
func TestSimpleQuery(t *testing.T) {
|
||||
log.Println("TestSimpleQuery starting")
|
||||
mdnsClient, server, _ := setup(t)
|
||||
defer mdnsClient.Shutdown()
|
||||
defer server.Shutdown()
|
||||
|
||||
context := newTestContext()
|
||||
|
||||
// First, a test we expect to succeed
|
||||
mdnsClient.SendQuery(successTestName, dns.TypeA, false, context.channel)
|
||||
for resp := range context.channel {
|
||||
context.checkResponse(t, true, resp)
|
||||
}
|
||||
|
||||
if !context.receivedAddr.Equal(testAddr) || context.receivedCount != 1 {
|
||||
t.Fatal("Unexpected result for", successTestName, context.receivedAddr, context.receivedCount)
|
||||
}
|
||||
|
||||
// Now, a test we expect to time out with no responses
|
||||
context = newTestContext()
|
||||
mdnsClient.SendQuery("test2.weave.", dns.TypeA, false, context.channel)
|
||||
for resp := range context.channel {
|
||||
context.checkResponse(t, true, resp)
|
||||
}
|
||||
|
||||
if context.receivedCount > 0 {
|
||||
t.Fatal("Unexpected result for test2.weave", context.receivedAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelQuery(t *testing.T) {
|
||||
log.Println("TestParallelQuery starting")
|
||||
mdnsClient, server, _ := setup(t)
|
||||
defer mdnsClient.Shutdown()
|
||||
defer server.Shutdown()
|
||||
|
||||
context1 := newTestContext()
|
||||
context2 := newTestContext()
|
||||
|
||||
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context1.channel)
|
||||
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context2.channel)
|
||||
timeout := time.After(2 * time.Second)
|
||||
outerloop:
|
||||
for context1.channel != nil || context2.channel != nil {
|
||||
select {
|
||||
case resp, ok := <-context1.channel:
|
||||
context1.checkResponse(t, ok, resp)
|
||||
case resp, ok := <-context2.channel:
|
||||
context2.checkResponse(t, ok, resp)
|
||||
case <-timeout:
|
||||
break outerloop
|
||||
}
|
||||
}
|
||||
|
||||
if !context1.receivedAddr.Equal(testAddr) || !context2.receivedAddr.Equal(testAddr) || context1.receivedCount != 1 || context2.receivedCount != 1 {
|
||||
t.Fatal("Unexpected result for", successTestName, context1.receivedAddr, context2.receivedAddr, context1.receivedCount, context2.receivedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsLookup(t *testing.T) {
|
||||
mdnsClient, server, _ := setup(t)
|
||||
defer mdnsClient.Shutdown()
|
||||
defer server.Shutdown()
|
||||
|
||||
ips, err := mdnsClient.LookupName(successTestName)
|
||||
wt.AssertNoErr(t, err)
|
||||
if !testAddr.Equal(ips[0].IP()) {
|
||||
t.Fatalf("Returned address incorrect %s", ips)
|
||||
}
|
||||
|
||||
ips, err = mdnsClient.LookupName("foo.example.com.")
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), "unknown hostname")
|
||||
|
||||
names, err := mdnsClient.LookupInaddr(testInAddr)
|
||||
wt.AssertNoErr(t, err)
|
||||
if !(successTestName == names[0].Name()) {
|
||||
t.Fatalf("Expected name %s, got %s", successTestName, names)
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
func mdnsLookup(client *MDNSClient, name string, qtype uint16, insistent bool) ([]ZoneRecord, error) {
|
||||
var responses []*Response
|
||||
channel := make(chan *Response)
|
||||
client.SendQuery(name, qtype, insistent, channel)
|
||||
|
||||
for resp := range channel {
|
||||
if err := resp.err; err != nil {
|
||||
Debug.Printf("[mdns] Error for query type %s name %s: %s", dns.TypeToString[qtype], name, err)
|
||||
return nil, err
|
||||
}
|
||||
Debug.Printf("[mdns] Got response name for %s-query about name '%s': name '%s', addr '%s'",
|
||||
dns.TypeToString[qtype], name, resp.name, resp.addr)
|
||||
|
||||
if !insistent {
|
||||
return []ZoneRecord{resp}, nil // non-background queries return the first response...
|
||||
}
|
||||
|
||||
// add the response, avoiding duplicates
|
||||
// TODO: replace this linear search by something better... (not a big deal as we don't expect long responses)
|
||||
duplicate := false
|
||||
for _, r := range responses {
|
||||
if r.Equal(resp) {
|
||||
duplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !duplicate {
|
||||
responses = append(responses, resp)
|
||||
}
|
||||
}
|
||||
|
||||
if len(responses) == 0 {
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
|
||||
// convert []Response to []ZoneRecord
|
||||
res := make([]ZoneRecord, len(responses))
|
||||
for i, rr := range responses {
|
||||
res[i] = ZoneRecord(rr)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Perform a lookup for a name, returning either an IP address or an error
|
||||
func (client *MDNSClient) LookupName(name string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, name, dns.TypeA, false)
|
||||
}
|
||||
|
||||
// Perform a lookup for an IP address, returning either a name or an error
|
||||
func (client *MDNSClient) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, inaddr, dns.TypePTR, false)
|
||||
}
|
||||
|
||||
// Perform lookup for a name, returning either a list of IP addresses or an error
|
||||
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
|
||||
func (client *MDNSClient) InsistentLookupName(name string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, name, dns.TypeA, true)
|
||||
}
|
||||
|
||||
// Perform an insistent lookup for an IP address, returning either a list of names or an error
|
||||
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
|
||||
func (client *MDNSClient) InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, inaddr, dns.TypePTR, true)
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type MDNSServer struct {
|
||||
localAddrs []net.Addr
|
||||
sendconn *net.UDPConn
|
||||
srv *dns.Server
|
||||
zone Zone
|
||||
allowLocal bool
|
||||
ttl int
|
||||
}
|
||||
|
||||
// Create a new mDNS server
|
||||
// Nothing will be done (including port bindings) until you `Start()` the server
|
||||
func NewMDNSServer(zone Zone, local bool, ttl int) (*MDNSServer, error) {
|
||||
return &MDNSServer{
|
||||
zone: zone,
|
||||
allowLocal: local,
|
||||
ttl: ttl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Return true if testaddr is a UDP address with IP matching my local i/f
|
||||
func (s *MDNSServer) addrIsLocal(testaddr net.Addr) bool {
|
||||
if udpaddr, ok := testaddr.(*net.UDPAddr); ok {
|
||||
for _, localaddr := range s.localAddrs {
|
||||
if ipnetlocal, ok := localaddr.(*net.IPNet); ok {
|
||||
if ipnetlocal.IP.Equal(udpaddr.IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Start the mDNS server
|
||||
func (s *MDNSServer) Start(ifi *net.Interface) (err error) {
|
||||
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
|
||||
s.sendconn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := LinkLocalMulticastListener(ifi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ifi == nil {
|
||||
s.localAddrs, err = net.InterfaceAddrs()
|
||||
} else {
|
||||
s.localAddrs, err = ifi.Addrs()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleLocal := s.makeHandler(dns.TypeA,
|
||||
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
|
||||
if ips, err := zone.LookupName(q.Name); err == nil {
|
||||
return makeAddressReply(r, q, ips, s.ttl)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
handleReverse := s.makeHandler(dns.TypePTR,
|
||||
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
|
||||
if names, err := zone.LookupInaddr(q.Name); err == nil {
|
||||
return makePTRReply(r, q, names, s.ttl)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
mux := dns.NewServeMux()
|
||||
mux.HandleFunc(s.zone.Domain(), handleLocal)
|
||||
mux.HandleFunc(RDNSDomain, handleReverse)
|
||||
|
||||
s.srv = &dns.Server{
|
||||
Listener: nil,
|
||||
PacketConn: conn,
|
||||
Handler: mux,
|
||||
}
|
||||
go s.srv.ActivateAndServe()
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop the mDNS server
|
||||
func (s *MDNSServer) Stop() error {
|
||||
return s.srv.Shutdown()
|
||||
}
|
||||
|
||||
func (s *MDNSServer) Zone() Zone {
|
||||
return s.zone
|
||||
}
|
||||
|
||||
type LookupFunc func(ZoneLookup, *dns.Msg, *dns.Question) *dns.Msg
|
||||
|
||||
func (s *MDNSServer) makeHandler(qtype uint16, lookup LookupFunc) dns.HandlerFunc {
|
||||
return func(rw dns.ResponseWriter, r *dns.Msg) {
|
||||
// Handle only questions, ignore answers. We might also ignore
|
||||
// questions that arise locally (i.e., that come from an IP we
|
||||
// think is local), but in the interest of avoiding
|
||||
// complication, and easier testing, this is elided on the
|
||||
// assumption that the client wouldn't ask if it already knew
|
||||
// the answer, and if it does ask, it'll be happy to get an
|
||||
// answer.
|
||||
remoteAddr := rw.RemoteAddr()
|
||||
if s.addrIsLocal(remoteAddr) && !s.allowLocal {
|
||||
Debug.Printf("[mdns] srv: mDNS query from local host ('%s'): ignored", remoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: use any Answer in the query for adding records in the Zone database...
|
||||
if len(r.Answer) == 0 && len(r.Question) > 0 {
|
||||
q := &r.Question[0]
|
||||
if q.Qtype == qtype {
|
||||
Debug.Printf("[mdns msgid %d] srv: trying to answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
|
||||
if m := lookup(s.zone, r, q); m != nil {
|
||||
Debug.Printf("[mdns msgid %d] srv: found local answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
|
||||
if err := s.sendResponse(m); err != nil {
|
||||
Warning.Printf("[mdns msgid %d] srv: error writing mDNS response to %v", r.MsgHdr.Id, s.sendconn)
|
||||
} else {
|
||||
Debug.Printf("[mdns msgid %d] srv: response sent: %d answers", r.MsgHdr.Id, len(m.Answer))
|
||||
}
|
||||
} else {
|
||||
Debug.Printf("[mdns msgid %d] srv: no local answer for answering mDNS query '%s'",
|
||||
r.MsgHdr.Id, q.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MDNSServer) sendResponse(m *dns.Msg) error {
|
||||
buf, err := m.Pack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.sendconn.WriteTo(buf, ipv4Addr)
|
||||
return err
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func TestServerSimpleQuery(t *testing.T) {
|
||||
var (
|
||||
testRecord1 = Record{"test.weave.local.", net.ParseIP("10.20.20.10"), 0, 0, 0}
|
||||
testRecord2 = Record{"test.weave.local.", net.ParseIP("10.20.20.20"), 0, 0, 0}
|
||||
testInAddr1 = "10.20.20.10.in-addr.arpa."
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestServerSimpleQuery starting")
|
||||
|
||||
mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1, testRecord2})
|
||||
mdnsServer, err := NewMDNSServer(mzone, true, DefaultLocalTTL)
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsServer.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
defer mdnsServer.Stop()
|
||||
|
||||
var receivedAddrs []net.IP
|
||||
receivedName := ""
|
||||
recvChan := make(chan interface{})
|
||||
receivedCount := 0
|
||||
|
||||
// Implement a minimal listener for responses
|
||||
multicast, err := LinkLocalMulticastListener(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// Only handle responses here
|
||||
if len(r.Answer) > 0 {
|
||||
t.Logf("Received %d answer(s)", len(r.Answer))
|
||||
for _, answer := range r.Answer {
|
||||
recvChan <- answer
|
||||
}
|
||||
recvChan <- "ok"
|
||||
}
|
||||
}
|
||||
|
||||
sendQuery := func(name string, querytype uint16) {
|
||||
receivedAddrs = make([]net.IP, 0)
|
||||
receivedName = ""
|
||||
receivedCount = 0
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(name, querytype)
|
||||
m.RecursionDesired = false
|
||||
buf, err := m.Pack()
|
||||
wt.AssertNoErr(t, err)
|
||||
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
wt.AssertNoErr(t, err)
|
||||
Debug.Printf("Sending UDP packet to %s", ipv4Addr)
|
||||
_, err = conn.WriteTo(buf, ipv4Addr)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
Debug.Printf("Waiting for response")
|
||||
for {
|
||||
select {
|
||||
case x := <-recvChan:
|
||||
switch rr := x.(type) {
|
||||
case *dns.A:
|
||||
t.Logf("... A:\n%+v", rr)
|
||||
receivedAddrs = append(receivedAddrs, rr.A)
|
||||
receivedCount++
|
||||
case *dns.PTR:
|
||||
t.Logf("... PTR:\n%+v", rr)
|
||||
receivedName = rr.Ptr
|
||||
receivedCount++
|
||||
case string:
|
||||
return
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
Debug.Printf("Timeout while waiting for response")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listener := &dns.Server{
|
||||
Unsafe: true,
|
||||
PacketConn: multicast,
|
||||
Handler: dns.HandlerFunc(handleMDNS),
|
||||
ReadTimeout: 100 * time.Millisecond}
|
||||
go listener.ActivateAndServe()
|
||||
defer listener.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for server to get going
|
||||
|
||||
Debug.Printf("Checking that we get 2 IPs fo name '%s' [A]", testRecord1.Name())
|
||||
sendQuery(testRecord1.Name(), dns.TypeA)
|
||||
if receivedCount != 2 {
|
||||
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
|
||||
}
|
||||
if !(receivedAddrs[0].Equal(testRecord1.IP()) || receivedAddrs[0].Equal(testRecord2.IP())) {
|
||||
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
|
||||
}
|
||||
if !(receivedAddrs[1].Equal(testRecord1.IP()) || receivedAddrs[1].Equal(testRecord2.IP())) {
|
||||
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
|
||||
}
|
||||
|
||||
Debug.Printf("Checking that 'testfail.weave.' [A] gets no answers")
|
||||
sendQuery("testfail.weave.", dns.TypeA)
|
||||
if receivedCount != 0 {
|
||||
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
|
||||
}
|
||||
|
||||
Debug.Printf("Checking that '%s' [PTR] gets one name", testInAddr1)
|
||||
sendQuery(testInAddr1, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
|
||||
} else if !(testRecord1.Name() == receivedName) {
|
||||
t.Fatalf("Expected answer %s to query for %s, got %s", testRecord1.Name(), testInAddr1, receivedName)
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// Check that we can use a regular mDNS server with a regular mDNS client
|
||||
func TestClientServerSimpleQuery(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
|
||||
testRecord1 := Record{"test.weave.local.", net.ParseIP("10.2.2.1"), 0, 0, 0}
|
||||
testInAddr1 := "1.2.2.10.in-addr.arpa."
|
||||
|
||||
mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1})
|
||||
mdnsServer, err := NewMDNSServer(mzone, true, DefaultLocalTTL)
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsServer.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
defer mdnsServer.Stop()
|
||||
|
||||
var receivedAddr net.IP
|
||||
var receivedName string
|
||||
receivedCount := 0
|
||||
|
||||
mdnsCli, err := NewMDNSClient()
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsCli.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
sendQuery := func(name string, querytype uint16) {
|
||||
receivedAddr = nil
|
||||
receivedName = ""
|
||||
receivedCount = 0
|
||||
Debug.Printf("Sending query...")
|
||||
switch querytype {
|
||||
case dns.TypeA:
|
||||
r, err := mdnsCli.LookupName(name)
|
||||
if len(r) > 0 {
|
||||
receivedAddr = r[0].IP()
|
||||
if err == nil {
|
||||
receivedCount++
|
||||
}
|
||||
}
|
||||
case dns.TypePTR:
|
||||
r, err := mdnsCli.LookupInaddr(name)
|
||||
if len(r) > 0 {
|
||||
receivedName = r[0].Name()
|
||||
if err == nil {
|
||||
receivedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for server to get going
|
||||
|
||||
Debug.Printf("Query: %s dns.TypeA", testRecord1.Name())
|
||||
sendQuery(testRecord1.Name(), dns.TypeA)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
|
||||
}
|
||||
if !receivedAddr.Equal(testRecord1.IP()) {
|
||||
t.Fatalf("Unexpected result %s for %s", receivedAddr, testRecord1.Name())
|
||||
}
|
||||
|
||||
Debug.Printf("Query: testfail.weave. dns.TypeA")
|
||||
sendQuery("testfail.weave.", dns.TypeA)
|
||||
if receivedCount != 0 {
|
||||
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
|
||||
}
|
||||
|
||||
Debug.Printf("Query: %s dns.TypePTR", testInAddr1)
|
||||
sendQuery(testInAddr1, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
|
||||
} else if !(testRecord1.Name() == receivedName) {
|
||||
t.Fatalf("Expected answer %s to query for %s, got %s", testRecord1.Name(), testInAddr1, receivedName)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we can use a use "insistent" queries
|
||||
func TestClientServerInsistentQuery(t *testing.T) {
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
|
||||
testRecord1 := Record{"test.weave.local.", net.ParseIP("10.2.2.1"), 0, 0, 0}
|
||||
testInAddr1 := "1.2.2.10.in-addr.arpa."
|
||||
testRecord2 := Record{"test.weave.local.", net.ParseIP("10.2.2.2"), 0, 0, 0}
|
||||
testInAddr2 := "2.2.2.10.in-addr.arpa."
|
||||
|
||||
mzone1 := newMockedZoneWithRecords([]ZoneRecord{testRecord1})
|
||||
mdnsServer1, err := NewMDNSServer(mzone1, true, DefaultLocalTTL)
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsServer1.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
defer mdnsServer1.Stop()
|
||||
|
||||
mzone2 := newMockedZoneWithRecords([]ZoneRecord{testRecord2})
|
||||
mdnsServer2, err := NewMDNSServer(mzone2, true, DefaultLocalTTL)
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsServer2.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
defer mdnsServer2.Stop()
|
||||
|
||||
// create a third server with exactly the same info as the second server (so we can test duplicates removals)
|
||||
mdnsServer3, err := NewMDNSServer(mzone2, true, DefaultLocalTTL)
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsServer3.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
defer mdnsServer3.Stop()
|
||||
|
||||
var receivedAddrs []ZoneRecord
|
||||
var receivedNames []ZoneRecord
|
||||
receivedCount := 0
|
||||
|
||||
mdnsCli, err := NewMDNSClient()
|
||||
wt.AssertNoErr(t, err)
|
||||
err = mdnsCli.Start(nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
sendQuery := func(name string, querytype uint16) {
|
||||
receivedAddrs = nil
|
||||
receivedNames = nil
|
||||
receivedCount = 0
|
||||
Debug.Printf("Sending query...")
|
||||
switch querytype {
|
||||
case dns.TypeA:
|
||||
receivedAddrs, err = mdnsCli.InsistentLookupName(name)
|
||||
if err == nil {
|
||||
receivedCount = len(receivedAddrs)
|
||||
}
|
||||
case dns.TypePTR:
|
||||
receivedNames, err = mdnsCli.InsistentLookupInaddr(name)
|
||||
if err == nil {
|
||||
receivedCount = len(receivedNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for server to get going
|
||||
|
||||
Debug.Printf("Query: %s dns.TypeA", testRecord1.Name())
|
||||
sendQuery(testRecord1.Name(), dns.TypeA)
|
||||
if receivedCount != 2 {
|
||||
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
|
||||
}
|
||||
|
||||
Debug.Printf("Query: testfail.weave. dns.TypeA")
|
||||
sendQuery("testfail.weave.", dns.TypeA)
|
||||
if receivedCount != 0 {
|
||||
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
|
||||
}
|
||||
|
||||
Debug.Printf("Query: %s dns.TypePTR", testInAddr1)
|
||||
sendQuery(testInAddr1, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
|
||||
}
|
||||
|
||||
Debug.Printf("Query: %s dns.TypePTR", testInAddr2)
|
||||
sendQuery(testInAddr2, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr2, receivedCount)
|
||||
}
|
||||
}
|
||||
@@ -1,507 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
const (
|
||||
testSocketTimeout = 100 // in millisecs
|
||||
)
|
||||
|
||||
// Warn about some methods that some day should be implemented...
|
||||
func notImplWarn() { Warning.Printf("Mocked method. Not implemented.") }
|
||||
|
||||
// A mocked Zone that always returns the same records
|
||||
// * it does not send/receive any mDNS query
|
||||
type mockedZoneWithRecords struct {
|
||||
sync.RWMutex
|
||||
|
||||
records []ZoneRecord
|
||||
|
||||
// Statistics
|
||||
NumLookupsName int
|
||||
NumLookupsInaddr int
|
||||
}
|
||||
|
||||
func newMockedZoneWithRecords(zr []ZoneRecord) *mockedZoneWithRecords {
|
||||
return &mockedZoneWithRecords{records: zr}
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) Domain() string { return DefaultLocalDomain }
|
||||
func (mz *mockedZoneWithRecords) LookupName(name string) ([]ZoneRecord, error) {
|
||||
Debug.Printf("[mocked zone]: LookupName: returning records %s", mz.records)
|
||||
mz.Lock()
|
||||
defer mz.Unlock()
|
||||
|
||||
mz.NumLookupsName++
|
||||
var res []ZoneRecord
|
||||
for _, r := range mz.records {
|
||||
if r.Name() == name {
|
||||
res = append(res, r)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mz *mockedZoneWithRecords) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
Debug.Printf("[mocked zone]: LookupInaddr: returning records %s", mz.records)
|
||||
mz.Lock()
|
||||
defer mz.Unlock()
|
||||
|
||||
mz.NumLookupsInaddr++
|
||||
var res []ZoneRecord
|
||||
for _, r := range mz.records {
|
||||
revIP, err := raddrToIP(inaddr)
|
||||
if err != nil {
|
||||
return nil, newParseError("lookup address", inaddr)
|
||||
}
|
||||
if r.IP().Equal(revIP) {
|
||||
res = append(res, r)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mz *mockedZoneWithRecords) DomainLookupName(name string) ([]ZoneRecord, error) {
|
||||
return mz.LookupName(name)
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) DomainLookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
return mz.LookupInaddr(inaddr)
|
||||
}
|
||||
|
||||
// the following methods are not currently needed...
|
||||
func (mz *mockedZoneWithRecords) AddRecord(ident string, name string, ip net.IP) error {
|
||||
notImplWarn()
|
||||
return nil
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) DeleteRecords(ident string, name string, ip net.IP) int {
|
||||
notImplWarn()
|
||||
return 0
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) Status() string { notImplWarn(); return "nothing" }
|
||||
func (mz *mockedZoneWithRecords) ObserveName(name string, observer ZoneRecordObserver) error {
|
||||
notImplWarn()
|
||||
return nil
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) ObserveInaddr(inaddr string, observer ZoneRecordObserver) error {
|
||||
notImplWarn()
|
||||
return nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked mDNS server
|
||||
type mockedMDNSServer struct {
|
||||
zone Zone
|
||||
}
|
||||
|
||||
func newMockedMDNSServer(z Zone) *mockedMDNSServer { return &mockedMDNSServer{zone: z} }
|
||||
func (ms *mockedMDNSServer) Start(_ *net.Interface) error { return nil }
|
||||
func (ms *mockedMDNSServer) Stop() error { return nil }
|
||||
func (ms *mockedMDNSServer) Zone() Zone { return ms.zone }
|
||||
|
||||
// a useful mix of NewMockedZone and NewMockedMDNSServer
|
||||
func newMockedMDNSServerWithRecord(zr ZoneRecord) *mockedMDNSServer {
|
||||
return newMockedMDNSServer(newMockedZoneWithRecords([]ZoneRecord{zr}))
|
||||
}
|
||||
func newMockedMDNSServerWithRecords(zrs []ZoneRecord) *mockedMDNSServer {
|
||||
return newMockedMDNSServer(newMockedZoneWithRecords(zrs))
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked mDNS client.
|
||||
// This mock asks a group of (potentially mocked) mDNS servers
|
||||
type mockedMDNSClient struct {
|
||||
sync.RWMutex
|
||||
|
||||
servers map[ZoneMDNSServer]ZoneMDNSServer
|
||||
|
||||
// Statistics
|
||||
NumLookupsName int
|
||||
NumLookupsInaddr int
|
||||
NumInsLookupsName int
|
||||
NumInsLookupsInaddr int
|
||||
}
|
||||
|
||||
func newMockedMDNSClient(srvrs []*mockedMDNSServer) *mockedMDNSClient {
|
||||
r := mockedMDNSClient{
|
||||
servers: make(map[ZoneMDNSServer]ZoneMDNSServer),
|
||||
}
|
||||
if srvrs != nil {
|
||||
for _, server := range srvrs {
|
||||
r.servers[server] = server
|
||||
}
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) Start(ifi *net.Interface) error { return nil }
|
||||
func (mc *mockedMDNSClient) Stop() error { return nil }
|
||||
func (mc *mockedMDNSClient) Domain() string { return DefaultLocalDomain }
|
||||
func (mc *mockedMDNSClient) LookupName(name string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
// get a random result
|
||||
mc.NumLookupsName++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupName(name)
|
||||
if err == nil {
|
||||
return r, nil // return the first answer
|
||||
}
|
||||
}
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
// get a random result
|
||||
mc.NumLookupsInaddr++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupInaddr(inaddr)
|
||||
if err == nil {
|
||||
return r, nil // return the first answer
|
||||
}
|
||||
}
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) InsistentLookupName(name string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
var res []ZoneRecord
|
||||
mc.NumInsLookupsName++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupName(name)
|
||||
if err == nil {
|
||||
res = append(res, r...)
|
||||
}
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
var res []ZoneRecord
|
||||
mc.NumInsLookupsInaddr++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupInaddr(inaddr)
|
||||
if err == nil {
|
||||
res = append(res, r...)
|
||||
}
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) AddServer(srv ZoneMDNSServer) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
mc.servers[srv] = srv
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) RemoveServer(srv ZoneMDNSServer) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
delete(mc.servers, srv)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
type zbmEntry struct {
|
||||
Server *mockedMDNSServer
|
||||
Client *mockedMDNSClient
|
||||
Zone *ZoneDb
|
||||
}
|
||||
type zoneDbsWithMockedMDns []*zbmEntry
|
||||
|
||||
// Creates a group of (real) zone databases linked through mocked mDNS servers and clients
|
||||
// This effectively creates a cluster of WeaveDNS peers
|
||||
func newZoneDbsWithMockedMDns(num int, config ZoneConfig) zoneDbsWithMockedMDns {
|
||||
res := make(zoneDbsWithMockedMDns, num)
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
res[i] = new(zbmEntry)
|
||||
|
||||
Debug.Printf("[test] Creating mocked mDNS server #%d", i)
|
||||
res[i].Server = newMockedMDNSServer(nil)
|
||||
|
||||
Debug.Printf("[test] Creating mocked mDNS client #%d", i)
|
||||
res[i].Client = newMockedMDNSClient([]*mockedMDNSServer{})
|
||||
|
||||
cfg := config
|
||||
cfg.MDNSClient = res[i].Client
|
||||
cfg.MDNSServer = res[i].Server
|
||||
|
||||
Debug.Printf("[test] Creating ZoneDb #%d", i)
|
||||
res[i].Zone, _ = NewZoneDb(cfg)
|
||||
|
||||
// link the mDNS server to its zone
|
||||
// ZoneDbs are connected to mDNS in two directions: they use their mDNS
|
||||
// clients for asking other peers about names/IPs, and export their records
|
||||
// through a mDNS server.
|
||||
res[i].Server.zone = res[i].Zone
|
||||
}
|
||||
|
||||
// link all the clients to all the servers
|
||||
for i := 0; i < num; i++ {
|
||||
for j := 0; j < num; j++ {
|
||||
// with this check, the local client will not ask to the local server
|
||||
// this is not what we currently do, but can be convenient in some cases
|
||||
// for finding some bugs...
|
||||
if i != j {
|
||||
Debug.Printf("[test] Linking mocked mDNS client #%d to server #%d", i, j)
|
||||
res[i].Client.AddServer(res[j].Server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (zbs zoneDbsWithMockedMDns) Start() {
|
||||
for _, entry := range zbs {
|
||||
entry.Zone.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (zbs zoneDbsWithMockedMDns) Stop() {
|
||||
for _, entry := range zbs {
|
||||
entry.Zone.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (zbs zoneDbsWithMockedMDns) Flush() {
|
||||
for _, entry := range zbs {
|
||||
entry.Zone.refreshScheds.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked cache where we never find a single thing... ;)
|
||||
type mockedCache struct {
|
||||
NumGets int
|
||||
NumPuts int
|
||||
NumRemovals int
|
||||
}
|
||||
|
||||
func newMockedCache() *mockedCache { return &mockedCache{} }
|
||||
|
||||
func (c *mockedCache) Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error) {
|
||||
c.NumGets++
|
||||
return nil, nil
|
||||
}
|
||||
func (c *mockedCache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int {
|
||||
c.NumPuts++
|
||||
return 0
|
||||
}
|
||||
func (c *mockedCache) Remove(question *dns.Question) { c.NumRemovals++ }
|
||||
func (c *mockedCache) Purge() {}
|
||||
func (c *mockedCache) Clear() {}
|
||||
func (c *mockedCache) Len() int { return 0 }
|
||||
func (c *mockedCache) Capacity() int { return DefaultCacheLen }
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked fallback server
|
||||
type mockedFallback struct {
|
||||
CliConfig *dns.ClientConfig
|
||||
Addr string
|
||||
Port int
|
||||
|
||||
udpSrv *dns.Server
|
||||
tcpSrv *dns.Server
|
||||
}
|
||||
|
||||
// Create a mocked fallback server
|
||||
// You can use the server's `CliConfig` as the `UpstreamCfg` of a `DNSServer`...
|
||||
func newMockedFallback(udpH dns.HandlerFunc, tcpH dns.HandlerFunc) (*mockedFallback, error) {
|
||||
udpSrv, fallbackAddr, err := runLocalUDPServer("127.0.0.1:0", udpH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, fallbackPort, err := net.SplitHostPort(fallbackAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fallbackPortI, _ := strconv.Atoi(fallbackPort)
|
||||
|
||||
res := mockedFallback{
|
||||
udpSrv: udpSrv,
|
||||
Addr: fallbackAddr,
|
||||
Port: fallbackPortI,
|
||||
CliConfig: &dns.ClientConfig{
|
||||
Servers: []string{"127.0.0.1"},
|
||||
Port: fallbackPort,
|
||||
},
|
||||
}
|
||||
|
||||
if tcpH != nil {
|
||||
fallbackTCPAddr := fmt.Sprintf("127.0.0.1:%s", fallbackPort)
|
||||
res.tcpSrv, _, err = runLocalTCPServer(fallbackTCPAddr, tcpH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// Start the fallback server
|
||||
func (mf *mockedFallback) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop the fallback server
|
||||
func (mf *mockedFallback) Stop() error {
|
||||
if mf.tcpSrv != nil {
|
||||
mf.udpSrv.Shutdown()
|
||||
mf.udpSrv = nil
|
||||
}
|
||||
if mf.tcpSrv != nil {
|
||||
mf.tcpSrv.Shutdown()
|
||||
mf.udpSrv = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run a UDP fallback server
|
||||
func runLocalUDPServer(laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
|
||||
Debug.Printf("[mocked fallback] Starting fallback UDP server at %s", laddr)
|
||||
pc, err := net.ListenPacket("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
server := &dns.Server{PacketConn: pc, Handler: handler, ReadTimeout: testSocketTimeout * time.Millisecond}
|
||||
|
||||
go func() {
|
||||
server.ActivateAndServe()
|
||||
pc.Close()
|
||||
}()
|
||||
|
||||
Debug.Printf("[mocked fallback] Fallback UDP server listening at %s", pc.LocalAddr())
|
||||
return server, pc.LocalAddr().String(), nil
|
||||
}
|
||||
|
||||
// Run a TCP fallback server
|
||||
func runLocalTCPServer(laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
|
||||
Debug.Printf("[mocked fallback] Starting fallback TCP server at %s", laddr)
|
||||
laddrTCP, err := net.ResolveTCPAddr("tcp", laddr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
l, err := net.ListenTCP("tcp", laddrTCP)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
server := &dns.Server{Listener: l, Handler: handler, ReadTimeout: testSocketTimeout * time.Millisecond}
|
||||
|
||||
go func() {
|
||||
server.ActivateAndServe()
|
||||
l.Close()
|
||||
}()
|
||||
|
||||
Debug.Printf("[mocked fallback] Fallback TCP server listening at %s", l.Addr().String())
|
||||
return server, l.Addr().String(), nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
type mockedClock struct {
|
||||
*clock.Mock
|
||||
}
|
||||
|
||||
func newMockedClock() *mockedClock {
|
||||
return &mockedClock{clock.NewMock()}
|
||||
}
|
||||
|
||||
// Note: moving the clock forward does not take into account the data that could
|
||||
// be waiting in channels. So your code should not depend on the channels
|
||||
// for creating new timers. Otherwise, time travelling will not be reliable...
|
||||
func (clk *mockedClock) Forward(secs int) {
|
||||
Debug.Printf(">>>>>>> Moving clock forward %d seconds - Time traveling >>>>>>>", secs)
|
||||
clk.Add(time.Duration(secs) * time.Second)
|
||||
Debug.Printf("<<<<<<< Time travel finished! We are at %s <<<<<<<", clk.Now())
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// Perform a DNS query and assert the reply code, number or answers, etc
|
||||
func assertExchange(t *testing.T, z string, ty uint16, port int, minAnswers int, maxAnswers int, expErr int) (*dns.Msg, *dns.Msg) {
|
||||
wt.AssertNotEqualInt(t, port, 0, "invalid DNS server port")
|
||||
|
||||
c := &dns.Client{
|
||||
UDPSize: testUDPBufSize,
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.RecursionDesired = true
|
||||
m.SetQuestion(z, ty)
|
||||
m.SetEdns0(testUDPBufSize, false) // we don't want to play with truncation here...
|
||||
|
||||
lstAddr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
r, _, err := c.Exchange(m, lstAddr)
|
||||
t.Logf("Response from '%s':\n%+v\n", lstAddr, r)
|
||||
if err != nil {
|
||||
t.Errorf("Error when querying DNS server at %s: %s", lstAddr, err)
|
||||
}
|
||||
wt.AssertNoErr(t, err)
|
||||
if minAnswers == 0 && maxAnswers == 0 {
|
||||
wt.AssertStatus(t, r.Rcode, expErr, "DNS response code")
|
||||
} else {
|
||||
wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
|
||||
}
|
||||
answers := len(r.Answer)
|
||||
if minAnswers >= 0 && answers < minAnswers {
|
||||
wt.Fatalf(t, "Number of answers >= %d", minAnswers)
|
||||
}
|
||||
if maxAnswers >= 0 && answers > maxAnswers {
|
||||
wt.Fatalf(t, "Number of answers <= %d", maxAnswers)
|
||||
}
|
||||
return m, r
|
||||
}
|
||||
|
||||
// Assert that we have a response in the cache for a query `q`
|
||||
func assertInCache(t *testing.T, cache ZoneCache, q *dns.Msg, desc string) {
|
||||
r, err := cache.Get(q, maxUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotNil(t, r, fmt.Sprintf("value in the cache: %s", desc))
|
||||
}
|
||||
|
||||
// Assert that we have a response in the cache for a query `q`
|
||||
func assertNotLocalInCache(t *testing.T, cache ZoneCache, q *dns.Msg, desc string) {
|
||||
r, err := cache.Get(q, maxUDPSize)
|
||||
if !(r == nil && err == errNoLocalReplies) {
|
||||
t.Fatalf("Cache does not return a noLocalReplies error for query %s", q)
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that we do not have a response in the cache for a query `q`
|
||||
func assertNotInCache(t *testing.T, cache ZoneCache, q *dns.Msg, desc string) {
|
||||
r, err := cache.Get(q, maxUDPSize)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNil(t, r, fmt.Sprintf("value in the cache: %s\n%s", desc, r))
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
const (
|
||||
// Tombstones do not need to survive long periods of peer disconnection, as
|
||||
// we delete entries for disconnected peers. Therefore they just need to hang
|
||||
// around to account for propagation delay through gossip. We do need them
|
||||
// to hang around longer than the allowed clock skew (defined next). 30mins
|
||||
// tombstone timeout allows for 15mins max clock skew.
|
||||
tombstoneTimeout = time.Minute * 30
|
||||
|
||||
// Maximum age of acceptable gossip messages (to account for clock skew)
|
||||
gossipWindow = int64(tombstoneTimeout/time.Second) / 2
|
||||
|
||||
// Used by prog/weaver/main.go and proxy/create_container_interceptor.go
|
||||
DefaultDomain = "weave.local."
|
||||
)
|
||||
|
||||
// Nameserver: gossip-based, in memory nameserver.
|
||||
// - Holds a sorted list of (hostname, peer, container id, ip) tuples for the whole cluster.
|
||||
// - This list is gossiped & merged around the cluser.
|
||||
// - Lookup-by-hostname are O(nlogn), and return a (copy of a) slice of the entries
|
||||
// - Update is O(n) for now
|
||||
type Nameserver struct {
|
||||
sync.RWMutex
|
||||
ourName router.PeerName
|
||||
domain string
|
||||
gossip router.Gossip
|
||||
docker *docker.Client
|
||||
entries Entries
|
||||
peers *router.Peers
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func New(ourName router.PeerName, peers *router.Peers, docker *docker.Client, domain string) *Nameserver {
|
||||
ns := &Nameserver{
|
||||
ourName: ourName,
|
||||
domain: dns.Fqdn(domain),
|
||||
peers: peers,
|
||||
docker: docker,
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
if peers != nil {
|
||||
peers.OnGC(ns.PeerGone)
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func (n *Nameserver) SetGossip(gossip router.Gossip) {
|
||||
n.gossip = gossip
|
||||
}
|
||||
|
||||
func (n *Nameserver) Start() {
|
||||
go func() {
|
||||
ticker := time.Tick(tombstoneTimeout)
|
||||
for {
|
||||
select {
|
||||
case <-n.quit:
|
||||
return
|
||||
case <-ticker:
|
||||
n.deleteTombstones()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (n *Nameserver) Stop() {
|
||||
n.quit <- struct{}{}
|
||||
}
|
||||
|
||||
func (n *Nameserver) broadcastEntries(es ...Entry) error {
|
||||
if n.gossip != nil {
|
||||
return n.gossip.GossipBroadcast(&GossipData{
|
||||
Entries: Entries(es),
|
||||
Timestamp: now(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) AddEntry(hostname, containerid string, origin router.PeerName, addr address.Address) error {
|
||||
n.infof("adding entry %s -> %s", hostname, addr.String())
|
||||
n.Lock()
|
||||
entry := n.entries.add(hostname, containerid, origin, addr)
|
||||
n.Unlock()
|
||||
return n.broadcastEntries(entry)
|
||||
}
|
||||
|
||||
func (n *Nameserver) Lookup(hostname string) []address.Address {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
|
||||
entries := n.entries.lookup(hostname)
|
||||
result := []address.Address{}
|
||||
for _, e := range entries {
|
||||
if e.Tombstone > 0 {
|
||||
continue
|
||||
}
|
||||
result = append(result, e.Addr)
|
||||
}
|
||||
n.debugf("lookup %s -> %s", hostname, &result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *Nameserver) ReverseLookup(ip address.Address) (string, error) {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
|
||||
match, err := n.entries.first(func(e *Entry) bool {
|
||||
return e.Tombstone == 0 && e.Addr == ip
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
n.debugf("reverse lookup %s -> %s", ip, match.Hostname)
|
||||
return match.Hostname, nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) ContainerDied(ident string) {
|
||||
n.Lock()
|
||||
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
||||
if e.ContainerID == ident {
|
||||
n.infof("container %s died; tombstoning entry %s", ident, e.String())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
n.Unlock()
|
||||
if len(entries) > 0 {
|
||||
if err := n.broadcastEntries(entries...); err != nil {
|
||||
n.errorf("failed to broadcast container %s death: %v", ident, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nameserver) PeerGone(peer *router.Peer) {
|
||||
n.infof("peer %s gone", peer.String())
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
n.entries.filter(func(e *Entry) bool {
|
||||
return e.Origin != peer.Name
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Nameserver) Delete(hostname, containerid, ipStr string, ip address.Address) error {
|
||||
n.Lock()
|
||||
n.infof("tombstoning hostname=%s, container=%s, ip=%s", hostname, containerid, ipStr)
|
||||
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
||||
if hostname != "*" && e.Hostname != hostname {
|
||||
return false
|
||||
}
|
||||
|
||||
if containerid != "*" && e.ContainerID != containerid {
|
||||
return false
|
||||
}
|
||||
|
||||
if ipStr != "*" && e.Addr != ip {
|
||||
return false
|
||||
}
|
||||
|
||||
n.infof("tombstoning entry %v", e)
|
||||
return true
|
||||
})
|
||||
n.Unlock()
|
||||
return n.broadcastEntries(entries...)
|
||||
}
|
||||
|
||||
func (n *Nameserver) deleteTombstones() {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
now := time.Now().Unix()
|
||||
n.entries.filter(func(e *Entry) bool {
|
||||
return e.Tombstone == 0 || now-e.Tombstone <= int64(tombstoneTimeout/time.Second)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Nameserver) Gossip() router.GossipData {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
gossip := &GossipData{
|
||||
Entries: make(Entries, len(n.entries)),
|
||||
Timestamp: now(),
|
||||
}
|
||||
copy(gossip.Entries, n.entries)
|
||||
return gossip
|
||||
}
|
||||
|
||||
func (n *Nameserver) OnGossipUnicast(sender router.PeerName, msg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.GossipData, error) {
|
||||
var gossip GossipData
|
||||
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&gossip); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if delta := gossip.Timestamp - now(); delta > gossipWindow || delta < -gossipWindow {
|
||||
return nil, nil, fmt.Errorf("clock skew of %d detected", delta)
|
||||
}
|
||||
|
||||
if err := gossip.Entries.check(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
if n.peers != nil {
|
||||
gossip.Entries.filter(func(e *Entry) bool {
|
||||
return n.peers.Fetch(e.Origin) != nil
|
||||
})
|
||||
}
|
||||
|
||||
newEntries := n.entries.merge(gossip.Entries)
|
||||
if len(newEntries) > 0 {
|
||||
return &GossipData{Entries: newEntries, Timestamp: now()}, &gossip, nil
|
||||
}
|
||||
return nil, &gossip, nil
|
||||
}
|
||||
|
||||
// merge received data into state and return "everything new I've
|
||||
// just learnt", or nil if nothing in the received data was new
|
||||
func (n *Nameserver) OnGossip(msg []byte) (router.GossipData, error) {
|
||||
newEntries, _, err := n.receiveGossip(msg)
|
||||
return newEntries, err
|
||||
}
|
||||
|
||||
// merge received data into state and return a representation of
|
||||
// the received data, for further propagation
|
||||
func (n *Nameserver) OnGossipBroadcast(_ router.PeerName, msg []byte) (router.GossipData, error) {
|
||||
_, entries, err := n.receiveGossip(msg)
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (n *Nameserver) infof(fmt string, args ...interface{}) {
|
||||
Log.Infof("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
|
||||
}
|
||||
func (n *Nameserver) debugf(fmt string, args ...interface{}) {
|
||||
Log.Debugf("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
|
||||
}
|
||||
func (n *Nameserver) errorf(fmt string, args ...interface{}) {
|
||||
Log.Errorf("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/router"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
|
||||
func makeNetwork(size int) ([]*Nameserver, *gossip.TestRouter) {
|
||||
gossipRouter := gossip.NewTestRouter(0.0)
|
||||
nameservers := make([]*Nameserver, size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
name, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i))
|
||||
nameserver := New(name, nil, nil, "")
|
||||
nameserver.SetGossip(gossipRouter.Connect(nameserver.ourName, nameserver))
|
||||
nameserver.Start()
|
||||
nameservers[i] = nameserver
|
||||
}
|
||||
|
||||
return nameservers, gossipRouter
|
||||
}
|
||||
|
||||
func stopNetwork(nameservers []*Nameserver, grouter *gossip.TestRouter) {
|
||||
for _, nameserver := range nameservers {
|
||||
nameserver.Stop()
|
||||
}
|
||||
grouter.Stop()
|
||||
}
|
||||
|
||||
type pair struct {
|
||||
origin router.PeerName
|
||||
addr address.Address
|
||||
}
|
||||
|
||||
type mapping struct {
|
||||
hostname string
|
||||
addrs []pair
|
||||
}
|
||||
|
||||
type addrs []address.Address
|
||||
|
||||
func (a addrs) Len() int { return len(a) }
|
||||
func (a addrs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a addrs) Less(i, j int) bool { return a[i] < a[j] }
|
||||
func (a addrs) String() string {
|
||||
ss := []string{}
|
||||
for _, addr := range a {
|
||||
ss = append(ss, addr.String())
|
||||
}
|
||||
return strings.Join(ss, " ")
|
||||
}
|
||||
|
||||
func (m mapping) Addrs() []address.Address {
|
||||
want := addrs{}
|
||||
for _, p := range m.addrs {
|
||||
want = append(want, p.addr)
|
||||
}
|
||||
sort.Sort(want)
|
||||
return want
|
||||
}
|
||||
|
||||
func TestNameservers(t *testing.T) {
|
||||
wt.RunWithTimeout(t, time.Minute, func() {
|
||||
testNameservers(t)
|
||||
})
|
||||
}
|
||||
|
||||
func testNameservers(t *testing.T) {
|
||||
//common.SetLogLevel("debug")
|
||||
|
||||
lookupTimeout := 10 // ms
|
||||
nameservers, grouter := makeNetwork(50)
|
||||
defer stopNetwork(nameservers, grouter)
|
||||
nameserversByName := map[router.PeerName]*Nameserver{}
|
||||
for _, n := range nameservers {
|
||||
nameserversByName[n.ourName] = n
|
||||
}
|
||||
mappings := []mapping{}
|
||||
|
||||
check := func(nameserver *Nameserver, expected mapping) {
|
||||
have := []address.Address{}
|
||||
for i := 0; i < lookupTimeout; i++ {
|
||||
have = nameserver.Lookup(expected.hostname)
|
||||
sort.Sort(addrs(have))
|
||||
if reflect.DeepEqual(have, expected.Addrs()) {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
want := expected.Addrs()
|
||||
require.Equal(t, addrs(want).String(), addrs(have).String())
|
||||
}
|
||||
|
||||
addMapping := func() {
|
||||
nameserver := nameservers[rand.Intn(len(nameservers))]
|
||||
addr := address.Address(rand.Int31())
|
||||
hostname := fmt.Sprintf("hostname%d", rand.Int63())
|
||||
mapping := mapping{hostname, []pair{{nameserver.ourName, addr}}}
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
require.Nil(t, nameserver.AddEntry(hostname, "", nameserver.ourName, addr))
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
addExtraMapping := func() {
|
||||
if len(mappings) <= 0 {
|
||||
return
|
||||
}
|
||||
nameserver := nameservers[rand.Intn(len(nameservers))]
|
||||
i := rand.Intn(len(mappings))
|
||||
mapping := mappings[i]
|
||||
addr := address.Address(rand.Int31())
|
||||
mapping.addrs = append(mapping.addrs, pair{nameserver.ourName, addr})
|
||||
mappings[i] = mapping
|
||||
|
||||
require.Nil(t, nameserver.AddEntry(mapping.hostname, "", nameserver.ourName, addr))
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
deleteMapping := func() {
|
||||
if len(mappings) <= 0 {
|
||||
return
|
||||
}
|
||||
i := rand.Intn(len(mappings))
|
||||
mapping := mappings[i]
|
||||
if len(mapping.addrs) <= 0 {
|
||||
return
|
||||
}
|
||||
j := rand.Intn(len(mapping.addrs))
|
||||
pair := mapping.addrs[j]
|
||||
mapping.addrs = append(mapping.addrs[:j], mapping.addrs[j+1:]...)
|
||||
mappings[i] = mapping
|
||||
nameserver := nameserversByName[pair.origin]
|
||||
|
||||
require.Nil(t, nameserver.Delete(mapping.hostname, "*", pair.addr.String(), pair.addr))
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
doLookup := func() {
|
||||
if len(mappings) <= 0 {
|
||||
return
|
||||
}
|
||||
mapping := mappings[rand.Intn(len(mappings))]
|
||||
nameserver := nameservers[rand.Intn(len(nameservers))]
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
doReverseLookup := func() {
|
||||
if len(mappings) <= 0 {
|
||||
return
|
||||
}
|
||||
mapping := mappings[rand.Intn(len(mappings))]
|
||||
if len(mapping.addrs) <= 0 {
|
||||
return
|
||||
}
|
||||
nameserver := nameservers[rand.Intn(len(nameservers))]
|
||||
hostname := ""
|
||||
var err error
|
||||
for i := 0; i < lookupTimeout; i++ {
|
||||
hostname, err = nameserver.ReverseLookup(mapping.addrs[0].addr)
|
||||
if err != nil && mapping.hostname == hostname {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, mapping.hostname, hostname)
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
r := rand.Float32()
|
||||
switch {
|
||||
case r < 0.1:
|
||||
addMapping()
|
||||
|
||||
case 0.1 <= r && r < 0.2:
|
||||
addExtraMapping()
|
||||
|
||||
case 0.2 <= r && r < 0.3:
|
||||
deleteMapping()
|
||||
|
||||
case 0.3 <= r && r < 0.9:
|
||||
doLookup()
|
||||
|
||||
case 0.9 <= r:
|
||||
doReverseLookup()
|
||||
}
|
||||
|
||||
grouter.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerAndPeerDeath(t *testing.T) {
|
||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, nil, nil, "")
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
nameserver.ContainerDied("containerid")
|
||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
nameserver.PeerGone(&router.Peer{Name: peername})
|
||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||
}
|
||||
|
||||
func TestTombstoneDeletion(t *testing.T) {
|
||||
oldNow := now
|
||||
defer func() { now = oldNow }()
|
||||
now = func() int64 { return 1234 }
|
||||
|
||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, nil, nil, "")
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
nameserver.deleteTombstones()
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
err = nameserver.Delete("hostname", "containerid", "", address.Address(0))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||
require.Equal(t, Entries{{
|
||||
ContainerID: "containerid",
|
||||
Origin: peername,
|
||||
Addr: address.Address(0),
|
||||
Hostname: "hostname",
|
||||
Version: 1,
|
||||
Tombstone: 1234,
|
||||
}}, nameserver.entries)
|
||||
|
||||
now = func() int64 { return 1234 + int64(tombstoneTimeout/time.Second) + 1 }
|
||||
nameserver.deleteTombstones()
|
||||
require.Equal(t, Entries{}, nameserver.entries)
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServerPort = 53 // The default server port
|
||||
DefaultHTTPPort = 6785 // The default http port
|
||||
DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
|
||||
DefaultLocalTTL = 30 // default TTL for responses for local domain queries
|
||||
DefaultUDPBuflen = 4096 // bigger than the default 512
|
||||
DefaultCacheLen = 8192 // default cache capacity
|
||||
DefaultTimeout = 5000 // default timeout for DNS resolutions (millisecs)
|
||||
DefaultMaxAnswers = 1 // default number of answers provided to users
|
||||
)
|
||||
|
||||
type DNSServerConfig struct {
|
||||
// The zone
|
||||
Zone Zone
|
||||
// (Optional) client config file for resolving upstream servers
|
||||
UpstreamCfgFile string
|
||||
// (Optional) DNS client config for the fallback server(s)
|
||||
UpstreamCfg *dns.ClientConfig
|
||||
// (Optional) port number (for TCP and UDP)
|
||||
Port int
|
||||
// (Optional) cache size
|
||||
CacheLen int
|
||||
// (Optional) disable the cache
|
||||
CacheDisabled bool
|
||||
// (Optional) timeout for DNS queries
|
||||
Timeout int
|
||||
// (Optional) UDP buffer length
|
||||
UDPBufLen int
|
||||
// (Optional) force a specific cache
|
||||
Cache ZoneCache
|
||||
// (Optional) TTL for local domain responses
|
||||
LocalTTL int
|
||||
// (Optional) TTL for negative results in the local domain (defaults to LocalTTL)
|
||||
CacheNegLocalTTL int
|
||||
// (Optional) for a specific clock provider
|
||||
Clock clock.Clock
|
||||
// (Optional) Listening socket read timeout (in milliseconds)
|
||||
ListenReadTimeout int
|
||||
// Maximum number of answers provided to users
|
||||
MaxAnswers int
|
||||
}
|
||||
|
||||
type dnsProtocol uint8
|
||||
|
||||
const (
|
||||
protUDP dnsProtocol = iota // UDP protocol
|
||||
protTCP dnsProtocol = iota // TCP protocol
|
||||
)
|
||||
|
||||
func (proto dnsProtocol) String() string {
|
||||
switch proto {
|
||||
case protUDP:
|
||||
return "UDP"
|
||||
case protTCP:
|
||||
return "TCP"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// get a new dns.Client for a protocol
|
||||
func (proto dnsProtocol) GetNewClient(bufsize int, timeout time.Duration) *dns.Client {
|
||||
Debug.Printf("[dns] Creating %s DNS client with %s timeout", proto, timeout)
|
||||
switch proto {
|
||||
case protTCP:
|
||||
return &dns.Client{Net: "tcp", ReadTimeout: timeout}
|
||||
case protUDP:
|
||||
return &dns.Client{Net: "udp", ReadTimeout: timeout, UDPSize: uint16(bufsize)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// a DNS server
|
||||
type DNSServer struct {
|
||||
Zone Zone
|
||||
Upstream *dns.ClientConfig
|
||||
Domain string // the local domain
|
||||
ListenAddr string // the address the server is listening at
|
||||
|
||||
udpSrv *dns.Server
|
||||
tcpSrv *dns.Server
|
||||
pc net.PacketConn
|
||||
lst net.Listener
|
||||
cache ZoneCache
|
||||
cacheDisabled bool
|
||||
maxAnswers int
|
||||
localTTL int
|
||||
negLocalTTL int
|
||||
timeout time.Duration
|
||||
readTimeout time.Duration
|
||||
udpBuf int
|
||||
listenersWg *sync.WaitGroup
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// Creates a new DNS server
|
||||
func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) {
|
||||
s = &DNSServer{
|
||||
Zone: config.Zone,
|
||||
Domain: DefaultLocalDomain,
|
||||
ListenAddr: fmt.Sprintf(":%d", config.Port),
|
||||
|
||||
listenersWg: new(sync.WaitGroup),
|
||||
timeout: DefaultTimeout * time.Millisecond,
|
||||
readTimeout: DefaultTimeout * time.Millisecond,
|
||||
cacheDisabled: false,
|
||||
maxAnswers: DefaultMaxAnswers,
|
||||
localTTL: DefaultLocalTTL,
|
||||
clock: config.Clock,
|
||||
}
|
||||
|
||||
// check some basic parameters are valid
|
||||
if s.Zone == nil {
|
||||
return nil, fmt.Errorf("No valid Zone provided in server initialization")
|
||||
}
|
||||
if len(s.Domain) == 0 {
|
||||
return nil, fmt.Errorf("No valid Domain provided in server initialization")
|
||||
}
|
||||
if s.clock == nil {
|
||||
s.clock = clock.New()
|
||||
}
|
||||
|
||||
// fill empty parameters with defaults...
|
||||
if config.UpstreamCfg != nil {
|
||||
s.Upstream = config.UpstreamCfg
|
||||
} else {
|
||||
cfgFile := DefaultCLICfgFile
|
||||
if len(config.UpstreamCfgFile) > 0 {
|
||||
cfgFile = config.UpstreamCfgFile
|
||||
}
|
||||
if s.Upstream, err = dns.ClientConfigFromFile(cfgFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if config.Timeout > 0 {
|
||||
s.timeout = time.Duration(config.Timeout) * time.Millisecond
|
||||
}
|
||||
if config.ListenReadTimeout > 0 {
|
||||
s.readTimeout = time.Duration(config.ListenReadTimeout) * time.Millisecond
|
||||
}
|
||||
if config.UDPBufLen > 0 {
|
||||
s.udpBuf = config.UDPBufLen
|
||||
}
|
||||
if config.MaxAnswers > 0 {
|
||||
s.maxAnswers = config.MaxAnswers
|
||||
}
|
||||
if config.LocalTTL > 0 {
|
||||
s.localTTL = config.LocalTTL
|
||||
}
|
||||
if config.CacheNegLocalTTL > 0 {
|
||||
s.negLocalTTL = config.CacheNegLocalTTL
|
||||
} else {
|
||||
s.negLocalTTL = s.localTTL
|
||||
}
|
||||
if config.CacheDisabled {
|
||||
s.cacheDisabled = true
|
||||
}
|
||||
if !s.cacheDisabled {
|
||||
if config.Cache != nil {
|
||||
s.cache = config.Cache
|
||||
} else {
|
||||
cacheLen := DefaultCacheLen
|
||||
if config.CacheLen > 0 {
|
||||
cacheLen = config.CacheLen
|
||||
}
|
||||
if s.cache, err = NewCache(cacheLen, s.clock); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Start the DNS server
|
||||
func (s *DNSServer) Start() error {
|
||||
Info.Printf("[dns] Upstream server(s): %+v", s.Upstream)
|
||||
if s.cacheDisabled {
|
||||
Info.Printf("[dns] Cache: disabled")
|
||||
} else {
|
||||
Info.Printf("[dns] Cache: %d entries", s.cache.Capacity())
|
||||
}
|
||||
|
||||
pc, err := net.ListenPacket("udp", s.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.pc = pc
|
||||
|
||||
_, port, err := net.SplitHostPort(pc.LocalAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.ListenAddr = fmt.Sprintf(":%s", port)
|
||||
s.udpSrv = &dns.Server{PacketConn: s.pc, Handler: s.createMux(protUDP), ReadTimeout: s.readTimeout}
|
||||
|
||||
// Bind the TCP socket at the same port, aborting otherwise
|
||||
l, err := net.Listen("tcp", s.ListenAddr)
|
||||
if err != nil {
|
||||
s.Stop()
|
||||
return err
|
||||
}
|
||||
s.lst = l
|
||||
s.tcpSrv = &dns.Server{Listener: l, Handler: s.createMux(protTCP), ReadTimeout: s.readTimeout}
|
||||
|
||||
s.listenersWg.Add(2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DNSServer) ActivateAndServe() {
|
||||
go func() {
|
||||
defer s.listenersWg.Done()
|
||||
|
||||
Info.Printf("[dns] Listening for DNS on %s (UDP)", s.ListenAddr)
|
||||
err := s.udpSrv.ActivateAndServe()
|
||||
CheckFatal(err)
|
||||
Debug.Printf("[dns] DNS UDP server exiting...")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer s.listenersWg.Done()
|
||||
|
||||
Info.Printf("[dns] Listening for DNS on %s (TCP)", s.ListenAddr)
|
||||
err := s.tcpSrv.ActivateAndServe()
|
||||
CheckFatal(err)
|
||||
Debug.Printf("[dns] DNS TCP server exiting...")
|
||||
}()
|
||||
|
||||
// Waiting for all goroutines to finish (otherwise they die as main routine dies)
|
||||
s.listenersWg.Wait()
|
||||
|
||||
Info.Printf("[dns] Server exiting...")
|
||||
}
|
||||
|
||||
// Return status string
|
||||
func (s *DNSServer) Status() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintln(&buf, "Listen address", s.ListenAddr)
|
||||
fmt.Fprintln(&buf, "Fallback DNS config", s.Upstream)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Perform a graceful shutdown
|
||||
func (s *DNSServer) Stop() error {
|
||||
// Stop the listeners/handlers
|
||||
if s.tcpSrv != nil {
|
||||
if err := s.tcpSrv.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.lst.Close()
|
||||
s.tcpSrv = nil
|
||||
}
|
||||
if s.udpSrv != nil {
|
||||
if err := s.udpSrv.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.pc.Close()
|
||||
s.udpSrv = nil
|
||||
}
|
||||
s.listenersWg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a multiplexer for requests
|
||||
// We must create two DNS request multiplexers, depending on the protocol used by
|
||||
// clients (as we use the same protocol for asking upstream servers)
|
||||
func (s *DNSServer) createMux(proto dnsProtocol) *dns.ServeMux {
|
||||
failFallback := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
w.WriteMsg(makeDNSFailResponse(r))
|
||||
}
|
||||
notUsHandler := s.notUsHandler(proto)
|
||||
notUsFallback := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
Info.Printf("[dns msgid %d] -> sending to fallback server", r.MsgHdr.Id)
|
||||
notUsHandler(w, r)
|
||||
}
|
||||
|
||||
// create the multiplexer
|
||||
m := dns.NewServeMux()
|
||||
m.HandleFunc(s.Zone.Domain(), s.localHandler(proto, "Query", dns.TypeA,
|
||||
s.Zone.DomainLookupName, makeAddressReply, s.Zone.ObserveName, failFallback))
|
||||
m.HandleFunc(RDNSDomain, s.localHandler(proto, "Reverse query", dns.TypePTR,
|
||||
s.Zone.DomainLookupInaddr, makePTRReply, s.Zone.ObserveInaddr, notUsFallback))
|
||||
m.HandleFunc(".", s.notUsHandler(proto))
|
||||
return m
|
||||
}
|
||||
|
||||
// Process a request for a name/address in the '.weave.local.' zone
|
||||
func (s *DNSServer) localHandler(proto dnsProtocol, kind string, qtype uint16,
|
||||
lookup func(inaddr string) ([]ZoneRecord, error),
|
||||
msgBuilder DNSResponseBuilder, observer ZoneObserverFunc, fallback dns.HandlerFunc) dns.HandlerFunc {
|
||||
|
||||
return func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
q := r.Question[0]
|
||||
Debug.Printf("[dns msgid %d] %s: %+v", r.MsgHdr.Id, kind, q)
|
||||
maxLen := getMaxReplyLen(r, proto)
|
||||
|
||||
// cache a response if the cache is enabled, and observe the name/IP
|
||||
maybeCache := func(m *dns.Msg, ttl int, flags uint8) {
|
||||
if !s.cacheDisabled {
|
||||
s.cache.Put(r, m, ttl, flags)
|
||||
// any change in the Zone database for this IP/name will lead to this
|
||||
// cache entry being removed...
|
||||
// TODO: this closure results in unnecessary `Remove`s and some wasted
|
||||
// mem... but we can live with that.
|
||||
observer(q.Name, func() { s.cache.Remove(&q) })
|
||||
}
|
||||
}
|
||||
|
||||
if !s.cacheDisabled {
|
||||
reply, err := s.cache.Get(r, maxLen)
|
||||
if err != nil {
|
||||
if err == errNoLocalReplies {
|
||||
Debug.Printf("[dns msgid %d] Cached no-local-replies", r.MsgHdr.Id)
|
||||
fallback(w, r)
|
||||
} else {
|
||||
Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
|
||||
w.WriteMsg(makeDNSFailResponse(r))
|
||||
}
|
||||
return
|
||||
}
|
||||
if reply != nil {
|
||||
reply.Answer = pruneAnswers(shuffleAnswers(reply.Answer), s.maxAnswers)
|
||||
Debug.Printf("[dns msgid %d] Returning reply from cache: %s/%d answers",
|
||||
r.MsgHdr.Id, dns.RcodeToString[reply.MsgHdr.Rcode], len(reply.Answer))
|
||||
w.WriteMsg(reply)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// catch unsupported queries
|
||||
if q.Qtype != qtype {
|
||||
Debug.Printf("[dns msgid %d] Unsupported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
|
||||
m := makeDNSFailResponse(r)
|
||||
maybeCache(m, s.negLocalTTL, 0)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
}
|
||||
|
||||
if answers, err := lookup(q.Name); err != nil {
|
||||
Info.Printf("[dns msgid %d] No results for type %s query for '%s' [caching no-local]",
|
||||
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
|
||||
maybeCache(nil, s.negLocalTTL, CacheNoLocalReplies)
|
||||
fallback(w, r)
|
||||
} else {
|
||||
m := msgBuilder(r, &q, answers, s.localTTL)
|
||||
m.Authoritative = true
|
||||
maybeCache(m, nullTTL, 0)
|
||||
m.Answer = pruneAnswers(shuffleAnswers(m.Answer), s.maxAnswers)
|
||||
Debug.Printf("[dns msgid %d] Sending response: %s/%d answers [code:%s]",
|
||||
m.MsgHdr.Id, dns.TypeToString[q.Qtype], len(m.Answer), dns.RcodeToString[m.Rcode])
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When we receive a request for a name outside of our '.weave.local.'
|
||||
// domain, ask the configured DNS server as a fallback.
|
||||
func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
|
||||
dnsClient := proto.GetNewClient(DefaultUDPBuflen, s.timeout)
|
||||
|
||||
return func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
q := r.Question[0]
|
||||
|
||||
// announce our max payload size as the max payload our client supports
|
||||
maxLen := getMaxReplyLen(r, proto)
|
||||
rcopy := r
|
||||
rcopy.SetEdns0(uint16(maxLen), false)
|
||||
|
||||
Debug.Printf("[dns msgid %d] Fallback query: %+v [%s, max:%d bytes]", rcopy.MsgHdr.Id, q, proto, maxLen)
|
||||
for _, server := range s.Upstream.Servers {
|
||||
reply, _, err := dnsClient.Exchange(rcopy, fmt.Sprintf("%s:%s", server, s.Upstream.Port))
|
||||
if err != nil {
|
||||
Debug.Printf("[dns msgid %d] Network error trying %s (%s)",
|
||||
r.MsgHdr.Id, server, err)
|
||||
continue
|
||||
}
|
||||
if reply != nil && reply.Rcode != dns.RcodeSuccess {
|
||||
Debug.Printf("[dns msgid %d] Failure reported by %s for query %s",
|
||||
r.MsgHdr.Id, server, q.Name)
|
||||
continue
|
||||
}
|
||||
Debug.Printf("[dns msgid %d] Given answer by %s for query %s",
|
||||
r.MsgHdr.Id, server, q.Name)
|
||||
if reply.Len() > maxLen {
|
||||
reply.Compress = true
|
||||
}
|
||||
w.WriteMsg(reply)
|
||||
return
|
||||
}
|
||||
Warning.Printf("[dns msgid %d] Failed lookup for external name %s", r.MsgHdr.Id, q.Name)
|
||||
w.WriteMsg(makeDNSFailResponse(r))
|
||||
}
|
||||
}
|
||||
|
||||
// Get the listen port
|
||||
func (s *DNSServer) GetPort() (int, error) {
|
||||
_, portS, err := net.SplitHostPort(s.ListenAddr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.Atoi(portS)
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// Check that AddRecord/DeleteRecord/... in the Zone database lead to cache invalidations
|
||||
func TestServerDbCacheInvalidation(t *testing.T) {
|
||||
const (
|
||||
containerID = "somecontainer"
|
||||
testName1 = "first.weave.local."
|
||||
testName2 = "second.weave.local."
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestServerDbCacheInvalidation starting")
|
||||
|
||||
clk := newMockedClock()
|
||||
|
||||
Debug.Printf("Creating mocked mDNS client and server")
|
||||
mdnsServer1 := newMockedMDNSServerWithRecord(Record{testName1, net.ParseIP("10.2.2.9"), 0, 0, 0})
|
||||
mdnsCli1 := newMockedMDNSClient([]*mockedMDNSServer{mdnsServer1})
|
||||
|
||||
Debug.Printf("Creating zone database with the mocked mDNS client and server")
|
||||
zoneConfig := ZoneConfig{
|
||||
MDNSServer: mdnsServer1,
|
||||
MDNSClient: mdnsCli1,
|
||||
Clock: clk,
|
||||
}
|
||||
zone, err := NewZoneDb(zoneConfig)
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
Debug.Printf("Creating a cache")
|
||||
cache, err := NewCache(1024, clk)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
if len(req.Question) == 1 {
|
||||
m.Rcode = dns.RcodeNameError
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
// Run another DNS server for fallback
|
||||
fallback, err := newMockedFallback(fallbackHandler, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
fallback.Start()
|
||||
defer fallback.Stop()
|
||||
|
||||
Debug.Printf("Creating a real DNS server with a mocked cache")
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: zone,
|
||||
Cache: cache,
|
||||
Clock: clk,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
UpstreamCfg: fallback.CliConfig,
|
||||
MaxAnswers: 4,
|
||||
})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = srv.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow server goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotEqualInt(t, testPort, 0, "invalid listen port")
|
||||
|
||||
Debug.Printf("Adding two IPs to %s", testName1)
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1"))
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.2"))
|
||||
q, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0)
|
||||
assertInCache(t, cache, q, fmt.Sprintf("after asking for %s", testName1))
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.2
|
||||
|
||||
zone.AddRecord(containerID, testName2, net.ParseIP("10.9.9.1"))
|
||||
assertInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName2))
|
||||
|
||||
// we should have an entry in the cache for this query
|
||||
// if we add another IP, that cache entry should be removed
|
||||
Debug.Printf("Adding a new IP to %s: the cache entry should be removed", testName1)
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.3"))
|
||||
assertNotInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName1))
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.2 10.2.2.3
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
Debug.Printf("Querying again (so a cache entry will be created)")
|
||||
q, _ = assertExchange(t, testName1, dns.TypeA, testPort, 3, 4, 0)
|
||||
assertInCache(t, cache, q, "after asking about the name")
|
||||
Debug.Printf("... and removing one of the IP addresses")
|
||||
zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.2"))
|
||||
assertNotInCache(t, cache, q, "after deleting IP for 10.2.2.2")
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.3
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
// generate cache responses
|
||||
Debug.Printf("Querying for a raddr")
|
||||
qname, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0)
|
||||
qptr, _ := assertExchange(t, "1.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
qotherName, _ := assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0)
|
||||
qotherPtr, _ := assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
qwrongName, _ := assertExchange(t, "wrong.weave.local.", dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertInCache(t, cache, qname, "after asking for name")
|
||||
assertInCache(t, cache, qptr, "after asking for address")
|
||||
assertInCache(t, cache, qotherName, "after asking for second name")
|
||||
assertInCache(t, cache, qotherPtr, "after asking for second address")
|
||||
assertNotLocalInCache(t, cache, qwrongName, "after asking for a wrong name")
|
||||
|
||||
// now we will check if a removal affects all the responses
|
||||
Debug.Printf("... and removing an IP should invalidate both the cached responses for name and raddr")
|
||||
zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.1"))
|
||||
assertNotInCache(t, cache, qptr, "after deleting record")
|
||||
assertNotInCache(t, cache, qname, "after deleting record")
|
||||
assertInCache(t, cache, qotherName, "after deleting record")
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.3
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
// generate cache responses
|
||||
Debug.Printf("Querying for a raddr")
|
||||
qptr, _ = assertExchange(t, "3.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
qname, _ = assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0)
|
||||
qotherName, _ = assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0)
|
||||
qotherPtr, _ = assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
assertInCache(t, cache, qname, "after asking for name")
|
||||
assertInCache(t, cache, qptr, "after asking for PTR")
|
||||
assertInCache(t, cache, qotherName, "after asking for second name")
|
||||
assertInCache(t, cache, qotherPtr, "after asking for second address")
|
||||
|
||||
// let's repeat this, but adding an IP
|
||||
Debug.Printf("... and adding a new IP should invalidate both the cached responses for the name")
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.7"))
|
||||
assertNotInCache(t, cache, qname, "after adding a new IP")
|
||||
assertInCache(t, cache, qotherName, "after adding a new IP")
|
||||
assertInCache(t, cache, qotherPtr, "after adding a new IP")
|
||||
|
||||
// check that after some time, the cache entry is expired
|
||||
clk.Forward(int(DefaultLocalTTL) + 1)
|
||||
assertNotInCache(t, cache, qotherName, "after passing some time")
|
||||
assertNotInCache(t, cache, qwrongName, "after passing some time")
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.3 10.2.2.7
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
zone.DeleteRecords(containerID, "", net.IP{})
|
||||
assertNotInCache(t, cache, qotherName, "after removing container")
|
||||
assertNotInCache(t, cache, qotherPtr, "after removing container")
|
||||
}
|
||||
|
||||
// Check if the entries in the cache expire
|
||||
func TestServerCacheExpiration(t *testing.T) {
|
||||
const (
|
||||
containerID = "somecontainer"
|
||||
testName1 = "first.weave.local."
|
||||
testName2 = "second.weave.local."
|
||||
negativeLocalTTL = 10
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestServerCacheExpiration starting")
|
||||
|
||||
clk := newMockedClock()
|
||||
|
||||
Debug.Printf("Creating 2 zone databases")
|
||||
zoneConfig := ZoneConfig{
|
||||
RefreshInterval: 0, // no name updates
|
||||
Clock: clk,
|
||||
}
|
||||
dbs := newZoneDbsWithMockedMDns(2, zoneConfig)
|
||||
dbs.Start()
|
||||
defer dbs.Stop()
|
||||
|
||||
Debug.Printf("Creating a cache")
|
||||
cache, err := NewCache(1024, clk)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
Debug.Printf("Creating a real DNS server for the first zone database and with the cache")
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: dbs[0].Zone,
|
||||
Cache: cache,
|
||||
Clock: clk,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
CacheNegLocalTTL: negativeLocalTTL,
|
||||
})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = srv.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow server goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotEqualInt(t, testPort, 0, "invalid listen port")
|
||||
|
||||
// Check that the DNS server knows nothing about testName1
|
||||
qName1, _ := assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
// there should be a negative reply cached during [0, negativeLocalTTL]
|
||||
assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
clk.Forward(negativeLocalTTL - 1)
|
||||
assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
// after negativeLocalTTL, there should be absolutely nothing in the cache
|
||||
clk.Forward(2)
|
||||
assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
|
||||
// We add the IP in the second peer right after
|
||||
Debug.Printf("Adding an IPs to %s in database #2", testName1)
|
||||
dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1"))
|
||||
t.Logf("Zone database #2:\n%s", dbs[1].Zone)
|
||||
|
||||
// Check that the DNS server returns updated information, and it will be
|
||||
// cached during [0, DefaultLocalTTL]
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0)
|
||||
|
||||
assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
clk.Forward(int(DefaultLocalTTL) - 1)
|
||||
assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
// and it will expire after DefaultLocalTTL
|
||||
clk.Forward(2)
|
||||
assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
|
||||
// We delete the IP in the second peer right after
|
||||
Debug.Printf("Removing the IPs from %s in database #2", testName1)
|
||||
dbs[1].Zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.1"))
|
||||
|
||||
// Check that we return to the initial state: a neg-local entry in the cache
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
clk.Forward(negativeLocalTTL + 1)
|
||||
|
||||
// Check that all entries should be outdated
|
||||
cache.Purge()
|
||||
if cache.Len() != 0 {
|
||||
t.Fatalf("Some entries still valid in cache:\n%s", cache)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the names updates lead to cache invalidations
|
||||
func TestServerCacheRefresh(t *testing.T) {
|
||||
const (
|
||||
containerID = "somecontainer"
|
||||
testName1 = "first.weave.local."
|
||||
testName2 = "second.weave.local."
|
||||
refreshInterval = int(DefaultLocalTTL) / 3
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestServerCacheRefresh starting")
|
||||
clk := newMockedClock()
|
||||
|
||||
Debug.Printf("Creating 2 zone databases")
|
||||
zoneConfig := ZoneConfig{
|
||||
RefreshInterval: refreshInterval,
|
||||
Clock: clk,
|
||||
}
|
||||
dbs := newZoneDbsWithMockedMDns(2, zoneConfig)
|
||||
dbs.Start()
|
||||
defer dbs.Stop()
|
||||
|
||||
Debug.Printf("Creating a cache")
|
||||
cache, err := NewCache(1024, clk)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
Debug.Printf("Creating a real DNS server for the first zone database and with the cache")
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: dbs[0].Zone,
|
||||
Cache: cache,
|
||||
Clock: clk,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
MaxAnswers: 4,
|
||||
})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = srv.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotEqualInt(t, testPort, 0, "listen port")
|
||||
|
||||
Debug.Printf("Adding an IPs to %s", testName1)
|
||||
dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1"))
|
||||
dbs.Flush() // ensure the immediate refresh is done
|
||||
|
||||
// Zone database #2 at this point:
|
||||
// first.weave.local = 10.2.2.1
|
||||
|
||||
// testName1 and testName2 should have no IPs yet
|
||||
qName1, _ := assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0)
|
||||
qName2, _ := assertExchange(t, testName2, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertInCache(t, cache, qName1, "after asking for first name")
|
||||
assertNotLocalInCache(t, cache, qName2, "after asking for second name")
|
||||
|
||||
clk.Forward(refreshInterval / 2)
|
||||
dbs.Flush()
|
||||
|
||||
Debug.Printf("Adding an IP to %s and to %s", testName1, testName2)
|
||||
dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.2"))
|
||||
dbs[1].Zone.AddRecord(containerID, testName2, net.ParseIP("10.9.9.2"))
|
||||
|
||||
// Zone database #2 at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.2
|
||||
// second.weave.local = 10.9.9.2
|
||||
clk.Forward(refreshInterval/2 + 2)
|
||||
dbs.Flush()
|
||||
|
||||
// at this point, the testName1 should have been refreshed
|
||||
// so it should have two IPs, and the cache entry should have been invalidated
|
||||
assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
assertNotLocalInCache(t, cache, qName2, fmt.Sprintf("after asking for %s", testName2))
|
||||
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0)
|
||||
qName2, _ = assertExchange(t, testName2, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
assertNotLocalInCache(t, cache, qName2, "after asking for a unknown name")
|
||||
|
||||
// delete the IPs, and some time passes by so the cache should be purged...
|
||||
dbs[1].Zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.1"))
|
||||
dbs[1].Zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.2"))
|
||||
clk.Forward(refreshInterval + 1)
|
||||
dbs.Flush()
|
||||
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
qName2, _ = assertExchange(t, testName2, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertNotLocalInCache(t, cache, qName1, "after asking for a unknown name")
|
||||
assertNotLocalInCache(t, cache, qName2, "after asking for a unknown name")
|
||||
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
const (
|
||||
testRDNSsuccess = "1.2.2.10.in-addr.arpa."
|
||||
testRDNSfail = "4.3.2.1.in-addr.arpa."
|
||||
testRDNSnonlocal = "8.8.8.8.in-addr.arpa."
|
||||
testUDPBufSize = 16384
|
||||
)
|
||||
|
||||
func setupForTest(t *testing.T) {
|
||||
// fail early if we cannot find a default multicast interface
|
||||
multicast, err := LinkLocalMulticastListener(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create multicast listener: %s. No default multicast interface?", err)
|
||||
}
|
||||
multicast.Close()
|
||||
}
|
||||
|
||||
func TestUDPDNSServer(t *testing.T) {
|
||||
setupForTest(t)
|
||||
|
||||
const (
|
||||
successTestName = "test1.weave.local."
|
||||
failTestName = "fail.weave.local."
|
||||
compressedTestName = "compressed.weave.works."
|
||||
nonLocalName = "weave.works."
|
||||
testAddr1 = "10.2.2.1"
|
||||
containerID = "somecontainer"
|
||||
)
|
||||
testCIDR1 := testAddr1 + "/24"
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestUDPDNSServer starting")
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
ip, _, _ := net.ParseCIDR(testCIDR1)
|
||||
zone.AddRecord(containerID, successTestName, ip)
|
||||
|
||||
fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
if len(req.Question) == 0 {
|
||||
return // ignore empty queries (sent when shutting down the server)
|
||||
}
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
q := req.Question[0]
|
||||
if q.Name == nonLocalName && q.Qtype == dns.TypeMX {
|
||||
m.Answer = make([]dns.RR, 1)
|
||||
m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail." + nonLocalName}
|
||||
} else if q.Name == nonLocalName && q.Qtype == dns.TypeANY {
|
||||
m.Answer = make([]dns.RR, 512/len("mailn."+nonLocalName)+1)
|
||||
for i := range m.Answer {
|
||||
m.Answer[i] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: fmt.Sprintf("mail%d.%s", i, nonLocalName)}
|
||||
}
|
||||
} else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR {
|
||||
m.Answer = make([]dns.RR, 1)
|
||||
m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."}
|
||||
} else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR {
|
||||
m.Rcode = dns.RcodeNameError
|
||||
} else if q.Name == compressedTestName {
|
||||
// Construct a response that is >512 when uncompressed, <512 when compressed
|
||||
header := makeHeader(m, &q, 300)
|
||||
for m.Len() <= minUDPSize {
|
||||
ip := address.Address(rand.Uint32()).IP4()
|
||||
m.Answer = append(m.Answer, &dns.A{Hdr: *header, A: ip})
|
||||
}
|
||||
m.Compress = true
|
||||
wt.AssertTrue(t, m.Len() <= minUDPSize, fmt.Sprintf("length: %d", m.Len()))
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
// Run another DNS server for fallback
|
||||
fallback, err := newMockedFallback(fallbackHandler, nil)
|
||||
wt.AssertNoErr(t, err)
|
||||
fallback.Start()
|
||||
defer fallback.Stop()
|
||||
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: zone,
|
||||
UpstreamCfg: fallback.CliConfig,
|
||||
CacheDisabled: true,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = srv.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
|
||||
|
||||
var r *dns.Msg
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotEqualInt(t, testPort, 0, "invalid listen port")
|
||||
|
||||
_, r = assertExchange(t, successTestName, dns.TypeA, testPort, 1, 1, 0)
|
||||
wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record")
|
||||
wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address")
|
||||
|
||||
assertExchange(t, failTestName, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
|
||||
_, r = assertExchange(t, testRDNSsuccess, dns.TypePTR, testPort, 1, 1, 0)
|
||||
wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record")
|
||||
wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address")
|
||||
|
||||
assertExchange(t, testRDNSfail, dns.TypePTR, testPort, 0, 0, dns.RcodeNameError)
|
||||
|
||||
// This should fail because we don't handle MX records
|
||||
assertExchange(t, successTestName, dns.TypeMX, testPort, 0, 0, dns.RcodeNameError)
|
||||
|
||||
// This non-local query for an MX record should succeed by being
|
||||
// passed on to the fallback server
|
||||
assertExchange(t, nonLocalName, dns.TypeMX, testPort, 1, -1, 0)
|
||||
|
||||
// Now ask a query that we expect to return a lot of data.
|
||||
assertExchange(t, nonLocalName, dns.TypeANY, testPort, 5, -1, 0)
|
||||
|
||||
assertExchange(t, testRDNSnonlocal, dns.TypePTR, testPort, 1, -1, 0)
|
||||
|
||||
_, r = assertExchange(t, compressedTestName, dns.TypeA, testPort, -1, -1, 0)
|
||||
wt.AssertTrue(t, r.Len() > minUDPSize, "unexpected message length")
|
||||
|
||||
// Not testing MDNS functionality of server here (yet), since it
|
||||
// needs two servers, each listening on its own address
|
||||
}
|
||||
|
||||
func TestTCPDNSServer(t *testing.T) {
|
||||
setupForTest(t)
|
||||
|
||||
const (
|
||||
numAnswers = 512
|
||||
nonLocalName = "weave.works."
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestTCPDNSServer starting")
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
// generate a list of `numAnswers` IP addresses
|
||||
var addrs []ZoneRecord
|
||||
bs := make([]byte, 4)
|
||||
for i := 0; i < numAnswers; i++ {
|
||||
binary.LittleEndian.PutUint32(bs, uint32(i))
|
||||
ip := net.IPv4(bs[0], bs[1], bs[2], bs[3])
|
||||
addrs = append(addrs, ZoneRecord(Record{"", ip, 0, 0, 0}))
|
||||
}
|
||||
|
||||
// handler for the fallback server: it will just return a very long response
|
||||
fallbackUDPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
if len(req.Question) == 0 {
|
||||
return // ignore empty queries (sent when shutting down the server)
|
||||
}
|
||||
maxLen := getMaxReplyLen(req, protUDP)
|
||||
t.Logf("Fallback UDP server got asked: returning %d answers", numAnswers)
|
||||
q := req.Question[0]
|
||||
m := makeAddressReply(req, &q, addrs, DefaultLocalTTL)
|
||||
mLen := m.Len()
|
||||
m.SetEdns0(uint16(maxLen), false)
|
||||
|
||||
if mLen > maxLen {
|
||||
t.Logf("... truncated response (%d > %d)", mLen, maxLen)
|
||||
m.Truncated = true
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
fallbackTCPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
if len(req.Question) == 0 {
|
||||
return // ignore empty queries (sent when shutting down the server)
|
||||
}
|
||||
t.Logf("Fallback TCP server got asked: returning %d answers", numAnswers)
|
||||
q := req.Question[0]
|
||||
m := makeAddressReply(req, &q, addrs, DefaultLocalTTL)
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
t.Logf("Running a DNS fallback server with UDP")
|
||||
fallback, err := newMockedFallback(fallbackUDPHandler, fallbackTCPHandler)
|
||||
wt.AssertNoErr(t, err)
|
||||
fallback.Start()
|
||||
defer fallback.Stop()
|
||||
|
||||
t.Logf("Creating a WeaveDNS server instance, falling back to 127.0.0.1:%d", fallback.Port)
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: zone,
|
||||
UpstreamCfg: fallback.CliConfig,
|
||||
CacheDisabled: true,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = srv.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotEqualInt(t, testPort, 0, "listen port")
|
||||
dnsAddr := fmt.Sprintf("127.0.0.1:%d", testPort)
|
||||
|
||||
t.Logf("Creating a UDP and a TCP client")
|
||||
uc := new(dns.Client)
|
||||
uc.UDPSize = minUDPSize
|
||||
tc := new(dns.Client)
|
||||
tc.Net = "tcp"
|
||||
|
||||
t.Logf("Creating DNS query message")
|
||||
m := new(dns.Msg)
|
||||
m.RecursionDesired = true
|
||||
m.SetQuestion(nonLocalName, dns.TypeA)
|
||||
|
||||
t.Logf("Checking the fallback server at %s returns a truncated response with UDP", fallback.Addr)
|
||||
r, _, err := uc.Exchange(m, fallback.Addr)
|
||||
t.Logf("Got response from fallback server (UDP) with %d answers", len(r.Answer))
|
||||
t.Logf("Response:\n%+v\n", r)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
|
||||
wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)")
|
||||
|
||||
t.Logf("Checking the WeaveDNS server at %s returns a truncated reponse with UDP", dnsAddr)
|
||||
r, _, err = uc.Exchange(m, dnsAddr)
|
||||
t.Logf("UDP Response:\n%+v\n", r)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotNil(t, r, "response")
|
||||
t.Logf("%d answers", len(r.Answer))
|
||||
wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
|
||||
wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)")
|
||||
|
||||
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with TCP", dnsAddr)
|
||||
r, _, err = tc.Exchange(m, dnsAddr)
|
||||
t.Logf("TCP Response:\n%+v\n", r)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotNil(t, r, "response")
|
||||
t.Logf("%d answers", len(r.Answer))
|
||||
wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag")
|
||||
wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (TCP)")
|
||||
|
||||
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with UDP with a bigger buffer", dnsAddr)
|
||||
m.SetEdns0(testUDPBufSize, false)
|
||||
r, _, err = uc.Exchange(m, dnsAddr)
|
||||
t.Logf("UDP-large Response:\n%+v\n", r)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertNotNil(t, r, "response")
|
||||
t.Logf("%d answers", len(r.Answer))
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag")
|
||||
wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP-long)")
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package nameserver
|
||||
|
||||
type Status struct {
|
||||
Domain string
|
||||
Address string
|
||||
TTL uint32
|
||||
Entries []EntryStatus
|
||||
}
|
||||
|
||||
type EntryStatus struct {
|
||||
Hostname string
|
||||
Origin string
|
||||
ContainerID string
|
||||
Address string
|
||||
Version int
|
||||
Tombstone int64
|
||||
}
|
||||
|
||||
func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
|
||||
if dnsServer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ns.RLock()
|
||||
defer ns.RUnlock()
|
||||
|
||||
var entryStatusSlice []EntryStatus
|
||||
for _, entry := range ns.entries {
|
||||
entryStatusSlice = append(entryStatusSlice, EntryStatus{
|
||||
entry.Hostname,
|
||||
entry.Origin.String(),
|
||||
entry.ContainerID,
|
||||
entry.Addr.String(),
|
||||
entry.Version,
|
||||
entry.Tombstone})
|
||||
}
|
||||
|
||||
return &Status{
|
||||
dnsServer.domain,
|
||||
dnsServer.address,
|
||||
dnsServer.ttl,
|
||||
entryStatusSlice}
|
||||
}
|
||||
@@ -1,739 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
RDNSDomain = "in-addr.arpa."
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultLocalDomain = "weave.local." // The default name used for the local domain
|
||||
DefaultRefreshInterval = int(DefaultLocalTTL) // Period for background updates with mDNS
|
||||
DefaultRelevantTime = 60 // When to forget info about remote info if nobody asks...
|
||||
|
||||
defaultRemoteIdent = "weave:remote" // Ident used for info obtained from mDNS
|
||||
)
|
||||
|
||||
// +1 to also exclude a dot
|
||||
var rdnsDomainLen = len(RDNSDomain) + 1
|
||||
|
||||
type LookupError string
|
||||
|
||||
func (ops LookupError) Error() string {
|
||||
return "Unable to find " + string(ops)
|
||||
}
|
||||
|
||||
type DuplicateError struct {
|
||||
}
|
||||
|
||||
func (dup DuplicateError) Error() string {
|
||||
return "Tried to add a duplicate entry"
|
||||
}
|
||||
|
||||
type Zone interface {
|
||||
ZoneObservable
|
||||
ZoneLookup
|
||||
// The domain where we operate (eg, "weave.local.")
|
||||
Domain() string
|
||||
// Add a record in the local database
|
||||
AddRecord(ident string, name string, ip net.IP) error
|
||||
// Delete matching records (uninitialised values act as wildcards)
|
||||
DeleteRecords(ident string, name string, ip net.IP) (count int)
|
||||
// Lookup for a name in the whole domain
|
||||
DomainLookupName(name string) ([]ZoneRecord, error)
|
||||
// Lookup for an address in the whole domain
|
||||
DomainLookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
// Return a status string
|
||||
Status() string
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Zone database overview:
|
||||
//
|
||||
// idents names entry
|
||||
// *-------* *------* *------*
|
||||
// zone -> | ident | --*--> | name | --*-> | IPv4 |
|
||||
// *-------* | *------* | *------*
|
||||
// | ident | *--> | name | *-> | IPv4 |
|
||||
// *-------* *------* *------*
|
||||
// ... ...
|
||||
//
|
||||
// The zone database keeps an ident per container, but also a special
|
||||
// ident for names/IPs obtained from remote peers.
|
||||
//
|
||||
// Each name can have multiple IPs (so far, we only consider IPv4s), and
|
||||
// each IP is stored in a `recordEntry` in the database. Entries store
|
||||
// some additional information like priority and weight of the IP (for
|
||||
// example, for future use with SRV records)
|
||||
//
|
||||
|
||||
// An entry in the zone database
|
||||
type recordEntry struct {
|
||||
Record
|
||||
|
||||
observers []ZoneRecordObserver // the observers for this record
|
||||
insTime time.Time // time when this record was inserted
|
||||
}
|
||||
|
||||
func newRecordEntry(zr ZoneRecord, now time.Time) *recordEntry {
|
||||
return &recordEntry{
|
||||
Record: Record{
|
||||
name: zr.Name(),
|
||||
ip: zr.IP(),
|
||||
priority: zr.Priority(),
|
||||
weight: zr.Weight(),
|
||||
ttl: zr.TTL(),
|
||||
},
|
||||
insTime: now,
|
||||
observers: make([]ZoneRecordObserver, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (re *recordEntry) addIPObserver(zro ZoneRecordObserver) {
|
||||
re.observers = append(re.observers, zro)
|
||||
}
|
||||
|
||||
// Notify all the IP observers and then remove them all
|
||||
// IP address observers are notified when
|
||||
// - the IP is removed
|
||||
// - the name is removed
|
||||
func (re *recordEntry) notifyIPObservers() {
|
||||
numObservers := len(re.observers)
|
||||
if numObservers > 0 {
|
||||
Debug.Printf("[zonedb] Notifying %d observers of '%s'", numObservers, re.ip)
|
||||
for _, observer := range re.observers {
|
||||
observer()
|
||||
}
|
||||
re.observers = make([]ZoneRecordObserver, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (re *recordEntry) TTL() int {
|
||||
// if we never set the TTL (eg, when using AddRecord()), return the standard value...
|
||||
if re.ttl == 0 {
|
||||
return int(DefaultLocalTTL)
|
||||
}
|
||||
return re.ttl
|
||||
}
|
||||
|
||||
// Check if the info in this record is still valid according to the TTL
|
||||
func (re *recordEntry) hasExpired(now time.Time) bool {
|
||||
return re.ttl > 0 && now.Sub(re.insTime).Seconds() > float64(re.ttl)
|
||||
}
|
||||
|
||||
func (re *recordEntry) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s", re.Record)
|
||||
lobs := len(re.observers)
|
||||
if lobs > 0 {
|
||||
fmt.Fprintf(&buf, "/OBS:%d", lobs)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// a name, with group of records by IPv4
|
||||
type name struct {
|
||||
name string
|
||||
ipv4 map[IPv4]*recordEntry // all the IPv4 records for this name
|
||||
observers []ZoneRecordObserver // the observers for this name
|
||||
lastRefreshTime time.Time // last time this name was updated by broadcasting a query
|
||||
lastAccessTime time.Time // last time a Lookup* used info from this name
|
||||
}
|
||||
|
||||
func newName(n string) *name {
|
||||
return &name{
|
||||
name: n,
|
||||
ipv4: make(map[IPv4]*recordEntry),
|
||||
observers: make([]ZoneRecordObserver, 0),
|
||||
}
|
||||
}
|
||||
func (n *name) len() int { return len(n.ipv4) }
|
||||
func (n *name) empty() bool { return n.len() == 0 }
|
||||
func (n *name) hasIP(ip net.IP) bool { return n.hasIPv4(ipToIPv4(ip)) }
|
||||
func (n *name) hasIPv4(ip IPv4) bool { _, found := n.ipv4[ip]; return found }
|
||||
|
||||
// Get all the entries for this name
|
||||
func (n *name) getAllEntries() (res []*recordEntry) {
|
||||
for _, entry := range n.ipv4 {
|
||||
res = append(res, entry)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the entry for an IP in this name
|
||||
func (n *name) getEntryForIP(ip IPv4) (res *recordEntry) {
|
||||
if entry, found := n.ipv4[ip]; found {
|
||||
res = entry
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a new IPv4 to this name
|
||||
func (n *name) addIP(zr ZoneRecord, now time.Time) (*recordEntry, error) {
|
||||
ne := newRecordEntry(zr, now)
|
||||
ipv4 := ipToIPv4(zr.IP())
|
||||
n.ipv4[ipv4] = ne
|
||||
n.notifyNameObservers()
|
||||
return ne, nil
|
||||
}
|
||||
|
||||
// Delete by IP. Pass uninitialized value for wildcard
|
||||
func (n *name) deleteIP(ip net.IP) int {
|
||||
count := 0
|
||||
if !ip.Equal(net.IP{}) {
|
||||
ipv4 := ipToIPv4(ip)
|
||||
if ipv4V, found := n.ipv4[ipv4]; found {
|
||||
ipv4V.notifyIPObservers()
|
||||
delete(n.ipv4, ipv4)
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
for ipv4K, ipv4V := range n.ipv4 {
|
||||
ipv4V.notifyIPObservers()
|
||||
delete(n.ipv4, ipv4K)
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
n.notifyNameObservers()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Update the list of IPs for this name, adding new ones and removing old IPs...
|
||||
func (n *name) updateIPs(records []ZoneRecord, now time.Time) (added int, removed int) {
|
||||
ipsMap := make(map[IPv4]bool)
|
||||
|
||||
// add the new IPs
|
||||
for _, record := range records {
|
||||
ipv4 := ipToIPv4(record.IP())
|
||||
ipsMap[ipv4] = true
|
||||
if !n.hasIPv4(ipv4) {
|
||||
n.addIP(record, now)
|
||||
added++
|
||||
}
|
||||
}
|
||||
|
||||
// remove the old IPs
|
||||
for ipv4, ipRecord := range n.ipv4 {
|
||||
if _, isNew := ipsMap[ipv4]; !isNew {
|
||||
ipRecord.notifyIPObservers() // we must notify the observers before removing the IP record...
|
||||
delete(n.ipv4, ipv4)
|
||||
removed++
|
||||
}
|
||||
}
|
||||
|
||||
// notify observers and update times
|
||||
if added > 0 || removed > 0 {
|
||||
n.lastRefreshTime = now
|
||||
n.notifyNameObservers()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a observer for this name
|
||||
func (n *name) addNameObserver(observer ZoneRecordObserver) {
|
||||
n.observers = append(n.observers, observer)
|
||||
}
|
||||
|
||||
// Notify and flush all the observers
|
||||
// Name observers are notified when
|
||||
// - an IP is added
|
||||
// - an IP is removed
|
||||
// - the name is removed
|
||||
func (n *name) notifyNameObservers() {
|
||||
numObservers := len(n.observers)
|
||||
if numObservers > 0 {
|
||||
Debug.Printf("[zonedb] Notifying %d observers of '%s'", numObservers, n.name)
|
||||
for _, observer := range n.observers {
|
||||
observer()
|
||||
}
|
||||
n.observers = make([]ZoneRecordObserver, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a set of IP records to a comma-separated string
|
||||
func (n *name) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, ip := range n.ipv4 {
|
||||
fmt.Fprintf(&buf, "%s, ", ip)
|
||||
}
|
||||
l := buf.Len()
|
||||
if l > 2 {
|
||||
buf.Truncate(l - 2)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// all the names in a ident
|
||||
type nameSet struct {
|
||||
names map[string]*name
|
||||
}
|
||||
|
||||
func newNameSet() *nameSet { return &nameSet{names: make(map[string]*name)} }
|
||||
func (ns *nameSet) len() int { return len(ns.names) }
|
||||
func (ns *nameSet) empty() bool { return ns.len() == 0 }
|
||||
func (ns *nameSet) hasName(name string) bool {
|
||||
n, found := ns.names[name]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
// the name must have some valid IPs in order to be considered as "existent"
|
||||
return len(n.ipv4) > 0
|
||||
}
|
||||
|
||||
// Get the zone entries for a name
|
||||
// If the name does not exist and `create` is true, a new name will be created
|
||||
func (ns *nameSet) getName(name string, create bool) (n *name) {
|
||||
n, found := ns.names[name]
|
||||
if !found && create {
|
||||
newName := newName(name)
|
||||
ns.names[name] = newName
|
||||
return newName
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Get all the entries for a name
|
||||
func (ns *nameSet) getEntriesForName(name string) (res []*recordEntry) {
|
||||
n := ns.getName(name, false)
|
||||
if n != nil {
|
||||
res = n.getAllEntries()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get all the entries for an IP, for all names
|
||||
func (ns *nameSet) getEntriesForIP(ip IPv4) (res []*recordEntry) {
|
||||
for _, name := range ns.names {
|
||||
entry := name.getEntryForIP(ip)
|
||||
if entry != nil {
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a new IPv4 to a name
|
||||
func (ns *nameSet) addIPToName(zr ZoneRecord, now time.Time) (*recordEntry, error) {
|
||||
n := ns.getName(zr.Name(), true)
|
||||
ipv4 := ipToIPv4(zr.IP())
|
||||
if n.hasIPv4(ipv4) {
|
||||
return n.getEntryForIP(ipv4), DuplicateError{}
|
||||
}
|
||||
ns.touchName(zr.Name(), now)
|
||||
return n.addIP(zr, now)
|
||||
}
|
||||
|
||||
// Delete by name and IP. Pass uninitialized values for wildcard
|
||||
func (ns nameSet) deleteNameIP(name string, ip net.IP) int {
|
||||
count := 0
|
||||
if name != "" {
|
||||
if nameV, found := ns.names[name]; found {
|
||||
count += nameV.deleteIP(ip)
|
||||
if nameV.empty() {
|
||||
delete(ns.names, name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for nameK, nameV := range ns.names {
|
||||
count += nameV.deleteIP(ip)
|
||||
if nameV.empty() {
|
||||
delete(ns.names, nameK)
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Get the name query time
|
||||
func (ns *nameSet) getNameLastAccess(n string) time.Time {
|
||||
if name, found := ns.names[n]; found {
|
||||
return name.lastAccessTime
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// Touch the last access time for a name
|
||||
// The access time is saved only for locally-introduced records (otherwise it could
|
||||
// be lost when names are irrelevant...)
|
||||
func (ns *nameSet) touchName(name string, now time.Time) {
|
||||
Debug.Printf("[zonedb] Touching name %s", name)
|
||||
n := ns.getName(name, false)
|
||||
if n != nil {
|
||||
n.lastAccessTime = now
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *nameSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, name := range ns.names {
|
||||
fmt.Fprintf(&buf, "%s, ", name)
|
||||
}
|
||||
l := buf.Len()
|
||||
if l > 2 {
|
||||
buf.Truncate(l - 2)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// Name sets, by ident name
|
||||
type identRecordSet map[string]*nameSet
|
||||
|
||||
func (irs identRecordSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
for ident, nameset := range irs {
|
||||
fmt.Fprintf(&buf, "%.12s: %s\n", ident, nameset)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// Invariant on updateRequests: they are in ascending order of time.
|
||||
type refreshRequest struct {
|
||||
name string
|
||||
time time.Time
|
||||
}
|
||||
|
||||
type ZoneConfig struct {
|
||||
// The local domain
|
||||
Domain string
|
||||
// The interface where mDNS operates
|
||||
Iface *net.Interface
|
||||
// Refresh interval for local names in the zone database (disabled by default) (in seconds)
|
||||
RefreshInterval int
|
||||
// Forget about remote info if nobody asks for it in this time (in seconds)
|
||||
RelevantTime int
|
||||
// TTL returnined in local asnwers (with mDNS)
|
||||
LocalTTL int
|
||||
// Force a specific mDNS client
|
||||
MDNSClient ZoneMDNSClient
|
||||
// Force a specific mDNS server
|
||||
MDNSServer ZoneMDNSServer
|
||||
// For a specific clock provider
|
||||
Clock clock.Clock
|
||||
}
|
||||
|
||||
// Zone database
|
||||
// The zone database contains the locally known information about the zone.
|
||||
// The zone database uses some other mechanisms (eg, mDNS) for finding unknown
|
||||
// information or for keeping current information up to date.
|
||||
type ZoneDb struct {
|
||||
mx sync.RWMutex
|
||||
idents identRecordSet
|
||||
|
||||
mdnsCli ZoneMDNSClient
|
||||
mdnsSrv ZoneMDNSServer
|
||||
iface *net.Interface
|
||||
domain string // the local domain
|
||||
relevantLimit time.Duration // if no one asks for something in this time, it is not relevant a anymore...
|
||||
clock clock.Clock
|
||||
|
||||
refreshScheds *SchedQueue
|
||||
refreshCloseChan chan bool
|
||||
refreshInterval time.Duration
|
||||
}
|
||||
|
||||
// Create a new zone database
|
||||
func NewZoneDb(config ZoneConfig) (zone *ZoneDb, err error) {
|
||||
zone = &ZoneDb{
|
||||
domain: config.Domain,
|
||||
idents: make(identRecordSet),
|
||||
mdnsCli: config.MDNSClient,
|
||||
mdnsSrv: config.MDNSServer,
|
||||
iface: config.Iface,
|
||||
clock: config.Clock,
|
||||
relevantLimit: time.Duration(DefaultRelevantTime) * time.Second,
|
||||
refreshCloseChan: make(chan bool),
|
||||
}
|
||||
|
||||
// fix the default configuration parameters
|
||||
if zone.clock == nil {
|
||||
zone.clock = clock.New()
|
||||
}
|
||||
if len(zone.domain) == 0 {
|
||||
zone.domain = DefaultLocalDomain
|
||||
}
|
||||
if config.RefreshInterval > 0 {
|
||||
zone.refreshInterval = time.Duration(config.RefreshInterval) * time.Second
|
||||
}
|
||||
if config.RelevantTime > 0 {
|
||||
zone.relevantLimit = time.Duration(config.RelevantTime) * time.Second
|
||||
}
|
||||
|
||||
if zone.refreshInterval > 0 {
|
||||
zone.refreshScheds = NewSchedQueue(zone.clock)
|
||||
}
|
||||
|
||||
// Create the mDNS client and server
|
||||
if zone.mdnsCli == nil {
|
||||
if zone.mdnsCli, err = NewMDNSClient(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if zone.mdnsSrv == nil {
|
||||
mdnsTTL := DefaultLocalTTL
|
||||
if config.LocalTTL > 0 {
|
||||
mdnsTTL = config.LocalTTL
|
||||
}
|
||||
|
||||
if zone.mdnsSrv, err = NewMDNSServer(zone, false, mdnsTTL); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Start the zone database
|
||||
func (zone *ZoneDb) Start() (err error) {
|
||||
if zone.iface != nil {
|
||||
Info.Printf("[zonedb] Using mDNS on %+v", zone.iface)
|
||||
} else {
|
||||
Info.Printf("[zonedb] Using mDNS on all interfaces")
|
||||
}
|
||||
|
||||
if err = zone.mdnsCli.Start(zone.iface); err != nil {
|
||||
return
|
||||
}
|
||||
if err = zone.mdnsSrv.Start(zone.iface); err != nil {
|
||||
return
|
||||
}
|
||||
if zone.refreshInterval > 0 {
|
||||
zone.refreshScheds.Start()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Perform a graceful shutdown of the zone database
|
||||
func (zone *ZoneDb) Stop() error {
|
||||
if zone.refreshInterval > 0 {
|
||||
Debug.Printf("[zonedb] Closing background updaters...")
|
||||
zone.refreshScheds.Stop()
|
||||
}
|
||||
|
||||
Debug.Printf("[zonedb] Exiting mDNS client and server...")
|
||||
zone.mdnsCli.Stop()
|
||||
zone.mdnsSrv.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtain the domain where this database keeps information for
|
||||
func (zone *ZoneDb) Domain() string {
|
||||
return zone.domain
|
||||
}
|
||||
|
||||
// Return the status string
|
||||
func (zone *ZoneDb) Status() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintln(&buf, "Local domain", zone.domain)
|
||||
fmt.Fprintln(&buf, "Interface", zone.iface)
|
||||
fmt.Fprintf(&buf, "Zone database:\n%s", zone)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Add a record with `name` pointing to `ip`
|
||||
func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
Debug.Printf("[zonedb] Adding record: '%s'/'%s'[%s]", ident, name, ip)
|
||||
record := Record{dns.Fqdn(name), ip, 0, 0, 0}
|
||||
_, err = zone.getNameSet(ident).addIPToName(record, zone.clock.Now())
|
||||
return
|
||||
}
|
||||
|
||||
// Delete matching records (uninitialised values act as wildcards)
|
||||
func (zone *ZoneDb) DeleteRecords(ident string, name string, ip net.IP) int {
|
||||
if name != "" {
|
||||
name = dns.Fqdn(name)
|
||||
}
|
||||
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
return zone.deleteIdentNameIP(ident, name, ip)
|
||||
}
|
||||
|
||||
// Observe a name.
|
||||
// The name must have at least one valid IP address.
|
||||
// Name observers are notified when
|
||||
// - an IP is added
|
||||
// - an IP is removed
|
||||
// - the name is removed
|
||||
// The observer will be invoked on any change/removal that affects this name.
|
||||
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
|
||||
// The observer should not try to lock the ZoneDB (you will get a deadlock)
|
||||
func (zone *ZoneDb) ObserveName(name string, observer ZoneRecordObserver) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
cnt := 0
|
||||
name = dns.Fqdn(name)
|
||||
for _, nameset := range zone.idents {
|
||||
if n := nameset.getName(name, false); n != nil {
|
||||
n.addNameObserver(observer)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
if cnt == 0 {
|
||||
err = LookupError(fmt.Sprintf("%s", name))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Observe a IP address.
|
||||
// The IP address must be exit in the database.
|
||||
// IP address observers are notified when
|
||||
// - the IP is removed
|
||||
// - the name is removed
|
||||
// The observer will be invoked on any change/removal that affects this IP.
|
||||
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
|
||||
// The observer should not try to lock the ZoneDB (you will get a deadlock)
|
||||
func (zone *ZoneDb) ObserveInaddr(inaddr string, observer ZoneRecordObserver) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
cnt := 0
|
||||
revIP, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return newParseError("lookup address", inaddr)
|
||||
}
|
||||
for _, nameset := range zone.idents {
|
||||
for _, entry := range nameset.getEntriesForIP(revIP) {
|
||||
entry.addIPObserver(observer)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
if cnt == 0 {
|
||||
err = LookupError(fmt.Sprintf("%+v/%+v", inaddr, revIP))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the string representation of a zone
|
||||
func (zone *ZoneDb) String() string {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
return zone.idents.String()
|
||||
}
|
||||
|
||||
// Notify that a container has died
|
||||
func (zone *ZoneDb) ContainerDied(ident string) error {
|
||||
Info.Printf("[zonedb] Container %s down. Removing records", ident)
|
||||
zone.DeleteRecords(ident, "", net.IP{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return true if a remote name is still relevant
|
||||
func (zone *ZoneDb) IsNameRelevant(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
lastAccess := time.Time{}
|
||||
for _, nameset := range zone.idents {
|
||||
t := nameset.getNameLastAccess(n)
|
||||
if !t.IsZero() && (lastAccess.IsZero() || t.After(lastAccess)) {
|
||||
lastAccess = t
|
||||
}
|
||||
}
|
||||
|
||||
return !lastAccess.IsZero() && zone.clock.Now().Sub(lastAccess) <= zone.relevantLimit
|
||||
}
|
||||
|
||||
// Returns true if the info we have for a remote name has expired. Local names are never expired
|
||||
// Returns false if we do not have info for this name from remote peers.
|
||||
func (zone *ZoneDb) IsNameExpired(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
now := zone.clock.Now()
|
||||
remoteNameSet := zone.getNameSet(defaultRemoteIdent)
|
||||
entries := remoteNameSet.getEntriesForName(n)
|
||||
if entries != nil {
|
||||
for _, entry := range entries {
|
||||
if entry.hasExpired(now) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) HasNameLocalInfo(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
remoteNameSet := zone.getNameSet(defaultRemoteIdent)
|
||||
for _, nameset := range zone.idents {
|
||||
if nameset != remoteNameSet {
|
||||
if nameset.hasName(n) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Return true if the we have obtained some information for a name from remote peers
|
||||
// We can have both local (eg, introduced through the HTTP API) and remote (eg, mDNS)
|
||||
// information for a name. This method only checks if there is remote information...
|
||||
func (zone *ZoneDb) HasNameRemoteInfo(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
remoteNameSet := zone.getNameSet(defaultRemoteIdent)
|
||||
if remoteNameSet != nil {
|
||||
return remoteNameSet.hasName(n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get or create a name set
|
||||
func (zone *ZoneDb) getNameSet(ident string) *nameSet {
|
||||
ns, found := zone.idents[ident]
|
||||
if !found {
|
||||
ns = newNameSet()
|
||||
zone.idents[ident] = ns
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
// Delete by ident, name and IP. Pass uninitialized value for wildcard
|
||||
func (zone *ZoneDb) deleteIdentNameIP(ident string, name string, ip net.IP) int {
|
||||
count := 0
|
||||
if ident != "" {
|
||||
if identV, found := zone.idents[ident]; found {
|
||||
count += identV.deleteNameIP(name, ip)
|
||||
if identV.empty() {
|
||||
delete(zone.idents, ident)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for identK, identV := range zone.idents {
|
||||
count += identV.deleteNameIP(name, ip)
|
||||
if identV.empty() {
|
||||
delete(zone.idents, identK)
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type uniqZoneRecordKey struct {
|
||||
name string
|
||||
ipv4 IPv4
|
||||
}
|
||||
|
||||
// A group of ZoneRecords where there are no duplicates (according to the name & IPv4)
|
||||
type uniqZoneRecords map[uniqZoneRecordKey]ZoneRecord
|
||||
|
||||
func newUniqZoneRecords() uniqZoneRecords {
|
||||
return make(uniqZoneRecords, 0)
|
||||
}
|
||||
|
||||
// Add a new ZoneRecord to the group
|
||||
func (uzr *uniqZoneRecords) add(zr ZoneRecord) {
|
||||
key := uniqZoneRecordKey{zr.Name(), ipToIPv4(zr.IP())}
|
||||
(*uzr)[key] = zr
|
||||
}
|
||||
|
||||
// Return the group as an slice
|
||||
func (uzr *uniqZoneRecords) toSlice() []ZoneRecord {
|
||||
res := make([]ZoneRecord, len(*uzr))
|
||||
i := 0
|
||||
for _, r := range *uzr {
|
||||
res[i] = r
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Lookup in the database for locally-introduced information
|
||||
func (zone *ZoneDb) lookup(target string, lfun func(ns *nameSet) []*recordEntry) (res []ZoneRecord, err error) {
|
||||
uniq := newUniqZoneRecords()
|
||||
for identName, nameset := range zone.idents {
|
||||
if identName != defaultRemoteIdent {
|
||||
for _, ze := range lfun(nameset) {
|
||||
uniq.add(ze)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(uniq) == 0 {
|
||||
return nil, LookupError(target)
|
||||
}
|
||||
return uniq.toSlice(), nil
|
||||
}
|
||||
|
||||
// Perform a lookup for a name in the zone
|
||||
// The name can be resolved locally with the local database
|
||||
func (zone *ZoneDb) LookupName(name string) (res []ZoneRecord, err error) {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
|
||||
// note: LookupName() is usually called from the mDNS server, so we do not touch the name
|
||||
name = dns.Fqdn(name)
|
||||
Debug.Printf("[zonedb] Looking for name '%s' in local database", name)
|
||||
return zone.lookup(name, func(ns *nameSet) []*recordEntry { return ns.getEntriesForName(name) })
|
||||
}
|
||||
|
||||
// Perform a lookup for a IP address in the zone
|
||||
// The address can be resolved locally with the local database
|
||||
func (zone *ZoneDb) LookupInaddr(inaddr string) (res []ZoneRecord, err error) {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
|
||||
// note: LookupInaddr() is usually called from the mDNS server, so we do not touch the name
|
||||
revIPv4, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return nil, newParseError("lookup address", inaddr)
|
||||
}
|
||||
Debug.Printf("[zonedb] Looking for address in local database: '%s' (%s)", revIPv4, inaddr)
|
||||
return zone.lookup(inaddr, func(ns *nameSet) []*recordEntry { return ns.getEntriesForIP(revIPv4) })
|
||||
}
|
||||
|
||||
// Perform a domain lookup with mDNS
|
||||
func (zone *ZoneDb) domainLookup(target string, lfun ZoneLookupFunc) (res []ZoneRecord, err error) {
|
||||
// no local results have been obtained in the local database: try with a mDNS query
|
||||
Debug.Printf("[zonedb] '%s' not in local database... trying with mDNS", target)
|
||||
lanswers, err := lfun(target)
|
||||
if err != nil {
|
||||
Debug.Printf("[zonedb] mDNS lookup error for '%s': %s", target, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if the request has been successful, save the IP in the local database and return the corresponding ZoneRecord
|
||||
// (we do not get the remote ident in the mDNS reply, so we save it in a "remote" ident)
|
||||
Debug.Printf("[zonedb] adding '%s' (obtained with mDNS) to '%s'", lanswers, target)
|
||||
res = make([]ZoneRecord, len(lanswers))
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
uniq := newUniqZoneRecords()
|
||||
remoteIdent := zone.getNameSet(defaultRemoteIdent)
|
||||
for _, answer := range lanswers {
|
||||
r, err := remoteIdent.addIPToName(answer, now)
|
||||
if err != nil {
|
||||
if _, ok := err.(DuplicateError); !ok {
|
||||
zone.mx.Unlock()
|
||||
Warning.Printf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
uniq.add(r)
|
||||
}
|
||||
zone.mx.Unlock()
|
||||
|
||||
return uniq.toSlice(), nil
|
||||
}
|
||||
|
||||
// Perform a lookup for a name in the zone
|
||||
// The name can be resolved locally with the local database or with some other resolution method (eg, a mDNS query)
|
||||
func (zone *ZoneDb) DomainLookupName(name string) (res []ZoneRecord, err error) {
|
||||
name = dns.Fqdn(name)
|
||||
Debug.Printf("[zonedb] Looking for name '%s' in local(&remote) database", name)
|
||||
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
uniq := newUniqZoneRecords()
|
||||
for identName, nameset := range zone.idents {
|
||||
for _, ze := range nameset.getEntriesForName(name) {
|
||||
// filter the entries with expired TTL
|
||||
// locally introduced entries are never expired: they always have TTL=0
|
||||
if ze.hasExpired(now) {
|
||||
Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", name, ze)
|
||||
nameset.deleteNameIP(name, net.IP{})
|
||||
} else {
|
||||
uniq.add(ze)
|
||||
}
|
||||
}
|
||||
if identName != defaultRemoteIdent {
|
||||
nameset.touchName(name, now)
|
||||
}
|
||||
}
|
||||
zone.mx.Unlock()
|
||||
|
||||
if len(uniq) > 0 {
|
||||
Debug.Printf("[zonedb] '%s' resolved in local database", name)
|
||||
res = uniq.toSlice()
|
||||
} else {
|
||||
res, err = zone.domainLookup(name, zone.mdnsCli.LookupName)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
zone.startUpdatingName(name)
|
||||
return res, nil
|
||||
}
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
|
||||
// Perform a lookup for a IP address in the zone
|
||||
// The address can be resolved either with the local database or
|
||||
// with some other resolution method (eg, a mDNS query)
|
||||
func (zone *ZoneDb) DomainLookupInaddr(inaddr string) (res []ZoneRecord, err error) {
|
||||
revIPv4, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return nil, newParseError("lookup address", inaddr)
|
||||
}
|
||||
|
||||
Debug.Printf("[zonedb] Looking for address in local(&remote) database: '%s' (%s)", revIPv4, inaddr)
|
||||
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
uniq := newUniqZoneRecords()
|
||||
for identName, nameset := range zone.idents {
|
||||
for _, ze := range nameset.getEntriesForIP(revIPv4) {
|
||||
// filter the entries with expired TTL
|
||||
// locally introduced entries are never expired: they always have TTL=0
|
||||
if ze.hasExpired(now) {
|
||||
Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", revIPv4, ze)
|
||||
nameset.deleteNameIP("", revIPv4.toNetIP())
|
||||
} else {
|
||||
uniq.add(ze)
|
||||
if identName != defaultRemoteIdent {
|
||||
nameset.touchName(ze.Name(), now)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
zone.mx.Unlock()
|
||||
|
||||
if len(uniq) > 0 {
|
||||
Debug.Printf("[zonedb] '%s' resolved in local database", inaddr)
|
||||
res = uniq.toSlice()
|
||||
} else {
|
||||
res, err = zone.domainLookup(inaddr, zone.mdnsCli.LookupInaddr)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
// note: even for reverse addresses, we perform the background updates in the name, not in the IP
|
||||
// this simplifies the process and produces basically the same results...
|
||||
// note: we do not spend time trying to update names that did not return an initial response...
|
||||
for _, r := range res {
|
||||
zone.startUpdatingName(r.Name())
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Names updates try to find all the IPs for a given name with a mDNS query
|
||||
//
|
||||
// There are two types of names updates:
|
||||
//
|
||||
// - immediate updates.
|
||||
// After a `DomainLookup*()` for a name not in the database we will return the
|
||||
// first IP we can get with mDNS from other peers. Waiting for more responses would
|
||||
// mean more latency in the response to the client, so we send only one answer BUT
|
||||
// we also trigger an immediate update request for that name in order to get all
|
||||
// the other IPs we didn't wait for...
|
||||
//
|
||||
// - periodic updates
|
||||
// once we have obtained the first group of IPs for a name, we schedule a periodic
|
||||
// refresh for that name, so we keep the list of IPs for that name up to date.
|
||||
//
|
||||
// These names updates are repeated until either
|
||||
//
|
||||
// a) there is no interest in the name, determined by a global 'relevant time'
|
||||
// and the last time some local client asked about the name,
|
||||
// or
|
||||
// b) no peers answer one of our refresh requests (because the name has probably
|
||||
// disappeared from the network)
|
||||
//
|
||||
|
||||
// Check if we must start updating a name and, in that case, trigger a immediate update
|
||||
func (zone *ZoneDb) startUpdatingName(name string) {
|
||||
if zone.refreshInterval > 0 {
|
||||
zone.mx.Lock()
|
||||
|
||||
// check if we should enqueue a refresh request for this name
|
||||
n := zone.getNameSet(defaultRemoteIdent).getName(name, true)
|
||||
if n.lastRefreshTime.IsZero() {
|
||||
now := zone.clock.Now()
|
||||
n.lastRefreshTime = now
|
||||
zone.mx.Unlock()
|
||||
|
||||
Debug.Printf("[zonedb] Creating new immediate refresh request for '%s'", name)
|
||||
zone.refreshScheds.Add(func() time.Time { return zone.updater(name) }, now)
|
||||
} else {
|
||||
zone.mx.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the IPs we have for a name
|
||||
func (zone *ZoneDb) updater(name string) (nextTime time.Time) {
|
||||
deleteRemoteInfo := func() {
|
||||
zone.mx.Lock()
|
||||
zone.getNameSet(defaultRemoteIdent).deleteNameIP(name, net.IP{})
|
||||
zone.mx.Unlock()
|
||||
}
|
||||
|
||||
// if nobody has asked for this name for long time, just forget about it...
|
||||
if !zone.IsNameRelevant(name) || zone.IsNameExpired(name) {
|
||||
Debug.Printf("[zonedb] '%s' seem to be irrelevant now: removing any remote information", name)
|
||||
deleteRemoteInfo()
|
||||
return
|
||||
}
|
||||
|
||||
// perform the refresh for this name
|
||||
fullName := dns.Fqdn(name)
|
||||
startTime := zone.clock.Now()
|
||||
Debug.Printf("[zonedb] Refreshing name '%s' with mDNS...", fullName)
|
||||
res, _ := zone.mdnsCli.InsistentLookupName(fullName)
|
||||
if res != nil && len(res) > 0 {
|
||||
numIps := len(res)
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
added, removed := zone.getNameSet(defaultRemoteIdent).getName(name, true).updateIPs(res, now)
|
||||
zone.mx.Unlock()
|
||||
Debug.Printf("[zonedb] Obtained %d IPs for name '%s' with mDNS: %d added, %d removed",
|
||||
numIps, name, added, removed)
|
||||
|
||||
// once the name has been updated, we re-schedule the update
|
||||
nextTime = startTime.Add(zone.refreshInterval)
|
||||
Debug.Printf("[zonedb] Rescheduling update for '%s' in %s", name, nextTime.Sub(zone.clock.Now()))
|
||||
} else {
|
||||
Debug.Printf("[zonedb] nobody knows about '%s'... removing", name)
|
||||
deleteRemoteInfo()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// Check that the refreshing mechanism works as expected
|
||||
func TestZoneRefresh(t *testing.T) {
|
||||
const (
|
||||
refreshInterval = 5
|
||||
relevantTime = 30
|
||||
)
|
||||
var (
|
||||
name = "mysql.weave.local."
|
||||
addr1 = "10.2.8.4"
|
||||
addr2 = "10.2.8.5"
|
||||
addr3 = "10.2.8.6"
|
||||
addr4 = "10.2.8.7"
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestZoneRefresh starting")
|
||||
|
||||
clk := newMockedClock()
|
||||
|
||||
// Create multiple zone databases, linked through mocked mDNS connections
|
||||
zc := ZoneConfig{
|
||||
RefreshInterval: refreshInterval,
|
||||
RelevantTime: relevantTime,
|
||||
Clock: clk,
|
||||
}
|
||||
dbs := newZoneDbsWithMockedMDns(2, zc)
|
||||
dbs.Start()
|
||||
defer dbs.Stop()
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // allow for server to get going
|
||||
|
||||
Debug.Printf("Adding '%s' to Db #1", name)
|
||||
dbs[0].Zone.AddRecord("someident", name, net.ParseIP(addr1))
|
||||
|
||||
Debug.Printf("Checking that the name %s is relevant (as it has been locally inserted) and not remote", name)
|
||||
wt.AssertTrue(t, dbs[0].Zone.IsNameRelevant(name), "name relevant")
|
||||
wt.AssertTrue(t, dbs[0].Zone.HasNameLocalInfo(name), "local name info")
|
||||
wt.AssertFalse(t, dbs[0].Zone.HasNameRemoteInfo(name), "remote name info")
|
||||
|
||||
Debug.Printf("Asking for '%s' to Db #1: should get 1 IP from the local database...", name)
|
||||
res, err := dbs[0].Zone.DomainLookupName(name)
|
||||
wt.AssertNoErr(t, err)
|
||||
Debug.Printf("Got: %s", res)
|
||||
t.Logf("Db #1 after the lookup:\n%s", dbs[0].Zone)
|
||||
wt.AssertEqualInt(t, len(res), 1, "lookup result")
|
||||
|
||||
clk.Forward(refreshInterval / 2)
|
||||
dbs.Flush()
|
||||
Debug.Printf("A couple of seconds later, we should still have one IP for that name")
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
wt.AssertNoErr(t, err)
|
||||
wt.AssertEqualInt(t, len(res), 1, "lookup result")
|
||||
|
||||
Debug.Printf("And then we add 2 IPs for that name at ZoneDb 2")
|
||||
clk.Forward(1)
|
||||
dbs.Flush()
|
||||
Debug.Printf("Adding 2 IPs to '%s' in Db #2", name)
|
||||
dbs[1].Zone.AddRecord("someident", name, net.ParseIP(addr2))
|
||||
dbs[1].Zone.AddRecord("someident", name, net.ParseIP(addr3))
|
||||
|
||||
Debug.Printf("Perform a lookup, to ensure the name will be updated in the background...")
|
||||
dbs[0].Zone.DomainLookupName(name)
|
||||
|
||||
Debug.Printf("Wait for a while, until a refresh is performed...")
|
||||
clk.Forward(refreshInterval + 1)
|
||||
dbs.Flush()
|
||||
Debug.Printf("A refresh should have been scheduled now: we should have 3 IPs:")
|
||||
Debug.Printf("the first (local) IP and the others obtained from zone2 with a mDNS query")
|
||||
Debug.Printf("Asking for '%s' again... we should have 3 IPs now", name)
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
Debug.Printf("Got: %s", res)
|
||||
t.Logf("Db #1 after the second lookup:\n%s", dbs[0].Zone)
|
||||
wt.AssertEqualInt(t, len(res), 3, "lookup result length")
|
||||
|
||||
Debug.Printf("We will not ask for `name` for a while, so it will become irrelevant and will be removed...")
|
||||
clk.Forward(refreshInterval + relevantTime + 1)
|
||||
dbs.Flush()
|
||||
|
||||
// the name should be irrelevant now, and all remote info should have been
|
||||
// removed from the zone database
|
||||
Debug.Printf("Name '%s' should not be in the remote database in ZoneDb 1", name)
|
||||
wt.AssertFalse(t, dbs[0].Zone.IsNameRelevant(name), "name still relevant after some inactivity time")
|
||||
wt.AssertTrue(t, dbs[0].Zone.HasNameLocalInfo(name), "local name info")
|
||||
wt.AssertFalse(t, dbs[0].Zone.HasNameRemoteInfo(name), "remote name info")
|
||||
|
||||
Debug.Printf("There is no remote info about this name at zone 1: a new IP appears remotely meanwhile...")
|
||||
clk.Forward(1)
|
||||
dbs.Flush()
|
||||
|
||||
Debug.Printf("Adding '%s' to Db #2", name)
|
||||
dbs[1].Zone.AddRecord("someident", name, net.ParseIP(addr4))
|
||||
|
||||
Debug.Printf("When we ask about this name again, we get 4 IPs (1 local, 3 remote)")
|
||||
Debug.Printf("Asking for '%s' again... the first lookup will return only the local results", name)
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
wt.AssertEqualInt(t, len(res), 1, "lookup result length")
|
||||
Debug.Printf("... but a second lookup should return all the results in the network")
|
||||
|
||||
clk.Forward(refreshInterval + 1)
|
||||
dbs.Flush()
|
||||
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
Debug.Printf("Got: %s", res)
|
||||
wt.AssertEqualInt(t, len(res), 4, "lookup result length")
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func TestZone(t *testing.T) {
|
||||
var (
|
||||
container1 = "deadbeef"
|
||||
container2 = "cowjuice"
|
||||
name1 = "test1.weave."
|
||||
name1Addr1 = "10.9.2.1/24"
|
||||
name1Addr2 = "10.9.2.2/24"
|
||||
revName1Addr1 = "1.2.9.10.in-addr.arpa."
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
ip1, _, _ := net.ParseCIDR(name1Addr1)
|
||||
t.Logf("Adding '%s'/%s to '%s'", name1, ip1, container1)
|
||||
err = zone.AddRecord(container1, name1, ip1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
// Add a few more records to make the job harder
|
||||
t.Logf("Adding 'adummy.weave.' to 'abcdef0123'")
|
||||
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.9.0.1"))
|
||||
wt.AssertNoErr(t, err)
|
||||
t.Logf("Adding 'zdummy.weave.' to '0123abcdef'")
|
||||
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.9.0.2"))
|
||||
wt.AssertNoErr(t, err)
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
|
||||
t.Logf("Checking if we can find the name '%s'", name1)
|
||||
foundIPs, err := zone.LookupName(name1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
if !foundIPs[0].IP().Equal(ip1) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
|
||||
t.Logf("Checking if we cannot find some silly name like 'something.wrong'")
|
||||
foundIPs, err = zone.LookupName("something.wrong")
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), fmt.Sprintf("unknown name: %+v", foundIPs))
|
||||
|
||||
ip2, _, _ := net.ParseCIDR(name1Addr2)
|
||||
t.Logf("Adding a second IP for '%s'/%s to '%s'", name1, ip2, container1)
|
||||
err = zone.AddRecord(container1, name1, ip2)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
t.Logf("Checking if we can find both the old IP and the new IP for '%s'", name1)
|
||||
foundIPs, err = zone.LookupName(name1)
|
||||
wt.AssertNoErr(t, err)
|
||||
if !(foundIPs[0].IP().Equal(ip1) || foundIPs[1].IP().Equal(ip1)) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
if !(foundIPs[0].IP().Equal(ip2) || foundIPs[1].IP().Equal(ip2)) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
|
||||
t.Logf("Checking if we can find the address by IP '1.2.9.10.in-addr.arpa.'")
|
||||
foundNames, err := zone.LookupInaddr("1.2.9.10.in-addr.arpa.")
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
if foundNames[0].Name() != name1 {
|
||||
t.Fatal("Unexpected result for", ip1, foundNames)
|
||||
}
|
||||
|
||||
t.Logf("Checking we can not find an unknown address '30.20.10.1.in-addr.arpa.'")
|
||||
foundNames, err = zone.LookupInaddr("30.20.10.1.in-addr.arpa.")
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), fmt.Sprintf("unknown IP: %+v", foundNames))
|
||||
|
||||
t.Logf("Checking if adding again '%s'/%s in %s results in an error", name1, ip1, container1)
|
||||
err = zone.AddRecord(container1, name1, ip1)
|
||||
wt.AssertErrorType(t, err, (*DuplicateError)(nil), "duplicate add")
|
||||
|
||||
t.Logf("Adding '%s'/%s in %s too", name1, ip1, container2)
|
||||
err = zone.AddRecord(container2, name1, ip1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
name1Removed := 0
|
||||
err = zone.ObserveName(name1, func() { t.Logf("Observer #1 for '%s' notified.", name1); name1Removed++ })
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.ObserveName(name1, func() { t.Logf("Observer #2 for '%s' notified.", name1); name1Removed++ })
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.ObserveInaddr(revName1Addr1, func() { t.Logf("Observer #1 for '%s' notified.", revName1Addr1); name1Removed++ })
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
t.Logf("Deleting the %s in %s", ip1, container1)
|
||||
count := zone.DeleteRecords(container1, "", ip1)
|
||||
wt.AssertEqualInt(t, count, 1, "delete failed")
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
|
||||
t.Logf("Checking %s's observers have been notified on removal", name1)
|
||||
if name1Removed < 3 {
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
t.Fatalf("Unexpected number (%d) of calls to observers", name1Removed)
|
||||
}
|
||||
|
||||
t.Logf("Checking %s can be found", name1)
|
||||
_, err = zone.LookupName(name1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
t.Logf("Checking %s is not found after removing %s it in %s and %s in %s",
|
||||
name1, ip1, container2, ip2, container1)
|
||||
count = zone.DeleteRecords(container1, "", ip2)
|
||||
wt.AssertEqualInt(t, count, 1, "delete failed")
|
||||
count = zone.DeleteRecords(container2, "", ip1)
|
||||
wt.AssertEqualInt(t, count, 1, "delete failed")
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
_, err = zone.LookupName(name1)
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting record")
|
||||
|
||||
t.Logf("Checking if removing an unknown record results in an error")
|
||||
count = zone.DeleteRecords(container1, "", net.ParseIP("0.0.0.0"))
|
||||
wt.AssertEqualInt(t, count, 0, "delete failed")
|
||||
}
|
||||
|
||||
func TestDeleteRecords(t *testing.T) {
|
||||
var (
|
||||
id = "foobar"
|
||||
name = "foo.weave."
|
||||
addr1 = "10.2.2.3/24"
|
||||
addr2 = "10.2.7.8/24"
|
||||
)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
for _, addr := range []string{addr1, addr2} {
|
||||
ip, _, _ := net.ParseCIDR(addr)
|
||||
err := zone.AddRecord(id, name, ip)
|
||||
wt.AssertNoErr(t, err)
|
||||
}
|
||||
|
||||
_, err = zone.LookupName(name)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
count := zone.DeleteRecords(id, "", net.IP{})
|
||||
wt.AssertEqualInt(t, count, 2, "wildcard delete failed")
|
||||
_, err = zone.LookupName(name)
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting records for ident")
|
||||
}
|
||||
@@ -23,6 +23,17 @@ func (r Range) String() string { return fmt.Sprintf("[%s-%s)", r.Sta
|
||||
func (r Range) Overlaps(or Range) bool { return !(r.Start >= or.End || r.End <= or.Start) }
|
||||
func (r Range) Contains(addr Address) bool { return addr >= r.Start && addr < r.End }
|
||||
|
||||
func (r Range) AsCIDRString() string {
|
||||
prefixLen := 32
|
||||
for size := r.Size(); size > 1; size = size / 2 {
|
||||
if size%2 != 0 { // Size not a power of two; cannot be expressed as a CIDR.
|
||||
return r.String()
|
||||
}
|
||||
prefixLen--
|
||||
}
|
||||
return CIDR{Start: r.Start, PrefixLen: prefixLen}.String()
|
||||
}
|
||||
|
||||
type CIDR struct {
|
||||
Start Address
|
||||
PrefixLen int
|
||||
@@ -79,6 +90,10 @@ func (addr Address) IP4() (r net.IP) {
|
||||
return
|
||||
}
|
||||
|
||||
func (addr Address) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%q", addr.String())), nil
|
||||
}
|
||||
|
||||
func (addr Address) String() string {
|
||||
return addr.IP4().String()
|
||||
}
|
||||
@@ -98,3 +113,10 @@ func Min(a, b Offset) Offset {
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (addr Address) Reverse() Address {
|
||||
return ((addr >> 24) & 0xff) | // move byte 3 to byte 0
|
||||
((addr << 8) & 0xff0000) | // move byte 1 to byte 2
|
||||
((addr >> 8) & 0xff00) | // move byte 2 to byte 1
|
||||
((addr << 24) & 0xff000000) // byte 0 to byte 3
|
||||
}
|
||||
@@ -2,30 +2,54 @@ package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
"syscall"
|
||||
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
)
|
||||
|
||||
const (
|
||||
sleepTime = 100 * time.Millisecond
|
||||
)
|
||||
// Wait for an interface to come up.
|
||||
func EnsureInterface(ifaceName string) (*net.Interface, error) {
|
||||
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
iface, err := ensureInterface(s, ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iface, err
|
||||
}
|
||||
|
||||
// Wait `wait` seconds for an interface to come up. Pass zero to check once
|
||||
// and return immediately, or a negative value to wait indefinitely.
|
||||
func EnsureInterface(ifaceName string, wait int) (iface *net.Interface, err error) {
|
||||
if iface, err = findInterface(ifaceName); err == nil || wait == 0 {
|
||||
return
|
||||
func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) {
|
||||
if iface, err := findInterface(ifaceName); err == nil {
|
||||
return iface, nil
|
||||
}
|
||||
i := int64(math.MaxInt64)
|
||||
if wait > 0 {
|
||||
i = int64(time.Duration(wait) * time.Second / sleepTime)
|
||||
netlinkReceiveUntil(s, matchIfName(ifaceName))
|
||||
iface, err := findInterface(ifaceName)
|
||||
return iface, err
|
||||
}
|
||||
|
||||
// Wait for an interface to come up and have a route added to the multicast subnet.
|
||||
// This matches the behaviour in 'weave attach', which is the only context in which
|
||||
// we expect this to be called. If you change one, change the other to match.
|
||||
func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
|
||||
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for ; err != nil && i > 0; i-- {
|
||||
time.Sleep(sleepTime)
|
||||
iface, err = findInterface(ifaceName)
|
||||
defer s.Close()
|
||||
iface, err := ensureInterface(s, ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
dest := net.IPv4(224, 0, 0, 0)
|
||||
if CheckRouteExists(ifaceName, dest) {
|
||||
return iface, err
|
||||
}
|
||||
netlinkReceiveUntil(s, matchRoute(ifaceName, dest))
|
||||
return iface, err
|
||||
}
|
||||
|
||||
func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
||||
@@ -37,3 +61,40 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func netlinkReceiveUntil(s *nl.NetlinkSocket, f func(syscall.NetlinkMessage) (bool, error)) error {
|
||||
for {
|
||||
msgs, err := s.Receive()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range msgs {
|
||||
if done, err := f(m); done {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchIfName(ifaceName string) func(m syscall.NetlinkMessage) (bool, error) {
|
||||
return func(m syscall.NetlinkMessage) (bool, error) {
|
||||
switch m.Header.Type {
|
||||
case syscall.RTM_NEWLINK: // receive this type for link 'up'
|
||||
ifmsg := nl.DeserializeIfInfomsg(m.Data)
|
||||
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
name := ""
|
||||
for _, attr := range attrs {
|
||||
if attr.Attr.Type == syscall.IFA_LABEL {
|
||||
name = string(attr.Value[:len(attr.Value)-1])
|
||||
}
|
||||
}
|
||||
if ifaceName == name && ifmsg.Flags&syscall.IFF_UP != 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
+96
-9
@@ -3,26 +3,113 @@ package net
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
)
|
||||
|
||||
// A network is considered free if it does not overlap any existing
|
||||
// routes on this host. This is the same approach taken by Docker.
|
||||
func CheckNetworkFree(subnet *net.IPNet) error {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, route := range routes {
|
||||
func CheckNetworkFree(subnet *net.IPNet, ignoreIfaceNames map[string]struct{}) error {
|
||||
return forEachRoute(ignoreIfaceNames, func(name string, route netlink.Route) error {
|
||||
if route.Dst != nil && overlaps(route.Dst, subnet) {
|
||||
return fmt.Errorf("network %s would overlap with route %s", subnet, route.Dst)
|
||||
return fmt.Errorf("Network %s overlaps with existing route %s on host.", subnet, route.Dst)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Two networks overlap if the start-point of one is inside the other.
|
||||
func overlaps(n1, n2 *net.IPNet) bool {
|
||||
return n1.Contains(n2.IP) || n2.Contains(n1.IP)
|
||||
}
|
||||
|
||||
// For a specific address, we only care if it is actually *inside* an
|
||||
// existing route, because weave-local traffic never hits IP routing.
|
||||
func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) error {
|
||||
return forEachRoute(ignoreIfaceNames, func(name string, 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 nil
|
||||
})
|
||||
}
|
||||
|
||||
func forEachRoute(ignoreIfaceNames map[string]struct{}, check func(name string, r netlink.Route) error) error {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, route := range routes {
|
||||
link, err := netlink.LinkByIndex(route.LinkIndex)
|
||||
if err == nil {
|
||||
if _, found := ignoreIfaceNames[link.Attrs().Name]; found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := check(link.Attrs().Name, route); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckRouteExists(ifaceName string, dest net.IP) bool {
|
||||
found := false
|
||||
forEachRoute(map[string]struct{}{}, func(name string, route netlink.Route) error {
|
||||
if name == ifaceName && route.Dst.IP.Equal(dest) {
|
||||
found = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func matchRoute(ifaceName string, dest net.IP) func(m syscall.NetlinkMessage) (bool, error) {
|
||||
return func(m syscall.NetlinkMessage) (bool, error) {
|
||||
switch m.Header.Type {
|
||||
case syscall.RTM_NEWROUTE:
|
||||
route, err := deserializeRoute(m.Data)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
link, _ := netlink.LinkByIndex(route.LinkIndex)
|
||||
if link.Attrs().Name == ifaceName && route.Dst.IP.Equal(dest) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// This code proposed for addition to the vishvananda/netlink library
|
||||
// deserializeRoute decodes a binary netlink message into a Route struct
|
||||
func deserializeRoute(m []byte) (netlink.Route, error) {
|
||||
route := netlink.Route{}
|
||||
msg := nl.DeserializeRtMsg(m)
|
||||
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
|
||||
if err != nil {
|
||||
return route, err
|
||||
}
|
||||
route.Scope = netlink.Scope(msg.Scope)
|
||||
|
||||
native := nl.NativeEndian()
|
||||
for _, attr := range attrs {
|
||||
switch attr.Attr.Type {
|
||||
case syscall.RTA_GATEWAY:
|
||||
route.Gw = net.IP(attr.Value)
|
||||
case syscall.RTA_PREFSRC:
|
||||
route.Src = net.IP(attr.Value)
|
||||
case syscall.RTA_DST:
|
||||
route.Dst = &net.IPNet{
|
||||
IP: attr.Value,
|
||||
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
|
||||
}
|
||||
case syscall.RTA_OIF:
|
||||
routeIndex := int(native.Uint32(attr.Value[0:4]))
|
||||
route.LinkIndex = routeIndex
|
||||
}
|
||||
}
|
||||
return route, nil
|
||||
}
|
||||
+10
-10
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOverlap(t *testing.T) {
|
||||
@@ -13,13 +13,13 @@ func TestOverlap(t *testing.T) {
|
||||
_, subnet3, _ := net.ParseCIDR("10.0.3.128/25")
|
||||
_, subnet4, _ := net.ParseCIDR("10.0.3.192/25")
|
||||
_, universe, _ := net.ParseCIDR("10.0.0.0/8")
|
||||
wt.AssertEquals(t, overlaps(subnet1, subnet2), false)
|
||||
wt.AssertEquals(t, overlaps(subnet2, subnet1), false)
|
||||
wt.AssertEquals(t, overlaps(subnet1, subnet1), true)
|
||||
wt.AssertEquals(t, overlaps(subnet1, subnet3), true)
|
||||
wt.AssertEquals(t, overlaps(subnet1, subnet4), true)
|
||||
wt.AssertEquals(t, overlaps(subnet2, subnet4), false)
|
||||
wt.AssertEquals(t, overlaps(subnet4, subnet2), false)
|
||||
wt.AssertEquals(t, overlaps(universe, subnet1), true)
|
||||
wt.AssertEquals(t, overlaps(subnet1, universe), true)
|
||||
require.False(t, overlaps(subnet1, subnet2))
|
||||
require.False(t, overlaps(subnet2, subnet1))
|
||||
require.True(t, overlaps(subnet1, subnet1))
|
||||
require.True(t, overlaps(subnet1, subnet3))
|
||||
require.True(t, overlaps(subnet1, subnet4))
|
||||
require.False(t, overlaps(subnet2, subnet4))
|
||||
require.False(t, overlaps(subnet4, subnet2))
|
||||
require.True(t, overlaps(universe, subnet1))
|
||||
require.True(t, overlaps(subnet1, universe))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* netcheck: check whether a given network overlaps with any existing routes */
|
||||
/* netcheck: check whether a given network or address overlaps with any existing routes */
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -6,25 +6,48 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Println(err)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
type stringList map[string]struct{}
|
||||
|
||||
func (ss *stringList) Set(value string) error {
|
||||
(*ss)[value] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *stringList) String() string {
|
||||
return fmt.Sprintf("%v", *ss)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) <= 1 {
|
||||
os.Exit(0)
|
||||
ignoreIfaceNames := make(stringList)
|
||||
|
||||
mflag.Var(&ignoreIfaceNames, []string{"-ignore-iface"}, "name of interface to ignore)")
|
||||
mflag.Parse()
|
||||
|
||||
if len(mflag.Args()) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: netcheck [--ignore-iface <iface-name>] network-cidr")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ipRangeStr := os.Args[1]
|
||||
_, ipnet, err := net.ParseCIDR(ipRangeStr)
|
||||
cidrStr := mflag.Args()[0]
|
||||
addr, ipnet, err := net.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
if err := weavenet.CheckNetworkFree(ipnet); err != nil {
|
||||
if ipnet.IP.Equal(addr) {
|
||||
err = weavenet.CheckNetworkFree(ipnet, ignoreIfaceNames)
|
||||
} else {
|
||||
err = weavenet.CheckAddressOverlap(addr, ignoreIfaceNames)
|
||||
}
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM scratch
|
||||
MAINTAINER Weaveworks Inc <help@weave.works>
|
||||
WORKDIR /home/weave
|
||||
ADD ./weavedns /home/weave/
|
||||
EXPOSE 53/udp
|
||||
ENTRYPOINT ["/home/weave/weavedns"]
|
||||
@@ -1,186 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
weavedns "github.com/weaveworks/weave/nameserver"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
|
||||
var version = "(unreleased version)"
|
||||
|
||||
func main() {
|
||||
var (
|
||||
justVersion bool
|
||||
dockerCli *docker.Client
|
||||
ifaceName string
|
||||
apiPath string
|
||||
domain string
|
||||
dnsPort int
|
||||
httpIfaceName string
|
||||
httpPort int
|
||||
wait int
|
||||
ttl int
|
||||
negTTL int
|
||||
timeout int
|
||||
udpbuf int
|
||||
fallback string
|
||||
refreshInterval int
|
||||
relevantTime int
|
||||
maxAnswers int
|
||||
cacheLen int
|
||||
cacheDisabled bool
|
||||
watch bool
|
||||
debug bool
|
||||
err error
|
||||
)
|
||||
|
||||
flag.BoolVar(&justVersion, "version", false, "print version and exit")
|
||||
flag.StringVar(&ifaceName, "iface", "", "name of interface to use for multicast")
|
||||
flag.StringVar(&apiPath, "api", "unix:///var/run/docker.sock", "path to Docker API socket")
|
||||
flag.StringVar(&domain, "domain", weavedns.DefaultLocalDomain, "local domain (ie, 'weave.local.')")
|
||||
flag.IntVar(&wait, "wait", -1, "number of seconds to wait for interfaces to come up (0=don't wait, -1=wait forever)")
|
||||
flag.IntVar(&dnsPort, "dnsport", weavedns.DefaultServerPort, "port to listen to DNS requests")
|
||||
flag.StringVar(&httpIfaceName, "httpiface", "", "interface on which to listen for HTTP requests (defaults to empty string which listens on all interfaces)")
|
||||
flag.IntVar(&httpPort, "httpport", weavedns.DefaultHTTPPort, "port to listen to HTTP requests")
|
||||
flag.IntVar(&cacheLen, "cache", weavedns.DefaultCacheLen, "cache length")
|
||||
flag.IntVar(&ttl, "ttl", weavedns.DefaultLocalTTL, "TTL (in secs) for responses for local names")
|
||||
flag.BoolVar(&watch, "watch", true, "watch the docker socket for container events")
|
||||
flag.BoolVar(&debug, "debug", false, "output debugging info to stderr")
|
||||
// advanced options
|
||||
flag.IntVar(&negTTL, "neg-ttl", 0, "negative TTL (in secs) for unanswered queries for local names (0=same value as -ttl")
|
||||
flag.IntVar(&refreshInterval, "refresh", weavedns.DefaultRefreshInterval, "refresh interval (in secs) for local names (0=disable)")
|
||||
flag.IntVar(&maxAnswers, "max-answers", weavedns.DefaultMaxAnswers, "maximum number of answers returned to clients (0=unlimited)")
|
||||
flag.IntVar(&relevantTime, "relevant", weavedns.DefaultRelevantTime, "life time for info in the absence of queries (in secs)")
|
||||
flag.IntVar(&udpbuf, "udpbuf", weavedns.DefaultUDPBuflen, "UDP buffer length")
|
||||
flag.IntVar(&timeout, "timeout", weavedns.DefaultTimeout, "timeout for resolutions (in millisecs)")
|
||||
flag.BoolVar(&cacheDisabled, "no-cache", false, "disable the cache")
|
||||
flag.StringVar(&fallback, "fallback", "", "force a fallback server (ie, '8.8.8.8:53') (instead of /etc/resolv.conf values)")
|
||||
flag.Parse()
|
||||
|
||||
if justVersion {
|
||||
fmt.Printf("weave DNS %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
InitDefaultLogging(debug)
|
||||
Info.Printf("[main] WeaveDNS version %s\n", version) // first thing in log: the version
|
||||
|
||||
var iface *net.Interface
|
||||
if ifaceName != "" {
|
||||
var err error
|
||||
Info.Println("[main] Waiting for mDNS interface", ifaceName, "to come up")
|
||||
iface, err = weavenet.EnsureInterface(ifaceName, wait)
|
||||
if err != nil {
|
||||
Error.Fatal(err)
|
||||
} else {
|
||||
Info.Println("[main] Interface", ifaceName, "is up")
|
||||
}
|
||||
}
|
||||
|
||||
var httpIP string
|
||||
if httpIfaceName == "" {
|
||||
httpIP = "0.0.0.0"
|
||||
} else {
|
||||
Info.Println("[main] Waiting for HTTP interface", httpIfaceName, "to come up")
|
||||
httpIface, err := weavenet.EnsureInterface(httpIfaceName, wait)
|
||||
if err != nil {
|
||||
Error.Fatal(err)
|
||||
}
|
||||
Info.Println("[main] Interface", httpIfaceName, "is up")
|
||||
|
||||
addrs, err := httpIface.Addrs()
|
||||
if err != nil {
|
||||
Error.Fatal(err)
|
||||
}
|
||||
|
||||
if len(addrs) == 0 {
|
||||
Error.Fatal("[main] No addresses on HTTP interface")
|
||||
}
|
||||
|
||||
ip, _, err := net.ParseCIDR(addrs[0].String())
|
||||
if err != nil {
|
||||
Error.Fatal(err)
|
||||
}
|
||||
|
||||
httpIP = ip.String()
|
||||
|
||||
}
|
||||
httpAddr := net.JoinHostPort(httpIP, strconv.Itoa(httpPort))
|
||||
|
||||
zoneConfig := weavedns.ZoneConfig{
|
||||
Domain: domain,
|
||||
Iface: iface,
|
||||
LocalTTL: ttl,
|
||||
RefreshInterval: refreshInterval,
|
||||
RelevantTime: relevantTime,
|
||||
}
|
||||
zone, err := weavedns.NewZoneDb(zoneConfig)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Unable to initialize the Zone database", err)
|
||||
}
|
||||
if err := zone.Start(); err != nil {
|
||||
Error.Fatal("[main] Unable to start the Zone database", err)
|
||||
}
|
||||
defer zone.Stop()
|
||||
|
||||
dockerCli, err = docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Unable to start docker client: ", err)
|
||||
}
|
||||
|
||||
if watch {
|
||||
err := dockerCli.AddObserver(zone)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Unable to start watcher", err)
|
||||
}
|
||||
}
|
||||
|
||||
srvConfig := weavedns.DNSServerConfig{
|
||||
Zone: zone,
|
||||
Port: dnsPort,
|
||||
CacheLen: cacheLen,
|
||||
LocalTTL: ttl,
|
||||
CacheNegLocalTTL: negTTL,
|
||||
MaxAnswers: maxAnswers,
|
||||
Timeout: timeout,
|
||||
UDPBufLen: udpbuf,
|
||||
CacheDisabled: cacheDisabled,
|
||||
}
|
||||
|
||||
if len(fallback) > 0 {
|
||||
fallbackHost, fallbackPort, err := net.SplitHostPort(fallback)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Could not parse fallback host and port", err)
|
||||
}
|
||||
srvConfig.UpstreamCfg = &dns.ClientConfig{Servers: []string{fallbackHost}, Port: fallbackPort}
|
||||
Debug.Printf("[main] DNS fallback at %s:%s", fallbackHost, fallbackPort)
|
||||
}
|
||||
|
||||
srv, err := weavedns.NewDNSServer(srvConfig)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Failed to initialize the WeaveDNS server", err)
|
||||
}
|
||||
|
||||
httpListener, err := net.Listen("tcp", httpAddr)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Unable to create http listener: ", err)
|
||||
}
|
||||
Info.Println("[main] HTTP API listening on", httpAddr)
|
||||
|
||||
go SignalHandlerLoop(srv)
|
||||
go weavedns.ServeHTTP(httpListener, version, srv, dockerCli)
|
||||
|
||||
err = srv.Start()
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Failed to start the WeaveDNS server: ", err)
|
||||
}
|
||||
srv.ActivateAndServe()
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
FROM gliderlabs/alpine
|
||||
|
||||
MAINTAINER Weaveworks Inc <help@weave.works>
|
||||
LABEL works.weave.role=system
|
||||
|
||||
WORKDIR /home/weave
|
||||
VOLUME /w
|
||||
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
|
||||
|
||||
RUN apk add --update \
|
||||
curl \
|
||||
@@ -9,17 +14,10 @@ RUN apk add --update \
|
||||
iproute2 \
|
||||
util-linux \
|
||||
conntrack-tools \
|
||||
bind-tools \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
WORKDIR /home/weave
|
||||
|
||||
ADD ./weave /home/weave/
|
||||
ADD ./sigproxy /home/weave/
|
||||
ADD ./weaveproxy /home/weave/
|
||||
ADD ./netcheck /usr/bin/
|
||||
ADD ./weave ./sigproxy ./weaveproxy /home/weave/
|
||||
ADD ./netcheck ./weavehosts /usr/bin/
|
||||
ADD ./weavewait /w/w
|
||||
ADD ./docker.tgz /
|
||||
|
||||
VOLUME /w
|
||||
|
||||
ENTRYPOINT ["/home/weave/sigproxy", "/home/weave/weave"]
|
||||
@@ -0,0 +1,120 @@
|
||||
/* weavehosts: rewrite /etc/hosts as necessary to put the weave address(es) at the top */
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := os.Args
|
||||
if len(args) < 4 {
|
||||
fmt.Fprintln(os.Stderr, "usage: weavehosts hostpath hostname cidr [cidr ...]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
checkErr(updateHosts(args[1], args[2], args[3:]))
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func updateHosts(path, hostname string, cidrs []string) error {
|
||||
hosts, err := parseHosts(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove existing ips pointing to our hostname
|
||||
toRemove := []string{}
|
||||
for ip, addrs := range hosts {
|
||||
for _, addr := range addrs {
|
||||
if addr == hostname {
|
||||
toRemove = append(toRemove, ip)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ip := range toRemove {
|
||||
delete(hosts, ip)
|
||||
}
|
||||
|
||||
// Add the weave ip(s)
|
||||
for _, cidr := range cidrs {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
checkErr(err)
|
||||
ipStr := ip.String()
|
||||
hosts[ipStr] = append(hosts[ipStr], hostname)
|
||||
}
|
||||
|
||||
return writeHosts(path, hosts)
|
||||
}
|
||||
|
||||
func parseHosts(path string) (map[string][]string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
ips := map[string][]string{}
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// Remove any comments
|
||||
if i := strings.IndexByte(line, '#'); i != -1 {
|
||||
line = line[:i]
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 0 {
|
||||
ips[fields[0]] = append(ips[fields[0]], fields[1:]...)
|
||||
}
|
||||
}
|
||||
if scanner.Err() != nil {
|
||||
return nil, scanner.Err()
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func writeHosts(path string, contents map[string][]string) error {
|
||||
ips := []string{}
|
||||
for ip := range contents {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
sort.Strings(ips)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
fmt.Fprintln(buf, "# modified by weave")
|
||||
for _, ip := range ips {
|
||||
if addrs := contents[ip]; len(addrs) > 0 {
|
||||
fmt.Fprintf(buf, "%s\t%s\n", ip, strings.Join(uniqueStrs(addrs), " "))
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(path, buf.Bytes(), 644)
|
||||
}
|
||||
|
||||
func uniqueStrs(s []string) []string {
|
||||
m := map[string]struct{}{}
|
||||
result := []string{}
|
||||
for _, str := range s {
|
||||
if _, ok := m[str]; !ok {
|
||||
m[str] = struct{}{}
|
||||
result = append(result, str)
|
||||
}
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
+30
-42
@@ -5,41 +5,42 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pborman/getopt"
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/mflagext"
|
||||
"github.com/weaveworks/weave/proxy"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "(unreleased version)"
|
||||
defaultDockerAddr = "unix:///var/run/docker.sock"
|
||||
defaultListenAddr = "tcp://0.0.0.0:12375"
|
||||
version = "(unreleased version)"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
debug bool
|
||||
justVersion bool
|
||||
c = proxy.Config{
|
||||
DockerAddr: defaultDockerAddr,
|
||||
ListenAddr: defaultListenAddr,
|
||||
}
|
||||
logLevel = "info"
|
||||
c = proxy.Config{ListenAddrs: []string{}}
|
||||
)
|
||||
|
||||
c.Version = version
|
||||
getopt.BoolVarLong(&debug, "debug", 'd', "log debugging information")
|
||||
getopt.BoolVarLong(&justVersion, "version", 0, "print version and exit")
|
||||
getopt.StringVar(&c.ListenAddr, 'H', fmt.Sprintf("address on which to listen (default %s)", defaultListenAddr))
|
||||
getopt.StringVar(&c.DockerAddr, 'D', fmt.Sprintf("docker daemon URL to proxy (default %s)", defaultDockerAddr))
|
||||
getopt.BoolVarLong(&c.NoDefaultIPAM, "no-default-ipam", 0, "do not automatically allocate addresses for containers without a WEAVE_CIDR")
|
||||
getopt.StringVarLong(&c.TLSConfig.CACert, "tlscacert", 0, "Trust certs signed only by this CA")
|
||||
getopt.StringVarLong(&c.TLSConfig.Cert, "tlscert", 0, "Path to TLS certificate file")
|
||||
getopt.BoolVarLong(&c.TLSConfig.Enabled, "tls", 0, "Use TLS; implied by --tlsverify")
|
||||
getopt.StringVarLong(&c.TLSConfig.Key, "tlskey", 0, "Path to TLS key file")
|
||||
getopt.BoolVarLong(&c.TLSConfig.Verify, "tlsverify", 0, "Use TLS and verify the remote")
|
||||
getopt.BoolVarLong(&c.WithDNS, "with-dns", 'w', "instruct created containers to always use weaveDNS as their nameserver")
|
||||
getopt.BoolVarLong(&c.WithoutDNS, "without-dns", 0, "instruct created containers to never use weaveDNS as their nameserver")
|
||||
getopt.Parse()
|
||||
|
||||
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
|
||||
mflag.StringVar(&logLevel, []string{"-log-level"}, "info", "logging level (debug, info, warning, error)")
|
||||
mflagext.ListVar(&c.ListenAddrs, []string{"H"}, nil, "addresses on which to listen")
|
||||
mflag.StringVar(&c.HostnameFromLabel, []string{"-hostname-from-label"}, "", "Key of container label from which to obtain the container's hostname")
|
||||
mflag.StringVar(&c.HostnameMatch, []string{"-hostname-match"}, "(.*)", "Regexp pattern to apply on container names (e.g. '^aws-[0-9]+-(.*)$')")
|
||||
mflag.StringVar(&c.HostnameReplacement, []string{"-hostname-replacement"}, "$1", "Expression to generate hostnames based on matches from --hostname-match (e.g. 'my-app-$1')")
|
||||
mflag.BoolVar(&c.RewriteInspect, []string{"-rewrite-inspect"}, false, "Rewrite 'inspect' calls to return the weave network settings (if attached)")
|
||||
mflag.BoolVar(&c.NoDefaultIPAM, []string{"#-no-default-ipam", "-no-default-ipalloc"}, false, "do not automatically allocate addresses for containers without a WEAVE_CIDR")
|
||||
mflag.BoolVar(&c.NoRewriteHosts, []string{"-no-rewrite-hosts"}, false, "do not automatically rewrite /etc/hosts. Use if you need the docker IP to remain in /etc/hosts")
|
||||
mflag.StringVar(&c.TLSConfig.CACert, []string{"#tlscacert", "-tlscacert"}, "", "Trust certs signed only by this CA")
|
||||
mflag.StringVar(&c.TLSConfig.Cert, []string{"#tlscert", "-tlscert"}, "", "Path to TLS certificate file")
|
||||
mflag.BoolVar(&c.TLSConfig.Enabled, []string{"#tls", "-tls"}, false, "Use TLS; implied by --tls-verify")
|
||||
mflag.StringVar(&c.TLSConfig.Key, []string{"#tlskey", "-tlskey"}, "", "Path to TLS key file")
|
||||
mflag.BoolVar(&c.TLSConfig.Verify, []string{"#tlsverify", "-tlsverify"}, false, "Use TLS and verify the remote")
|
||||
mflag.BoolVar(&c.WithDNS, []string{"-with-dns", "w"}, false, "instruct created containers to always use weaveDNS as their nameserver")
|
||||
mflag.BoolVar(&c.WithoutDNS, []string{"-without-dns"}, false, "instruct created containers to never use weaveDNS as their nameserver")
|
||||
mflag.Parse()
|
||||
|
||||
if justVersion {
|
||||
fmt.Printf("weave proxy %s\n", version)
|
||||
@@ -47,32 +48,19 @@ func main() {
|
||||
}
|
||||
|
||||
if c.WithDNS && c.WithoutDNS {
|
||||
Error.Fatalf("Cannot use both '--with-dns' and '--without-dns' flags")
|
||||
Log.Fatalf("Cannot use both '--with-dns' and '--without-dns' flags")
|
||||
}
|
||||
|
||||
if debug {
|
||||
InitDefaultLogging(true)
|
||||
}
|
||||
SetLogLevel(logLevel)
|
||||
|
||||
Info.Println("weave proxy", version)
|
||||
Info.Println("Command line arguments:", strings.Join(os.Args[1:], " "))
|
||||
|
||||
protoAddrParts := strings.SplitN(c.ListenAddr, "://", 2)
|
||||
if len(protoAddrParts) == 2 {
|
||||
if protoAddrParts[0] != "tcp" {
|
||||
Error.Fatalf("Invalid protocol format: %q", protoAddrParts[0])
|
||||
}
|
||||
c.ListenAddr = protoAddrParts[1]
|
||||
} else {
|
||||
c.ListenAddr = protoAddrParts[0]
|
||||
}
|
||||
Log.Infoln("weave proxy", version)
|
||||
Log.Infoln("Command line arguments:", strings.Join(os.Args[1:], " "))
|
||||
|
||||
p, err := proxy.NewProxy(c)
|
||||
if err != nil {
|
||||
Error.Fatalf("Could not start proxy: %s", err)
|
||||
Log.Fatalf("Could not start proxy: %s", err)
|
||||
}
|
||||
|
||||
if err := p.ListenAndServe(); err != nil {
|
||||
Error.Fatalf("Could not listen on %s: %s", p.ListenAddr, err)
|
||||
}
|
||||
go p.ListenAndServe()
|
||||
SignalHandlerLoop()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
weavetest "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
weavetest.TrimTestArgs()
|
||||
main()
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
FROM scratch
|
||||
MAINTAINER Weaveworks Inc <help@weave.works>
|
||||
LABEL works.weave.role=system
|
||||
WORKDIR /home/weave
|
||||
ADD ./weaver /home/weave/
|
||||
ENTRYPOINT ["/home/weave/weaver"]
|
||||
@@ -0,0 +1,198 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/ipam"
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
weave "github.com/weaveworks/weave/router"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var rootTemplate = template.New("root").Funcs(map[string]interface{}{
|
||||
"countDNSEntries": func(entries []nameserver.EntryStatus) int {
|
||||
count := 0
|
||||
for _, entry := range entries {
|
||||
if entry.Tombstone == 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
},
|
||||
"printConnectionCounts": func(conns []weave.LocalConnectionStatus) string {
|
||||
counts := make(map[string]int)
|
||||
for _, conn := range conns {
|
||||
counts[conn.State]++
|
||||
}
|
||||
return printCounts(counts, []string{"established", "pending", "retrying", "failed", "connecting"})
|
||||
},
|
||||
"printPeerConnectionCounts": func(peers []weave.PeerStatus) string {
|
||||
counts := make(map[string]int)
|
||||
for _, peer := range peers {
|
||||
for _, conn := range peer.Connections {
|
||||
if conn.Established {
|
||||
counts["established"]++
|
||||
} else {
|
||||
counts["pending"]++
|
||||
}
|
||||
}
|
||||
}
|
||||
return printCounts(counts, []string{"established", "pending"})
|
||||
},
|
||||
"printState": func(enabled bool) string {
|
||||
if enabled {
|
||||
return "enabled"
|
||||
}
|
||||
return "disabled"
|
||||
},
|
||||
"trimSuffix": strings.TrimSuffix,
|
||||
})
|
||||
|
||||
// Print counts in a specified order
|
||||
func printCounts(counts map[string]int, keys []string) string {
|
||||
var stringCounts []string
|
||||
for _, key := range keys {
|
||||
if count, ok := counts[key]; ok {
|
||||
stringCounts = append(stringCounts, fmt.Sprintf("%d %s", count, key))
|
||||
}
|
||||
}
|
||||
return strings.Join(stringCounts, ", ")
|
||||
}
|
||||
|
||||
// Strip escaped newlines from template
|
||||
func escape(template string) string {
|
||||
return strings.Replace(template, "\\\n", "", -1)
|
||||
}
|
||||
|
||||
// Define a named template panicking on error
|
||||
func defTemplate(name string, text string) *template.Template {
|
||||
return template.Must(rootTemplate.New(name).Parse(escape(text)))
|
||||
}
|
||||
|
||||
var statusTemplate = defTemplate("status", `\
|
||||
Version: {{.Version}}
|
||||
|
||||
Service: router
|
||||
Protocol: {{.Router.Protocol}} \
|
||||
{{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\
|
||||
{{.Router.ProtocolMaxVersion}}\
|
||||
{{else}}\
|
||||
{{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\
|
||||
{{end}}
|
||||
Name: {{.Router.Name}}({{.Router.NickName}})
|
||||
Encryption: {{printState .Router.Encryption}}
|
||||
PeerDiscovery: {{printState .Router.PeerDiscovery}}
|
||||
Targets: {{len .Router.Targets}}
|
||||
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
|
||||
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections between them){{end}}
|
||||
{{if .IPAM}}\
|
||||
|
||||
Service: ipam
|
||||
{{if .IPAM.Entries}}\
|
||||
Consensus: achieved
|
||||
{{else if .IPAM.Paxos}}\
|
||||
Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
|
||||
{{else}}\
|
||||
Consensus: deferred
|
||||
{{end}}\
|
||||
Range: {{.IPAM.Range}}
|
||||
DefaultSubnet: {{.IPAM.DefaultSubnet}}
|
||||
{{end}}\
|
||||
{{if .DNS}}\
|
||||
|
||||
Service: dns
|
||||
Domain: {{.DNS.Domain}}
|
||||
TTL: {{.DNS.TTL}}
|
||||
Entries: {{countDNSEntries .DNS.Entries}}
|
||||
{{end}}\
|
||||
`)
|
||||
|
||||
var targetsTemplate = defTemplate("targetsTemplate", `\
|
||||
{{range .Router.Targets}}{{.}}
|
||||
{{end}}\
|
||||
`)
|
||||
|
||||
var connectionsTemplate = defTemplate("connectionsTemplate", `\
|
||||
{{range .Router.Connections}}\
|
||||
{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} {{printf "%-11v" .State}} {{.Info}}
|
||||
{{end}}\
|
||||
`)
|
||||
|
||||
var peersTemplate = defTemplate("peers", `\
|
||||
{{range .Router.Peers}}\
|
||||
{{.Name}}({{.NickName}})
|
||||
{{range .Connections}}\
|
||||
{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} \
|
||||
{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-32v" $nameNickName}} \
|
||||
{{if .Established}}established{{else}}pending{{end}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
`)
|
||||
|
||||
var dnsEntriesTemplate = defTemplate("dnsEntries", `\
|
||||
{{$domain := printf ".%v" .DNS.Domain}}\
|
||||
{{range .DNS.Entries}}\
|
||||
{{if eq .Tombstone 0}}\
|
||||
{{$hostname := trimSuffix .Hostname $domain}}\
|
||||
{{printf "%-12v" $hostname}} {{printf "%-15v" .Address}} {{printf "%12.12v" .ContainerID}} {{.Origin}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
`)
|
||||
|
||||
type WeaveStatus struct {
|
||||
Version string
|
||||
Router *weave.Status `json:"Router,omitempty"`
|
||||
IPAM *ipam.Status `json:"IPAM,omitempty"`
|
||||
DNS *nameserver.Status `json:"DNS,omitempty"`
|
||||
}
|
||||
|
||||
func HandleHTTP(muxRouter *mux.Router, version string, router *weave.Router, allocator *ipam.Allocator, defaultSubnet address.CIDR, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) {
|
||||
status := func() WeaveStatus {
|
||||
return WeaveStatus{
|
||||
version,
|
||||
weave.NewStatus(router),
|
||||
ipam.NewStatus(allocator, defaultSubnet),
|
||||
nameserver.NewStatus(ns, dnsserver)}
|
||||
}
|
||||
muxRouter.Methods("GET").Path("/report").Headers("Accept", "application/json").HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
json, _ := json.MarshalIndent(status(), "", " ")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(json)
|
||||
})
|
||||
|
||||
muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
formatTemplate, err := template.New("format").Parse(mux.Vars(r)["format"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := formatTemplate.Execute(w, status()); err != nil {
|
||||
http.Error(w, "error during template execution", http.StatusInternalServerError)
|
||||
Log.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
defHandler := func(path string, template *template.Template) {
|
||||
muxRouter.Methods("GET").Path(path).HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := template.Execute(w, status()); err != nil {
|
||||
http.Error(w, "error during template execution", http.StatusInternalServerError)
|
||||
Log.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defHandler("/status", statusTemplate)
|
||||
defHandler("/status/targets", targetsTemplate)
|
||||
defHandler("/status/connections", connectionsTemplate)
|
||||
defHandler("/status/peers", peersTemplate)
|
||||
defHandler("/status/dns", dnsEntriesTemplate)
|
||||
|
||||
}
|
||||
+155
-130
@@ -2,33 +2,31 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/davecheney/profile"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
"github.com/weaveworks/weave/ipam"
|
||||
"github.com/weaveworks/weave/ipam/address"
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
weave "github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
var version = "(unreleased version)"
|
||||
|
||||
func main() {
|
||||
|
||||
log.SetPrefix(weave.Protocol + " ")
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
|
||||
|
||||
procs := runtime.NumCPU()
|
||||
// packet sniffing can block an OS thread, so we need one thread
|
||||
// for that plus at least one more.
|
||||
@@ -38,80 +36,107 @@ func main() {
|
||||
runtime.GOMAXPROCS(procs)
|
||||
|
||||
var (
|
||||
config weave.Config
|
||||
justVersion bool
|
||||
ifaceName string
|
||||
routerName string
|
||||
nickName string
|
||||
password string
|
||||
wait int
|
||||
debug bool
|
||||
pktdebug bool
|
||||
prof string
|
||||
bufSzMB int
|
||||
noDiscovery bool
|
||||
httpAddr string
|
||||
iprangeCIDR string
|
||||
ipsubnetCIDR string
|
||||
peerCount int
|
||||
apiPath string
|
||||
peers []string
|
||||
config weave.Config
|
||||
justVersion bool
|
||||
protocolMinVersion int
|
||||
ifaceName string
|
||||
routerName string
|
||||
nickName string
|
||||
password string
|
||||
pktdebug bool
|
||||
logLevel string
|
||||
prof string
|
||||
bufSzMB int
|
||||
noDiscovery bool
|
||||
httpAddr string
|
||||
iprangeCIDR string
|
||||
ipsubnetCIDR string
|
||||
peerCount int
|
||||
apiPath string
|
||||
peers []string
|
||||
|
||||
noDNS bool
|
||||
dnsDomain string
|
||||
dnsListenAddress string
|
||||
dnsTTL int
|
||||
dnsClientTimeout time.Duration
|
||||
dnsEffectiveListenAddress string
|
||||
)
|
||||
|
||||
flag.BoolVar(&justVersion, "version", false, "print version and exit")
|
||||
flag.IntVar(&config.Port, "port", weave.Port, "router port")
|
||||
flag.StringVar(&ifaceName, "iface", "", "name of interface to capture/inject from (disabled if blank)")
|
||||
flag.StringVar(&routerName, "name", "", "name of router (defaults to MAC of interface)")
|
||||
flag.StringVar(&nickName, "nickname", "", "nickname of peer (defaults to hostname)")
|
||||
flag.StringVar(&password, "password", "", "network password")
|
||||
flag.IntVar(&wait, "wait", -1, "number of seconds to wait for interface to come up (0=don't wait, -1=wait forever)")
|
||||
flag.BoolVar(&debug, "debug", false, "enable debug logging")
|
||||
flag.BoolVar(&pktdebug, "pktdebug", false, "enable per-packet debug logging")
|
||||
flag.StringVar(&prof, "profile", "", "enable profiling and write profiles to given path")
|
||||
flag.IntVar(&config.ConnLimit, "connlimit", 30, "connection limit (0 for unlimited)")
|
||||
flag.BoolVar(&noDiscovery, "nodiscovery", false, "disable peer discovery")
|
||||
flag.IntVar(&bufSzMB, "bufsz", 8, "capture buffer size in MB")
|
||||
flag.StringVar(&httpAddr, "httpaddr", fmt.Sprintf(":%d", weave.HTTPPort), "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)")
|
||||
flag.StringVar(&iprangeCIDR, "iprange", "", "IP address range reserved for automatic allocation, in CIDR notation")
|
||||
flag.StringVar(&ipsubnetCIDR, "ipsubnet", "", "subnet to allocate within by default, in CIDR notation")
|
||||
flag.IntVar(&peerCount, "initpeercount", 0, "number of peers in network (for IP address allocation)")
|
||||
flag.StringVar(&apiPath, "api", "unix:///var/run/docker.sock", "Path to Docker API socket")
|
||||
flag.Parse()
|
||||
peers = flag.Args()
|
||||
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
|
||||
mflag.IntVar(&config.Port, []string{"#port", "-port"}, weave.Port, "router port")
|
||||
mflag.IntVar(&protocolMinVersion, []string{"-min-protocol-version"}, weave.ProtocolMinVersion, "minimum weave protocol version")
|
||||
mflag.StringVar(&ifaceName, []string{"#iface", "-iface"}, "", "name of interface to capture/inject from (disabled if blank)")
|
||||
mflag.StringVar(&routerName, []string{"#name", "-name"}, "", "name of router (defaults to MAC of interface)")
|
||||
mflag.StringVar(&nickName, []string{"#nickname", "-nickname"}, "", "nickname of peer (defaults to hostname)")
|
||||
mflag.StringVar(&password, []string{"#password", "-password"}, "", "network password")
|
||||
mflag.StringVar(&logLevel, []string{"-log-level"}, "info", "logging level (debug, info, warning, error)")
|
||||
mflag.BoolVar(&pktdebug, []string{"#pktdebug", "#-pktdebug", "-pkt-debug"}, false, "enable per-packet debug logging")
|
||||
mflag.StringVar(&prof, []string{"#profile", "-profile"}, "", "enable profiling and write profiles to given path")
|
||||
mflag.IntVar(&config.ConnLimit, []string{"#connlimit", "#-connlimit", "-conn-limit"}, 30, "connection limit (0 for unlimited)")
|
||||
mflag.BoolVar(&noDiscovery, []string{"#nodiscovery", "#-nodiscovery", "-no-discovery"}, false, "disable peer discovery")
|
||||
mflag.IntVar(&bufSzMB, []string{"#bufsz", "-bufsz"}, 8, "capture buffer size in MB")
|
||||
mflag.StringVar(&httpAddr, []string{"#httpaddr", "#-httpaddr", "-http-addr"}, fmt.Sprintf(":%d", weave.HTTPPort), "address to bind HTTP interface to (disabled if blank, absolute path indicates unix domain socket)")
|
||||
mflag.StringVar(&iprangeCIDR, []string{"#iprange", "#-iprange", "-ipalloc-range"}, "", "IP address range reserved for automatic allocation, in CIDR notation")
|
||||
mflag.StringVar(&ipsubnetCIDR, []string{"#ipsubnet", "#-ipsubnet", "-ipalloc-default-subnet"}, "", "subnet to allocate within by default, in CIDR notation")
|
||||
mflag.IntVar(&peerCount, []string{"#initpeercount", "#-initpeercount", "-init-peer-count"}, 0, "number of peers in network (for IP address allocation)")
|
||||
mflag.StringVar(&apiPath, []string{"#api", "-api"}, "unix:///var/run/docker.sock", "Path to Docker API socket")
|
||||
mflag.BoolVar(&noDNS, []string{"-no-dns"}, false, "disable DNS server")
|
||||
mflag.StringVar(&dnsDomain, []string{"-dns-domain"}, nameserver.DefaultDomain, "local domain to server requests for")
|
||||
mflag.StringVar(&dnsListenAddress, []string{"-dns-listen-address"}, nameserver.DefaultListenAddress, "address to listen on for DNS requests")
|
||||
mflag.IntVar(&dnsTTL, []string{"-dns-ttl"}, nameserver.DefaultTTL, "TTL for DNS request from our domain")
|
||||
mflag.DurationVar(&dnsClientTimeout, []string{"-dns-fallback-timeout"}, nameserver.DefaultClientTimeout, "timeout for fallback DNS requests")
|
||||
mflag.StringVar(&dnsEffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping")
|
||||
|
||||
InitDefaultLogging(debug)
|
||||
// crude way of detecting that we probably have been started in a
|
||||
// container, with `weave launch` --> suppress misleading paths in
|
||||
// mflags error messages.
|
||||
if os.Args[0] == "/home/weave/weaver" { // matches the Dockerfile ENTRYPOINT
|
||||
os.Args[0] = "weave"
|
||||
mflag.CommandLine.Init("weave", mflag.ExitOnError)
|
||||
}
|
||||
mflag.Parse()
|
||||
|
||||
peers = mflag.Args()
|
||||
|
||||
SetLogLevel(logLevel)
|
||||
if justVersion {
|
||||
fmt.Printf("weave router %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
log.Println("Command line options:", options())
|
||||
log.Println("Command line peers:", peers)
|
||||
Log.Println("Command line options:", options())
|
||||
Log.Println("Command line peers:", peers)
|
||||
|
||||
if protocolMinVersion < weave.ProtocolMinVersion || protocolMinVersion > weave.ProtocolMaxVersion {
|
||||
Log.Fatalf("--min-protocol-version must be in range [%d,%d]", weave.ProtocolMinVersion, weave.ProtocolMaxVersion)
|
||||
}
|
||||
config.ProtocolMinVersion = byte(protocolMinVersion)
|
||||
|
||||
var err error
|
||||
|
||||
if ifaceName != "" {
|
||||
config.Iface, err = weavenet.EnsureInterface(ifaceName, wait)
|
||||
config.Iface, err = weavenet.EnsureInterface(ifaceName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
Log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if routerName == "" {
|
||||
if config.Iface == nil {
|
||||
log.Fatal("Either an interface must be specified with -iface or a name with -name")
|
||||
Log.Fatal("Either an interface must be specified with --iface or a name with -name")
|
||||
}
|
||||
routerName = config.Iface.HardwareAddr.String()
|
||||
}
|
||||
name, err := weave.PeerNameFromUserInput(routerName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
Log.Fatal(err)
|
||||
}
|
||||
|
||||
if nickName == "" {
|
||||
nickName, err = os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
Log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,10 +144,10 @@ func main() {
|
||||
password = os.Getenv("WEAVE_PASSWORD")
|
||||
}
|
||||
if password == "" {
|
||||
log.Println("Communication between peers is unencrypted.")
|
||||
Log.Println("Communication between peers is unencrypted.")
|
||||
} else {
|
||||
config.Password = []byte(password)
|
||||
log.Println("Communication between peers is encrypted.")
|
||||
Log.Println("Communication between peers is encrypted.")
|
||||
}
|
||||
|
||||
if prof != "" {
|
||||
@@ -137,80 +162,119 @@ func main() {
|
||||
config.PeerDiscovery = !noDiscovery
|
||||
|
||||
router := weave.NewRouter(config, name, nickName)
|
||||
log.Println("Our name is", router.Ourself)
|
||||
Log.Println("Our name is", router.Ourself)
|
||||
|
||||
dockerCli, err := docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
Log.Fatal("Unable to start docker client: ", err)
|
||||
}
|
||||
|
||||
var allocator *ipam.Allocator
|
||||
var defaultSubnet address.CIDR
|
||||
var dockerCli *docker.Client
|
||||
if iprangeCIDR != "" {
|
||||
allocator, defaultSubnet = createAllocator(router, iprangeCIDR, ipsubnetCIDR, determineQuorum(peerCount, peers))
|
||||
dockerCli, err = docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
Error.Fatal("Unable to start docker client: ", err)
|
||||
}
|
||||
if err = dockerCli.AddObserver(allocator); err != nil {
|
||||
Error.Fatal("Unable to start watcher", err)
|
||||
Log.Fatal("Unable to start watcher", err)
|
||||
}
|
||||
} else if peerCount > 0 {
|
||||
log.Fatal("-initpeercount flag specified without -iprange")
|
||||
Log.Fatal("--init-peer-count flag specified without --ipalloc-range")
|
||||
}
|
||||
|
||||
var (
|
||||
ns *nameserver.Nameserver
|
||||
dnsserver *nameserver.DNSServer
|
||||
)
|
||||
if !noDNS {
|
||||
ns = nameserver.New(router.Ourself.Peer.Name, router.Peers, dockerCli, dnsDomain)
|
||||
ns.SetGossip(router.NewGossip("nameserver", ns))
|
||||
if err = dockerCli.AddObserver(ns); err != nil {
|
||||
Log.Fatal("Unable to start watcher", err)
|
||||
}
|
||||
ns.Start()
|
||||
defer ns.Stop()
|
||||
dnsserver, err = nameserver.NewDNSServer(ns, dnsDomain, dnsListenAddress,
|
||||
dnsEffectiveListenAddress, uint32(dnsTTL), dnsClientTimeout)
|
||||
if err != nil {
|
||||
Log.Fatal("Unable to start dns server: ", err)
|
||||
}
|
||||
dnsserver.ActivateAndServe()
|
||||
defer dnsserver.Stop()
|
||||
}
|
||||
|
||||
router.Start()
|
||||
if errors := router.ConnectionMaker.InitiateConnections(peers, false); len(errors) > 0 {
|
||||
log.Fatal(errorMessages(errors))
|
||||
Log.Fatal(ErrorMessages(errors))
|
||||
}
|
||||
|
||||
// The weave script always waits for a status call to succeed,
|
||||
// so there is no point in doing "weave launch -httpaddr ''".
|
||||
// so there is no point in doing "weave launch --http-addr ''".
|
||||
// This is here to support stand-alone use of weaver.
|
||||
if httpAddr != "" {
|
||||
go handleHTTP(router, httpAddr, allocator, defaultSubnet, dockerCli)
|
||||
muxRouter := mux.NewRouter()
|
||||
if allocator != nil {
|
||||
allocator.HandleHTTP(muxRouter, defaultSubnet, dockerCli)
|
||||
}
|
||||
if ns != nil {
|
||||
ns.HandleHTTP(muxRouter)
|
||||
}
|
||||
router.HandleHTTP(muxRouter)
|
||||
HandleHTTP(muxRouter, version, router, allocator, defaultSubnet, ns, dnsserver)
|
||||
http.Handle("/", muxRouter)
|
||||
go listenAndServeHTTP(httpAddr, muxRouter)
|
||||
}
|
||||
|
||||
SignalHandlerLoop(router)
|
||||
}
|
||||
|
||||
func errorMessages(errors []error) string {
|
||||
var result []string
|
||||
for _, err := range errors {
|
||||
result = append(result, err.Error())
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
func options() map[string]string {
|
||||
options := make(map[string]string)
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
mflag.Visit(func(f *mflag.Flag) {
|
||||
value := f.Value.String()
|
||||
if f.Name == "password" {
|
||||
name := canonicalName(f)
|
||||
if name == "password" {
|
||||
value = "<elided>"
|
||||
}
|
||||
options[f.Name] = value
|
||||
options[name] = value
|
||||
})
|
||||
return options
|
||||
}
|
||||
|
||||
func canonicalName(f *mflag.Flag) string {
|
||||
for _, n := range f.Names {
|
||||
if n[0] != '#' {
|
||||
return strings.TrimLeft(n, "#-")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func logFrameFunc(debug bool) weave.LogFrameFunc {
|
||||
if !debug {
|
||||
return func(prefix string, frame []byte, eth *layers.Ethernet) {}
|
||||
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {}
|
||||
}
|
||||
return func(prefix string, frame []byte, eth *layers.Ethernet) {
|
||||
return func(prefix string, frame []byte, dec *weave.EthernetDecoder) {
|
||||
h := fmt.Sprintf("%x", sha256.Sum256(frame))
|
||||
if eth == nil {
|
||||
log.Println(prefix, len(frame), "bytes (", h, ")")
|
||||
} else {
|
||||
log.Println(prefix, len(frame), "bytes (", h, "):", eth.SrcMAC, "->", eth.DstMAC)
|
||||
parts := []interface{}{prefix, len(frame), "bytes (", h, ")"}
|
||||
|
||||
if dec != nil {
|
||||
parts = append(parts, dec.Eth.SrcMAC, "->", dec.Eth.DstMAC)
|
||||
|
||||
if dec.DF() {
|
||||
parts = append(parts, "(DF)")
|
||||
}
|
||||
}
|
||||
|
||||
Log.Println(parts...)
|
||||
}
|
||||
}
|
||||
|
||||
func parseAndCheckCIDR(cidrStr string) address.CIDR {
|
||||
_, cidr, err := address.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
Log.Fatal(err)
|
||||
}
|
||||
if cidr.Size() < ipam.MinSubnetSize {
|
||||
log.Fatalf("Allocation range smaller than minimum size %d: %s", ipam.MinSubnetSize, cidrStr)
|
||||
Log.Fatalf("Allocation range smaller than minimum size %d: %s", ipam.MinSubnetSize, cidrStr)
|
||||
}
|
||||
return cidr
|
||||
}
|
||||
@@ -221,7 +285,7 @@ func createAllocator(router *weave.Router, ipRangeStr string, defaultSubnetStr s
|
||||
if defaultSubnetStr != "" {
|
||||
defaultSubnet = parseAndCheckCIDR(defaultSubnetStr)
|
||||
if !ipRange.Range().Overlaps(defaultSubnet.Range()) {
|
||||
log.Fatalf("Default subnet %s out of bounds: %s", defaultSubnet, ipRange)
|
||||
Log.Fatalf("IP address allocation default subnet %s does not overlap with allocation range %s", defaultSubnet, ipRange)
|
||||
}
|
||||
}
|
||||
allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange.Range(), quorum)
|
||||
@@ -248,49 +312,11 @@ func determineQuorum(initPeerCountFlag int, peers []string) uint {
|
||||
// specify it explicitly if that becomes a problem.
|
||||
clusterSize := uint(len(peers) + 1)
|
||||
quorum := clusterSize/2 + 1
|
||||
log.Println("Assuming quorum size of", quorum)
|
||||
Log.Println("Assuming quorum size of", quorum)
|
||||
return quorum
|
||||
}
|
||||
|
||||
func handleHTTP(router *weave.Router, httpAddr string, allocator *ipam.Allocator, defaultSubnet address.CIDR, docker *docker.Client) {
|
||||
muxRouter := mux.NewRouter()
|
||||
|
||||
if allocator != nil {
|
||||
allocator.HandleHTTP(muxRouter, defaultSubnet, docker)
|
||||
}
|
||||
|
||||
muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "weave router", version)
|
||||
fmt.Fprintln(w, router.Status())
|
||||
if allocator != nil {
|
||||
fmt.Fprintln(w, allocator.String())
|
||||
fmt.Fprintln(w, "Allocator default subnet:", defaultSubnet)
|
||||
}
|
||||
})
|
||||
|
||||
muxRouter.Methods("GET").Path("/status-json").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
json, _ := router.StatusJSON(version)
|
||||
w.Write(json)
|
||||
})
|
||||
|
||||
muxRouter.Methods("POST").Path("/connect").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, fmt.Sprint("unable to parse form: ", err), http.StatusBadRequest)
|
||||
}
|
||||
if errors := router.ConnectionMaker.InitiateConnections(r.Form["peer"], r.FormValue("replace") == "true"); len(errors) > 0 {
|
||||
http.Error(w, errorMessages(errors), http.StatusBadRequest)
|
||||
}
|
||||
})
|
||||
|
||||
muxRouter.Methods("POST").Path("/forget").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, fmt.Sprint("unable to parse form: ", err), http.StatusBadRequest)
|
||||
}
|
||||
router.ConnectionMaker.ForgetConnections(r.Form["peer"])
|
||||
})
|
||||
|
||||
http.Handle("/", muxRouter)
|
||||
|
||||
func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
|
||||
protocol := "tcp"
|
||||
if strings.HasPrefix(httpAddr, "/") {
|
||||
os.Remove(httpAddr) // in case it's there from last time
|
||||
@@ -298,11 +324,10 @@ func handleHTTP(router *weave.Router, httpAddr string, allocator *ipam.Allocator
|
||||
}
|
||||
l, err := net.Listen(protocol, httpAddr)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create http listener socket: ", err)
|
||||
Log.Fatal("Unable to create http listener socket: ", err)
|
||||
}
|
||||
|
||||
err = http.Serve(l, nil)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create http server", err)
|
||||
Log.Fatal("Unable to create http server", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
weavetest "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
weavetest.TrimTestArgs()
|
||||
main()
|
||||
}
|
||||
+10
-13
@@ -1,32 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/proxy"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoCommandSpecified = errors.New("No command specified")
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
var (
|
||||
args = os.Args[1:]
|
||||
)
|
||||
|
||||
if len(args) > 0 && args[0] == "-s" {
|
||||
args = args[1:]
|
||||
} else {
|
||||
usr2 := make(chan os.Signal)
|
||||
signal.Notify(usr2, syscall.SIGUSR2)
|
||||
<-usr2
|
||||
}
|
||||
|
||||
_, err := weavenet.EnsureInterface("ethwe", -1)
|
||||
_, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe")
|
||||
checkErr(err)
|
||||
|
||||
if len(args) == 0 {
|
||||
checkErr(proxy.ErrNoCommandSpecified)
|
||||
checkErr(ErrNoCommandSpecified)
|
||||
}
|
||||
|
||||
binary, err := exec.LookPath(args[0])
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Based on net/http/internal
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLineTooLong = errors.New("header line too long")
|
||||
ErrInvalidChunkLength = errors.New("invalid byte in chunk length")
|
||||
)
|
||||
|
||||
// Unlike net/http/internal.chunkedReader, this has an interface where we can
|
||||
// handle individual chunks. The interface is based on database/sql.Rows.
|
||||
func NewChunkedReader(r io.Reader) *ChunkedReader {
|
||||
br, ok := r.(*bufio.Reader)
|
||||
if !ok {
|
||||
br = bufio.NewReader(r)
|
||||
}
|
||||
return &ChunkedReader{r: br}
|
||||
}
|
||||
|
||||
type ChunkedReader struct {
|
||||
r *bufio.Reader
|
||||
chunk *io.LimitedReader
|
||||
err error
|
||||
buf [2]byte
|
||||
}
|
||||
|
||||
// Next prepares the next chunk for reading. It returns true on success, or
|
||||
// false if there is no next chunk or an error happened while preparing
|
||||
// it. Err should be consulted to distinguish between the two cases.
|
||||
//
|
||||
// Every call to Chunk, even the first one, must be preceded by a call to Next.
|
||||
//
|
||||
// Calls to Next will discard any unread bytes in the current Chunk.
|
||||
func (cr *ChunkedReader) Next() bool {
|
||||
if cr.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check the termination of the previous chunk
|
||||
if cr.chunk != nil {
|
||||
// Make sure the remainder is drained, in case the user of this quit
|
||||
// reading early.
|
||||
if _, cr.err = io.Copy(ioutil.Discard, cr.chunk); cr.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check the next two bytes after the chunk are \r\n
|
||||
if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err != nil {
|
||||
return false
|
||||
}
|
||||
if cr.buf[0] != '\r' || cr.buf[1] != '\n' {
|
||||
cr.err = errors.New("malformed chunked encoding")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
cr.chunk = &io.LimitedReader{R: cr.r}
|
||||
}
|
||||
|
||||
// Setup the next chunk
|
||||
if n := cr.beginChunk(); n > 0 {
|
||||
cr.chunk.N = int64(n)
|
||||
} else if cr.err == nil {
|
||||
cr.err = io.EOF
|
||||
}
|
||||
return cr.err == nil
|
||||
}
|
||||
|
||||
// Chunk returns the io.Reader of the current chunk. On each call, this returns
|
||||
// the same io.Reader for a given chunk.
|
||||
func (cr *ChunkedReader) Chunk() io.Reader {
|
||||
return cr.chunk
|
||||
}
|
||||
|
||||
// Err returns the error, if any, that was encountered during iteration.
|
||||
func (cr *ChunkedReader) Err() error {
|
||||
if cr.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return cr.err
|
||||
}
|
||||
|
||||
func (cr *ChunkedReader) beginChunk() uint64 {
|
||||
var (
|
||||
line []byte
|
||||
n uint64
|
||||
)
|
||||
// chunk-size CRLF
|
||||
line, cr.err = readLine(cr.r)
|
||||
if cr.err != nil {
|
||||
return 0
|
||||
}
|
||||
n, cr.err = strconv.ParseUint(string(line), 16, 64)
|
||||
if cr.err != nil {
|
||||
cr.err = ErrInvalidChunkLength
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Read a line of bytes (up to \n) from b.
|
||||
// Give up if the line exceeds the buffer size.
|
||||
// The returned bytes are a pointer into storage in
|
||||
// the bufio, so they are only valid until the next bufio read.
|
||||
func readLine(b *bufio.Reader) (p []byte, err error) {
|
||||
if p, err = b.ReadSlice('\n'); err != nil {
|
||||
// We always know when EOF is coming.
|
||||
// If the caller asked for a line, there should be a line.
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
} else if err == bufio.ErrBufferFull {
|
||||
err = ErrLineTooLong
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return bytes.TrimRight(p, " \t\n\r"), nil
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// Based on net/http/internal
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChunk(t *testing.T) {
|
||||
r := NewChunkedReader(bytes.NewBufferString(
|
||||
"7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n",
|
||||
))
|
||||
|
||||
assertNextChunk(t, r, "hello, ")
|
||||
assertNextChunk(t, r, "world! 0123456789abcdef")
|
||||
assertNoMoreChunks(t, r)
|
||||
}
|
||||
|
||||
func TestIncompleteReadOfChunk(t *testing.T) {
|
||||
r := NewChunkedReader(bytes.NewBufferString(
|
||||
"7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n",
|
||||
))
|
||||
|
||||
// Incomplete read of first chunk
|
||||
{
|
||||
if !r.Next() {
|
||||
t.Fatalf("Expected chunk, but ran out early: %v", r.Err())
|
||||
}
|
||||
if r.Err() != nil {
|
||||
t.Fatalf("Error reading chunk: %q", r.Err())
|
||||
}
|
||||
// Read just 2 bytes
|
||||
buf := make([]byte, 2)
|
||||
if _, err := io.ReadFull(r.Chunk(), buf[:2]); err != nil {
|
||||
t.Fatalf("Error reading first bytes of chunk: %q", err)
|
||||
}
|
||||
if buf[0] != 'h' || buf[1] != 'e' {
|
||||
t.Fatalf("Unexpected first 2 bytes of chunk: %q", buf)
|
||||
}
|
||||
}
|
||||
|
||||
assertNextChunk(t, r, "world! 0123456789abcdef")
|
||||
assertNoMoreChunks(t, r)
|
||||
}
|
||||
|
||||
func TestMalformedChunks(t *testing.T) {
|
||||
r := NewChunkedReader(bytes.NewBufferString(
|
||||
"7\r\nhello, GARBAGEBYTES17\r\nworld! 0123456789abcdef\r\n0\r\n",
|
||||
))
|
||||
|
||||
assertNextChunk(t, r, "hello, ")
|
||||
assertError(t, r, "malformed chunked encoding")
|
||||
}
|
||||
|
||||
type charReader byte
|
||||
|
||||
// Read an infinite sequence of some char
|
||||
func (r *charReader) Read(p []byte) (int, error) {
|
||||
b := byte(*r)
|
||||
for i := range p {
|
||||
p[i] = b
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TestLargeChunks(t *testing.T) {
|
||||
var expected int64 = 1024 * 1024
|
||||
chars := charReader('a')
|
||||
r := NewChunkedReader(io.MultiReader(
|
||||
strings.NewReader(strconv.FormatInt(expected, 16)+"\r\n"),
|
||||
&io.LimitedReader{N: expected, R: &chars},
|
||||
strings.NewReader("\r\n0\r\n"),
|
||||
))
|
||||
|
||||
if !r.Next() {
|
||||
t.Fatalf("Expected chunk, but ran out early: %v", r.Err())
|
||||
}
|
||||
if r.Err() != nil {
|
||||
t.Fatalf("Error reading chunk: %q", r.Err())
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, r.Chunk())
|
||||
if n != expected {
|
||||
t.Errorf("chunk reader read %q; want %q", n, expected)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("reading chunk: %v", err)
|
||||
}
|
||||
assertNoMoreChunks(t, r)
|
||||
}
|
||||
|
||||
func TestInvalidChunkSize(t *testing.T) {
|
||||
r := NewChunkedReader(bytes.NewBufferString(
|
||||
"foobar\r\nhello, \r\n0\r\n",
|
||||
))
|
||||
|
||||
assertError(t, r, "invalid byte in chunk length")
|
||||
}
|
||||
|
||||
func TestChunkSizeLineTooLong(t *testing.T) {
|
||||
var (
|
||||
maxLineLength = 4096
|
||||
chunkSize string
|
||||
)
|
||||
for i := 0; i < maxLineLength; i++ {
|
||||
chunkSize = chunkSize + "0"
|
||||
}
|
||||
chunkSize = chunkSize + "7"
|
||||
|
||||
r := NewChunkedReader(bytes.NewBufferString(
|
||||
chunkSize + "\r\nhello, \r\n0\r\n",
|
||||
))
|
||||
|
||||
assertError(t, r, "header line too long")
|
||||
}
|
||||
|
||||
func TestBytesAfterLastChunkAreIgnored(t *testing.T) {
|
||||
r := NewChunkedReader(bytes.NewBufferString(
|
||||
"7\r\nhello, \r\n0\r\nGARBAGEBYTES",
|
||||
))
|
||||
|
||||
assertNextChunk(t, r, "hello, ")
|
||||
assertNoMoreChunks(t, r)
|
||||
}
|
||||
|
||||
func assertNextChunk(t *testing.T, r *ChunkedReader, expected string) {
|
||||
if !r.Next() {
|
||||
t.Fatalf("Expected chunk, but ran out early: %v", r.Err())
|
||||
}
|
||||
if r.Err() != nil {
|
||||
t.Fatalf("Error reading chunk: %q", r.Err())
|
||||
}
|
||||
data, err := ioutil.ReadAll(r.Chunk())
|
||||
if string(data) != expected {
|
||||
t.Errorf("chunk reader read %q; want %q", data, expected)
|
||||
}
|
||||
if err != nil {
|
||||
t.Logf(`data: %q`, data)
|
||||
t.Fatalf("reading chunk: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertError(t *testing.T, r *ChunkedReader, e string) {
|
||||
if r.Next() {
|
||||
t.Errorf("Expected failure when reading chunks, but got one")
|
||||
}
|
||||
if r.Err() == nil || r.Err().Error() != e {
|
||||
t.Errorf("chunk reader errored %q; want %q", r.Err(), e)
|
||||
}
|
||||
data, err := ioutil.ReadAll(r.Chunk())
|
||||
if len(data) != 0 {
|
||||
t.Errorf("chunk should have been empty. got %q", data)
|
||||
}
|
||||
if err != nil {
|
||||
t.Logf(`data: %q`, data)
|
||||
t.Errorf("reading chunk: %v", err)
|
||||
}
|
||||
|
||||
if r.Next() {
|
||||
t.Errorf("Expected no more chunks, but found too many")
|
||||
}
|
||||
}
|
||||
|
||||
func assertNoMoreChunks(t *testing.T, r *ChunkedReader) {
|
||||
if r.Next() {
|
||||
t.Errorf("Expected no more chunks, but found too many")
|
||||
}
|
||||
if r.Err() != nil {
|
||||
t.Errorf("Expected no error, but found: %q", r.Err())
|
||||
}
|
||||
}
|
||||
+84
-6
@@ -3,12 +3,15 @@ package proxy
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
@@ -19,9 +22,9 @@ var (
|
||||
weaveWaitEntrypoint = []string{"/w/w"}
|
||||
)
|
||||
|
||||
func callWeave(args ...string) ([]byte, error) {
|
||||
func callWeave(args ...string) ([]byte, []byte, error) {
|
||||
args = append([]string{"--local"}, args...)
|
||||
Debug.Print("Calling weave", args)
|
||||
Log.Debug("Calling weave", args)
|
||||
cmd := exec.Command("./weave", args...)
|
||||
cmd.Env = []string{
|
||||
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
@@ -30,8 +33,26 @@ func callWeave(args ...string) ([]byte, error) {
|
||||
if bridge := os.Getenv("DOCKER_BRIDGE"); bridge != "" {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_BRIDGE=%s", bridge))
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
return out, err
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
return stdout.Bytes(), stderr.Bytes(), err
|
||||
}
|
||||
|
||||
func unmarshalRequestBody(r *http.Request, target interface{}) error {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.Body.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||
|
||||
d := json.NewDecoder(bytes.NewReader(body))
|
||||
d.UseNumber() // don't want large numbers in scientific format
|
||||
return d.Decode(&target)
|
||||
}
|
||||
|
||||
func marshalRequestBody(r *http.Request, body interface{}) error {
|
||||
@@ -44,18 +65,75 @@ func marshalRequestBody(r *http.Request, body interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalResponseBody(r *http.Response, target interface{}) error {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.Body.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||
|
||||
d := json.NewDecoder(bytes.NewReader(body))
|
||||
d.UseNumber() // don't want large numbers in scientific format
|
||||
return d.Decode(&target)
|
||||
}
|
||||
|
||||
func marshalResponseBody(r *http.Response, body interface{}) error {
|
||||
newBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
|
||||
r.ContentLength = int64(len(newBody))
|
||||
// Stop it being chunked, because that hangs
|
||||
r.TransferEncoding = nil
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func inspectContainerInPath(client *docker.Client, path string) (*docker.Container, error) {
|
||||
subs := containerIDRegexp.FindStringSubmatch(path)
|
||||
if subs == nil {
|
||||
err := fmt.Errorf("No container id found in request with path %s", path)
|
||||
Warning.Println(err)
|
||||
Log.Warningln(err)
|
||||
return nil, err
|
||||
}
|
||||
containerID := subs[2]
|
||||
|
||||
container, err := client.InspectContainer(containerID)
|
||||
if err != nil {
|
||||
Warning.Printf("Error inspecting container %s: %v", containerID, err)
|
||||
Log.Warningf("Error inspecting container %s: %v", containerID, err)
|
||||
}
|
||||
return container, err
|
||||
}
|
||||
|
||||
func weaveContainerIPs(containerID string) (mac string, ips []net.IP, nets []*net.IPNet, err error) {
|
||||
stdout, stderr, err := callWeave("ps", containerID)
|
||||
if err != nil || len(stderr) > 0 {
|
||||
err = errors.New(string(stderr))
|
||||
return
|
||||
}
|
||||
if len(stdout) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
fields := strings.Fields(string(stdout))
|
||||
if len(fields) < 2 {
|
||||
return
|
||||
}
|
||||
mac = fields[1]
|
||||
|
||||
var ip net.IP
|
||||
var ipnet *net.IPNet
|
||||
for _, cidr := range fields[2:] {
|
||||
ip, ipnet, err = net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
nets = append(nets, ipnet)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -10,8 +8,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/nameserver"
|
||||
"github.com/weaveworks/weave/router"
|
||||
)
|
||||
|
||||
const MaxDockerHostname = 64
|
||||
@@ -40,22 +40,15 @@ func (err *ErrNoSuchImage) Error() string {
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body.Close()
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||
|
||||
container := createContainerRequestBody{}
|
||||
if err := json.Unmarshal(body, &container); err != nil {
|
||||
if err := unmarshalRequestBody(r, &container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig); err != nil {
|
||||
Info.Printf("Ignoring container due to %s", err)
|
||||
Log.Infof("Leaving container alone because %s", err)
|
||||
} else {
|
||||
Info.Printf("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
|
||||
Log.Infof("Creating container with WEAVE_CIDR \"%s\"", strings.Join(cidrs, " "))
|
||||
if container.HostConfig == nil {
|
||||
container.HostConfig = &docker.HostConfig{}
|
||||
}
|
||||
@@ -66,16 +59,18 @@ func (i *createContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
if err := i.setWeaveWaitEntrypoint(container.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := i.setWeaveDNS(&container, r); err != nil {
|
||||
hostname := r.URL.Query().Get("name")
|
||||
if i.proxy.Config.HostnameFromLabel != "" {
|
||||
if labelValue, ok := container.Labels[i.proxy.Config.HostnameFromLabel]; ok {
|
||||
hostname = labelValue
|
||||
}
|
||||
}
|
||||
hostname = i.proxy.hostnameMatchRegexp.ReplaceAllString(hostname, i.proxy.HostnameReplacement)
|
||||
if err := i.setWeaveDNS(&container, hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newBody, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
|
||||
r.ContentLength = int64(len(newBody))
|
||||
return marshalRequestBody(r, container)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -122,7 +117,7 @@ func (i *createContainerInterceptor) setWeaveWaitEntrypoint(container *docker.Co
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody, r *http.Request) error {
|
||||
func (i *createContainerInterceptor) setWeaveDNS(container *createContainerRequestBody, name string) error {
|
||||
if i.proxy.WithoutDNS {
|
||||
return nil
|
||||
}
|
||||
@@ -134,12 +129,11 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque
|
||||
|
||||
container.HostConfig.DNS = append(container.HostConfig.DNS, i.proxy.dockerBridgeIP)
|
||||
|
||||
name := r.URL.Query().Get("name")
|
||||
if container.Hostname == "" && name != "" {
|
||||
// Strip trailing period because it's unusual to see it used on the end of a host name
|
||||
trimmedDNSDomain := strings.TrimSuffix(dnsDomain, ".")
|
||||
if len(name)+1+len(trimmedDNSDomain) > MaxDockerHostname {
|
||||
Warning.Printf("Container name [%s] too long to be used as hostname", name)
|
||||
Log.Warningf("Container name [%s] too long to be used as hostname", name)
|
||||
} else {
|
||||
container.Hostname = name
|
||||
container.Domainname = trimmedDNSDomain
|
||||
@@ -158,15 +152,15 @@ func (i *createContainerInterceptor) setWeaveDNS(container *createContainerReque
|
||||
}
|
||||
|
||||
func (i *createContainerInterceptor) getDNSDomain() (domain string, running bool) {
|
||||
domain = nameserver.DefaultLocalDomain
|
||||
dnsContainer, err := i.proxy.client.InspectContainer("weavedns")
|
||||
domain = nameserver.DefaultDomain
|
||||
weaveContainer, err := i.proxy.client.InspectContainer("weave")
|
||||
if err != nil ||
|
||||
dnsContainer.NetworkSettings == nil ||
|
||||
dnsContainer.NetworkSettings.IPAddress == "" {
|
||||
weaveContainer.NetworkSettings == nil ||
|
||||
weaveContainer.NetworkSettings.IPAddress == "" {
|
||||
return
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("http://%s:%d/domain", dnsContainer.NetworkSettings.IPAddress, nameserver.DefaultHTTPPort)
|
||||
url := fmt.Sprintf("http://%s:%d/domain", weaveContainer.NetworkSettings.IPAddress, router.HTTPPort)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -14,15 +11,8 @@ import (
|
||||
type createExecInterceptor struct{ proxy *Proxy }
|
||||
|
||||
func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body.Close()
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||
|
||||
options := docker.CreateExecOptions{}
|
||||
if err := json.Unmarshal(body, &options); err != nil {
|
||||
if err := unmarshalRequestBody(r, &options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -37,13 +27,12 @@ func (i *createExecInterceptor) InterceptRequest(r *http.Request) error {
|
||||
|
||||
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
|
||||
if err != nil {
|
||||
Info.Printf("Ignoring container %s due to %s", container.ID, err)
|
||||
Log.Infof("Leaving container %s alone because %s", container.ID, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
Info.Printf("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
|
||||
cmd := append(weaveWaitEntrypoint, "-s")
|
||||
options.Cmd = append(cmd, options.Cmd...)
|
||||
Log.Infof("Exec in container %s with WEAVE_CIDR \"%s\"", container.ID, strings.Join(cidrs, " "))
|
||||
options.Cmd = append(weaveWaitEntrypoint, options.Cmd...)
|
||||
|
||||
return marshalRequestBody(r, options)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type inspectContainerInterceptor struct{ proxy *Proxy }
|
||||
|
||||
func (i *inspectContainerInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inspectContainerInterceptor) InterceptResponse(r *http.Response) error {
|
||||
if !i.proxy.RewriteInspect {
|
||||
return nil
|
||||
}
|
||||
|
||||
container := map[string]interface{}{}
|
||||
if err := unmarshalResponseBody(r, &container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateContainerNetworkSettings(container); err != nil {
|
||||
Log.Warningf("Inspecting container %s failed: %s", container["Id"], err)
|
||||
}
|
||||
|
||||
return marshalResponseBody(r, container)
|
||||
}
|
||||
|
||||
func updateContainerNetworkSettings(container map[string]interface{}) error {
|
||||
containerID, err := lookupString(container, "Id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mac, ips, nets, err := weaveContainerIPs(containerID)
|
||||
if err != nil || len(ips) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
networkSettings, err := lookupObject(container, "NetworkSettings")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
networkSettings["MacAddress"] = mac
|
||||
networkSettings["IPAddress"] = ips[0].String()
|
||||
networkSettings["IPPrefixLen"], _ = nets[0].Mask.Size()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type inspectExecInterceptor struct{ proxy *Proxy }
|
||||
|
||||
func (i *inspectExecInterceptor) InterceptRequest(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inspectExecInterceptor) InterceptResponse(r *http.Response) error {
|
||||
if !i.proxy.RewriteInspect {
|
||||
return nil
|
||||
}
|
||||
|
||||
exec := map[string]interface{}{}
|
||||
if err := unmarshalResponseBody(r, &exec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container, err := lookupObject(exec, "Container")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateContainerNetworkSettings(container); err != nil {
|
||||
Log.Warningf("Inspecting exec %s failed: %s", exec["Id"], err)
|
||||
}
|
||||
|
||||
return marshalResponseBody(r, exec)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type UnmarshalWrongTypeError struct {
|
||||
Field, Expected string
|
||||
Got interface{}
|
||||
}
|
||||
|
||||
func (e *UnmarshalWrongTypeError) Error() string {
|
||||
return fmt.Sprintf("Wrong type for %s field, expected %s, but got %T", e.Field, e.Expected, e.Got)
|
||||
}
|
||||
|
||||
// For parsing json objects. Look up (or create) a child object in the given map.
|
||||
func lookupObject(m map[string]interface{}, key string) (map[string]interface{}, error) {
|
||||
iface, ok := m[key]
|
||||
if !ok || iface == nil {
|
||||
result := map[string]interface{}{}
|
||||
m[key] = result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result, ok := iface.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, &UnmarshalWrongTypeError{key, "object", iface}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// For parsing json objects. Look up (or create) a string in the given map.
|
||||
func lookupString(m map[string]interface{}, key string) (string, error) {
|
||||
iface, ok := m[key]
|
||||
if !ok || iface == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
result, ok := iface.(string)
|
||||
if !ok {
|
||||
return "", &UnmarshalWrongTypeError{key, "string", iface}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
+152
-60
@@ -6,9 +6,10 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
@@ -18,75 +19,76 @@ const (
|
||||
defaultCaFile = "ca.pem"
|
||||
defaultKeyFile = "key.pem"
|
||||
defaultCertFile = "cert.pem"
|
||||
dockerSock = "/var/run/docker.sock"
|
||||
dockerSockUnix = "unix://" + dockerSock
|
||||
)
|
||||
|
||||
var (
|
||||
containerCreateRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/create$")
|
||||
containerStartRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/[^/]*/(re)?start$")
|
||||
execCreateRegexp = regexp.MustCompile("^(/v[0-9\\.]*)?/containers/[^/]*/exec$")
|
||||
containerCreateRegexp = dockerAPIEndpoint("containers/create")
|
||||
containerStartRegexp = dockerAPIEndpoint("containers/[^/]*/(re)?start")
|
||||
containerInspectRegexp = dockerAPIEndpoint("containers/[^/]*/json")
|
||||
execCreateRegexp = dockerAPIEndpoint("containers/[^/]*/exec")
|
||||
execInspectRegexp = dockerAPIEndpoint("exec/[^/]*/json")
|
||||
|
||||
ErrWeaveCIDRNone = errors.New("WEAVE_CIDR=none")
|
||||
ErrNoDefaultIPAM = errors.New("--no-default-ipam option")
|
||||
ErrWeaveCIDRNone = errors.New("the container was created with the '-e WEAVE_CIDR=none' option")
|
||||
ErrNoDefaultIPAM = errors.New("the container was created without specifying an IP address with '-e WEAVE_CIDR=...' and the proxy was started with the '--no-default-ipalloc' option")
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
Config
|
||||
|
||||
dial func() (net.Conn, error)
|
||||
client *docker.Client
|
||||
dockerBridgeIP string
|
||||
weaveWaitVolume string
|
||||
func dockerAPIEndpoint(endpoint string) *regexp.Regexp {
|
||||
return regexp.MustCompile("^(/v[0-9\\.]*)?/" + endpoint + "$")
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
DockerAddr string
|
||||
ListenAddr string
|
||||
NoDefaultIPAM bool
|
||||
TLSConfig TLSConfig
|
||||
Version string
|
||||
WithDNS bool
|
||||
WithoutDNS bool
|
||||
HostnameFromLabel string
|
||||
HostnameMatch string
|
||||
HostnameReplacement string
|
||||
ListenAddrs []string
|
||||
RewriteInspect bool
|
||||
NoDefaultIPAM bool
|
||||
NoRewriteHosts bool
|
||||
TLSConfig TLSConfig
|
||||
Version string
|
||||
WithDNS bool
|
||||
WithoutDNS bool
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
Config
|
||||
client *docker.Client
|
||||
dockerBridgeIP string
|
||||
hostnameMatchRegexp *regexp.Regexp
|
||||
weaveWaitVolume string
|
||||
}
|
||||
|
||||
func NewProxy(c Config) (*Proxy, error) {
|
||||
u, err := url.Parse(c.DockerAddr)
|
||||
p := &Proxy{Config: c}
|
||||
|
||||
if err := p.TLSConfig.loadCerts(); err != nil {
|
||||
Log.Fatalf("Could not configure tls for proxy: %s", err)
|
||||
}
|
||||
|
||||
// We pin the protocol version to 1.15 (which corresponds to
|
||||
// Docker 1.3.x; the earliest version supported by weave) in order
|
||||
// to insulate ourselves from breaking changes to the API, as
|
||||
// happened in 1.20 (Docker 1.8.0) when the presentation of
|
||||
// volumes changed in `inspect`.
|
||||
client, err := docker.NewVersionedClient(dockerSockUnix, "1.15")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetAddr := ""
|
||||
switch u.Scheme {
|
||||
case "tcp":
|
||||
targetAddr = u.Host
|
||||
case "unix":
|
||||
targetAddr = u.Path
|
||||
}
|
||||
|
||||
p := &Proxy{
|
||||
Config: c,
|
||||
dial: func() (net.Conn, error) {
|
||||
return net.Dial(u.Scheme, targetAddr)
|
||||
},
|
||||
}
|
||||
p.client = client
|
||||
|
||||
if !p.WithoutDNS {
|
||||
dockerBridgeIP, err := callWeave("docker-bridge-ip")
|
||||
dockerBridgeIP, stderr, err := callWeave("docker-bridge-ip")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf(string(stderr))
|
||||
}
|
||||
p.dockerBridgeIP = string(dockerBridgeIP)
|
||||
}
|
||||
|
||||
if err := p.TLSConfig.loadCerts(); err != nil {
|
||||
Error.Fatalf("Could not configure tls for proxy: %s", err)
|
||||
}
|
||||
|
||||
// We pin the protocol version to 1.15 (which corresponds to Docker 1.3.x;
|
||||
// the earliest version supported by weave) in order to insulate ourselves
|
||||
// from breaking changes to the API, as happened in 1.20 (Docker 1.8.0) when
|
||||
// the presentation of volumes changed in inspect.
|
||||
p.client, err = docker.NewVersionedClient(p.DockerAddr, "1.15")
|
||||
p.hostnameMatchRegexp, err = regexp.Compile(c.HostnameMatch)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Incorrect hostname match '%s': %s", c.HostnameMatch, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -97,6 +99,10 @@ func NewProxy(c Config) (*Proxy, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) Dial() (net.Conn, error) {
|
||||
return net.Dial("unix", dockerSock)
|
||||
}
|
||||
|
||||
func (proxy *Proxy) findWeaveWaitVolume() error {
|
||||
container, err := proxy.client.InspectContainer("weaveproxy")
|
||||
if err != nil {
|
||||
@@ -117,7 +123,7 @@ func (proxy *Proxy) findWeaveWaitVolume() error {
|
||||
}
|
||||
|
||||
func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Info.Printf("%s %s", r.Method, r.URL)
|
||||
Log.Infof("%s %s", r.Method, r.URL)
|
||||
path := r.URL.Path
|
||||
var i interceptor
|
||||
switch {
|
||||
@@ -125,35 +131,121 @@ func (proxy *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
i = &createContainerInterceptor{proxy}
|
||||
case containerStartRegexp.MatchString(path):
|
||||
i = &startContainerInterceptor{proxy}
|
||||
case containerInspectRegexp.MatchString(path):
|
||||
i = &inspectContainerInterceptor{proxy}
|
||||
case execCreateRegexp.MatchString(path):
|
||||
i = &createExecInterceptor{proxy}
|
||||
case execInspectRegexp.MatchString(path):
|
||||
i = &inspectExecInterceptor{proxy}
|
||||
default:
|
||||
i = &nullInterceptor{}
|
||||
}
|
||||
proxy.Intercept(i, w, r)
|
||||
}
|
||||
|
||||
func (proxy *Proxy) ListenAndServe() error {
|
||||
listener, err := net.Listen("tcp", proxy.ListenAddr)
|
||||
func (proxy *Proxy) ListenAndServe() {
|
||||
listeners := []net.Listener{}
|
||||
addrs := []string{}
|
||||
for _, addr := range proxy.ListenAddrs {
|
||||
listener, normalisedAddr, err := proxy.listen(addr)
|
||||
if err != nil {
|
||||
Log.Fatalf("Cannot listen on %s: %s", addr, err)
|
||||
}
|
||||
listeners = append(listeners, listener)
|
||||
addrs = append(addrs, normalisedAddr)
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
Log.Infoln("proxy listening on", addr)
|
||||
}
|
||||
|
||||
errs := make(chan error)
|
||||
for _, listener := range listeners {
|
||||
go func(listener net.Listener) {
|
||||
errs <- (&http.Server{Handler: proxy}).Serve(listener)
|
||||
}(listener)
|
||||
}
|
||||
for range listeners {
|
||||
err := <-errs
|
||||
if err != nil {
|
||||
Log.Fatalf("Serve failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func copyOwnerAndPermissions(from, to string) error {
|
||||
stat, err := os.Stat(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if proxy.TLSConfig.enabled() {
|
||||
listener = tls.NewListener(listener, proxy.TLSConfig.Config)
|
||||
if err = os.Chmod(to, stat.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Info.Println("proxy listening on", proxy.ListenAddr)
|
||||
moreStat, ok := stat.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (&http.Server{Handler: proxy}).Serve(listener)
|
||||
if err = os.Chown(to, int(moreStat.Uid), int(moreStat.Gid)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) listen(protoAndAddr string) (net.Listener, string, error) {
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
proto, addr string
|
||||
)
|
||||
|
||||
if protoAddrParts := strings.SplitN(protoAndAddr, "://", 2); len(protoAddrParts) == 2 {
|
||||
proto, addr = protoAddrParts[0], protoAddrParts[1]
|
||||
} else if strings.HasPrefix(protoAndAddr, "/") {
|
||||
proto, addr = "unix", protoAndAddr
|
||||
} else {
|
||||
proto, addr = "tcp", protoAndAddr
|
||||
}
|
||||
|
||||
switch proto {
|
||||
case "tcp":
|
||||
listener, err = net.Listen(proto, addr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if proxy.TLSConfig.enabled() {
|
||||
listener = tls.NewListener(listener, proxy.TLSConfig.Config)
|
||||
}
|
||||
|
||||
case "unix":
|
||||
// remove socket from last invocation
|
||||
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) {
|
||||
return nil, "", err
|
||||
}
|
||||
listener, err = net.Listen(proto, addr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err = copyOwnerAndPermissions(dockerSock, addr); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
default:
|
||||
Log.Fatalf("Invalid protocol format: %q", proto)
|
||||
}
|
||||
|
||||
return listener, fmt.Sprintf("%s://%s", proto, addr), nil
|
||||
}
|
||||
|
||||
func (proxy *Proxy) weaveCIDRsFromConfig(config *docker.Config, hostConfig *docker.HostConfig) ([]string, error) {
|
||||
if hostConfig != nil &&
|
||||
hostConfig.NetworkMode != "" &&
|
||||
hostConfig.NetworkMode != "default" &&
|
||||
hostConfig.NetworkMode != "bridge" {
|
||||
return nil, fmt.Errorf("--net option: %q", hostConfig.NetworkMode)
|
||||
netMode := ""
|
||||
if hostConfig != nil {
|
||||
netMode = hostConfig.NetworkMode
|
||||
}
|
||||
if netMode == "host" || strings.HasPrefix(netMode, "container:") {
|
||||
return nil, fmt.Errorf("the container was created with the '--net=%s'", netMode)
|
||||
}
|
||||
for _, e := range config.Env {
|
||||
if strings.HasPrefix(e, "WEAVE_CIDR=") {
|
||||
|
||||
+40
-36
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
@@ -24,15 +23,15 @@ func (proxy *Proxy) Intercept(i interceptor, w http.ResponseWriter, r *http.Requ
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
default:
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
Warning.Print("Error intercepting request: ", err)
|
||||
Log.Warning("Error intercepting request: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := proxy.dial()
|
||||
conn, err := proxy.Dial()
|
||||
if err != nil {
|
||||
http.Error(w, "Could not connect to target", http.StatusInternalServerError)
|
||||
Warning.Print(err)
|
||||
Log.Warning(err)
|
||||
return
|
||||
}
|
||||
client := httputil.NewClientConn(conn, nil)
|
||||
@@ -41,13 +40,13 @@ func (proxy *Proxy) Intercept(i interceptor, w http.ResponseWriter, r *http.Requ
|
||||
resp, err := client.Do(r)
|
||||
if err != nil && err != httputil.ErrPersistEOF {
|
||||
http.Error(w, fmt.Sprintf("Could not make request to target: %v", err), http.StatusInternalServerError)
|
||||
Warning.Print("Error forwarding request: ", err)
|
||||
Log.Warning("Error forwarding request: ", err)
|
||||
return
|
||||
}
|
||||
err = i.InterceptResponse(resp)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
Warning.Print("Error intercepting response: ", err)
|
||||
Log.Warning("Error intercepting response: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -57,7 +56,7 @@ func (proxy *Proxy) Intercept(i interceptor, w http.ResponseWriter, r *http.Requ
|
||||
hdr.Add(k, v)
|
||||
}
|
||||
}
|
||||
Debug.Printf("Response from target: %s %v", resp.Status, w.Header())
|
||||
Log.Debugf("Response from target: %s %v", resp.Status, w.Header())
|
||||
|
||||
if resp.Header.Get("Content-Type") == "application/vnd.docker.raw-stream" {
|
||||
doRawStream(w, resp, client)
|
||||
@@ -66,13 +65,13 @@ func (proxy *Proxy) Intercept(i interceptor, w http.ResponseWriter, r *http.Requ
|
||||
} else {
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||
Warning.Print(err)
|
||||
Log.Warning(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
|
||||
down, downBuf, up, rem, err := hijack(w, client)
|
||||
down, downBuf, up, remaining, err := hijack(w, client)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -81,23 +80,23 @@ func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.Cl
|
||||
defer up.Close()
|
||||
|
||||
if _, err := down.Write([]byte("HTTP/1.1 200 OK\n")); err != nil {
|
||||
Warning.Print(err)
|
||||
Log.Warning(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := resp.Header.Write(down); err != nil {
|
||||
Warning.Print(err)
|
||||
Log.Warning(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := down.Write([]byte("\n")); err != nil {
|
||||
Warning.Print(err)
|
||||
Log.Warning(err)
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go copyStream(down, io.MultiReader(rem, up), &wg)
|
||||
go copyStream(down, io.MultiReader(remaining, up), &wg)
|
||||
go copyStream(up, downBuf, &wg)
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -109,7 +108,7 @@ type closeWriter interface {
|
||||
func copyStream(dst io.WriteCloser, src io.Reader, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
Warning.Print(err)
|
||||
Log.Warning(err)
|
||||
}
|
||||
var err error
|
||||
if c, ok := dst.(closeWriter); ok {
|
||||
@@ -118,37 +117,42 @@ func copyStream(dst io.WriteCloser, src io.Reader, wg *sync.WaitGroup) {
|
||||
err = dst.Close()
|
||||
}
|
||||
if err != nil {
|
||||
Warning.Printf("Error closing connection: %s", err)
|
||||
Log.Warningf("Error closing connection: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type writeFlusher interface {
|
||||
io.Writer
|
||||
http.Flusher
|
||||
}
|
||||
|
||||
func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
|
||||
// Because we can't go back to request/response after we
|
||||
// hijack the connection, we need to close it and make the
|
||||
// client open another.
|
||||
w.Header().Add("Connection", "close")
|
||||
wf, ok := w.(writeFlusher)
|
||||
if !ok {
|
||||
http.Error(w, "Error forwarding chunked response body: flush not available", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
down, _, up, rem, err := hijack(w, client)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to hijack response stream for chunked response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
up, remaining := client.Hijack()
|
||||
defer up.Close()
|
||||
defer down.Close()
|
||||
// Copy the chunked response body to downstream,
|
||||
// stopping at the end of the chunked section.
|
||||
rawResponseBody := io.MultiReader(rem, up)
|
||||
if _, err := io.Copy(ioutil.Discard, httputil.NewChunkedReader(io.TeeReader(rawResponseBody, down))); err != nil {
|
||||
http.Error(w, "Error copying chunked response body", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
var err error
|
||||
chunks := NewChunkedReader(io.MultiReader(remaining, up))
|
||||
for chunks.Next() && err == nil {
|
||||
_, err = io.Copy(wf, chunks.Chunk())
|
||||
wf.Flush()
|
||||
}
|
||||
if err == nil {
|
||||
err = chunks.Err()
|
||||
}
|
||||
if err != nil {
|
||||
Log.Errorf("Error forwarding chunked response body: %s", err)
|
||||
}
|
||||
resp.Trailer.Write(down)
|
||||
// a chunked response ends with a CRLF
|
||||
down.Write([]byte("\r\n"))
|
||||
}
|
||||
|
||||
func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, rem io.Reader, err error) {
|
||||
func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, remaining io.Reader, err error) {
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
err = errors.New("Unable to cast to Hijack")
|
||||
@@ -158,6 +162,6 @@ func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn,
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
up, rem = client.Hijack()
|
||||
up, remaining = client.Hijack()
|
||||
return
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
@@ -27,17 +26,22 @@ func (i *startContainerInterceptor) InterceptResponse(r *http.Response) error {
|
||||
|
||||
cidrs, err := i.proxy.weaveCIDRsFromConfig(container.Config, container.HostConfig)
|
||||
if err != nil {
|
||||
Info.Printf("Ignoring container %s due to %s", container.ID, err)
|
||||
Log.Infof("Leaving container %s alone because %s", container.ID, err)
|
||||
return nil
|
||||
}
|
||||
Info.Printf("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
|
||||
Log.Infof("Attaching container %s with WEAVE_CIDR \"%s\" to weave network", container.ID, strings.Join(cidrs, " "))
|
||||
args := []string{"attach"}
|
||||
args = append(args, cidrs...)
|
||||
if !i.proxy.NoRewriteHosts {
|
||||
args = append(args, "--rewrite-hosts")
|
||||
}
|
||||
args = append(args, "--or-die", container.ID)
|
||||
if output, err := callWeave(args...); err != nil {
|
||||
Warning.Printf("Attaching container %s to weave network failed: %s", container.ID, string(output))
|
||||
return errors.New(string(output))
|
||||
if _, stderr, err := callWeave(args...); err != nil {
|
||||
Log.Warningf("Attaching container %s to weave network failed: %s", container.ID, string(stderr))
|
||||
return errors.New(string(stderr))
|
||||
} else if len(stderr) > 0 {
|
||||
Log.Warningf("Attaching container %s to weave network: %s", container.ID, string(stderr))
|
||||
}
|
||||
|
||||
return i.proxy.client.KillContainer(docker.KillContainerOptions{ID: container.ID, Signal: docker.SIGUSR2})
|
||||
return nil
|
||||
}
|
||||
+139
-66
@@ -2,10 +2,10 @@ package router
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -29,6 +29,8 @@ const (
|
||||
TieBreakTied
|
||||
)
|
||||
|
||||
var ErrConnectToSelf = errors.New("Cannot connect to ourself")
|
||||
|
||||
type RemoteConnection struct {
|
||||
local *Peer
|
||||
remote *Peer
|
||||
@@ -50,7 +52,7 @@ type LocalConnection struct {
|
||||
SessionKey *[32]byte
|
||||
heartbeatTCP *time.Ticker
|
||||
heartbeatTimeout *time.Timer
|
||||
heartbeatFrame *ForwardedFrame
|
||||
heartbeatFrame []byte
|
||||
heartbeat *time.Ticker
|
||||
fragTest *time.Ticker
|
||||
forwarder *Forwarder
|
||||
@@ -86,38 +88,28 @@ func (conn *RemoteConnection) Log(args ...interface{}) {
|
||||
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
func (conn *RemoteConnection) String() string {
|
||||
from := "<nil>"
|
||||
if conn.local != nil {
|
||||
from = conn.local.String()
|
||||
}
|
||||
to := "<nil>"
|
||||
if conn.remote != nil {
|
||||
to = conn.remote.String()
|
||||
}
|
||||
return fmt.Sprint("Connection ", from, "->", to)
|
||||
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
|
||||
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
func NewLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router) *LocalConnection {
|
||||
// Does not return anything. If the connection is successful, it will
|
||||
// end up in the local peer's connections map.
|
||||
func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router, acceptNewPeer bool) {
|
||||
if connRemote.local != router.Ourself.Peer {
|
||||
log.Fatal("Attempt to create local connection from a peer which is not ourself")
|
||||
}
|
||||
// NB, we're taking a copy of connRemote here.
|
||||
return &LocalConnection{
|
||||
actionChan := make(chan ConnectionAction, ChannelSize)
|
||||
finished := make(chan struct{})
|
||||
conn := &LocalConnection{
|
||||
RemoteConnection: *connRemote,
|
||||
Router: router,
|
||||
TCPConn: tcpConn,
|
||||
remoteUDPAddr: udpAddr,
|
||||
effectivePMTU: DefaultPMTU}
|
||||
}
|
||||
|
||||
// Async. Does not return anything. If the connection is successful,
|
||||
// it will end up in the local peer's connections map.
|
||||
func (conn *LocalConnection) Start(acceptNewPeer bool) {
|
||||
actionChan := make(chan ConnectionAction, ChannelSize)
|
||||
conn.actionChan = actionChan
|
||||
finished := make(chan struct{})
|
||||
conn.finished = finished
|
||||
effectivePMTU: DefaultPMTU,
|
||||
uid: randUint64(),
|
||||
actionChan: actionChan,
|
||||
finished: finished}
|
||||
go conn.run(actionChan, finished, acceptNewPeer)
|
||||
}
|
||||
|
||||
@@ -159,8 +151,7 @@ func (conn *LocalConnection) setEffectivePMTU(pmtu int) {
|
||||
}
|
||||
|
||||
// Called by the connection's actor process, and by the connection's
|
||||
// TCP received process. StackFrag is read in conn.Forward (called by
|
||||
// router udp listener and sniffer processes)
|
||||
// TCP receiver process. StackFrag is read in conn.forward
|
||||
func (conn *LocalConnection) setStackFrag(frag bool) {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
@@ -254,15 +245,11 @@ func (conn *LocalConnection) SetEstablished() {
|
||||
}
|
||||
// Send a large frame down the DF channel in order to prompt
|
||||
// PMTU discovery to start.
|
||||
conn.Forward(true, &ForwardedFrame{
|
||||
srcPeer: conn.local,
|
||||
dstPeer: conn.remote,
|
||||
frame: PMTUDiscovery},
|
||||
nil)
|
||||
conn.Send(true, PMTUDiscovery)
|
||||
conn.heartbeat = time.NewTicker(SlowHeartbeat)
|
||||
conn.fragTest = time.NewTicker(FragTestInterval)
|
||||
// avoid initial waits for timers to fire
|
||||
conn.Forward(true, conn.heartbeatFrame, nil)
|
||||
conn.Send(true, conn.heartbeatFrame)
|
||||
conn.performFragTest()
|
||||
return nil
|
||||
})
|
||||
@@ -286,12 +273,38 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
|
||||
defer close(finished)
|
||||
|
||||
conn.TCPConn.SetLinger(0)
|
||||
|
||||
_, dec, err := conn.handshake(acceptNewPeer)
|
||||
intro, err := ProtocolIntroParams{
|
||||
MinVersion: conn.Router.ProtocolMinVersion,
|
||||
MaxVersion: ProtocolMaxVersion,
|
||||
Features: conn.makeFeatures(),
|
||||
Conn: conn.TCPConn,
|
||||
Password: conn.Router.Password,
|
||||
Outbound: conn.outbound,
|
||||
}.DoIntro()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn.Log("completed handshake; using protocol version", conn.version)
|
||||
|
||||
conn.SessionKey = intro.SessionKey
|
||||
conn.tcpSender = intro.Sender
|
||||
conn.version = intro.Version
|
||||
|
||||
remote, err := conn.parseFeatures(intro.Features)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if conn.SessionKey == nil {
|
||||
conn.Decryptor = NewNonDecryptor()
|
||||
} else {
|
||||
conn.Decryptor = NewNaClDecryptor(conn.SessionKey, conn.outbound)
|
||||
}
|
||||
|
||||
conn.Log("connection ready; using protocol version", conn.version)
|
||||
|
||||
// The ordering of the following is very important. [1]
|
||||
|
||||
@@ -306,7 +319,8 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
|
||||
if err = conn.initHeartbeats(); err != nil {
|
||||
return
|
||||
}
|
||||
go conn.receiveTCP(dec)
|
||||
|
||||
go conn.receiveTCP(intro.Receiver)
|
||||
err = conn.actorLoop(actionChan)
|
||||
}
|
||||
|
||||
@@ -355,15 +369,88 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished ch
|
||||
// prevent that completely, since, for example, forwarder can only be
|
||||
// created when we know the remote UDP address, but it helps to try.
|
||||
|
||||
func (conn *LocalConnection) makeFeatures() map[string]string {
|
||||
return map[string]string{
|
||||
"PeerNameFlavour": PeerNameFlavour,
|
||||
"Name": conn.local.Name.String(),
|
||||
"NickName": conn.local.NickName,
|
||||
"UID": fmt.Sprint(conn.local.UID),
|
||||
"ConnID": fmt.Sprint(conn.uid),
|
||||
}
|
||||
}
|
||||
|
||||
type features map[string]string
|
||||
|
||||
func (features features) MustHave(keys []string) error {
|
||||
for _, key := range keys {
|
||||
if _, ok := features[key]; !ok {
|
||||
return fmt.Errorf("Field %s is missing", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (features features) Get(key string) string {
|
||||
return features[key]
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
|
||||
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "UID", "ConnID"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remotePeerNameFlavour := features.Get("PeerNameFlavour")
|
||||
if remotePeerNameFlavour != PeerNameFlavour {
|
||||
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
|
||||
}
|
||||
|
||||
name, err := PeerNameFromString(features.Get("Name"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nickName := features.Get("NickName")
|
||||
|
||||
uid, err := ParsePeerUID(features.Get("UID"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.uid ^= remoteConnID
|
||||
return NewPeer(name, nickName, uid, 0), nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
|
||||
if acceptNewPeer {
|
||||
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
|
||||
} else {
|
||||
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
|
||||
if conn.remote == nil {
|
||||
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if conn.remote.UID != remote.UID {
|
||||
return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
|
||||
}
|
||||
|
||||
if conn.remote == conn.local {
|
||||
return ErrConnectToSelf
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) initHeartbeats() error {
|
||||
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
|
||||
conn.heartbeatTimeout = time.NewTimer(HeartbeatTimeout)
|
||||
heartbeatFrameBytes := make([]byte, EthernetOverhead+8)
|
||||
binary.BigEndian.PutUint64(heartbeatFrameBytes[EthernetOverhead:], conn.uid)
|
||||
conn.heartbeatFrame = &ForwardedFrame{
|
||||
srcPeer: conn.local,
|
||||
dstPeer: conn.remote,
|
||||
frame: heartbeatFrameBytes}
|
||||
conn.heartbeatFrame = make([]byte, EthernetOverhead+8)
|
||||
binary.BigEndian.PutUint64(conn.heartbeatFrame[EthernetOverhead:], conn.uid)
|
||||
if conn.remoteUDPAddr == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -380,7 +467,7 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err
|
||||
case <-conn.heartbeatTimeout.C:
|
||||
err = fmt.Errorf("timed out waiting for UDP heartbeat")
|
||||
case <-tickerChan(conn.heartbeat):
|
||||
conn.Forward(true, conn.heartbeatFrame, nil)
|
||||
conn.Send(true, conn.heartbeatFrame)
|
||||
case <-tickerChan(conn.fragTest):
|
||||
conn.performFragTest()
|
||||
}
|
||||
@@ -390,9 +477,9 @@ func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err
|
||||
|
||||
func (conn *LocalConnection) shutdown(err error) {
|
||||
if conn.remote == nil {
|
||||
log.Printf("->[%s] connection shutting down due to error during handshake: %v\n", conn.remoteTCPAddr, err)
|
||||
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
|
||||
} else {
|
||||
conn.Log("connection shutting down due to error:", err)
|
||||
conn.ErrorLog("connection shutting down due to error:", err)
|
||||
}
|
||||
|
||||
if conn.TCPConn != nil {
|
||||
@@ -429,23 +516,13 @@ func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
|
||||
return conn.tcpSender.Send(Concat([]byte{byte(m.tag)}, m.msg))
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) receiveTCP(decoder *gob.Decoder) {
|
||||
usingPassword := conn.SessionKey != nil
|
||||
var receiver TCPReceiver
|
||||
if usingPassword {
|
||||
receiver = NewEncryptedTCPReceiver(conn.SessionKey, conn.outbound)
|
||||
} else {
|
||||
receiver = NewSimpleTCPReceiver()
|
||||
}
|
||||
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
|
||||
var err error
|
||||
for {
|
||||
var msg []byte
|
||||
conn.extendReadDeadline()
|
||||
if err = decoder.Decode(&msg); err != nil {
|
||||
break
|
||||
}
|
||||
msg, err = receiver.Decode(msg)
|
||||
if err != nil {
|
||||
|
||||
var msg []byte
|
||||
if msg, err = receiver.Receive(); err != nil {
|
||||
break
|
||||
}
|
||||
if len(msg) < 1 {
|
||||
@@ -488,18 +565,14 @@ func (conn *LocalConnection) sendFastHeartbeats() error {
|
||||
err := conn.ensureForwarders()
|
||||
if err == nil {
|
||||
conn.heartbeat = time.NewTicker(FastHeartbeat)
|
||||
conn.Forward(true, conn.heartbeatFrame, nil) // avoid initial wait
|
||||
conn.Send(true, conn.heartbeatFrame) // avoid initial wait
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) performFragTest() {
|
||||
conn.setStackFrag(false)
|
||||
conn.Forward(false, &ForwardedFrame{
|
||||
srcPeer: conn.local,
|
||||
dstPeer: conn.remote,
|
||||
frame: FragTest},
|
||||
nil)
|
||||
conn.Send(false, FragTest)
|
||||
}
|
||||
|
||||
func tickerChan(ticker *time.Ticker) <-chan time.Time {
|
||||
|
||||
+15
-63
@@ -1,9 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
@@ -37,19 +35,17 @@ type Target struct {
|
||||
type ConnectionMakerAction func() bool
|
||||
|
||||
func NewConnectionMaker(ourself *LocalPeer, peers *Peers, port int, discovery bool) *ConnectionMaker {
|
||||
return &ConnectionMaker{
|
||||
actionChan := make(chan ConnectionMakerAction, ChannelSize)
|
||||
cm := &ConnectionMaker{
|
||||
ourself: ourself,
|
||||
peers: peers,
|
||||
port: port,
|
||||
discovery: discovery,
|
||||
directPeers: peerAddrs{},
|
||||
targets: make(map[string]*Target)}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) Start() {
|
||||
actionChan := make(chan ConnectionMakerAction, ChannelSize)
|
||||
cm.actionChan = actionChan
|
||||
targets: make(map[string]*Target),
|
||||
actionChan: actionChan}
|
||||
go cm.queryLoop(actionChan)
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) InitiateConnections(peers []string, replace bool) []error {
|
||||
@@ -107,58 +103,6 @@ func (cm *ConnectionMaker) Refresh() {
|
||||
cm.actionChan <- func() bool { return true }
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) Status() ConnectionMakerStatus {
|
||||
// We need to Refresh first in order to clear out any 'attempting'
|
||||
// connections from cm.targets that have been established since
|
||||
// the last run of cm.checkStateAndAttemptConnections. These
|
||||
// entries are harmless but do represent stale state that we do
|
||||
// not want to report.
|
||||
cm.Refresh()
|
||||
resultChan := make(chan ConnectionMakerStatus, 0)
|
||||
cm.actionChan <- func() bool {
|
||||
status := ConnectionMakerStatus{
|
||||
DirectPeers: []string{},
|
||||
Reconnects: make(map[string]Target),
|
||||
}
|
||||
for peer := range cm.directPeers {
|
||||
status.DirectPeers = append(status.DirectPeers, peer)
|
||||
}
|
||||
|
||||
for address, target := range cm.targets {
|
||||
status.Reconnects[address] = *target
|
||||
}
|
||||
resultChan <- status
|
||||
return false
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
type ConnectionMakerStatus struct {
|
||||
DirectPeers []string
|
||||
Reconnects map[string]Target
|
||||
}
|
||||
|
||||
func (status ConnectionMakerStatus) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, "Direct Peers:")
|
||||
for _, peer := range status.DirectPeers {
|
||||
fmt.Fprintf(&buf, " %s", peer)
|
||||
}
|
||||
fmt.Fprintln(&buf, "\nReconnects:")
|
||||
for address, target := range status.Reconnects {
|
||||
fmt.Fprintf(&buf, "->[%s]", address)
|
||||
if target.lastError != nil {
|
||||
fmt.Fprintf(&buf, " (%s)", target.lastError)
|
||||
}
|
||||
if target.attempting {
|
||||
fmt.Fprintf(&buf, " trying since %v\n", target.tryAfter)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, " next try at %v\n", target.tryAfter)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
|
||||
timer := time.NewTimer(MaxDuration)
|
||||
run := func() { timer.Reset(cm.checkStateAndAttemptConnections()) }
|
||||
@@ -287,6 +231,9 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, dir
|
||||
delete(cm.targets, address)
|
||||
continue
|
||||
}
|
||||
if target.tryAfter.IsZero() {
|
||||
continue
|
||||
}
|
||||
switch duration := target.tryAfter.Sub(now); {
|
||||
case duration <= 0:
|
||||
target.attempting = true
|
||||
@@ -300,9 +247,9 @@ func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, dir
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) attemptConnection(address string, acceptNewPeer bool) {
|
||||
log.Printf("->[%s] attempting connection\n", address)
|
||||
log.Printf("->[%s] attempting connection", address)
|
||||
if err := cm.ourself.CreateConnection(address, acceptNewPeer); err != nil {
|
||||
log.Printf("->[%s] error during connection attempt: %v\n", address, err)
|
||||
log.Errorf("->[%s] error during connection attempt: %v", address, err)
|
||||
cm.ConnectionTerminated(address, err)
|
||||
}
|
||||
}
|
||||
@@ -315,6 +262,11 @@ func (t *Target) tryNow() {
|
||||
// The delay at the nth retry is a random value in the range
|
||||
// [i-i/2,i+i/2], where i = InitialInterval * 1.5^(n-1).
|
||||
func (t *Target) retry() {
|
||||
if t.lastError == ErrConnectToSelf {
|
||||
t.tryAfter = time.Time{}
|
||||
t.tryInterval = MaxInterval
|
||||
return
|
||||
}
|
||||
t.tryAfter = time.Now().Add(t.tryInterval/2 + time.Duration(rand.Int63n(int64(t.tryInterval))))
|
||||
t.tryInterval = t.tryInterval * 3 / 2
|
||||
if t.tryInterval > MaxInterval {
|
||||
|
||||
+1
-1
@@ -24,8 +24,8 @@ const (
|
||||
PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt
|
||||
MaxDuration = time.Duration(math.MaxInt64)
|
||||
MaxMissedHeartbeats = 6
|
||||
HeaderTimeout = 10 * time.Second
|
||||
HeartbeatTimeout = MaxMissedHeartbeats * SlowHeartbeat
|
||||
MaxTCPMsgSize = 10 * 1024 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
+74
-29
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/andybalholm/go-bit"
|
||||
@@ -31,7 +32,6 @@ func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte
|
||||
type Encryptor interface {
|
||||
FrameOverhead() int
|
||||
PacketOverhead() int
|
||||
IsEmpty() bool
|
||||
Bytes() ([]byte, error)
|
||||
AppendFrame(src []byte, dst []byte, frame []byte)
|
||||
TotalLen() int
|
||||
@@ -72,10 +72,6 @@ func (ne *NonEncryptor) FrameOverhead() int {
|
||||
return NameSize + NameSize + 2
|
||||
}
|
||||
|
||||
func (ne *NonEncryptor) IsEmpty() bool {
|
||||
return ne.buffered == ne.prefixLen
|
||||
}
|
||||
|
||||
func (ne *NonEncryptor) Bytes() ([]byte, error) {
|
||||
buf := ne.buf[:ne.buffered]
|
||||
ne.buffered = ne.prefixLen
|
||||
@@ -337,28 +333,48 @@ type TCPSender interface {
|
||||
Send([]byte) error
|
||||
}
|
||||
|
||||
type SimpleTCPSender struct {
|
||||
type GobTCPSender struct {
|
||||
encoder *gob.Encoder
|
||||
}
|
||||
|
||||
type LengthPrefixTCPSender struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
type EncryptedTCPSender struct {
|
||||
SimpleTCPSender
|
||||
sync.RWMutex
|
||||
state *TCPCryptoState
|
||||
sender TCPSender
|
||||
state *TCPCryptoState
|
||||
}
|
||||
|
||||
func NewSimpleTCPSender(encoder *gob.Encoder) *SimpleTCPSender {
|
||||
return &SimpleTCPSender{encoder: encoder}
|
||||
func NewGobTCPSender(encoder *gob.Encoder) *GobTCPSender {
|
||||
return &GobTCPSender{encoder: encoder}
|
||||
}
|
||||
|
||||
func (sender *SimpleTCPSender) Send(msg []byte) error {
|
||||
func (sender *GobTCPSender) Send(msg []byte) error {
|
||||
return sender.encoder.Encode(msg)
|
||||
}
|
||||
|
||||
func NewEncryptedTCPSender(encoder *gob.Encoder, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender {
|
||||
return &EncryptedTCPSender{
|
||||
SimpleTCPSender: *NewSimpleTCPSender(encoder),
|
||||
state: NewTCPCryptoState(sessionKey, outbound)}
|
||||
func NewLengthPrefixTCPSender(writer io.Writer) *LengthPrefixTCPSender {
|
||||
return &LengthPrefixTCPSender{writer: writer}
|
||||
}
|
||||
|
||||
func (sender *LengthPrefixTCPSender) Send(msg []byte) error {
|
||||
l := len(msg)
|
||||
if l > MaxTCPMsgSize {
|
||||
return fmt.Errorf("outgoing message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
|
||||
}
|
||||
// We copy the message so we can send it in a single Write
|
||||
// operation, thus making this thread-safe without locking.
|
||||
prefixedMsg := make([]byte, 4+l)
|
||||
binary.BigEndian.PutUint32(prefixedMsg, uint32(l))
|
||||
copy(prefixedMsg[4:], msg)
|
||||
_, err := sender.writer.Write(prefixedMsg)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewEncryptedTCPSender(sender TCPSender, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender {
|
||||
return &EncryptedTCPSender{sender: sender, state: NewTCPCryptoState(sessionKey, outbound)}
|
||||
}
|
||||
|
||||
func (sender *EncryptedTCPSender) Send(msg []byte) error {
|
||||
@@ -366,40 +382,69 @@ func (sender *EncryptedTCPSender) Send(msg []byte) error {
|
||||
defer sender.Unlock()
|
||||
encodedMsg := secretbox.Seal(nil, msg, &sender.state.nonce, sender.state.sessionKey)
|
||||
sender.state.advance()
|
||||
return sender.SimpleTCPSender.Send(encodedMsg)
|
||||
return sender.sender.Send(encodedMsg)
|
||||
}
|
||||
|
||||
type TCPReceiver interface {
|
||||
Decode([]byte) ([]byte, error)
|
||||
Receive() ([]byte, error)
|
||||
}
|
||||
|
||||
type SimpleTCPReceiver struct {
|
||||
type GobTCPReceiver struct {
|
||||
decoder *gob.Decoder
|
||||
}
|
||||
|
||||
type LengthPrefixTCPReceiver struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
type EncryptedTCPReceiver struct {
|
||||
SimpleTCPReceiver
|
||||
state *TCPCryptoState
|
||||
receiver TCPReceiver
|
||||
state *TCPCryptoState
|
||||
}
|
||||
|
||||
func NewSimpleTCPReceiver() *SimpleTCPReceiver {
|
||||
return &SimpleTCPReceiver{}
|
||||
func NewGobTCPReceiver(decoder *gob.Decoder) *GobTCPReceiver {
|
||||
return &GobTCPReceiver{decoder: decoder}
|
||||
}
|
||||
|
||||
func (receiver *SimpleTCPReceiver) Decode(msg []byte) ([]byte, error) {
|
||||
return msg, nil
|
||||
func (receiver *GobTCPReceiver) Receive() ([]byte, error) {
|
||||
var msg []byte
|
||||
err := receiver.decoder.Decode(&msg)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func NewEncryptedTCPReceiver(sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver {
|
||||
return &EncryptedTCPReceiver{
|
||||
SimpleTCPReceiver: *NewSimpleTCPReceiver(),
|
||||
state: NewTCPCryptoState(sessionKey, !outbound)}
|
||||
func NewLengthPrefixTCPReceiver(reader io.Reader) *LengthPrefixTCPReceiver {
|
||||
return &LengthPrefixTCPReceiver{reader: reader}
|
||||
}
|
||||
|
||||
func (receiver *EncryptedTCPReceiver) Decode(msg []byte) ([]byte, error) {
|
||||
func (receiver *LengthPrefixTCPReceiver) Receive() ([]byte, error) {
|
||||
lenPrefix := make([]byte, 4)
|
||||
if _, err := io.ReadFull(receiver.reader, lenPrefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := binary.BigEndian.Uint32(lenPrefix)
|
||||
if l > MaxTCPMsgSize {
|
||||
return nil, fmt.Errorf("incoming message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
|
||||
}
|
||||
msg := make([]byte, l)
|
||||
_, err := io.ReadFull(receiver.reader, msg)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func NewEncryptedTCPReceiver(receiver TCPReceiver, sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver {
|
||||
return &EncryptedTCPReceiver{receiver: receiver, state: NewTCPCryptoState(sessionKey, !outbound)}
|
||||
}
|
||||
|
||||
func (receiver *EncryptedTCPReceiver) Receive() ([]byte, error) {
|
||||
msg, err := receiver.receiver.Receive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodedMsg, success := secretbox.Open(nil, msg, &receiver.state.nonce, receiver.state.sessionKey)
|
||||
if !success {
|
||||
return nil, fmt.Errorf("Unable to decrypt TCP msg")
|
||||
}
|
||||
|
||||
receiver.state.advance()
|
||||
return decodedMsg, nil
|
||||
}
|
||||
+25
-18
@@ -2,7 +2,6 @@ package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
@@ -10,20 +9,24 @@ import (
|
||||
)
|
||||
|
||||
type EthernetDecoder struct {
|
||||
eth layers.Ethernet
|
||||
ip layers.IPv4
|
||||
Eth layers.Ethernet
|
||||
IP layers.IPv4
|
||||
decoded []gopacket.LayerType
|
||||
parser *gopacket.DecodingLayerParser
|
||||
}
|
||||
|
||||
func NewEthernetDecoder() *EthernetDecoder {
|
||||
dec := &EthernetDecoder{}
|
||||
dec.parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &dec.eth, &dec.ip)
|
||||
dec.parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &dec.Eth, &dec.IP)
|
||||
return dec
|
||||
}
|
||||
|
||||
func (dec *EthernetDecoder) DecodeLayers(data []byte) error {
|
||||
return dec.parser.DecodeLayers(data, &dec.decoded)
|
||||
func (dec *EthernetDecoder) DecodeLayers(data []byte) {
|
||||
// We intentionally discard the error return here, because it
|
||||
// is normal for gopacket to return an error saying that it
|
||||
// cannot decode a layer beyond the ones we specified when
|
||||
// setting up the parser.
|
||||
dec.parser.DecodeLayers(data, &dec.decoded)
|
||||
}
|
||||
|
||||
func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) error) error {
|
||||
@@ -31,23 +34,23 @@ func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) e
|
||||
opts := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true}
|
||||
ipHeaderSize := int(dec.ip.IHL) * 4 // IHL is the number of 32-byte words in the header
|
||||
payload := gopacket.Payload(dec.ip.BaseLayer.Contents[:ipHeaderSize+8])
|
||||
ipHeaderSize := int(dec.IP.IHL) * 4 // IHL is the number of 32-byte words in the header
|
||||
payload := gopacket.Payload(dec.IP.BaseLayer.Contents[:ipHeaderSize+8])
|
||||
err := gopacket.SerializeLayers(buf, opts,
|
||||
&layers.Ethernet{
|
||||
SrcMAC: dec.eth.DstMAC,
|
||||
DstMAC: dec.eth.SrcMAC,
|
||||
EthernetType: dec.eth.EthernetType},
|
||||
SrcMAC: dec.Eth.DstMAC,
|
||||
DstMAC: dec.Eth.SrcMAC,
|
||||
EthernetType: dec.Eth.EthernetType},
|
||||
&layers.IPv4{
|
||||
Version: 4,
|
||||
TOS: dec.ip.TOS,
|
||||
TOS: dec.IP.TOS,
|
||||
Id: 0,
|
||||
Flags: 0,
|
||||
FragOffset: 0,
|
||||
TTL: 64,
|
||||
Protocol: layers.IPProtocolICMPv4,
|
||||
DstIP: dec.ip.SrcIP,
|
||||
SrcIP: dec.ip.DstIP},
|
||||
DstIP: dec.IP.SrcIP,
|
||||
SrcIP: dec.IP.DstIP},
|
||||
&layers.ICMPv4{
|
||||
TypeCode: 0x304,
|
||||
Id: 0,
|
||||
@@ -57,7 +60,7 @@ func (dec *EthernetDecoder) sendICMPFragNeeded(mtu int, sendFrame func([]byte) e
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU= %v\n", dec.ip.DstIP, dec.ip.SrcIP, mtu)
|
||||
log.Printf("Sending ICMP 3,4 (%v -> %v): PMTU= %v", dec.IP.DstIP, dec.IP.SrcIP, mtu)
|
||||
return sendFrame(buf.Bytes())
|
||||
}
|
||||
|
||||
@@ -68,10 +71,14 @@ var (
|
||||
)
|
||||
|
||||
func (dec *EthernetDecoder) DropFrame() bool {
|
||||
return bytes.Equal(stpMACPrefix, dec.eth.DstMAC[:len(stpMACPrefix)])
|
||||
return bytes.Equal(stpMACPrefix, dec.Eth.DstMAC[:len(stpMACPrefix)])
|
||||
}
|
||||
|
||||
func (dec *EthernetDecoder) IsSpecial() bool {
|
||||
return dec.eth.Length == 0 && dec.eth.EthernetType == layers.EthernetTypeLLC &&
|
||||
bytes.Equal(zeroMAC, dec.eth.SrcMAC) && bytes.Equal(zeroMAC, dec.eth.DstMAC)
|
||||
return dec.Eth.Length == 0 && dec.Eth.EthernetType == layers.EthernetTypeLLC &&
|
||||
bytes.Equal(zeroMAC, dec.Eth.SrcMAC) && bytes.Equal(zeroMAC, dec.Eth.DstMAC)
|
||||
}
|
||||
|
||||
func (dec *EthernetDecoder) DF() bool {
|
||||
return len(dec.decoded) == 2 && (dec.IP.Flags&layers.IPv4DontFragment != 0)
|
||||
}
|
||||
+35
-31
@@ -40,16 +40,12 @@ func (conn *LocalConnection) ensureForwarders() error {
|
||||
|
||||
forwarder := NewForwarder(conn, encryptor, udpSender, DefaultPMTU)
|
||||
forwarderDF := NewForwarderDF(conn, encryptorDF, udpSenderDF, DefaultPMTU)
|
||||
effectivePMTU := forwarderDF.unverifiedPMTU
|
||||
forwarder.Start()
|
||||
forwarderDF.Start()
|
||||
|
||||
// Various fields in the conn struct are read by other processes,
|
||||
// so we have to use locks.
|
||||
conn.Lock()
|
||||
conn.forwarder = forwarder
|
||||
conn.forwarderDF = forwarderDF
|
||||
conn.effectivePMTU = effectivePMTU
|
||||
conn.Unlock()
|
||||
|
||||
return nil
|
||||
@@ -63,11 +59,23 @@ func (conn *LocalConnection) stopForwarders() {
|
||||
conn.forwarderDF.Shutdown()
|
||||
}
|
||||
|
||||
// Called from peer.Relay[Broadcast] which is itself invoked from
|
||||
// router (both UDP listener process and sniffer process). Also called
|
||||
// from connection's heartbeat process, and from the connection's TCP
|
||||
// receiver process.
|
||||
func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *EthernetDecoder) error {
|
||||
// Called from connection's actor process, and from the connection's
|
||||
// TCP receiver process.
|
||||
func (conn *LocalConnection) Send(df bool, frameBytes []byte) error {
|
||||
frame := &ForwardedFrame{
|
||||
srcPeer: conn.local,
|
||||
dstPeer: conn.remote,
|
||||
frame: frameBytes}
|
||||
return conn.forward(frame, nil, df)
|
||||
}
|
||||
|
||||
// Called from LocalPeer.Relay[Broadcast] which is itself invoked from
|
||||
// router (both UDP listener process and sniffer process).
|
||||
func (conn *LocalConnection) Forward(frame *ForwardedFrame, dec *EthernetDecoder) error {
|
||||
return conn.forward(frame, dec, dec != nil && dec.DF())
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) forward(frame *ForwardedFrame, dec *EthernetDecoder, df bool) error {
|
||||
conn.RLock()
|
||||
var (
|
||||
forwarder = conn.forwarder
|
||||
@@ -81,6 +89,7 @@ func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *Ethern
|
||||
conn.Log("Cannot forward frame yet - awaiting contact")
|
||||
return nil
|
||||
}
|
||||
|
||||
// We could use non-blocking channel sends here, i.e. drop frames
|
||||
// on the floor when the forwarder is busy. This would allow our
|
||||
// caller - the capturing loop in the router - to read frames more
|
||||
@@ -109,11 +118,10 @@ func (conn *LocalConnection) Forward(df bool, frame *ForwardedFrame, dec *Ethern
|
||||
forwarderDF.Forward(frame)
|
||||
return nil
|
||||
}
|
||||
conn.Router.LogFrame("Fragmenting", frame.frame, &dec.eth)
|
||||
// We can't trust the stack to fragment, we have IP, and we
|
||||
// have a frame that's too big for the MTU, so we have to
|
||||
// fragment it ourself.
|
||||
return fragment(dec.eth, dec.ip, effectivePMTU, frame, func(segFrame *ForwardedFrame) {
|
||||
return fragment(dec.Eth, dec.IP, effectivePMTU, frame, func(segFrame *ForwardedFrame) {
|
||||
forwarderDF.Forward(segFrame)
|
||||
})
|
||||
}
|
||||
@@ -195,20 +203,18 @@ type Forwarder struct {
|
||||
}
|
||||
|
||||
func NewForwarder(conn *LocalConnection, enc Encryptor, udpSender UDPSender, pmtu int) *Forwarder {
|
||||
return &Forwarder{
|
||||
ch := make(chan *ForwardedFrame, ChannelSize)
|
||||
finished := make(chan struct{})
|
||||
fwd := &Forwarder{
|
||||
conn: conn,
|
||||
ch: ch,
|
||||
finished: finished,
|
||||
enc: enc,
|
||||
udpSender: udpSender,
|
||||
maxPayload: pmtu - UDPOverhead,
|
||||
processSendError: func(err error) error { return err }}
|
||||
}
|
||||
|
||||
func (fwd *Forwarder) Start() {
|
||||
ch := make(chan *ForwardedFrame, ChannelSize)
|
||||
fwd.ch = ch
|
||||
finished := make(chan struct{})
|
||||
fwd.finished = finished
|
||||
go fwd.run(ch, finished)
|
||||
return fwd
|
||||
}
|
||||
|
||||
func (fwd *Forwarder) Shutdown() {
|
||||
@@ -275,7 +281,7 @@ func (fwd *Forwarder) accumulateAndSendFrames(ch <-chan *ForwardedFrame, frame *
|
||||
}
|
||||
|
||||
func (fwd *Forwarder) logDrop(frame *ForwardedFrame) {
|
||||
fwd.conn.Log("Dropping too big frame during forwarding: frame len:", len(frame.frame), "; effective PMTU:", fwd.maxPayload+UDPOverhead-fwd.effectiveOverhead())
|
||||
fwd.conn.ErrorLog("Dropping too big frame during forwarding: frame len:", len(frame.frame), "; effective PMTU:", fwd.maxPayload+UDPOverhead-fwd.effectiveOverhead())
|
||||
}
|
||||
|
||||
func (fwd *Forwarder) appendFrame(frame *ForwardedFrame) bool {
|
||||
@@ -310,25 +316,23 @@ type ForwarderDF struct {
|
||||
}
|
||||
|
||||
func NewForwarderDF(conn *LocalConnection, enc Encryptor, udpSender UDPSender, pmtu int) *ForwarderDF {
|
||||
ch := make(chan *ForwardedFrame, ChannelSize)
|
||||
finished := make(chan struct{})
|
||||
verifyPMTU := make(chan int, ChannelSize)
|
||||
fwd := &ForwarderDF{
|
||||
Forwarder: Forwarder{
|
||||
conn: conn,
|
||||
ch: ch,
|
||||
finished: finished,
|
||||
enc: enc,
|
||||
udpSender: udpSender,
|
||||
maxPayload: pmtu - UDPOverhead}}
|
||||
maxPayload: pmtu - UDPOverhead},
|
||||
verifyPMTU: verifyPMTU}
|
||||
fwd.Forwarder.processSendError = fwd.processSendError
|
||||
fwd.unverifiedPMTU = pmtu - fwd.effectiveOverhead()
|
||||
return fwd
|
||||
}
|
||||
|
||||
func (fwd *ForwarderDF) Start() {
|
||||
ch := make(chan *ForwardedFrame, ChannelSize)
|
||||
fwd.ch = ch
|
||||
finished := make(chan struct{})
|
||||
fwd.finished = finished
|
||||
verifyPMTU := make(chan int, ChannelSize)
|
||||
fwd.verifyPMTU = verifyPMTU
|
||||
conn.setEffectivePMTU(fwd.unverifiedPMTU)
|
||||
go fwd.run(ch, finished, verifyPMTU)
|
||||
return fwd
|
||||
}
|
||||
|
||||
func (fwd *ForwarderDF) PMTUVerified(pmtu int) {
|
||||
|
||||
+9
-7
@@ -21,7 +21,7 @@ type Gossiper interface {
|
||||
OnGossipUnicast(sender PeerName, msg []byte) error
|
||||
// merge received data into state and return a representation of
|
||||
// the received data, for further propagation
|
||||
OnGossipBroadcast(update []byte) (GossipData, error)
|
||||
OnGossipBroadcast(sender PeerName, update []byte) (GossipData, error)
|
||||
// return state of everything we know; gets called periodically
|
||||
Gossip() GossipData
|
||||
// merge received data into state and return "everything new I've
|
||||
@@ -40,13 +40,15 @@ type GossipSender struct {
|
||||
}
|
||||
|
||||
func NewGossipSender(send func(GossipData)) *GossipSender {
|
||||
return &GossipSender{send: send}
|
||||
}
|
||||
|
||||
func (sender *GossipSender) Start() {
|
||||
sender.cell = make(chan GossipData, 1)
|
||||
sender.flushch = make(chan chan bool)
|
||||
cell := make(chan GossipData, 1)
|
||||
flushch := make(chan chan bool)
|
||||
sender := &GossipSender{
|
||||
send: send,
|
||||
cell: cell,
|
||||
flushch: flushch,
|
||||
}
|
||||
go sender.run()
|
||||
return sender
|
||||
}
|
||||
|
||||
func (sender *GossipSender) run() {
|
||||
|
||||
Loaded 100 of 197 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user