]>
Commit | Line | Data |
---|---|---|
559843ed | 1 | /* |
c2715e8d | 2 | * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2016 Nicira, Inc. |
559843ed BP |
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 | ||
45c8d3a1 | 19 | #include "netlink-notifier.h" |
559843ed BP |
20 | |
21 | #include <errno.h> | |
559843ed | 22 | #include <poll.h> |
8f5b651c | 23 | #include <stdlib.h> |
559843ed BP |
24 | |
25 | #include "coverage.h" | |
26 | #include "netlink.h" | |
2fe27d5a | 27 | #include "netlink-socket.h" |
64c96779 | 28 | #include "openvswitch/ofpbuf.h" |
e6211adc | 29 | #include "openvswitch/vlog.h" |
559843ed | 30 | |
0a811051 | 31 | VLOG_DEFINE_THIS_MODULE(netlink_notifier); |
5136ce49 | 32 | |
0a811051 | 33 | COVERAGE_DEFINE(nln_changed); |
d76f09ea | 34 | |
0a811051 EJ |
35 | struct nln { |
36 | struct nl_sock *notify_sock; /* Netlink socket. */ | |
ca6ba700 | 37 | struct ovs_list all_notifiers; /* All nln notifiers. */ |
4e77828b | 38 | bool has_run; /* Guard for run and wait functions. */ |
21d6e22e | 39 | |
0a811051 | 40 | /* Passed in by nln_create(). */ |
ec9f40dc | 41 | int protocol; /* Protocol passed to nl_sock_create(). */ |
0a811051 | 42 | nln_parse_func *parse; /* Message parsing function. */ |
21d6e22e EJ |
43 | void *change; /* Change passed to parse. */ |
44 | }; | |
45 | ||
2ee6545f | 46 | struct nln_notifier { |
c2715e8d | 47 | struct ovs_list node; /* In struct nln's 'all_notifiers' list. */ |
2ee6545f EJ |
48 | struct nln *nln; /* Parent nln. */ |
49 | ||
77ee67e4 | 50 | int multicast_group; /* Multicast group we listen on. */ |
2ee6545f EJ |
51 | nln_notify_func *cb; |
52 | void *aux; | |
53 | }; | |
54 | ||
0a811051 EJ |
55 | /* Creates an nln handle which may be used to manage change notifications. The |
56 | * created handle will listen for netlink messages on 'multicast_group' using | |
57 | * netlink protocol 'protocol' (e.g. NETLINK_ROUTE, NETLINK_GENERIC, ...). | |
58 | * Incoming messages will be parsed with 'parse' which will be passed 'change' | |
59 | * as an argument. */ | |
60 | struct nln * | |
77ee67e4 | 61 | nln_create(int protocol, nln_parse_func *parse, void *change) |
21d6e22e | 62 | { |
0a811051 EJ |
63 | struct nln *nln; |
64 | ||
65 | nln = xzalloc(sizeof *nln); | |
66 | nln->notify_sock = NULL; | |
67 | nln->protocol = protocol; | |
0a811051 EJ |
68 | nln->parse = parse; |
69 | nln->change = change; | |
70 | nln->has_run = false; | |
71 | ||
417e7e66 | 72 | ovs_list_init(&nln->all_notifiers); |
0a811051 | 73 | return nln; |
21d6e22e | 74 | } |
559843ed | 75 | |
0a811051 | 76 | /* Destroys 'nln' by freeing any memory it has reserved and closing any sockets |
2ee6545f EJ |
77 | * it has opened. |
78 | * | |
79 | * The caller is responsible for destroying any notifiers created by this | |
80 | * 'nln' before destroying 'nln'. */ | |
8f5b651c | 81 | void |
0a811051 | 82 | nln_destroy(struct nln *nln) |
8f5b651c | 83 | { |
0a811051 | 84 | if (nln) { |
417e7e66 | 85 | ovs_assert(ovs_list_is_empty(&nln->all_notifiers)); |
0a811051 EJ |
86 | nl_sock_destroy(nln->notify_sock); |
87 | free(nln); | |
8f5b651c EJ |
88 | } |
89 | } | |
90 | ||
21d6e22e EJ |
91 | /* Registers 'cb' to be called with auxiliary data 'aux' with change |
92 | * notifications. The notifier is stored in 'notifier', which the caller must | |
93 | * not modify or free. | |
559843ed | 94 | * |
21d6e22e EJ |
95 | * This is probably not the function you want. You should probably be using |
96 | * message specific notifiers like rtnetlink_link_notifier_register(). | |
559843ed | 97 | * |
2ee6545f EJ |
98 | * Returns an initialized nln_notifier if successful, otherwise NULL. */ |
99 | struct nln_notifier * | |
77ee67e4 JR |
100 | nln_notifier_create(struct nln *nln, int multicast_group, nln_notify_func *cb, |
101 | void *aux) | |
559843ed | 102 | { |
2ee6545f | 103 | struct nln_notifier *notifier; |
77ee67e4 | 104 | int error; |
2ee6545f | 105 | |
0a811051 | 106 | if (!nln->notify_sock) { |
cceb11f5 | 107 | struct nl_sock *sock; |
cceb11f5 | 108 | |
0a811051 | 109 | error = nl_sock_create(nln->protocol, &sock); |
559843ed | 110 | if (error) { |
10a89ef0 BP |
111 | VLOG_WARN("could not create netlink socket: %s", |
112 | ovs_strerror(error)); | |
2ee6545f | 113 | return NULL; |
559843ed | 114 | } |
0a811051 | 115 | nln->notify_sock = sock; |
559843ed BP |
116 | } else { |
117 | /* Catch up on notification work so that the new notifier won't | |
118 | * receive any stale notifications. */ | |
18a23781 | 119 | nln_run(nln); |
559843ed BP |
120 | } |
121 | ||
77ee67e4 JR |
122 | error = nl_sock_join_mcgroup(nln->notify_sock, multicast_group); |
123 | if (error) { | |
124 | VLOG_WARN("could not join netlink multicast group: %s", | |
125 | ovs_strerror(error)); | |
126 | return NULL; | |
127 | } | |
128 | ||
2ee6545f | 129 | notifier = xmalloc(sizeof *notifier); |
77ee67e4 | 130 | notifier->multicast_group = multicast_group; |
559843ed BP |
131 | notifier->cb = cb; |
132 | notifier->aux = aux; | |
2ee6545f | 133 | notifier->nln = nln; |
77ee67e4 JR |
134 | |
135 | ovs_list_push_back(&nln->all_notifiers, ¬ifier->node); | |
136 | ||
2ee6545f | 137 | return notifier; |
559843ed BP |
138 | } |
139 | ||
2ee6545f EJ |
140 | /* Destroys 'notifier', which must have previously been created with |
141 | * nln_notifier_register(). */ | |
559843ed | 142 | void |
2ee6545f | 143 | nln_notifier_destroy(struct nln_notifier *notifier) |
559843ed | 144 | { |
2ee6545f EJ |
145 | if (notifier) { |
146 | struct nln *nln = notifier->nln; | |
77ee67e4 JR |
147 | struct nln_notifier *iter; |
148 | int count = 0; | |
2ee6545f | 149 | |
417e7e66 | 150 | ovs_list_remove(¬ifier->node); |
77ee67e4 JR |
151 | |
152 | /* Leave the group if no other notifier is interested in it. */ | |
153 | LIST_FOR_EACH (iter, node, &nln->all_notifiers) { | |
154 | if (iter->multicast_group == notifier->multicast_group) { | |
155 | count++; | |
156 | } | |
157 | } | |
158 | if (count == 0) { | |
159 | nl_sock_leave_mcgroup(nln->notify_sock, notifier->multicast_group); | |
160 | } | |
161 | ||
417e7e66 | 162 | if (ovs_list_is_empty(&nln->all_notifiers)) { |
2ee6545f EJ |
163 | nl_sock_destroy(nln->notify_sock); |
164 | nln->notify_sock = NULL; | |
165 | } | |
166 | free(notifier); | |
559843ed BP |
167 | } |
168 | } | |
169 | ||
170 | /* Calls all of the registered notifiers, passing along any as-yet-unreported | |
21d6e22e | 171 | * change events. */ |
559843ed | 172 | void |
18a23781 | 173 | nln_run(struct nln *nln) |
559843ed BP |
174 | { |
175 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); | |
176 | ||
0a811051 | 177 | if (!nln->notify_sock || nln->has_run) { |
559843ed BP |
178 | return; |
179 | } | |
180 | ||
0a811051 | 181 | nln->has_run = true; |
559843ed | 182 | for (;;) { |
72d32ac0 BP |
183 | uint64_t buf_stub[4096 / 8]; |
184 | struct ofpbuf buf; | |
559843ed BP |
185 | int error; |
186 | ||
72d32ac0 | 187 | ofpbuf_use_stub(&buf, buf_stub, sizeof buf_stub); |
a86bd14e | 188 | error = nl_sock_recv(nln->notify_sock, &buf, NULL, false); |
559843ed | 189 | if (!error) { |
77ee67e4 JR |
190 | int group = nln->parse(&buf, nln->change); |
191 | ||
192 | if (group != 0) { | |
193 | nln_report(nln, nln->change, group); | |
559843ed | 194 | } else { |
8e2b2656 | 195 | VLOG_WARN_RL(&rl, "unexpected netlink message contents"); |
77ee67e4 | 196 | nln_report(nln, NULL, 0); |
559843ed | 197 | } |
72d32ac0 | 198 | ofpbuf_uninit(&buf); |
559843ed BP |
199 | } else if (error == EAGAIN) { |
200 | return; | |
201 | } else { | |
202 | if (error == ENOBUFS) { | |
222f0451 DDP |
203 | /* The socket buffer might be full, there could be too many |
204 | * notifications, so it makes sense to call nln_report() */ | |
77ee67e4 | 205 | nln_report(nln, NULL, 0); |
0a811051 | 206 | VLOG_WARN_RL(&rl, "netlink receive buffer overflowed"); |
559843ed | 207 | } else { |
0a811051 | 208 | VLOG_WARN_RL(&rl, "error reading netlink socket: %s", |
10a89ef0 | 209 | ovs_strerror(error)); |
559843ed | 210 | } |
222f0451 | 211 | return; |
559843ed BP |
212 | } |
213 | } | |
214 | } | |
215 | ||
21d6e22e | 216 | /* Causes poll_block() to wake up when change notifications are ready. */ |
559843ed | 217 | void |
18a23781 | 218 | nln_wait(struct nln *nln) |
559843ed | 219 | { |
0a811051 EJ |
220 | nln->has_run = false; |
221 | if (nln->notify_sock) { | |
222 | nl_sock_wait(nln->notify_sock, POLLIN); | |
559843ed BP |
223 | } |
224 | } | |
225 | ||
e8e1a409 | 226 | void |
77ee67e4 | 227 | nln_report(const struct nln *nln, void *change, int group) |
559843ed | 228 | { |
0a811051 | 229 | struct nln_notifier *notifier; |
559843ed | 230 | |
21d6e22e | 231 | if (change) { |
0a811051 | 232 | COVERAGE_INC(nln_changed); |
559843ed | 233 | } |
559843ed | 234 | |
0a811051 | 235 | LIST_FOR_EACH (notifier, node, &nln->all_notifiers) { |
77ee67e4 JR |
236 | if (!change || group == notifier->multicast_group) { |
237 | notifier->cb(change, notifier->aux); | |
238 | } | |
559843ed BP |
239 | } |
240 | } |