]>
Commit | Line | Data |
---|---|---|
a36de779 | 1 | /* |
b70e6976 | 2 | * Copyright (c) 2014, 2015, 2016 Nicira, Inc. |
a36de779 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> | |
ccaa81e0 | 18 | |
53902038 | 19 | #include "tnl-neigh-cache.h" |
ccaa81e0 | 20 | |
a36de779 | 21 | #include <inttypes.h> |
57b0dbe3 YT |
22 | #include <sys/types.h> |
23 | #include <netinet/in.h> | |
4673650f | 24 | #include <netinet/icmp6.h> |
a36de779 PS |
25 | #include <stdlib.h> |
26 | ||
27 | #include "bitmap.h" | |
28 | #include "cmap.h" | |
29 | #include "coverage.h" | |
30 | #include "dpif-netdev.h" | |
3e8a2ad1 | 31 | #include "openvswitch/dynamic-string.h" |
a36de779 PS |
32 | #include "errno.h" |
33 | #include "flow.h" | |
34 | #include "netdev.h" | |
35 | #include "ovs-thread.h" | |
36 | #include "packets.h" | |
fd016ae3 | 37 | #include "openvswitch/poll-loop.h" |
a36de779 | 38 | #include "seq.h" |
3514c765 | 39 | #include "socket-util.h" |
a36de779 | 40 | #include "timeval.h" |
a36de779 PS |
41 | #include "unaligned.h" |
42 | #include "unixctl.h" | |
43 | #include "util.h" | |
e6211adc | 44 | #include "openvswitch/vlog.h" |
a36de779 PS |
45 | |
46 | ||
47 | /* In seconds */ | |
53902038 | 48 | #define NEIGH_ENTRY_DEFAULT_IDLE_TIME (15 * 60) |
a36de779 | 49 | |
53902038 | 50 | struct tnl_neigh_entry { |
a36de779 | 51 | struct cmap_node cmap_node; |
4673650f | 52 | struct in6_addr ip; |
74ff3298 | 53 | struct eth_addr mac; |
a36de779 PS |
54 | time_t expires; /* Expiration time. */ |
55 | char br_name[IFNAMSIZ]; | |
56 | }; | |
57 | ||
b70e6976 | 58 | static struct cmap table = CMAP_INITIALIZER; |
a36de779 PS |
59 | static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; |
60 | ||
4673650f | 61 | static uint32_t |
53902038 | 62 | tnl_neigh_hash(const struct in6_addr *ip) |
4673650f TLSC |
63 | { |
64 | return hash_bytes(ip->s6_addr, 16, 0); | |
65 | } | |
66 | ||
53902038 TLSC |
67 | static struct tnl_neigh_entry * |
68 | tnl_neigh_lookup__(const char br_name[IFNAMSIZ], const struct in6_addr *dst) | |
a36de779 | 69 | { |
53902038 | 70 | struct tnl_neigh_entry *neigh; |
4673650f | 71 | uint32_t hash; |
a36de779 | 72 | |
53902038 TLSC |
73 | hash = tnl_neigh_hash(dst); |
74 | CMAP_FOR_EACH_WITH_HASH (neigh, cmap_node, hash, &table) { | |
75 | if (ipv6_addr_equals(&neigh->ip, dst) && !strcmp(neigh->br_name, br_name)) { | |
09d364af PS |
76 | if (neigh->expires <= time_now()) { |
77 | return NULL; | |
78 | } | |
79 | ||
53902038 TLSC |
80 | neigh->expires = time_now() + NEIGH_ENTRY_DEFAULT_IDLE_TIME; |
81 | return neigh; | |
a36de779 PS |
82 | } |
83 | } | |
84 | return NULL; | |
85 | } | |
86 | ||
4673650f | 87 | int |
53902038 TLSC |
88 | tnl_neigh_lookup(const char br_name[IFNAMSIZ], const struct in6_addr *dst, |
89 | struct eth_addr *mac) | |
4673650f | 90 | { |
53902038 | 91 | struct tnl_neigh_entry *neigh; |
4673650f TLSC |
92 | int res = ENOENT; |
93 | ||
53902038 TLSC |
94 | neigh = tnl_neigh_lookup__(br_name, dst); |
95 | if (neigh) { | |
96 | *mac = neigh->mac; | |
4673650f TLSC |
97 | res = 0; |
98 | } | |
99 | return res; | |
100 | } | |
101 | ||
a36de779 | 102 | static void |
53902038 | 103 | neigh_entry_free(struct tnl_neigh_entry *neigh) |
a36de779 | 104 | { |
53902038 | 105 | free(neigh); |
a36de779 PS |
106 | } |
107 | ||
108 | static void | |
53902038 | 109 | tnl_neigh_delete(struct tnl_neigh_entry *neigh) |
a36de779 | 110 | { |
53902038 TLSC |
111 | uint32_t hash = tnl_neigh_hash(&neigh->ip); |
112 | cmap_remove(&table, &neigh->cmap_node, hash); | |
113 | ovsrcu_postpone(neigh_entry_free, neigh); | |
a36de779 PS |
114 | } |
115 | ||
3514c765 | 116 | static void |
53902038 | 117 | tnl_neigh_set__(const char name[IFNAMSIZ], const struct in6_addr *dst, |
05bb9148 | 118 | const struct eth_addr mac) |
a36de779 | 119 | { |
a36de779 | 120 | ovs_mutex_lock(&mutex); |
53902038 TLSC |
121 | struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst); |
122 | if (neigh) { | |
123 | if (eth_addr_equals(neigh->mac, mac)) { | |
124 | neigh->expires = time_now() + NEIGH_ENTRY_DEFAULT_IDLE_TIME; | |
a36de779 | 125 | ovs_mutex_unlock(&mutex); |
3514c765 | 126 | return; |
a36de779 | 127 | } |
53902038 | 128 | tnl_neigh_delete(neigh); |
a36de779 | 129 | } |
6fd4b2a3 | 130 | seq_change(tnl_conf_seq); |
a36de779 | 131 | |
53902038 | 132 | neigh = xmalloc(sizeof *neigh); |
a36de779 | 133 | |
53902038 TLSC |
134 | neigh->ip = *dst; |
135 | neigh->mac = mac; | |
136 | neigh->expires = time_now() + NEIGH_ENTRY_DEFAULT_IDLE_TIME; | |
137 | ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name); | |
138 | cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip)); | |
a36de779 | 139 | ovs_mutex_unlock(&mutex); |
3514c765 BP |
140 | } |
141 | ||
4673650f TLSC |
142 | static void |
143 | tnl_arp_set(const char name[IFNAMSIZ], ovs_be32 dst, | |
144 | const struct eth_addr mac) | |
145 | { | |
12d0ee08 | 146 | struct in6_addr dst6 = in6_addr_mapped_ipv4(dst); |
53902038 | 147 | tnl_neigh_set__(name, &dst6, mac); |
4673650f TLSC |
148 | } |
149 | ||
53902038 | 150 | static int |
3514c765 BP |
151 | tnl_arp_snoop(const struct flow *flow, struct flow_wildcards *wc, |
152 | const char name[IFNAMSIZ]) | |
153 | { | |
ba07cf22 MKC |
154 | /* Snoop normal ARP replies and gratuitous ARP requests/replies only */ |
155 | if (!is_arp(flow) | |
156 | || (!is_garp(flow, wc) && | |
157 | FLOW_WC_GET_AND_MASK_WC(flow, wc, nw_proto) != ARP_OP_REPLY) | |
05bb9148 | 158 | || eth_addr_is_zero(FLOW_WC_GET_AND_MASK_WC(flow, wc, arp_sha))) { |
3514c765 BP |
159 | return EINVAL; |
160 | } | |
161 | ||
05bb9148 | 162 | tnl_arp_set(name, FLOW_WC_GET_AND_MASK_WC(flow, wc, nw_src), flow->arp_sha); |
4673650f TLSC |
163 | return 0; |
164 | } | |
3514c765 | 165 | |
53902038 | 166 | static int |
4673650f | 167 | tnl_nd_snoop(const struct flow *flow, struct flow_wildcards *wc, |
05bb9148 | 168 | const char name[IFNAMSIZ]) |
4673650f | 169 | { |
05bb9148 | 170 | if (!is_nd(flow, wc) || flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) { |
4673650f TLSC |
171 | return EINVAL; |
172 | } | |
b23ddcc5 PS |
173 | /* - RFC4861 says Neighbor Advertisements sent in response to unicast Neighbor |
174 | * Solicitations SHOULD include the Target link-layer address. However, Linux | |
175 | * doesn't. So, the response to Solicitations sent by OVS will include the | |
176 | * TLL address and other Advertisements not including it can be ignored. | |
177 | * - OVS flow extract can set this field to zero in case of packet parsing errors. | |
178 | * For details refer miniflow_extract()*/ | |
05bb9148 | 179 | if (eth_addr_is_zero(FLOW_WC_GET_AND_MASK_WC(flow, wc, arp_tha))) { |
b23ddcc5 PS |
180 | return EINVAL; |
181 | } | |
4673650f TLSC |
182 | |
183 | memset(&wc->masks.ipv6_src, 0xff, sizeof wc->masks.ipv6_src); | |
184 | memset(&wc->masks.ipv6_dst, 0xff, sizeof wc->masks.ipv6_dst); | |
185 | memset(&wc->masks.nd_target, 0xff, sizeof wc->masks.nd_target); | |
4673650f | 186 | |
53902038 | 187 | tnl_neigh_set__(name, &flow->nd_target, flow->arp_tha); |
a36de779 PS |
188 | return 0; |
189 | } | |
190 | ||
53902038 TLSC |
191 | int |
192 | tnl_neigh_snoop(const struct flow *flow, struct flow_wildcards *wc, | |
193 | const char name[IFNAMSIZ]) | |
194 | { | |
195 | int res; | |
196 | res = tnl_arp_snoop(flow, wc, name); | |
197 | if (res != EINVAL) { | |
198 | return res; | |
199 | } | |
200 | return tnl_nd_snoop(flow, wc, name); | |
201 | } | |
202 | ||
a36de779 | 203 | void |
53902038 | 204 | tnl_neigh_cache_run(void) |
a36de779 | 205 | { |
53902038 | 206 | struct tnl_neigh_entry *neigh; |
a36de779 PS |
207 | bool changed = false; |
208 | ||
209 | ovs_mutex_lock(&mutex); | |
53902038 TLSC |
210 | CMAP_FOR_EACH(neigh, cmap_node, &table) { |
211 | if (neigh->expires <= time_now()) { | |
212 | tnl_neigh_delete(neigh); | |
08a95643 | 213 | changed = true; |
a36de779 PS |
214 | } |
215 | } | |
216 | ovs_mutex_unlock(&mutex); | |
217 | ||
218 | if (changed) { | |
219 | seq_change(tnl_conf_seq); | |
220 | } | |
221 | } | |
222 | ||
223 | static void | |
53902038 | 224 | tnl_neigh_cache_flush(struct unixctl_conn *conn, int argc OVS_UNUSED, |
a36de779 PS |
225 | const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) |
226 | { | |
53902038 | 227 | struct tnl_neigh_entry *neigh; |
a36de779 PS |
228 | bool changed = false; |
229 | ||
230 | ovs_mutex_lock(&mutex); | |
53902038 TLSC |
231 | CMAP_FOR_EACH(neigh, cmap_node, &table) { |
232 | tnl_neigh_delete(neigh); | |
08a95643 | 233 | changed = true; |
a36de779 PS |
234 | } |
235 | ovs_mutex_unlock(&mutex); | |
236 | if (changed) { | |
237 | seq_change(tnl_conf_seq); | |
238 | } | |
239 | unixctl_command_reply(conn, "OK"); | |
240 | } | |
241 | ||
5f639b68 TLSC |
242 | static int |
243 | lookup_any(const char *host_name, struct in6_addr *address) | |
244 | { | |
245 | if (addr_is_ipv6(host_name)) { | |
246 | return lookup_ipv6(host_name, address); | |
247 | } else { | |
248 | int r; | |
249 | struct in_addr ip; | |
250 | r = lookup_ip(host_name, &ip); | |
251 | if (r == 0) { | |
252 | in6_addr_set_mapped_ipv4(address, ip.s_addr); | |
253 | } | |
254 | return r; | |
255 | } | |
256 | return ENOENT; | |
257 | } | |
258 | ||
3514c765 | 259 | static void |
53902038 TLSC |
260 | tnl_neigh_cache_add(struct unixctl_conn *conn, int argc OVS_UNUSED, |
261 | const char *argv[], void *aux OVS_UNUSED) | |
3514c765 BP |
262 | { |
263 | const char *br_name = argv[1]; | |
264 | struct eth_addr mac; | |
4673650f | 265 | struct in6_addr ip6; |
3514c765 | 266 | |
5f639b68 | 267 | if (lookup_any(argv[2], &ip6) != 0) { |
3514c765 BP |
268 | unixctl_command_reply_error(conn, "bad IP address"); |
269 | return; | |
270 | } | |
271 | ||
272 | if (!eth_addr_from_string(argv[3], &mac)) { | |
273 | unixctl_command_reply_error(conn, "bad MAC address"); | |
274 | return; | |
275 | } | |
276 | ||
53902038 | 277 | tnl_neigh_set__(br_name, &ip6, mac); |
3514c765 BP |
278 | unixctl_command_reply(conn, "OK"); |
279 | } | |
280 | ||
a36de779 | 281 | static void |
53902038 TLSC |
282 | tnl_neigh_cache_show(struct unixctl_conn *conn, int argc OVS_UNUSED, |
283 | const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) | |
a36de779 PS |
284 | { |
285 | struct ds ds = DS_EMPTY_INITIALIZER; | |
53902038 | 286 | struct tnl_neigh_entry *neigh; |
a36de779 | 287 | |
4673650f TLSC |
288 | ds_put_cstr(&ds, "IP MAC Bridge\n"); |
289 | ds_put_cstr(&ds, "==========================================================================\n"); | |
a36de779 | 290 | ovs_mutex_lock(&mutex); |
53902038 | 291 | CMAP_FOR_EACH(neigh, cmap_node, &table) { |
a36de779 PS |
292 | int start_len, need_ws; |
293 | ||
294 | start_len = ds.length; | |
53902038 | 295 | ipv6_format_mapped(&neigh->ip, &ds); |
a36de779 | 296 | |
4673650f | 297 | need_ws = INET6_ADDRSTRLEN - (ds.length - start_len); |
a36de779 PS |
298 | ds_put_char_multiple(&ds, ' ', need_ws); |
299 | ||
09d364af | 300 | ds_put_format(&ds, ETH_ADDR_FMT" %s", |
53902038 | 301 | ETH_ADDR_ARGS(neigh->mac), neigh->br_name); |
09d364af PS |
302 | if (neigh->expires <= time_now()) { |
303 | ds_put_format(&ds, " STALE"); | |
304 | } | |
305 | ds_put_char(&ds, '\n'); | |
a36de779 PS |
306 | |
307 | } | |
308 | ovs_mutex_unlock(&mutex); | |
309 | unixctl_command_reply(conn, ds_cstr(&ds)); | |
310 | ds_destroy(&ds); | |
311 | } | |
312 | ||
313 | void | |
53902038 | 314 | tnl_neigh_cache_init(void) |
a36de779 | 315 | { |
53902038 TLSC |
316 | unixctl_command_register("tnl/arp/show", "", 0, 0, tnl_neigh_cache_show, NULL); |
317 | unixctl_command_register("tnl/arp/set", "BRIDGE IP MAC", 3, 3, tnl_neigh_cache_add, NULL); | |
318 | unixctl_command_register("tnl/arp/flush", "", 0, 0, tnl_neigh_cache_flush, NULL); | |
319 | unixctl_command_register("tnl/neigh/show", "", 0, 0, tnl_neigh_cache_show, NULL); | |
320 | unixctl_command_register("tnl/neigh/set", "BRIDGE IP MAC", 3, 3, tnl_neigh_cache_add, NULL); | |
321 | unixctl_command_register("tnl/neigh/flush", "", 0, 0, tnl_neigh_cache_flush, NULL); | |
a36de779 | 322 | } |