]>
Commit | Line | Data |
---|---|---|
6a8d3f1c OZ |
1 | /* |
2 | * BIRD -- Bidirectional Forwarding Detection (BFD) | |
3 | * | |
4 | * Can be freely distributed and used under the terms of the GNU GPL. | |
5 | */ | |
bf139664 | 6 | |
6a8d3f1c | 7 | #include "bfd.h" |
e03dc6a9 | 8 | #include "lib/mac.h" |
bf139664 OZ |
9 | |
10 | ||
11 | struct bfd_ctl_packet | |
12 | { | |
e03dc6a9 OZ |
13 | u8 vdiag; /* Version and diagnostic */ |
14 | u8 flags; /* State and flags */ | |
bf139664 | 15 | u8 detect_mult; |
e03dc6a9 OZ |
16 | u8 length; /* Whole packet length */ |
17 | u32 snd_id; /* Sender ID, aka 'my discriminator' */ | |
18 | u32 rcv_id; /* Receiver ID, aka 'your discriminator' */ | |
bf139664 OZ |
19 | u32 des_min_tx_int; |
20 | u32 req_min_rx_int; | |
21 | u32 req_min_echo_rx_int; | |
22 | }; | |
23 | ||
e03dc6a9 OZ |
24 | struct bfd_auth |
25 | { | |
26 | u8 type; /* Authentication type (BFD_AUTH_*) */ | |
27 | u8 length; /* Authentication section length */ | |
28 | }; | |
29 | ||
30 | struct bfd_simple_auth | |
31 | { | |
32 | u8 type; /* BFD_AUTH_SIMPLE */ | |
33 | u8 length; /* Length of bfd_simple_auth + pasword length */ | |
34 | u8 key_id; /* Key ID */ | |
35 | byte password[0]; /* Password itself, variable length */ | |
36 | }; | |
37 | ||
38 | #define BFD_MAX_PASSWORD_LENGTH 16 | |
39 | ||
40 | struct bfd_crypto_auth | |
41 | { | |
42 | u8 type; /* BFD_AUTH_*_MD5 or BFD_AUTH_*_SHA1 */ | |
43 | u8 length; /* Length of bfd_crypto_auth + hash length */ | |
44 | u8 key_id; /* Key ID */ | |
45 | u8 zero; /* Reserved, zero on transmit */ | |
46 | u32 csn; /* Cryptographic sequence number */ | |
47 | byte data[0]; /* Authentication key/hash, length 16 or 20 */ | |
48 | }; | |
49 | ||
0e175f9f OZ |
50 | #define BFD_BASE_LEN sizeof(struct bfd_ctl_packet) |
51 | #define BFD_MAX_LEN 64 | |
bf139664 | 52 | |
e03dc6a9 OZ |
53 | #define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0) |
54 | ||
55 | #define LOG_PKT(msg, args...) \ | |
56 | log(L_REMOTE "%s: " msg, p->p.name, args) | |
57 | ||
58 | #define LOG_PKT_AUTH(msg, args...) \ | |
59 | log(L_AUTH "%s: " msg, p->p.name, args) | |
60 | ||
61 | ||
6a8d3f1c | 62 | static inline u8 bfd_pack_vdiag(u8 version, u8 diag) |
bf139664 OZ |
63 | { return (version << 5) | diag; } |
64 | ||
6a8d3f1c OZ |
65 | static inline u8 bfd_pack_flags(u8 state, u8 flags) |
66 | { return (state << 6) | flags; } | |
bf139664 OZ |
67 | |
68 | static inline u8 bfd_pkt_get_version(struct bfd_ctl_packet *pkt) | |
69 | { return pkt->vdiag >> 5; } | |
70 | ||
71 | static inline u8 bfd_pkt_get_diag(struct bfd_ctl_packet *pkt) | |
8ce9a877 | 72 | { return pkt->vdiag & 0x1f; } |
bf139664 OZ |
73 | |
74 | ||
75 | static inline u8 bfd_pkt_get_state(struct bfd_ctl_packet *pkt) | |
76 | { return pkt->flags >> 6; } | |
77 | ||
3e236955 | 78 | static inline void UNUSED bfd_pkt_set_state(struct bfd_ctl_packet *pkt, u8 val) |
bf139664 OZ |
79 | { pkt->flags = val << 6; } |
80 | ||
81 | ||
0e175f9f OZ |
82 | char * |
83 | bfd_format_flags(u8 flags, char *buf) | |
84 | { | |
85 | char *bp = buf; | |
86 | if (flags & BFD_FLAGS) *bp++ = ' '; | |
87 | if (flags & BFD_FLAG_POLL) *bp++ = 'P'; | |
88 | if (flags & BFD_FLAG_FINAL) *bp++ = 'F'; | |
89 | if (flags & BFD_FLAG_CPI) *bp++ = 'C'; | |
90 | if (flags & BFD_FLAG_AP) *bp++ = 'A'; | |
91 | if (flags & BFD_FLAG_DEMAND) *bp++ = 'D'; | |
92 | if (flags & BFD_FLAG_MULTIPOINT) *bp++ = 'M'; | |
93 | *bp = 0; | |
94 | ||
95 | return buf; | |
96 | } | |
97 | ||
e03dc6a9 OZ |
98 | const u8 bfd_auth_type_to_hash_alg[] = { |
99 | [BFD_AUTH_NONE] = ALG_UNDEFINED, | |
100 | [BFD_AUTH_SIMPLE] = ALG_UNDEFINED, | |
101 | [BFD_AUTH_KEYED_MD5] = ALG_MD5, | |
102 | [BFD_AUTH_METICULOUS_KEYED_MD5] = ALG_MD5, | |
103 | [BFD_AUTH_KEYED_SHA1] = ALG_SHA1, | |
104 | [BFD_AUTH_METICULOUS_KEYED_SHA1] = ALG_SHA1, | |
105 | }; | |
106 | ||
107 | ||
108 | /* Fill authentication section and modifies final length in control section packet */ | |
109 | static void | |
110 | bfd_fill_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt) | |
111 | { | |
112 | struct bfd_iface_config *cf = s->ifa->cf; | |
113 | struct password_item *pass = password_find(cf->passwords, 0); | |
114 | uint meticulous = 0; | |
115 | ||
116 | if (!pass) | |
117 | { | |
118 | /* FIXME: This should not happen */ | |
119 | log(L_ERR "%s: No suitable password found for authentication", p->p.name); | |
120 | return; | |
121 | } | |
122 | ||
123 | switch (cf->auth_type) | |
124 | { | |
125 | case BFD_AUTH_SIMPLE: | |
126 | { | |
127 | struct bfd_simple_auth *auth = (void *) (pkt + 1); | |
128 | uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH); | |
129 | ||
130 | auth->type = BFD_AUTH_SIMPLE; | |
131 | auth->length = sizeof(struct bfd_simple_auth) + pass_len; | |
132 | auth->key_id = pass->id; | |
133 | ||
134 | pkt->flags |= BFD_FLAG_AP; | |
135 | pkt->length += auth->length; | |
136 | ||
137 | memcpy(auth->password, pass->password, pass_len); | |
138 | return; | |
139 | } | |
140 | ||
141 | case BFD_AUTH_METICULOUS_KEYED_MD5: | |
142 | case BFD_AUTH_METICULOUS_KEYED_SHA1: | |
143 | meticulous = 1; | |
144 | ||
145 | case BFD_AUTH_KEYED_MD5: | |
146 | case BFD_AUTH_KEYED_SHA1: | |
147 | { | |
148 | struct bfd_crypto_auth *auth = (void *) (pkt + 1); | |
149 | uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type]; | |
150 | uint hash_len = mac_type_length(pass->alg); | |
151 | ||
152 | /* Increase CSN about one time per second */ | |
153 | u32 new_time = (u64) current_time() >> 20; | |
154 | if ((new_time != s->tx_csn_time) || meticulous) | |
155 | { | |
156 | s->tx_csn++; | |
157 | s->tx_csn_time = new_time; | |
158 | } | |
159 | ||
160 | DBG("[%I] CSN: %u\n", s->addr, s->last_tx_csn); | |
161 | ||
162 | auth->type = cf->auth_type; | |
163 | auth->length = sizeof(struct bfd_crypto_auth) + hash_len; | |
164 | auth->key_id = pass->id; | |
165 | auth->zero = 0; | |
166 | auth->csn = htonl(s->tx_csn); | |
167 | ||
168 | pkt->flags |= BFD_FLAG_AP; | |
169 | pkt->length += auth->length; | |
170 | ||
171 | strncpy(auth->data, pass->password, hash_len); | |
172 | mac_fill(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth->data); | |
173 | return; | |
174 | } | |
175 | } | |
176 | } | |
177 | ||
178 | static int | |
179 | bfd_check_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt) | |
180 | { | |
181 | struct bfd_iface_config *cf = s->ifa->cf; | |
182 | const char *err_dsc = NULL; | |
183 | uint err_val = 0; | |
184 | uint auth_type = 0; | |
185 | uint meticulous = 0; | |
186 | ||
187 | if (pkt->flags & BFD_FLAG_AP) | |
188 | { | |
189 | struct bfd_auth *auth = (void *) (pkt + 1); | |
190 | ||
191 | if ((pkt->length < (BFD_BASE_LEN + sizeof(struct bfd_auth))) || | |
192 | (pkt->length < (BFD_BASE_LEN + auth->length))) | |
193 | DROP("packet length mismatch", pkt->length); | |
194 | ||
195 | /* Zero is reserved, we use it as BFD_AUTH_NONE internally */ | |
196 | if (auth->type == 0) | |
197 | DROP("reserved authentication type", 0); | |
198 | ||
199 | auth_type = auth->type; | |
200 | } | |
201 | ||
202 | if (auth_type != cf->auth_type) | |
203 | DROP("authentication method mismatch", auth_type); | |
204 | ||
205 | switch (auth_type) | |
206 | { | |
207 | case BFD_AUTH_NONE: | |
208 | return 1; | |
209 | ||
210 | case BFD_AUTH_SIMPLE: | |
211 | { | |
212 | struct bfd_simple_auth *auth = (void *) (pkt + 1); | |
213 | ||
214 | if (auth->length < sizeof(struct bfd_simple_auth)) | |
215 | DROP("wrong authentication length", auth->length); | |
216 | ||
217 | struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id); | |
218 | if (!pass) | |
219 | DROP("no suitable password found", auth->key_id); | |
220 | ||
221 | uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH); | |
222 | uint auth_len = sizeof(struct bfd_simple_auth) + pass_len; | |
223 | ||
224 | if ((auth->length != auth_len) || memcmp(auth->password, pass->password, pass_len)) | |
225 | DROP("wrong password", pass->id); | |
226 | ||
227 | return 1; | |
228 | } | |
229 | ||
230 | case BFD_AUTH_METICULOUS_KEYED_MD5: | |
231 | case BFD_AUTH_METICULOUS_KEYED_SHA1: | |
232 | meticulous = 1; | |
233 | ||
234 | case BFD_AUTH_KEYED_MD5: | |
235 | case BFD_AUTH_KEYED_SHA1: | |
236 | { | |
237 | struct bfd_crypto_auth *auth = (void *) (pkt + 1); | |
238 | uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type]; | |
239 | uint hash_len = mac_type_length(hash_alg); | |
240 | ||
241 | if (auth->length != (sizeof(struct bfd_crypto_auth) + hash_len)) | |
242 | DROP("wrong authentication length", auth->length); | |
243 | ||
244 | struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id); | |
245 | if (!pass) | |
246 | DROP("no suitable password found", auth->key_id); | |
247 | ||
248 | /* BFD CSNs are in 32-bit circular number space */ | |
249 | u32 csn = ntohl(auth->csn); | |
250 | if (s->rx_csn_known && | |
734e9fb8 | 251 | (((csn - s->rx_csn) > (3 * (uint) s->detect_mult)) || |
e03dc6a9 OZ |
252 | (meticulous && (csn == s->rx_csn)))) |
253 | { | |
254 | /* We want to report both new and old CSN */ | |
255 | LOG_PKT_AUTH("Authentication failed for %I - " | |
256 | "wrong sequence number (rcv %u, old %u)", | |
257 | s->addr, csn, s->rx_csn); | |
258 | return 0; | |
259 | } | |
260 | ||
261 | byte *auth_data = alloca(hash_len); | |
262 | memcpy(auth_data, auth->data, hash_len); | |
263 | strncpy(auth->data, pass->password, hash_len); | |
264 | ||
265 | if (!mac_verify(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth_data)) | |
266 | DROP("wrong authentication code", pass->id); | |
267 | ||
268 | s->rx_csn = csn; | |
269 | s->rx_csn_known = 1; | |
270 | ||
271 | return 1; | |
272 | } | |
273 | } | |
274 | ||
275 | drop: | |
276 | LOG_PKT_AUTH("Authentication failed for %I - %s (%u)", | |
277 | s->addr, err_dsc, err_val); | |
278 | return 0; | |
279 | } | |
280 | ||
bf139664 OZ |
281 | void |
282 | bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final) | |
283 | { | |
1ec52253 | 284 | sock *sk = s->ifa->sk; |
ffa398b8 | 285 | struct bfd_ctl_packet *pkt; |
0e175f9f | 286 | char fb[8]; |
bf139664 | 287 | |
ffa398b8 OZ |
288 | if (!sk) |
289 | return; | |
290 | ||
291 | pkt = (struct bfd_ctl_packet *) sk->tbuf; | |
bf139664 OZ |
292 | pkt->vdiag = bfd_pack_vdiag(1, s->loc_diag); |
293 | pkt->flags = bfd_pack_flags(s->loc_state, 0); | |
294 | pkt->detect_mult = s->detect_mult; | |
6a8d3f1c | 295 | pkt->length = BFD_BASE_LEN; |
bf139664 OZ |
296 | pkt->snd_id = htonl(s->loc_id); |
297 | pkt->rcv_id = htonl(s->rem_id); | |
6a8d3f1c OZ |
298 | pkt->des_min_tx_int = htonl(s->des_min_tx_new); |
299 | pkt->req_min_rx_int = htonl(s->req_min_rx_new); | |
bf139664 OZ |
300 | pkt->req_min_echo_rx_int = 0; |
301 | ||
302 | if (final) | |
303 | pkt->flags |= BFD_FLAG_FINAL; | |
304 | else if (s->poll_active) | |
305 | pkt->flags |= BFD_FLAG_POLL; | |
306 | ||
e03dc6a9 OZ |
307 | if (s->ifa->cf->auth_type) |
308 | bfd_fill_authentication(p, s, pkt); | |
309 | ||
6a8d3f1c | 310 | if (sk->tbuf != sk->tpos) |
0e175f9f OZ |
311 | log(L_WARN "%s: Old packet overwritten in TX buffer", p->p.name); |
312 | ||
313 | TRACE(D_PACKETS, "Sending CTL to %I [%s%s]", s->addr, | |
314 | bfd_state_names[s->loc_state], bfd_format_flags(pkt->flags, fb)); | |
6a8d3f1c OZ |
315 | |
316 | sk_send_to(sk, pkt->length, s->addr, sk->dport); | |
bf139664 OZ |
317 | } |
318 | ||
6a8d3f1c | 319 | static int |
3e236955 | 320 | bfd_rx_hook(sock *sk, uint len) |
bf139664 | 321 | { |
6a8d3f1c OZ |
322 | struct bfd_proto *p = sk->data; |
323 | struct bfd_ctl_packet *pkt = (struct bfd_ctl_packet *) sk->rbuf; | |
324 | const char *err_dsc = NULL; | |
325 | uint err_val = 0; | |
0e175f9f OZ |
326 | char fb[8]; |
327 | ||
05476c4d OZ |
328 | if ((sk->sport == BFD_CONTROL_PORT) && (sk->rcv_ttl < 255)) |
329 | DROP("wrong TTL", sk->rcv_ttl); | |
bf139664 OZ |
330 | |
331 | if (len < BFD_BASE_LEN) | |
332 | DROP("too short", len); | |
333 | ||
334 | u8 version = bfd_pkt_get_version(pkt); | |
335 | if (version != 1) | |
336 | DROP("version mismatch", version); | |
337 | ||
338 | if ((pkt->length < BFD_BASE_LEN) || (pkt->length > len)) | |
339 | DROP("length mismatch", pkt->length); | |
340 | ||
341 | if (pkt->detect_mult == 0) | |
342 | DROP("invalid detect mult", 0); | |
343 | ||
0e175f9f OZ |
344 | if ((pkt->flags & BFD_FLAG_MULTIPOINT) || |
345 | ((pkt->flags & BFD_FLAG_POLL) && (pkt->flags & BFD_FLAG_FINAL))) | |
bf139664 OZ |
346 | DROP("invalid flags", pkt->flags); |
347 | ||
348 | if (pkt->snd_id == 0) | |
349 | DROP("invalid my discriminator", 0); | |
350 | ||
351 | struct bfd_session *s; | |
352 | u32 id = ntohl(pkt->rcv_id); | |
353 | ||
354 | if (id) | |
355 | { | |
356 | s = bfd_find_session_by_id(p, id); | |
357 | ||
358 | if (!s) | |
0e175f9f | 359 | DROP("unknown session id", id); |
bf139664 OZ |
360 | } |
361 | else | |
362 | { | |
363 | u8 ps = bfd_pkt_get_state(pkt); | |
364 | if (ps > BFD_STATE_DOWN) | |
365 | DROP("invalid init state", ps); | |
ffa398b8 | 366 | |
6a8d3f1c | 367 | s = bfd_find_session_by_addr(p, sk->faddr); |
bf139664 OZ |
368 | |
369 | /* FIXME: better session matching and message */ | |
1ec52253 | 370 | if (!s) |
6a8d3f1c | 371 | return 1; |
bf139664 OZ |
372 | } |
373 | ||
e03dc6a9 OZ |
374 | /* bfd_check_authentication() has its own error logging */ |
375 | if (!bfd_check_authentication(p, s, pkt)) | |
376 | return 1; | |
bf139664 | 377 | |
6a8d3f1c OZ |
378 | u32 old_tx_int = s->des_min_tx_int; |
379 | u32 old_rx_int = s->rem_min_rx_int; | |
bf139664 | 380 | |
1ec52253 | 381 | s->rem_id= ntohl(pkt->snd_id); |
bf139664 | 382 | s->rem_state = bfd_pkt_get_state(pkt); |
6a8d3f1c | 383 | s->rem_diag = bfd_pkt_get_diag(pkt); |
bf139664 OZ |
384 | s->rem_demand_mode = pkt->flags & BFD_FLAG_DEMAND; |
385 | s->rem_min_tx_int = ntohl(pkt->des_min_tx_int); | |
386 | s->rem_min_rx_int = ntohl(pkt->req_min_rx_int); | |
387 | s->rem_detect_mult = pkt->detect_mult; | |
388 | ||
0e175f9f OZ |
389 | TRACE(D_PACKETS, "CTL received from %I [%s%s]", sk->faddr, |
390 | bfd_state_names[s->rem_state], bfd_format_flags(pkt->flags, fb)); | |
391 | ||
6a8d3f1c | 392 | bfd_session_process_ctl(s, pkt->flags, old_tx_int, old_rx_int); |
bf139664 OZ |
393 | return 1; |
394 | ||
e03dc6a9 OZ |
395 | drop: |
396 | LOG_PKT("Bad packet from %I - %s (%u)", sk->faddr, err_dsc, err_val); | |
bf139664 OZ |
397 | return 1; |
398 | } | |
399 | ||
6a8d3f1c OZ |
400 | static void |
401 | bfd_err_hook(sock *sk, int err) | |
402 | { | |
403 | struct bfd_proto *p = sk->data; | |
404 | log(L_ERR "%s: Socket error: %m", p->p.name, err); | |
405 | } | |
406 | ||
bf139664 | 407 | sock * |
08b3a24d | 408 | bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af) |
bf139664 | 409 | { |
6a8d3f1c | 410 | sock *sk = sk_new(p->tpool); |
bf139664 | 411 | sk->type = SK_UDP; |
08b3a24d | 412 | sk->subtype = af; |
bf139664 OZ |
413 | sk->sport = !multihop ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT; |
414 | sk->data = p; | |
415 | ||
0e175f9f | 416 | sk->rbsize = BFD_MAX_LEN; |
bf139664 OZ |
417 | sk->rx_hook = bfd_rx_hook; |
418 | sk->err_hook = bfd_err_hook; | |
6a8d3f1c OZ |
419 | |
420 | /* TODO: configurable ToS and priority */ | |
421 | sk->tos = IP_PREC_INTERNET_CONTROL; | |
422 | sk->priority = sk_priority_control; | |
423 | sk->flags = SKF_THREAD | SKF_LADDR_RX | (!multihop ? SKF_TTL_RX : 0); | |
424 | ||
bf139664 OZ |
425 | if (sk_open(sk) < 0) |
426 | goto err; | |
6a8d3f1c OZ |
427 | |
428 | sk_start(sk); | |
429 | return sk; | |
430 | ||
431 | err: | |
05476c4d | 432 | sk_log_error(sk, p->p.name); |
6a8d3f1c OZ |
433 | rfree(sk); |
434 | return NULL; | |
bf139664 OZ |
435 | } |
436 | ||
1ec52253 | 437 | sock * |
bf139664 OZ |
438 | bfd_open_tx_sk(struct bfd_proto *p, ip_addr local, struct iface *ifa) |
439 | { | |
6a8d3f1c | 440 | sock *sk = sk_new(p->tpool); |
bf139664 OZ |
441 | sk->type = SK_UDP; |
442 | sk->saddr = local; | |
6a8d3f1c OZ |
443 | sk->dport = ifa ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT; |
444 | sk->iface = ifa; | |
bf139664 OZ |
445 | sk->data = p; |
446 | ||
0e175f9f | 447 | sk->tbsize = BFD_MAX_LEN; |
bf139664 | 448 | sk->err_hook = bfd_err_hook; |
bf139664 | 449 | |
6a8d3f1c OZ |
450 | /* TODO: configurable ToS, priority and TTL security */ |
451 | sk->tos = IP_PREC_INTERNET_CONTROL; | |
452 | sk->priority = sk_priority_control; | |
453 | sk->ttl = ifa ? 255 : -1; | |
9c89560e | 454 | sk->flags = SKF_THREAD | SKF_BIND | SKF_HIGH_PORT; |
6a8d3f1c | 455 | |
bf139664 OZ |
456 | if (sk_open(sk) < 0) |
457 | goto err; | |
458 | ||
6a8d3f1c OZ |
459 | sk_start(sk); |
460 | return sk; | |
461 | ||
462 | err: | |
05476c4d | 463 | sk_log_error(sk, p->p.name); |
6a8d3f1c OZ |
464 | rfree(sk); |
465 | return NULL; | |
bf139664 | 466 | } |