Merge tag 'upstream/1.40' into tizen.
[platform/upstream/connman.git] / plugins / ethernet.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2013  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 <errno.h>
27 #include <net/if.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30 #include <sys/types.h>
31 #include <unistd.h>
32 #include <stdio.h>
33
34 #include <linux/if_vlan.h>
35 #include <linux/sockios.h>
36 #include <linux/ethtool.h>
37
38 #ifndef IFF_LOWER_UP
39 #define IFF_LOWER_UP    0x10000
40 #endif
41
42 #include <glib.h>
43
44 #define CONNMAN_API_SUBJECT_TO_CHANGE
45 #include <connman/technology.h>
46 #include <connman/plugin.h>
47 #include <connman/device.h>
48 #include <connman/inet.h>
49 #include <connman/rtnl.h>
50 #include <connman/log.h>
51 #include <connman/setting.h>
52 #if defined TIZEN_EXT_WIFI_MESH
53 #include <connman/mesh.h>
54 #endif
55
56 #if defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET
57 #include <gsupplicant/gsupplicant.h>
58 #endif /* defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET */
59
60 static bool eth_tethering = false;
61
62 struct ethernet_data {
63         int index;
64         unsigned flags;
65         unsigned int watch;
66         struct connman_network *network;
67 #if defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET
68         GSupplicantInterface *interface;
69 #endif /* defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET */
70 };
71
72
73 static int get_vlan_vid(const char *ifname)
74 {
75         struct vlan_ioctl_args vifr;
76         int vid;
77         int sk;
78
79         memset(&vifr, 0, sizeof(vifr));
80
81         sk = socket(AF_INET, SOCK_STREAM, 0);
82         if (sk < 0)
83                 return -errno;
84
85         vifr.cmd = GET_VLAN_VID_CMD;
86         stpncpy(vifr.device1, ifname, sizeof(vifr.device1) - 1);
87
88         if(ioctl(sk, SIOCSIFVLAN, &vifr) >= 0)
89                 vid = vifr.u.VID;
90         else
91                 vid = -errno;
92
93         close(sk);
94
95         return vid;
96 }
97
98 static int get_dsa_port(const char *ifname)
99 {
100         int sk;
101         int dsaport = -1;
102         struct ifreq ifr;
103         struct ethtool_cmd cmd;
104         struct ethtool_drvinfo drvinfocmd;
105         struct vlan_ioctl_args vifr;
106
107         sk = socket(AF_INET, SOCK_STREAM, 0);
108         if (sk < 0)
109                 return -errno;
110
111         memset(&ifr, 0, sizeof(ifr));
112         stpncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1);
113
114         /* check if it is a vlan and get physical interface name*/
115         vifr.cmd = GET_VLAN_REALDEV_NAME_CMD;
116         stpncpy(vifr.device1, ifname, sizeof(vifr.device1) - 1);
117
118         if(ioctl(sk, SIOCSIFVLAN, &vifr) >= 0) {
119                 stpncpy(ifr.ifr_name, vifr.u.device2, sizeof(ifr.ifr_name) - 1);
120                 ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
121         }
122
123         /* get driver info */
124         drvinfocmd.cmd =  ETHTOOL_GDRVINFO;
125         ifr.ifr_data = (caddr_t)&drvinfocmd;
126
127         if (!ioctl(sk, SIOCETHTOOL, &ifr)) {
128                 if(!strcmp(drvinfocmd.driver, "dsa")) {
129                         /* get dsa port*/
130                         cmd.cmd =  ETHTOOL_GSET;
131                         ifr.ifr_data = (caddr_t)&cmd;
132
133                         if (!ioctl(sk, SIOCETHTOOL, &ifr))
134                                 dsaport = cmd.phy_address;
135                 }
136         }
137         close(sk);
138
139         return dsaport;
140 }
141
142 static int eth_network_probe(struct connman_network *network)
143 {
144         DBG("network %p", network);
145
146         return 0;
147 }
148
149 static void eth_network_remove(struct connman_network *network)
150 {
151         DBG("network %p", network);
152 }
153
154 #if defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET
155 #define NETCONFIG_SERVICE               "net.netconfig"
156 #define NETCONFIG_ETHERNET_INTERFACE    NETCONFIG_SERVICE ".ethernet"
157 #define NETCONFIG_ETHERNET_PATH         "/net/netconfig/ethernet"
158
159 struct eapol_method_call_data {
160         DBusConnection *connection;
161         struct connman_network *network;
162 };
163
164 static struct eapol_method_call_data enable_eapol_data;
165
166 void handle_eap_signal(GSupplicantInterface *interface, bool status)
167 {
168         DBG("captured EAP signal");
169
170         if (!enable_eapol_data.network)
171                 return;
172
173         if (g_strcmp0("wired", g_supplicant_interface_get_driver(interface)))
174                 return;
175
176         if (!connman_network_check_validity(enable_eapol_data.network))
177                 return;
178
179         DBG("network is valid");
180
181         g_supplicant_unregister_eap_callback();
182
183         if (!status) {
184                 // Should we mark service as non favorite or make autoconnect as false?
185
186                 struct ethernet_data *ethernet = g_supplicant_interface_get_data(interface);
187                 if (ethernet && ethernet->interface) {
188                         g_supplicant_interface_remove(ethernet->interface, NULL, NULL);
189                         ethernet->interface = NULL;
190                 }
191
192                 connman_network_set_error(enable_eapol_data.network, CONNMAN_NETWORK_ERROR_ASSOCIATE_FAIL);
193                 enable_eapol_data.network = NULL;
194                 return;
195         }
196
197         connman_network_set_connected(enable_eapol_data.network, status);
198         enable_eapol_data.network = NULL;
199 }
200
201 static void interface_create_callback(int result,
202                 GSupplicantInterface *interface, void *user_data)
203 {
204         struct ethernet_data *ethernet = user_data;
205
206         if (result < 0 || !interface || !ethernet)
207                 return;
208
209         DBG("result %d ifname %s, ethernet %p", result,
210                         g_supplicant_interface_get_ifname(interface),
211                         ethernet);
212
213         ethernet->interface = interface;
214         g_supplicant_interface_set_data(interface, ethernet);
215 }
216
217 static int eapol_interface_create(void)
218 {
219         struct connman_network *network = enable_eapol_data.network;
220         struct connman_service *service = connman_service_lookup_from_network(network);
221
222         if (!service) {
223                 DBG("service not found");
224                 return -1;
225         }
226
227         struct connman_device *device = connman_network_get_device(network);
228         struct ethernet_data *ethernet = connman_device_get_data(device);
229         const char *driver = "wired";
230         int index = connman_network_get_index(network);
231         char *ifname = connman_inet_ifname(index);;
232         char *config_file = NULL;
233
234         g_supplicant_register_eap_callback(handle_eap_signal);
235
236         if (asprintf(&config_file, "/var/lib/connman/%s-eapol.conf", ifname) < 0) {
237                 g_free(ifname);
238                 return -ENOMEM;
239         }
240
241         DBG("config_file %s", config_file);
242
243         g_supplicant_replace_config_file(ifname, config_file);
244         free(config_file);
245
246         /*
247          *  TODO: RemoveInterface if already present because
248          *  already created interface will not start EAP handshake.
249          */
250         return g_supplicant_interface_create(ifname, driver, NULL,
251                         0, 0, 60, interface_create_callback, ethernet);
252 }
253
254 static void enable_eapol_reply(DBusPendingCall *call, void *user_data)
255 {
256         DBusMessage *reply;
257         DBusError error;
258
259         DBG("");
260
261         reply = dbus_pending_call_steal_reply(call);
262
263         dbus_error_init(&error);
264         if (dbus_set_error_from_message(&error, reply)) {
265                 DBG("enable_eapol_request() %s %s", error.name, error.message);
266                 dbus_error_free(&error);
267                 dbus_message_unref(reply);
268                 dbus_pending_call_unref(call);
269                 dbus_connection_unref(enable_eapol_data.connection);
270
271                 enable_eapol_data.connection = NULL;
272                 return;
273         }
274
275         if (eapol_interface_create() < 0)
276                 DBG("Failed to create eapol interface");
277 }
278
279 static int eth_network_enable_eapol(struct connman_service *service, struct connman_network *network)
280 {
281         DBusMessage *msg = NULL;
282         DBusPendingCall *call;
283
284         DBusConnection *connection = connman_dbus_get_connection();
285         if (!connection) {
286                 DBG("dbus connection does not exist");
287                 return -EINVAL;
288         }
289
290         msg = dbus_message_new_method_call(NETCONFIG_SERVICE, NETCONFIG_ETHERNET_PATH,
291                         NETCONFIG_ETHERNET_INTERFACE, "EnableEap");
292         if (!msg) {
293                 dbus_connection_unref(connection);
294                 return -EINVAL;
295         }
296
297         const char *path = __connman_service_get_path(service);
298         dbus_bool_t enable = true;
299
300         dbus_message_append_args(msg, DBUS_TYPE_STRING, &path,
301                         DBUS_TYPE_INVALID);
302         dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &enable,
303                         DBUS_TYPE_INVALID);
304
305         if (!dbus_connection_send_with_reply(connection, msg,
306                                 &call, DBUS_TIMEOUT_USE_DEFAULT)) {
307                 dbus_message_unref(msg);
308                 dbus_connection_unref(connection);
309                 return -EIO;
310         }
311
312         if (!call) {
313                 dbus_message_unref(msg);
314                 dbus_connection_unref(connection);
315                 return -EIO;
316         }
317
318         enable_eapol_data.connection = connection;
319         enable_eapol_data.network = network;
320
321         dbus_pending_call_set_notify(call, enable_eapol_reply, NULL, NULL);
322         dbus_message_unref(msg);
323
324         return 0;
325 }
326
327 static int eth_network_connect(struct connman_network *network)
328 {
329         DBG("network %p", network);
330
331         int err = 0;
332         struct connman_service *service = connman_service_lookup_from_network(network);
333
334         if (service && __connman_service_get_use_eapol(service)) {
335                 /** Enable eapol on device reboot **/
336                 if (__connman_service_get_connect_reason(service) != CONNMAN_SERVICE_CONNECT_REASON_USER) {
337                         err = eth_network_enable_eapol(service, network);
338                         if (err < 0) {
339                                 DBG("Failed to enable eapol");
340                                 return err;
341                         }
342                 } else {
343                         err = eapol_interface_create();
344                         if (err < 0) {
345                                 DBG("Failed to create eapol interface");
346                                 return err;
347                         }
348
349                         return 0;
350                 }
351         }
352
353         connman_network_set_connected(network, true);
354
355         return 0;
356 }
357
358 static int eth_network_disconnect(struct connman_network *network)
359 {
360         DBG("network %p", network);
361
362         struct connman_service *service = connman_service_lookup_from_network(network);
363
364         if (service && __connman_service_get_use_eapol(service)) {
365                 struct connman_device *device = connman_network_get_device(network);
366                 struct ethernet_data *ethernet = connman_device_get_data(device);
367
368                 enable_eapol_data.network = NULL;
369                 g_supplicant_unregister_eap_callback();
370                 if (ethernet && ethernet->interface) {
371                         g_supplicant_interface_remove(ethernet->interface, NULL, NULL);
372                         ethernet->interface = NULL;
373                 }
374                 connman_network_set_associating(network, false);
375                 connman_network_set_connected(network, false);
376
377                 return 0;
378         }
379
380         connman_network_set_connected(network, false);
381
382         return 0;
383 }
384
385 #else /* defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET */
386
387 static int eth_network_connect(struct connman_network *network)
388 {
389         DBG("network %p", network);
390
391         connman_network_set_connected(network, true);
392
393         return 0;
394 }
395
396 static int eth_network_disconnect(struct connman_network *network)
397 {
398         DBG("network %p", network);
399
400         connman_network_set_connected(network, false);
401
402         return 0;
403 }
404
405 #endif /* defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET */
406
407 static struct connman_network_driver eth_network_driver = {
408         .name           = "cable",
409         .type           = CONNMAN_NETWORK_TYPE_ETHERNET,
410         .probe          = eth_network_probe,
411         .remove         = eth_network_remove,
412         .connect        = eth_network_connect,
413         .disconnect     = eth_network_disconnect,
414 };
415
416 static void add_network(struct connman_device *device,
417                         struct ethernet_data *ethernet)
418 {
419         struct connman_network *network;
420         int index;
421         char *ifname;
422
423         network = connman_network_create("carrier",
424                                         CONNMAN_NETWORK_TYPE_ETHERNET);
425         if (!network)
426                 return;
427
428         index = connman_device_get_index(device);
429         connman_network_set_index(network, index);
430         ifname = connman_inet_ifname(index);
431         if (!ifname)
432                 return;
433
434         connman_network_set_name(network, "Wired");
435
436         if (connman_device_add_network(device, network) < 0) {
437                 connman_network_unref(network);
438                 g_free(ifname);
439                 return;
440         }
441
442         if (!eth_tethering) {
443                 char group[25] = "cable";
444                 int vid, dsaport;
445
446                 vid = get_vlan_vid(ifname);
447                 dsaport = get_dsa_port(ifname);
448
449                 /*
450                  * Prevent service from starting the reconnect
451                  * procedure as we do not want the DHCP client
452                  * to run when tethering.
453                  */
454                 if((vid >= 0) && (dsaport >= 0))
455                         snprintf(group, sizeof(group), "p%02x_%03x_cable", dsaport, vid);
456                 else if (vid >= 0)
457                         snprintf(group, sizeof(group), "%03x_cable", vid);
458                 else if (dsaport >= 0)
459                         snprintf(group, sizeof(group), "p%02x_cable", dsaport);
460
461                 connman_network_set_group(network, group);
462         }
463
464         ethernet->network = network;
465         g_free(ifname);
466 }
467
468 static void remove_network(struct connman_device *device,
469                                 struct ethernet_data *ethernet)
470 {
471         if (!ethernet->network)
472                 return;
473
474         connman_device_remove_network(device, ethernet->network);
475         connman_network_unref(ethernet->network);
476
477         ethernet->network = NULL;
478 }
479
480 static void ethernet_newlink(unsigned flags, unsigned change, void *user_data)
481 {
482         struct connman_device *device = user_data;
483         struct ethernet_data *ethernet = connman_device_get_data(device);
484
485         DBG("index %d flags %d change %d", ethernet->index, flags, change);
486
487         if ((ethernet->flags & IFF_UP) != (flags & IFF_UP)) {
488                 if (flags & IFF_UP) {
489                         DBG("power on");
490                         connman_device_set_powered(device, true);
491                 } else {
492                         DBG("power off");
493                         connman_device_set_powered(device, false);
494                 }
495         }
496
497         if ((ethernet->flags & IFF_LOWER_UP) != (flags & IFF_LOWER_UP)) {
498                 if (flags & IFF_LOWER_UP) {
499                         DBG("carrier on");
500                         add_network(device, ethernet);
501                 } else {
502                         DBG("carrier off");
503                         remove_network(device, ethernet);
504 #if defined TIZEN_EXT_WIFI_MESH
505                         /* Remove ethernet from mesh bridge */
506                         __connman_mesh_remove_ethernet_from_bridge();
507 #endif
508                 }
509         }
510
511         ethernet->flags = flags;
512 }
513
514 static int eth_dev_probe(struct connman_device *device)
515 {
516         struct ethernet_data *ethernet;
517
518         DBG("device %p", device);
519
520         ethernet = g_try_new0(struct ethernet_data, 1);
521         if (!ethernet)
522                 return -ENOMEM;
523
524         connman_device_set_data(device, ethernet);
525
526         ethernet->index = connman_device_get_index(device);
527         ethernet->flags = 0;
528 #if defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET
529         ethernet->interface = NULL;
530 #endif /* defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET */
531
532         ethernet->watch = connman_rtnl_add_newlink_watch(ethernet->index,
533                                                 ethernet_newlink, device);
534
535         return 0;
536 }
537
538 static void eth_dev_remove(struct connman_device *device)
539 {
540         struct ethernet_data *ethernet = connman_device_get_data(device);
541
542         DBG("device %p", device);
543
544         connman_device_set_data(device, NULL);
545
546 #if defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET
547         if (!ethernet)
548                return;
549
550         if (ethernet->interface) {
551                 g_supplicant_interface_remove(ethernet->interface, NULL, NULL);
552                 ethernet->interface = NULL;
553         }
554 #endif /* defined TIZEN_EXT && defined TIZEN_EXT_EAP_ON_ETHERNET */
555
556         connman_rtnl_remove_watch(ethernet->watch);
557
558         remove_network(device, ethernet);
559
560         g_free(ethernet);
561 }
562
563 static int eth_dev_enable(struct connman_device *device)
564 {
565         struct ethernet_data *ethernet = connman_device_get_data(device);
566
567         DBG("device %p", device);
568
569         return connman_inet_ifup(ethernet->index);
570 }
571
572 static int eth_dev_disable(struct connman_device *device)
573 {
574         struct ethernet_data *ethernet = connman_device_get_data(device);
575
576         DBG("device %p", device);
577
578         return connman_inet_ifdown(ethernet->index);
579 }
580
581 static struct connman_device_driver eth_dev_driver = {
582         .name           = "ethernet",
583         .type           = CONNMAN_DEVICE_TYPE_ETHERNET,
584         .probe          = eth_dev_probe,
585         .remove         = eth_dev_remove,
586         .enable         = eth_dev_enable,
587         .disable        = eth_dev_disable,
588 };
589
590 static int eth_tech_probe(struct connman_technology *technology)
591 {
592         return 0;
593 }
594
595 static void eth_tech_remove(struct connman_technology *technology)
596 {
597         DBG("");
598 }
599
600 static GList *eth_interface_list = NULL;
601
602 static void eth_tech_add_interface(struct connman_technology *technology,
603                         int index, const char *name, const char *ident)
604 {
605         DBG("index %d name %s ident %s", index, name, ident);
606
607         if (g_list_find(eth_interface_list, GINT_TO_POINTER((int)index)))
608                 return;
609
610         eth_interface_list = g_list_prepend(eth_interface_list,
611                                         (GINT_TO_POINTER((int) index)));
612 }
613
614 static void eth_tech_remove_interface(struct connman_technology *technology,
615                                                                 int index)
616 {
617         DBG("index %d", index);
618
619         eth_interface_list = g_list_remove(eth_interface_list,
620                                         GINT_TO_POINTER((int) index));
621 }
622
623 static void eth_tech_enable_tethering(struct connman_technology *technology,
624                                                 const char *bridge)
625 {
626         GList *list;
627         struct ethernet_data *ethernet;
628
629         for (list = eth_interface_list; list; list = list->next) {
630                 int index = GPOINTER_TO_INT(list->data);
631                 struct connman_device *device =
632                         connman_device_find_by_index(index);
633
634                 if (device) {
635                         ethernet = connman_device_get_data(device);
636                         if (ethernet)
637                                 remove_network(device, ethernet);
638                 }
639
640                 connman_technology_tethering_notify(technology, true);
641
642                 connman_inet_ifup(index);
643
644                 connman_inet_add_to_bridge(index, bridge);
645
646                 eth_tethering = true;
647         }
648 }
649
650 static void eth_tech_disable_tethering(struct connman_technology *technology,
651                                                 const char *bridge)
652 {
653         GList *list;
654
655         for (list = eth_interface_list; list; list = list->next) {
656                 int index = GPOINTER_TO_INT(list->data);
657                 struct connman_device *device =
658                         connman_device_find_by_index(index);
659
660                 connman_inet_remove_from_bridge(index, bridge);
661
662                 connman_technology_tethering_notify(technology, false);
663
664                 if (device)
665                         connman_device_reconnect_service(device);
666
667                 eth_tethering = false;
668         }
669 }
670
671 static int eth_tech_set_tethering(struct connman_technology *technology,
672                                 const char *identifier, const char *passphrase,
673                                 const char *bridge, bool enabled)
674 {
675         if (!connman_technology_is_tethering_allowed(
676                         CONNMAN_SERVICE_TYPE_ETHERNET))
677                 return 0;
678
679         DBG("bridge %s enabled %d", bridge, enabled);
680
681         if (enabled)
682                 eth_tech_enable_tethering(technology, bridge);
683         else
684                 eth_tech_disable_tethering(technology, bridge);
685
686         return 0;
687 }
688
689 static struct connman_technology_driver eth_tech_driver = {
690         .name                   = "ethernet",
691         .type                   = CONNMAN_SERVICE_TYPE_ETHERNET,
692         .probe                  = eth_tech_probe,
693         .remove                 = eth_tech_remove,
694         .add_interface          = eth_tech_add_interface,
695         .remove_interface       = eth_tech_remove_interface,
696         .set_tethering          = eth_tech_set_tethering,
697 };
698
699 #if defined TIZEN_EXT_WIFI_MESH
700 static int eth_mesh_add_to_bridge(const char *bridge)
701 {
702         GList *list;
703         struct ethernet_data *ethernet;
704
705         DBG("Add ethernet to bridge %s", bridge);
706
707         for (list = eth_interface_list; list; list = list->next) {
708                 int index = GPOINTER_TO_INT(list->data);
709                 struct connman_device *device =
710                         connman_device_find_by_index(index);
711
712                 if (device) {
713                         ethernet = connman_device_get_data(device);
714                         if (ethernet)
715                                 remove_network(device, ethernet);
716                 }
717
718                 connman_inet_ifup(index);
719
720                 connman_inet_add_to_bridge(index, bridge);
721         }
722
723         return 0;
724 }
725
726 static int eth_mesh_remove_from_bridge(const char *bridge)
727 {
728         GList *list;
729
730         DBG("Remove ethernet from bridge %s", bridge);
731
732         for (list = eth_interface_list; list; list = list->next) {
733                 int index = GPOINTER_TO_INT(list->data);
734
735                 connman_inet_remove_from_bridge(index, bridge);
736         }
737
738         return 0;
739 }
740
741 static struct connman_mesh_eth_driver eth_mesh_driver = {
742         .add_to_bridge          = eth_mesh_add_to_bridge,
743         .remove_from_bridge     = eth_mesh_remove_from_bridge,
744 };
745 #endif
746
747 static int ethernet_init(void)
748 {
749         int err;
750
751         err = connman_technology_driver_register(&eth_tech_driver);
752         if (err < 0)
753                 return err;
754
755 #if defined TIZEN_EXT_WIFI_MESH
756         err = connman_mesh_eth_driver_register(&eth_mesh_driver);
757         if (err < 0)
758                 return err;
759 #endif
760
761         err = connman_network_driver_register(&eth_network_driver);
762         if (err < 0)
763                 return err;
764
765         err = connman_device_driver_register(&eth_dev_driver);
766         if (err < 0) {
767                 connman_network_driver_unregister(&eth_network_driver);
768                 return err;
769         }
770
771         return 0;
772 }
773
774 static void ethernet_exit(void)
775 {
776         connman_technology_driver_unregister(&eth_tech_driver);
777
778 #if defined TIZEN_EXT_WIFI_MESH
779         connman_mesh_eth_driver_unregister(&eth_mesh_driver);
780 #endif
781
782         connman_network_driver_unregister(&eth_network_driver);
783
784         connman_device_driver_unregister(&eth_dev_driver);
785 }
786
787 CONNMAN_PLUGIN_DEFINE(ethernet, "Ethernet interface plugin", VERSION,
788                 CONNMAN_PLUGIN_PRIORITY_DEFAULT, ethernet_init, ethernet_exit)