Bring Ethernet device up and down if needed
[platform/upstream/connman.git] / plugins / ethernet.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2008  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <unistd.h>
27 #include <string.h>
28 #include <sys/ioctl.h>
29 #include <sys/socket.h>
30 #include <linux/if.h>
31 #include <linux/netlink.h>
32 #include <linux/rtnetlink.h>
33
34 #include <connman/plugin.h>
35 #include <connman/driver.h>
36 #include <connman/log.h>
37
38 static GStaticMutex ethernet_mutex = G_STATIC_MUTEX_INIT;
39 static GSList *ethernet_list = NULL;
40
41 static GStaticMutex element_mutex = G_STATIC_MUTEX_INIT;
42 static GSList *element_list = NULL;
43
44 static void create_element(struct connman_element *parent,
45                                         enum connman_element_type type)
46 {
47         struct connman_element *element;
48
49         DBG("parent %p name %s", parent, parent->name);
50
51         element = connman_element_create();
52
53         element->type = type;
54         element->netdev.index = parent->netdev.index;
55         element->netdev.name = g_strdup(parent->netdev.name);
56
57         g_static_mutex_lock(&element_mutex);
58         element_list = g_slist_append(element_list, element);
59         g_static_mutex_unlock(&element_mutex);
60
61         connman_element_register(element, parent);
62 }
63
64 static void remove_elements(struct connman_element *parent)
65 {
66         GSList *list = element_list;
67
68         DBG("parent %p name %s", parent, parent->name);
69
70         g_static_mutex_lock(&element_mutex);
71
72         while (list) {
73                 GSList *next = list->next;
74                 struct connman_element *element = list->data;
75
76                 if (element->netdev.index != parent->netdev.index) {
77                         list = next;
78                         continue;
79                 }
80
81                 element_list = g_slist_delete_link(element_list, list);
82
83                 connman_element_unregister(element);
84
85                 connman_element_unref(element);
86
87                 list = next;
88         }
89
90         g_static_mutex_unlock(&element_mutex);
91 }
92
93 static void rtnl_link(struct nlmsghdr *hdr, const char *type)
94 {
95         GSList *list;
96         struct ifinfomsg *msg;
97         int bytes;
98
99         msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
100         bytes = IFLA_PAYLOAD(hdr);
101
102         DBG("%s ifi_index %d ifi_flags 0x%04x",
103                                 type, msg->ifi_index, msg->ifi_flags);
104
105         g_static_mutex_lock(&ethernet_mutex);
106
107         for (list = ethernet_list; list; list = list->next) {
108                 struct connman_element *element = list->data;
109
110                 if (element->type != CONNMAN_ELEMENT_TYPE_DEVICE)
111                         continue;
112
113                 if (element->netdev.index != msg->ifi_index)
114                         continue;
115
116                 if ((element->netdev.flags & IFF_RUNNING) ==
117                                                 (msg->ifi_flags & IFF_RUNNING))
118                         continue;
119
120                 element->netdev.flags = msg->ifi_flags;
121
122                 if (msg->ifi_flags & IFF_RUNNING) {
123                         DBG("carrier on");
124
125                         create_element(element, CONNMAN_ELEMENT_TYPE_DHCP);
126                         create_element(element, CONNMAN_ELEMENT_TYPE_ZEROCONF);
127                 } else {
128                         DBG("carrier off");
129
130                         remove_elements(element);
131                 }
132         }
133
134         g_static_mutex_unlock(&ethernet_mutex);
135 }
136
137 static gboolean rtnl_event(GIOChannel *chan, GIOCondition cond, gpointer data)
138 {
139         unsigned char buf[1024];
140         void *ptr = buf;
141         gsize len;
142         GIOError err;
143
144         if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
145                 return FALSE;
146
147         memset(buf, 0, sizeof(buf));
148
149         err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
150         if (err) {
151                 if (err == G_IO_ERROR_AGAIN)
152                         return TRUE;
153                 return FALSE;
154         }
155
156         DBG("buf %p len %zd", buf, len);
157
158         while (len > 0) {
159                 struct nlmsghdr *hdr = ptr;
160                 struct nlmsgerr *err;
161
162                 if (!NLMSG_OK(hdr, len))
163                         break;
164
165                 DBG("len %d type %d flags 0x%04x seq %d",
166                                         hdr->nlmsg_len, hdr->nlmsg_type,
167                                         hdr->nlmsg_flags, hdr->nlmsg_seq);
168
169                 switch (hdr->nlmsg_type) {
170                 case NLMSG_ERROR:
171                         err = NLMSG_DATA(hdr);
172                         DBG("ERROR %d (%s)", -err->error,
173                                                 strerror(-err->error));
174                         break;
175
176                 case RTM_NEWLINK:
177                         rtnl_link(hdr, "NEWLINK");
178                         break;
179
180                 case RTM_DELLINK:
181                         rtnl_link(hdr, "DELLINK");
182                         break;
183                 }
184
185                 len -= hdr->nlmsg_len;
186                 ptr += hdr->nlmsg_len;
187         }
188
189         return TRUE;
190 }
191
192 static GIOChannel *channel = NULL;
193
194 static int rtnl_request(void)
195 {
196         struct {
197                 struct nlmsghdr hdr;
198                 struct rtgenmsg msg;
199         } req;
200
201         struct sockaddr_nl addr;
202         int sk;
203
204         DBG("");
205
206         memset(&req, 0, sizeof(req));
207         req.hdr.nlmsg_len = sizeof(req.hdr) + sizeof(req.msg);
208         req.hdr.nlmsg_type = RTM_GETLINK;
209         req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
210         req.hdr.nlmsg_pid = 0;
211         req.hdr.nlmsg_seq = 42;
212         req.msg.rtgen_family = AF_INET;
213
214         sk = g_io_channel_unix_get_fd(channel);
215
216         memset(&addr, 0, sizeof(addr));
217         addr.nl_family = AF_NETLINK;
218
219         return sendto(sk, &req, sizeof(req), 0,
220                         (struct sockaddr *) &addr, sizeof(addr));
221 }
222
223 static int iface_up(struct connman_element *element)
224 {
225         struct ifreq ifr;
226         int sk, err;
227
228         DBG("element %p", element);
229
230         sk = socket(PF_INET, SOCK_DGRAM, 0);
231         if (sk < 0)
232                 return -errno;
233
234         memset(&ifr, 0, sizeof(ifr));
235         ifr.ifr_ifindex = element->netdev.index;
236
237         if (ioctl(sk, SIOCGIFNAME, &ifr) < 0) {
238                 err = -errno;
239                 goto done;
240         }
241
242         if (ioctl(sk, SIOCGIFFLAGS, &ifr) < 0) {
243                 err = -errno;
244                 goto done;
245         }
246
247         if (ifr.ifr_flags & IFF_UP) {
248                 err = -EALREADY;
249                 goto done;
250         }
251
252         ifr.ifr_flags |= IFF_UP;
253
254         if (ioctl(sk, SIOCSIFFLAGS, &ifr) < 0) {
255                 err = -errno;
256                 goto done;
257         }
258
259         err = 0;
260
261 done:
262         close(sk);
263
264         return err;
265 }
266
267 static int iface_down(struct connman_element *element)
268 {
269         struct ifreq ifr;
270         int sk, err;
271
272         DBG("element %p", element);
273
274         sk = socket(PF_INET, SOCK_DGRAM, 0);
275         if (sk < 0)
276                 return -errno;
277
278         memset(&ifr, 0, sizeof(ifr));
279         ifr.ifr_ifindex = element->netdev.index;
280
281         if (ioctl(sk, SIOCGIFNAME, &ifr) < 0) {
282                 err = -errno;
283                 goto done;
284         }
285
286         if (ioctl(sk, SIOCGIFFLAGS, &ifr) < 0) {
287                 err = -errno;
288                 goto done;
289         }
290
291         if (!(ifr.ifr_flags & IFF_UP)) {
292                 err = -EALREADY;
293                 goto done;
294         }
295
296         ifr.ifr_flags &= ~IFF_UP;
297
298         if (ioctl(sk, SIOCSIFFLAGS, &ifr) < 0)
299                 err = -errno;
300         else
301                 err = 0;
302
303 done:
304         close(sk);
305
306         return err;
307 }
308
309 static int ethernet_probe(struct connman_element *element)
310 {
311         DBG("element %p name %s", element, element->name);
312
313         g_static_mutex_lock(&ethernet_mutex);
314         ethernet_list = g_slist_append(ethernet_list, element);
315         g_static_mutex_unlock(&ethernet_mutex);
316
317         iface_up(element);
318
319         rtnl_request();
320
321         return 0;
322 }
323
324 static void ethernet_remove(struct connman_element *element)
325 {
326         DBG("element %p name %s", element, element->name);
327
328         iface_down(element);
329
330         remove_elements(element);
331
332         g_static_mutex_lock(&ethernet_mutex);
333         ethernet_list = g_slist_remove(ethernet_list, element);
334         g_static_mutex_unlock(&ethernet_mutex);
335 }
336
337 static struct connman_driver ethernet_driver = {
338         .name           = "ethernet",
339         .type           = CONNMAN_ELEMENT_TYPE_DEVICE,
340         .subtype        = CONNMAN_ELEMENT_SUBTYPE_ETHERNET,
341         .probe          = ethernet_probe,
342         .remove         = ethernet_remove,
343 };
344
345 static int rtnl_init(void)
346 {
347         struct sockaddr_nl addr;
348         int sk, err;
349
350         DBG("");
351
352         sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
353         if (sk < 0)
354                 return -errno;
355
356         memset(&addr, 0, sizeof(addr));
357         addr.nl_family = AF_NETLINK;
358         addr.nl_groups = RTMGRP_LINK;
359
360         if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
361                 err = -errno;
362                 close(sk);
363                 return err;
364         }
365
366         channel = g_io_channel_unix_new(sk);
367         g_io_channel_set_close_on_unref(channel, TRUE);
368
369         g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
370                                                         rtnl_event, NULL);
371
372         return 0;
373 }
374
375 static void rtnl_cleanup(void)
376 {
377         DBG("");
378
379         g_io_channel_shutdown(channel, TRUE, NULL);
380         g_io_channel_unref(channel);
381
382         channel = NULL;
383 }
384
385 static int ethernet_init(void)
386 {
387         int err;
388
389         err = rtnl_init();
390         if (err < 0)
391                 return err;
392
393         err = connman_driver_register(&ethernet_driver);
394         if (err < 0) {
395                 rtnl_cleanup();
396                 return err;
397         }
398
399         return 0;
400 }
401
402 static void ethernet_exit(void)
403 {
404         connman_driver_unregister(&ethernet_driver);
405
406         rtnl_cleanup();
407 }
408
409 CONNMAN_PLUGIN_DEFINE("ethernet", "Ethernet interface plugin", VERSION,
410                                                 ethernet_init, ethernet_exit)