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