Simplify the locking and remove some deadlocks
[framework/connectivity/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/socket.h>
29 #include <linux/if.h>
30 #include <linux/netlink.h>
31 #include <linux/rtnetlink.h>
32
33 #include <connman/plugin.h>
34 #include <connman/driver.h>
35 #include <connman/log.h>
36
37 static GStaticMutex ethernet_mutex = G_STATIC_MUTEX_INIT;
38 static GSList *ethernet_list = NULL;
39
40 static GStaticMutex element_mutex = G_STATIC_MUTEX_INIT;
41 static GSList *element_list = NULL;
42
43 static void create_element(struct connman_element *parent,
44                                         enum connman_element_type type)
45 {
46         struct connman_element *element;
47
48         DBG("parent %p name %s", parent, parent->name);
49
50         element = connman_element_create();
51
52         element->type = type;
53         element->netdev.index = parent->netdev.index;
54         element->netdev.name = g_strdup(parent->netdev.name);
55
56         g_static_mutex_lock(&element_mutex);
57         element_list = g_slist_append(element_list, element);
58         g_static_mutex_unlock(&element_mutex);
59
60         connman_element_register(element, parent);
61 }
62
63 static void remove_elements(struct connman_element *parent)
64 {
65         GSList *list = element_list;
66
67         DBG("parent %p name %s", parent, parent->name);
68
69         g_static_mutex_lock(&element_mutex);
70
71         while (list) {
72                 GSList *next = list->next;
73                 struct connman_element *element = list->data;
74
75                 if (element->netdev.index != parent->netdev.index) {
76                         list = next;
77                         continue;
78                 }
79
80                 element_list = g_slist_delete_link(element_list, list);
81
82                 connman_element_unregister(element);
83
84                 connman_element_unref(element);
85
86                 list = next;
87         }
88
89         g_static_mutex_unlock(&element_mutex);
90 }
91
92 static void rtnl_link(struct nlmsghdr *hdr, const char *type)
93 {
94         GSList *list;
95         struct ifinfomsg *msg;
96         int bytes;
97
98         msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
99         bytes = IFLA_PAYLOAD(hdr);
100
101         DBG("%s ifi_index %d ifi_flags 0x%04x",
102                                 type, msg->ifi_index, msg->ifi_flags);
103
104         g_static_mutex_lock(&ethernet_mutex);
105
106         for (list = ethernet_list; list; list = list->next) {
107                 struct connman_element *element = list->data;
108
109                 if (element->type != CONNMAN_ELEMENT_TYPE_DEVICE)
110                         continue;
111
112                 if (element->netdev.index != msg->ifi_index)
113                         continue;
114
115                 if ((element->netdev.flags & IFF_RUNNING) ==
116                                                 (msg->ifi_flags & IFF_RUNNING))
117                         continue;
118
119                 element->netdev.flags = msg->ifi_flags;
120
121                 if (msg->ifi_flags & IFF_RUNNING) {
122                         DBG("carrier on");
123
124                         create_element(element, CONNMAN_ELEMENT_TYPE_DHCP);
125                         create_element(element, CONNMAN_ELEMENT_TYPE_ZEROCONF);
126                 } else {
127                         DBG("carrier off");
128
129                         remove_elements(element);
130                 }
131         }
132
133         g_static_mutex_unlock(&ethernet_mutex);
134 }
135
136 static gboolean rtnl_event(GIOChannel *chan, GIOCondition cond, gpointer data)
137 {
138         unsigned char buf[1024];
139         void *ptr = buf;
140         gsize len;
141         GIOError err;
142
143         if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
144                 return FALSE;
145
146         memset(buf, 0, sizeof(buf));
147
148         err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
149         if (err) {
150                 if (err == G_IO_ERROR_AGAIN)
151                         return TRUE;
152                 return FALSE;
153         }
154
155         DBG("buf %p len %zd", buf, len);
156
157         while (len > 0) {
158                 struct nlmsghdr *hdr = ptr;
159                 struct nlmsgerr *err;
160
161                 if (!NLMSG_OK(hdr, len))
162                         break;
163
164                 DBG("len %d type %d flags 0x%04x seq %d",
165                                         hdr->nlmsg_len, hdr->nlmsg_type,
166                                         hdr->nlmsg_flags, hdr->nlmsg_seq);
167
168                 switch (hdr->nlmsg_type) {
169                 case NLMSG_ERROR:
170                         err = NLMSG_DATA(hdr);
171                         DBG("ERROR %d (%s)", -err->error,
172                                                 strerror(-err->error));
173                         break;
174
175                 case RTM_NEWLINK:
176                         rtnl_link(hdr, "NEWLINK");
177                         break;
178
179                 case RTM_DELLINK:
180                         rtnl_link(hdr, "DELLINK");
181                         break;
182                 }
183
184                 len -= hdr->nlmsg_len;
185                 ptr += hdr->nlmsg_len;
186         }
187
188         return TRUE;
189 }
190
191 static GIOChannel *channel = NULL;
192
193 static int rtnl_request(void)
194 {
195         struct {
196                 struct nlmsghdr hdr;
197                 struct rtgenmsg msg;
198         } req;
199
200         struct sockaddr_nl addr;
201         int sk;
202
203         DBG("");
204
205         memset(&req, 0, sizeof(req));
206         req.hdr.nlmsg_len = sizeof(req.hdr) + sizeof(req.msg);
207         req.hdr.nlmsg_type = RTM_GETLINK;
208         req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
209         req.hdr.nlmsg_pid = 0;
210         req.hdr.nlmsg_seq = 42;
211         req.msg.rtgen_family = AF_INET;
212
213         sk = g_io_channel_unix_get_fd(channel);
214
215         memset(&addr, 0, sizeof(addr));
216         addr.nl_family = AF_NETLINK;
217
218         return sendto(sk, &req, sizeof(req), 0,
219                         (struct sockaddr *) &addr, sizeof(addr));
220 }
221
222 static int ethernet_probe(struct connman_element *element)
223 {
224         DBG("element %p name %s", element, element->name);
225
226         g_static_mutex_lock(&ethernet_mutex);
227         ethernet_list = g_slist_append(ethernet_list, element);
228         g_static_mutex_unlock(&ethernet_mutex);
229
230         rtnl_request();
231
232         return 0;
233 }
234
235 static void ethernet_remove(struct connman_element *element)
236 {
237         DBG("element %p name %s", element, element->name);
238
239         remove_elements(element);
240
241         g_static_mutex_lock(&ethernet_mutex);
242         ethernet_list = g_slist_remove(ethernet_list, element);
243         g_static_mutex_unlock(&ethernet_mutex);
244 }
245
246 static struct connman_driver ethernet_driver = {
247         .name           = "ethernet",
248         .type           = CONNMAN_ELEMENT_TYPE_DEVICE,
249         .subtype        = CONNMAN_ELEMENT_SUBTYPE_ETHERNET,
250         .probe          = ethernet_probe,
251         .remove         = ethernet_remove,
252 };
253
254 static int rtnl_init(void)
255 {
256         struct sockaddr_nl addr;
257         int sk, err;
258
259         DBG("");
260
261         sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
262         if (sk < 0)
263                 return -errno;
264
265         memset(&addr, 0, sizeof(addr));
266         addr.nl_family = AF_NETLINK;
267         addr.nl_groups = RTMGRP_LINK;
268
269         if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
270                 err = -errno;
271                 close(sk);
272                 return err;
273         }
274
275         channel = g_io_channel_unix_new(sk);
276         g_io_channel_set_close_on_unref(channel, TRUE);
277
278         g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
279                                                         rtnl_event, NULL);
280
281         return 0;
282 }
283
284 static void rtnl_cleanup(void)
285 {
286         DBG("");
287
288         g_io_channel_shutdown(channel, TRUE, NULL);
289         g_io_channel_unref(channel);
290
291         channel = NULL;
292 }
293
294 static int ethernet_init(void)
295 {
296         int err;
297
298         err = rtnl_init();
299         if (err < 0)
300                 return err;
301
302         err = connman_driver_register(&ethernet_driver);
303         if (err < 0) {
304                 rtnl_cleanup();
305                 return err;
306         }
307
308         return 0;
309 }
310
311 static void ethernet_exit(void)
312 {
313         connman_driver_unregister(&ethernet_driver);
314
315         rtnl_cleanup();
316 }
317
318 CONNMAN_PLUGIN_DEFINE("ethernet", "Ethernet interface plugin", VERSION,
319                                                 ethernet_init, ethernet_exit)