/src/openvswitch/lib/tnl-neigh-cache.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2014, 2015, 2016 Nicira, Inc. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at: |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <config.h> |
18 | | |
19 | | #include "tnl-neigh-cache.h" |
20 | | |
21 | | #include <inttypes.h> |
22 | | #include <sys/types.h> |
23 | | #include <netinet/in.h> |
24 | | #include <netinet/icmp6.h> |
25 | | #include <stdlib.h> |
26 | | |
27 | | #include "bitmap.h" |
28 | | #include "cmap.h" |
29 | | #include "coverage.h" |
30 | | #include "dpif-netdev.h" |
31 | | #include "openvswitch/dynamic-string.h" |
32 | | #include "errno.h" |
33 | | #include "flow.h" |
34 | | #include "netdev.h" |
35 | | #include "ovs-atomic.h" |
36 | | #include "ovs-thread.h" |
37 | | #include "packets.h" |
38 | | #include "openvswitch/poll-loop.h" |
39 | | #include "seq.h" |
40 | | #include "socket-util.h" |
41 | | #include "timeval.h" |
42 | | #include "unaligned.h" |
43 | | #include "unixctl.h" |
44 | | #include "util.h" |
45 | | #include "openvswitch/vlog.h" |
46 | | |
47 | | |
48 | | #define NEIGH_ENTRY_DEFAULT_IDLE_TIME_MS (15 * 60 * 1000) |
49 | 0 | #define NEIGH_ENTRY_MAX_AGING_TIME_S 3600 |
50 | | #define NEIGH_ENTRY_LOOKUP_RETRANS_TIME 1000 |
51 | | |
52 | | struct tnl_neigh_entry { |
53 | | struct cmap_node cmap_node; |
54 | | struct in6_addr ip; |
55 | | struct eth_addr mac; |
56 | | atomic_llong expires; /* Expiration time in ms. */ |
57 | | char br_name[IFNAMSIZ]; |
58 | | atomic_bool complete; |
59 | | }; |
60 | | |
61 | | static struct cmap table = CMAP_INITIALIZER; |
62 | | static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; |
63 | | static atomic_uint32_t neigh_aging; |
64 | | static atomic_uint32_t neigh_retrans_time; |
65 | | |
66 | | static uint32_t |
67 | | tnl_neigh_hash(const struct in6_addr *ip) |
68 | 0 | { |
69 | 0 | return hash_bytes(ip->s6_addr, 16, 0); |
70 | 0 | } |
71 | | |
72 | | static bool |
73 | | tnl_neigh_expired(struct tnl_neigh_entry *neigh) |
74 | 0 | { |
75 | 0 | long long expires; |
76 | |
|
77 | 0 | atomic_read_explicit(&neigh->expires, &expires, memory_order_acquire); |
78 | |
|
79 | 0 | return expires <= time_msec(); |
80 | 0 | } |
81 | | |
82 | | static uint32_t |
83 | | tnl_neigh_get_aging(void) |
84 | 0 | { |
85 | 0 | unsigned int aging; |
86 | |
|
87 | 0 | atomic_read_explicit(&neigh_aging, &aging, memory_order_acquire); |
88 | 0 | return aging; |
89 | 0 | } |
90 | | |
91 | | static uint32_t |
92 | | tnl_neigh_get_retrans_time(void) |
93 | 0 | { |
94 | 0 | unsigned int retrans_time; |
95 | |
|
96 | 0 | atomic_read_explicit(&neigh_retrans_time, &retrans_time, |
97 | 0 | memory_order_acquire); |
98 | 0 | return retrans_time; |
99 | 0 | } |
100 | | |
101 | | static bool |
102 | | tnl_neigh_is_complete(struct tnl_neigh_entry *neigh) |
103 | 0 | { |
104 | 0 | bool complete; |
105 | |
|
106 | 0 | atomic_read_explicit(&neigh->complete, &complete, memory_order_acquire); |
107 | 0 | return complete; |
108 | 0 | } |
109 | | |
110 | | static struct tnl_neigh_entry * |
111 | | tnl_neigh_lookup__(const char br_name[IFNAMSIZ], const struct in6_addr *dst) |
112 | 0 | { |
113 | 0 | struct tnl_neigh_entry *neigh; |
114 | 0 | uint32_t hash; |
115 | |
|
116 | 0 | hash = tnl_neigh_hash(dst); |
117 | 0 | CMAP_FOR_EACH_WITH_HASH (neigh, cmap_node, hash, &table) { |
118 | 0 | if (ipv6_addr_equals(&neigh->ip, dst) && !strcmp(neigh->br_name, br_name)) { |
119 | 0 | if (tnl_neigh_expired(neigh)) { |
120 | 0 | return NULL; |
121 | 0 | } |
122 | | |
123 | 0 | if (tnl_neigh_is_complete(neigh)) { |
124 | 0 | atomic_store_explicit(&neigh->expires, |
125 | 0 | time_msec() + tnl_neigh_get_aging(), |
126 | 0 | memory_order_release); |
127 | 0 | } |
128 | |
|
129 | 0 | return neigh; |
130 | 0 | } |
131 | 0 | } |
132 | 0 | return NULL; |
133 | 0 | } |
134 | | |
135 | | static void |
136 | | tnl_neigh_set_partial(const char name[IFNAMSIZ], const struct in6_addr *dst) |
137 | 0 | { |
138 | 0 | ovs_mutex_lock(&mutex); |
139 | 0 | struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst); |
140 | 0 | if (neigh) { |
141 | | /* Entry inserted before lock taken. */ |
142 | |
|
143 | 0 | ovs_mutex_unlock(&mutex); |
144 | 0 | return; |
145 | 0 | } |
146 | 0 | neigh = xmalloc(sizeof *neigh); |
147 | |
|
148 | 0 | neigh->ip = *dst; |
149 | 0 | atomic_store_relaxed(&neigh->complete, false); |
150 | 0 | atomic_store_relaxed(&neigh->expires, |
151 | 0 | time_msec() + tnl_neigh_get_retrans_time()); |
152 | 0 | ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name); |
153 | 0 | cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip)); |
154 | |
|
155 | 0 | ovs_mutex_unlock(&mutex); |
156 | 0 | seq_change(tnl_conf_seq); |
157 | 0 | } |
158 | | |
159 | | int |
160 | | tnl_neigh_lookup(const char br_name[IFNAMSIZ], const struct in6_addr *dst, |
161 | | struct eth_addr *mac, bool insert_partial) |
162 | 0 | { |
163 | 0 | struct tnl_neigh_entry *neigh; |
164 | 0 | int res = ENOENT; |
165 | |
|
166 | 0 | neigh = tnl_neigh_lookup__(br_name, dst); |
167 | 0 | if (neigh) { |
168 | 0 | if (tnl_neigh_is_complete(neigh)) { |
169 | 0 | *mac = neigh->mac; |
170 | 0 | res = 0; |
171 | 0 | } else { |
172 | 0 | res = EINPROGRESS; |
173 | 0 | } |
174 | 0 | } else if (insert_partial && tnl_neigh_get_retrans_time()) { |
175 | | /* Insert a partial entry only if there is a retransmit timer set. */ |
176 | 0 | tnl_neigh_set_partial(br_name, dst); |
177 | 0 | } |
178 | |
|
179 | 0 | return res; |
180 | 0 | } |
181 | | |
182 | | static void |
183 | | neigh_entry_free(struct tnl_neigh_entry *neigh) |
184 | 0 | { |
185 | 0 | free(neigh); |
186 | 0 | } |
187 | | |
188 | | static void |
189 | | tnl_neigh_delete(struct tnl_neigh_entry *neigh) |
190 | 0 | { |
191 | 0 | uint32_t hash = tnl_neigh_hash(&neigh->ip); |
192 | 0 | cmap_remove(&table, &neigh->cmap_node, hash); |
193 | 0 | ovsrcu_postpone(neigh_entry_free, neigh); |
194 | 0 | } |
195 | | |
196 | | void |
197 | | tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, |
198 | | const struct eth_addr mac) |
199 | 0 | { |
200 | 0 | ovs_mutex_lock(&mutex); |
201 | 0 | struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst); |
202 | 0 | bool insert = true; |
203 | |
|
204 | 0 | if (neigh) { |
205 | 0 | if (!tnl_neigh_is_complete(neigh)) { |
206 | 0 | insert = false; |
207 | 0 | } else if (eth_addr_equals(neigh->mac, mac)) { |
208 | 0 | atomic_store_relaxed(&neigh->expires, |
209 | 0 | time_msec() + tnl_neigh_get_aging()); |
210 | 0 | ovs_mutex_unlock(&mutex); |
211 | 0 | return; |
212 | 0 | } else { |
213 | 0 | tnl_neigh_delete(neigh); |
214 | 0 | } |
215 | 0 | } |
216 | | |
217 | 0 | if (insert) { |
218 | 0 | neigh = xmalloc(sizeof *neigh); |
219 | |
|
220 | 0 | neigh->ip = *dst; |
221 | 0 | ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name); |
222 | 0 | } |
223 | |
|
224 | 0 | neigh->mac = mac; |
225 | 0 | atomic_store_explicit(&neigh->expires, |
226 | 0 | time_msec() + tnl_neigh_get_aging(), |
227 | 0 | memory_order_release); |
228 | 0 | atomic_store_explicit(&neigh->complete, true, memory_order_release); |
229 | |
|
230 | 0 | if (insert) { |
231 | 0 | cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip)); |
232 | 0 | } |
233 | |
|
234 | 0 | ovs_mutex_unlock(&mutex); |
235 | 0 | seq_change(tnl_conf_seq); |
236 | 0 | } |
237 | | |
238 | | static void |
239 | | tnl_arp_set(const char name[IFNAMSIZ], ovs_be32 dst, |
240 | | const struct eth_addr mac) |
241 | 0 | { |
242 | 0 | struct in6_addr dst6 = in6_addr_mapped_ipv4(dst); |
243 | 0 | tnl_neigh_set(name, &dst6, mac); |
244 | 0 | } |
245 | | |
246 | | static int |
247 | | tnl_arp_snoop(const struct flow *flow, struct flow_wildcards *wc, |
248 | | const char name[IFNAMSIZ], bool allow_update) |
249 | 0 | { |
250 | | /* Snoop normal ARP replies and gratuitous ARP requests/replies only */ |
251 | 0 | if (!is_arp(flow) |
252 | 0 | || (!is_garp(flow, wc) && |
253 | 0 | FLOW_WC_GET_AND_MASK_WC(flow, wc, nw_proto) != ARP_OP_REPLY) |
254 | 0 | || eth_addr_is_zero(FLOW_WC_GET_AND_MASK_WC(flow, wc, arp_sha))) { |
255 | 0 | return EINVAL; |
256 | 0 | } |
257 | | |
258 | 0 | memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src); |
259 | |
|
260 | 0 | if (allow_update) { |
261 | 0 | tnl_arp_set(name, flow->nw_src, flow->arp_sha); |
262 | 0 | } |
263 | 0 | return 0; |
264 | 0 | } |
265 | | |
266 | | static int |
267 | | tnl_nd_snoop(const struct flow *flow, struct flow_wildcards *wc, |
268 | | const char name[IFNAMSIZ], bool allow_update) |
269 | 0 | { |
270 | 0 | if (!is_nd(flow, wc) || flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) { |
271 | 0 | return EINVAL; |
272 | 0 | } |
273 | | /* - RFC4861 says Neighbor Advertisements sent in response to unicast Neighbor |
274 | | * Solicitations SHOULD include the Target link-layer address. However, Linux |
275 | | * doesn't. So, the response to Solicitations sent by OVS will include the |
276 | | * TLL address and other Advertisements not including it can be ignored. |
277 | | * - OVS flow extract can set this field to zero in case of packet parsing errors. |
278 | | * For details refer miniflow_extract()*/ |
279 | 0 | if (eth_addr_is_zero(FLOW_WC_GET_AND_MASK_WC(flow, wc, arp_tha))) { |
280 | 0 | return EINVAL; |
281 | 0 | } |
282 | | |
283 | 0 | memset(&wc->masks.ipv6_src, 0xff, sizeof wc->masks.ipv6_src); |
284 | 0 | memset(&wc->masks.ipv6_dst, 0xff, sizeof wc->masks.ipv6_dst); |
285 | 0 | memset(&wc->masks.nd_target, 0xff, sizeof wc->masks.nd_target); |
286 | |
|
287 | 0 | if (allow_update) { |
288 | 0 | tnl_neigh_set(name, &flow->nd_target, flow->arp_tha); |
289 | 0 | } |
290 | 0 | return 0; |
291 | 0 | } |
292 | | |
293 | | int |
294 | | tnl_neigh_snoop(const struct flow *flow, struct flow_wildcards *wc, |
295 | | const char name[IFNAMSIZ], bool allow_update) |
296 | 0 | { |
297 | 0 | int res; |
298 | 0 | res = tnl_arp_snoop(flow, wc, name, allow_update); |
299 | 0 | if (res != EINVAL) { |
300 | 0 | return res; |
301 | 0 | } |
302 | 0 | return tnl_nd_snoop(flow, wc, name, allow_update); |
303 | 0 | } |
304 | | |
305 | | void |
306 | | tnl_neigh_cache_run(void) |
307 | 0 | { |
308 | 0 | struct tnl_neigh_entry *neigh; |
309 | 0 | bool changed = false; |
310 | |
|
311 | 0 | ovs_mutex_lock(&mutex); |
312 | 0 | CMAP_FOR_EACH(neigh, cmap_node, &table) { |
313 | 0 | if (tnl_neigh_expired(neigh)) { |
314 | 0 | tnl_neigh_delete(neigh); |
315 | 0 | changed = true; |
316 | 0 | } |
317 | 0 | } |
318 | 0 | ovs_mutex_unlock(&mutex); |
319 | |
|
320 | 0 | if (changed) { |
321 | 0 | seq_change(tnl_conf_seq); |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | void |
326 | | tnl_neigh_flush(const char br_name[IFNAMSIZ]) |
327 | 0 | { |
328 | 0 | struct tnl_neigh_entry *neigh; |
329 | 0 | bool changed = false; |
330 | |
|
331 | 0 | ovs_mutex_lock(&mutex); |
332 | 0 | CMAP_FOR_EACH (neigh, cmap_node, &table) { |
333 | 0 | if (!strcmp(neigh->br_name, br_name)) { |
334 | 0 | tnl_neigh_delete(neigh); |
335 | 0 | changed = true; |
336 | 0 | } |
337 | 0 | } |
338 | 0 | ovs_mutex_unlock(&mutex); |
339 | |
|
340 | 0 | if (changed) { |
341 | 0 | seq_change(tnl_conf_seq); |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | | static void |
346 | | tnl_neigh_cache_flush(struct unixctl_conn *conn, int argc OVS_UNUSED, |
347 | | const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) |
348 | 0 | { |
349 | 0 | struct tnl_neigh_entry *neigh; |
350 | 0 | bool changed = false; |
351 | |
|
352 | 0 | ovs_mutex_lock(&mutex); |
353 | 0 | CMAP_FOR_EACH(neigh, cmap_node, &table) { |
354 | 0 | tnl_neigh_delete(neigh); |
355 | 0 | changed = true; |
356 | 0 | } |
357 | 0 | ovs_mutex_unlock(&mutex); |
358 | 0 | if (changed) { |
359 | 0 | seq_change(tnl_conf_seq); |
360 | 0 | } |
361 | 0 | unixctl_command_reply(conn, "OK"); |
362 | 0 | } |
363 | | |
364 | | static void |
365 | | tnl_neigh_cache_aging(struct unixctl_conn *conn, int argc, |
366 | | const char *argv[], void *aux OVS_UNUSED) |
367 | 0 | { |
368 | 0 | long long int new_exp, curr_exp; |
369 | 0 | struct tnl_neigh_entry *neigh; |
370 | 0 | uint32_t aging; |
371 | |
|
372 | 0 | if (argc == 1) { |
373 | 0 | struct ds ds = DS_EMPTY_INITIALIZER; |
374 | 0 | ds_put_format(&ds, "%"PRIu32, tnl_neigh_get_aging() / 1000); |
375 | 0 | unixctl_command_reply(conn, ds_cstr(&ds)); |
376 | 0 | ds_destroy(&ds); |
377 | |
|
378 | 0 | return; |
379 | 0 | } |
380 | | |
381 | 0 | if (!ovs_scan(argv[1], "%"SCNu32, &aging) || |
382 | 0 | !aging || aging > NEIGH_ENTRY_MAX_AGING_TIME_S) { |
383 | 0 | unixctl_command_reply_error(conn, "bad aging value"); |
384 | 0 | return; |
385 | 0 | } |
386 | | |
387 | 0 | aging *= 1000; |
388 | |
|
389 | 0 | if (aging < tnl_neigh_get_retrans_time()) { |
390 | 0 | unixctl_command_reply_error(conn, "aging value cannot be less than " |
391 | 0 | "retrans_time"); |
392 | 0 | return; |
393 | 0 | } |
394 | | |
395 | 0 | atomic_store_explicit(&neigh_aging, aging, memory_order_release); |
396 | 0 | new_exp = time_msec() + aging; |
397 | |
|
398 | 0 | CMAP_FOR_EACH (neigh, cmap_node, &table) { |
399 | 0 | atomic_read_explicit(&neigh->expires, &curr_exp, |
400 | 0 | memory_order_acquire); |
401 | 0 | if (new_exp < curr_exp) { |
402 | 0 | atomic_store_explicit(&neigh->expires, new_exp, |
403 | 0 | memory_order_release); |
404 | 0 | } |
405 | 0 | } |
406 | |
|
407 | 0 | unixctl_command_reply(conn, "OK"); |
408 | 0 | } |
409 | | |
410 | | static void |
411 | | tnl_neigh_cache_retrans_time(struct unixctl_conn *conn, int argc, |
412 | | const char *argv[], void *aux OVS_UNUSED) |
413 | 0 | { |
414 | 0 | long long int new_exp, curr_exp; |
415 | 0 | struct tnl_neigh_entry *neigh; |
416 | 0 | uint32_t retrans_time; |
417 | |
|
418 | 0 | if (argc == 1) { |
419 | 0 | struct ds ds = DS_EMPTY_INITIALIZER; |
420 | 0 | ds_put_format(&ds, "%"PRIu32, tnl_neigh_get_retrans_time()); |
421 | 0 | unixctl_command_reply(conn, ds_cstr(&ds)); |
422 | 0 | ds_destroy(&ds); |
423 | |
|
424 | 0 | return; |
425 | 0 | } |
426 | | |
427 | | /* Zero retransmit value is acceptable. */ |
428 | 0 | if (!ovs_scan(argv[1], "%"SCNu32, &retrans_time)) { |
429 | 0 | unixctl_command_reply_error(conn, "bad retrans_time value"); |
430 | 0 | return; |
431 | 0 | } else if (retrans_time > tnl_neigh_get_aging()) { |
432 | 0 | unixctl_command_reply_error(conn, "retrans_time value cannot be " |
433 | 0 | "greater than aging"); |
434 | 0 | return; |
435 | 0 | } |
436 | | |
437 | 0 | atomic_store_explicit(&neigh_retrans_time, retrans_time, |
438 | 0 | memory_order_release); |
439 | 0 | new_exp = time_msec() + retrans_time; |
440 | |
|
441 | 0 | CMAP_FOR_EACH (neigh, cmap_node, &table) { |
442 | 0 | if (tnl_neigh_is_complete(neigh)) { |
443 | 0 | continue; |
444 | 0 | } |
445 | 0 | atomic_read_explicit(&neigh->expires, &curr_exp, |
446 | 0 | memory_order_acquire); |
447 | 0 | if (new_exp < curr_exp) { |
448 | 0 | atomic_store_explicit(&neigh->expires, new_exp, |
449 | 0 | memory_order_release); |
450 | 0 | } |
451 | 0 | } |
452 | |
|
453 | 0 | unixctl_command_reply(conn, "OK"); |
454 | 0 | } |
455 | | |
456 | | static int |
457 | | lookup_any(const char *host_name, struct in6_addr *address) |
458 | 0 | { |
459 | 0 | if (addr_is_ipv6(host_name)) { |
460 | 0 | return lookup_ipv6(host_name, address); |
461 | 0 | } else { |
462 | 0 | int r; |
463 | 0 | struct in_addr ip; |
464 | 0 | r = lookup_ip(host_name, &ip); |
465 | 0 | if (r == 0) { |
466 | 0 | in6_addr_set_mapped_ipv4(address, ip.s_addr); |
467 | 0 | } |
468 | 0 | return r; |
469 | 0 | } |
470 | 0 | return ENOENT; |
471 | 0 | } |
472 | | |
473 | | static void |
474 | | tnl_neigh_cache_add(struct unixctl_conn *conn, int argc OVS_UNUSED, |
475 | | const char *argv[], void *aux OVS_UNUSED) |
476 | 0 | { |
477 | 0 | const char *br_name = argv[1]; |
478 | 0 | struct eth_addr mac; |
479 | 0 | struct in6_addr ip6; |
480 | |
|
481 | 0 | if (lookup_any(argv[2], &ip6) != 0) { |
482 | 0 | unixctl_command_reply_error(conn, "bad IP address"); |
483 | 0 | return; |
484 | 0 | } |
485 | | |
486 | 0 | if (!eth_addr_from_string(argv[3], &mac)) { |
487 | 0 | unixctl_command_reply_error(conn, "bad MAC address"); |
488 | 0 | return; |
489 | 0 | } |
490 | | |
491 | 0 | tnl_neigh_set(br_name, &ip6, mac); |
492 | 0 | unixctl_command_reply(conn, "OK"); |
493 | 0 | } |
494 | | |
495 | | static void |
496 | | tnl_neigh_cache_show(struct unixctl_conn *conn, int argc OVS_UNUSED, |
497 | | const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) |
498 | 0 | { |
499 | 0 | struct ds ds = DS_EMPTY_INITIALIZER; |
500 | 0 | struct tnl_neigh_entry *neigh; |
501 | |
|
502 | 0 | ds_put_cstr(&ds, "IP MAC Bridge\n"); |
503 | 0 | ds_put_cstr(&ds, "==========================================================================\n"); |
504 | 0 | ovs_mutex_lock(&mutex); |
505 | 0 | CMAP_FOR_EACH(neigh, cmap_node, &table) { |
506 | 0 | int start_len, need_ws; |
507 | |
|
508 | 0 | start_len = ds.length; |
509 | 0 | ipv6_format_mapped(&neigh->ip, &ds); |
510 | |
|
511 | 0 | need_ws = INET6_ADDRSTRLEN - (ds.length - start_len); |
512 | 0 | ds_put_char_multiple(&ds, ' ', need_ws); |
513 | |
|
514 | 0 | if (tnl_neigh_is_complete(neigh)) { |
515 | 0 | ds_put_format(&ds, ETH_ADDR_FMT" %s", |
516 | 0 | ETH_ADDR_ARGS(neigh->mac), neigh->br_name); |
517 | 0 | } else { |
518 | 0 | ds_put_format(&ds, " %s INCOMPLETE", |
519 | 0 | neigh->br_name); |
520 | 0 | } |
521 | 0 | if (tnl_neigh_expired(neigh)) { |
522 | 0 | ds_put_format(&ds, " STALE"); |
523 | 0 | } |
524 | 0 | ds_put_char(&ds, '\n'); |
525 | |
|
526 | 0 | } |
527 | 0 | ovs_mutex_unlock(&mutex); |
528 | 0 | unixctl_command_reply(conn, ds_cstr(&ds)); |
529 | 0 | ds_destroy(&ds); |
530 | 0 | } |
531 | | |
532 | | void |
533 | | tnl_neigh_cache_init(void) |
534 | 0 | { |
535 | 0 | atomic_init(&neigh_aging, NEIGH_ENTRY_DEFAULT_IDLE_TIME_MS); |
536 | 0 | atomic_init(&neigh_retrans_time, NEIGH_ENTRY_LOOKUP_RETRANS_TIME); |
537 | 0 | unixctl_command_register("tnl/arp/show", "", 0, 0, |
538 | 0 | tnl_neigh_cache_show, NULL); |
539 | 0 | unixctl_command_register("tnl/arp/set", "BRIDGE IP MAC", 3, 3, |
540 | 0 | tnl_neigh_cache_add, NULL); |
541 | 0 | unixctl_command_register("tnl/arp/flush", "", 0, 0, |
542 | 0 | tnl_neigh_cache_flush, NULL); |
543 | 0 | unixctl_command_register("tnl/arp/aging", "[SECS]", 0, 1, |
544 | 0 | tnl_neigh_cache_aging, NULL); |
545 | 0 | unixctl_command_register("tnl/neigh/show", "", 0, 0, |
546 | 0 | tnl_neigh_cache_show, NULL); |
547 | 0 | unixctl_command_register("tnl/neigh/set", "BRIDGE IP MAC", 3, 3, |
548 | 0 | tnl_neigh_cache_add, NULL); |
549 | 0 | unixctl_command_register("tnl/neigh/flush", "", 0, 0, |
550 | 0 | tnl_neigh_cache_flush, NULL); |
551 | 0 | unixctl_command_register("tnl/neigh/aging", "[SECS]", 0, 1, |
552 | 0 | tnl_neigh_cache_aging, NULL); |
553 | 0 | unixctl_command_register("tnl/neigh/retrans_time", "[MSECS]", 0, 1, |
554 | | tnl_neigh_cache_retrans_time, NULL); |
555 | 0 | } |