AUTHORS: Add Kyle Mestery.
[sliver-openvswitch.git] / lib / rtbsd.c
1 /*
2  * Copyright (c) 2011 Gaetano Catalli.
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 <unistd.h>
20 #include <errno.h>
21 #include <sys/socket.h>
22 #include <net/if.h>
23 #include <net/route.h>
24 #include <poll.h>
25
26 #include "coverage.h"
27 #include "socket-util.h"
28 #include "poll-loop.h"
29 #include "vlog.h"
30 #include "rtbsd.h"
31
32 VLOG_DEFINE_THIS_MODULE(rtbsd);
33 COVERAGE_DEFINE(rtbsd_changed);
34
35 /* PF_ROUTE socket. */
36 static int notify_sock = -1;
37
38 /* All registered notifiers. */
39 static struct list all_notifiers = LIST_INITIALIZER(&all_notifiers);
40
41 static void rtbsd_report_change(const struct if_msghdr *);
42 static void rtbsd_report_notify_error(void);
43
44 /* Registers 'cb' to be called with auxiliary data 'aux' with network device
45  * change notifications.  The notifier is stored in 'notifier', which the
46  * caller must not modify or free.
47  *
48  * Returns 0 if successful, otherwise a positive errno value. */
49 int
50 rtbsd_notifier_register(struct rtbsd_notifier *notifier,
51                             rtbsd_notify_func *cb, void *aux)
52 {
53     if (notify_sock < 0) {
54         int error;
55         notify_sock = socket(PF_ROUTE, SOCK_RAW, 0);
56         if (notify_sock < 0) {
57             VLOG_WARN("could not create PF_ROUTE socket: %s",
58                       strerror(errno));
59             return errno;
60         }
61         error = set_nonblocking(notify_sock);
62         if (error) {
63             VLOG_WARN("error set_nonblocking PF_ROUTE socket: %s",
64                     strerror(error));
65             return error;
66         }
67     } else {
68         /* Catch up on notification work so that the new notifier won't
69          * receive any stale notifications. XXX*/
70         rtbsd_notifier_run();
71     }
72
73     list_push_back(&all_notifiers, &notifier->node);
74     notifier->cb = cb;
75     notifier->aux = aux;
76     return 0;
77 }
78
79 /* Cancels notification on 'notifier', which must have previously been
80  * registered with rtbsd_notifier_register(). */
81 void
82 rtbsd_notifier_unregister(struct rtbsd_notifier *notifier)
83 {
84     list_remove(&notifier->node);
85     if (list_is_empty(&all_notifiers)) {
86         close(notify_sock);
87         notify_sock = -1;
88     }
89 }
90
91 /* Calls all of the registered notifiers, passing along any as-yet-unreported
92  * netdev change events. */
93 void
94 rtbsd_notifier_run(void)
95 {
96     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
97     struct if_msghdr msg;
98     if (notify_sock < 0) {
99         return;
100     }
101
102     for (;;) {
103         int retval;
104
105         msg.ifm_type = RTM_IFINFO;
106         msg.ifm_version = RTM_VERSION; //XXX check if necessary
107
108         /* read from PF_ROUTE socket */
109         retval = read(notify_sock, (char *)&msg, sizeof(msg));
110         if (retval >= 0) {
111             /* received packet from PF_ROUTE socket
112              * XXX check for bad packets */
113             if (msg.ifm_type == RTM_IFINFO) {
114                 rtbsd_report_change(&msg);
115             }
116         } else if (errno == EAGAIN) {
117             return;
118         } else {
119             if (errno == ENOBUFS) {
120                 VLOG_WARN_RL(&rl, "PF_ROUTE receive buffer overflowed");
121             } else {
122                 VLOG_WARN_RL(&rl, "error reading PF_ROUTE socket: %s",
123                              strerror(errno));
124             }
125             rtbsd_report_notify_error();
126         }
127     }
128 }
129
130 /* Causes poll_block() to wake up when network device change notifications are
131  * ready. */
132 void
133 rtbsd_notifier_wait(void)
134 {
135     if (notify_sock >= 0) {
136         poll_fd_wait(notify_sock, POLLIN);
137     }
138 }
139
140 static void
141 rtbsd_report_change(const struct if_msghdr *msg)
142 {
143     struct rtbsd_notifier *notifier;
144     struct rtbsd_change change;
145
146     COVERAGE_INC(rtbsd_changed);
147
148     change.msg_type = msg->ifm_type; //XXX
149     change.if_index = msg->ifm_index;
150     if_indextoname(msg->ifm_index, change.if_name);
151     change.master_ifindex = 0; //XXX
152
153     LIST_FOR_EACH (notifier, node, &all_notifiers) {
154         notifier->cb(&change, notifier->aux);
155     }
156 }
157
158 /* If an error occurs the notifiers' callbacks are called with NULL changes */
159 static void
160 rtbsd_report_notify_error(void)
161 {
162     struct rtbsd_notifier *notifier;
163
164     LIST_FOR_EACH (notifier, node, &all_notifiers) {
165         notifier->cb(NULL, notifier->aux);
166     }
167 }