]>
Commit | Line | Data |
---|---|---|
96fba48f BP |
1 | /* |
2 | * Copyright (c) 2008, 2009 Nicira Networks. | |
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 | #include "dpif.h" | |
19 | ||
20 | #include <assert.h> | |
21 | #include <ctype.h> | |
22 | #include <errno.h> | |
23 | #include <fcntl.h> | |
24 | #include <inttypes.h> | |
25 | #include <net/if.h> | |
26 | #include <linux/ethtool.h> | |
e9e28be3 | 27 | #include <linux/rtnetlink.h> |
96fba48f BP |
28 | #include <linux/sockios.h> |
29 | #include <stdlib.h> | |
30 | #include <sys/ioctl.h> | |
31 | #include <unistd.h> | |
32 | ||
33 | #include "dpif-provider.h" | |
e9e28be3 | 34 | #include "netdev-linux.h" |
96fba48f BP |
35 | #include "ofpbuf.h" |
36 | #include "poll-loop.h" | |
e9e28be3 | 37 | #include "svec.h" |
96fba48f BP |
38 | #include "util.h" |
39 | ||
40 | #include "vlog.h" | |
41 | #define THIS_MODULE VLM_dpif_linux | |
42 | ||
43 | /* Datapath interface for the openvswitch Linux kernel module. */ | |
44 | struct dpif_linux { | |
45 | struct dpif dpif; | |
46 | int fd; | |
e9e28be3 BP |
47 | |
48 | /* Change notification. */ | |
49 | int local_ifindex; /* Ifindex of local port. */ | |
50 | struct svec changed_ports; /* Ports that have changed. */ | |
51 | struct linux_netdev_notifier port_notifier; | |
96fba48f BP |
52 | }; |
53 | ||
54 | static struct vlog_rate_limit error_rl = VLOG_RATE_LIMIT_INIT(9999, 5); | |
55 | ||
56 | static int do_ioctl(const struct dpif *, int cmd, const void *arg); | |
57 | static int lookup_minor(const char *name, int *minor); | |
e9e28be3 | 58 | static int finish_open(struct dpif *, const char *local_ifname); |
96fba48f BP |
59 | static int create_minor(const char *name, int minor, struct dpif **dpifp); |
60 | static int open_minor(int minor, struct dpif **dpifp); | |
61 | static int make_openvswitch_device(int minor, char **fnp); | |
e9e28be3 BP |
62 | static void dpif_linux_port_changed(const struct linux_netdev_change *, |
63 | void *dpif); | |
96fba48f BP |
64 | |
65 | static struct dpif_linux * | |
66 | dpif_linux_cast(const struct dpif *dpif) | |
67 | { | |
68 | dpif_assert_class(dpif, &dpif_linux_class); | |
69 | return CONTAINER_OF(dpif, struct dpif_linux, dpif); | |
70 | } | |
71 | ||
e9e28be3 BP |
72 | static void |
73 | dpif_linux_run(void) | |
74 | { | |
75 | linux_netdev_notifier_run(); | |
76 | } | |
77 | ||
78 | static void | |
79 | dpif_linux_wait(void) | |
80 | { | |
81 | linux_netdev_notifier_wait(); | |
82 | } | |
83 | ||
96fba48f BP |
84 | static int |
85 | dpif_linux_open(const char *name UNUSED, char *suffix, bool create, | |
86 | struct dpif **dpifp) | |
87 | { | |
88 | int minor; | |
89 | ||
90 | minor = !strncmp(name, "dp", 2) && isdigit(name[2]) ? atoi(name + 2) : -1; | |
91 | if (create) { | |
92 | if (minor >= 0) { | |
93 | return create_minor(suffix, minor, dpifp); | |
94 | } else { | |
95 | /* Scan for unused minor number. */ | |
96 | for (minor = 0; minor < ODP_MAX; minor++) { | |
97 | int error = create_minor(suffix, minor, dpifp); | |
98 | if (error != EBUSY) { | |
99 | return error; | |
100 | } | |
101 | } | |
102 | ||
103 | /* All datapath numbers in use. */ | |
104 | return ENOBUFS; | |
105 | } | |
106 | } else { | |
107 | struct dpif_linux *dpif; | |
e9e28be3 | 108 | struct odp_port port; |
96fba48f BP |
109 | int error; |
110 | ||
111 | if (minor < 0) { | |
112 | error = lookup_minor(suffix, &minor); | |
113 | if (error) { | |
114 | return error; | |
115 | } | |
116 | } | |
117 | ||
118 | error = open_minor(minor, dpifp); | |
119 | if (error) { | |
120 | return error; | |
121 | } | |
122 | dpif = dpif_linux_cast(*dpifp); | |
123 | ||
e9e28be3 BP |
124 | /* We need the local port's ifindex for the poll function. Start by |
125 | * getting the local port's name. */ | |
126 | memset(&port, 0, sizeof port); | |
127 | port.port = ODPP_LOCAL; | |
128 | if (ioctl(dpif->fd, ODP_PORT_QUERY, &port)) { | |
129 | error = errno; | |
96fba48f BP |
130 | if (error != ENODEV) { |
131 | VLOG_WARN("%s: probe returned unexpected error: %s", | |
132 | dpif_name(*dpifp), strerror(error)); | |
133 | } | |
134 | dpif_close(*dpifp); | |
e9e28be3 | 135 | return error; |
96fba48f | 136 | } |
e9e28be3 BP |
137 | |
138 | /* Then use that to finish up opening. */ | |
139 | return finish_open(&dpif->dpif, port.devname); | |
96fba48f BP |
140 | } |
141 | } | |
142 | ||
143 | static void | |
144 | dpif_linux_close(struct dpif *dpif_) | |
145 | { | |
146 | struct dpif_linux *dpif = dpif_linux_cast(dpif_); | |
e9e28be3 BP |
147 | linux_netdev_notifier_unregister(&dpif->port_notifier); |
148 | svec_destroy(&dpif->changed_ports); | |
96fba48f BP |
149 | close(dpif->fd); |
150 | free(dpif); | |
151 | } | |
152 | ||
153 | static int | |
154 | dpif_linux_delete(struct dpif *dpif_) | |
155 | { | |
156 | return do_ioctl(dpif_, ODP_DP_DESTROY, NULL); | |
157 | } | |
158 | ||
159 | static int | |
160 | dpif_linux_get_stats(const struct dpif *dpif_, struct odp_stats *stats) | |
161 | { | |
162 | return do_ioctl(dpif_, ODP_DP_STATS, stats); | |
163 | } | |
164 | ||
165 | static int | |
166 | dpif_linux_get_drop_frags(const struct dpif *dpif_, bool *drop_fragsp) | |
167 | { | |
168 | int drop_frags; | |
169 | int error; | |
170 | ||
171 | error = do_ioctl(dpif_, ODP_GET_DROP_FRAGS, &drop_frags); | |
172 | if (!error) { | |
173 | *drop_fragsp = drop_frags & 1; | |
174 | } | |
175 | return error; | |
176 | } | |
177 | ||
178 | static int | |
179 | dpif_linux_set_drop_frags(struct dpif *dpif_, bool drop_frags) | |
180 | { | |
181 | int drop_frags_int = drop_frags; | |
182 | return do_ioctl(dpif_, ODP_SET_DROP_FRAGS, &drop_frags_int); | |
183 | } | |
184 | ||
185 | static int | |
186 | dpif_linux_port_add(struct dpif *dpif_, const char *devname, uint16_t flags, | |
187 | uint16_t *port_no) | |
188 | { | |
189 | struct odp_port port; | |
190 | int error; | |
191 | ||
192 | memset(&port, 0, sizeof port); | |
193 | strncpy(port.devname, devname, sizeof port.devname); | |
194 | port.flags = flags; | |
195 | error = do_ioctl(dpif_, ODP_PORT_ADD, &port); | |
196 | if (!error) { | |
197 | *port_no = port.port; | |
198 | } | |
199 | return error; | |
200 | } | |
201 | ||
202 | static int | |
203 | dpif_linux_port_del(struct dpif *dpif_, uint16_t port_no) | |
204 | { | |
205 | int tmp = port_no; | |
206 | return do_ioctl(dpif_, ODP_PORT_DEL, &tmp); | |
207 | } | |
208 | ||
209 | static int | |
210 | dpif_linux_port_query_by_number(const struct dpif *dpif_, uint16_t port_no, | |
211 | struct odp_port *port) | |
212 | { | |
213 | memset(port, 0, sizeof *port); | |
214 | port->port = port_no; | |
215 | return do_ioctl(dpif_, ODP_PORT_QUERY, port); | |
216 | } | |
217 | ||
218 | static int | |
219 | dpif_linux_port_query_by_name(const struct dpif *dpif_, const char *devname, | |
220 | struct odp_port *port) | |
221 | { | |
222 | memset(port, 0, sizeof *port); | |
223 | strncpy(port->devname, devname, sizeof port->devname); | |
224 | return do_ioctl(dpif_, ODP_PORT_QUERY, port); | |
225 | } | |
226 | ||
227 | static int | |
228 | dpif_linux_flow_flush(struct dpif *dpif_) | |
229 | { | |
230 | return do_ioctl(dpif_, ODP_FLOW_FLUSH, NULL); | |
231 | } | |
232 | ||
233 | static int | |
234 | dpif_linux_port_list(const struct dpif *dpif_, struct odp_port *ports, int n) | |
235 | { | |
236 | struct odp_portvec pv; | |
237 | int error; | |
238 | ||
239 | pv.ports = ports; | |
240 | pv.n_ports = n; | |
241 | error = do_ioctl(dpif_, ODP_PORT_LIST, &pv); | |
242 | return error ? -error : pv.n_ports; | |
243 | } | |
244 | ||
e9e28be3 BP |
245 | static int |
246 | dpif_linux_port_poll(const struct dpif *dpif_, char **devnamep) | |
247 | { | |
248 | struct dpif_linux *dpif = dpif_linux_cast(dpif_); | |
249 | int error; | |
250 | ||
251 | error = linux_netdev_notifier_get_error(&dpif->port_notifier); | |
252 | if (!error) { | |
253 | if (!dpif->changed_ports.n) { | |
254 | return EAGAIN; | |
255 | } | |
256 | *devnamep = dpif->changed_ports.names[--dpif->changed_ports.n]; | |
257 | } else { | |
258 | svec_clear(&dpif->changed_ports); | |
259 | } | |
260 | return error; | |
261 | } | |
262 | ||
263 | static void | |
264 | dpif_linux_port_poll_wait(const struct dpif *dpif_) | |
265 | { | |
266 | struct dpif_linux *dpif = dpif_linux_cast(dpif_); | |
267 | if (dpif->changed_ports.n | |
268 | || linux_netdev_notifier_peek_error(&dpif->port_notifier)) { | |
269 | poll_immediate_wake(); | |
270 | } else { | |
271 | linux_netdev_notifier_wait(); | |
272 | } | |
273 | } | |
274 | ||
96fba48f BP |
275 | static int |
276 | dpif_linux_port_group_get(const struct dpif *dpif_, int group, | |
277 | uint16_t ports[], int n) | |
278 | { | |
279 | struct odp_port_group pg; | |
280 | int error; | |
281 | ||
282 | assert(n <= UINT16_MAX); | |
283 | pg.group = group; | |
284 | pg.ports = ports; | |
285 | pg.n_ports = n; | |
286 | error = do_ioctl(dpif_, ODP_PORT_GROUP_GET, &pg); | |
287 | return error ? -error : pg.n_ports; | |
288 | } | |
289 | ||
290 | static int | |
291 | dpif_linux_port_group_set(struct dpif *dpif_, int group, | |
292 | const uint16_t ports[], int n) | |
293 | { | |
294 | struct odp_port_group pg; | |
295 | ||
296 | assert(n <= UINT16_MAX); | |
297 | pg.group = group; | |
298 | pg.ports = (uint16_t *) ports; | |
299 | pg.n_ports = n; | |
300 | return do_ioctl(dpif_, ODP_PORT_GROUP_SET, &pg); | |
301 | } | |
302 | ||
303 | static int | |
304 | dpif_linux_flow_get(const struct dpif *dpif_, struct odp_flow flows[], int n) | |
305 | { | |
306 | struct odp_flowvec fv; | |
307 | fv.flows = flows; | |
308 | fv.n_flows = n; | |
309 | return do_ioctl(dpif_, ODP_FLOW_GET, &fv); | |
310 | } | |
311 | ||
312 | static int | |
313 | dpif_linux_flow_put(struct dpif *dpif_, struct odp_flow_put *put) | |
314 | { | |
315 | return do_ioctl(dpif_, ODP_FLOW_PUT, put); | |
316 | } | |
317 | ||
318 | static int | |
319 | dpif_linux_flow_del(struct dpif *dpif_, struct odp_flow *flow) | |
320 | { | |
321 | return do_ioctl(dpif_, ODP_FLOW_DEL, flow); | |
322 | } | |
323 | ||
324 | static int | |
325 | dpif_linux_flow_list(const struct dpif *dpif_, struct odp_flow flows[], int n) | |
326 | { | |
327 | struct odp_flowvec fv; | |
328 | int error; | |
329 | ||
330 | fv.flows = flows; | |
331 | fv.n_flows = n; | |
332 | error = do_ioctl(dpif_, ODP_FLOW_LIST, &fv); | |
333 | return error ? -error : fv.n_flows; | |
334 | } | |
335 | ||
336 | static int | |
337 | dpif_linux_execute(struct dpif *dpif_, uint16_t in_port, | |
338 | const union odp_action actions[], int n_actions, | |
339 | const struct ofpbuf *buf) | |
340 | { | |
341 | struct odp_execute execute; | |
342 | memset(&execute, 0, sizeof execute); | |
343 | execute.in_port = in_port; | |
344 | execute.actions = (union odp_action *) actions; | |
345 | execute.n_actions = n_actions; | |
346 | execute.data = buf->data; | |
347 | execute.length = buf->size; | |
348 | return do_ioctl(dpif_, ODP_EXECUTE, &execute); | |
349 | } | |
350 | ||
351 | static int | |
352 | dpif_linux_recv_get_mask(const struct dpif *dpif_, int *listen_mask) | |
353 | { | |
354 | return do_ioctl(dpif_, ODP_GET_LISTEN_MASK, listen_mask); | |
355 | } | |
356 | ||
357 | static int | |
358 | dpif_linux_recv_set_mask(struct dpif *dpif_, int listen_mask) | |
359 | { | |
360 | return do_ioctl(dpif_, ODP_SET_LISTEN_MASK, &listen_mask); | |
361 | } | |
362 | ||
363 | static int | |
364 | dpif_linux_recv(struct dpif *dpif_, struct ofpbuf **bufp) | |
365 | { | |
366 | struct dpif_linux *dpif = dpif_linux_cast(dpif_); | |
367 | struct ofpbuf *buf; | |
368 | int retval; | |
369 | int error; | |
370 | ||
371 | buf = ofpbuf_new(65536); | |
372 | retval = read(dpif->fd, ofpbuf_tail(buf), ofpbuf_tailroom(buf)); | |
373 | if (retval < 0) { | |
374 | error = errno; | |
375 | if (error != EAGAIN) { | |
376 | VLOG_WARN_RL(&error_rl, "%s: read failed: %s", | |
377 | dpif_name(dpif_), strerror(error)); | |
378 | } | |
379 | } else if (retval >= sizeof(struct odp_msg)) { | |
380 | struct odp_msg *msg = buf->data; | |
381 | if (msg->length <= retval) { | |
382 | buf->size += retval; | |
383 | *bufp = buf; | |
384 | return 0; | |
385 | } else { | |
386 | VLOG_WARN_RL(&error_rl, "%s: discarding message truncated " | |
387 | "from %zu bytes to %d", | |
388 | dpif_name(dpif_), msg->length, retval); | |
389 | error = ERANGE; | |
390 | } | |
391 | } else if (!retval) { | |
392 | VLOG_WARN_RL(&error_rl, "%s: unexpected end of file", dpif_name(dpif_)); | |
393 | error = EPROTO; | |
394 | } else { | |
395 | VLOG_WARN_RL(&error_rl, | |
396 | "%s: discarding too-short message (%d bytes)", | |
397 | dpif_name(dpif_), retval); | |
398 | error = ERANGE; | |
399 | } | |
400 | ||
401 | *bufp = NULL; | |
402 | ofpbuf_delete(buf); | |
403 | return error; | |
404 | } | |
405 | ||
406 | static void | |
407 | dpif_linux_recv_wait(struct dpif *dpif_) | |
408 | { | |
409 | struct dpif_linux *dpif = dpif_linux_cast(dpif_); | |
410 | poll_fd_wait(dpif->fd, POLLIN); | |
411 | } | |
412 | ||
413 | const struct dpif_class dpif_linux_class = { | |
414 | "", /* This is the default class. */ | |
415 | "linux", | |
e9e28be3 BP |
416 | dpif_linux_run, |
417 | dpif_linux_wait, | |
96fba48f BP |
418 | dpif_linux_open, |
419 | dpif_linux_close, | |
420 | dpif_linux_delete, | |
421 | dpif_linux_get_stats, | |
422 | dpif_linux_get_drop_frags, | |
423 | dpif_linux_set_drop_frags, | |
424 | dpif_linux_port_add, | |
425 | dpif_linux_port_del, | |
426 | dpif_linux_port_query_by_number, | |
427 | dpif_linux_port_query_by_name, | |
428 | dpif_linux_port_list, | |
e9e28be3 BP |
429 | dpif_linux_port_poll, |
430 | dpif_linux_port_poll_wait, | |
96fba48f BP |
431 | dpif_linux_port_group_get, |
432 | dpif_linux_port_group_set, | |
433 | dpif_linux_flow_get, | |
434 | dpif_linux_flow_put, | |
435 | dpif_linux_flow_del, | |
436 | dpif_linux_flow_flush, | |
437 | dpif_linux_flow_list, | |
438 | dpif_linux_execute, | |
439 | dpif_linux_recv_get_mask, | |
440 | dpif_linux_recv_set_mask, | |
441 | dpif_linux_recv, | |
442 | dpif_linux_recv_wait, | |
443 | }; | |
444 | \f | |
445 | static int get_openvswitch_major(void); | |
446 | static int get_major(const char *target, int default_major); | |
447 | ||
448 | static int | |
449 | do_ioctl(const struct dpif *dpif_, int cmd, const void *arg) | |
450 | { | |
451 | struct dpif_linux *dpif = dpif_linux_cast(dpif_); | |
452 | return ioctl(dpif->fd, cmd, arg) ? errno : 0; | |
453 | } | |
454 | ||
455 | static int | |
456 | lookup_minor(const char *name, int *minor) | |
457 | { | |
458 | struct ethtool_drvinfo drvinfo; | |
459 | struct ifreq ifr; | |
460 | int error; | |
461 | int sock; | |
462 | ||
463 | sock = socket(AF_INET, SOCK_DGRAM, 0); | |
464 | if (sock < 0) { | |
465 | VLOG_WARN("socket(AF_INET) failed: %s", strerror(errno)); | |
466 | error = errno; | |
467 | goto error; | |
468 | } | |
469 | ||
470 | memset(&ifr, 0, sizeof ifr); | |
471 | strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name); | |
472 | ifr.ifr_data = (caddr_t) &drvinfo; | |
473 | ||
474 | memset(&drvinfo, 0, sizeof drvinfo); | |
475 | drvinfo.cmd = ETHTOOL_GDRVINFO; | |
476 | if (ioctl(sock, SIOCETHTOOL, &ifr)) { | |
477 | VLOG_WARN("ioctl(SIOCETHTOOL) failed: %s", strerror(errno)); | |
478 | error = errno; | |
479 | goto error_close_sock; | |
480 | } | |
481 | ||
482 | if (strcmp(drvinfo.driver, "openvswitch")) { | |
483 | VLOG_WARN("%s is not an openvswitch device", name); | |
484 | error = EOPNOTSUPP; | |
485 | goto error_close_sock; | |
486 | } | |
487 | ||
488 | if (!isdigit(drvinfo.bus_info[0])) { | |
489 | VLOG_WARN("%s ethtool info does not contain an openvswitch minor", | |
490 | name); | |
491 | error = EPROTOTYPE; | |
492 | goto error_close_sock; | |
493 | } | |
494 | ||
495 | *minor = atoi(drvinfo.bus_info); | |
496 | close(sock); | |
497 | return 0; | |
498 | ||
499 | error_close_sock: | |
500 | close(sock); | |
501 | error: | |
502 | return error; | |
503 | } | |
504 | ||
505 | static int | |
506 | make_openvswitch_device(int minor, char **fnp) | |
507 | { | |
508 | dev_t dev = makedev(get_openvswitch_major(), minor); | |
509 | const char dirname[] = "/dev/net"; | |
510 | struct stat s; | |
511 | char fn[128]; | |
512 | ||
513 | *fnp = NULL; | |
514 | sprintf(fn, "%s/dp%d", dirname, minor); | |
515 | if (!stat(fn, &s)) { | |
516 | if (!S_ISCHR(s.st_mode)) { | |
517 | VLOG_WARN_RL(&error_rl, "%s is not a character device, fixing", | |
518 | fn); | |
519 | } else if (s.st_rdev != dev) { | |
520 | VLOG_WARN_RL(&error_rl, | |
521 | "%s is device %u:%u instead of %u:%u, fixing", | |
522 | fn, major(s.st_rdev), minor(s.st_rdev), | |
523 | major(dev), minor(dev)); | |
524 | } else { | |
525 | goto success; | |
526 | } | |
527 | if (unlink(fn)) { | |
528 | VLOG_WARN_RL(&error_rl, "%s: unlink failed (%s)", | |
529 | fn, strerror(errno)); | |
530 | return errno; | |
531 | } | |
532 | } else if (errno == ENOENT) { | |
533 | if (stat(dirname, &s)) { | |
534 | if (errno == ENOENT) { | |
535 | if (mkdir(dirname, 0755)) { | |
536 | VLOG_WARN_RL(&error_rl, "%s: mkdir failed (%s)", | |
537 | dirname, strerror(errno)); | |
538 | return errno; | |
539 | } | |
540 | } else { | |
541 | VLOG_WARN_RL(&error_rl, "%s: stat failed (%s)", | |
542 | dirname, strerror(errno)); | |
543 | return errno; | |
544 | } | |
545 | } | |
546 | } else { | |
547 | VLOG_WARN_RL(&error_rl, "%s: stat failed (%s)", fn, strerror(errno)); | |
548 | return errno; | |
549 | } | |
550 | ||
551 | /* The device needs to be created. */ | |
552 | if (mknod(fn, S_IFCHR | 0700, dev)) { | |
553 | VLOG_WARN_RL(&error_rl, | |
554 | "%s: creating character device %u:%u failed (%s)", | |
555 | fn, major(dev), minor(dev), strerror(errno)); | |
556 | return errno; | |
557 | } | |
558 | ||
559 | success: | |
560 | *fnp = xstrdup(fn); | |
561 | return 0; | |
562 | } | |
563 | ||
564 | ||
565 | static int | |
566 | get_openvswitch_major(void) | |
567 | { | |
568 | static unsigned int openvswitch_major; | |
569 | if (!openvswitch_major) { | |
570 | enum { DEFAULT_MAJOR = 248 }; | |
571 | openvswitch_major = get_major("openvswitch", DEFAULT_MAJOR); | |
572 | } | |
573 | return openvswitch_major; | |
574 | } | |
575 | ||
576 | static int | |
577 | get_major(const char *target, int default_major) | |
578 | { | |
579 | const char fn[] = "/proc/devices"; | |
580 | char line[128]; | |
581 | FILE *file; | |
582 | int ln; | |
583 | ||
584 | file = fopen(fn, "r"); | |
585 | if (!file) { | |
586 | VLOG_ERR("opening %s failed (%s)", fn, strerror(errno)); | |
587 | goto error; | |
588 | } | |
589 | ||
590 | for (ln = 1; fgets(line, sizeof line, file); ln++) { | |
591 | char name[64]; | |
592 | int major; | |
593 | ||
594 | if (!strncmp(line, "Character", 9) || line[0] == '\0') { | |
595 | /* Nothing to do. */ | |
596 | } else if (!strncmp(line, "Block", 5)) { | |
597 | /* We only want character devices, so skip the rest of the file. */ | |
598 | break; | |
599 | } else if (sscanf(line, "%d %63s", &major, name)) { | |
600 | if (!strcmp(name, target)) { | |
601 | fclose(file); | |
602 | return major; | |
603 | } | |
604 | } else { | |
605 | static bool warned; | |
606 | if (!warned) { | |
607 | VLOG_WARN("%s:%d: syntax error", fn, ln); | |
608 | } | |
609 | warned = true; | |
610 | } | |
611 | } | |
612 | ||
613 | VLOG_ERR("%s: %s major not found (is the module loaded?), using " | |
614 | "default major %d", fn, target, default_major); | |
615 | error: | |
616 | VLOG_INFO("using default major %d for %s", default_major, target); | |
617 | return default_major; | |
618 | } | |
619 | ||
e9e28be3 BP |
620 | static int |
621 | finish_open(struct dpif *dpif_, const char *local_ifname) | |
622 | { | |
623 | struct dpif_linux *dpif = dpif_linux_cast(dpif_); | |
624 | dpif->local_ifindex = if_nametoindex(local_ifname); | |
625 | if (!dpif->local_ifindex) { | |
626 | int error = errno; | |
627 | dpif_close(dpif_); | |
628 | VLOG_WARN("could not get ifindex of %s device: %s", | |
629 | local_ifname, strerror(errno)); | |
630 | return error; | |
631 | } | |
632 | return 0; | |
633 | } | |
634 | ||
96fba48f BP |
635 | static int |
636 | create_minor(const char *name, int minor, struct dpif **dpifp) | |
637 | { | |
638 | int error = open_minor(minor, dpifp); | |
639 | if (!error) { | |
640 | error = do_ioctl(*dpifp, ODP_DP_CREATE, name); | |
e9e28be3 BP |
641 | if (!error) { |
642 | error = finish_open(*dpifp, name); | |
643 | } else { | |
96fba48f BP |
644 | dpif_close(*dpifp); |
645 | } | |
646 | } | |
647 | return error; | |
648 | } | |
649 | ||
650 | static int | |
651 | open_minor(int minor, struct dpif **dpifp) | |
652 | { | |
653 | int error; | |
654 | char *fn; | |
655 | int fd; | |
656 | ||
657 | error = make_openvswitch_device(minor, &fn); | |
658 | if (error) { | |
659 | return error; | |
660 | } | |
661 | ||
662 | fd = open(fn, O_RDONLY | O_NONBLOCK); | |
663 | if (fd >= 0) { | |
e9e28be3 BP |
664 | struct dpif_linux *dpif = xmalloc(sizeof *dpif); |
665 | error = linux_netdev_notifier_register(&dpif->port_notifier, | |
666 | dpif_linux_port_changed, dpif); | |
667 | if (!error) { | |
668 | char *name; | |
669 | ||
670 | name = xasprintf("dp%d", minor); | |
671 | dpif_init(&dpif->dpif, &dpif_linux_class, name, minor, minor); | |
672 | free(name); | |
673 | ||
674 | dpif->fd = fd; | |
675 | dpif->local_ifindex = 0; | |
676 | svec_init(&dpif->changed_ports); | |
677 | *dpifp = &dpif->dpif; | |
678 | } else { | |
679 | free(dpif); | |
680 | } | |
96fba48f BP |
681 | } else { |
682 | error = errno; | |
683 | VLOG_WARN("%s: open failed (%s)", fn, strerror(error)); | |
684 | } | |
685 | free(fn); | |
686 | ||
687 | return error; | |
688 | } | |
e9e28be3 BP |
689 | |
690 | static void | |
691 | dpif_linux_port_changed(const struct linux_netdev_change *change, void *dpif_) | |
692 | { | |
693 | struct dpif_linux *dpif = dpif_; | |
694 | ||
695 | if (change->master_ifindex == dpif->local_ifindex | |
696 | && (change->nlmsg_type == RTM_NEWLINK | |
697 | || change->nlmsg_type == RTM_DELLINK)) | |
698 | { | |
699 | /* Our datapath changed, either adding a new port or deleting an | |
700 | * existing one. */ | |
701 | if (!svec_contains(&dpif->changed_ports, change->ifname)) { | |
702 | svec_add(&dpif->changed_ports, change->ifname); | |
703 | svec_sort(&dpif->changed_ports); | |
704 | } | |
705 | } | |
706 | } |