]>
Commit | Line | Data |
---|---|---|
d9b4ebc5 | 1 | /* |
7209202e | 2 | * Copyright (c) 2014, 2015, 2016 Nicira, Inc. |
d9b4ebc5 PS |
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> | |
1f9575ca TLSC |
18 | |
19 | #include "ovs-router.h" | |
20 | ||
d9b4ebc5 PS |
21 | #include <arpa/inet.h> |
22 | #include <errno.h> | |
23 | #include <inttypes.h> | |
24 | #include <sys/socket.h> | |
25 | #include <net/if.h> | |
26 | #include <netinet/in.h> | |
27 | #include <stdarg.h> | |
28 | #include <stdlib.h> | |
29 | #include <string.h> | |
30 | #include <unistd.h> | |
31 | ||
32 | #include "classifier.h" | |
33 | #include "command-line.h" | |
34 | #include "compiler.h" | |
35 | #include "dpif.h" | |
3e8a2ad1 | 36 | #include "openvswitch/dynamic-string.h" |
d9b4ebc5 PS |
37 | #include "netdev.h" |
38 | #include "packets.h" | |
a36de779 | 39 | #include "seq.h" |
fccd7c09 | 40 | #include "ovs-thread.h" |
88ffdc93 | 41 | #include "route-table.h" |
7f9b8504 | 42 | #include "tnl-ports.h" |
d9b4ebc5 PS |
43 | #include "unixctl.h" |
44 | #include "util.h" | |
a8704b50 PS |
45 | #include "unaligned.h" |
46 | #include "unixctl.h" | |
47 | #include "util.h" | |
ed52ca57 PS |
48 | #include "openvswitch/vlog.h" |
49 | ||
50 | VLOG_DEFINE_THIS_MODULE(ovs_router); | |
51 | ||
52 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); | |
d9b4ebc5 | 53 | |
fccd7c09 | 54 | static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; |
d9b4ebc5 PS |
55 | static struct classifier cls; |
56 | ||
57 | struct ovs_router_entry { | |
58 | struct cls_rule cr; | |
59 | char output_bridge[IFNAMSIZ]; | |
0b8da9ae TLSC |
60 | struct in6_addr gw; |
61 | struct in6_addr nw_addr; | |
a8704b50 | 62 | struct in6_addr src_addr; |
d9b4ebc5 PS |
63 | uint8_t plen; |
64 | uint8_t priority; | |
ed52ca57 | 65 | uint32_t mark; |
d9b4ebc5 PS |
66 | }; |
67 | ||
68 | static struct ovs_router_entry * | |
69 | ovs_router_entry_cast(const struct cls_rule *cr) | |
70 | { | |
71 | if (offsetof(struct ovs_router_entry, cr) == 0) { | |
72 | return CONTAINER_OF(cr, struct ovs_router_entry, cr); | |
73 | } else { | |
74 | return cr ? CONTAINER_OF(cr, struct ovs_router_entry, cr) : NULL; | |
75 | } | |
76 | } | |
77 | ||
ec6c5379 PS |
78 | static bool |
79 | ovs_router_lookup_fallback(const struct in6_addr *ip6_dst, char output_bridge[], | |
80 | struct in6_addr *src6, struct in6_addr *gw6) | |
81 | { | |
82 | ovs_be32 src; | |
83 | ||
84 | if (!route_table_fallback_lookup(ip6_dst, output_bridge, gw6)) { | |
85 | return false; | |
86 | } | |
87 | if (netdev_get_in4_by_name(output_bridge, (struct in_addr *)&src)) { | |
88 | return false; | |
89 | } | |
90 | if (src6) { | |
91 | in6_addr_set_mapped_ipv4(src6, src); | |
92 | } | |
93 | return true; | |
94 | } | |
95 | ||
d9b4ebc5 | 96 | bool |
ed52ca57 PS |
97 | ovs_router_lookup(uint32_t mark, const struct in6_addr *ip6_dst, |
98 | char output_bridge[], | |
a8704b50 | 99 | struct in6_addr *src, struct in6_addr *gw) |
d9b4ebc5 PS |
100 | { |
101 | const struct cls_rule *cr; | |
ed52ca57 | 102 | struct flow flow = {.ipv6_dst = *ip6_dst, .pkt_mark = mark}; |
d9b4ebc5 | 103 | |
44e0c35d | 104 | cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL); |
d9b4ebc5 PS |
105 | if (cr) { |
106 | struct ovs_router_entry *p = ovs_router_entry_cast(cr); | |
107 | ||
8742957c | 108 | ovs_strlcpy(output_bridge, p->output_bridge, IFNAMSIZ); |
d9b4ebc5 | 109 | *gw = p->gw; |
a8704b50 PS |
110 | if (src) { |
111 | *src = p->src_addr; | |
112 | } | |
d9b4ebc5 PS |
113 | return true; |
114 | } | |
ec6c5379 | 115 | return ovs_router_lookup_fallback(ip6_dst, output_bridge, src, gw); |
d9b4ebc5 PS |
116 | } |
117 | ||
118 | static void | |
119 | rt_entry_free(struct ovs_router_entry *p) | |
120 | { | |
121 | cls_rule_destroy(&p->cr); | |
122 | free(p); | |
123 | } | |
124 | ||
ed52ca57 PS |
125 | static void rt_init_match(struct match *match, uint32_t mark, |
126 | const struct in6_addr *ip6_dst, | |
0b8da9ae | 127 | uint8_t plen) |
d9b4ebc5 | 128 | { |
0b8da9ae TLSC |
129 | struct in6_addr dst; |
130 | struct in6_addr mask; | |
d9b4ebc5 | 131 | |
0b8da9ae | 132 | mask = ipv6_create_mask(plen); |
d9b4ebc5 | 133 | |
0b8da9ae | 134 | dst = ipv6_addr_bitand(ip6_dst, &mask); |
d9b4ebc5 | 135 | memset(match, 0, sizeof *match); |
0b8da9ae TLSC |
136 | match->flow.ipv6_dst = dst; |
137 | match->wc.masks.ipv6_dst = mask; | |
ed52ca57 PS |
138 | match->wc.masks.pkt_mark = UINT32_MAX; |
139 | match->flow.pkt_mark = mark; | |
d9b4ebc5 PS |
140 | } |
141 | ||
a8704b50 PS |
142 | static int |
143 | get_src_addr(const struct in6_addr *ip6_dst, | |
144 | const char output_bridge[], struct in6_addr *psrc) | |
145 | { | |
146 | struct in6_addr *mask, *addr6; | |
147 | int err, n_in6, i, max_plen = -1; | |
148 | struct netdev *dev; | |
20dcdc7f | 149 | bool is_ipv4; |
a8704b50 PS |
150 | |
151 | err = netdev_open(output_bridge, NULL, &dev); | |
152 | if (err) { | |
153 | return err; | |
154 | } | |
155 | ||
156 | err = netdev_get_addr_list(dev, &addr6, &mask, &n_in6); | |
157 | if (err) { | |
158 | goto out; | |
159 | } | |
160 | ||
20dcdc7f TLSC |
161 | is_ipv4 = IN6_IS_ADDR_V4MAPPED(ip6_dst); |
162 | ||
a8704b50 PS |
163 | for (i = 0; i < n_in6; i++) { |
164 | struct in6_addr a1, a2; | |
165 | int mask_bits; | |
166 | ||
20dcdc7f TLSC |
167 | if (is_ipv4 && !IN6_IS_ADDR_V4MAPPED(&addr6[i])) { |
168 | continue; | |
169 | } | |
170 | ||
a8704b50 PS |
171 | a1 = ipv6_addr_bitand(ip6_dst, &mask[i]); |
172 | a2 = ipv6_addr_bitand(&addr6[i], &mask[i]); | |
173 | mask_bits = bitmap_count1(ALIGNED_CAST(const unsigned long *, &mask[i]), 128); | |
174 | ||
175 | if (!memcmp(&a1, &a2, sizeof (a1)) && mask_bits > max_plen) { | |
176 | *psrc = addr6[i]; | |
177 | max_plen = mask_bits; | |
178 | } | |
179 | } | |
180 | if (max_plen == -1) { | |
181 | err = ENOENT; | |
182 | } | |
183 | out: | |
184 | free(addr6); | |
185 | free(mask); | |
186 | netdev_close(dev); | |
187 | return err; | |
188 | } | |
189 | ||
190 | static int | |
ed52ca57 PS |
191 | ovs_router_insert__(uint32_t mark, uint8_t priority, |
192 | const struct in6_addr *ip6_dst, | |
0b8da9ae TLSC |
193 | uint8_t plen, const char output_bridge[], |
194 | const struct in6_addr *gw) | |
d9b4ebc5 PS |
195 | { |
196 | const struct cls_rule *cr; | |
197 | struct ovs_router_entry *p; | |
198 | struct match match; | |
a8704b50 | 199 | int err; |
d9b4ebc5 | 200 | |
ed52ca57 | 201 | rt_init_match(&match, mark, ip6_dst, plen); |
d9b4ebc5 PS |
202 | |
203 | p = xzalloc(sizeof *p); | |
8742957c | 204 | ovs_strlcpy(p->output_bridge, output_bridge, sizeof p->output_bridge); |
0b8da9ae TLSC |
205 | if (ipv6_addr_is_set(gw)) { |
206 | p->gw = *gw; | |
207 | } | |
ed52ca57 | 208 | p->mark = mark; |
0b8da9ae | 209 | p->nw_addr = match.flow.ipv6_dst; |
d9b4ebc5 PS |
210 | p->plen = plen; |
211 | p->priority = priority; | |
a8704b50 | 212 | err = get_src_addr(ip6_dst, output_bridge, &p->src_addr); |
4e08f54b AW |
213 | if (err && ipv6_addr_is_set(gw)) { |
214 | err = get_src_addr(gw, output_bridge, &p->src_addr); | |
215 | } | |
a8704b50 | 216 | if (err) { |
ed52ca57 PS |
217 | struct ds ds = DS_EMPTY_INITIALIZER; |
218 | ||
219 | ipv6_format_mapped(ip6_dst, &ds); | |
220 | VLOG_DBG_RL(&rl, "src addr not available for route %s", ds_cstr(&ds)); | |
7209202e | 221 | free(p); |
ed52ca57 | 222 | ds_destroy(&ds); |
a8704b50 PS |
223 | return err; |
224 | } | |
2b7b1427 | 225 | /* Longest prefix matches first. */ |
bd53aa17 | 226 | cls_rule_init(&p->cr, &match, priority); |
d9b4ebc5 | 227 | |
fccd7c09 | 228 | ovs_mutex_lock(&mutex); |
44e0c35d | 229 | cr = classifier_replace(&cls, &p->cr, OVS_VERSION_MIN, NULL, 0); |
fccd7c09 JR |
230 | ovs_mutex_unlock(&mutex); |
231 | ||
d9b4ebc5 PS |
232 | if (cr) { |
233 | /* An old rule with the same match was displaced. */ | |
234 | ovsrcu_postpone(rt_entry_free, ovs_router_entry_cast(cr)); | |
235 | } | |
7f9b8504 | 236 | tnl_port_map_insert_ipdev(output_bridge); |
a36de779 | 237 | seq_change(tnl_conf_seq); |
a8704b50 | 238 | return 0; |
d9b4ebc5 PS |
239 | } |
240 | ||
241 | void | |
ed52ca57 | 242 | ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst, uint8_t plen, |
0b8da9ae | 243 | const char output_bridge[], const struct in6_addr *gw) |
d9b4ebc5 | 244 | { |
ed52ca57 | 245 | ovs_router_insert__(mark, plen, ip_dst, plen, output_bridge, gw); |
d9b4ebc5 PS |
246 | } |
247 | ||
7f9b8504 PS |
248 | static bool |
249 | __rt_entry_delete(const struct cls_rule *cr) | |
250 | { | |
251 | struct ovs_router_entry *p = ovs_router_entry_cast(cr); | |
252 | ||
253 | tnl_port_map_delete_ipdev(p->output_bridge); | |
254 | /* Remove it. */ | |
255 | cr = classifier_remove(&cls, cr); | |
256 | if (cr) { | |
257 | ovsrcu_postpone(rt_entry_free, ovs_router_entry_cast(cr)); | |
258 | return true; | |
259 | } | |
260 | return false; | |
261 | } | |
262 | ||
d9b4ebc5 | 263 | static bool |
ed52ca57 PS |
264 | rt_entry_delete(uint32_t mark, uint8_t priority, |
265 | const struct in6_addr *ip6_dst, uint8_t plen) | |
d9b4ebc5 | 266 | { |
dfea28b3 | 267 | const struct cls_rule *cr; |
d9b4ebc5 PS |
268 | struct cls_rule rule; |
269 | struct match match; | |
7f9b8504 | 270 | bool res = false; |
d9b4ebc5 | 271 | |
ed52ca57 | 272 | rt_init_match(&match, mark, ip6_dst, plen); |
d9b4ebc5 | 273 | |
bd53aa17 | 274 | cls_rule_init(&rule, &match, priority); |
d9b4ebc5 PS |
275 | |
276 | /* Find the exact rule. */ | |
44e0c35d | 277 | cr = classifier_find_rule_exactly(&cls, &rule, OVS_VERSION_MAX); |
d9b4ebc5 | 278 | if (cr) { |
fccd7c09 | 279 | ovs_mutex_lock(&mutex); |
7f9b8504 | 280 | res = __rt_entry_delete(cr); |
fccd7c09 | 281 | ovs_mutex_unlock(&mutex); |
d9b4ebc5 | 282 | } |
7f9b8504 | 283 | return res; |
d9b4ebc5 PS |
284 | } |
285 | ||
0b8da9ae TLSC |
286 | static bool |
287 | scan_ipv6_route(const char *s, struct in6_addr *addr, unsigned int *plen) | |
288 | { | |
e7695092 | 289 | char *error = ipv6_parse_cidr(s, addr, plen); |
184dfff0 JP |
290 | if (error) { |
291 | free(error); | |
292 | return false; | |
0b8da9ae | 293 | } |
184dfff0 | 294 | return true; |
0b8da9ae TLSC |
295 | } |
296 | ||
d9b4ebc5 PS |
297 | static bool |
298 | scan_ipv4_route(const char *s, ovs_be32 *addr, unsigned int *plen) | |
299 | { | |
e7695092 BP |
300 | char *error = ip_parse_cidr(s, addr, plen); |
301 | if (error) { | |
302 | free(error); | |
d9b4ebc5 PS |
303 | return false; |
304 | } | |
e7695092 | 305 | return true; |
d9b4ebc5 PS |
306 | } |
307 | ||
308 | static void | |
309 | ovs_router_add(struct unixctl_conn *conn, int argc, | |
310 | const char *argv[], void *aux OVS_UNUSED) | |
311 | { | |
ed52ca57 | 312 | struct in6_addr gw6 = in6addr_any; |
0b8da9ae | 313 | struct in6_addr ip6; |
ed52ca57 PS |
314 | uint32_t mark = 0; |
315 | unsigned int plen; | |
316 | ovs_be32 ip; | |
a8704b50 | 317 | int err; |
d9b4ebc5 PS |
318 | |
319 | if (scan_ipv4_route(argv[1], &ip, &plen)) { | |
e7695092 | 320 | ovs_be32 gw = 0; |
ed52ca57 PS |
321 | |
322 | if (argc > 3) { | |
323 | if (!ovs_scan(argv[3], "pkt_mark=%"SCNi32, &mark) && | |
324 | !ip_parse(argv[3], &gw)) { | |
325 | unixctl_command_reply_error(conn, "Invalid pkt_mark or gateway"); | |
326 | return; | |
327 | } | |
d9b4ebc5 | 328 | } |
0b8da9ae | 329 | in6_addr_set_mapped_ipv4(&ip6, ip); |
ed52ca57 PS |
330 | if (gw) { |
331 | in6_addr_set_mapped_ipv4(&gw6, gw); | |
332 | } | |
0b8da9ae TLSC |
333 | plen += 96; |
334 | } else if (scan_ipv6_route(argv[1], &ip6, &plen)) { | |
ed52ca57 PS |
335 | if (argc > 3) { |
336 | if (!ovs_scan(argv[3], "pkt_mark=%"SCNi32, &mark) && | |
337 | !ipv6_parse(argv[3], &gw6)) { | |
338 | unixctl_command_reply_error(conn, "Invalid pkt_mark or IPv6 gateway"); | |
339 | return; | |
340 | } | |
0b8da9ae | 341 | } |
d9b4ebc5 | 342 | } else { |
f2e06274 BP |
343 | unixctl_command_reply_error(conn, "Invalid parameters"); |
344 | return; | |
d9b4ebc5 | 345 | } |
ed52ca57 PS |
346 | if (argc > 4) { |
347 | if (!ovs_scan(argv[4], "pkt_mark=%"SCNi32, &mark)) { | |
348 | unixctl_command_reply_error(conn, "Invalid pkt_mark"); | |
349 | return; | |
350 | } | |
351 | } | |
352 | ||
353 | err = ovs_router_insert__(mark, plen + 32, &ip6, plen, argv[2], &gw6); | |
a8704b50 | 354 | if (err) { |
9104895e | 355 | unixctl_command_reply_error(conn, "Error while inserting route."); |
a8704b50 PS |
356 | } else { |
357 | unixctl_command_reply(conn, "OK"); | |
358 | } | |
d9b4ebc5 PS |
359 | } |
360 | ||
361 | static void | |
362 | ovs_router_del(struct unixctl_conn *conn, int argc OVS_UNUSED, | |
363 | const char *argv[], void *aux OVS_UNUSED) | |
364 | { | |
0b8da9ae | 365 | struct in6_addr ip6; |
ed52ca57 PS |
366 | uint32_t mark = 0; |
367 | unsigned int plen; | |
368 | ovs_be32 ip; | |
d9b4ebc5 PS |
369 | |
370 | if (scan_ipv4_route(argv[1], &ip, &plen)) { | |
0b8da9ae TLSC |
371 | in6_addr_set_mapped_ipv4(&ip6, ip); |
372 | plen += 96; | |
373 | } else if (!scan_ipv6_route(argv[1], &ip6, &plen)) { | |
f2e06274 BP |
374 | unixctl_command_reply_error(conn, "Invalid parameters"); |
375 | return; | |
d9b4ebc5 | 376 | } |
ed52ca57 PS |
377 | if (argc > 2) { |
378 | if (!ovs_scan(argv[2], "pkt_mark=%"SCNi32, &mark)) { | |
379 | unixctl_command_reply_error(conn, "Invalid pkt_mark"); | |
380 | return; | |
381 | } | |
382 | } | |
383 | ||
384 | if (rt_entry_delete(mark, plen + 32, &ip6, plen)) { | |
0b8da9ae TLSC |
385 | unixctl_command_reply(conn, "OK"); |
386 | seq_change(tnl_conf_seq); | |
387 | } else { | |
f2e06274 | 388 | unixctl_command_reply_error(conn, "Not found"); |
0b8da9ae | 389 | } |
d9b4ebc5 PS |
390 | } |
391 | ||
392 | static void | |
393 | ovs_router_show(struct unixctl_conn *conn, int argc OVS_UNUSED, | |
394 | const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) | |
395 | { | |
396 | struct ovs_router_entry *rt; | |
397 | struct ds ds = DS_EMPTY_INITIALIZER; | |
398 | ||
399 | ds_put_format(&ds, "Route Table:\n"); | |
400 | CLS_FOR_EACH(rt, cr, &cls) { | |
0b8da9ae | 401 | uint8_t plen; |
d9b4ebc5 PS |
402 | if (rt->priority == rt->plen) { |
403 | ds_put_format(&ds, "Cached: "); | |
404 | } else { | |
405 | ds_put_format(&ds, "User: "); | |
406 | } | |
ac6d120f | 407 | ipv6_format_mapped(&rt->nw_addr, &ds); |
0b8da9ae TLSC |
408 | plen = rt->plen; |
409 | if (IN6_IS_ADDR_V4MAPPED(&rt->nw_addr)) { | |
410 | plen -= 96; | |
411 | } | |
ed52ca57 PS |
412 | ds_put_format(&ds, "/%"PRIu16, plen); |
413 | if (rt->mark) { | |
414 | ds_put_format(&ds, " MARK %"PRIu32, rt->mark); | |
415 | } | |
416 | ||
417 | ds_put_format(&ds, " dev %s", rt->output_bridge); | |
0b8da9ae TLSC |
418 | if (ipv6_addr_is_set(&rt->gw)) { |
419 | ds_put_format(&ds, " GW "); | |
ac6d120f | 420 | ipv6_format_mapped(&rt->gw, &ds); |
d9b4ebc5 | 421 | } |
a8704b50 PS |
422 | ds_put_format(&ds, " SRC "); |
423 | ipv6_format_mapped(&rt->src_addr, &ds); | |
d9b4ebc5 PS |
424 | ds_put_format(&ds, "\n"); |
425 | } | |
426 | unixctl_command_reply(conn, ds_cstr(&ds)); | |
427 | ds_destroy(&ds); | |
428 | } | |
429 | ||
bc786797 | 430 | static void |
ed52ca57 | 431 | ovs_router_lookup_cmd(struct unixctl_conn *conn, int argc, |
bc786797 YT |
432 | const char *argv[], void *aux OVS_UNUSED) |
433 | { | |
ed52ca57 PS |
434 | struct in6_addr gw, src; |
435 | char iface[IFNAMSIZ]; | |
0b8da9ae | 436 | struct in6_addr ip6; |
bc786797 | 437 | unsigned int plen; |
ed52ca57 PS |
438 | uint32_t mark = 0; |
439 | ovs_be32 ip; | |
bc786797 YT |
440 | |
441 | if (scan_ipv4_route(argv[1], &ip, &plen) && plen == 32) { | |
0b8da9ae TLSC |
442 | in6_addr_set_mapped_ipv4(&ip6, ip); |
443 | } else if (!(scan_ipv6_route(argv[1], &ip6, &plen) && plen == 128)) { | |
f2e06274 BP |
444 | unixctl_command_reply_error(conn, "Invalid parameters"); |
445 | return; | |
0b8da9ae | 446 | } |
ed52ca57 PS |
447 | if (argc > 2) { |
448 | if (!ovs_scan(argv[2], "pkt_mark=%"SCNi32, &mark)) { | |
449 | unixctl_command_reply_error(conn, "Invalid pkt_mark"); | |
450 | return; | |
451 | } | |
452 | } | |
453 | if (ovs_router_lookup(mark, &ip6, iface, &src, &gw)) { | |
0b8da9ae | 454 | struct ds ds = DS_EMPTY_INITIALIZER; |
ed52ca57 | 455 | |
a8704b50 PS |
456 | ds_put_format(&ds, "src "); |
457 | ipv6_format_mapped(&src, &ds); | |
2ddf1bca | 458 | ds_put_format(&ds, "\ngateway "); |
a8704b50 | 459 | ipv6_format_mapped(&gw, &ds); |
0b8da9ae TLSC |
460 | ds_put_format(&ds, "\ndev %s\n", iface); |
461 | unixctl_command_reply(conn, ds_cstr(&ds)); | |
462 | ds_destroy(&ds); | |
bc786797 | 463 | } else { |
f2e06274 | 464 | unixctl_command_reply_error(conn, "Not found"); |
bc786797 YT |
465 | } |
466 | } | |
467 | ||
d9b4ebc5 PS |
468 | void |
469 | ovs_router_flush(void) | |
470 | { | |
471 | struct ovs_router_entry *rt; | |
472 | ||
802f84ff JR |
473 | ovs_mutex_lock(&mutex); |
474 | classifier_defer(&cls); | |
de4ad4a2 | 475 | CLS_FOR_EACH(rt, cr, &cls) { |
d9b4ebc5 | 476 | if (rt->priority == rt->plen) { |
7f9b8504 | 477 | __rt_entry_delete(&rt->cr); |
d9b4ebc5 PS |
478 | } |
479 | } | |
802f84ff JR |
480 | classifier_publish(&cls); |
481 | ovs_mutex_unlock(&mutex); | |
a36de779 | 482 | seq_change(tnl_conf_seq); |
d9b4ebc5 PS |
483 | } |
484 | ||
485 | /* May not be called more than once. */ | |
486 | void | |
1bc50ef3 | 487 | ovs_router_init(void) |
d9b4ebc5 PS |
488 | { |
489 | classifier_init(&cls, NULL); | |
ed52ca57 | 490 | unixctl_command_register("ovs/route/add", "ip_addr/prefix_len out_br_name [gw] [pkt_mark=mark]", 2, 4, |
d9b4ebc5 PS |
491 | ovs_router_add, NULL); |
492 | unixctl_command_register("ovs/route/show", "", 0, 0, ovs_router_show, NULL); | |
ed52ca57 PS |
493 | unixctl_command_register("ovs/route/del", "ip_addr/prefix_len [pkt_mark=mark]", 1, 2, |
494 | ovs_router_del, NULL); | |
495 | unixctl_command_register("ovs/route/lookup", "ip_addr [pkt_mark=mark]", 1, 2, | |
bc786797 | 496 | ovs_router_lookup_cmd, NULL); |
d9b4ebc5 | 497 | } |