diff --git a/Makefile b/Makefile index 97ba6b81f..8ce14e640 100644 --- a/Makefile +++ b/Makefile @@ -368,6 +368,7 @@ units: mod core lib $(UNITS_OBJ) $(MOD_OBJ) @$(CC) -o $(PREFIX)/test/modunit_dev_ppp.elf $(CFLAGS) -I. test/unit/modunit_pico_dev_ppp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a @$(CC) -o $(PREFIX)/test/modunit_mld.elf $(CFLAGS) -I. test/unit/modunit_pico_mld.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a @$(CC) -o $(PREFIX)/test/modunit_igmp.elf $(CFLAGS) -I. test/unit/modunit_pico_igmp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a + @$(CC) -o $(PREFIX)/test/modunit_hotplug_detection.elf $(CFLAGS) -I. test/unit/modunit_pico_hotplug_detection.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a devunits: mod core lib @echo -e "\n\t[UNIT TESTS SUITE: device drivers]" diff --git a/modules/pico_dev_tap.c b/modules/pico_dev_tap.c index 744bfae38..6998cf48b 100644 --- a/modules/pico_dev_tap.c +++ b/modules/pico_dev_tap.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "pico_device.h" #include "pico_dev_tap.h" #include "pico_stack.h" @@ -26,6 +27,26 @@ struct pico_device_tap { #define TUN_MTU 2048 +// We only support one global link state - we only have two USR signals, we +// can't spread these out over an arbitrary amount of devices. When you unplug +// one tap, you unplug all of them. + +static int link_state = 0; + +static void sig_handler(int signo) +{ + if (signo == SIGUSR1) + link_state = 0; + if (signo == SIGUSR2) + link_state = 1; +} + +static int tap_link_state(struct pico_device *self) +{ + return link_state; +} + + static int pico_tap_send(struct pico_device *dev, void *buf, int len) { struct pico_device_tap *tap = (struct pico_device_tap *) dev; @@ -155,10 +176,19 @@ struct pico_device *pico_tap_create(char *name) { struct pico_device_tap *tap = PICO_ZALLOC(sizeof(struct pico_device_tap)); uint8_t mac[6] = {}; + struct sigaction sa; if (!tap) return NULL; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = sig_handler; + + if ((sigaction(SIGUSR1, &sa, NULL) == 0) && + (sigaction(SIGUSR2, &sa, NULL) == 0)) + tap->dev.link_state = &tap_link_state; + tap->dev.overhead = 0; tap->fd = tap_open(name); if (tap->fd < 0) { diff --git a/modules/pico_hotplug_detection.c b/modules/pico_hotplug_detection.c new file mode 100644 index 000000000..732b4d776 --- /dev/null +++ b/modules/pico_hotplug_detection.c @@ -0,0 +1,134 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Frederik Van Slycken + *********************************************************************/ +#include "pico_protocol.h" +#include "pico_hotplug_detection.h" +#include "pico_tree.h" +#include "pico_device.h" + +struct pico_hotplug_device{ + struct pico_device *dev; + int prev_state; + struct pico_tree callbacks; +}; + +uint32_t timer_id = 0; + +static int pico_hotplug_dev_cmp(void *ka, void *kb) +{ + struct pico_hotplug_device *a = ka, *b = kb; + if (a->dev->hash < b->dev->hash) + return -1; + + if (a->dev->hash > b->dev->hash) + return 1; + + return 0; +} + +static int callback_compare(void *ka, void *kb) +{ + if (ka < kb) + return -1; + if (ka > kb) + return 1; + return 0; +} + +PICO_TREE_DECLARE(Hotplug_device_tree, pico_hotplug_dev_cmp); + +static void timer_cb(__attribute__((unused)) pico_time t, __attribute__((unused)) void* v) +{ + struct pico_tree_node *node = NULL, *safe = NULL, *cb_node = NULL, *cb_safe = NULL; + int new_state, event; + struct pico_hotplug_device *hpdev = NULL; + void (*cb)(struct pico_device *dev, int event); + + //we don't know if one of the callbacks might deregister, so be safe + pico_tree_foreach_safe(node, &Hotplug_device_tree, safe) + { + hpdev = node->keyValue; + new_state = hpdev->dev->link_state(hpdev->dev); + if (new_state != hpdev->prev_state) + { + if (new_state == 1){ + event = PICO_HOTPLUG_EVENT_UP; + } else { + event = PICO_HOTPLUG_EVENT_DOWN; + } + //we don't know if one of the callbacks might deregister, so be safe + pico_tree_foreach_safe(cb_node, &(hpdev->callbacks), cb_safe) + { + cb = cb_node->keyValue; + cb(hpdev->dev, event); + } + hpdev->prev_state = new_state; + } + } + + timer_id = pico_timer_add(PICO_HOTPLUG_INTERVAL, &timer_cb, NULL); +} + + +int pico_hotplug_register(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)) +{ + struct pico_hotplug_device *hotplug_dev; + struct pico_hotplug_device search = {.dev = dev}; + + if (dev->link_state == NULL){ + pico_err = PICO_ERR_EPROTONOSUPPORT; + return -1; + } + + hotplug_dev = (struct pico_hotplug_device*)pico_tree_findKey(&Hotplug_device_tree, &search); + if (! hotplug_dev ) + { + hotplug_dev = PICO_ZALLOC(sizeof(struct pico_hotplug_device)); + if (!hotplug_dev) + { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + hotplug_dev->dev = dev; + hotplug_dev->prev_state = dev->link_state(hotplug_dev->dev); + hotplug_dev->callbacks.root = &LEAF; + hotplug_dev->callbacks.compare = &callback_compare; + pico_tree_insert(&Hotplug_device_tree, hotplug_dev); + } + pico_tree_insert(&(hotplug_dev->callbacks), cb); + + if (timer_id == 0) + { + timer_id = pico_timer_add(PICO_HOTPLUG_INTERVAL, &timer_cb, NULL); + } + + return 0; +} + +int pico_hotplug_deregister(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)) +{ + struct pico_hotplug_device* hotplug_dev; + struct pico_hotplug_device search = {.dev = dev}; + + hotplug_dev = (struct pico_hotplug_device*)pico_tree_findKey(&Hotplug_device_tree, &search); + if (!hotplug_dev) + //wasn't registered + return 0; + pico_tree_delete(&hotplug_dev->callbacks, cb); + if (pico_tree_empty(&hotplug_dev->callbacks)) + { + pico_tree_delete(&Hotplug_device_tree, hotplug_dev); + PICO_FREE(hotplug_dev); + } + + if (pico_tree_empty(&Hotplug_device_tree) && timer_id != 0) + { + pico_timer_cancel(timer_id); + timer_id = 0; + } + return 0; +} + diff --git a/modules/pico_hotplug_detection.h b/modules/pico_hotplug_detection.h new file mode 100644 index 000000000..19319b46a --- /dev/null +++ b/modules/pico_hotplug_detection.h @@ -0,0 +1,20 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + Authors: Frederik Van Slycken + *********************************************************************/ +#ifndef INCLUDE_PICO_SUPPORT_HOTPLUG +#define INCLUDE_PICO_SUPPORT_HOTPLUG +#include "pico_stack.h" + +#define PICO_HOTPLUG_EVENT_UP 1 /* link went up */ +#define PICO_HOTPLUG_EVENT_DOWN 2 /* link went down */ + +#define PICO_HOTPLUG_INTERVAL 100 + +int pico_hotplug_register(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)); +int pico_hotplug_deregister(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)); + +#endif /* _INCLUDE_PICO_SUPPORT_HOTPLUG */ + diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index 0ead3e92c..6926e3d52 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -1307,6 +1307,7 @@ int pico_ipv4_link_del(struct pico_device *dev, struct pico_ip4 address) PICO_FREE(g); } } while(0); + pico_free(found->MCASTGroups); #endif pico_ipv4_cleanup_routes(found); diff --git a/modules/pico_slaacv4.c b/modules/pico_slaacv4.c index b6c0d807f..ff9b33d1b 100644 --- a/modules/pico_slaacv4.c +++ b/modules/pico_slaacv4.c @@ -8,6 +8,7 @@ #include "pico_arp.h" #include "pico_constants.h" #include "pico_stack.h" +#include "pico_hotplug_detection.h" #ifdef PICO_SUPPORT_SLAACV4 @@ -26,6 +27,7 @@ #define ANNOUNCE_NB 2 /* number of announcement packets */ #define ANNOUNCE_INTERVAL 2 /* time between announcement packets */ #define MAX_CONFLICTS 10 /* max conflicts before rate limiting */ +#define MAX_CONFLICTS_FAIL 20 /* max conflicts before declaring failure */ #define RATE_LIMIT_INTERVAL 60 /* time between successive attempts */ #define DEFEND_INTERVAL 10 /* minimum interval between defensive ARP */ @@ -48,6 +50,8 @@ struct slaacv4_cookie { void (*cb)(struct pico_ip4 *ip, uint8_t code); }; +static void pico_slaacv4_hotplug_cb(struct pico_device *dev, int event); + static struct slaacv4_cookie slaacv4_local; static uint32_t pico_slaacv4_getip(struct pico_device *dev, uint8_t rand) @@ -125,8 +129,6 @@ static void pico_slaacv4_send_probe_timer(pico_time now, void *arg) } } - - static void pico_slaacv4_receive_ipconflict(int reason) { struct slaacv4_cookie *tmp = &slaacv4_local; @@ -153,10 +155,20 @@ static void pico_slaacv4_receive_ipconflict(int reason) tmp->probe_try_nb++; tmp->timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, tmp); } + else if (tmp->conflict_nb < MAX_CONFLICTS_FAIL) + { + tmp->state = SLAACV4_CLAIMING; + tmp->probe_try_nb = 0; + tmp->announce_nb = 0; + tmp->ip.addr = pico_slaacv4_getip(tmp->device, (uint8_t)1); + pico_arp_register_ipconflict(&tmp->ip, &tmp->device->eth->mac, pico_slaacv4_receive_ipconflict); + tmp->timer = pico_timer_add(RATE_LIMIT_INTERVAL * 1000, pico_slaacv4_send_probe_timer, tmp); + } else { if (tmp->cb != NULL) { + pico_hotplug_deregister(tmp->device, &pico_slaacv4_hotplug_cb); tmp->cb(&tmp->ip, PICO_SLAACV4_ERROR); } @@ -165,6 +177,30 @@ static void pico_slaacv4_receive_ipconflict(int reason) } +static void pico_slaacv4_hotplug_cb(struct pico_device *dev, int event) +{ + struct slaacv4_cookie *tmp = &slaacv4_local; + + if (event == PICO_HOTPLUG_EVENT_UP ) + { + slaacv4_local.state = SLAACV4_CLAIMING; + tmp->probe_try_nb = 0; + tmp->announce_nb = 0; + + pico_arp_register_ipconflict(&tmp->ip, &tmp->device->eth->mac, pico_slaacv4_receive_ipconflict); + pico_arp_request(tmp->device, &tmp->ip, PICO_ARP_PROBE); + tmp->probe_try_nb++; + tmp->timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, tmp); + + } + else + { + if (tmp->state == SLAACV4_CLAIMED ) + pico_ipv4_link_del(tmp->device, tmp->ip); + pico_slaacv4_cancel_timers(tmp); + } +} + int pico_slaacv4_claimip(struct pico_device *dev, void (*cb)(struct pico_ip4 *ip, uint8_t code)) { struct pico_ip4 ip; @@ -174,14 +210,37 @@ int pico_slaacv4_claimip(struct pico_device *dev, void (*cb)(struct pico_ip4 *ip return -1; } - ip.addr = pico_slaacv4_getip(dev, 0); + if( dev->link_state != NULL ) + { + //hotplug detect will work - pico_slaacv4_init_cookie(&ip, dev, &slaacv4_local, cb); - pico_arp_register_ipconflict(&ip, &dev->eth->mac, pico_slaacv4_receive_ipconflict); - pico_arp_request(dev, &ip, PICO_ARP_PROBE); - slaacv4_local.state = SLAACV4_CLAIMING; - slaacv4_local.probe_try_nb++; - slaacv4_local.timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, &slaacv4_local); + ip.addr = pico_slaacv4_getip(dev, 0); + pico_slaacv4_init_cookie(&ip, dev, &slaacv4_local, cb); + + if (pico_hotplug_register(dev, &pico_slaacv4_hotplug_cb)) + { + return -1; + } + if (dev->link_state(dev) == 1) + { + pico_arp_register_ipconflict(&ip, &dev->eth->mac, pico_slaacv4_receive_ipconflict); + pico_arp_request(dev, &ip, PICO_ARP_PROBE); + slaacv4_local.state = SLAACV4_CLAIMING; + slaacv4_local.probe_try_nb++; + slaacv4_local.timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, &slaacv4_local); + } + } + else + { + ip.addr = pico_slaacv4_getip(dev, 0); + + pico_slaacv4_init_cookie(&ip, dev, &slaacv4_local, cb); + pico_arp_register_ipconflict(&ip, &dev->eth->mac, pico_slaacv4_receive_ipconflict); + pico_arp_request(dev, &ip, PICO_ARP_PROBE); + slaacv4_local.state = SLAACV4_CLAIMING; + slaacv4_local.probe_try_nb++; + slaacv4_local.timer = pico_timer_add(PROBE_WAIT * 1000, pico_slaacv4_send_probe_timer, &slaacv4_local); + } return 0; } @@ -201,7 +260,7 @@ void pico_slaacv4_unregisterip(void) pico_slaacv4_cancel_timers(tmp); pico_slaacv4_init_cookie(&empty, NULL, tmp, NULL); pico_arp_register_ipconflict(&tmp->ip, NULL, NULL); - + pico_hotplug_deregister(tmp->device, &pico_slaacv4_hotplug_cb); } #endif diff --git a/rules/slaacv4.mk b/rules/slaacv4.mk index cd3d2975c..2073a2ab0 100644 --- a/rules/slaacv4.mk +++ b/rules/slaacv4.mk @@ -1,2 +1,2 @@ OPTIONS+=-DPICO_SUPPORT_SLAACV4 -MOD_OBJ+=$(LIBBASE)modules/pico_slaacv4.o +MOD_OBJ+=$(LIBBASE)modules/pico_slaacv4.o $(LIBBASE)modules/pico_hotplug_detection.o diff --git a/test/unit/modunit_pico_hotplug_detection.c b/test/unit/modunit_pico_hotplug_detection.c new file mode 100644 index 000000000..946e3997f --- /dev/null +++ b/test/unit/modunit_pico_hotplug_detection.c @@ -0,0 +1,174 @@ +#include "pico_hotplug_detection.h" +#include "pico_tree.h" +#include "pico_device.h" +#include "modules/pico_hotplug_detection.c" +#include "check.h" +#include "pico_dev_null.c" + +/* stubs for timer */ +static int8_t timer_active = 0; +void (*timer_cb_function)(pico_time, void *); +uint32_t pico_timer_add(pico_time expire, void (*timer)(pico_time, void *), void *arg) { + timer_active++; + timer_cb_function = timer; + + return 123; +} + +void pico_timer_cancel(uint32_t id){ + timer_active--; + fail_if(id != 123); +} + +/* callbacks for testing */ +uint32_t cb_one_cntr = 0; +int cb_one_last_event = 0; +void cb_one(struct pico_device *dev, int event) +{ + cb_one_cntr++; + cb_one_last_event = event; +} +uint32_t cb_two_cntr = 0; +int cb_two_last_event = 0; +void cb_two(struct pico_device *dev, int event) +{ + cb_two_cntr++; + cb_two_last_event = event; +} + +/* link state functions for the testing devices */ +int state_a = 0; +int link_state_a(struct pico_device *self) +{ + return state_a; +} + +int state_b = 0; +int link_state_b(struct pico_device *self) +{ + return state_b; +} + + +START_TEST(tc_pico_hotplug_reg_dereg) +{ + //create some devices + struct pico_device *dev_a , *dev_b; + dev_a = pico_null_create("dummy1"); + dev_b = pico_null_create("dummy2"); + + dev_a->link_state = &link_state_a; + dev_b->link_state = &link_state_b; + + //add some function pointers to be called + pico_hotplug_register(dev_a, &cb_one); + fail_unless(timer_active == 1); + pico_hotplug_register(dev_a, &cb_two); + pico_hotplug_register(dev_b, &cb_two); + fail_unless(timer_active == 1); + + //remove function pointers + pico_hotplug_deregister(dev_a, &cb_one); + pico_hotplug_deregister(dev_a, &cb_two); + pico_hotplug_deregister(dev_b, &cb_two); + + //check that our tree is empty at the end + fail_unless(pico_tree_empty(&Hotplug_device_tree)); + + //register functions multiple times + pico_hotplug_register(dev_a, &cb_one); + pico_hotplug_register(dev_a, &cb_one); + pico_hotplug_register(dev_a, &cb_two); + pico_hotplug_register(dev_a, &cb_two); + pico_hotplug_register(dev_b, &cb_two); + pico_hotplug_register(dev_b, &cb_two); + + //remove function pointers once + pico_hotplug_deregister(dev_a, &cb_one); + pico_hotplug_deregister(dev_a, &cb_two); + fail_unless(timer_active == 1); + pico_hotplug_deregister(dev_b, &cb_two); + fail_unless(timer_active == 0); + + //check that our tree is empty at the end + fail_unless(pico_tree_empty(&Hotplug_device_tree)); +} +END_TEST + +START_TEST(tc_pico_hotplug_callbacks) +{ + //create some devices + struct pico_device *dev_a , *dev_b; + dev_a = pico_null_create("dummy1"); + dev_b = pico_null_create("dummy2"); + + dev_a->link_state = &link_state_a; + dev_b->link_state = &link_state_b; + + //add some function pointers to be called + pico_hotplug_register(dev_a, &cb_one); + pico_hotplug_register(dev_a, &cb_two); + pico_hotplug_register(dev_b, &cb_two); + + fail_unless(timer_active == 1); + + timer_active = 0; + timer_cb_function(0, NULL); + fail_unless(timer_active == 1); + fail_unless(cb_one_cntr == 0); + fail_unless(cb_two_cntr == 0); + + state_a = 1; + timer_active = 0; + timer_cb_function(0, NULL); + fail_unless(timer_active == 1); + fail_unless(cb_one_cntr == 1); + fail_unless(cb_one_last_event == PICO_HOTPLUG_EVENT_UP ); + fail_unless(cb_two_cntr == 1); + fail_unless(cb_two_last_event == PICO_HOTPLUG_EVENT_UP ); + + state_b = 1; + timer_active = 0; + timer_cb_function(0, NULL); + fail_unless(timer_active == 1); + fail_unless(cb_one_cntr == 1); + fail_unless(cb_one_last_event == PICO_HOTPLUG_EVENT_UP ); + fail_unless(cb_two_cntr == 2); + fail_unless(cb_two_last_event == PICO_HOTPLUG_EVENT_UP ); + + state_a = 0; + state_b = 0; + timer_active = 0; + timer_cb_function(0, NULL); + fail_unless(timer_active == 1); + fail_unless(cb_one_cntr == 2); + fail_unless(cb_one_last_event == PICO_HOTPLUG_EVENT_DOWN ); + fail_unless(cb_two_cntr == 4); + fail_unless(cb_two_last_event == PICO_HOTPLUG_EVENT_DOWN ); +} +END_TEST + +Suite *pico_suite(void) +{ + Suite *s = suite_create("PicoTCP"); + + TCase *TCase_pico_hotplug_reg_dereg = tcase_create("Unit test for pico_hotplug_reg_dereg"); + TCase *TCase_pico_hotplug_callbacks = tcase_create("Unit test for pico_hotplug_callbacks"); + + tcase_add_test(TCase_pico_hotplug_reg_dereg, tc_pico_hotplug_reg_dereg); + suite_add_tcase(s, TCase_pico_hotplug_reg_dereg); + tcase_add_test(TCase_pico_hotplug_callbacks, tc_pico_hotplug_callbacks); + suite_add_tcase(s, TCase_pico_hotplug_callbacks); +return s; +} + +int main(void) +{ + int fails; + Suite *s = pico_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +} diff --git a/test/units.c b/test/units.c index 1bc7aaae8..c34265239 100644 --- a/test/units.c +++ b/test/units.c @@ -34,6 +34,7 @@ #include "pico_ipfilter.c" #include "pico_tree.c" #include "pico_slaacv4.c" +#include "pico_hotplug_detection.c" #ifdef PICO_SUPPORT_MCAST #include "pico_igmp.c" #endif diff --git a/test/units.sh b/test/units.sh index 717a6b5e9..66d1ba03c 100755 --- a/test/units.sh +++ b/test/units.sh @@ -22,6 +22,7 @@ ASAN_OPTIONS="detect_leaks=0" ./build/test/modunit_aodv.elf || exit 1 ASAN_OPTIONS="detect_leaks=0" ./build/test/modunit_dev_ppp.elf || exit 1 ASAN_OPTIONS="detect_leaks=0" ./build/test/modunit_mld.elf || exit 1 ASAN_OPTIONS="detect_leaks=0" ./build/test/modunit_igmp.elf || exit 1 +ASAN_OPTIONS="detect_leaks=0" ./build/test/modunit_hotplug_detection.elf || exit 1 MAXMEM=`cat /tmp/pico-mem-report-* | sort -r -n |head -1` echo