From ca8f61447d5059404400307b28caacee10019b6f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 3 Feb 2015 11:15:35 +0100 Subject: [PATCH 01/61] Removed old company ASCII art in the code. Closed #218 --- stack/pico_stack.c | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/stack/pico_stack.c b/stack/pico_stack.c index 3da8d7e62..8603d31a2 100644 --- a/stack/pico_stack.c +++ b/stack/pico_stack.c @@ -894,41 +894,6 @@ static int calc_score(int *score, int *index, int avg[][PROTO_DEF_AVG_NR], int * return 0; } - - -/* - - . - .vS. - . :iXXZUZXXe= - )2SS2SS2S2S2I =oS2S2S2S2X22;. _vuXS22S2S2S22i ._wZZXZZZXZZXZX= - )22S2S2S2S2Sl |S2S2S22S2SSSXc: .S2SS2S2S22S2SS= .]#XZZZXZXZZZZZZ: - )oSS2SS2S2Sol |2}!"""!32S22S(. uS2S2Se**12oS2e ]dXZZXX2?YYXXXZ* - .:2S2So:..-. . :]S2S2e;=X2SS2o .)oc ]XZZXZ( =nX: - .S2S22. ___s_i,.)oS2So(;2SS2So, ` 3XZZZZc, - - .S2SSo. =oXXXSSS2XoS2S2o( XS2S2XSos;. ]ZZZZXXXX|= - .S2S22. .)S2S2S22S2S2S2S2o( "X2SS2S2S2Sus,, +3XZZZZZXZZoos_ - .S2S22. .]2S2SS22S222S2SS2o( ]S22S2S2S222So :3XXZZZZZZZZXXv - .S2S22. =u2SS2e"~---"{2S2So( -"12S2S2SSS2Su. "?SXXXZXZZZZXo - .S2SSo. )SS22z; :S2S2o( ={vS2S2S22v .;:S2S2o( . - - - */ - void pico_stack_tick(void) { static int score[PROTO_DEF_NR] = { From 5609878e7f8af7587670a2575d27e8e5623dcbae Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 3 Feb 2015 13:59:52 +0100 Subject: [PATCH 02/61] Added iperf client to test tools --- test/examples/Makefile | 1 + test/examples/iperfc.c | 126 +++++++++++++++++++++++++++++++++++++++++ test/picoapp.c | 3 + 3 files changed, 130 insertions(+) create mode 100644 test/examples/iperfc.c diff --git a/test/examples/Makefile b/test/examples/Makefile index 09b67b54e..597ca9495 100644 --- a/test/examples/Makefile +++ b/test/examples/Makefile @@ -27,6 +27,7 @@ $(PREFIX)/examples/udp_client.o \ $(PREFIX)/examples/udp_echo.o \ $(PREFIX)/examples/udpnat.o \ $(PREFIX)/examples/udp_sendto_test.o \ +$(PREFIX)/examples/iperfc.o \ all: $(OBJS) diff --git a/test/examples/iperfc.c b/test/examples/iperfc.c new file mode 100644 index 000000000..18130f639 --- /dev/null +++ b/test/examples/iperfc.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include "pico_stack.h" +#include "pico_socket.h" + +#define DURATION 30 + +struct iperf_hdr { + int32_t flags; /* 0 */ + int32_t numThreads; /* 1 */ + int32_t mPort; /* 5001 */ + int32_t bufferlen; /* 0 */ + int32_t mWinBand; /* 0 */ + int32_t mAmount; /* 0xfffffc18 */ +}; + +#define IPERF_PORT 5001 +#define MTU 1444 +#define SEND_BUF_SIZ (1024 * 2048) + +char *cpy_arg(char **dst, char *str); +extern int IPV6_MODE; + +static pico_time deadline; + +static void panic(void) +{ + for(;;); +} + +static char buf[MTU] = {}; + +static void buf_paint(void) +{ + char paint[11] = "0123456789"; + int i; + for (i = 0; i < MTU; i++) { + buf[i] = paint[i % 10]; + } +} + +static void send_hdr(struct pico_socket *s) +{ + struct iperf_hdr hdr = {} ; + hdr.numThreads = long_be(1); + hdr.mPort = long_be(5001); + hdr.mAmount = long_be(0xfffffc18); + pico_socket_write(s, &hdr, sizeof(hdr)); + deadline = PICO_TIME_MS() + DURATION * 1000; +} + +static void iperf_cb(uint16_t ev, struct pico_socket *s) +{ + int r; + if (ev & PICO_SOCK_EV_CONN) { + send_hdr(s); + return; + } + + if (ev & PICO_SOCK_EV_WR) { + if (PICO_TIME_MS() > deadline) { + pico_socket_close(s); + return; + } + pico_socket_write(s, buf, MTU); + } +} + +static void iperfc_socket_setup(union pico_address *addr, uint16_t family) +{ + int yes = 1; + uint16_t send_port = 0; + struct pico_socket *s = NULL; + uint32_t bufsize = SEND_BUF_SIZ; + send_port = short_be(5001); + s = pico_socket_open(family, PICO_PROTO_TCP, &iperf_cb); + pico_socket_setoption(s, PICO_SOCKET_OPT_SNDBUF, &bufsize); + pico_socket_connect(s, addr, send_port); +} + +void app_iperfc(char *arg) +{ + struct pico_ip4 my_eth_addr, netmask; + struct pico_device *pico_dev_eth; + char *daddr = NULL, *dport = NULL; + char *nxt = arg; + uint16_t send_port = 0, listen_port = short_be(5001); + int i = 0, ret = 0, yes = 1; + struct pico_socket *s = NULL; + uint16_t family = PICO_PROTO_IPV4; + union pico_address dst = { + .ip4 = {0}, .ip6 = {{0}} + }; + union pico_address inaddr_any = { + .ip4 = {0}, .ip6 = {{0}} + }; + + /* start of argument parsing */ + if (nxt) { + nxt = cpy_arg(&daddr, arg); + if (daddr) { + if (!IPV6_MODE) + pico_string_to_ipv4(daddr, &dst.ip4.addr); + + #ifdef PICO_SUPPORT_IPV6 + else { + pico_string_to_ipv6(daddr, dst.ip6.addr); + family = PICO_PROTO_IPV6; + } + #endif + } else { + goto out; + } + } else { + /* missing dest_addr */ + goto out; + } + + iperfc_socket_setup(&dst, family); + return; +out: + dbg("Error parsing options!\n"); + exit(1); +} + diff --git a/test/picoapp.c b/test/picoapp.c index e11e8d5af..cf675fdc6 100644 --- a/test/picoapp.c +++ b/test/picoapp.c @@ -62,6 +62,7 @@ void app_slaacv4(char *args); void app_udpecho(char *args); void app_sendto_test(char *args); void app_noop(void); +void app_iperfc(char *args); struct pico_ip4 ZERO_IP4 = { @@ -611,6 +612,8 @@ int main(int argc, char **argv) #endif } else IF_APPNAME("udp_sendto_test") { app_sendto_test(args); + } else IF_APPNAME("iperfc") { + app_iperfc(args); } else { fprintf(stderr, "Unknown application %s\n", name); usage(argv[0]); From 8a0469836da962bd1cdabeef2c3da7554497ce8c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 3 Feb 2015 15:15:04 +0100 Subject: [PATCH 03/61] Added pico_socket_getpeername --- docs/user_manual/chap_api_sock.tex | 41 ++++++++++++++++++++++++++++++ include/pico_socket.h | 1 + stack/pico_socket.c | 34 +++++++++++++++++++++++++ test/examples/iperfc.c | 7 ++++- 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/docs/user_manual/chap_api_sock.tex b/docs/user_manual/chap_api_sock.tex index d75309e57..8671774d1 100644 --- a/docs/user_manual/chap_api_sock.tex +++ b/docs/user_manual/chap_api_sock.tex @@ -365,6 +365,47 @@ \subsubsection*{Example} } \end{verbatim} +\subsection{pico$\_$socket$\_$getpeername} + +\subsubsection*{Description} +This function returns the IP-address of the remote peer connected to the specified socket. + +\subsubsection*{Function prototype} +\begin{verbatim} +int pico_socket_getpeername(struct pico_socket *s, void *remote_addr, uint16_t *port, + uint16_t *proto); +\end{verbatim} + + +\subsubsection*{Parameters} +\begin{itemize}[noitemsep] +\item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} +\item \texttt{remote$\_$addr} - Address (IPv4 or IPv6) associated to the socket remote endpoint +\item \texttt{port} - Local portnumber associated to the socket +\item \texttt{proto} - Proto of the address returned in the \texttt{local$\_$addr} field. Can be either \texttt{PICO$\_$PROTO$\_$IPV4} or \texttt{PICO$\_$PROTO$\_$IPV6} +\end{itemize} + +\subsubsection*{Return value} +On success, this call returns 0 and populates the three fields {local$\_$addr} \texttt{port} and \texttt{proto} accordingly. +On error, -1 is returned, and \texttt{pico$\_$err} is set appropriately. + +\subsubsection*{Errors} +\begin{itemize}[noitemsep] +\item \texttt{PICO$\_$ERR$\_$EINVAL} - invalid argument(s) provided +\item \texttt{PICO$\_$ERR$\_$ENOTCONN} - the socket is not connected to any peer +\end{itemize} + +\subsubsection*{Example} +\begin{verbatim} +errMsg = pico_socket_getpeername(sk_tcp, address, &port, &proto); +if (errMsg == 0) { + if (proto == PICO_PROTO_IPV4) + addr4 = (struct pico_ip4 *)address; + else + addr6 = (struct pico_ip6 *)address; +} +\end{verbatim} + \subsection{pico$\_$socket$\_$connect} diff --git a/include/pico_socket.h b/include/pico_socket.h index 9e8beafac..e93aa5738 100644 --- a/include/pico_socket.h +++ b/include/pico_socket.h @@ -192,6 +192,7 @@ int pico_socket_recv(struct pico_socket *s, void *buf, int len); int pico_socket_bind(struct pico_socket *s, void *local_addr, uint16_t *port); int pico_socket_getname(struct pico_socket *s, void *local_addr, uint16_t *port, uint16_t *proto); +int pico_socket_getpeername(struct pico_socket *s, void *local_addr, uint16_t *port, uint16_t *proto); int pico_socket_connect(struct pico_socket *s, const void *srv_addr, uint16_t remote_port); int pico_socket_listen(struct pico_socket *s, const int backlog); diff --git a/stack/pico_socket.c b/stack/pico_socket.c index 425af012a..25ccd1d81 100644 --- a/stack/pico_socket.c +++ b/stack/pico_socket.c @@ -1391,6 +1391,40 @@ int pico_socket_getname(struct pico_socket *s, void *local_addr, uint16_t *port, return 0; } +int pico_socket_getpeername(struct pico_socket *s, void *remote_addr, uint16_t *port, uint16_t *proto) +{ + if (!s || !remote_addr || !port || !proto) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + if ((s->state & PICO_SOCKET_STATE_CONNECTED) == 0) { + pico_err = PICO_ERR_ENOTCONN; + return -1; + } + + if (is_sock_ipv4(s)) { + #ifdef PICO_SUPPORT_IPV4 + struct pico_ip4 *ip = (struct pico_ip4 *)remote_addr; + ip->addr = s->remote_addr.ip4.addr; + *proto = PICO_PROTO_IPV4; + #endif + } else if (is_sock_ipv6(s)) { + #ifdef PICO_SUPPORT_IPV6 + struct pico_ip6 *ip = (struct pico_ip6 *)remote_addr; + memcpy(ip->addr, s->remote_addr.ip6.addr, PICO_SIZE_IP6); + *proto = PICO_PROTO_IPV6; + #endif + } else { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + *port = s->remote_port; + return 0; + +} + int pico_socket_bind(struct pico_socket *s, void *local_addr, uint16_t *port) { if (!s || !local_addr || !port) { diff --git a/test/examples/iperfc.c b/test/examples/iperfc.c index 18130f639..eb2016a9e 100644 --- a/test/examples/iperfc.c +++ b/test/examples/iperfc.c @@ -21,6 +21,7 @@ struct iperf_hdr { char *cpy_arg(char **dst, char *str); extern int IPV6_MODE; +void deferred_exit(pico_time now, void *arg); static pico_time deadline; @@ -53,6 +54,7 @@ static void send_hdr(struct pico_socket *s) static void iperf_cb(uint16_t ev, struct pico_socket *s) { int r; + static int end = 0; if (ev & PICO_SOCK_EV_CONN) { send_hdr(s); return; @@ -61,10 +63,13 @@ static void iperf_cb(uint16_t ev, struct pico_socket *s) if (ev & PICO_SOCK_EV_WR) { if (PICO_TIME_MS() > deadline) { pico_socket_close(s); - return; + pico_timer_add(2000, deferred_exit, NULL); } pico_socket_write(s, buf, MTU); } + if (!(end) && (ev & (PICO_SOCK_EV_FIN | PICO_SOCK_EV_CLOSE))) { + pico_timer_add(2000, deferred_exit, NULL); + } } static void iperfc_socket_setup(union pico_address *addr, uint16_t family) From 1f49f20a8e2f4de56fb3069f0fc443056674fc14 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 3 Feb 2015 15:20:16 +0100 Subject: [PATCH 04/61] Added prototype for pico_socket_getpeername --- include/pico_socket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pico_socket.h b/include/pico_socket.h index e93aa5738..e96b37aab 100644 --- a/include/pico_socket.h +++ b/include/pico_socket.h @@ -192,7 +192,7 @@ int pico_socket_recv(struct pico_socket *s, void *buf, int len); int pico_socket_bind(struct pico_socket *s, void *local_addr, uint16_t *port); int pico_socket_getname(struct pico_socket *s, void *local_addr, uint16_t *port, uint16_t *proto); -int pico_socket_getpeername(struct pico_socket *s, void *local_addr, uint16_t *port, uint16_t *proto); +int pico_socket_getpeername(struct pico_socket *s, void *remote_addr, uint16_t *port, uint16_t *proto); int pico_socket_connect(struct pico_socket *s, const void *srv_addr, uint16_t remote_port); int pico_socket_listen(struct pico_socket *s, const int backlog); From 276e3ec140586ace52cc4e3263b6aa0e8777f6c7 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 3 Feb 2015 15:40:54 +0100 Subject: [PATCH 05/61] Added performance test --- test/examples/iperfc.c | 4 +++- test/perf.sh | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100755 test/perf.sh diff --git a/test/examples/iperfc.c b/test/examples/iperfc.c index eb2016a9e..eec2fd027 100644 --- a/test/examples/iperfc.c +++ b/test/examples/iperfc.c @@ -60,15 +60,17 @@ static void iperf_cb(uint16_t ev, struct pico_socket *s) return; } - if (ev & PICO_SOCK_EV_WR) { + if ((!end) && (ev & PICO_SOCK_EV_WR)) { if (PICO_TIME_MS() > deadline) { pico_socket_close(s); pico_timer_add(2000, deferred_exit, NULL); + end++; } pico_socket_write(s, buf, MTU); } if (!(end) && (ev & (PICO_SOCK_EV_FIN | PICO_SOCK_EV_CLOSE))) { pico_timer_add(2000, deferred_exit, NULL); + end++; } } diff --git a/test/perf.sh b/test/perf.sh new file mode 100755 index 000000000..c899c41d2 --- /dev/null +++ b/test/perf.sh @@ -0,0 +1,23 @@ +#!/bin/bash +THRESHOLD=300 + +(iperf -s >/tmp/iperf.log)& +./build/test/picoapp.elf --vde pic0:/tmp/pic0.ctl:10.50.0.2:255.255.255.0:10.50.0.1: --app iperfc:10.50.0.1: &>/dev/null +killall iperf +SPEED=`cat /tmp/iperf.log |grep Mbits |cut -d " " -f 12` +UNITS=`cat /tmp/iperf.log |grep Mbits |cut -d " " -f 13` + +if [ ["$UNITS"] != ["Mbits/sec"] ]; then + echo "Wrong test result units: expected Mbits/sec, got $UNITS" + exit 1 +fi + +if (test $SPEED -lt $THRESHOLD); then + echo "Speed too low: expected $THRESHOLD MBits/s, got $SPEED $UNITS" + exit 2 +fi + +echo Test result: $SPEED $UNITS + +rm -f /tmp/iperf.log +exit 0 From 463e8d69b7fbf4b38dc1fae7f7ffb809c91c5c13 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 3 Feb 2015 15:56:10 +0100 Subject: [PATCH 06/61] Added switch command at beginning of perf script --- test/perf.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/perf.sh b/test/perf.sh index c899c41d2..962b8983b 100755 --- a/test/perf.sh +++ b/test/perf.sh @@ -1,5 +1,7 @@ #!/bin/bash THRESHOLD=300 +sh ./test/vde_sock_start_user.sh +sleep 2 (iperf -s >/tmp/iperf.log)& ./build/test/picoapp.elf --vde pic0:/tmp/pic0.ctl:10.50.0.2:255.255.255.0:10.50.0.1: --app iperfc:10.50.0.1: &>/dev/null From 997696737db5e6f91830b3b56351abf82852cfe9 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 3 Feb 2015 17:24:15 +0100 Subject: [PATCH 07/61] Fixed perf script --- test/perf.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/perf.sh b/test/perf.sh index 962b8983b..c9debbea7 100755 --- a/test/perf.sh +++ b/test/perf.sh @@ -6,8 +6,9 @@ sleep 2 (iperf -s >/tmp/iperf.log)& ./build/test/picoapp.elf --vde pic0:/tmp/pic0.ctl:10.50.0.2:255.255.255.0:10.50.0.1: --app iperfc:10.50.0.1: &>/dev/null killall iperf -SPEED=`cat /tmp/iperf.log |grep Mbits |cut -d " " -f 12` -UNITS=`cat /tmp/iperf.log |grep Mbits |cut -d " " -f 13` +RES=`cat /tmp/iperf.log |grep Mbits |sed -e "s/.*Bytes//g" |sed -e "s/^[ ]*//g"` +SPEED=`echo $RES | cut -d " " -f 1` +UNITS=`echo $RES | cut -d " " -f 2` if [ ["$UNITS"] != ["Mbits/sec"] ]; then echo "Wrong test result units: expected Mbits/sec, got $UNITS" From 84c6b30a9de86f8cc91dc14d5a2e166a885ce352 Mon Sep 17 00:00:00 2001 From: Maxime Vincent Date: Thu, 12 Feb 2015 12:48:43 +0100 Subject: [PATCH 08/61] proper lpc17xx support --- Makefile | 6 ++++ .../arch/{pico_lpc1768.h => pico_lpc17xx.h} | 0 include/arch/pico_lpc18xx.h | 31 +++++++++++-------- include/pico_config.h | 4 ++- 4 files changed, 27 insertions(+), 14 deletions(-) rename include/arch/{pico_lpc1768.h => pico_lpc17xx.h} (100%) diff --git a/Makefile b/Makefile index a10209d01..4351976e7 100644 --- a/Makefile +++ b/Makefile @@ -159,6 +159,12 @@ ifeq ($(ARCH),lpc18xx) -mcpu=cortex-m3 -mthumb -MMD -MP -DLPC18XX endif +ifeq ($(ARCH),lpc17xx) + CFLAGS+=-fmessage-length=0 -fno-builtin \ + -ffunction-sections -fdata-sections -mlittle-endian \ + -mcpu=cortex-m3 -mthumb -MMD -MP -DLPC17XX +endif + ifeq ($(ARCH),lpc43xx) CFLAGS+=-fmessage-length=0 -fno-builtin \ -ffunction-sections -fdata-sections -mlittle-endian \ diff --git a/include/arch/pico_lpc1768.h b/include/arch/pico_lpc17xx.h similarity index 100% rename from include/arch/pico_lpc1768.h rename to include/arch/pico_lpc17xx.h diff --git a/include/arch/pico_lpc18xx.h b/include/arch/pico_lpc18xx.h index c038c956d..849a01c2d 100644 --- a/include/arch/pico_lpc18xx.h +++ b/include/arch/pico_lpc18xx.h @@ -13,8 +13,7 @@ #define dbg(...) - -extern volatile uint32_t tassTick; +extern volatile unsigned int tassTick; #ifdef PICO_SUPPORT_RTOS #define PICO_SUPPORT_MUTEX @@ -77,19 +76,25 @@ static inline void pico_free(void *x) return pico_ffree(x); } +#else +//#define MEM_MEAS +#ifdef MEM_MEAS + extern void * memmeas_zalloc(size_t size); + extern void memmeas_free(void *); + #define pico_free(x) memmeas_free(x) + #define pico_zalloc(x) memmeas_zalloc(x) #else #define pico_free(x) free(x) - -static inline void *pico_zalloc(size_t size) -{ - void *ptr = malloc(size); - - if(ptr) - memset(ptr, 0u, size); - - return ptr; -} - + static inline void *pico_zalloc(size_t size) + { + void *ptr = malloc(size); + + if(ptr) + memset(ptr, 0u, size); + + return ptr; + } +#endif #endif extern volatile uint32_t lpc_tick; diff --git a/include/pico_config.h b/include/pico_config.h index bcd2c6b04..0f2ff959e 100644 --- a/include/pico_config.h +++ b/include/pico_config.h @@ -191,9 +191,11 @@ static inline uint64_t long_long_be(uint64_t le) #elif defined STELLARIS # include "arch/pico_stellaris.h" #elif defined LPC -# include "arch/pico_lpc1768.h" +# include "arch/pico_lpc17xx.h" #elif defined LPC43XX # include "arch/pico_lpc43xx.h" +#elif defined LPC17XX +# include "arch/pico_lpc17xx.h" #elif defined LPC18XX # include "arch/pico_lpc18xx.h" #elif defined PIC24 From b686ead92bb6be85f374c7a95b039ac312d8044a Mon Sep 17 00:00:00 2001 From: Maxime Vincent Date: Thu, 12 Feb 2015 12:48:43 +0100 Subject: [PATCH 09/61] proper lpc17xx support --- Makefile | 6 ++++ .../arch/{pico_lpc1768.h => pico_lpc17xx.h} | 0 include/arch/pico_lpc18xx.h | 31 +++++++++++-------- include/pico_config.h | 4 ++- 4 files changed, 27 insertions(+), 14 deletions(-) rename include/arch/{pico_lpc1768.h => pico_lpc17xx.h} (100%) diff --git a/Makefile b/Makefile index a10209d01..4351976e7 100644 --- a/Makefile +++ b/Makefile @@ -159,6 +159,12 @@ ifeq ($(ARCH),lpc18xx) -mcpu=cortex-m3 -mthumb -MMD -MP -DLPC18XX endif +ifeq ($(ARCH),lpc17xx) + CFLAGS+=-fmessage-length=0 -fno-builtin \ + -ffunction-sections -fdata-sections -mlittle-endian \ + -mcpu=cortex-m3 -mthumb -MMD -MP -DLPC17XX +endif + ifeq ($(ARCH),lpc43xx) CFLAGS+=-fmessage-length=0 -fno-builtin \ -ffunction-sections -fdata-sections -mlittle-endian \ diff --git a/include/arch/pico_lpc1768.h b/include/arch/pico_lpc17xx.h similarity index 100% rename from include/arch/pico_lpc1768.h rename to include/arch/pico_lpc17xx.h diff --git a/include/arch/pico_lpc18xx.h b/include/arch/pico_lpc18xx.h index c038c956d..849a01c2d 100644 --- a/include/arch/pico_lpc18xx.h +++ b/include/arch/pico_lpc18xx.h @@ -13,8 +13,7 @@ #define dbg(...) - -extern volatile uint32_t tassTick; +extern volatile unsigned int tassTick; #ifdef PICO_SUPPORT_RTOS #define PICO_SUPPORT_MUTEX @@ -77,19 +76,25 @@ static inline void pico_free(void *x) return pico_ffree(x); } +#else +//#define MEM_MEAS +#ifdef MEM_MEAS + extern void * memmeas_zalloc(size_t size); + extern void memmeas_free(void *); + #define pico_free(x) memmeas_free(x) + #define pico_zalloc(x) memmeas_zalloc(x) #else #define pico_free(x) free(x) - -static inline void *pico_zalloc(size_t size) -{ - void *ptr = malloc(size); - - if(ptr) - memset(ptr, 0u, size); - - return ptr; -} - + static inline void *pico_zalloc(size_t size) + { + void *ptr = malloc(size); + + if(ptr) + memset(ptr, 0u, size); + + return ptr; + } +#endif #endif extern volatile uint32_t lpc_tick; diff --git a/include/pico_config.h b/include/pico_config.h index bcd2c6b04..0f2ff959e 100644 --- a/include/pico_config.h +++ b/include/pico_config.h @@ -191,9 +191,11 @@ static inline uint64_t long_long_be(uint64_t le) #elif defined STELLARIS # include "arch/pico_stellaris.h" #elif defined LPC -# include "arch/pico_lpc1768.h" +# include "arch/pico_lpc17xx.h" #elif defined LPC43XX # include "arch/pico_lpc43xx.h" +#elif defined LPC17XX +# include "arch/pico_lpc17xx.h" #elif defined LPC18XX # include "arch/pico_lpc18xx.h" #elif defined PIC24 From 8057828008215063b1bc9285912d78642595b191 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 16 Feb 2015 10:39:15 +0100 Subject: [PATCH 10/61] Added initial packet structure for AODV (#219) --- modules/pico_aodv.h | 70 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 modules/pico_aodv.h diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h new file mode 100644 index 000000000..7c61f4905 --- /dev/null +++ b/modules/pico_aodv.h @@ -0,0 +1,70 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Author: Daniele Lacamera + *********************************************************************/ +#ifndef PICO_AODV_H_ +#define PICO_AODV_H_ +#include + +#define AODV_TYPE_RREQ 1 +#define AODV_TYPE_RREP 2 +#define AODV_TYPE_RERR 3 +#define AODV_TYPE_RACK 4 + +struct __attribute__((packed)) pico_aodv_rreq +{ + uint8_t type; + uint16_t req_flags; + uint8_t hop_count; + uint32_t rreq_id; + uint32_t dest; + uint32_t dseq; + uint32_t orig; + uint32_t oseq; +}; + +#define AODV_RREQ_FLAG_J 0x8000 +#define AODV_RREQ_FLAG_R 0x4000 +#define AODV_RREQ_FLAG_G 0x2000 +#define AODV_RREQ_FLAG_D 0x1000 +#define AODV_RREQ_FLAG_U 0x0800 +#define AODV_RREQ_FLAG_RESERVED 0x07FF + + + +struct __attribute__((packed)) pico_aodv_rrep +{ + uint8_t type; + uint8_t rep_flags; + uint8_t prefix_sz; + uint8_t hop_count; + uint32_t dest; + uint32_t dseq; + uint32_t orig; + uint32_t lifetime; +}; + +#define AODV_RREP_MAX_PREFIX 0x1F +#define AODV_RREP_FLAG_R 0x80 +#define AODV_RREP_FLAG_A 0x40 +#define AODV_RREQ_FLAG_RESERVED 0x3F + +struct __attribute__((packed)) pico_aodv_node +{ + uint32_t dest; + uint32_t dseq; +}; + +struct __attribute__((packed)) pico_aodv_rerr +{ + uint8_t type; + uint16_t rerr_flags; + uint8_t dst_count; + struct pico_aodv_node unreach[1]; /* unrechable nodes: must be at least 1. See dst_count field above */ +}; + +#endif From 9ffe209e87d9c429a0c6fbe4b9c5c8a794283211 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 16 Feb 2015 10:41:00 +0100 Subject: [PATCH 11/61] Generic definition for packed structure (#219) --- modules/pico_aodv.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index 7c61f4905..61be49007 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -15,7 +15,7 @@ #define AODV_TYPE_RERR 3 #define AODV_TYPE_RACK 4 -struct __attribute__((packed)) pico_aodv_rreq +PACKED_STRUCT_DEF pico_aodv_rreq { uint8_t type; uint16_t req_flags; @@ -34,9 +34,7 @@ struct __attribute__((packed)) pico_aodv_rreq #define AODV_RREQ_FLAG_U 0x0800 #define AODV_RREQ_FLAG_RESERVED 0x07FF - - -struct __attribute__((packed)) pico_aodv_rrep +PACKED_STRUCT_DEF pico_aodv_rrep { uint8_t type; uint8_t rep_flags; @@ -53,13 +51,13 @@ struct __attribute__((packed)) pico_aodv_rrep #define AODV_RREP_FLAG_A 0x40 #define AODV_RREQ_FLAG_RESERVED 0x3F -struct __attribute__((packed)) pico_aodv_node +PACKED_STRUCT_DEF pico_aodv_node { uint32_t dest; uint32_t dseq; }; -struct __attribute__((packed)) pico_aodv_rerr +PACKED_STRUCT_DEF pico_aodv_rerr { uint8_t type; uint16_t rerr_flags; From 62d1f4b1db4e9e03d04c070a7af893d72ea812f0 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 16 Feb 2015 17:32:29 +0100 Subject: [PATCH 12/61] AODV: initial code structure for the module (#219) --- Makefile | 6 ++++ include/pico_stack.h | 1 + modules/pico_aodv.h | 6 ++-- modules/pico_tcp.c | 64 +++++++++++++---------------------------- stack/pico_stack.c | 25 ++++++++++++++++ test/unit/modunit_seq.c | 30 +++++++++---------- 6 files changed, 71 insertions(+), 61 deletions(-) diff --git a/Makefile b/Makefile index 4351976e7..5ea506387 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ CRC?=1 OLSR?=0 SLAACV4?=1 TFTP?=1 +AODV?=1 MEMORY_MANAGER?=0 MEMORY_MANAGER_PROFILING?=0 TUN?=0 @@ -73,6 +74,11 @@ ifeq ($(TFTP),1) OPTIONS+=-DPICO_SUPPORT_TFTP endif +ifeq ($(AODV),1) + MOD_OBJ+=$(LIBBASE)modules/pico_aodv.o + OPTIONS+=-DPICO_SUPPORT_AODV +endif + ifneq ($(ENDIAN),little) CFLAGS+=-DPICO_BIGENDIAN diff --git a/include/pico_stack.h b/include/pico_stack.h index 8e1c696a9..36e8b318c 100644 --- a/include/pico_stack.h +++ b/include/pico_stack.h @@ -80,5 +80,6 @@ uint32_t pico_rand(void); void pico_rand_feed(uint32_t feed); void pico_to_lowercase(char *str); int pico_address_compare(union pico_address *a, union pico_address *b, uint16_t proto); +int32_t pico_seq_compare(uint32_t a, uint32_t b); #endif diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index 61be49007..5af886efc 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -10,6 +10,8 @@ #define PICO_AODV_H_ #include +#define PICO_AODV_PORT (654) + #define AODV_TYPE_RREQ 1 #define AODV_TYPE_RREP 2 #define AODV_TYPE_RERR 3 @@ -49,11 +51,11 @@ PACKED_STRUCT_DEF pico_aodv_rrep #define AODV_RREP_MAX_PREFIX 0x1F #define AODV_RREP_FLAG_R 0x80 #define AODV_RREP_FLAG_A 0x40 -#define AODV_RREQ_FLAG_RESERVED 0x3F +#define AODV_RREP_FLAG_RESERVED 0x3F PACKED_STRUCT_DEF pico_aodv_node { - uint32_t dest; + union pico_address dest; uint32_t dseq; }; diff --git a/modules/pico_tcp.c b/modules/pico_tcp.c index 6af530255..4cf6142ac 100644 --- a/modules/pico_tcp.c +++ b/modules/pico_tcp.c @@ -66,30 +66,6 @@ static void *Mutex = NULL; #endif -static /* inline*/ int32_t seq_compare(uint32_t a, uint32_t b) -{ - uint32_t thresh = ((uint32_t)(-1)) >> 1; - - if (a > b) /* return positive number, if not wrapped */ - { - if ((a - b) > thresh) /* b wrapped */ - return -(int32_t)(b - a); /* b = very small, a = very big */ - else - return (int32_t)(a - b); /* a = biggest, b = a bit smaller */ - - } - - if (a < b) /* return negative number, if not wrapped */ - { - if ((b - a) > thresh) /* a wrapped */ - return (int32_t)(a - b); /* a = very small, b = very big */ - else - return -(int32_t)(b - a); /* b = biggest, a = a bit smaller */ - - } - - return 0; -} /* Input segment, used to keep only needed data, not the full frame */ struct tcp_input_segment @@ -104,7 +80,7 @@ struct tcp_input_segment static int input_segment_compare(void *ka, void *kb) { struct tcp_input_segment *a = ka, *b = kb; - return seq_compare(a->seq, b->seq); + return pico_seq_compare(a->seq, b->seq); } static struct tcp_input_segment *segment_from_frame(struct pico_frame *f) @@ -129,7 +105,7 @@ static struct tcp_input_segment *segment_from_frame(struct pico_frame *f) static int segment_compare(void *ka, void *kb) { struct pico_frame *a = ka, *b = kb; - return seq_compare(SEQN(a), SEQN(b)); + return pico_seq_compare(SEQN(a), SEQN(b)); } struct pico_tcp_queue @@ -362,9 +338,9 @@ static int release_until(struct pico_tcp_queue *q, uint32_t seq) void *cur = head; if (IS_INPUT_QUEUE(q)) - seq_result = seq_compare(((struct tcp_input_segment *)head)->seq + ((struct tcp_input_segment *)head)->payload_len, seq); + seq_result = pico_seq_compare(((struct tcp_input_segment *)head)->seq + ((struct tcp_input_segment *)head)->payload_len, seq); else - seq_result = seq_compare(SEQN((struct pico_frame *)head) + ((struct pico_frame *)head)->payload_len, seq); + seq_result = pico_seq_compare(SEQN((struct pico_frame *)head) + ((struct pico_frame *)head)->payload_len, seq); if (seq_result <= 0) { @@ -393,9 +369,9 @@ static int release_all_until(struct pico_tcp_queue *q, uint32_t seq, pico_time * f = idx->keyValue; if (IS_INPUT_QUEUE(q)) - seq_result = seq_compare(((struct tcp_input_segment *)f)->seq + ((struct tcp_input_segment *)f)->payload_len, seq); + seq_result = pico_seq_compare(((struct tcp_input_segment *)f)->seq + ((struct tcp_input_segment *)f)->payload_len, seq); else - seq_result = seq_compare(SEQN((struct pico_frame *)f) + ((struct pico_frame *)f)->payload_len, seq); + seq_result = pico_seq_compare(SEQN((struct pico_frame *)f) + ((struct pico_frame *)f)->payload_len, seq); if (seq_result <= 0) { tcp_dbg("Releasing %p\n", f); @@ -517,7 +493,7 @@ static int pico_tcp_process_out(struct pico_protocol *self, struct pico_frame *f } if (f->payload_len > 0) { - if (seq_compare(SEQN(f) + f->payload_len, t->snd_nxt) > 0) { + if (pico_seq_compare(SEQN(f) + f->payload_len, t->snd_nxt) > 0) { t->snd_nxt = SEQN(f) + f->payload_len; tcp_dbg("%s: snd_nxt is now %08x\n", __FUNCTION__, t->snd_nxt); } @@ -737,12 +713,12 @@ uint16_t pico_tcp_overhead(struct pico_socket *s) static inline int tcp_sack_marker(struct pico_frame *f, uint32_t start, uint32_t end, uint16_t *count) { int cmp; - cmp = seq_compare(SEQN(f), start); + cmp = pico_seq_compare(SEQN(f), start); if (cmp > 0) return 0; if (cmp == 0) { - cmp = seq_compare(SEQN(f) + f->payload_len, end); + cmp = pico_seq_compare(SEQN(f) + f->payload_len, end); if (cmp > 0) { tcp_dbg("Invalid SACK: ignoring.\n"); } @@ -913,7 +889,7 @@ static inline void tcp_send_add_tcpflags(struct pico_socket_tcp *ts, struct pico { struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) f->transport_hdr; if (ts->rcv_nxt != 0) { - if ((ts->rcv_ackd == 0) || (seq_compare(ts->rcv_ackd, ts->rcv_nxt) != 0) || (hdr->flags & PICO_TCP_ACK)) { + if ((ts->rcv_ackd == 0) || (pico_seq_compare(ts->rcv_ackd, ts->rcv_nxt) != 0) || (hdr->flags & PICO_TCP_ACK)) { hdr->flags |= PICO_TCP_ACK; hdr->ack = long_be(ts->rcv_nxt); ts->rcv_ackd = ts->rcv_nxt; @@ -1084,7 +1060,7 @@ uint32_t pico_tcp_read(struct pico_socket *s, void *buf, uint32_t len) if (!f) return tcp_read_finish(s, tot_rd_len); - in_frame_off = seq_compare(t->rcv_processed, f->seq); + in_frame_off = pico_seq_compare(t->rcv_processed, f->seq); /* Check for hole at the beginning of data, awaiting retransmissions. */ if (in_frame_off < 0) { tcp_dbg("TCP> read hole beginning of data, %08x - %08x. rcv_nxt is %08x\n", t->rcv_processed, f->seq, t->rcv_nxt); @@ -1559,7 +1535,7 @@ static void tcp_sack_prepare(struct pico_socket_tcp *t) static inline int tcp_data_in_expected(struct pico_socket_tcp *t, struct pico_frame *f) { struct tcp_input_segment *nxt; - if (seq_compare(SEQN(f), t->rcv_nxt) == 0) { /* Exactly what we expected */ + if (pico_seq_compare(SEQN(f), t->rcv_nxt) == 0) { /* Exactly what we expected */ /* Create new segment and enqueue it */ struct tcp_input_segment *input = segment_from_frame(f); if (!input) { @@ -1638,7 +1614,7 @@ static int tcp_data_in(struct pico_socket *s, struct pico_frame *f) f->payload_len = payload_len; tcp_dbg("TCP> Received segment. (exp: %x got: %x)\n", t->rcv_nxt, SEQN(f)); - if (seq_compare(SEQN(f), t->rcv_nxt) <= 0) { + if (pico_seq_compare(SEQN(f), t->rcv_nxt) <= 0) { ret = tcp_data_in_expected(t, f); } else { ret = tcp_data_in_high_segment(t, f); @@ -2079,10 +2055,10 @@ static int tcp_ack(struct pico_socket *s, struct pico_frame *f) tcp_dbg("Skipping %08x because it is sacked.\n", SEQN(nxt)); nxt = next_segment(&t->tcpq_out, nxt); } - if (nxt && (seq_compare(SEQN(nxt), t->snd_nxt)) > 0) + if (nxt && (pico_seq_compare(SEQN(nxt), t->snd_nxt)) > 0) nxt = NULL; - if (nxt && (seq_compare(SEQN(nxt), SEQN((struct pico_frame *)first_segment(&t->tcpq_out))) > (int)(t->recv_wnd << t->recv_wnd_scale))) + if (nxt && (pico_seq_compare(SEQN(nxt), SEQN((struct pico_frame *)first_segment(&t->tcpq_out))) > (int)(t->recv_wnd << t->recv_wnd_scale))) nxt = NULL; if(!nxt) @@ -2110,7 +2086,7 @@ static int tcp_ack(struct pico_socket *s, struct pico_frame *f) /* Linux very special zero-window probe detection (see bug #107) */ if ((0 == (hdr->flags & (PICO_TCP_PSH | PICO_TCP_SYN))) && /* This is a pure ack, and... */ (ACKN(f) == t->snd_nxt) && /* it's acking our snd_nxt, and... */ - (seq_compare(SEQN(f), t->rcv_nxt) < 0)) /* Has an old seq number */ + (pico_seq_compare(SEQN(f), t->rcv_nxt) < 0)) /* Has an old seq number */ { tcp_send_ack(t); } @@ -2416,10 +2392,10 @@ static void tcp_attempt_closewait(struct pico_socket *s, struct pico_frame *f) { struct pico_socket_tcp *t = (struct pico_socket_tcp *)s; struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (f->transport_hdr); - if (seq_compare(SEQN(f), t->rcv_nxt) == 0) { + if (pico_seq_compare(SEQN(f), t->rcv_nxt) == 0) { /* received FIN, increase ACK nr */ t->rcv_nxt = long_be(hdr->seq) + 1; - if (seq_compare(SEQN(f), t->rcv_processed) == 0) { + if (pico_seq_compare(SEQN(f), t->rcv_processed) == 0) { if ((s->state & PICO_SOCKET_STATE_TCP) == PICO_SOCKET_STATE_TCP_ESTABLISHED) { tcp_dbg("Changing state to CLOSE_WAIT\n"); s->state &= 0x00FFU; @@ -2580,7 +2556,7 @@ static int tcp_closeconn(struct pico_socket *s, struct pico_frame *fr) struct pico_socket_tcp *t = (struct pico_socket_tcp *) s; struct pico_tcp_hdr *hdr = (struct pico_tcp_hdr *) (fr->transport_hdr); - if (seq_compare(SEQN(fr), t->rcv_nxt) == 0) { + if (pico_seq_compare(SEQN(fr), t->rcv_nxt) == 0) { /* received FIN, increase ACK nr */ t->rcv_nxt = long_be(hdr->seq) + 1; s->state &= 0x00FFU; @@ -2803,7 +2779,7 @@ int pico_tcp_output(struct pico_socket *s, int loop_score) f->timestamp = TCP_TIME; add_retransmission_timer(t, t->rto + TCP_TIME); tcp_add_options_frame(t, f); - seq_diff = seq_compare(SEQN(f), SEQN(una)); + seq_diff = pico_seq_compare(SEQN(f), SEQN(una)); if (seq_diff < 0) { dbg(">>> FATAL: seq diff is negative!\n"); break; diff --git a/stack/pico_stack.c b/stack/pico_stack.c index 8603d31a2..597c844b9 100644 --- a/stack/pico_stack.c +++ b/stack/pico_stack.c @@ -766,6 +766,31 @@ DECLARE_HEAP(pico_timer_ref, expire); static heap_pico_timer_ref *Timers; +int32_t pico_seq_compare(uint32_t a, uint32_t b) +{ + uint32_t thresh = ((uint32_t)(-1)) >> 1; + + if (a > b) /* return positive number, if not wrapped */ + { + if ((a - b) > thresh) /* b wrapped */ + return -(int32_t)(b - a); /* b = very small, a = very big */ + else + return (int32_t)(a - b); /* a = biggest, b = a bit smaller */ + + } + + if (a < b) /* return negative number, if not wrapped */ + { + if ((b - a) > thresh) /* a wrapped */ + return (int32_t)(a - b); /* a = very small, b = very big */ + else + return -(int32_t)(b - a); /* b = biggest, a = a bit smaller */ + + } + + return 0; +} + void pico_check_timers(void) { struct pico_timer *t; diff --git a/test/unit/modunit_seq.c b/test/unit/modunit_seq.c index 73b9fb4e4..25264e86d 100644 --- a/test/unit/modunit_seq.c +++ b/test/unit/modunit_seq.c @@ -10,26 +10,26 @@ START_TEST(tc_seq_compare) uint32_t over_thresh = 0x80000000lu; uint32_t zero = 0lu; - fail_if(seq_compare(small_a, small_b) >= 0); - fail_if(seq_compare(small_b, small_a) <= 0); + fail_if(pico_seq_compare(small_a, small_b) >= 0); + fail_if(pico_seq_compare(small_b, small_a) <= 0); - fail_if(seq_compare(over_thresh, under_thresh) <= 0); - fail_if(seq_compare(under_thresh, over_thresh) >= 0); + fail_if(pico_seq_compare(over_thresh, under_thresh) <= 0); + fail_if(pico_seq_compare(under_thresh, over_thresh) >= 0); - fail_if(seq_compare(small_a, big_b) <= 0); - fail_if(seq_compare(big_b, small_a) >= 0); + fail_if(pico_seq_compare(small_a, big_b) <= 0); + fail_if(pico_seq_compare(big_b, small_a) >= 0); - fail_if(seq_compare(small_a, zero) <= 0); - fail_if(seq_compare(zero, small_a) >= 0); + fail_if(pico_seq_compare(small_a, zero) <= 0); + fail_if(pico_seq_compare(zero, small_a) >= 0); - fail_if(seq_compare(big_a, zero) >= 0); - fail_if(seq_compare(zero, big_a) <= 0); + fail_if(pico_seq_compare(big_a, zero) >= 0); + fail_if(pico_seq_compare(zero, big_a) <= 0); - fail_if(seq_compare(big_a, big_b) >= 0); - fail_if(seq_compare(big_b, big_a) <= 0); + fail_if(pico_seq_compare(big_a, big_b) >= 0); + fail_if(pico_seq_compare(big_b, big_a) <= 0); - fail_if(seq_compare(big_a, big_a) != 0); - fail_if(seq_compare(zero, zero) != 0); + fail_if(pico_seq_compare(big_a, big_a) != 0); + fail_if(pico_seq_compare(zero, zero) != 0); } END_TEST @@ -37,7 +37,7 @@ END_TEST Suite *pico_suite(void) { Suite *s = suite_create("pico tcp sequence numbers"); - TCase *TCase_seq_compare = tcase_create("Unit test for seq_compare"); + TCase *TCase_seq_compare = tcase_create("Unit test for pico_seq_compare"); tcase_add_test(TCase_seq_compare, tc_seq_compare); suite_add_tcase(s, TCase_seq_compare); return s; From 07dfbab86338c5e66266e4f15896835f59abac8e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 17 Feb 2015 15:02:43 +0100 Subject: [PATCH 13/61] AODV: Sending requests upon lookup invocation (#219) --- modules/pico_aodv.h | 44 ++++++++++++++++++++++++++++++++++++++++++-- stack/pico_stack.c | 4 ++++ test/picoapp.c | 20 ++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index 5af886efc..2aea44994 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -10,8 +10,35 @@ #define PICO_AODV_H_ #include +/* RFC3561 */ #define PICO_AODV_PORT (654) +/* RFC3561 $10 */ +#define AODV_TTL_VALUE(f) (((struct pico_ipv4_hdr *)f->net_hdr)->ttl) +#define AODV_ACTIVE_ROUTE_TIMEOUT 5000 /* Conservative value for link breakage detection */ +#define AODV_DELETE_PERIOD (5 * AODV_ACTIVE_ROUTE_TIMEOUT) /* Recommended value K = 5 */ +#define AODV_ALLOWED_HELLO_LOSS (4) /* conservative */ +#define AODV_NET_DIAMETER (35) +#define AODV_RREQ_RETRIES (2) +#define AODV_NODE_TRAVERSAL_TIME (40) +#define AODV_HELLO_INTERVAL (1) +#define AODV_LOCAL_ADD_TTL (2) +#define AODV_RREQ_RATELIMIT (10) +#define AODV_TIMEOUT_BUFFER (2) +#define AODV_TTL_START (1) +#define AODV_TTL_INCREMENT (2) +#define AODV_TTL_THRESHOLD (7) +#define AODV_RERR_RATELIMIT (10) +#define AODV_MAX_REPAIR_TTL (AODV_NET_DIAMETER / 3) +#define AODV_MY_ROUTE_TIMEOUT (2 * AODV_ACTIVE_ROUTE_TIMEOUT) +#define AODV_NET_TRAVERSAL_TIME (2 * AODV_NODE_TRAVERSAL_TIME * AODV_NET_DIAMETER) +#define AODV_BLACKLIST_TIMEOUT (AODV_RREQ_RETRIES * AODV_NET_TRAVERSAL_TIME) +#define AODV_NEXT_HOP_WAIT (AODV_NODE_TRAVERSAL_TIME + 10) +#define AODV_PATH_DISCOVERY_TIME (2 * AODV_NET_TRAVERSAL_TIME) +#define AODV_RING_TRAVERSAL_TIME(f) (2 * AODV_NODE_TRAVERSAL_TIME * (AODV_TTL_VALUE(f) + AODV_TIMEOUT_BUFFER)) +/* End section RFC3561 $10 */ + + #define AODV_TYPE_RREQ 1 #define AODV_TYPE_RREP 2 #define AODV_TYPE_RERR 3 @@ -53,10 +80,18 @@ PACKED_STRUCT_DEF pico_aodv_rrep #define AODV_RREP_FLAG_A 0x40 #define AODV_RREP_FLAG_RESERVED 0x3F -PACKED_STRUCT_DEF pico_aodv_node +struct pico_aodv_node { union pico_address dest; uint32_t dseq; + int valid_dseq; + pico_time last_seen; +}; + +PACKED_STRUCT_DEF pico_aodv_unreachable +{ + uint32_t addr; + uint32_t dseq; }; PACKED_STRUCT_DEF pico_aodv_rerr @@ -64,7 +99,12 @@ PACKED_STRUCT_DEF pico_aodv_rerr uint8_t type; uint16_t rerr_flags; uint8_t dst_count; - struct pico_aodv_node unreach[1]; /* unrechable nodes: must be at least 1. See dst_count field above */ + uint32_t unreach_addr; + uint32_t unreach_dseq; + struct pico_aodv_unreachable unreach[1]; /* unrechable nodes: must be at least 1. See dst_count field above */ }; +int pico_aodv_init(void); +int pico_aodv_add(struct pico_device *dev); +int pico_aodv_lookup(const union pico_address *addr); #endif diff --git a/stack/pico_stack.c b/stack/pico_stack.c index 597c844b9..11766d111 100644 --- a/stack/pico_stack.c +++ b/stack/pico_stack.c @@ -17,6 +17,7 @@ #include "pico_dns_client.h" #include "pico_olsr.h" +#include "pico_aodv.h" #include "pico_eth.h" #include "pico_arp.h" #include "pico_ipv4.h" @@ -1066,6 +1067,9 @@ int pico_stack_init(void) #ifdef PICO_SUPPORT_OLSR pico_olsr_init(); #endif +#ifdef PICO_SUPPORT_AODV + pico_aodv_init(); +#endif pico_stack_tick(); pico_stack_tick(); diff --git a/test/picoapp.c b/test/picoapp.c index cf675fdc6..e4f09f6ff 100644 --- a/test/picoapp.c +++ b/test/picoapp.c @@ -28,6 +28,7 @@ #include "pico_dhcp_server.h" #include "pico_ipfilter.h" #include "pico_olsr.h" +#include "pico_aodv.h" #include "pico_sntp_client.h" #include "pico_mdns.h" #include "pico_tftp.h" @@ -604,6 +605,25 @@ int main(int argc, char **argv) app_noop(); #endif +#ifdef PICO_SUPPORT_AODV + } else IF_APPNAME("aodv") { + union pico_address aaa; + pico_string_to_ipv4("10.10.10.10", &aaa.ip4.addr); + dev = pico_get_device("pic0"); + if(dev) { + pico_aodv_add(dev); + } + + dev = pico_get_device("pic1"); + if(dev) { + pico_aodv_add(dev); + } + + /* TEST */ + pico_aodv_lookup(&aaa); + + app_noop(); +#endif } else IF_APPNAME("slaacv4") { #ifndef PICO_SUPPORT_SLAACV4 return 0; From 3a4cdf00047bfa4ad4deb847fcd690b120629eda Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 18 Feb 2015 11:03:18 +0100 Subject: [PATCH 14/61] Added extended information for sendto/recvfrom --- include/pico_frame.h | 3 +++ include/pico_socket.h | 10 +++++++++ modules/pico_aodv.h | 8 +++++++ modules/pico_ipv4.c | 6 +++++ modules/pico_ipv6.c | 8 +++++++ modules/pico_socket_udp.h | 2 +- modules/pico_udp.c | 26 +++++++++++++++++++++- modules/pico_udp.h | 2 +- stack/pico_socket.c | 46 +++++++++++++++++++++++++++++---------- 9 files changed, 96 insertions(+), 15 deletions(-) diff --git a/include/pico_frame.h b/include/pico_frame.h index 87833719f..22a40026c 100644 --- a/include/pico_frame.h +++ b/include/pico_frame.h @@ -80,6 +80,9 @@ struct pico_frame { /* Callback to notify listener when the buffer has been discarded */ void (*notify_free)(uint8_t *); + + uint8_t send_ttl; /* Special TTL/HOPS value, 0 = auto assign */ + uint8_t send_tos; /* Type of service */ }; /** frame alloc/dealloc/copy **/ diff --git a/include/pico_socket.h b/include/pico_socket.h index e96b37aab..4d27daf2b 100644 --- a/include/pico_socket.h +++ b/include/pico_socket.h @@ -178,6 +178,11 @@ struct pico_ipv6_mreq_source { #define PICO_SOCK_EV_FIN 0x10u #define PICO_SOCK_EV_ERR 0x80u +struct pico_msginfo { + struct pico_device *dev; + uint8_t ttl; + uint8_t tos; +}; struct pico_socket *pico_socket_open(uint16_t net, uint16_t proto, void (*wakeup)(uint16_t ev, struct pico_socket *s)); @@ -185,7 +190,12 @@ int pico_socket_read(struct pico_socket *s, void *buf, int len); int pico_socket_write(struct pico_socket *s, const void *buf, int len); int pico_socket_sendto(struct pico_socket *s, const void *buf, int len, void *dst, uint16_t remote_port); +int pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len, + void *dst, uint16_t remote_port, struct pico_msginfo *msginfo); + int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, uint16_t *local_port); +int pico_socket_recvfrom_extended(struct pico_socket *s, void *buf, int len, void *orig, + uint16_t *remote_port, struct pico_msginfo *msginfo); int pico_socket_send(struct pico_socket *s, const void *buf, int len); int pico_socket_recv(struct pico_socket *s, void *buf, int len); diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index 2aea44994..fd186603e 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -84,7 +84,9 @@ struct pico_aodv_node { union pico_address dest; uint32_t dseq; + uint16_t metric; int valid_dseq; + int active; pico_time last_seen; }; @@ -104,6 +106,12 @@ PACKED_STRUCT_DEF pico_aodv_rerr struct pico_aodv_unreachable unreach[1]; /* unrechable nodes: must be at least 1. See dst_count field above */ }; +PACKED_STRUCT_DEF pico_aodv_rack +{ + uint8_t type; + uint8_t reserved; +}; + int pico_aodv_init(void); int pico_aodv_add(struct pico_device *dev); int pico_aodv_lookup(const union pico_address *addr); diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index d00544b0b..65d26423d 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -1205,6 +1205,7 @@ int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t pro return -1; } + hdr = (struct pico_ipv4_hdr *) f->net_hdr; if (!hdr) { dbg("IP header error\n"); @@ -1269,10 +1270,15 @@ int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t pro 1 ) ipv4_progressive_id++; + if (f->send_ttl > 0) { + ttl = f->send_ttl; + } + hdr->id = short_be(ipv4_progressive_id); hdr->dst.addr = dst->addr; hdr->src.addr = link->address.addr; hdr->ttl = ttl; + hdr->tos = f->send_tos; hdr->proto = proto; hdr->frag = short_be(PICO_IPV4_DONTFRAG); #ifdef PICO_SUPPORT_IPFRAG diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index e5fd1e7dc..534e1f596 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -812,6 +812,14 @@ static inline void ipv6_push_hdr_adjust(struct pico_frame *f, struct pico_ipv6_l hdr->src = link->address; hdr->dst = *dst; + if (f->send_ttl) { + hdr->hop = f->send_ttl; + } + + if (f->send_tos) { + hdr->vtf |= ((uint32_t)f->send_tos << 20u); + } + /* make adjustments to defaults according to proto */ switch (proto) { diff --git a/modules/pico_socket_udp.h b/modules/pico_socket_udp.h index fd7783a92..6b3a4c9a3 100644 --- a/modules/pico_socket_udp.h +++ b/modules/pico_socket_udp.h @@ -8,7 +8,7 @@ int pico_socket_udp_deliver(struct pico_sockport *sp, struct pico_frame *f); #ifdef PICO_SUPPORT_UDP int pico_setsockopt_udp(struct pico_socket *s, int option, void *value); int pico_getsockopt_udp(struct pico_socket *s, int option, void *value); -# define pico_socket_udp_recv(s, buf, len, addr, port) pico_udp_recv(s, buf, len, addr, port) +# define pico_socket_udp_recv(s, buf, len, addr, port) pico_udp_recv(s, buf, len, addr, port, NULL) #else # define pico_socket_udp_recv(...) (0) # define pico_getsockopt_udp(...) (-1) diff --git a/modules/pico_udp.c b/modules/pico_udp.c index cdf62c5a9..deef46397 100644 --- a/modules/pico_udp.c +++ b/modules/pico_udp.c @@ -154,7 +154,28 @@ struct pico_socket *pico_udp_open(void) return &u->sock; } -uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src, uint16_t *port) +static void pico_udp_get_msginfo(struct pico_frame *f, struct pico_msginfo *msginfo) +{ + msginfo->dev = f->dev; + if (!msginfo || !f->net_hdr) + return; + + if (IS_IPV4(f)) { /* IPV4 */ +#ifdef PICO_SUPPORT_IPV4 + struct pico_ipv4_hdr *hdr = (struct pico_ipv4_hdr *)(f->net_hdr); + msginfo->ttl = hdr->ttl; + msginfo->tos = hdr->tos; +#endif + } else { +#ifdef PICO_SUPPORT_IPV6 + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)(f->net_hdr); + msginfo->ttl = hdr->hop; + msginfo->tos = (hdr->vtf >> 20) & 0xFF; /* IPv6 traffic class */ +#endif + } +} + +uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src, uint16_t *port, struct pico_msginfo *msginfo) { struct pico_frame *f = pico_queue_peek(&s->q_in); if (f) { @@ -184,6 +205,9 @@ uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src pico_frame_discard(f); return ret; } + if (msginfo) { + pico_udp_get_msginfo(f, msginfo); + } } else return 0; } diff --git a/modules/pico_udp.h b/modules/pico_udp.h index f85b607d2..3b38b6fcf 100644 --- a/modules/pico_udp.h +++ b/modules/pico_udp.h @@ -32,7 +32,7 @@ PACKED_STRUCT_DEF pico_udp_hdr { #define PICO_UDPHDR_SIZE 8 struct pico_socket *pico_udp_open(void); -uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src, uint16_t *port); +uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src, uint16_t *port, struct pico_msginfo *msginfo); uint16_t pico_udp_checksum_ipv4(struct pico_frame *f); #ifdef PICO_SUPPORT_IPV6 diff --git a/stack/pico_socket.c b/stack/pico_socket.c index 25ccd1d81..5ef7e2d8c 100644 --- a/stack/pico_socket.c +++ b/stack/pico_socket.c @@ -1019,7 +1019,8 @@ static int pico_socket_final_xmit(struct pico_socket *s, struct pico_frame *f) } } -static int pico_socket_xmit_one(struct pico_socket *s, const void *buf, const int len, void *src, struct pico_remote_endpoint *ep) +static int pico_socket_xmit_one(struct pico_socket *s, const void *buf, const int len, void *src, + struct pico_remote_endpoint *ep, struct pico_msginfo *msginfo) { struct pico_frame *f; uint16_t hdr_offset = (uint16_t)pico_socket_sendto_transport_offset(s); @@ -1045,6 +1046,11 @@ static int pico_socket_xmit_one(struct pico_socket *s, const void *buf, const in } } + if (msginfo) { + f->send_ttl = (uint8_t)msginfo->ttl; + f->send_tos = (uint8_t)msginfo->tos; + } + memcpy(f->payload, (const uint8_t *)buf, f->payload_len); /* dbg("Pushing segment, hdr len: %d, payload_len: %d\n", header_offset, f->payload_len); */ ret = pico_socket_final_xmit(s, f); @@ -1081,7 +1087,8 @@ static void pico_socket_xmit_next_fragment_setup(struct pico_frame *f, int hdr_o } #endif -static int pico_socket_xmit_fragments(struct pico_socket *s, const void *buf, const int len, void *src, struct pico_remote_endpoint *ep) +static int pico_socket_xmit_fragments(struct pico_socket *s, const void *buf, const int len, + void *src, struct pico_remote_endpoint *ep, struct pico_msginfo *msginfo) { int space = pico_socket_xmit_avail_space(s); int hdr_offset = pico_socket_sendto_transport_offset(s); @@ -1089,13 +1096,13 @@ static int pico_socket_xmit_fragments(struct pico_socket *s, const void *buf, co struct pico_frame *f = NULL; if (space > len) { - return pico_socket_xmit_one(s, buf, len, src, ep); + return pico_socket_xmit_one(s, buf, len, src, ep, msginfo); } #ifdef PICO_SUPPORT_IPV6 /* Can't fragment IPv6 */ if (is_sock_ipv6(s)) { - return pico_socket_xmit_one(s, buf, space, src, ep); + return pico_socket_xmit_one(s, buf, space, src, ep, msginfo); } #endif @@ -1153,7 +1160,7 @@ static int pico_socket_xmit_fragments(struct pico_socket *s, const void *buf, co (void) f; (void) hdr_offset; (void) total_payload_written; - return pico_socket_xmit_one(s, buf, space, src, ep); + return pico_socket_xmit_one(s, buf, space, src, ep, msginfo); #endif } @@ -1218,7 +1225,8 @@ static int pico_socket_xmit_avail_space(struct pico_socket *s) } -static int pico_socket_xmit(struct pico_socket *s, const void *buf, const int len, void *src, struct pico_remote_endpoint *ep) +static int pico_socket_xmit(struct pico_socket *s, const void *buf, const int len, void *src, + struct pico_remote_endpoint *ep, struct pico_msginfo *msginfo) { int space = pico_socket_xmit_avail_space(s); int total_payload_written = 0; @@ -1229,7 +1237,7 @@ static int pico_socket_xmit(struct pico_socket *s, const void *buf, const int le } if ((PROTO(s) == PICO_PROTO_UDP) && (len > space)) { - return pico_socket_xmit_fragments(s, buf, len, src, ep); + return pico_socket_xmit_fragments(s, buf, len, src, ep, msginfo); } while (total_payload_written < len) { @@ -1237,7 +1245,7 @@ static int pico_socket_xmit(struct pico_socket *s, const void *buf, const int le if (chunk_len > space) chunk_len = space; - w = pico_socket_xmit_one(s, (const void *)((const uint8_t *)buf + total_payload_written), chunk_len, src, ep); + w = pico_socket_xmit_one(s, (const void *)((const uint8_t *)buf + total_payload_written), chunk_len, src, ep, msginfo); if (w <= 0) { break; } @@ -1260,7 +1268,8 @@ static void pico_socket_sendto_set_dport(struct pico_socket *s, uint16_t port) } -int MOCKABLE pico_socket_sendto(struct pico_socket *s, const void *buf, const int len, void *dst, uint16_t remote_port) +int pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len, + void *dst, uint16_t remote_port, struct pico_msginfo *msginfo) { struct pico_remote_endpoint *remote_endpoint = NULL; void *src = NULL; @@ -1283,7 +1292,12 @@ int MOCKABLE pico_socket_sendto(struct pico_socket *s, const void *buf, const in pico_socket_sendto_set_dport(s, remote_port); - return pico_socket_xmit(s, buf, len, src, remote_endpoint); + return pico_socket_xmit(s, buf, len, src, remote_endpoint, msginfo); +} + +int MOCKABLE pico_socket_sendto(struct pico_socket *s, const void *buf, const int len, void *dst, uint16_t remote_port) +{ + return pico_socket_sendto_extended(s, buf, len, dst, remote_port, NULL); } int pico_socket_send(struct pico_socket *s, const void *buf, int len) @@ -1308,7 +1322,8 @@ int pico_socket_send(struct pico_socket *s, const void *buf, int len) return pico_socket_sendto(s, buf, len, &s->remote_addr, s->remote_port); } -int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, uint16_t *remote_port) +int pico_socket_recvfrom_extended(struct pico_socket *s, void *buf, int len, void *orig, + uint16_t *remote_port, struct pico_msginfo *msginfo) { if (!s || buf == NULL) { /* / || orig == NULL || remote_port == NULL) { */ pico_err = PICO_ERR_EINVAL; @@ -1335,7 +1350,7 @@ int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, return -1; } - return pico_udp_recv(s, buf, (uint16_t)len, orig, remote_port); + return pico_udp_recv(s, buf, (uint16_t)len, orig, remote_port, msginfo); } #endif @@ -1356,6 +1371,13 @@ int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, return 0; } +int pico_socket_recvfrom(struct pico_socket *s, void *buf, int len, void *orig, + uint16_t *remote_port) +{ + return pico_socket_recvfrom_extended(s, buf, len, orig, remote_port, NULL); + +} + int pico_socket_recv(struct pico_socket *s, void *buf, int len) { return pico_socket_recvfrom(s, buf, len, NULL, NULL); From 3c0af8e7e4fa1e1e3d977179d8d585392b6d8f73 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 18 Feb 2015 11:03:50 +0100 Subject: [PATCH 15/61] Added initial aodv implementation (#219) --- modules/pico_aodv.c | 318 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 modules/pico_aodv.c diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c new file mode 100644 index 000000000..0d1f044f9 --- /dev/null +++ b/modules/pico_aodv.c @@ -0,0 +1,318 @@ +/********************************************************************* + PicoTCP. Copyright (c) 2015 Altran Intelligent Systems. Some rights reserved. + See LICENSE and COPYING for usage. + + . + + Author: Daniele Lacamera + *********************************************************************/ +#include +#include +#include +#include +#include + +#include +#define AODV_MAX_PKT (64) +static const struct pico_ip4 HOST_NETMASK = { + 0xffffffff +}; + +static const struct pico_ip4 ANY_HOST = { + 0x0 +}; + +static uint32_t pico_aodv_local_id = 0; + +static int aodv_node_compare(void *ka, void *kb) +{ + struct pico_aodv_node *a = ka, *b = kb; + if (a->dest.ip4.addr < b->dest.ip4.addr) + return -1; + if (b->dest.ip4.addr < a->dest.ip4.addr) + return 1; + return 0; +} + +static int aodv_dev_cmp(void *ka, void *kb) +{ + struct pico_device *a = ka, *b = kb; + if (a->hash < b->hash) + return -1; + + if (a->hash > b->hash) + return 1; + + return 0; +} + +PICO_TREE_DECLARE(aodv_nodes, aodv_node_compare); +PICO_TREE_DECLARE(aodv_devices, aodv_dev_cmp); + +static struct pico_socket *aodv_socket = NULL; + +static struct pico_aodv_node *get_node_by_addr(const union pico_address *addr) +{ + struct pico_aodv_node search; + memcpy(&search.dest, addr, sizeof(union pico_address)); + return pico_tree_findKey(&aodv_nodes, &search); + +} + +static void pico_aodv_set_dev(struct pico_device *dev) +{ + pico_ipv4_route_set_bcast_link(pico_ipv4_link_by_dev(dev)); +} + + +static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) +{ + if ((0 == node->valid_dseq) || (pico_seq_compare(seq, node->dseq) > 0)) { + node->dseq = seq; + node->last_seen = PICO_TIME_MS(); + return 0; + } + return -1; +} + +static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw, int metric, struct pico_device *dev) +{ + metric++; + if (metric < node->metric) { + pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); + if (!gw) { + pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(dev)); + node->metric = 1; + } else { + node->metric = (uint16_t)metric; + pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, gw->ip4, metric, NULL); + } + node->active = 1; + } +} + +static struct pico_aodv_node *aodv_peer_new(union pico_address *addr) +{ + struct pico_aodv_node *node = PICO_ZALLOC(sizeof(struct pico_aodv_node)); + if (!node) + return NULL; + memcpy(&node->dest, addr, sizeof(union pico_address)); + pico_tree_insert(&aodv_nodes, node); + return node; +} + + +static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq) +{ + struct pico_aodv_node *node = NULL; + node = get_node_by_addr(addr); + if (!node) { + node = aodv_peer_new(addr); + } + if (node && aodv_peer_refresh(node, long_be(seq)) == 0) + return node; + return NULL; +} + +/* Parser functions */ + +static void aodv_recv_valid_rreq(struct pico_aodv_node *node) +{ + struct pico_device *dev; + dev = pico_ipv4_link_find(&node->dest.ip4); + if (dev) { + /* Case 1: destination is ourselves. Send reply. */ + } else if (node->active) { + /* Case 2: we have a possible route. Send reply. */ + } else { + /* Case 3: destination unknown. Evaluate forwarding. */ + } +} + +static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + struct pico_aodv_rreq *req = (struct pico_aodv_rreq *) buf; + struct pico_aodv_node *node = NULL; + union pico_address orig; + if (len != sizeof(struct pico_aodv_rreq)) + return; + + orig.ip4.addr = req->orig; + node = aodv_peer_eval(&orig, req->oseq); + if (!node) + return; + aodv_elect_route(node, from, req->hop_count, msginfo->dev); + + aodv_recv_valid_rreq(node); +} + +static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *) buf; + struct pico_aodv_node *node = NULL; + union pico_address dest; + if (len != sizeof(struct pico_aodv_rrep)) + return; + + dest.ip4.addr = rep->dest; + node = aodv_peer_eval(&dest, rep->dseq); + if (!node) + return; + dest.ip4.addr = node->dest.ip4.addr; + aodv_elect_route(node, from, rep->hop_count, msginfo->dev); +} + +static void aodv_parse_rerr(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + if ((uint32_t)len < sizeof(struct pico_aodv_rerr) || + (((uint32_t)len - sizeof(struct pico_aodv_rerr)) % sizeof(struct pico_aodv_unreachable)) > 0) + return; + (void)from; + (void)buf; + (void)len; + (void)msginfo; + /* TODO: invalidate routes... */ +} + +static void aodv_parse_rack(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + if (len != sizeof(struct pico_aodv_rack)) + return; + (void)from; + (void)buf; + (void)len; + (void)msginfo; +} + +struct aodv_parser_s { + void (*call)(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo); +}; + +struct aodv_parser_s aodv_parser[5] = { + {.call = NULL}, + {.call = aodv_parse_rreq }, + {.call = aodv_parse_rrep }, + {.call = aodv_parse_rerr }, + {.call = aodv_parse_rack } +}; + + +static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) +{ + if ((buf[0] < 1) || (buf[0] > 4)) { + /* Type is invalid. Discard silently. */ + return; + } + aodv_parser[buf[0]].call(from, buf, len, msginfo); +} + +static void pico_aodv_socket_callback(uint16_t ev, struct pico_socket *s) +{ + static uint8_t aodv_pkt[AODV_MAX_PKT]; + static union pico_address from; + static struct pico_msginfo msginfo; + uint16_t sport; + int r; + if (s != aodv_socket) + return; + if (ev & PICO_SOCK_EV_RD) { + r = pico_socket_recvfrom_extended(s, aodv_pkt, AODV_MAX_PKT, &from, &sport, &msginfo); + if (r <= 0) + return; + dbg("Received AODV packet: %d bytes \n", r); + pico_aodv_parse(&from, aodv_pkt, r, &msginfo); + } +} + +static void aodv_make_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req) +{ + req->type = AODV_TYPE_RREQ; + req->req_flags |= short_be(AODV_RREQ_FLAG_G); /* RFC3561 $6.3: we SHOULD set G flag as originators */ + if (!node->valid_dseq) { + req->req_flags |= short_be(AODV_RREQ_FLAG_U); /* no known dseq, mark as U */ + req->dseq = 0; /* Unknown */ + } else { + req->dseq = long_be(node->dseq); + } + /* Hop count = 0; */ + req->rreq_id = long_be(pico_aodv_local_id); + req->dest = node->dest.ip4.addr; + req->oseq = long_be(pico_aodv_local_id); +} + +static int aodv_send_req(struct pico_aodv_node *node) +{ + struct pico_device *dev; + struct pico_tree_node *index; + struct pico_ip4 all_bcast = { .addr = 0xFFFFFFFFu }; + static struct pico_aodv_rreq rreq; + int n = 0; + struct pico_ipv4_link *ip4l = NULL; + + if (pico_tree_empty(&aodv_devices)) + return n; + + if (!aodv_socket) { + pico_err = PICO_ERR_EINVAL; + return -1; + } + + pico_aodv_local_id++; + aodv_make_rreq(node, &rreq); + pico_tree_foreach(index, &aodv_devices){ + dev = index->keyValue; + pico_aodv_set_dev(dev); + ip4l = pico_ipv4_link_by_dev(dev); + if (ip4l) { + rreq.orig = ip4l->address.addr; + pico_socket_sendto(aodv_socket, &rreq, sizeof(rreq), &all_bcast, short_be(PICO_AODV_PORT)); + n++; + } + } + return n; +} + +int pico_aodv_init(void) +{ + struct pico_ip4 any = { .addr = 0u}; + uint16_t port = short_be(PICO_AODV_PORT); + if (aodv_socket) { + pico_err = PICO_ERR_EADDRINUSE; + return -1; + } + aodv_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, pico_aodv_socket_callback); + if (!aodv_socket) + return -1; + + if (pico_socket_bind(aodv_socket, &any, &port) != 0) { + uint16_t err = pico_err; + pico_socket_close(aodv_socket); + pico_err = err; + aodv_socket = NULL; + return -1; + } + pico_aodv_local_id = pico_rand(); + return 0; +} + + +int pico_aodv_add(struct pico_device *dev) +{ + return (pico_tree_insert(&aodv_devices, dev))?(0):(-1); +} + +int pico_aodv_lookup(const union pico_address *addr) +{ + struct pico_aodv_node *node = get_node_by_addr(addr); + if (!node) { + node = PICO_ZALLOC(sizeof(struct pico_aodv_node)); + if (!node) + return -1; + memcpy(&node->dest, addr, sizeof(union pico_address)); + } + if (aodv_send_req(node) > 0) + return 0; + pico_err = PICO_ERR_EINVAL; + return -1; +} + From 0efeb3cac1e610047fa690c15f80c60866042cc9 Mon Sep 17 00:00:00 2001 From: Sam Van Den Berge Date: Thu, 19 Feb 2015 11:32:06 +0100 Subject: [PATCH 16/61] TCP socket should not be deleted when in state PICO_SOCKET_STATE_TCP_SYN_SENT and a RST,ACK is received. --- modules/pico_tcp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/pico_tcp.c b/modules/pico_tcp.c index 6af530255..d6d761650 100644 --- a/modules/pico_tcp.c +++ b/modules/pico_tcp.c @@ -2535,7 +2535,6 @@ static int tcp_rst(struct pico_socket *s, struct pico_frame *f) tcp_force_closed(s); pico_err = PICO_ERR_ECONNRESET; tcp_wakeup_pending(s, PICO_SOCK_EV_ERR); - pico_socket_del(&t->sock); /* delete socket */ } else { /* not valid, ignore */ tcp_dbg("TCP RST> IGNORE\n"); return 0; From f1f485cc259ea5b21b47e3cefc94cf9d74920ede Mon Sep 17 00:00:00 2001 From: Sam Van Den Berge Date: Thu, 19 Feb 2015 17:03:03 +0100 Subject: [PATCH 17/61] Fixed inconsistency between socket states. --- stack/pico_socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stack/pico_socket.c b/stack/pico_socket.c index 25ccd1d81..9992f6e79 100644 --- a/stack/pico_socket.c +++ b/stack/pico_socket.c @@ -1563,7 +1563,7 @@ int pico_socket_connect(struct pico_socket *s, const void *remote_addr, uint16_t #ifdef PICO_SUPPORT_TCP if (PROTO(s) == PICO_PROTO_TCP) { if (pico_tcp_initconn(s) == 0) { - pico_socket_alter_state(s, PICO_SOCKET_STATE_CONNECTED | PICO_SOCKET_STATE_TCP_SYN_SENT, 0, 0); + pico_socket_alter_state(s, PICO_SOCKET_STATE_CONNECTED | PICO_SOCKET_STATE_TCP_SYN_SENT, PICO_SOCKET_STATE_CLOSED, 0); pico_err = PICO_ERR_NOERR; ret = 0; } else { From d411b845474b750425f611995d1b23f91a8efb52 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2015 17:16:50 +0100 Subject: [PATCH 18/61] AODV: First 10-hop experiment successful. Partially done #219 --- modules/pico_aodv.c | 242 +++++++++++++++++++++++++++++++++++++------- modules/pico_aodv.h | 10 +- modules/pico_ipv4.c | 8 +- modules/pico_udp.c | 7 +- test/picoapp.c | 2 - 5 files changed, 223 insertions(+), 46 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index 0d1f044f9..41394cf36 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -17,13 +17,13 @@ static const struct pico_ip4 HOST_NETMASK = { 0xffffffff }; +static struct pico_ip4 all_bcast = { .addr = 0xFFFFFFFFu }; static const struct pico_ip4 ANY_HOST = { 0x0 }; static uint32_t pico_aodv_local_id = 0; - static int aodv_node_compare(void *ka, void *kb) { struct pico_aodv_node *a = ka, *b = kb; @@ -65,33 +65,34 @@ static void pico_aodv_set_dev(struct pico_device *dev) } -static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) +static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq, int neighbor) { - if ((0 == node->valid_dseq) || (pico_seq_compare(seq, node->dseq) > 0)) { + if (neighbor || (0 == node->valid_dseq) || (pico_seq_compare(seq, node->dseq) > 0)) { node->dseq = seq; + node->valid_dseq = 1; node->last_seen = PICO_TIME_MS(); return 0; } return -1; } -static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw, int metric, struct pico_device *dev) +static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw, uint8_t metric, struct pico_device *dev) { metric++; - if (metric < node->metric) { + if (!node->active || metric < node->metric) { pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); if (!gw) { pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(dev)); node->metric = 1; } else { - node->metric = (uint16_t)metric; + node->metric = metric; pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, gw->ip4, metric, NULL); } node->active = 1; } } -static struct pico_aodv_node *aodv_peer_new(union pico_address *addr) +static struct pico_aodv_node *aodv_peer_new(const union pico_address *addr) { struct pico_aodv_node *node = PICO_ZALLOC(sizeof(struct pico_aodv_node)); if (!node) @@ -102,30 +103,121 @@ static struct pico_aodv_node *aodv_peer_new(union pico_address *addr) } -static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq) +static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq, int neighbor, int valid_seq) { struct pico_aodv_node *node = NULL; node = get_node_by_addr(addr); if (!node) { node = aodv_peer_new(addr); } - if (node && aodv_peer_refresh(node, long_be(seq)) == 0) + + if (!valid_seq) + return node; + + if (node && aodv_peer_refresh(node, long_be(seq), neighbor) == 0) return node; return NULL; } +void aodv_forward(void *pkt, struct pico_msginfo *info, int reply) +{ + struct pico_aodv_node *orig; + union pico_address orig_addr; + struct pico_tree_node *index; + struct pico_device *dev; + pico_time now; + int size; + + printf("Forwarding %s packet\n", reply?"REPLY":"REQUEST"); + + if (reply) { + struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *)pkt; + orig_addr.ip4.addr = rep->dest; + rep->hop_count++; + size = sizeof(struct pico_aodv_rrep); + } else { + struct pico_aodv_rreq *req = (struct pico_aodv_rreq *)pkt; + orig_addr.ip4.addr = req->orig; + req->hop_count++; + size = sizeof(struct pico_aodv_rreq); + } + + orig = get_node_by_addr(&orig_addr); + if (!orig) + orig = aodv_peer_new(&orig_addr); + if (!orig) + return; + + now = PICO_TIME_MS(); + + if ( ((orig->fwd_time == 0) || ((now - orig->fwd_time) > AODV_NET_TRAVERSAL_TIME)) && (--info->ttl > 0)) { + orig->fwd_time = now; + info->dev = NULL; + pico_tree_foreach(index, &aodv_devices){ + dev = index->keyValue; + pico_aodv_set_dev(dev); + pico_socket_sendto_extended(aodv_socket, pkt, size, &all_bcast, short_be(PICO_AODV_PORT), info); + printf("Forwarding %s: complete! ==== \n", reply?"REPLY":"REQUEST"); + } + } +} + +static uint32_t aodv_lifetime(struct pico_aodv_node *node) +{ + uint32_t lifetime; + pico_time now = PICO_TIME_MS(); + if (!node->last_seen) + node->last_seen = now; + + if ((now - node->last_seen) > AODV_ACTIVE_ROUTE_TIMEOUT) + return 0; + + lifetime = AODV_ACTIVE_ROUTE_TIMEOUT - (uint32_t)(now - node->last_seen); + return lifetime; +} + +static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq *req, int node_is_local, struct pico_msginfo *info) +{ + struct pico_aodv_rrep reply; + union pico_address dest; + reply.type = AODV_TYPE_RREP; + reply.hop_count = 0; + reply.dest = req->dest; + reply.dseq = req->dseq; + reply.orig = req->orig; + + dest.ip4.addr = 0xFFFFFFFF; /* wide broadcast */ + + if (short_be(req->req_flags) & AODV_RREQ_FLAG_G) + dest.ip4.addr = req->orig; + else + pico_aodv_set_dev(info->dev); + + if (node_is_local) { + reply.lifetime = long_be(AODV_MY_ROUTE_TIMEOUT); + reply.dseq = long_be(++pico_aodv_local_id); + pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); + } else if (((short_be(req->req_flags) & AODV_RREQ_FLAG_D) == 0) && (node->valid_dseq)) { + reply.lifetime = long_be(aodv_lifetime(node)); + reply.hop_count = (uint8_t)((uint8_t)reply.hop_count + (uint8_t)node->metric); + reply.dseq = long_be(node->dseq); + printf("Generating RREQ for node %x, id=%x\n", reply.dest, reply.dseq); + pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); + } +} + /* Parser functions */ -static void aodv_recv_valid_rreq(struct pico_aodv_node *node) +static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req, struct pico_msginfo *info) { struct pico_device *dev; dev = pico_ipv4_link_find(&node->dest.ip4); - if (dev) { - /* Case 1: destination is ourselves. Send reply. */ - } else if (node->active) { - /* Case 2: we have a possible route. Send reply. */ + if (dev || node->active) { + /* if destination is ourselves, or we have a possible route: Send reply. */ + aodv_send_reply(node, req, dev != NULL, info); } else { - /* Case 3: destination unknown. Evaluate forwarding. */ + /* destination unknown. Evaluate forwarding. */ + aodv_forward(req, info, 0); } } @@ -134,16 +226,25 @@ static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, str struct pico_aodv_rreq *req = (struct pico_aodv_rreq *) buf; struct pico_aodv_node *node = NULL; union pico_address orig; + (void)from; if (len != sizeof(struct pico_aodv_rreq)) return; orig.ip4.addr = req->orig; - node = aodv_peer_eval(&orig, req->oseq); - if (!node) + node = aodv_peer_eval(&orig, req->oseq, 1, 1); /* Evaluate neighbor. */ + if (!node) { + printf("RREQ: Neighbor is not valid. oseq=%d, stored dseq: %d\n", long_be(req->oseq), node->dseq); return; - aodv_elect_route(node, from, req->hop_count, msginfo->dev); + } + aodv_elect_route(node, NULL, 1, msginfo->dev); - aodv_recv_valid_rreq(node); + orig.ip4.addr = req->dest; + node = aodv_peer_eval(&orig, req->dseq, 0, !(req->req_flags & short_be(AODV_RREQ_FLAG_U))); + if (!node) + node = aodv_peer_new(&orig); + if (!node) + return; + aodv_recv_valid_rreq(node, req, msginfo); } static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) @@ -151,15 +252,24 @@ static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, str struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *) buf; struct pico_aodv_node *node = NULL; union pico_address dest; + struct pico_device *dev = NULL; if (len != sizeof(struct pico_aodv_rrep)) return; dest.ip4.addr = rep->dest; - node = aodv_peer_eval(&dest, rep->dseq); - if (!node) + dev = pico_ipv4_link_find(&dest.ip4); + + if (dev) /* Our reply packet got rebounced, no useful information here, no need to fwd. */ return; - dest.ip4.addr = node->dest.ip4.addr; - aodv_elect_route(node, from, rep->hop_count, msginfo->dev); + + printf("::::::::::::: Parsing RREP for node %08x\n", rep->dest); + node = aodv_peer_eval(&dest, rep->dseq, 0, 1); + if (node) { + printf("::::::::::::: Node found. Electing route and forwarding.\n"); + dest.ip4.addr = node->dest.ip4.addr; + aodv_elect_route(node, from, rep->hop_count, msginfo->dev); + aodv_forward(rep, msginfo, 1); + } } static void aodv_parse_rerr(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) @@ -203,6 +313,7 @@ static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, str /* Type is invalid. Discard silently. */ return; } + pico_ipv4_route_add(from->ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(msginfo->dev)); aodv_parser[buf[0]].call(from, buf, len, msginfo); } @@ -220,34 +331,93 @@ static void pico_aodv_socket_callback(uint16_t ev, struct pico_socket *s) if (r <= 0) return; dbg("Received AODV packet: %d bytes \n", r); + pico_aodv_parse(&from, aodv_pkt, r, &msginfo); } } static void aodv_make_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req) { + memset(req, 0, sizeof(struct pico_aodv_rreq)); req->type = AODV_TYPE_RREQ; - req->req_flags |= short_be(AODV_RREQ_FLAG_G); /* RFC3561 $6.3: we SHOULD set G flag as originators */ + if (!node->valid_dseq) { req->req_flags |= short_be(AODV_RREQ_FLAG_U); /* no known dseq, mark as U */ req->dseq = 0; /* Unknown */ } else { req->dseq = long_be(node->dseq); + req->req_flags |= short_be(AODV_RREQ_FLAG_G); /* RFC3561 $6.3: we SHOULD set G flag as originators */ } /* Hop count = 0; */ - req->rreq_id = long_be(pico_aodv_local_id); + req->rreq_id = long_be(++pico_aodv_local_id); req->dest = node->dest.ip4.addr; req->oseq = long_be(pico_aodv_local_id); } +static void aodv_retrans_rreq(pico_time now, void *arg) +{ + struct pico_aodv_node *node = (struct pico_aodv_node *)arg; + struct pico_device *dev; + struct pico_tree_node *index; + static struct pico_aodv_rreq rreq; + struct pico_ipv4_link *ip4l = NULL; + struct pico_msginfo info = { + .dev = NULL, .tos = 0, .ttl = AODV_TTL_START + }; + (void)now; + + memset(&rreq, 0, sizeof(rreq)); + + if (node->active) { + node->ring_ttl = 0; + return; + } + + if (node->ring_ttl >= AODV_TTL_THRESHOLD) { + node->ring_ttl = AODV_NET_DIAMETER; + printf("----------- DIAMETER reached.\n"); + } + + + if (node->rreq_retry > AODV_RREQ_RETRIES) { + node->rreq_retry = 0; + node->ring_ttl = 0; + printf("Node is unreachable.\n"); + return; + } + + if (node->ring_ttl == AODV_NET_DIAMETER) { + node->rreq_retry++; + printf("Retry #%d\n", node->rreq_retry); + } + + aodv_make_rreq(node, &rreq); + info.ttl = (uint8_t)node->ring_ttl; + pico_tree_foreach(index, &aodv_devices){ + dev = index->keyValue; + pico_aodv_set_dev(dev); + ip4l = pico_ipv4_link_by_dev(dev); + if (ip4l) { + rreq.orig = ip4l->address.addr; + pico_socket_sendto_extended(aodv_socket, &rreq, sizeof(rreq), &all_bcast, short_be(PICO_AODV_PORT), &info); + } + } + if (node->ring_ttl < AODV_NET_DIAMETER) + node->ring_ttl += AODV_TTL_INCREMENT; + pico_timer_add((pico_time)AODV_RING_TRAVERSAL_TIME(node->ring_ttl), aodv_retrans_rreq, node); +} + static int aodv_send_req(struct pico_aodv_node *node) { struct pico_device *dev; struct pico_tree_node *index; - struct pico_ip4 all_bcast = { .addr = 0xFFFFFFFFu }; static struct pico_aodv_rreq rreq; int n = 0; struct pico_ipv4_link *ip4l = NULL; + struct pico_msginfo info = { + .dev = NULL, .tos = 0, .ttl = AODV_TTL_START + }; + memset(&rreq, 0, sizeof(rreq)); if (pico_tree_empty(&aodv_devices)) return n; @@ -257,7 +427,6 @@ static int aodv_send_req(struct pico_aodv_node *node) return -1; } - pico_aodv_local_id++; aodv_make_rreq(node, &rreq); pico_tree_foreach(index, &aodv_devices){ dev = index->keyValue; @@ -265,10 +434,11 @@ static int aodv_send_req(struct pico_aodv_node *node) ip4l = pico_ipv4_link_by_dev(dev); if (ip4l) { rreq.orig = ip4l->address.addr; - pico_socket_sendto(aodv_socket, &rreq, sizeof(rreq), &all_bcast, short_be(PICO_AODV_PORT)); + pico_socket_sendto_extended(aodv_socket, &rreq, sizeof(rreq), &all_bcast, short_be(PICO_AODV_PORT), &info); n++; } } + pico_timer_add(AODV_PATH_DISCOVERY_TIME, aodv_retrans_rreq, node); return n; } @@ -304,14 +474,16 @@ int pico_aodv_add(struct pico_device *dev) int pico_aodv_lookup(const union pico_address *addr) { struct pico_aodv_node *node = get_node_by_addr(addr); - if (!node) { - node = PICO_ZALLOC(sizeof(struct pico_aodv_node)); - if (!node) - return -1; - memcpy(&node->dest, addr, sizeof(union pico_address)); - } - if (aodv_send_req(node) > 0) + if (!node) + node = aodv_peer_new(addr); + if (!node) + return -1; + + if (node->ring_ttl < AODV_TTL_START) { + node->ring_ttl = AODV_TTL_START; + aodv_send_req(node); return 0; + } pico_err = PICO_ERR_EINVAL; return -1; } diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index fd186603e..7cabc4908 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -14,8 +14,7 @@ #define PICO_AODV_PORT (654) /* RFC3561 $10 */ -#define AODV_TTL_VALUE(f) (((struct pico_ipv4_hdr *)f->net_hdr)->ttl) -#define AODV_ACTIVE_ROUTE_TIMEOUT 5000 /* Conservative value for link breakage detection */ +#define AODV_ACTIVE_ROUTE_TIMEOUT (5000u) /* Conservative value for link breakage detection */ #define AODV_DELETE_PERIOD (5 * AODV_ACTIVE_ROUTE_TIMEOUT) /* Recommended value K = 5 */ #define AODV_ALLOWED_HELLO_LOSS (4) /* conservative */ #define AODV_NET_DIAMETER (35) @@ -35,7 +34,7 @@ #define AODV_BLACKLIST_TIMEOUT (AODV_RREQ_RETRIES * AODV_NET_TRAVERSAL_TIME) #define AODV_NEXT_HOP_WAIT (AODV_NODE_TRAVERSAL_TIME + 10) #define AODV_PATH_DISCOVERY_TIME (2 * AODV_NET_TRAVERSAL_TIME) -#define AODV_RING_TRAVERSAL_TIME(f) (2 * AODV_NODE_TRAVERSAL_TIME * (AODV_TTL_VALUE(f) + AODV_TIMEOUT_BUFFER)) +#define AODV_RING_TRAVERSAL_TIME(ttl) (2 * AODV_NODE_TRAVERSAL_TIME * (ttl + AODV_TIMEOUT_BUFFER)) /* End section RFC3561 $10 */ @@ -84,10 +83,13 @@ struct pico_aodv_node { union pico_address dest; uint32_t dseq; - uint16_t metric; + uint8_t metric; int valid_dseq; int active; pico_time last_seen; + pico_time fwd_time; + int ring_ttl; + int rreq_retry; }; PACKED_STRUCT_DEF pico_aodv_unreachable diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index 65d26423d..6f06e0d88 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -19,6 +19,7 @@ #include "pico_nat.h" #include "pico_igmp.h" #include "pico_tree.h" +#include "pico_aodv.h" #include "pico_socket_multicast.h" #ifdef PICO_SUPPORT_IPV4 @@ -885,9 +886,12 @@ struct pico_ip4 *pico_ipv4_source_find(const struct pico_ip4 *dst) rt = route_find(dst); if (rt && rt->link) { myself = &rt->link->address; - } else + } else { +#ifdef PICO_SUPPORT_AODV + pico_aodv_lookup((const union pico_address *)dst); +#endif pico_err = PICO_ERR_EHOSTUNREACH; - + } return myself; } diff --git a/modules/pico_udp.c b/modules/pico_udp.c index deef46397..678ed919d 100644 --- a/modules/pico_udp.c +++ b/modules/pico_udp.c @@ -193,6 +193,10 @@ uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src *port = hdr->sport; } + if (msginfo) { + pico_udp_get_msginfo(f, msginfo); + } + if (f->payload_len > len) { memcpy(buf, f->payload, len); f->payload += len; @@ -205,9 +209,6 @@ uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src pico_frame_discard(f); return ret; } - if (msginfo) { - pico_udp_get_msginfo(f, msginfo); - } } else return 0; } diff --git a/test/picoapp.c b/test/picoapp.c index e4f09f6ff..6c9f92ec2 100644 --- a/test/picoapp.c +++ b/test/picoapp.c @@ -619,8 +619,6 @@ int main(int argc, char **argv) pico_aodv_add(dev); } - /* TEST */ - pico_aodv_lookup(&aaa); app_noop(); #endif From b263d0468d3cb08002424acd2ddd25f100d6baeb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2015 21:58:39 +0100 Subject: [PATCH 19/61] AODV: return path (draft) (#219) --- modules/pico_aodv.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index 41394cf36..7068387cf 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -208,6 +208,16 @@ static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq * /* Parser functions */ +static int aodv_send_req(struct pico_aodv_node *node); + +static void aodv_reverse_path_discover(pico_time now, void *arg) +{ + struct pico_aodv_node *origin = (struct pico_aodv_node *)arg; + (void)now; + printf("Sending G RREP to ORIGIN.\n"); + aodv_send_req(origin); +} + static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req, struct pico_msginfo *info) { struct pico_device *dev; @@ -215,6 +225,15 @@ static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_r if (dev || node->active) { /* if destination is ourselves, or we have a possible route: Send reply. */ aodv_send_reply(node, req, dev != NULL, info); + if (dev) { + /* if really for us, we need to build the return route. Initiate a gratuitous request. */ + union pico_address origin_addr; + struct pico_aodv_node *origin; + origin_addr.ip4.addr = req->orig; + origin = get_node_by_addr(&origin_addr); + if (origin) + pico_timer_add(AODV_PATH_DISCOVERY_TIME, aodv_reverse_path_discover, origin); + } } else { /* destination unknown. Evaluate forwarding. */ aodv_forward(req, info, 0); From b48aec3fb92e1ed298d681decdd844b99d0e4d57 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2015 10:24:50 +0100 Subject: [PATCH 20/61] AODV: Working return route (#219) --- modules/pico_aodv.c | 102 +++++++++++++++++++++++++++++++++----------- modules/pico_ipv4.c | 14 +++++- 2 files changed, 89 insertions(+), 27 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index 7068387cf..e4eae3348 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -65,9 +65,9 @@ static void pico_aodv_set_dev(struct pico_device *dev) } -static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq, int neighbor) +static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) { - if (neighbor || (0 == node->valid_dseq) || (pico_seq_compare(seq, node->dseq) > 0)) { + if ((0 == node->valid_dseq) || (pico_seq_compare(seq, node->dseq) > 0)) { node->dseq = seq; node->valid_dseq = 1; node->last_seen = PICO_TIME_MS(); @@ -103,7 +103,7 @@ static struct pico_aodv_node *aodv_peer_new(const union pico_address *addr) } -static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq, int neighbor, int valid_seq) +static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq, int valid_seq) { struct pico_aodv_node *node = NULL; node = get_node_by_addr(addr); @@ -114,7 +114,7 @@ static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t if (!valid_seq) return node; - if (node && aodv_peer_refresh(node, long_be(seq), neighbor) == 0) + if (node && (aodv_peer_refresh(node, long_be(seq)) == 0)) return node; return NULL; } @@ -134,6 +134,7 @@ void aodv_forward(void *pkt, struct pico_msginfo *info, int reply) struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *)pkt; orig_addr.ip4.addr = rep->dest; rep->hop_count++; + printf("RREP hop count: %d\n", rep->hop_count); size = sizeof(struct pico_aodv_rrep); } else { struct pico_aodv_rreq *req = (struct pico_aodv_rreq *)pkt; @@ -150,7 +151,9 @@ void aodv_forward(void *pkt, struct pico_msginfo *info, int reply) now = PICO_TIME_MS(); - if ( ((orig->fwd_time == 0) || ((now - orig->fwd_time) > AODV_NET_TRAVERSAL_TIME)) && (--info->ttl > 0)) { + printf("Forwarding %s: last fwd_time: %llu now: %llu ttl: %d ==== \n", reply?"REPLY":"REQUEST", + orig->fwd_time, now, info->ttl); + if ( ((orig->fwd_time == 0) || ((now - orig->fwd_time) > AODV_NODE_TRAVERSAL_TIME)) && (--info->ttl > 0)) { orig->fwd_time = now; info->dev = NULL; pico_tree_foreach(index, &aodv_devices){ @@ -180,18 +183,26 @@ static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq * { struct pico_aodv_rrep reply; union pico_address dest; + union pico_address oaddr; + struct pico_aodv_node *orig; + oaddr.ip4.addr = req->orig; + orig = get_node_by_addr(&oaddr); reply.type = AODV_TYPE_RREP; - reply.hop_count = 0; reply.dest = req->dest; reply.dseq = req->dseq; reply.orig = req->orig; + reply.hop_count = (uint8_t)(orig->metric - 1u); + + if (!orig) + return; dest.ip4.addr = 0xFFFFFFFF; /* wide broadcast */ - if (short_be(req->req_flags) & AODV_RREQ_FLAG_G) + if (short_be(req->req_flags) & AODV_RREQ_FLAG_G) { dest.ip4.addr = req->orig; - else + } else { pico_aodv_set_dev(info->dev); + } if (node_is_local) { reply.lifetime = long_be(AODV_MY_ROUTE_TIMEOUT); @@ -199,9 +210,8 @@ static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq * pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); } else if (((short_be(req->req_flags) & AODV_RREQ_FLAG_D) == 0) && (node->valid_dseq)) { reply.lifetime = long_be(aodv_lifetime(node)); - reply.hop_count = (uint8_t)((uint8_t)reply.hop_count + (uint8_t)node->metric); reply.dseq = long_be(node->dseq); - printf("Generating RREQ for node %x, id=%x\n", reply.dest, reply.dseq); + printf("Generating RREP for node %x, id=%x\n", reply.dest, reply.dseq); pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); } } @@ -214,7 +224,8 @@ static void aodv_reverse_path_discover(pico_time now, void *arg) { struct pico_aodv_node *origin = (struct pico_aodv_node *)arg; (void)now; - printf("Sending G RREP to ORIGIN.\n"); + printf("Sending G RREQ to ORIGIN (metric = %d).\n", origin->metric); + origin->ring_ttl = origin->metric; aodv_send_req(origin); } @@ -222,6 +233,7 @@ static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_r { struct pico_device *dev; dev = pico_ipv4_link_find(&node->dest.ip4); + printf("Valid req.\n"); if (dev || node->active) { /* if destination is ourselves, or we have a possible route: Send reply. */ aodv_send_reply(node, req, dev != NULL, info); @@ -234,33 +246,49 @@ static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_r if (origin) pico_timer_add(AODV_PATH_DISCOVERY_TIME, aodv_reverse_path_discover, origin); } + printf("Replied.\n"); } else { /* destination unknown. Evaluate forwarding. */ + printf(" == Forwarding == .\n"); aodv_forward(req, info, 0); } } + static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) { struct pico_aodv_rreq *req = (struct pico_aodv_rreq *) buf; struct pico_aodv_node *node = NULL; - union pico_address orig; + struct pico_device *dev; + union pico_address orig, dest; (void)from; if (len != sizeof(struct pico_aodv_rreq)) return; orig.ip4.addr = req->orig; - node = aodv_peer_eval(&orig, req->oseq, 1, 1); /* Evaluate neighbor. */ + dev = pico_ipv4_link_find(&orig.ip4); + if (dev) { + printf("RREQ <-- myself\n"); + return; + } + + node = aodv_peer_eval(&orig, req->oseq, 1); if (!node) { - printf("RREQ: Neighbor is not valid. oseq=%d, stored dseq: %d\n", long_be(req->oseq), node->dseq); + printf("RREQ: Neighbor is not valid. oseq=%d\n", long_be(req->oseq)); return; } - aodv_elect_route(node, NULL, 1, msginfo->dev); - orig.ip4.addr = req->dest; - node = aodv_peer_eval(&orig, req->dseq, 0, !(req->req_flags & short_be(AODV_RREQ_FLAG_U))); - if (!node) - node = aodv_peer_new(&orig); + if (req->hop_count > 0) + aodv_elect_route(node, from, req->hop_count, msginfo->dev); + else + aodv_elect_route(node, NULL, 0, msginfo->dev); + + dest.ip4.addr = req->dest; + node = aodv_peer_eval(&dest, req->dseq, !(req->req_flags & short_be(AODV_RREQ_FLAG_U))); + if (!node) { + node = aodv_peer_new(&dest); + printf("RREQ: New peer! %08x\n", dest.ip4.addr); + } if (!node) return; aodv_recv_valid_rreq(node, req, msginfo); @@ -271,23 +299,31 @@ static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, str struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *) buf; struct pico_aodv_node *node = NULL; union pico_address dest; + union pico_address orig; struct pico_device *dev = NULL; if (len != sizeof(struct pico_aodv_rrep)) return; dest.ip4.addr = rep->dest; + orig.ip4.addr = rep->orig; dev = pico_ipv4_link_find(&dest.ip4); if (dev) /* Our reply packet got rebounced, no useful information here, no need to fwd. */ return; printf("::::::::::::: Parsing RREP for node %08x\n", rep->dest); - node = aodv_peer_eval(&dest, rep->dseq, 0, 1); + node = aodv_peer_eval(&dest, rep->dseq, 1); if (node) { printf("::::::::::::: Node found. Electing route and forwarding.\n"); dest.ip4.addr = node->dest.ip4.addr; - aodv_elect_route(node, from, rep->hop_count, msginfo->dev); - aodv_forward(rep, msginfo, 1); + if (rep->hop_count > 0) + aodv_elect_route(node, from, rep->hop_count, msginfo->dev); + else + aodv_elect_route(node, NULL, 0, msginfo->dev); + + /* If we are the final destination for the reply (orig), no need to forward. */ + if (!pico_ipv4_link_find(&orig.ip4)) + aodv_forward(rep, msginfo, 1); } } @@ -328,11 +364,19 @@ struct aodv_parser_s aodv_parser[5] = { static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) { + struct pico_aodv_node *node; if ((buf[0] < 1) || (buf[0] > 4)) { /* Type is invalid. Discard silently. */ return; } - pico_ipv4_route_add(from->ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(msginfo->dev)); + + node = aodv_peer_eval(from, 0, 0); + if (!node) + node = aodv_peer_new(from); + if (node) { + aodv_elect_route(node, NULL, 1, msginfo->dev); + } + printf("Received AODV packet, ttl = %d\n", msginfo->ttl); aodv_parser[buf[0]].call(from, buf, len, msginfo); } @@ -388,11 +432,11 @@ static void aodv_retrans_rreq(pico_time now, void *arg) memset(&rreq, 0, sizeof(rreq)); if (node->active) { - node->ring_ttl = 0; + printf("Node %08x already active.\n", node->dest.ip4.addr); return; } - if (node->ring_ttl >= AODV_TTL_THRESHOLD) { + if (node->ring_ttl > AODV_TTL_THRESHOLD) { node->ring_ttl = AODV_NET_DIAMETER; printf("----------- DIAMETER reached.\n"); } @@ -446,6 +490,9 @@ static int aodv_send_req(struct pico_aodv_node *node) return -1; } + if (node->active) + info.ttl = node->metric; + aodv_make_rreq(node, &rreq); pico_tree_foreach(index, &aodv_devices){ dev = index->keyValue; @@ -457,7 +504,7 @@ static int aodv_send_req(struct pico_aodv_node *node) n++; } } - pico_timer_add(AODV_PATH_DISCOVERY_TIME, aodv_retrans_rreq, node); + pico_timer_add((pico_time)AODV_RING_TRAVERSAL_TIME(1), aodv_retrans_rreq, node); return n; } @@ -498,6 +545,9 @@ int pico_aodv_lookup(const union pico_address *addr) if (!node) return -1; + if (node->active) + return 0; + if (node->ring_ttl < AODV_TTL_START) { node->ring_ttl = AODV_TTL_START; aodv_send_req(node); diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index 6f06e0d88..2381e06ce 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -888,7 +888,10 @@ struct pico_ip4 *pico_ipv4_source_find(const struct pico_ip4 *dst) myself = &rt->link->address; } else { #ifdef PICO_SUPPORT_AODV - pico_aodv_lookup((const union pico_address *)dst); + union pico_address node_address; + node_address.ip4.addr = dst->addr; + if(dst->addr && pico_ipv4_is_unicast(dst->addr)) + pico_aodv_lookup(&node_address); #endif pico_err = PICO_ERR_EHOSTUNREACH; } @@ -1325,6 +1328,15 @@ int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t pro #endif +#ifdef PICO_SUPPORT_AODV + { + union pico_address node_address; + node_address.ip4.addr = hdr->dst.addr; + if(hdr->dst.addr && pico_ipv4_is_unicast(hdr->dst.addr)) + pico_aodv_lookup(&node_address); + } +#endif + if(pico_ipv4_link_get(&hdr->dst)) { /* it's our own IP */ return pico_enqueue(&in, f); From dbab4a0aacf5299b4fb0312dfe90b02c34eb9486 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2015 13:56:33 +0100 Subject: [PATCH 21/61] AODV: Reduced debug. Verified 2-way routes working. (#219) --- modules/pico_aodv.c | 91 +++++++++++++++++++++++++++----------------- modules/pico_aodv.h | 34 +++++++++++------ modules/pico_icmp4.c | 10 +++++ modules/pico_ipv4.c | 15 ++++---- 4 files changed, 96 insertions(+), 54 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index e4eae3348..85df845d1 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -13,6 +13,9 @@ #include #include + +#define pico_aodv_dbg(...) do{}while(0) + #define AODV_MAX_PKT (64) static const struct pico_ip4 HOST_NETMASK = { 0xffffffff @@ -67,9 +70,9 @@ static void pico_aodv_set_dev(struct pico_device *dev) static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) { - if ((0 == node->valid_dseq) || (pico_seq_compare(seq, node->dseq) > 0)) { + if ((0 == (node->flags & PICO_AODV_NODE_SYNC)) || (pico_seq_compare(seq, node->dseq) > 0)) { node->dseq = seq; - node->valid_dseq = 1; + node->flags |= PICO_AODV_NODE_SYNC; node->last_seen = PICO_TIME_MS(); return 0; } @@ -79,7 +82,7 @@ static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw, uint8_t metric, struct pico_device *dev) { metric++; - if (!node->active || metric < node->metric) { + if (!(PICO_AODV_ACTIVE(node)) || metric < node->metric) { pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); if (!gw) { pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(dev)); @@ -88,7 +91,6 @@ static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw node->metric = metric; pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, gw->ip4, metric, NULL); } - node->active = 1; } } @@ -128,13 +130,13 @@ void aodv_forward(void *pkt, struct pico_msginfo *info, int reply) pico_time now; int size; - printf("Forwarding %s packet\n", reply?"REPLY":"REQUEST"); + pico_aodv_dbg("Forwarding %s packet\n", reply?"REPLY":"REQUEST"); if (reply) { struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *)pkt; orig_addr.ip4.addr = rep->dest; rep->hop_count++; - printf("RREP hop count: %d\n", rep->hop_count); + pico_aodv_dbg("RREP hop count: %d\n", rep->hop_count); size = sizeof(struct pico_aodv_rrep); } else { struct pico_aodv_rreq *req = (struct pico_aodv_rreq *)pkt; @@ -151,7 +153,7 @@ void aodv_forward(void *pkt, struct pico_msginfo *info, int reply) now = PICO_TIME_MS(); - printf("Forwarding %s: last fwd_time: %llu now: %llu ttl: %d ==== \n", reply?"REPLY":"REQUEST", + pico_aodv_dbg("Forwarding %s: last fwd_time: %llu now: %llu ttl: %d ==== \n", reply?"REPLY":"REQUEST", orig->fwd_time, now, info->ttl); if ( ((orig->fwd_time == 0) || ((now - orig->fwd_time) > AODV_NODE_TRAVERSAL_TIME)) && (--info->ttl > 0)) { orig->fwd_time = now; @@ -160,7 +162,7 @@ void aodv_forward(void *pkt, struct pico_msginfo *info, int reply) dev = index->keyValue; pico_aodv_set_dev(dev); pico_socket_sendto_extended(aodv_socket, pkt, size, &all_bcast, short_be(PICO_AODV_PORT), info); - printf("Forwarding %s: complete! ==== \n", reply?"REPLY":"REQUEST"); + pico_aodv_dbg("Forwarding %s: complete! ==== \n", reply?"REPLY":"REQUEST"); } } } @@ -208,10 +210,10 @@ static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq * reply.lifetime = long_be(AODV_MY_ROUTE_TIMEOUT); reply.dseq = long_be(++pico_aodv_local_id); pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); - } else if (((short_be(req->req_flags) & AODV_RREQ_FLAG_D) == 0) && (node->valid_dseq)) { + } else if (((short_be(req->req_flags) & AODV_RREQ_FLAG_D) == 0) && (node->flags & PICO_AODV_NODE_SYNC)) { reply.lifetime = long_be(aodv_lifetime(node)); reply.dseq = long_be(node->dseq); - printf("Generating RREP for node %x, id=%x\n", reply.dest, reply.dseq); + pico_aodv_dbg("Generating RREP for node %x, id=%x\n", reply.dest, reply.dseq); pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); } } @@ -224,7 +226,7 @@ static void aodv_reverse_path_discover(pico_time now, void *arg) { struct pico_aodv_node *origin = (struct pico_aodv_node *)arg; (void)now; - printf("Sending G RREQ to ORIGIN (metric = %d).\n", origin->metric); + pico_aodv_dbg("Sending G RREQ to ORIGIN (metric = %d).\n", origin->metric); origin->ring_ttl = origin->metric; aodv_send_req(origin); } @@ -233,8 +235,8 @@ static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_r { struct pico_device *dev; dev = pico_ipv4_link_find(&node->dest.ip4); - printf("Valid req.\n"); - if (dev || node->active) { + pico_aodv_dbg("Valid req.\n"); + if (dev || PICO_AODV_ACTIVE(node)) { /* if destination is ourselves, or we have a possible route: Send reply. */ aodv_send_reply(node, req, dev != NULL, info); if (dev) { @@ -243,13 +245,15 @@ static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_r struct pico_aodv_node *origin; origin_addr.ip4.addr = req->orig; origin = get_node_by_addr(&origin_addr); - if (origin) + if (origin) { + origin->flags |= PICO_AODV_NODE_ROUTE_DOWN; pico_timer_add(AODV_PATH_DISCOVERY_TIME, aodv_reverse_path_discover, origin); + } } - printf("Replied.\n"); + pico_aodv_dbg("Replied.\n"); } else { /* destination unknown. Evaluate forwarding. */ - printf(" == Forwarding == .\n"); + pico_aodv_dbg(" == Forwarding == .\n"); aodv_forward(req, info, 0); } } @@ -268,13 +272,13 @@ static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, str orig.ip4.addr = req->orig; dev = pico_ipv4_link_find(&orig.ip4); if (dev) { - printf("RREQ <-- myself\n"); + pico_aodv_dbg("RREQ <-- myself\n"); return; } node = aodv_peer_eval(&orig, req->oseq, 1); if (!node) { - printf("RREQ: Neighbor is not valid. oseq=%d\n", long_be(req->oseq)); + pico_aodv_dbg("RREQ: Neighbor is not valid. oseq=%d\n", long_be(req->oseq)); return; } @@ -287,7 +291,7 @@ static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, str node = aodv_peer_eval(&dest, req->dseq, !(req->req_flags & short_be(AODV_RREQ_FLAG_U))); if (!node) { node = aodv_peer_new(&dest); - printf("RREQ: New peer! %08x\n", dest.ip4.addr); + pico_aodv_dbg("RREQ: New peer! %08x\n", dest.ip4.addr); } if (!node) return; @@ -311,10 +315,10 @@ static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, str if (dev) /* Our reply packet got rebounced, no useful information here, no need to fwd. */ return; - printf("::::::::::::: Parsing RREP for node %08x\n", rep->dest); + pico_aodv_dbg("::::::::::::: Parsing RREP for node %08x\n", rep->dest); node = aodv_peer_eval(&dest, rep->dseq, 1); if (node) { - printf("::::::::::::: Node found. Electing route and forwarding.\n"); + pico_aodv_dbg("::::::::::::: Node found. Electing route and forwarding.\n"); dest.ip4.addr = node->dest.ip4.addr; if (rep->hop_count > 0) aodv_elect_route(node, from, rep->hop_count, msginfo->dev); @@ -322,8 +326,11 @@ static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, str aodv_elect_route(node, NULL, 0, msginfo->dev); /* If we are the final destination for the reply (orig), no need to forward. */ - if (!pico_ipv4_link_find(&orig.ip4)) + if (pico_ipv4_link_find(&orig.ip4)) { + node->flags |= PICO_AODV_NODE_ROUTE_UP; + } else { aodv_forward(rep, msginfo, 1); + } } } @@ -365,18 +372,25 @@ struct aodv_parser_s aodv_parser[5] = { static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) { struct pico_aodv_node *node; + uint8_t hopcount = 0; if ((buf[0] < 1) || (buf[0] > 4)) { /* Type is invalid. Discard silently. */ return; } + if (buf[0] == AODV_TYPE_RREQ) { + hopcount = ((struct pico_aodv_rreq *)buf)->hop_count; + } + if (buf[0] == AODV_TYPE_RREP) { + hopcount = ((struct pico_aodv_rrep *)buf)->hop_count; + } node = aodv_peer_eval(from, 0, 0); if (!node) node = aodv_peer_new(from); - if (node) { - aodv_elect_route(node, NULL, 1, msginfo->dev); + if (node && (hopcount == 0)) { + aodv_elect_route(node, NULL, hopcount, msginfo->dev); } - printf("Received AODV packet, ttl = %d\n", msginfo->ttl); + pico_aodv_dbg("Received AODV packet, ttl = %d\n", msginfo->ttl); aodv_parser[buf[0]].call(from, buf, len, msginfo); } @@ -393,7 +407,7 @@ static void pico_aodv_socket_callback(uint16_t ev, struct pico_socket *s) r = pico_socket_recvfrom_extended(s, aodv_pkt, AODV_MAX_PKT, &from, &sport, &msginfo); if (r <= 0) return; - dbg("Received AODV packet: %d bytes \n", r); + pico_aodv_dbg("Received AODV packet: %d bytes \n", r); pico_aodv_parse(&from, aodv_pkt, r, &msginfo); } @@ -404,7 +418,7 @@ static void aodv_make_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *r memset(req, 0, sizeof(struct pico_aodv_rreq)); req->type = AODV_TYPE_RREQ; - if (!node->valid_dseq) { + if (0 == (node->flags & PICO_AODV_NODE_SYNC)) { req->req_flags |= short_be(AODV_RREQ_FLAG_U); /* no known dseq, mark as U */ req->dseq = 0; /* Unknown */ } else { @@ -431,27 +445,28 @@ static void aodv_retrans_rreq(pico_time now, void *arg) memset(&rreq, 0, sizeof(rreq)); - if (node->active) { - printf("Node %08x already active.\n", node->dest.ip4.addr); + if (node->flags & PICO_AODV_NODE_ROUTE_UP) { + pico_aodv_dbg("------------------------------------------------------ Node %08x already active.\n", node->dest.ip4.addr); return; } if (node->ring_ttl > AODV_TTL_THRESHOLD) { node->ring_ttl = AODV_NET_DIAMETER; - printf("----------- DIAMETER reached.\n"); + pico_aodv_dbg("----------- DIAMETER reached.\n"); } if (node->rreq_retry > AODV_RREQ_RETRIES) { node->rreq_retry = 0; node->ring_ttl = 0; - printf("Node is unreachable.\n"); + pico_aodv_dbg("Node is unreachable.\n"); + node->flags &= (~PICO_AODV_NODE_ROUTE_DOWN); return; } if (node->ring_ttl == AODV_NET_DIAMETER) { node->rreq_retry++; - printf("Retry #%d\n", node->rreq_retry); + pico_aodv_dbg("Retry #%d\n", node->rreq_retry); } aodv_make_rreq(node, &rreq); @@ -482,6 +497,11 @@ static int aodv_send_req(struct pico_aodv_node *node) }; memset(&rreq, 0, sizeof(rreq)); + if (PICO_AODV_ACTIVE(node)) + return 0; + + node->flags |= PICO_AODV_NODE_REQUESTING; + if (pico_tree_empty(&aodv_devices)) return n; @@ -490,8 +510,9 @@ static int aodv_send_req(struct pico_aodv_node *node) return -1; } - if (node->active) + if (node->flags & PICO_AODV_NODE_ROUTE_DOWN) { info.ttl = node->metric; + } aodv_make_rreq(node, &rreq); pico_tree_foreach(index, &aodv_devices){ @@ -545,9 +566,9 @@ int pico_aodv_lookup(const union pico_address *addr) if (!node) return -1; - if (node->active) + if ((node->flags & PICO_AODV_NODE_ROUTE_UP) || (node->flags & PICO_AODV_NODE_ROUTE_DOWN)) return 0; - + if (node->ring_ttl < AODV_TTL_START) { node->ring_ttl = AODV_TTL_START; aodv_send_req(node); diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index 7cabc4908..f8bc75a79 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -17,18 +17,18 @@ #define AODV_ACTIVE_ROUTE_TIMEOUT (5000u) /* Conservative value for link breakage detection */ #define AODV_DELETE_PERIOD (5 * AODV_ACTIVE_ROUTE_TIMEOUT) /* Recommended value K = 5 */ #define AODV_ALLOWED_HELLO_LOSS (4) /* conservative */ -#define AODV_NET_DIAMETER (35) +#define AODV_NET_DIAMETER ((uint8_t)(35)) #define AODV_RREQ_RETRIES (2) #define AODV_NODE_TRAVERSAL_TIME (40) #define AODV_HELLO_INTERVAL (1) -#define AODV_LOCAL_ADD_TTL (2) +#define AODV_LOCAL_ADD_TTL 2 #define AODV_RREQ_RATELIMIT (10) #define AODV_TIMEOUT_BUFFER (2) -#define AODV_TTL_START (1) -#define AODV_TTL_INCREMENT (2) -#define AODV_TTL_THRESHOLD (7) +#define AODV_TTL_START ((uint8_t)(1)) +#define AODV_TTL_INCREMENT ((uint8_t)(2)) +#define AODV_TTL_THRESHOLD ((uint8_t)(7)) #define AODV_RERR_RATELIMIT (10) -#define AODV_MAX_REPAIR_TTL (AODV_NET_DIAMETER / 3) +#define AODV_MAX_REPAIR_TTL ((uint8_t)(AODV_NET_DIAMETER / 3)) #define AODV_MY_ROUTE_TIMEOUT (2 * AODV_ACTIVE_ROUTE_TIMEOUT) #define AODV_NET_TRAVERSAL_TIME (2 * AODV_NODE_TRAVERSAL_TIME * AODV_NET_DIAMETER) #define AODV_BLACKLIST_TIMEOUT (AODV_RREQ_RETRIES * AODV_NET_TRAVERSAL_TIME) @@ -79,17 +79,27 @@ PACKED_STRUCT_DEF pico_aodv_rrep #define AODV_RREP_FLAG_A 0x40 #define AODV_RREP_FLAG_RESERVED 0x3F +#define PICO_AODV_NODE_NEW 0x0000 +#define PICO_AODV_NODE_SYNC 0x0001 +#define PICO_AODV_NODE_REQUESTING 0x0002 +#define PICO_AODV_NODE_ROUTE_UP 0x0004 +#define PICO_AODV_NODE_ROUTE_DOWN 0x0008 +#define PICO_AODV_NODE_IDLING 0x0010 +#define PICO_AODV_NODE_UNREACH 0x0020 + +#define PICO_AODV_ACTIVE(node) ((node->flags & PICO_AODV_NODE_ROUTE_UP) && (node->flags & PICO_AODV_NODE_ROUTE_DOWN)) + + struct pico_aodv_node { union pico_address dest; - uint32_t dseq; - uint8_t metric; - int valid_dseq; - int active; pico_time last_seen; pico_time fwd_time; - int ring_ttl; - int rreq_retry; + uint32_t dseq; + uint16_t flags; + uint8_t metric; + uint8_t ring_ttl; + uint8_t rreq_retry; }; PACKED_STRUCT_DEF pico_aodv_unreachable diff --git a/modules/pico_icmp4.c b/modules/pico_icmp4.c index 0cd7f585c..9b7479757 100644 --- a/modules/pico_icmp4.c +++ b/modules/pico_icmp4.c @@ -47,6 +47,8 @@ static void ping_recv_reply(struct pico_frame *f); static int pico_icmp4_process_in(struct pico_protocol *self, struct pico_frame *f) { struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + static uint16_t last_id = 0; + static uint16_t last_seq = 0; IGNORE_PARAMETER(self); if (hdr->type == PICO_ICMP_ECHO) { @@ -55,6 +57,14 @@ static int pico_icmp4_process_in(struct pico_protocol *self, struct pico_frame * if (f->dev && f->dev->eth) f->len -= PICO_SIZE_ETHHDR; + if ((hdr->hun.ih_idseq.idseq_id == last_id) && (last_seq == hdr->hun.ih_idseq.idseq_seq)) { + /* The network duplicated the echo. Do not reply. */ + printf("DUP!\n"); + pico_frame_discard(f); + return 0; + } + last_id = hdr->hun.ih_idseq.idseq_id; + last_seq = hdr->hun.ih_idseq.idseq_seq; pico_icmp4_checksum(f); pico_ipv4_rebound(f); } else if (hdr->type == PICO_ICMP_UNREACH) { diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index 2381e06ce..710192c5d 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -877,6 +877,12 @@ struct pico_ip4 *pico_ipv4_source_find(const struct pico_ip4 *dst) { struct pico_ip4 *myself = NULL; struct pico_ipv4_route *rt; +#ifdef PICO_SUPPORT_AODV + union pico_address node_address; + node_address.ip4.addr = dst->addr; + if(dst->addr && pico_ipv4_is_unicast(dst->addr)) + pico_aodv_lookup(&node_address); +#endif if(!dst) { pico_err = PICO_ERR_EINVAL; @@ -887,12 +893,6 @@ struct pico_ip4 *pico_ipv4_source_find(const struct pico_ip4 *dst) if (rt && rt->link) { myself = &rt->link->address; } else { -#ifdef PICO_SUPPORT_AODV - union pico_address node_address; - node_address.ip4.addr = dst->addr; - if(dst->addr && pico_ipv4_is_unicast(dst->addr)) - pico_aodv_lookup(&node_address); -#endif pico_err = PICO_ERR_EHOSTUNREACH; } return myself; @@ -1328,7 +1328,8 @@ int pico_ipv4_frame_push(struct pico_frame *f, struct pico_ip4 *dst, uint8_t pro #endif -#ifdef PICO_SUPPORT_AODV +//#ifdef PICO_SUPPORT_AODV +#if 0 { union pico_address node_address; node_address.ip4.addr = hdr->dst.addr; From 7748280d380e5c63d358b1e436837c3ffb983387 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2015 13:58:13 +0100 Subject: [PATCH 22/61] Warning cleanup --- modules/pico_aodv.h | 2 +- modules/pico_icmp4.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index f8bc75a79..211f4b930 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -25,7 +25,7 @@ #define AODV_RREQ_RATELIMIT (10) #define AODV_TIMEOUT_BUFFER (2) #define AODV_TTL_START ((uint8_t)(1)) -#define AODV_TTL_INCREMENT ((uint8_t)(2)) +#define AODV_TTL_INCREMENT 2 #define AODV_TTL_THRESHOLD ((uint8_t)(7)) #define AODV_RERR_RATELIMIT (10) #define AODV_MAX_REPAIR_TTL ((uint8_t)(AODV_NET_DIAMETER / 3)) diff --git a/modules/pico_icmp4.c b/modules/pico_icmp4.c index 9b7479757..a6ae62dcf 100644 --- a/modules/pico_icmp4.c +++ b/modules/pico_icmp4.c @@ -59,7 +59,6 @@ static int pico_icmp4_process_in(struct pico_protocol *self, struct pico_frame * if ((hdr->hun.ih_idseq.idseq_id == last_id) && (last_seq == hdr->hun.ih_idseq.idseq_seq)) { /* The network duplicated the echo. Do not reply. */ - printf("DUP!\n"); pico_frame_discard(f); return 0; } From de7384634b41f13b5542b66f21099b2d5d97e439 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2015 14:54:29 +0100 Subject: [PATCH 23/61] AODV: Disruption detection (#219) --- modules/pico_aodv.c | 36 ++++++++++++++++++++++++++++++++++++ modules/pico_aodv.h | 5 +++-- modules/pico_ipv4.c | 6 ++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index 85df845d1..22d382c33 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -529,6 +529,33 @@ static int aodv_send_req(struct pico_aodv_node *node) return n; } +static void pico_aodv_expired(struct pico_aodv_node *node) +{ + node->flags |= PICO_AODV_NODE_UNREACH; + node->flags &= (~PICO_AODV_NODE_ROUTE_UP); + node->flags &= (~PICO_AODV_NODE_ROUTE_DOWN); + pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); + node->ring_ttl = 0; + /* TODO: send err */ + +} + +static void pico_aodv_collector(pico_time now, void *arg) +{ + struct pico_tree_node *index; + struct pico_aodv_node *node; + (void)arg; + pico_tree_foreach(index, &aodv_nodes){ + node = index->keyValue; + if (PICO_AODV_ACTIVE(node)) { + uint32_t lifetime = aodv_lifetime(node); + if (lifetime == 0) + pico_aodv_expired(node); + } + } + pico_timer_add(AODV_HELLO_INTERVAL, pico_aodv_collector, NULL); +} + int pico_aodv_init(void) { struct pico_ip4 any = { .addr = 0u}; @@ -549,6 +576,7 @@ int pico_aodv_init(void) return -1; } pico_aodv_local_id = pico_rand(); + pico_timer_add(AODV_HELLO_INTERVAL, pico_aodv_collector, NULL); return 0; } @@ -558,6 +586,14 @@ int pico_aodv_add(struct pico_device *dev) return (pico_tree_insert(&aodv_devices, dev))?(0):(-1); } +void pico_aodv_refresh(const union pico_address *addr) +{ + struct pico_aodv_node *node = get_node_by_addr(addr); + if (node) { + node->last_seen = PICO_TIME_MS(); + } +} + int pico_aodv_lookup(const union pico_address *addr) { struct pico_aodv_node *node = get_node_by_addr(addr); diff --git a/modules/pico_aodv.h b/modules/pico_aodv.h index 211f4b930..e6c3fad7c 100644 --- a/modules/pico_aodv.h +++ b/modules/pico_aodv.h @@ -14,13 +14,13 @@ #define PICO_AODV_PORT (654) /* RFC3561 $10 */ -#define AODV_ACTIVE_ROUTE_TIMEOUT (5000u) /* Conservative value for link breakage detection */ +#define AODV_ACTIVE_ROUTE_TIMEOUT (8000u) /* Conservative value for link breakage detection */ #define AODV_DELETE_PERIOD (5 * AODV_ACTIVE_ROUTE_TIMEOUT) /* Recommended value K = 5 */ #define AODV_ALLOWED_HELLO_LOSS (4) /* conservative */ #define AODV_NET_DIAMETER ((uint8_t)(35)) #define AODV_RREQ_RETRIES (2) #define AODV_NODE_TRAVERSAL_TIME (40) -#define AODV_HELLO_INTERVAL (1) +#define AODV_HELLO_INTERVAL (1000) #define AODV_LOCAL_ADD_TTL 2 #define AODV_RREQ_RATELIMIT (10) #define AODV_TIMEOUT_BUFFER (2) @@ -127,4 +127,5 @@ PACKED_STRUCT_DEF pico_aodv_rack int pico_aodv_init(void); int pico_aodv_add(struct pico_device *dev); int pico_aodv_lookup(const union pico_address *addr); +void pico_aodv_refresh(const union pico_address *addr); #endif diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index 710192c5d..ee9a63d03 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -220,6 +220,11 @@ int pico_ipv4_is_valid_src(uint32_t address, struct pico_device *dev) return 0; } else { +#ifdef PICO_SUPPORT_AODV + union pico_address src; + src.ip4.addr = address; + pico_aodv_refresh(&src); +#endif return 1; } } @@ -697,6 +702,7 @@ static int pico_ipv4_process_in(struct pico_protocol *self, struct pico_frame *f #endif + /* ret == 1 indicates to continue the function */ ret = pico_ipv4_crc_check(f); if (ret < 1) From edb264eb635dc073eaa33262105f6bef60be2018 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2015 22:43:22 +0100 Subject: [PATCH 24/61] Fixed regression in unit tests --- Makefile | 2 +- modules/pico_aodv.c | 2 +- test/unit/unit_socket.c | 4 ++++ test/unit/unit_timer.c | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5ea506387..21d5d72e7 100644 --- a/Makefile +++ b/Makefile @@ -355,7 +355,7 @@ units: mod core lib $(UNITS_OBJ) $(MOD_OBJ) @echo -e "\t[CC] units.o" @$(CC) -c -o $(PREFIX)/test/units.o test/units.c $(CFLAGS) -I stack -I modules -I includes -I test/unit -DUNIT_TEST @echo -e "\t[LD] $(PREFIX)/test/units" - @$(CC) -o $(PREFIX)/test/units $(CFLAGS) $(PREFIX)/test/units.o -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/units $(CFLAGS) $(PREFIX)/test/units.o -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/modules/pico_aodv.o @$(CC) -o $(PREFIX)/test/modunit_pico_protocol.elf $(CFLAGS) -I. test/unit/modunit_pico_protocol.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) @$(CC) -o $(PREFIX)/test/modunit_pico_frame.elf $(CFLAGS) -I. test/unit/modunit_pico_frame.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) @$(CC) -o $(PREFIX)/test/modunit_seq.elf $(CFLAGS) -I. test/unit/modunit_seq.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index 22d382c33..b94d01ef2 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -556,7 +556,7 @@ static void pico_aodv_collector(pico_time now, void *arg) pico_timer_add(AODV_HELLO_INTERVAL, pico_aodv_collector, NULL); } -int pico_aodv_init(void) +MOCKABLE int pico_aodv_init(void) { struct pico_ip4 any = { .addr = 0u}; uint16_t port = short_be(PICO_AODV_PORT); diff --git a/test/unit/unit_socket.c b/test/unit/unit_socket.c index b1e737cc4..9f4cb77d8 100644 --- a/test/unit/unit_socket.c +++ b/test/unit/unit_socket.c @@ -1,4 +1,8 @@ +int pico_aodv_init(void) +{ + return 0; +} START_TEST (test_socket) { int ret = 0; diff --git a/test/unit/unit_timer.c b/test/unit/unit_timer.c index ce8893dff..24e3101f6 100644 --- a/test/unit/unit_timer.c +++ b/test/unit/unit_timer.c @@ -1,5 +1,6 @@ #define EXISTING_TIMERS 4 + START_TEST (test_timers) { struct pico_timer *T[128]; From 1514c59187c401efc6be09deff0fb46cdd66f3e7 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 23 Feb 2015 18:02:00 +0100 Subject: [PATCH 25/61] Added some unit tests for AODV (#219) --- Makefile | 1 + modules/pico_aodv.c | 4 +- modules/pico_ipv4.c | 4 +- modules/pico_olsr.c | 61 +++---- test/unit/modunit_pico_aodv.c | 302 ++++++++++++++++++++++++++++++++++ test/units.sh | 1 + 6 files changed, 340 insertions(+), 33 deletions(-) create mode 100644 test/unit/modunit_pico_aodv.c diff --git a/Makefile b/Makefile index 21d5d72e7..887314779 100644 --- a/Makefile +++ b/Makefile @@ -368,6 +368,7 @@ units: mod core lib $(UNITS_OBJ) $(MOD_OBJ) @$(CC) -o $(PREFIX)/test/modunit_tftp.elf $(CFLAGS) -I. test/unit/modunit_pico_tftp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a @$(CC) -o $(PREFIX)/test/modunit_sntp_client.elf $(CFLAGS) -I. test/unit/modunit_pico_sntp_client.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) @$(CC) -o $(PREFIX)/test/modunit_ipfilter.elf $(CFLAGS) -I. test/unit/modunit_pico_ipfilter.c stack/pico_tree.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) + @$(CC) -o $(PREFIX)/test/modunit_aodv.elf $(CFLAGS) -I. test/unit/modunit_pico_aodv.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a @$(CC) -o $(PREFIX)/test/modunit_queue.elf $(CFLAGS) -I. test/unit/modunit_queue.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) devunits: mod core lib diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index b94d01ef2..f60060fbc 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -86,9 +86,11 @@ static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); if (!gw) { pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(dev)); + printf("Added neighbor %08x\n", node->dest.ip4); node->metric = 1; } else { node->metric = metric; + printf("Added node %08x via %08x metric %d\n", node->dest.ip4, gw->ip4, metric); pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, gw->ip4, metric, NULL); } } @@ -343,7 +345,7 @@ static void aodv_parse_rerr(union pico_address *from, uint8_t *buf, int len, str (void)buf; (void)len; (void)msginfo; - /* TODO: invalidate routes... */ + /* TODO: invalidate routes. This only makes sense if we are using HELLO messages. */ } static void aodv_parse_rack(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index ee9a63d03..6aa89e2b4 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -1379,7 +1379,7 @@ static int pico_ipv4_frame_sock_push(struct pico_protocol *self, struct pico_fra } -int pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link) +int MOCKABLE pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link) { struct pico_ipv4_route test, *new; test.dest.addr = address.addr; @@ -1541,7 +1541,7 @@ static int pico_ipv4_cleanup_routes(struct pico_ipv4_link *link) return 0; } -void pico_ipv4_route_set_bcast_link(struct pico_ipv4_link *link) +void MOCKABLE pico_ipv4_route_set_bcast_link(struct pico_ipv4_link *link) { if (link) default_bcast_route.link = link; diff --git a/modules/pico_olsr.c b/modules/pico_olsr.c index dc1bd6ece..6feae50a2 100644 --- a/modules/pico_olsr.c +++ b/modules/pico_olsr.c @@ -15,6 +15,7 @@ #ifdef PICO_SUPPORT_OLSR #define DGRAM_MAX_SIZE (100 - 28) #define MAX_OLSR_MEM (4 * DGRAM_MAX_SIZE) +#define olsr_dbg(...) do{}while(0) int OOM(void); @@ -140,7 +141,7 @@ static struct olsr_route_entry *get_next_hop(struct olsr_route_entry *dst) { struct olsr_route_entry *hop = dst; while(hop) { - /* dbg("Finding next hop to %08x m=%d\n", hop->destination.addr, hop->metric); */ + /* olsr_dbg("Finding next hop to %08x m=%d\n", hop->destination.addr, hop->metric); */ if(hop->metric <= 1) return hop; @@ -165,7 +166,7 @@ static inline void olsr_route_add(struct olsr_route_entry *el) el->next = el->gateway->children; el->gateway->children = el; el->link_type = OLSRLINK_MPR; - dbg("[OLSR] ----------Adding route to %08x via %08x metric %d\n", el->destination.addr, nexthop->destination.addr, el->metric); + olsr_dbg("[OLSR] ----------Adding route to %08x via %08x metric %d\n", el->destination.addr, nexthop->destination.addr, el->metric); pico_ipv4_route_add(el->destination, HOST_NETMASK, nexthop->destination, (int) el->metric, NULL); } else if (el->iface) { /* neighbor */ @@ -181,7 +182,7 @@ static inline void olsr_route_add(struct olsr_route_entry *el) ei->children = el; } - dbg("[OLSR] ----------Adding neighbor %08x iface %s\n", el->destination.addr, el->iface->name); + olsr_dbg("[OLSR] ----------Adding neighbor %08x iface %s\n", el->destination.addr, el->iface->name); pico_ipv4_route_add(el->destination, HOST_NETMASK, no_gw, 1, pico_ipv4_link_by_dev(el->iface)); } @@ -190,7 +191,7 @@ static inline void olsr_route_add(struct olsr_route_entry *el) static inline void olsr_route_del(struct olsr_route_entry *r) { struct olsr_route_entry *cur, *prev = NULL, *lst; - /* dbg("[OLSR] DELETING route..................\n"); */ + /* olsr_dbg("[OLSR] DELETING route..................\n"); */ my_ansn++; if (r->gateway) { lst = r->gateway->children; @@ -206,7 +207,7 @@ static inline void olsr_route_del(struct olsr_route_entry *r) /* found */ if (r->gateway) { pico_ipv4_route_del(r->destination, HOST_NETMASK, r->metric); - dbg("[OLSR] Deleting route to %08x \n", r->destination.addr); + olsr_dbg("[OLSR] Deleting route to %08x \n", r->destination.addr); if (!prev) r->gateway->children = r->next; else @@ -253,7 +254,7 @@ static struct olsr_route_entry *get_route_by_address(struct olsr_route_entry *ls uint8_t seconds2olsr(uint32_t seconds) { uint16_t a, b; - /* dbg("seconds=%u\n", (uint16_t)seconds); */ + /* olsr_dbg("seconds=%u\n", (uint16_t)seconds); */ if (seconds > 32767) seconds = 32767; @@ -265,16 +266,16 @@ uint8_t seconds2olsr(uint32_t seconds) break; } } - /* dbg("b=%u", b); */ + /* olsr_dbg("b=%u", b); */ /* compute the expression 16*(T/(C*(2^b))-1), which may not be a integer, and round it up. This results in the value for 'a' */ /* a = (T / ( C * (1u << b) ) ) - 1u; */ { uint16_t den = ((uint16_t)(1u << b) >> 4u); - /* dbg(" den=%u ", den); */ + /* olsr_dbg(" den=%u ", den); */ if (den == 0) { - /* dbg("div by 0!\n"); */ + /* olsr_dbg("div by 0!\n"); */ den = 1u; } @@ -282,7 +283,7 @@ uint8_t seconds2olsr(uint32_t seconds) } /* a = a & 0x0Fu; */ - /* dbg(" a=%u\n", a); */ + /* olsr_dbg(" a=%u\n", a); */ /* if 'a' is equal to 16: increment 'b' by one, and set 'a' to 0 */ if (16u == a) { @@ -297,16 +298,16 @@ uint32_t olsr2seconds(uint8_t olsr) { uint8_t a, b; uint16_t seconds; - /* dbg("olsr format: %u -- ", olsr); */ + /* olsr_dbg("olsr format: %u -- ", olsr); */ a = (olsr >> 4) & 0xFu; b = olsr & 0x0f; - /* dbg("o2s: a=%u, b=%u\n", a,b); */ + /* olsr_dbg("o2s: a=%u, b=%u\n", a,b); */ if (b < 4) seconds = (uint16_t)(((1u << b) + (uint16_t)(((uint16_t)(a << b) >> 4u) & 0xFu)) >> OLSR_C_SHIFT); else seconds = (uint16_t)(((1u << b) + (uint16_t)(((uint16_t)(a << (b - 4))) & 0xFu)) >> OLSR_C_SHIFT); - /* dbg("o2s: seconds: %u\n", seconds); */ + /* olsr_dbg("o2s: seconds: %u\n", seconds); */ return seconds; } @@ -363,7 +364,7 @@ void olsr_process_out(pico_time now, void *arg) ohdr->seq = short_be((uint16_t)(odev->pkt_counter)++); bcast.addr = (addr->netmask.addr & addr->address.addr) | (~addr->netmask.addr); if ( 0 > pico_socket_sendto(udpsock, p->buf, p->len, &bcast, OLSR_PORT)) { - dbg("olsr send\n"); + olsr_dbg("olsr send\n"); } } else { while(pdev) { @@ -374,7 +375,7 @@ void olsr_process_out(pico_time now, void *arg) bcast.addr = (addr->netmask.addr & addr->address.addr) | (~addr->netmask.addr); if ( 0 > pico_socket_sendto(udpsock, p->buf, p->len, &bcast, OLSR_PORT)) { - dbg("olsr send\n"); + olsr_dbg("olsr send\n"); } pdev = pdev->next; @@ -390,7 +391,7 @@ void olsr_process_out(pico_time now, void *arg) static void olsr_scheduled_output(uint32_t when, void *buffer, uint16_t size, struct pico_device *pdev) { struct olsr_fwd_pkt *p; - /* dbg("Scheduling olsr packet, type:%s, size: %x\n", when == OLSR_HELLO_INTERVAL?"HELLO":"TC", size); */ + /* olsr_dbg("Scheduling olsr packet, type:%s, size: %x\n", when == OLSR_HELLO_INTERVAL?"HELLO":"TC", size); */ if ((buffer_mem_used + DGRAM_MAX_SIZE) > MAX_OLSR_MEM) { PICO_FREE(buffer); return; @@ -437,7 +438,7 @@ static void refresh_routes(void) } else if (lnk) { struct olsr_route_entry *e = PICO_ZALLOC(sizeof (struct olsr_route_entry)); if (!e) { - dbg("olsr: adding local route entry\n"); + olsr_dbg("olsr: adding local route entry\n"); OOM(); return; } @@ -657,7 +658,7 @@ static void olsr_compose_hello_dgram(struct pico_device *pdev, struct pico_ipv4_ if (DGRAM_MAX_SIZE > size) { r = olsr_build_hello_neighbors(dgram + size, DGRAM_MAX_SIZE - size, &last_neighbor); if (r == 0) { - /* dbg("Building hello message\n"); */ + /* olsr_dbg("Building hello message\n"); */ PICO_FREE(dgram); return; } @@ -703,7 +704,7 @@ void recv_mid(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin) if (!e) { e = PICO_ZALLOC(sizeof(struct olsr_route_entry)); if (!e) { - dbg("olsr allocating route\n"); + olsr_dbg("olsr allocating route\n"); OOM(); return; } @@ -757,7 +758,7 @@ void recv_hello(uint8_t *buffer, uint32_t len, struct olsr_route_entry *origin, if (!e) { e = PICO_ZALLOC(sizeof(struct olsr_route_entry)); if (!e) { - dbg("olsr allocating route\n"); + olsr_dbg("olsr allocating route\n"); OOM(); return; } @@ -812,7 +813,7 @@ uint32_t reconsider_topology(uint8_t *buf, uint32_t size, struct olsr_route_entr e->advertised_tc = PICO_ZALLOC(size); if (!e->advertised_tc) { OOM(); - dbg("Allocating forward packet\n"); + olsr_dbg("Allocating forward packet\n"); return 0; } @@ -848,7 +849,7 @@ uint32_t reconsider_topology(uint8_t *buf, uint32_t size, struct olsr_route_entr olsr_route_add(rt); } } - /* dbg("Routes changed...\n"); */ + /* olsr_dbg("Routes changed...\n"); */ } return retval; @@ -889,7 +890,7 @@ static void olsr_recv(uint8_t *buffer, uint32_t len) origin = get_route_by_address(Local_interfaces, msg->orig.addr); if(pico_ipv4_link_find(&msg->orig) != NULL) { - /* dbg("rebound\n"); */ + /* olsr_dbg("rebound\n"); */ parsed += short_be(msg->size); continue; } @@ -947,7 +948,7 @@ static void olsr_recv(uint8_t *buffer, uint32_t len) msg->ttl = 0; } else { recv_mid(buffer + parsed + sizeof(struct olsrmsg), (uint32_t)(short_be(msg->size) - (sizeof(struct olsrmsg))), origin); - /* dbg("MID forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */ + /* olsr_dbg("MID forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */ origin->seq = short_be(msg->seq); } @@ -958,7 +959,7 @@ static void olsr_recv(uint8_t *buffer, uint32_t len) if ((origin->seq != 0) && (!fresher(short_be(msg->seq), origin->seq))) { msg->ttl = 0; } else { - /* dbg("TC forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */ + /* olsr_dbg("TC forwarded from origin %08x (seq: %u)\n", long_be(msg->orig.addr), short_be(msg->seq)); */ origin->seq = short_be(msg->seq); } } @@ -1055,7 +1056,7 @@ void pico_olsr_init(void) 0 }; uint16_t port = OLSR_PORT; - dbg("OLSR initialized.\n"); + olsr_dbg("OLSR initialized.\n"); if (!udpsock) { udpsock = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &wakeup); if (udpsock) @@ -1096,7 +1097,7 @@ int pico_olsr_add(struct pico_device *dev) return -1; } - /* dbg("OLSR: Adding device %s\n", dev->name); */ + /* olsr_dbg("OLSR: Adding device %s\n", dev->name); */ od = PICO_ZALLOC(sizeof(struct olsr_dev_entry)); if (!od) { pico_err = PICO_ERR_ENOMEM; @@ -1113,11 +1114,11 @@ int pico_olsr_add(struct pico_device *dev) lnk = pico_ipv4_link_by_dev_next(dev, lnk); if (lnk) { struct olsr_route_entry *e = PICO_ZALLOC(sizeof(struct olsr_route_entry)); - /* dbg("OLSR: Found IP address %08x\n", long_be(lnk->address.addr)); */ + /* olsr_dbg("OLSR: Found IP address %08x\n", long_be(lnk->address.addr)); */ pico_ipv4_to_string(ipaddr, (lnk->address.addr)); - /* dbg("OLSR: Found IP address %s\n", ipaddr); */ + /* olsr_dbg("OLSR: Found IP address %s\n", ipaddr); */ if (!e) { - dbg("olsr allocating route\n"); + olsr_dbg("olsr allocating route\n"); pico_err = PICO_ERR_ENOMEM; return -1; } diff --git a/test/unit/modunit_pico_aodv.c b/test/unit/modunit_pico_aodv.c new file mode 100644 index 000000000..5c3fe1085 --- /dev/null +++ b/test/unit/modunit_pico_aodv.c @@ -0,0 +1,302 @@ +#include +#include +#include +#include +#include +#include +#include "modules/pico_aodv.c" +#include "check.h" + + +START_TEST(tc_aodv_node_compare) +{ + struct pico_aodv_node a, b; + a.dest.ip4.addr = long_be(1); + b.dest.ip4.addr = long_be(2); + + fail_if(aodv_node_compare(&a, &b) >= 0); + a.dest.ip4.addr = long_be(3); + fail_if(aodv_node_compare(&a, &b) <= 0); + b.dest.ip4.addr = long_be(3); + fail_if(aodv_node_compare(&a, &b) != 0); +} +END_TEST + +START_TEST(tc_aodv_dev_cmp) +{ + struct pico_device a, b; + a.hash = 1; + b.hash = 2; + fail_if(aodv_dev_cmp(&a, &b) >= 0); + a.hash = 3; + fail_if(aodv_dev_cmp(&a, &b) <= 0); + b.hash = 3; + fail_if(aodv_dev_cmp(&a, &b) != 0); + +} +END_TEST + +START_TEST(tc_get_node_by_addr) +{ + struct pico_aodv_node a; + union pico_address test; + a.dest.ip4.addr = long_be(10); + test.ip4.addr = long_be(10); + + pico_tree_insert(&aodv_nodes, &a); + + fail_if(get_node_by_addr(&test) != &a); + pico_tree_delete(&aodv_nodes, &a); + fail_if(get_node_by_addr(&test) != NULL); + +} +END_TEST + +static int set_bcast_link_called = 0; +void pico_ipv4_route_set_bcast_link(struct pico_ipv4_link *link) +{ + fail_if(link); + set_bcast_link_called++; +} + +START_TEST(tc_pico_aodv_set_dev) +{ + struct pico_device *dev = NULL; + pico_aodv_set_dev(dev); + fail_if(set_bcast_link_called != 1); +} +END_TEST + +START_TEST(tc_aodv_peer_refresh) +{ + /* TODO: test this: static int aodv_peer_refresh(struct pico_aodv_node *node, uint32_t seq) */ + struct pico_aodv_node node; + memset(&node, 0, sizeof(node)); + node.dseq = 0xFFFF; + fail_if(aodv_peer_refresh(&node, 10) != 0); /* should succeed, because SYNC flag is not yet set... */ + fail_if(node.flags & PICO_AODV_NODE_SYNC == 0); /* Flag should be set after last call... */ + fail_if(aodv_peer_refresh(&node, 5) == 0); /* should FAIL, because seq number is lower... */ + fail_if(aodv_peer_refresh(&node, 10) == 0); /* should FAIL, because seq number is still the same... */ + fail_if(aodv_peer_refresh(&node, 15) != 0); /* should succeed, because seq number is now bigger... */ + fail_if(node.dseq != 15); +} +END_TEST + +static int called_route_add = 0; +static uint32_t route_add_gw = 0u; +static int route_add_metric = 0; +int pico_ipv4_route_add(struct pico_ip4 address, struct pico_ip4 netmask, struct pico_ip4 gateway, int metric, struct pico_ipv4_link *link) +{ + called_route_add++; + route_add_gw = gateway.addr; + route_add_metric = metric; + return 0; +} + +START_TEST(tc_aodv_elect_route) +{ + struct pico_aodv_node node; + union pico_address gateway; + memset(&node, 0, sizeof(node)); + gateway.ip4.addr = 0x55555555; + + called_route_add = 0; + aodv_elect_route(&node, NULL, 150, NULL); + fail_if(called_route_add != 1); /* Not active, should succeed */ + fail_if(route_add_gw != 0u); + fail_if(route_add_metric != 1); + + called_route_add = 0; + route_add_metric = 0; + route_add_gw = 0u; + node.flags = PICO_AODV_NODE_ROUTE_DOWN | PICO_AODV_NODE_ROUTE_UP; + aodv_elect_route(&node, &gateway, 150, NULL); + fail_if(called_route_add != 0); /* Already active, existing metric is lower */ + + called_route_add = 0; + route_add_metric = 0; + route_add_gw = 0u; + node.metric = 22; + aodv_elect_route(&node, &gateway, 15, NULL); + fail_if(called_route_add != 1); /* Already active, existing metric is higher */ + fail_if(route_add_metric != 16); + fail_if(route_add_gw != 0x55555555); + +} +END_TEST + +START_TEST(tc_aodv_peer_new) +{ + /* TODO: test this: static struct pico_aodv_node *aodv_peer_new(const union pico_address *addr) */ +} +END_TEST +START_TEST(tc_aodv_peer_eval) +{ + /* TODO: test this: static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq, int valid_seq) */ +} +END_TEST +START_TEST(tc_aodv_lifetime) +{ + /* TODO: test this: static uint32_t aodv_lifetime(struct pico_aodv_node *node) */ +} +END_TEST +START_TEST(tc_aodv_send_reply) +{ + /* TODO: test this: static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq *req, int node_is_local, struct pico_msginfo *info) */ +} +END_TEST +START_TEST(tc_aodv_send_req) +{ + /* TODO: test this: static int aodv_send_req(struct pico_aodv_node *node); */ +} +END_TEST +START_TEST(tc_aodv_reverse_path_discover) +{ + /* TODO: test this: static void aodv_reverse_path_discover(pico_time now, void *arg) */ +} +END_TEST +START_TEST(tc_aodv_recv_valid_rreq) +{ + /* TODO: test this: static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req, struct pico_msginfo *info) */ +} +END_TEST +START_TEST(tc_aodv_parse_rreq) +{ + /* TODO: test this: static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ +} +END_TEST +START_TEST(tc_aodv_parse_rrep) +{ + /* TODO: test this: static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ +} +END_TEST +START_TEST(tc_aodv_parse_rerr) +{ + /* TODO: test this: static void aodv_parse_rerr(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ +} +END_TEST +START_TEST(tc_aodv_parse_rack) +{ + /* TODO: test this: static void aodv_parse_rack(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ +} +END_TEST +START_TEST(tc_pico_aodv_parse) +{ + /* TODO: test this: static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ +} +END_TEST +START_TEST(tc_pico_aodv_socket_callback) +{ + /* TODO: test this: static void pico_aodv_socket_callback(uint16_t ev, struct pico_socket *s) */ +} +END_TEST +START_TEST(tc_aodv_make_rreq) +{ + /* TODO: test this: static void aodv_make_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req) */ +} +END_TEST +START_TEST(tc_aodv_retrans_rreq) +{ + /* TODO: test this: static void aodv_retrans_rreq(pico_time now, void *arg) */ +} +END_TEST +START_TEST(tc_pico_aodv_expired) +{ + /* TODO: test this: static void pico_aodv_expired(struct pico_aodv_node *node) */ +} +END_TEST +START_TEST(tc_pico_aodv_collector) +{ + /* TODO: test this: static void pico_aodv_collector(pico_time now, void *arg) */ +} +END_TEST + + +Suite *pico_suite(void) +{ + Suite *s = suite_create("PicoTCP"); + + TCase *TCase_aodv_node_compare = tcase_create("Unit test for aodv_node_compare"); + TCase *TCase_aodv_dev_cmp = tcase_create("Unit test for aodv_dev_cmp"); + TCase *TCase_get_node_by_addr = tcase_create("Unit test for get_node_by_addr"); + TCase *TCase_pico_aodv_set_dev = tcase_create("Unit test for pico_aodv_set_dev"); + TCase *TCase_aodv_peer_refresh = tcase_create("Unit test for aodv_peer_refresh"); + TCase *TCase_aodv_elect_route = tcase_create("Unit test for aodv_elect_route"); + TCase *TCase_aodv_peer_new = tcase_create("Unit test for aodv_peer_new"); + TCase *TCase_aodv_peer_eval = tcase_create("Unit test for aodv_peer_eval"); + TCase *TCase_aodv_lifetime = tcase_create("Unit test for aodv_lifetime"); + TCase *TCase_aodv_send_reply = tcase_create("Unit test for aodv_send_reply"); + TCase *TCase_aodv_send_req = tcase_create("Unit test for aodv_send_req"); + TCase *TCase_aodv_reverse_path_discover = tcase_create("Unit test for aodv_reverse_path_discover"); + TCase *TCase_aodv_recv_valid_rreq = tcase_create("Unit test for aodv_recv_valid_rreq"); + TCase *TCase_aodv_parse_rreq = tcase_create("Unit test for aodv_parse_rreq"); + TCase *TCase_aodv_parse_rrep = tcase_create("Unit test for aodv_parse_rrep"); + TCase *TCase_aodv_parse_rerr = tcase_create("Unit test for aodv_parse_rerr"); + TCase *TCase_aodv_parse_rack = tcase_create("Unit test for aodv_parse_rack"); + TCase *TCase_pico_aodv_parse = tcase_create("Unit test for pico_aodv_parse"); + TCase *TCase_pico_aodv_socket_callback = tcase_create("Unit test for pico_aodv_socket_callback"); + TCase *TCase_aodv_make_rreq = tcase_create("Unit test for aodv_make_rreq"); + TCase *TCase_aodv_retrans_rreq = tcase_create("Unit test for aodv_retrans_rreq"); + TCase *TCase_pico_aodv_expired = tcase_create("Unit test for pico_aodv_expired"); + TCase *TCase_pico_aodv_collector = tcase_create("Unit test for pico_aodv_collector"); + + + tcase_add_test(TCase_aodv_node_compare, tc_aodv_node_compare); + suite_add_tcase(s, TCase_aodv_node_compare); + tcase_add_test(TCase_aodv_dev_cmp, tc_aodv_dev_cmp); + suite_add_tcase(s, TCase_aodv_dev_cmp); + tcase_add_test(TCase_get_node_by_addr, tc_get_node_by_addr); + suite_add_tcase(s, TCase_get_node_by_addr); + tcase_add_test(TCase_pico_aodv_set_dev, tc_pico_aodv_set_dev); + suite_add_tcase(s, TCase_pico_aodv_set_dev); + tcase_add_test(TCase_aodv_peer_refresh, tc_aodv_peer_refresh); + suite_add_tcase(s, TCase_aodv_peer_refresh); + tcase_add_test(TCase_aodv_elect_route, tc_aodv_elect_route); + suite_add_tcase(s, TCase_aodv_elect_route); + tcase_add_test(TCase_aodv_peer_new, tc_aodv_peer_new); + suite_add_tcase(s, TCase_aodv_peer_new); + tcase_add_test(TCase_aodv_peer_eval, tc_aodv_peer_eval); + suite_add_tcase(s, TCase_aodv_peer_eval); + tcase_add_test(TCase_aodv_lifetime, tc_aodv_lifetime); + suite_add_tcase(s, TCase_aodv_lifetime); + tcase_add_test(TCase_aodv_send_reply, tc_aodv_send_reply); + suite_add_tcase(s, TCase_aodv_send_reply); + tcase_add_test(TCase_aodv_send_req, tc_aodv_send_req); + suite_add_tcase(s, TCase_aodv_send_req); + tcase_add_test(TCase_aodv_reverse_path_discover, tc_aodv_reverse_path_discover); + suite_add_tcase(s, TCase_aodv_reverse_path_discover); + tcase_add_test(TCase_aodv_recv_valid_rreq, tc_aodv_recv_valid_rreq); + suite_add_tcase(s, TCase_aodv_recv_valid_rreq); + tcase_add_test(TCase_aodv_parse_rreq, tc_aodv_parse_rreq); + suite_add_tcase(s, TCase_aodv_parse_rreq); + tcase_add_test(TCase_aodv_parse_rrep, tc_aodv_parse_rrep); + suite_add_tcase(s, TCase_aodv_parse_rrep); + tcase_add_test(TCase_aodv_parse_rerr, tc_aodv_parse_rerr); + suite_add_tcase(s, TCase_aodv_parse_rerr); + tcase_add_test(TCase_aodv_parse_rack, tc_aodv_parse_rack); + suite_add_tcase(s, TCase_aodv_parse_rack); + tcase_add_test(TCase_pico_aodv_parse, tc_pico_aodv_parse); + suite_add_tcase(s, TCase_pico_aodv_parse); + tcase_add_test(TCase_pico_aodv_socket_callback, tc_pico_aodv_socket_callback); + suite_add_tcase(s, TCase_pico_aodv_socket_callback); + tcase_add_test(TCase_aodv_make_rreq, tc_aodv_make_rreq); + suite_add_tcase(s, TCase_aodv_make_rreq); + tcase_add_test(TCase_aodv_retrans_rreq, tc_aodv_retrans_rreq); + suite_add_tcase(s, TCase_aodv_retrans_rreq); + tcase_add_test(TCase_pico_aodv_expired, tc_pico_aodv_expired); + suite_add_tcase(s, TCase_pico_aodv_expired); + tcase_add_test(TCase_pico_aodv_collector, tc_pico_aodv_collector); + suite_add_tcase(s, TCase_pico_aodv_collector); +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.sh b/test/units.sh index 93e1e17c7..e8fbfbfe2 100755 --- a/test/units.sh +++ b/test/units.sh @@ -15,6 +15,7 @@ rm -f /tmp/pico-mem-report-* ./build/test/modunit_ipfilter.elf || exit 1 ./build/test/modunit_queue.elf || exit 1 ./build/test/modunit_tftp.elf || exit 1 +./build/test/modunit_aodv.elf || exit 1 MAXMEM=`cat /tmp/pico-mem-report-* | sort -r -n |head -1` echo From 80794ea64d41fa0f347668b62a0232e2466d9604 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 23 Feb 2015 18:36:06 +0100 Subject: [PATCH 26/61] Added some unit tests, fixed some warnings (#219) --- modules/pico_aodv.c | 11 ++++---- test/unit/modunit_pico_aodv.c | 49 ++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index f60060fbc..b22dc77b2 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -86,11 +86,9 @@ static void aodv_elect_route(struct pico_aodv_node *node, union pico_address *gw pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); if (!gw) { pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, ANY_HOST, 1, pico_ipv4_link_by_dev(dev)); - printf("Added neighbor %08x\n", node->dest.ip4); node->metric = 1; } else { node->metric = metric; - printf("Added node %08x via %08x metric %d\n", node->dest.ip4, gw->ip4, metric); pico_ipv4_route_add(node->dest.ip4, HOST_NETMASK, gw->ip4, metric, NULL); } } @@ -462,7 +460,7 @@ static void aodv_retrans_rreq(pico_time now, void *arg) node->rreq_retry = 0; node->ring_ttl = 0; pico_aodv_dbg("Node is unreachable.\n"); - node->flags &= (~PICO_AODV_NODE_ROUTE_DOWN); + node->flags &= (uint16_t)(~PICO_AODV_NODE_ROUTE_DOWN); return; } @@ -483,7 +481,7 @@ static void aodv_retrans_rreq(pico_time now, void *arg) } } if (node->ring_ttl < AODV_NET_DIAMETER) - node->ring_ttl += AODV_TTL_INCREMENT; + node->ring_ttl = (uint8_t)(node->ring_ttl + AODV_TTL_INCREMENT); pico_timer_add((pico_time)AODV_RING_TRAVERSAL_TIME(node->ring_ttl), aodv_retrans_rreq, node); } @@ -534,8 +532,8 @@ static int aodv_send_req(struct pico_aodv_node *node) static void pico_aodv_expired(struct pico_aodv_node *node) { node->flags |= PICO_AODV_NODE_UNREACH; - node->flags &= (~PICO_AODV_NODE_ROUTE_UP); - node->flags &= (~PICO_AODV_NODE_ROUTE_DOWN); + node->flags &= (uint8_t)(~PICO_AODV_NODE_ROUTE_UP); + node->flags &= (uint8_t)(~PICO_AODV_NODE_ROUTE_DOWN); pico_ipv4_route_del(node->dest.ip4, HOST_NETMASK, node->metric); node->ring_ttl = 0; /* TODO: send err */ @@ -547,6 +545,7 @@ static void pico_aodv_collector(pico_time now, void *arg) struct pico_tree_node *index; struct pico_aodv_node *node; (void)arg; + (void)now; pico_tree_foreach(index, &aodv_nodes){ node = index->keyValue; if (PICO_AODV_ACTIVE(node)) { diff --git a/test/unit/modunit_pico_aodv.c b/test/unit/modunit_pico_aodv.c index 5c3fe1085..49675e508 100644 --- a/test/unit/modunit_pico_aodv.c +++ b/test/unit/modunit_pico_aodv.c @@ -127,19 +127,62 @@ END_TEST START_TEST(tc_aodv_peer_new) { - /* TODO: test this: static struct pico_aodv_node *aodv_peer_new(const union pico_address *addr) */ + union pico_address addr; + struct pico_aodv_node *new; + addr.ip4.addr = 0x44444444; + new = aodv_peer_new(&addr); + fail_if(!new); + fail_if(!get_node_by_addr(&addr)); + pico_set_mm_failure(1); + new = aodv_peer_new(&addr); + fail_if(new); } END_TEST START_TEST(tc_aodv_peer_eval) { - /* TODO: test this: static struct pico_aodv_node *aodv_peer_eval(union pico_address *addr, uint32_t seq, int valid_seq) */ + union pico_address addr; + struct pico_aodv_node *node = NULL; + /* Case 0: Creation */ + addr.ip4.addr = 0x11224433; + node = aodv_peer_eval(&addr, 0, 0); + fail_if(!node); + fail_if((node->flags & PICO_AODV_NODE_SYNC) != 0); /* Not synced! */ + + /* Case 1: retrieve, unsynced */ + node->metric = 42; + node = aodv_peer_eval(&addr, 0, 0); /* Should get existing node! */ + fail_if(!node); + fail_if(node->metric != 42); + fail_if((node->flags & PICO_AODV_NODE_SYNC) != 0); /* Not synced! */ + + + /* Case 2: new node, invalid allocation */ + addr.ip4.addr = 0x11224455; + pico_set_mm_failure(1); + node = aodv_peer_eval(&addr, long_be(10), 1); + fail_if(node); + + /* Case 3: existing node, setting the new sequence */ + addr.ip4.addr = 0x11224433; + node = aodv_peer_eval(&addr, long_be(10), 1); /* Should get existing node! */ + fail_if(node->metric != 42); + fail_if((node->flags & PICO_AODV_NODE_SYNC) == 0); + fail_if(node->dseq!= 10); } END_TEST + START_TEST(tc_aodv_lifetime) { - /* TODO: test this: static uint32_t aodv_lifetime(struct pico_aodv_node *node) */ + struct pico_aodv_node node; + pico_time now = PICO_TIME_MS(); + memset(&node, 0, sizeof(node)); + fail_if(aodv_lifetime(&node) == 0); + fail_if(node.last_seen < now); + node.last_seen = now - AODV_ACTIVE_ROUTE_TIMEOUT; + fail_if(aodv_lifetime(&node) != 0); } END_TEST + START_TEST(tc_aodv_send_reply) { /* TODO: test this: static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq *req, int node_is_local, struct pico_msginfo *info) */ From b96b832fe18231186610d37749cf24f71c7bb390 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 24 Feb 2015 12:01:55 +0100 Subject: [PATCH 27/61] AODV: added more unit tests. #219 --- modules/pico_aodv.c | 6 +- modules/pico_ipv4.c | 4 +- stack/pico_socket.c | 2 +- test/unit/modunit_pico_aodv.c | 146 +++++++++++++++++++++++++++++++++- 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index b22dc77b2..a90f068cc 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -193,10 +193,10 @@ static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq * reply.dest = req->dest; reply.dseq = req->dseq; reply.orig = req->orig; - reply.hop_count = (uint8_t)(orig->metric - 1u); - if (!orig) return; + reply.hop_count = (uint8_t)(orig->metric - 1u); + dest.ip4.addr = 0xFFFFFFFF; /* wide broadcast */ @@ -515,7 +515,7 @@ static int aodv_send_req(struct pico_aodv_node *node) } aodv_make_rreq(node, &rreq); - pico_tree_foreach(index, &aodv_devices){ + pico_tree_foreach(index, &aodv_devices) { dev = index->keyValue; pico_aodv_set_dev(dev); ip4l = pico_ipv4_link_by_dev(dev); diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index 6aa89e2b4..b0c65364b 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -1609,7 +1609,7 @@ struct pico_ipv4_link *pico_ipv4_link_get(struct pico_ip4 *address) return found; } -struct pico_ipv4_link *pico_ipv4_link_by_dev(struct pico_device *dev) +struct pico_ipv4_link * MOCKABLE pico_ipv4_link_by_dev(struct pico_device *dev) { struct pico_tree_node *index = NULL; struct pico_ipv4_link *link = NULL; @@ -1645,7 +1645,7 @@ struct pico_ipv4_link *pico_ipv4_link_by_dev_next(struct pico_device *dev, struc return NULL; } -struct pico_device *pico_ipv4_link_find(struct pico_ip4 *address) +struct pico_device * MOCKABLE pico_ipv4_link_find(struct pico_ip4 *address) { struct pico_ipv4_link test, *found; if(!address) { diff --git a/stack/pico_socket.c b/stack/pico_socket.c index 5ef7e2d8c..6b1edd742 100644 --- a/stack/pico_socket.c +++ b/stack/pico_socket.c @@ -1268,7 +1268,7 @@ static void pico_socket_sendto_set_dport(struct pico_socket *s, uint16_t port) } -int pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len, +int MOCKABLE pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len, void *dst, uint16_t remote_port, struct pico_msginfo *msginfo) { struct pico_remote_endpoint *remote_endpoint = NULL; diff --git a/test/unit/modunit_pico_aodv.c b/test/unit/modunit_pico_aodv.c index 49675e508..8b228e4dc 100644 --- a/test/unit/modunit_pico_aodv.c +++ b/test/unit/modunit_pico_aodv.c @@ -55,7 +55,6 @@ END_TEST static int set_bcast_link_called = 0; void pico_ipv4_route_set_bcast_link(struct pico_ipv4_link *link) { - fail_if(link); set_bcast_link_called++; } @@ -183,71 +182,212 @@ START_TEST(tc_aodv_lifetime) } END_TEST +static uint8_t sent_pkt_type = 0xFF; +static uint32_t dest_addr = 0; +static int pico_socket_sendto_called = 0; +static int pico_socket_sendto_extended_called = 0; +uint32_t expected_dseq = 0; +int pico_socket_sendto(struct pico_socket *s, const void *buf, const int len, void *dst, uint16_t remote_port) +{ + uint8_t *pkt = (uint8_t *)buf; + printf("Sendto called!\n"); + pico_socket_sendto_called++; + fail_if(remote_port != short_be(PICO_AODV_PORT)); + fail_if (s != aodv_socket); + fail_if(pkt[0] > 4); + fail_if(pkt[0] < 1); + sent_pkt_type = pkt[0]; + dest_addr = ((union pico_address *)dst)->ip4.addr; + if (sent_pkt_type == AODV_TYPE_RREQ) { + struct pico_aodv_rreq *req = (struct pico_aodv_rreq *)buf; + fail_if(len != sizeof(struct pico_aodv_rreq)); + } + else if (sent_pkt_type == AODV_TYPE_RREP) { + struct pico_aodv_rrep *rep = (struct pico_aodv_rrep *)buf; + fail_if(len != sizeof(struct pico_aodv_rrep)); + fail_if(rep->dest != 0x11111111); + fail_if(rep->orig != 0x22222222); + printf("rep->dseq= %08x, exp: %08x\n", rep->dseq, expected_dseq); + fail_if(rep->dseq != expected_dseq); + } + return len; +} + +int pico_socket_sendto_extended(struct pico_socket *s, const void *buf, const int len, + void *dst, uint16_t remote_port, struct pico_msginfo *msginfo) +{ + pico_socket_sendto_extended_called++; + return pico_socket_sendto(s, buf, len, dst, remote_port); +} + START_TEST(tc_aodv_send_reply) { - /* TODO: test this: static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq *req, int node_is_local, struct pico_msginfo *info) */ + struct pico_aodv_node node; + struct pico_aodv_rreq req; + struct pico_msginfo info; + union pico_address addr; + addr.ip4.addr = 0x22222222; + memset(&node, 0, sizeof(node)); + memset(&req, 0, sizeof(req)); + memset(&info, 0, sizeof(info)); + + req.dest = 0x11111111; + req.orig = addr.ip4.addr; + req.dseq = 99; + + aodv_send_reply(&node, &req, 1, &info); + fail_if(pico_socket_sendto_called != 0); /* Call should have no effect, due to non-existing origin node */ + + /* Creating origin... */ + fail_if(aodv_peer_new(&addr) == NULL); + aodv_send_reply(&node, &req, 0, &info); + fail_if(pico_socket_sendto_called != 0); /* Call should have no effect, node non-local, non sync'd */ + + expected_dseq = long_be(pico_aodv_local_id + 1); + aodv_send_reply(&node, &req, 1, &info); + fail_if(pico_socket_sendto_called != 1); /* Call should succeed */ + pico_socket_sendto_called = 0; + + node.flags = PICO_AODV_NODE_SYNC; + node.dseq = 42; + expected_dseq = long_be(42); + aodv_send_reply(&node, &req, 0, &info); + fail_if(pico_socket_sendto_called != 1); /* Call should succeed */ + pico_socket_sendto_called = 0; } END_TEST + +static struct pico_ipv4_link global_link; +struct pico_ipv4_link *pico_ipv4_link_by_dev(struct pico_device *dev) +{ + if (!global_link.address.addr) + return NULL; + printf("Setting link!\n"); + return &global_link; +} + +static int timer_set = 0; +struct pico_timer *pico_timer_add(pico_time expire, void (*timer)(pico_time, void *), void *arg) +{ + printf("Timer set!\n"); + timer_set++; + return (struct pico_timer *) 0x99999999; + +} + START_TEST(tc_aodv_send_req) { - /* TODO: test this: static int aodv_send_req(struct pico_aodv_node *node); */ + struct pico_aodv_node node; + struct pico_device d; + aodv_socket = NULL; + + memset(&node, 0, sizeof(node)); + node.flags = PICO_AODV_NODE_ROUTE_DOWN | PICO_AODV_NODE_ROUTE_UP; + fail_if(aodv_send_req(&node) != 0); /* Should fail: node already active */ + fail_if(pico_socket_sendto_called != 0); + fail_if(pico_socket_sendto_extended_called != 0); + + node.flags = 0; + fail_if(aodv_send_req(&node) != 0); /* Should fail: no devices in tree */ + fail_if(pico_socket_sendto_called != 0); + fail_if(pico_socket_sendto_extended_called != 0); + + pico_tree_insert(&aodv_devices, &d); + fail_if(aodv_send_req(&node) != -1); /* Should fail: aodv_socket == NULL */ + fail_if(pico_err != PICO_ERR_EINVAL); + fail_if(pico_socket_sendto_called != 0); + fail_if(pico_socket_sendto_extended_called != 0); + + + /* No valid link, timer is set, call does not send packets */ + aodv_socket = 1; + global_link.address.addr = 0; + fail_if(aodv_send_req(&node) != 0); + fail_if(pico_socket_sendto_called != 0); + fail_if(pico_socket_sendto_extended_called != 0); + fail_if(timer_set != 1); + timer_set = 0; + + + /* One valid link, timer is set, one packet is sent */ + global_link.address.addr = 0xFEFEFEFE; + fail_if(aodv_send_req(&node) != 1); + fail_if(pico_socket_sendto_called != 1); + fail_if(pico_socket_sendto_extended_called != 1); + fail_if(timer_set != 1); + pico_socket_sendto_called = 0; + pico_socket_sendto_extended_called = 0; + timer_set = 0; + } END_TEST + START_TEST(tc_aodv_reverse_path_discover) { /* TODO: test this: static void aodv_reverse_path_discover(pico_time now, void *arg) */ } END_TEST + START_TEST(tc_aodv_recv_valid_rreq) { /* TODO: test this: static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req, struct pico_msginfo *info) */ } END_TEST + START_TEST(tc_aodv_parse_rreq) { /* TODO: test this: static void aodv_parse_rreq(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ } END_TEST + START_TEST(tc_aodv_parse_rrep) { /* TODO: test this: static void aodv_parse_rrep(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ } END_TEST + START_TEST(tc_aodv_parse_rerr) { /* TODO: test this: static void aodv_parse_rerr(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ } END_TEST + START_TEST(tc_aodv_parse_rack) { /* TODO: test this: static void aodv_parse_rack(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ } END_TEST + START_TEST(tc_pico_aodv_parse) { /* TODO: test this: static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ } END_TEST + START_TEST(tc_pico_aodv_socket_callback) { /* TODO: test this: static void pico_aodv_socket_callback(uint16_t ev, struct pico_socket *s) */ } END_TEST + START_TEST(tc_aodv_make_rreq) { /* TODO: test this: static void aodv_make_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req) */ } END_TEST + START_TEST(tc_aodv_retrans_rreq) { /* TODO: test this: static void aodv_retrans_rreq(pico_time now, void *arg) */ } END_TEST + START_TEST(tc_pico_aodv_expired) { /* TODO: test this: static void pico_aodv_expired(struct pico_aodv_node *node) */ } END_TEST + START_TEST(tc_pico_aodv_collector) { /* TODO: test this: static void pico_aodv_collector(pico_time now, void *arg) */ From ef9a23ebcbc87a0991db58645b4344f40d7e6892 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 24 Feb 2015 14:00:05 +0100 Subject: [PATCH 28/61] AODV: more unit tests (#219) --- modules/pico_aodv.c | 4 ++- test/unit/modunit_pico_aodv.c | 53 ++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index a90f068cc..97b4bdde5 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -14,7 +14,8 @@ #include -#define pico_aodv_dbg(...) do{}while(0) +//#define pico_aodv_dbg(...) do{}while(0) +#define pico_aodv_dbg dbg #define AODV_MAX_PKT (64) static const struct pico_ip4 HOST_NETMASK = { @@ -216,6 +217,7 @@ static void aodv_send_reply(struct pico_aodv_node *node, struct pico_aodv_rreq * pico_aodv_dbg("Generating RREP for node %x, id=%x\n", reply.dest, reply.dseq); pico_socket_sendto(aodv_socket, &reply, sizeof(reply), &dest, short_be(PICO_AODV_PORT)); } + pico_aodv_dbg("no rrep generated.\n"); } /* Parser functions */ diff --git a/test/unit/modunit_pico_aodv.c b/test/unit/modunit_pico_aodv.c index 8b228e4dc..d644ab862 100644 --- a/test/unit/modunit_pico_aodv.c +++ b/test/unit/modunit_pico_aodv.c @@ -266,6 +266,15 @@ struct pico_ipv4_link *pico_ipv4_link_by_dev(struct pico_device *dev) return &global_link; } +static struct pico_device global_dev; +static int link_find_success = 0; +struct pico_device *pico_ipv4_link_find(struct pico_ip4 *ip4) +{ + if (link_find_success) + return &global_dev; + return NULL; +} + static int timer_set = 0; struct pico_timer *pico_timer_add(pico_time expire, void (*timer)(pico_time, void *), void *arg) { @@ -324,13 +333,50 @@ END_TEST START_TEST(tc_aodv_reverse_path_discover) { - /* TODO: test this: static void aodv_reverse_path_discover(pico_time now, void *arg) */ + struct pico_aodv_node node; + memset(&node, 0, sizeof(node)); + aodv_reverse_path_discover(0, &node); } END_TEST START_TEST(tc_aodv_recv_valid_rreq) { - /* TODO: test this: static void aodv_recv_valid_rreq(struct pico_aodv_node *node, struct pico_aodv_rreq *req, struct pico_msginfo *info) */ + struct pico_aodv_node node; + struct pico_aodv_rreq req; + struct pico_msginfo info; + union pico_address addr; + memset(&node, 0, sizeof(node)); + memset(&req, 0, sizeof(req)); + memset(&info, 0, sizeof(info)); + + addr.ip4.addr = 0x22222222; + + link_find_success = 0; + aodv_recv_valid_rreq(&node, &req, &info); + fail_if(pico_socket_sendto_called > 0); + + /* link not local, but active node, set to send reply, no timer */ + link_find_success = 0; + fail_if(aodv_peer_new(&addr) == NULL); + global_link.address.addr = 0x44444444; + req.orig = addr.ip4.addr; + req.dest = 0x11111111; + node.flags = PICO_AODV_NODE_SYNC | PICO_AODV_NODE_ROUTE_UP| PICO_AODV_NODE_ROUTE_DOWN; + node.dseq = 42; + expected_dseq = long_be(42); + aodv_recv_valid_rreq(&node, &req, &info); + fail_if(pico_socket_sendto_called < 1); + fail_if(timer_set > 0); + pico_socket_sendto_called = 0; + + /* link local, active node. Full send + set timer. */ + link_find_success = 1; + expected_dseq = long_be(pico_aodv_local_id + 1); + aodv_peer_new(&addr); + aodv_recv_valid_rreq(&node, &req, &info); + fail_if(pico_socket_sendto_called < 1); + fail_if(timer_set < 1); + } END_TEST @@ -354,13 +400,12 @@ END_TEST START_TEST(tc_aodv_parse_rack) { - /* TODO: test this: static void aodv_parse_rack(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ + aodv_parse_rack(NULL, NULL, 0, NULL); } END_TEST START_TEST(tc_pico_aodv_parse) { - /* TODO: test this: static void pico_aodv_parse(union pico_address *from, uint8_t *buf, int len, struct pico_msginfo *msginfo) */ } END_TEST From 9578cdb38295520eafdc893365ff183057109efb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 24 Feb 2015 14:34:20 +0100 Subject: [PATCH 29/61] Fixed socket manual. Added sendto/recvfrom _extended function description --- docs/user_manual/chap_api_sock.tex | 153 ++++++++++++++++++++++++----- 1 file changed, 130 insertions(+), 23 deletions(-) diff --git a/docs/user_manual/chap_api_sock.tex b/docs/user_manual/chap_api_sock.tex index 8671774d1..da43a1a30 100644 --- a/docs/user_manual/chap_api_sock.tex +++ b/docs/user_manual/chap_api_sock.tex @@ -8,7 +8,7 @@ \subsection{pico$\_$socket$\_$open} \subsubsection*{Description} This function will be called to open a socket from the application level. The created -socket will be unbound. +socket will be unbound and not connected. \subsubsection*{Function prototype} \begin{verbatim} @@ -57,7 +57,7 @@ \subsubsection*{Example} \subsection{pico$\_$socket$\_$read} \subsubsection*{Description} -This function will be called to read a string from a socket from the application level. The function checks whether or not the socket is bound. +This function will be called to read data from a connected socket. The function checks that the socket is bound and connected before attempting to receive data. \subsubsection*{Function prototype} \begin{verbatim} @@ -67,7 +67,7 @@ \subsubsection*{Function prototype} \subsubsection*{Parameters} \begin{itemize}[noitemsep] \item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} -\item \texttt{buf} - Void pointer to the start of a string buffer where the string will be stored +\item \texttt{buf} - Void pointer to the start of the buffer where the received data will be stored \item \texttt{len} - Length of the buffer (in bytes), represents the maximum amount of bytes that can be read \end{itemize} @@ -92,10 +92,9 @@ \subsubsection*{Example} \subsection{pico$\_$socket$\_$write} \subsubsection*{Description} -This function will be called to write a string to a socket from the application level. -This function also checks if the socket is bound, connected and that it isn't shutdown -locally. This is the preferred function to use when writing strings from application -level. +This function will be called to write the content of a buffer to a socket that has been previously connected. +This function checks that the socket is bound, connected and that it is allowed to send data, i.e. there hasn't been a local shutdown. +This is the preferred function to use when writing data from the application to a connected stream. \subsubsection*{Function prototype} \begin{verbatim} @@ -105,8 +104,8 @@ \subsubsection*{Function prototype} \subsubsection*{Parameters} \begin{itemize}[noitemsep] \item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} -\item \texttt{buf} - Void pointer to the start of a string buffer where the string is stored -\item \texttt{len} - Length of the string that is stored in the buffer (in bytes) +\item \texttt{buf} - Void pointer to the start of a (constant) buffer where the data is stored +\item \texttt{len} - Length of the data buffer \texttt{buf} \end{itemize} \subsubsection*{Return value} @@ -134,9 +133,10 @@ \subsubsection*{Example} \subsection{pico$\_$socket$\_$sendto} \subsubsection*{Description} -This function is be called by the \texttt{pico$\_$socket$\_$write} and \texttt{pico$\_$socket$\_$send} functions. -This function sends a string from the local address to the remote address, without checking -if the remote is connected or not. +This function sends data from the local address to the remote address, without checking +whether the remote endpoint is connected. Specifying the destination is particularly useful while sending single datagrams +to different destinations upon consecutive calls. This is the preferred mechanism to send datagrams to a remote destination +using a UDP socket. \subsubsection*{Function prototype} \begin{verbatim} @@ -147,8 +147,8 @@ \subsubsection*{Function prototype} \subsubsection*{Parameters} \begin{itemize}[noitemsep] \item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} -\item \texttt{buf} - Void pointer to the start of a string buffer where the string is stored -\item \texttt{len} - Length of the string that is stored in the buffer (in bytes) +\item \texttt{buf} - Void pointer to the start of the buffer +\item \texttt{len} - Length of the buffer \texttt{buf} \item \texttt{dst} - Pointer to the origin of the IPv4/IPv6 frame header \item \texttt{remote$\_$port} - Portnumber of the receiving socket \end{itemize} @@ -176,7 +176,7 @@ \subsubsection*{Example} \subsection{pico$\_$socket$\_$recvfrom} \subsubsection*{Description} -This function is called to receive a string of data from the specified socket. +This function is called to receive data from the specified socket. It is useful when called in the context of a non-connected socket, to receive the information regarding the origin of the data, namely the origin address and the remote port number. @@ -190,8 +190,8 @@ \subsubsection*{Function prototype} \subsubsection*{Parameters} \begin{itemize}[noitemsep] \item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} -\item \texttt{buf} - Void pointer to the start of a string buffer where the string will be stored -\item \texttt{len} - Length of the string that will be stored in the buffer (in bytes) +\item \texttt{buf} - Void pointer to the start of the buffer +\item \texttt{len} - Maximum allowed length for the data to be stored in the buffer \texttt{buf} \item \texttt{orig} - Pointer to the origin of the IPv4/IPv6 frame header, can be NULL \item \texttt{remote$\_$port} - Pointer to the port number of the sender socket, can be NULL \end{itemize} @@ -216,12 +216,119 @@ \subsubsection*{Example} bytesRcvd = pico_socket_recvfrom(sk_tcp, buf, bufLen, &peer, &port); \end{verbatim} +\subsection{Extended Socket operations} +The interface provided by sendto/recvfrom can be extended to include more information about the network communication. +This is especially useful in UDP communication, and whenever extended information is needed about the single datagram and its encapsulation in the networking layer. + +PicoTCP offers an extra structure that can be used to set and retrieve message information while transmitting and receiving datagrams, respectively. The structure \texttt{pico$\_$msginfo} is defined as follows: +\begin{verbatim} +struct pico_msginfo { + struct pico_device *dev; + uint8_t ttl; + uint8_t tos; +}; +\end{verbatim} + + + +\subsection{pico$\_$socket$\_$sendto$\_$extended} + +\subsubsection*{Description} +This function is an extension of the \texttt{pico$\_$socket$\_$sendto} function described above. It's exactly the same but it adds up an additional argument to set TTL and QOS information on the outgoing packet which contains the datagram. + +The usage of the extended argument makes sense in UDP context only, as the information is set at packet level, and only with UDP there is a 1:1 correspondence between datagrams and IP packets. + +\subsubsection*{Function prototype} +\begin{verbatim} +int pico_socket_sendto_extended(struct pico_socket *s, const void *buf, int len, +void *dst, uint16_t remote_port, struct pico_msginfo *info); +\end{verbatim} + +\subsubsection*{Parameters} +\begin{itemize}[noitemsep] +\item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} +\item \texttt{buf} - Void pointer to the start of the buffer +\item \texttt{len} - Length of the data that is stored in the buffer (in bytes) +\item \texttt{dst} - Pointer to the origin of the IPv4/IPv6 frame header +\item \texttt{remote$\_$port} - Port number of the receiving socket at the remote endpoint +\item \texttt{info} - Extended information about the packet containing this datagram. Only the fields "ttl" and "tos" are taken into consideeration, while "dev" is ignored. + +\end{itemize} + +\subsubsection*{Return value} +On success, this call returns an integer representing the number of bytes written to the socket. +On error, -1 is returned, and \texttt{pico$\_$err} is set appropriately. + +\subsubsection*{Errors} +\begin{itemize}[noitemsep] +\item \texttt{PICO$\_$ERR$\_$EADDRNOTAVAIL} - address not available +\item \texttt{PICO$\_$ERR$\_$EINVAL} - invalid argument +\item \texttt{PICO$\_$ERR$\_$EHOSTUNREACH} - host is unreachable +\item \texttt{PICO$\_$ERR$\_$ENOMEM} - not enough space +\item \texttt{PICO$\_$ERR$\_$EAGAIN} - resource temporarily unavailable +\end{itemize} + +\subsubsection*{Example} +\begin{verbatim} +struct pico_msginfo info = { }; +info.ttl = 5; +bytesWritten = pico_socket_sendto_extended(sk_tcp, buf, len, &sk_tcp->remote_addr, +sk_tcp->remote_port, &info); +\end{verbatim} + + +\subsection{pico$\_$socket$\_$recvfrom$\_$extended} + +\subsubsection*{Description} +This function is an extension to the normal \texttt{pico$\_$socket$\_$recvfrom} function, which allows to retrieve additional information about the networking layer that has been involved in the delivery of the datagram. + +\subsubsection*{Function prototype} +\begin{verbatim} +int pico_socket_recvfrom_extended(struct pico_socket *s, void *buf, int len, +void *orig, uint16_t *remote_port, struct pico_msginfo *info); +\end{verbatim} + +\subsubsection*{Parameters} +\begin{itemize}[noitemsep] +\item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} +\item \texttt{buf} - Void pointer to the start of the buffer +\item \texttt{len} - Maximum allowed length for the data to be stored in the buffer \texttt{buf} +\item \texttt{orig} - Pointer to the origin of the IPv4/IPv6 frame header, can be NULL +\item \texttt{remote$\_$port} - Pointer to the port number of the sender socket, can be NULL +\item \texttt{info} - Extended information about the incoming packet containing this datagram. The device where the packet was received is pointed by info->dev, the maximum TTL for the packet is stored in info->ttl, and finally the field info->tos keeps track of the flags in IP header's QoS. +\end{itemize} + +\subsubsection*{Return value} +On success, this call returns an integer representing the number of bytes read from the socket. On success, if \texttt{orig} +is not NULL, The address of the remote endpoint is stored in the memory area pointed by \texttt{orig}. +In the same way, \texttt{remote$\_$port} will contain the portnumber of the sending socket, unless a NULL is passed +from the caller. + +On error, -1 is returned, and \texttt{pico$\_$err} is set appropriately. + +\subsubsection*{Errors} +\begin{itemize}[noitemsep] +\item \texttt{PICO$\_$ERR$\_$EINVAL} - invalid argument +\item \texttt{PICO$\_$ERR$\_$ESHUTDOWN} - cannot read after transport endpoint shutdown +\item \texttt{PICO$\_$ERR$\_$EADDRNOTAVAIL} - address not available +\end{itemize} + +\subsubsection*{Example} +\begin{verbatim} +struct pico_msginfo info; +bytesRcvd = pico_socket_recvfrom_extended(sk_tcp, buf, bufLen, &peer, &port, &info); +if (info && info->dev) { + printf("Socket received a datagram via device %s, ttl:%d, tos: %08x\n", + info->dev->name, info->ttl, info->tos); +} +\end{verbatim} + \subsection{pico$\_$socket$\_$send} \subsubsection*{Description} -This function is called to send a string of data to the specified socket. -This function also checks if the socket is connected and then calls the +This function is called to send data to the specified socket. +It checks if the socket is connected and then calls the \texttt{pico$\_$socket$\_$sendto} function. \subsubsection*{Function prototype} @@ -233,8 +340,8 @@ \subsubsection*{Function prototype} \subsubsection*{Parameters} \begin{itemize}[noitemsep] \item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} -\item \texttt{buf} - Void pointer to the start of a string buffer where the string is stored -\item \texttt{len} - Length of the string that is stored in the buffer (in bytes) +\item \texttt{buf} - Void pointer to the start of the buffer +\item \texttt{len} - Length of the buffer \texttt{buf} \end{itemize} \subsubsection*{Return value} @@ -270,8 +377,8 @@ \subsubsection*{Function prototype} \subsubsection*{Parameters} \begin{itemize}[noitemsep] \item \texttt{s} - Pointer to socket of type \texttt{struct pico$\_$socket} -\item \texttt{buf} - Void pointer to the start of a string buffer where the string will be stored -\item \texttt{len} - Length of the string in the socket buffer (in bytes) +\item \texttt{buf} - Void pointer to the start of the buffer +\item \texttt{len} - Maximum allowed length for the data to be stored in the buffer \texttt{buf} \end{itemize} \subsubsection*{Return value} From 4187b419bd3c62508dd0b282f8591f2dc80b862c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 24 Feb 2015 14:52:05 +0100 Subject: [PATCH 30/61] AODV: updated manual (#219) --- docs/user_manual/chap_api_aodv.tex | 42 ++++++++++++++++++++++++++++++ docs/user_manual/chap_rfcs.tex | 3 +++ docs/user_manual/user_doc.tex | 1 + 3 files changed, 46 insertions(+) create mode 100644 docs/user_manual/chap_api_aodv.tex diff --git a/docs/user_manual/chap_api_aodv.tex b/docs/user_manual/chap_api_aodv.tex new file mode 100644 index 000000000..02171ea20 --- /dev/null +++ b/docs/user_manual/chap_api_aodv.tex @@ -0,0 +1,42 @@ +\section{Ad-hoc On-Demand Distance Vector Routing (AODV)} + + +AODV is a reactive routing protocol for mobile ad-hoc networks +(MANETs). Its best fit are especially ultra-low power radio networks, +or those RF topologies where sporadic traffic between a small specific set +of nodes is foreseen. +In order to create a route, one node must explicitly start the communication +towards a remote node, and the route is created ad-hoc upon the demand +for a specific network path. +AODV guarantees that the traffic generated by each node in order to create +and maintain routes is kept as low as possible. + +\subsection{pico\_aodv\_add} + +\subsubsection*{Description} +This function will add the target device to the AODV mechanism on the machine, +meaning that it will be possible to advertise and collect routing information +using Ad-hoc On-Demand Distance Vector Routing, as described in RFC3561, through the +target device. + +In order to use multiple devices in the AODV system, this function needs to be called +multiple times, once per device. + +\subsubsection*{Function prototype} +\texttt{pico\_aodv\_add(struct pico\_device *dev);} + +\subsubsection*{Parameters} +\begin{itemize}[noitemsep] +\item \texttt{dev} - a pointer to a struct \texttt{pico\_device} specifying the target interface. +\end{itemize} + +\subsubsection*{Return value} +0 returned if the device is successfully added. + +\subsubsection*{Example} +\begin{verbatim} + +ret = pico_aodv_add(dev); + +\end{verbatim} + diff --git a/docs/user_manual/chap_rfcs.tex b/docs/user_manual/chap_rfcs.tex index 934f935ba..d540126c6 100644 --- a/docs/user_manual/chap_rfcs.tex +++ b/docs/user_manual/chap_rfcs.tex @@ -127,6 +127,9 @@ RFC 3517 & A Conservative Selective Acknowledgment (SACK)-based Loss Recovery Algorithm for TCP \\ \hline +RFC 3561 & +Ad-hoc On-Demand Distance Vector (AODV) Routing \\ \hline + RFC 3626 & Optimized Link State Routing Protocol (OLSR) \\ \hline diff --git a/docs/user_manual/user_doc.tex b/docs/user_manual/user_doc.tex index 6464b7dc9..40d40c4a0 100644 --- a/docs/user_manual/user_doc.tex +++ b/docs/user_manual/user_doc.tex @@ -98,6 +98,7 @@ \chapter{API Documentation} \input{chap_api_slaacv4} \input{chap_api_tftp} \input{chap_api_olsr} +\input{chap_api_aodv} \chapter{Examples} \label{chap:examples} From d7e0dd42a15ce1a7b870c2c45da7d3d7f4b9bdbe Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 24 Feb 2015 15:00:21 +0100 Subject: [PATCH 31/61] Disabled debug (#219) --- modules/pico_aodv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index 97b4bdde5..d02b6a826 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -14,8 +14,8 @@ #include -//#define pico_aodv_dbg(...) do{}while(0) -#define pico_aodv_dbg dbg +#define pico_aodv_dbg(...) do{}while(0) +//#define pico_aodv_dbg dbg #define AODV_MAX_PKT (64) static const struct pico_ip4 HOST_NETMASK = { From 2ed0aa17e6c83da1dd13fc34f5558686f010571c Mon Sep 17 00:00:00 2001 From: Maxime Vincent Date: Wed, 25 Feb 2015 12:29:57 +0100 Subject: [PATCH 32/61] AODV: initialize complete address stuff instead of only IPv4 addr --- modules/pico_aodv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index d02b6a826..ce2a3fd80 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -561,7 +561,7 @@ static void pico_aodv_collector(pico_time now, void *arg) MOCKABLE int pico_aodv_init(void) { - struct pico_ip4 any = { .addr = 0u}; + struct pico_ip4 any; uint16_t port = short_be(PICO_AODV_PORT); if (aodv_socket) { pico_err = PICO_ERR_EADDRINUSE; @@ -571,6 +571,8 @@ MOCKABLE int pico_aodv_init(void) if (!aodv_socket) return -1; + memset(&any, 0, sizeof(union pico_address)); + if (pico_socket_bind(aodv_socket, &any, &port) != 0) { uint16_t err = pico_err; pico_socket_close(aodv_socket); From 7735db1256ee6ff95610ea26385cbe6f9ffc2e80 Mon Sep 17 00:00:00 2001 From: Maxime Vincent Date: Wed, 25 Feb 2015 14:49:20 +0100 Subject: [PATCH 33/61] Refactor to remove use of uninitialized memory --- modules/pico_aodv.c | 4 +--- stack/pico_socket.c | 31 +++++++++++++------------ stack/pico_stack.c | 56 +++++++++++++++++++++++++++++++-------------- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/modules/pico_aodv.c b/modules/pico_aodv.c index ce2a3fd80..031abc073 100644 --- a/modules/pico_aodv.c +++ b/modules/pico_aodv.c @@ -561,7 +561,7 @@ static void pico_aodv_collector(pico_time now, void *arg) MOCKABLE int pico_aodv_init(void) { - struct pico_ip4 any; + struct pico_ip4 any = { 0 }; uint16_t port = short_be(PICO_AODV_PORT); if (aodv_socket) { pico_err = PICO_ERR_EADDRINUSE; @@ -571,8 +571,6 @@ MOCKABLE int pico_aodv_init(void) if (!aodv_socket) return -1; - memset(&any, 0, sizeof(union pico_address)); - if (pico_socket_bind(aodv_socket, &any, &port) != 0) { uint16_t err = pico_err; pico_socket_close(aodv_socket); diff --git a/stack/pico_socket.c b/stack/pico_socket.c index f4b26d8e7..7c4db17b7 100644 --- a/stack/pico_socket.c +++ b/stack/pico_socket.c @@ -263,9 +263,9 @@ static int pico_port_in_use_ipv6(struct pico_sockport *sp, void *addr) struct pico_ip6 ip; /* IPv6 */ if (addr) - memcpy(&ip.addr, ((struct pico_ip6 *)addr)->addr, sizeof(struct pico_ip6)); + memcpy(ip.addr, ((struct pico_ip6 *)addr)->addr, sizeof(struct pico_ip6)); else - memcpy(&ip.addr, PICO_IP6_ANY, sizeof(struct pico_ip6)); + memcpy(ip.addr, PICO_IP6_ANY, sizeof(struct pico_ip6)); if (memcmp(ip.addr, PICO_IP6_ANY, sizeof(struct pico_ip6)) == 0) { if (!sp) @@ -282,24 +282,28 @@ static int pico_port_in_use_ipv6(struct pico_sockport *sp, void *addr) -static int pico_generic_port_in_use(uint16_t proto, uint16_t port, struct pico_sockport *sp, void *addr) +static int pico_generic_port_in_use(uint16_t proto, uint16_t port, struct pico_sockport *sp, void *addr, void *net) { #ifdef PICO_SUPPORT_IPV4 - if (pico_port_in_use_by_nat(proto, port)) { - return 1; - } + if (net == &pico_proto_ipv4) + { + if (pico_port_in_use_by_nat(proto, port)) { + return 1; + } - if (pico_port_in_use_ipv4(sp, addr)) { - return 1; + if (pico_port_in_use_ipv4(sp, addr)) { + return 1; + } } - #endif #ifdef PICO_SUPPORT_IPV6 - if (pico_port_in_use_ipv6(sp, addr)) { - return 1; + if (net == &pico_proto_ipv6) + { + if (pico_port_in_use_ipv6(sp, addr)) { + return 1; + } } - #endif return 0; @@ -308,10 +312,9 @@ static int pico_generic_port_in_use(uint16_t proto, uint16_t port, struct pico_s int pico_is_port_free(uint16_t proto, uint16_t port, void *addr, void *net) { struct pico_sockport *sp; - (void) net; sp = pico_get_sockport(proto, port); - if (pico_generic_port_in_use(proto, port, sp, addr)) + if (pico_generic_port_in_use(proto, port, sp, addr, net)) return 0; return 1; diff --git a/stack/pico_stack.c b/stack/pico_stack.c index 11766d111..b3d6374a6 100644 --- a/stack/pico_stack.c +++ b/stack/pico_stack.c @@ -444,24 +444,34 @@ struct pico_eth *pico_ethernet_mcast6_translate(struct pico_frame *f, uint8_t *p } #endif -struct pico_eth *pico_ethernet_ipv6_dst(struct pico_frame *f) +int pico_ethernet_ipv6_dst(struct pico_frame *f, struct pico_eth * const dstmac) { - struct pico_eth *dstmac = NULL; + int retval = -1; + if (!dstmac) + return -1; + #ifdef PICO_SUPPORT_IPV6 if (destination_is_mcast(f)) { uint8_t pico_mcast6_mac[6] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 }; - dstmac = pico_ethernet_mcast6_translate(f, pico_mcast6_mac); + pico_ethernet_mcast6_translate(f, pico_mcast6_mac); + memcpy(dstmac, pico_mcast6_mac, PICO_SIZE_ETH); + retval = 0; } else { - dstmac = pico_ipv6_get_neighbor(f); + struct pico_eth * neighbor = pico_ipv6_get_neighbor(f); + if (neighbor) + { + memcpy(dstmac, neighbor, PICO_SIZE_ETH); + retval = 0; + } } #else (void)f; pico_err = PICO_ERR_EPROTONOSUPPORT; #endif - return dstmac; + return retval; } @@ -521,7 +531,8 @@ static int32_t pico_ethsend_dispatch(struct pico_frame *f) int32_t MOCKABLE pico_ethernet_send(struct pico_frame *f) { - const struct pico_eth *dstmac = NULL; + struct pico_eth dstmac; + uint8_t dstmac_valid = 0; uint16_t proto = PICO_IDETH_IPV4; #ifdef PICO_SUPPORT_IPV6 @@ -529,11 +540,12 @@ int32_t MOCKABLE pico_ethernet_send(struct pico_frame *f) * destination address is taken from the ND tables */ if (IS_IPV6(f)) { - dstmac = pico_ethernet_ipv6_dst(f); - if (!dstmac) { + if (pico_ethernet_ipv6_dst(f, &dstmac) < 0) + { pico_ipv6_nd_postpone(f); return 0; /* I don't care if frame was actually postponed. If there is no room in the ND table, discard safely. */ } + dstmac_valid = 1; proto = PICO_IDETH_IPV6; } else @@ -541,32 +553,42 @@ int32_t MOCKABLE pico_ethernet_send(struct pico_frame *f) /* In case of broadcast (IPV4 only), dst mac is FF:FF:... */ if (IS_BCAST(f) || destination_is_bcast(f)) - dstmac = (const struct pico_eth *) PICO_ETHADDR_ALL; + { + memcpy(&dstmac, PICO_ETHADDR_ALL, PICO_SIZE_ETH); + dstmac_valid = 1; + } /* In case of multicast, dst mac is translated from the group address */ else if (destination_is_mcast(f)) { uint8_t pico_mcast_mac[6] = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 }; - dstmac = pico_ethernet_mcast_translate(f, pico_mcast_mac); + pico_ethernet_mcast_translate(f, pico_mcast_mac); + memcpy(&dstmac, pico_mcast_mac, PICO_SIZE_ETH); + dstmac_valid = 1; } #if (defined PICO_SUPPORT_IPV4) else { - dstmac = pico_arp_get(f); - /* At this point, ARP will discard the frame in any case. - * It is safe to return without discarding. - */ - if (!dstmac) { + struct pico_eth * arp_get; + arp_get = pico_arp_get(f); + if (arp_get) { + memcpy(&dstmac, arp_get, PICO_SIZE_ETH); + dstmac_valid = 1; + } else { + /* At this point, ARP will discard the frame in any case. + * It is safe to return without discarding. + */ pico_arp_postpone(f); return 0; /* Same case as for IPv6 ... */ } + } #endif /* This sets destination and source address, then pushes the packet to the device. */ - if (dstmac) { + if (dstmac_valid) { struct pico_eth_hdr *hdr; hdr = (struct pico_eth_hdr *) f->datalink_hdr; if ((f->start > f->buffer) && ((f->start - f->buffer) >= PICO_SIZE_ETHHDR)) @@ -576,7 +598,7 @@ int32_t MOCKABLE pico_ethernet_send(struct pico_frame *f) f->datalink_hdr = f->start; hdr = (struct pico_eth_hdr *) f->datalink_hdr; memcpy(hdr->saddr, f->dev->eth->mac.addr, PICO_SIZE_ETH); - memcpy(hdr->daddr, dstmac, PICO_SIZE_ETH); + memcpy(hdr->daddr, &dstmac, PICO_SIZE_ETH); hdr->proto = proto; } if (pico_ethsend_local(f, hdr) || pico_ethsend_bcast(f) || pico_ethsend_dispatch(f)) { From 456903a4a8989432e368654dcfd4740bbb82d82b Mon Sep 17 00:00:00 2001 From: Michele Di Pede Date: Sun, 1 Mar 2015 00:42:48 +0100 Subject: [PATCH 34/61] Source code for test_tftp_app_client.c (previous commit added executable by mistake) --- test/test_tftp_app_client | Bin 470200 -> 0 bytes test/test_tftp_app_client.c | 240 ++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) delete mode 100755 test/test_tftp_app_client create mode 100644 test/test_tftp_app_client.c diff --git a/test/test_tftp_app_client b/test/test_tftp_app_client deleted file mode 100755 index 495ca8357f12fad0d588129932d1a389b962ded9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 470200 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

!0ZM>!0Y>pR3zdxw>7gx@E+S*Qjn? zwXEAUKhbTY>PCG>3w-q{#@;nVbi-#1{Cxd~P`ENY|EZto_S8>wdq#EZt34kt9p0sy z@!4EepUXAl^SNex{wJ!wFkr?FFmr-mJ2ng#ReeU)P^qYzu+gjryfI;;T@83+A@idb zWmUaPRW)hn)Oe9C4MnY5Y>RWMRg3N18ZWV>;ZP5-6Ap3^i@Ic_s5+Nz3ajFj7FhKl zRu#{gy0hUSi<$m2rN7$HU#j$rm7aG0TxiItQA@L1%mt+{A+`6SN|$Ikms*Vx^tr3~ z`utI%%rpc3nS!r2;OE24s}(JiI*x|aHmImaNUd5$ebXA->zm?k8+8?pr-I`=7VwG+ zppQ}f^Yy38q_pRG0%{oKJR8$*wB8s3&Q}3_Rv53h2M`NS#Q5Z|RZ)fh`LQLGPwVPW zn8?sgKn}k`^rw6Du2q`{%4s%&<7w{^k90X*^7rHL#7&e8VOEX5H%HnqmD2J8N-z)a z%JeM<`*ZMTXFLRdcE&^SXJ7K}AS4cc#wu zR0-i4JfAe^7s>3P-zU6lpB1v>(6sRrfP{}EMp>8XTuZeV)^(ZkX+Baklz{oue54df z`7|G?z&BWzKh5j05d3LAQiJ@m1U}8{u@L-eKGLu!<;$n}NYg}0%cuEB3)1pwUXO*~ zPxFyBrXs_BpnUl>9~n_V<&sbHdMt#4$Y4Ddf`rYe(p5?`vGQp?GVL)kgM6CTV~Rno98FpDcdqj-aGEY9S-qrs7g2=}O7M1U5Ddb0jSWVa zt(Cwr7O^25a_d|`46$Kw?SvDgyRqR&*Ev6uDlu7?>Ff`hQ7edbI2R)}dN4tb;bO$b zAl>WylK#fVE+EQv4!*0z#!Ee{NK-nE^c4;QfnyVp-st=lX<3))TnM?cF4O5j{^UNw z+2Xtg9l9WQ8~aXdDr(`O#NvfkOx9&O*#ABz>oOhe%O8_ zptN0rCYNv!C~b4|D|0%LmUWrzA-1l|OzJTf50S7UKE|SmkFhA)LZE>nImSZbe)Ebi zA?5#n6!)7q0i_N-L8a=+n}U|ajVf>IK%>%QEF3w;0yR?PV3J#pv0x9(+YgPWjy*7M zrZy^i4CZAHqx_-HD(V&TW}QgsG0q>TgU_3dblO>o^c;`Ng@A%Ba**dMY@OGNt9iTRtPY;&z)}AU5GoN1vK9w^3y0Cx}55_HD2}x zr5yyw$15HKB)5xrC9+)y0rK%Gq!Z4KNLM3W=0F4Q7>lrbDP4s)Kt5jYP&piYSBa-U zYB_7^nGtWUq#O#KhKfiQLKOcYZNL&bdko>mnr2hlX&g;Wq~#rEf0Qy}ttdc3dW|-u z3#|e)?m|wZ2@?!h!!Ec&yh=6JjqXRfk^Rw)SkiSIz+hI0zpLcO-vAN5tK>^azJrkb zd@*iky9GD2y z(-id0SwvCaptON-CeEs(;PInfLrWNM^ z52yTC8j}YW;j&+G7BLEN0x{8NQ5KG-6{pYO9FAhH;LF`bpQQ$8y5g+J!qK$itTZ@} z&}(Ag3|uZ#oHGp0ZNQ=LQ-L$k!_a$O8GWDfF47frW35Q&t)P5O_lV`r@+4I68buN= z$K@`SFt9C=KTjoGE)sfJf?i}G;a4CbPl5zZ_fiwCCvUGA1RHDu!PK!ot&?ubt;L8l zuxSM=KfM;qEWN9)Ds+^A&A`FVZ{^#m%M9ENEgw*r~wE;l%^Yxcd4_>yC4yb%w3f5J&7#HXeXpux+vofp_daJ5v55oE* zxX`q4Tt(ndcox7UbZq65S^?-Ffl>Yj8pCUleXT-KZ_Gamt@LESCbD~K@rD$5O>Cd3 zfF^H{5J=wP#Y9-CW>c&Y?E-}ImO29C)oDad}Int@@Yj_O)GJI0N=h-!GCSQpCItmZ^to(7-o9vy2Gp z-NpEXe76DnV$!AOX=LrKUV*Ka2hk%v;5$r_HXEcDfkdWc8d?U(E zQW?qD1AdH$y;c|SOAGI9ScF?rgEYJO9<(lEsHu}g>j6()qEJN&)dS`tWzO)9qjV`2 z5G6}gOkX3d91N8Dfq|)f_b~UQCiKV@#(qH4` zao^@$_N4GSPy872(~}S283W#OB1(Fszj?B#`J_?vmjEW(BR$$w9JT3P(oW5TM$q+X zeET2D{7byMyhJKgQ@9^lQEIa%`A3GhNSEAfpmW7c12BW>BXiE&*UNopdS>DJ+dm|T3 zxEbvRSmsl9ju}&>EN2Wk>(}u&J-cs0 zcN(x!H7_XfMkC=<)i+>k=iGodA|jU|PNw-1^e7>{qzB#w7IE0e;^91)8w>hb;z%~$a4@Qj~o zzIU0}VMVSTgq&MBy@Cc-DmYoScNC$WYF|Tq$0^#8MswuCr%ug9JJqWrvBRmV+w?}- zc{{L%v8daoK64qV-m(*ZcE0_FqSqUAy!B$UZn_CUV5vU~>RiItKPbaW!!Wp9)VU;A zolCq*7IiMkQRlJ&>`J)fCca+2pBV0c4EA}7y(t%alc@%IX~@CeJb-=m7{Y#lKRbP= z*aMB}Ud4Vuv8kLl!C{_ehgmaIbeUsx`Bv$l_H=3f3h>V|IbJO4|BCCKEdI~RKG3i1 zUZHjs0_tI#uWlpyw~e|j%TOnu*ew|moid`2053V`C${(EUSEbP`GRFnRr�*MXiq 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 diff --git a/test/test_tftp_app_client.c b/test/test_tftp_app_client.c new file mode 100644 index 000000000..68efdb487 --- /dev/null +++ b/test/test_tftp_app_client.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include "pico_stack.h" +#include "pico_config.h" +#include "pico_ipv4.h" +#include "pico_icmp4.h" +#include "pico_socket.h" +#include "pico_stack.h" +#include "pico_device.h" +#include "pico_dev_vde.h" +#include "pico_tftp.h" + +static struct pico_device *pico_dev; + +int32_t get_filesize(const char *filename) +{ + int ret; + struct stat buf; + + ret = stat(filename, &buf); + if (ret) + return -1; + return buf.st_size; +} + +void start_rx(struct pico_tftp_session *session, int *synchro, const char *filename, int options) +{ + int ret; + int fd; + int32_t len; + uint8_t buf[PICO_TFTP_PAYLOAD_SIZE]; + int left = 1000; + int countdown = 0; + + printf("Start receiving file %s with options set to %d\n", filename, options); + + if (options) { + ret = pico_tftp_set_option(session, PICO_TFTP_OPTION_FILE, 0); + if (ret) { + fprintf(stderr, "Error in pico_tftp_set_option\n"); + exit(1); + } + } + + ret = pico_tftp_app_start_rx(session, filename); + if (ret) { + fprintf(stderr, "Error in pico_tftp_app_start_rx\n"); + exit(1); + } + + fd = open(filename, O_WRONLY | O_EXCL | O_CREAT, 0664); + if (!fd) { + fprintf(stderr, "Error in open\n"); + countdown = 1; + } + + for(;left; left -= countdown) { + usleep(2000); //PICO_IDLE(); + pico_stack_tick(); + if (countdown) + continue; + + if (*synchro) { + len = pico_tftp_get(session, buf, PICO_TFTP_PAYLOAD_SIZE); + if (len < 0) { + fprintf(stderr, "Failure in pico_tftp_get\n"); + close(fd); + countdown = 1; + continue; + } + ret = write(fd, buf, len); + if (ret < 0) { + fprintf(stderr, "Error in write\n"); + pico_tftp_abort(session, TFTP_ERR_EXCEEDED, "File write error"); + close(fd); + countdown = 1; + continue; + } + printf("Written %" PRId32 " bytes to file (synchro=%d)\n", len, *synchro); + + if (len != PICO_TFTP_PAYLOAD_SIZE) { + close(fd); + printf("Transfer complete!\n"); + countdown = 1; + } + } + } +} + +void start_tx(struct pico_tftp_session *session, int *synchro, const char *filename, int options) +{ + int ret; + int fd; + int32_t len; + uint8_t buf[PICO_TFTP_PAYLOAD_SIZE]; + int left = 1000; + int countdown = 0; + + printf("Start sending file %s with options set to %d\n", filename, options); + + if (options) { + ret = get_filesize(filename); + if (ret < 0) { + fprintf(stderr, "Error in get_filesize\n"); + exit(1); + } + + ret = pico_tftp_set_option(session, PICO_TFTP_OPTION_FILE, ret); + if (ret) { + fprintf(stderr, "Error in pico_tftp_set_option\n"); + exit(1); + } + } + + ret = pico_tftp_app_start_tx(session, filename); + if (ret) { + fprintf(stderr, "Error in pico_tftp_app_start_rx\n"); + exit(1); + } + + fd = open(filename, O_RDONLY, 0444); + if (!fd) { + fprintf(stderr, "Error in open\n"); + pico_tftp_abort(session, TFTP_ERR_EACC, "Error opening file"); + countdown = 1; + } + + for(;left; left -= countdown) { + usleep(2000); //PICO_IDLE(); + pico_stack_tick(); + if (countdown) + continue; + + if (*synchro) { + ret = read(fd, buf, PICO_TFTP_PAYLOAD_SIZE); + if (ret < 0) { + fprintf(stderr, "Error in read\n"); + pico_tftp_abort(session, TFTP_ERR_EACC, "File read error"); + close(fd); + countdown = 1; + continue; + } + printf("Read %" PRId32 " bytes from file (synchro=%d)\n", len, *synchro); + + len = pico_tftp_put(session, buf, ret); + if (len < 0) { + fprintf(stderr, "Failure in pico_tftp_put\n"); + close(fd); + countdown = 1; + continue; + } + + if (len != PICO_TFTP_PAYLOAD_SIZE) { + close(fd); + printf("Transfer complete!\n"); + countdown = 1; + } + } + } +} + +void usage(const char *text) +{ + fprintf(stderr, "%s\nArguments must be \n" + " can be:\n" + "\tg => GET request without options\n" + "\tG => GET request WITH options\n" + "\tp => PUT request without options\n" + "\tP => PUT request WITH options\n\n", + text); + exit(1); +} + +int main(int argc, char** argv) +{ + struct pico_ip4 my_ip; + union pico_address server_address; + struct pico_ip4 netmask; + struct pico_tftp_session *session; + int synchro; + int options = 0; + void (*operation)(struct pico_tftp_session *session, int *synchro, const char *filename, int options); + + unsigned char macaddr[6] = { + 0, 0, 0, 0xa, 0xb, 0x0 + }; + + uint16_t *macaddr_low = (uint16_t *) (macaddr + 2); + *macaddr_low = *macaddr_low ^ (uint16_t)((uint16_t)getpid() & (uint16_t)0xFFFFU); + macaddr[4] ^= (uint8_t)(getpid() >> 8); + macaddr[5] ^= (uint8_t) (getpid() & 0xFF); + + pico_string_to_ipv4("10.40.0.10", &my_ip.addr); + pico_string_to_ipv4("255.255.255.0", &netmask.addr); + pico_string_to_ipv4("10.40.0.2", &server_address.ip4.addr); + + if (argc != 3) { + usage("Invalid number or arguments"); + } + + switch (argv[2][0]) { + case 'G': + options = 1; + case 'g': + operation = start_rx; + break; + case 'P': + options = 1; + case 'p': + operation = start_tx; + break; + default: + usage("Invalid mode"); + } + + printf("%s start!\n", argv[0]); + pico_stack_init(); + pico_dev = (struct pico_device *) pico_vde_create("/tmp/vde_switch", "tap0", macaddr); + + if(!pico_dev) { + fprintf(stderr, "Error creating pico device, got enough privileges? Exiting...\n"); + exit(1); + } + + pico_ipv4_link_add(pico_dev, my_ip, netmask); + printf("Starting picoTCP loop\n"); + + session = pico_tftp_app_setup(&server_address, short_be(PICO_TFTP_PORT), PICO_PROTO_IPV4, &synchro); + if (!session) { + fprintf(stderr, "Error in pico_tftp_app_setup\n"); + exit(1); + } + + printf("synchro %d\n", synchro); + + operation(session, &synchro, argv[1], options); +} From 82fac1df278ab98cf0c46644de0fd608e341e5dd Mon Sep 17 00:00:00 2001 From: Michele Di Pede Date: Sun, 1 Mar 2015 16:28:51 +0100 Subject: [PATCH 35/61] pico_tftp_get now return only payload data (issue #221) --- modules/pico_tftp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pico_tftp.c b/modules/pico_tftp.c index 143be22ff..bb7098179 100644 --- a/modules/pico_tftp.c +++ b/modules/pico_tftp.c @@ -1259,7 +1259,7 @@ int32_t pico_tftp_get(struct pico_tftp_session *session, uint8_t *data, int32_t if (synchro < 0) return synchro; - memcpy(data, session->tftp_block, (size_t)session->len); + memcpy(data, tftp_payload(session->tftp_block), (size_t)session->len); len = session->len; tftp_send_ack(session); From 5b99277a1c31a27adf94588236a3e21b58251eaa Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 6 Mar 2015 13:59:39 +0100 Subject: [PATCH 36/61] Fixed bug in ipv6 forward --- modules/pico_ipv6.c | 20 +++++++------------- test/autotest.sh | 8 ++++++++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index 534e1f596..213d9c4f4 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -453,7 +453,6 @@ static int pico_ipv6_pre_forward_checks(struct pico_frame *f) if (pico_ipv6_forward_check_dev(f) < 0) return -1; - pico_sendto_dev(f); return 0; } @@ -481,8 +480,7 @@ static int pico_ipv6_forward(struct pico_frame *f) if (pico_ipv6_forward_check_dev(f) < 0) return -1; - pico_sendto_dev(f); - return 0; + return pico_sendto_dev(f); } int pico_ipv6_process_hopbyhop(struct pico_ipv6_exthdr *hbh, struct pico_frame *f) @@ -717,15 +715,16 @@ int pico_ipv6_process_in(struct pico_protocol *self, struct pico_frame *f) if (0) { } else if (pico_ipv6_is_unicast(&hdr->dst)) { - pico_transport_receive(f, f->proto); + if (pico_ipv6_link_get(&hdr->dst)) { + pico_transport_receive(f, f->proto); + } else { + /* not local, try to forward. */ + return pico_ipv6_forward(f); + } } else if (pico_ipv6_is_multicast(hdr->dst.addr)) { /* XXX perform multicast filtering: solicited-node multicast address MUST BE allowed! */ pico_transport_receive(f, f->proto); - } else { - /* not local, try to forward. */ - pico_ipv6_forward(f); } - return 0; } @@ -853,15 +852,12 @@ static int ipv6_frame_push_final(struct pico_frame *f) { struct pico_ipv6_hdr *hdr = NULL; hdr = (struct pico_ipv6_hdr *)f->net_hdr; - if(pico_ipv6_link_get(&hdr->dst)) { return pico_enqueue(&ipv6_in, f); } else { return pico_enqueue(&ipv6_out, f); } - - } struct pico_ipv6_link *pico_ipv6_linklocal_get(struct pico_device *dev); @@ -907,9 +903,7 @@ int pico_ipv6_frame_push(struct pico_frame *f, struct pico_ip6 *dst, uint8_t pro push_final: ipv6_push_hdr_adjust(f, link, dst, proto); - return ipv6_frame_push_final(f); - } static int pico_ipv6_frame_sock_push(struct pico_protocol *self, struct pico_frame *f) diff --git a/test/autotest.sh b/test/autotest.sh index 1b71fe544..c5cd9f6e3 100755 --- a/test/autotest.sh +++ b/test/autotest.sh @@ -65,6 +65,14 @@ wait || exit 1 wait killall picoapp6.elf +echo "IPV6 FWD TCP TEST" +(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::1,ffff::,aaaa::ff,, -a tcpbench,t,aabb::2,6667,,) & +(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::ff,ffff::,,, --vde pic1,/tmp/pic1.ctl,aabb::ff,ffff::,,, -a noop,) & +./build/test/picoapp6.elf --vde pic0,/tmp/pic1.ctl,aabb::2,ffff::,aabb::ff,, -a tcpbench,r,6667,, || exit 1 +sleep 2 +killall picoapp.elf + + echo "MULTICAST TEST" (./build/test/picoapp.elf --vde pic1:/tmp/pic0.ctl:10.40.0.3:255.255.0.0: -a mcastreceive:10.40.0.3:224.7.7.7:6667:6667) & (./build/test/picoapp.elf --vde pic2:/tmp/pic0.ctl:10.40.0.4:255.255.0.0: -a mcastreceive:10.40.0.4:224.7.7.7:6667:6667) & From c7dcef5d31ad5f9d4e3760ff2dbfda46e74aba43 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 6 Mar 2015 14:00:30 +0100 Subject: [PATCH 37/61] IPv6 Routing test temporarly commented out --- test/autotest.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/autotest.sh b/test/autotest.sh index c5cd9f6e3..5ae90b7a3 100755 --- a/test/autotest.sh +++ b/test/autotest.sh @@ -65,12 +65,12 @@ wait || exit 1 wait killall picoapp6.elf -echo "IPV6 FWD TCP TEST" -(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::1,ffff::,aaaa::ff,, -a tcpbench,t,aabb::2,6667,,) & -(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::ff,ffff::,,, --vde pic1,/tmp/pic1.ctl,aabb::ff,ffff::,,, -a noop,) & -./build/test/picoapp6.elf --vde pic0,/tmp/pic1.ctl,aabb::2,ffff::,aabb::ff,, -a tcpbench,r,6667,, || exit 1 -sleep 2 -killall picoapp.elf +#echo "IPV6 FWD TCP TEST" +#(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::1,ffff::,aaaa::ff,, -a tcpbench,t,aabb::2,6667,,) & +#(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::ff,ffff::,,, --vde pic1,/tmp/pic1.ctl,aabb::ff,ffff::,,, -a noop,) & +#./build/test/picoapp6.elf --vde pic0,/tmp/pic1.ctl,aabb::2,ffff::,aabb::ff,, -a tcpbench,r,6667,, || exit 1 +#sleep 2 +#killall picoapp.elf echo "MULTICAST TEST" From 927d011a5f30e0a6279973525c264222d548d46e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 6 Mar 2015 14:05:32 +0100 Subject: [PATCH 38/61] Fixed compile with no TCP --- stack/pico_socket.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stack/pico_socket.c b/stack/pico_socket.c index 7c4db17b7..6aa7b567f 100644 --- a/stack/pico_socket.c +++ b/stack/pico_socket.c @@ -1811,6 +1811,7 @@ static inline int pico_transport_crc_check(struct pico_frame *f) switch (net_hdr->proto) { +#ifdef PICO_SUPPORT_TCP case PICO_PROTO_TCP: checksum_invalid = short_be(pico_tcp_checksum(f)); /* dbg("TCP CRC validation == %u\n", checksum_invalid); */ @@ -1821,7 +1822,9 @@ static inline int pico_transport_crc_check(struct pico_frame *f) } break; +#endif /* PICO_SUPPORT_TCP */ +#ifdef PICO_SUPPORT_UDP case PICO_PROTO_UDP: udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; if (short_be(udp_hdr->crc)) { @@ -1844,6 +1847,7 @@ static inline int pico_transport_crc_check(struct pico_frame *f) } break; +#endif /* PICO_SUPPORT_UDP */ default: /* Do nothing */ From ebe032d51444a9b4892143f903cdf0f12087f7be Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 6 Mar 2015 14:09:09 +0100 Subject: [PATCH 39/61] Removed warning about GCC optimization missing --- include/pico_config.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/pico_config.h b/include/pico_config.h index 0f2ff959e..1df7f71d9 100644 --- a/include/pico_config.h +++ b/include/pico_config.h @@ -133,7 +133,6 @@ static inline uint64_t long_long_be(uint64_t le) be = b[7] + (b6 << 8) + (b5 << 16) + (b4 << 24) + (b3 << 32) + (b2 << 40) + (b1 << 48) + (b0 << 56); return be; } -#warning "Not using GCC Optimizations!" # else /* extern uint32_t __builtin_bswap32(uint32_t); From cf7bebb18aeb36aefa96e2471fe6921e1e095895 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Sat, 7 Mar 2015 11:49:37 +0100 Subject: [PATCH 40/61] IPv6 routing: fix forwarding problem --- modules/pico_ipv6.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index 213d9c4f4..5c0c22b91 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -477,9 +477,6 @@ static int pico_ipv6_forward(struct pico_frame *f) f->start = f->net_hdr; - if (pico_ipv6_forward_check_dev(f) < 0) - return -1; - return pico_sendto_dev(f); } @@ -703,6 +700,11 @@ int pico_ipv6_process_in(struct pico_protocol *self, struct pico_frame *f) IGNORE_PARAMETER(self); + if (pico_ipv6_is_unicast(&hdr->dst) && !pico_ipv6_link_get(&hdr->dst)) { + /* not local, try to forward. */ + return pico_ipv6_forward(f); + } + proto = pico_ipv6_extension_headers(f); if (proto <= 0) { pico_frame_discard(f); @@ -712,15 +714,8 @@ int pico_ipv6_process_in(struct pico_protocol *self, struct pico_frame *f) f->proto = (uint8_t)proto; ipv6_dbg("IPv6: payload %u net_len %u nxthdr %u\n", short_be(hdr->len), f->net_len, proto); - - if (0) { - } else if (pico_ipv6_is_unicast(&hdr->dst)) { - if (pico_ipv6_link_get(&hdr->dst)) { + if (pico_ipv6_is_unicast(&hdr->dst)) { pico_transport_receive(f, f->proto); - } else { - /* not local, try to forward. */ - return pico_ipv6_forward(f); - } } else if (pico_ipv6_is_multicast(hdr->dst.addr)) { /* XXX perform multicast filtering: solicited-node multicast address MUST BE allowed! */ pico_transport_receive(f, f->proto); From 1c2676ce1cbed2ccc54e1cd7a9ee219009c54706 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Sat, 7 Mar 2015 11:49:58 +0100 Subject: [PATCH 41/61] Enabled IPv6 routing test --- test/autotest.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/autotest.sh b/test/autotest.sh index 5ae90b7a3..8ebeac5f9 100755 --- a/test/autotest.sh +++ b/test/autotest.sh @@ -65,12 +65,15 @@ wait || exit 1 wait killall picoapp6.elf -#echo "IPV6 FWD TCP TEST" -#(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::1,ffff::,aaaa::ff,, -a tcpbench,t,aabb::2,6667,,) & -#(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,aaaa::ff,ffff::,,, --vde pic1,/tmp/pic1.ctl,aabb::ff,ffff::,,, -a noop,) & -#./build/test/picoapp6.elf --vde pic0,/tmp/pic1.ctl,aabb::2,ffff::,aabb::ff,, -a tcpbench,r,6667,, || exit 1 -#sleep 2 -#killall picoapp.elf +echo +echo +echo +echo "IPV6 FWD TCP TEST" +(./build/test/picoapp6.elf --vde pic0,/tmp/pic1.ctl,2001:aabb::2,ffff:ffff::,2001:aabb::ff,, -a tcpbench,r,6667,,) & +(./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,2001:aaaa::ff,ffff:ffff::,,, --vde pic1,/tmp/pic1.ctl,2001:aabb::ff,ffff:ffff::,,, -a noop,) & +./build/test/picoapp6.elf --vde pic0,/tmp/pic0.ctl,2001:aaaa::1,ffff:ffff::,2001:aaaa::ff,, -a tcpbench,t,2001:aabb::2,6667,, || exit 1 +sleep 2 +killall picoapp6.elf echo "MULTICAST TEST" From 8b64c8ece3ce21fa2f8ffdfcb1571cb115e72adf Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 11 Mar 2015 11:53:10 +0100 Subject: [PATCH 42/61] Fixed destination interface in ICMP6 parameter problem --- modules/pico_icmp6.c | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/pico_icmp6.c b/modules/pico_icmp6.c index a0c8bf3a2..929d996bc 100644 --- a/modules/pico_icmp6.c +++ b/modules/pico_icmp6.c @@ -182,6 +182,7 @@ static int pico_icmp6_notify(struct pico_frame *f, uint8_t type, uint8_t code, u icmp6_hdr->type = type; icmp6_hdr->code = code; memcpy(notice->payload, f->net_hdr, notice->payload_len); + notice->dev = f->dev; /* f->src is set in frame_push, checksum calculated there */ pico_ipv6_frame_push(notice, &ipv6_hdr->src, PICO_PROTO_ICMP6); return 0; From 34abb9a90ad562f6b4f0767e48fa312a3b45ede2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 11 Mar 2015 14:08:05 +0100 Subject: [PATCH 43/61] Do not send parameter problem upon wrong IPv6 version field (TAHI section 1, test 2) --- stack/pico_stack.c | 1 - 1 file changed, 1 deletion(-) diff --git a/stack/pico_stack.c b/stack/pico_stack.c index b3d6374a6..ca3ca09c7 100644 --- a/stack/pico_stack.c +++ b/stack/pico_stack.c @@ -351,7 +351,6 @@ static int32_t pico_ipv6_ethernet_receive(struct pico_frame *f) pico_enqueue(pico_proto_ipv6.q_in, f); } else { /* Wrong version for link layer type */ - (void)pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 0); pico_frame_discard(f); return -1; } From 914d1293a9add9ddc2b923336d2fd80bd3a4baa3 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 11 Mar 2015 17:33:22 +0100 Subject: [PATCH 44/61] IPv6 ping reply: calculate correct transport size --- modules/pico_icmp6.c | 15 ++------------- modules/pico_ipv6.c | 6 ++++-- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/modules/pico_icmp6.c b/modules/pico_icmp6.c index 929d996bc..ecd1a5d3f 100644 --- a/modules/pico_icmp6.c +++ b/modules/pico_icmp6.c @@ -61,21 +61,10 @@ static int pico_icmp6_process_in(struct pico_protocol *self, struct pico_frame * case PICO_ICMP6_ECHO_REQUEST: icmp6_dbg("ICMP6: Received ECHO REQ\n"); hdr->type = PICO_ICMP6_ECHO_REPLY; - /* XXX these pointers and len should already be set correctly in pico_ipv6_process_in */ - /* Ugly, but the best way to get ICMP data size here. */ - f->transport_len = (uint16_t)(f->buffer_len - PICO_SIZE_IP6HDR); - if (f->dev->eth) - f->transport_len = (uint16_t)(f->transport_len - PICO_SIZE_ETHHDR); - + f->transport_len = (uint16_t)(f->len - f->net_len - (uint16_t)(f->net_hdr - f->buffer)); hdr->crc = 0; hdr->crc = short_be(pico_icmp6_checksum(f)); - - f->net_hdr = f->transport_hdr - PICO_SIZE_IP6HDR; - f->start = f->net_hdr; - f->len = f->buffer_len; - if (f->dev->eth) - f->len -= PICO_SIZE_ETHHDR; - + f->len = (uint32_t)(f->transport_len + f->net_len); pico_ipv6_rebound(f); break; diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index 5c0c22b91..49b90c8df 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -480,6 +480,8 @@ static int pico_ipv6_forward(struct pico_frame *f) return pico_sendto_dev(f); } +#define HBH_LEN(hbh) ((((hbh->ext.hopbyhop.len + 1) << 3) - 2)) /* len in bytes, minus nxthdr and len byte */ + int pico_ipv6_process_hopbyhop(struct pico_ipv6_exthdr *hbh, struct pico_frame *f) { uint8_t *option = NULL; @@ -490,7 +492,7 @@ int pico_ipv6_process_hopbyhop(struct pico_ipv6_exthdr *hbh, struct pico_frame * IGNORE_PARAMETER(f); option = hbh->ext.hopbyhop.options; - len = (uint8_t)(((hbh->ext.hopbyhop.len + 1) << 3) - 2); /* len in bytes, minus nxthdr and len byte */ + len = (uint8_t)HBH_LEN(hbh); ipv6_dbg("IPv6: hop by hop extension header length %u\n", len + 2); while (len) { switch (*option) @@ -715,7 +717,7 @@ int pico_ipv6_process_in(struct pico_protocol *self, struct pico_frame *f) ipv6_dbg("IPv6: payload %u net_len %u nxthdr %u\n", short_be(hdr->len), f->net_len, proto); if (pico_ipv6_is_unicast(&hdr->dst)) { - pico_transport_receive(f, f->proto); + pico_transport_receive(f, f->proto); } else if (pico_ipv6_is_multicast(hdr->dst.addr)) { /* XXX perform multicast filtering: solicited-node multicast address MUST BE allowed! */ pico_transport_receive(f, f->proto); From 73a9d411d6758ca3486aeeee8bd938a7a05f9e06 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 11 Mar 2015 18:42:14 +0100 Subject: [PATCH 45/61] ICMP6: refactoring of echo reply, fixes Tahi 1.22, 1.23 etc. --- modules/pico_icmp6.c | 38 +++++++++++++++++++++++++++++++++----- modules/pico_ipv6.c | 17 ----------------- modules/pico_ipv6.h | 1 - 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/modules/pico_icmp6.c b/modules/pico_icmp6.c index ecd1a5d3f..6633754c9 100644 --- a/modules/pico_icmp6.c +++ b/modules/pico_icmp6.c @@ -43,6 +43,7 @@ uint16_t pico_icmp6_checksum(struct pico_frame *f) #ifdef PICO_SUPPORT_PING static void pico_icmp6_ping_recv_reply(struct pico_frame *f); #endif +static int pico_icmp6_send_echoreply(struct pico_frame *echo); static int pico_icmp6_process_in(struct pico_protocol *self, struct pico_frame *f) { @@ -60,12 +61,9 @@ static int pico_icmp6_process_in(struct pico_protocol *self, struct pico_frame * case PICO_ICMP6_ECHO_REQUEST: icmp6_dbg("ICMP6: Received ECHO REQ\n"); - hdr->type = PICO_ICMP6_ECHO_REPLY; f->transport_len = (uint16_t)(f->len - f->net_len - (uint16_t)(f->net_hdr - f->buffer)); - hdr->crc = 0; - hdr->crc = short_be(pico_icmp6_checksum(f)); - f->len = (uint32_t)(f->transport_len + f->net_len); - pico_ipv6_rebound(f); + pico_icmp6_send_echoreply(f); + pico_frame_discard(f); break; case PICO_ICMP6_ECHO_REPLY: @@ -472,6 +470,36 @@ static int pico_icmp6_send_echo(struct pico_icmp6_ping_cookie *cookie) return 0; } +static int pico_icmp6_send_echoreply(struct pico_frame *echo) +{ + struct pico_frame *reply = NULL; + struct pico_icmp6_hdr *ehdr = NULL, *rhdr = NULL; + struct pico_ip6 src; + + reply = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(echo->transport_len)); + if (!reply) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + echo->payload = echo->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; + reply->payload = reply->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; + reply->payload_len = echo->transport_len; + reply->dev = echo->dev; + + ehdr = (struct pico_icmp6_hdr *)echo->transport_hdr; + rhdr = (struct pico_icmp6_hdr *)reply->transport_hdr; + rhdr->type = PICO_ICMP6_ECHO_REPLY; + rhdr->code = 0; + rhdr->msg.info.echo_reply.id = ehdr->msg.info.echo_reply.id; + rhdr->msg.info.echo_reply.seq = ehdr->msg.info.echo_request.seq; + memcpy(reply->payload, echo->payload, (uint32_t)(echo->transport_len - PICO_ICMP6HDR_ECHO_REQUEST_SIZE)); + rhdr->crc = 0; + rhdr->crc = short_be(pico_icmp6_checksum(reply)); + memcpy(src.addr, ((struct pico_ipv6_hdr *)echo->net_hdr)->src.addr, PICO_SIZE_IP6); + pico_ipv6_frame_push(reply, &src, PICO_PROTO_ICMP6); + return 0; +} + static void pico_icmp6_ping_timeout(pico_time now, void *arg) { struct pico_icmp6_ping_cookie *cookie = NULL; diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index 49b90c8df..a4cdc191d 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -346,23 +346,6 @@ int pico_ipv6_is_unspecified(const uint8_t addr[PICO_SIZE_IP6]) return !memcmp(PICO_IP6_ANY, addr, PICO_SIZE_IP6); } -int pico_ipv6_rebound(struct pico_frame *f) -{ - struct pico_ip6 dst = {{0}}; - struct pico_ipv6_hdr *hdr = NULL; - - if(!f) - return -1; - - hdr = (struct pico_ipv6_hdr *)f->net_hdr; - if (!hdr) - return -1; - - dst = hdr->src; - - return pico_ipv6_frame_push(f, &dst, hdr->nxthdr); -} - static struct pico_ipv6_route *pico_ipv6_route_find(const struct pico_ip6 *addr) { struct pico_ipv6_route *r = NULL; diff --git a/modules/pico_ipv6.h b/modules/pico_ipv6.h index a6b1f92e3..08e12f8e0 100644 --- a/modules/pico_ipv6.h +++ b/modules/pico_ipv6.h @@ -108,7 +108,6 @@ int pico_ipv6_is_solicited(const uint8_t addr[PICO_SIZE_IP6]); int pico_ipv6_is_unspecified(const uint8_t addr[PICO_SIZE_IP6]); int pico_ipv6_frame_push(struct pico_frame *f, struct pico_ip6 *dst, uint8_t proto); -int pico_ipv6_rebound(struct pico_frame *f); int pico_ipv6_route_add(struct pico_ip6 address, struct pico_ip6 netmask, struct pico_ip6 gateway, int metric, struct pico_ipv6_link *link); void pico_ipv6_unreachable(struct pico_frame *f, uint8_t code); From bbb38a7bb6e2b750687d84a5f23778a9506a3c9d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 12 Mar 2015 16:28:15 +0100 Subject: [PATCH 46/61] Moved echoreply function outside #ifdef --- modules/pico_icmp6.c | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/modules/pico_icmp6.c b/modules/pico_icmp6.c index 6633754c9..5f976cd07 100644 --- a/modules/pico_icmp6.c +++ b/modules/pico_icmp6.c @@ -43,7 +43,36 @@ uint16_t pico_icmp6_checksum(struct pico_frame *f) #ifdef PICO_SUPPORT_PING static void pico_icmp6_ping_recv_reply(struct pico_frame *f); #endif -static int pico_icmp6_send_echoreply(struct pico_frame *echo); + +static int pico_icmp6_send_echoreply(struct pico_frame *echo) +{ + struct pico_frame *reply = NULL; + struct pico_icmp6_hdr *ehdr = NULL, *rhdr = NULL; + struct pico_ip6 src; + + reply = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(echo->transport_len)); + if (!reply) { + pico_err = PICO_ERR_ENOMEM; + return -1; + } + echo->payload = echo->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; + reply->payload = reply->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; + reply->payload_len = echo->transport_len; + reply->dev = echo->dev; + + ehdr = (struct pico_icmp6_hdr *)echo->transport_hdr; + rhdr = (struct pico_icmp6_hdr *)reply->transport_hdr; + rhdr->type = PICO_ICMP6_ECHO_REPLY; + rhdr->code = 0; + rhdr->msg.info.echo_reply.id = ehdr->msg.info.echo_reply.id; + rhdr->msg.info.echo_reply.seq = ehdr->msg.info.echo_request.seq; + memcpy(reply->payload, echo->payload, (uint32_t)(echo->transport_len - PICO_ICMP6HDR_ECHO_REQUEST_SIZE)); + rhdr->crc = 0; + rhdr->crc = short_be(pico_icmp6_checksum(reply)); + memcpy(src.addr, ((struct pico_ipv6_hdr *)echo->net_hdr)->src.addr, PICO_SIZE_IP6); + pico_ipv6_frame_push(reply, &src, PICO_PROTO_ICMP6); + return 0; +} static int pico_icmp6_process_in(struct pico_protocol *self, struct pico_frame *f) { @@ -470,35 +499,6 @@ static int pico_icmp6_send_echo(struct pico_icmp6_ping_cookie *cookie) return 0; } -static int pico_icmp6_send_echoreply(struct pico_frame *echo) -{ - struct pico_frame *reply = NULL; - struct pico_icmp6_hdr *ehdr = NULL, *rhdr = NULL; - struct pico_ip6 src; - - reply = pico_proto_ipv6.alloc(&pico_proto_ipv6, (uint16_t)(echo->transport_len)); - if (!reply) { - pico_err = PICO_ERR_ENOMEM; - return -1; - } - echo->payload = echo->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; - reply->payload = reply->transport_hdr + PICO_ICMP6HDR_ECHO_REQUEST_SIZE; - reply->payload_len = echo->transport_len; - reply->dev = echo->dev; - - ehdr = (struct pico_icmp6_hdr *)echo->transport_hdr; - rhdr = (struct pico_icmp6_hdr *)reply->transport_hdr; - rhdr->type = PICO_ICMP6_ECHO_REPLY; - rhdr->code = 0; - rhdr->msg.info.echo_reply.id = ehdr->msg.info.echo_reply.id; - rhdr->msg.info.echo_reply.seq = ehdr->msg.info.echo_request.seq; - memcpy(reply->payload, echo->payload, (uint32_t)(echo->transport_len - PICO_ICMP6HDR_ECHO_REQUEST_SIZE)); - rhdr->crc = 0; - rhdr->crc = short_be(pico_icmp6_checksum(reply)); - memcpy(src.addr, ((struct pico_ipv6_hdr *)echo->net_hdr)->src.addr, PICO_SIZE_IP6); - pico_ipv6_frame_push(reply, &src, PICO_PROTO_ICMP6); - return 0; -} static void pico_icmp6_ping_timeout(pico_time now, void *arg) { From 64fa4110b00bcc0736ddd6cc03c2c28e80075cad Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 12 Mar 2015 19:31:10 +0100 Subject: [PATCH 47/61] IPv6: Stub of fragmentation, several Tahi test cases fixed. --- modules/pico_ipv6.c | 164 +++++++++++++++++++++++++++++--------------- modules/pico_ipv6.h | 7 +- 2 files changed, 113 insertions(+), 58 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index a4cdc191d..c3d2b9668 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -503,8 +503,8 @@ int pico_ipv6_process_hopbyhop(struct pico_ipv6_exthdr *hbh, struct pico_frame * pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, ptr + (uint32_t)(option - extensions_start)); return -1; case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SINM: - /* TODO DLA: check if not multicast */ - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, ptr + (uint32_t)(option - extensions_start)); + if (!pico_ipv6_is_multicast(((struct pico_ipv6_hdr *)(f->net_hdr))->dst.addr)) + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, ptr + (uint32_t)(option - extensions_start)); return -1; } ipv6_dbg("IPv6: option with type %u and length %u\n", *option, optlen); @@ -535,116 +535,166 @@ int pico_ipv6_process_routing(struct pico_ipv6_exthdr *routing, struct pico_fram return 0; } -int pico_ipv6_process_frag(struct pico_ipv6_exthdr *fragm, struct pico_frame *f) + +#define IP6FRAG_OFF(x) ((short_be(x) & 0xFFF8) >> 3) +#define IP6FRAG_MORE(x) ((short_be(x) & 0x01)) + +static int pico_ipv6_frag_compare(void *ka, void *kb) { - IGNORE_PARAMETER(fragm); - if (!f) { - pico_err = PICO_ERR_EINVAL; + struct pico_frame *a = ka, *b = kb; + if (IP6FRAG_OFF(a->frag) > IP6FRAG_OFF(b->frag)) + return 1; + if (IP6FRAG_OFF(a->frag) < IP6FRAG_OFF(b->frag)) return -1; - } - ipv6_dbg("IPv6: fragmentation extension header\n"); return 0; } +PICO_TREE_DECLARE(ipv6_fragments, pico_ipv6_frag_compare); + +static struct pico_frame *pico_ipv6_alloc(struct pico_protocol *self, uint16_t size); + +static void pico_ipv6_fragments_complete(unsigned int len) +{ + struct pico_tree_node *index, *tmp; + struct pico_frame *f; + unsigned int bookmark = 0; + struct pico_frame *full = NULL; + struct pico_frame *first = pico_tree_first(&ipv6_fragments); + + full = pico_ipv6_alloc(&pico_proto_ipv6, (uint16_t)(PICO_SIZE_IP6HDR + len)); + if (full) { + uint8_t proto = ((struct pico_ipv6_hdr *)first->net_hdr)->nxthdr; + memcpy(full->net_hdr, first->net_hdr, first->net_len); + pico_tree_foreach_safe(index, &ipv6_fragments, tmp) { + f = index->keyValue; + memcpy(full->transport_hdr + bookmark, f->transport_hdr, f->transport_len); + bookmark += f->transport_len; + pico_tree_delete(&ipv6_fragments, f); + pico_frame_discard(f); + } + pico_transport_receive(full, proto); + } +} + +static void pico_ipv6_fragments_check_complete(void) +{ + struct pico_tree_node *index; + struct pico_frame *cur; + unsigned int bookmark = 0; + pico_tree_foreach(index, &ipv6_fragments) { + cur = index->keyValue; + if (IP6FRAG_OFF(cur->frag) != bookmark) + return; + bookmark += cur->transport_len; + if (!IP6FRAG_MORE(cur->frag)) { + pico_ipv6_fragments_complete(bookmark); + } + } +} + +static void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f) +{ + f->frag = frag->ext.frag.om; + pico_tree_insert(&ipv6_fragments, pico_frame_copy(f)); + pico_ipv6_fragments_check_complete(); +} -int pico_ipv6_process_destopt(struct pico_ipv6_exthdr *destopt, struct pico_frame *f) +static int pico_ipv6_process_destopt(struct pico_ipv6_exthdr *destopt, struct pico_frame *f, uint32_t opt_ptr) { uint8_t *option = NULL; uint8_t len = 0, optlen = 0; - + opt_ptr += (uint32_t)(2u); /* Skip Dest_opts header */ IGNORE_PARAMETER(f); option = destopt->ext.destopt.options; len = (uint8_t)(((destopt->ext.destopt.len + 1) << 3) - 2); /* len in bytes, minus nxthdr and len byte */ ipv6_dbg("IPv6: destination option extension header length %u\n", len + 2); while (len) { + optlen = (uint8_t)(*(option + 1) + 2); switch (*option) { case PICO_IPV6_EXTHDR_OPT_PAD1: - ++option; - --len; break; case PICO_IPV6_EXTHDR_OPT_PADN: - optlen = (uint8_t)(*(option + 1) + 2); /* plus type and len byte */ - option += optlen; - len = (uint8_t)(len - optlen); break; case PICO_IPV6_EXTHDR_OPT_SRCADDR: - optlen = (uint8_t)(*(option + 1) + 2); /* plus type and len byte */ - option += optlen; - len = (uint8_t)(len - optlen); /* 2 = 1 byte for option type and 1 byte for option length */ ipv6_dbg("IPv6: home address option with length %u\n", optlen); break; default: - optlen = *(option + 1); - ipv6_dbg("IPv6: option with type %u and length %u\n", *option, optlen + 2); - switch ((*option) & PICO_IPV6_EXTHDR_OPT_ACTION_MASK) { + ipv6_dbg("IPv6: option with type %u and length %u\n", *option, optlen); + switch (*option & PICO_IPV6_EXTHDR_OPT_ACTION_MASK) { case PICO_IPV6_EXTHDR_OPT_ACTION_SKIP: break; case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD: return -1; case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SI: - /* XXX: send ICMP parameter problem (code 2), pointing to the unrecognized option type */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, opt_ptr); return -1; case PICO_IPV6_EXTHDR_OPT_ACTION_DISCARD_SINM: - /* XXX: if destination address was not a multicast address, send an ICMP parameter problem (code 2) */ + if (!pico_ipv6_is_multicast(((struct pico_ipv6_hdr *)(f->net_hdr))->dst.addr)) + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, opt_ptr); return -1; } - option += optlen + 2; - len = (uint8_t)(len - optlen + 2); /* 2 = 1 byte for option type and 1 byte for option length */ break; } + opt_ptr += optlen; + option += optlen; + len = (uint8_t)(len - optlen); } return 0; } +#define IPV6_OPTLEN(x) ((uint16_t)(((x + 1) << 3))) + static int pico_ipv6_extension_headers(struct pico_frame *f) { struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; uint8_t nxthdr = hdr->nxthdr; struct pico_ipv6_exthdr *exthdr = NULL; uint32_t ptr = sizeof(struct pico_ipv6_hdr); - int is_ipv6_hdr = 1; /* ==1 indicates that the option being parsed is in the header, - * rather than in an extension. - */ + struct pico_ipv6_exthdr *frag_hdr = NULL; + uint16_t cur_optlen; + uint32_t cur_nexthdr = 6; f->net_len = sizeof(struct pico_ipv6_hdr); - for (;; ) { + for (;;) { exthdr = (struct pico_ipv6_exthdr *)(f->net_hdr + f->net_len); + cur_optlen = 0; + switch (nxthdr) { case PICO_IPV6_EXTHDR_HOPBYHOP: - /* The Hop-by-Hop Options header, - * when present, must immediately follow the IPv6 header. - */ - if (!is_ipv6_hdr) { - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, ptr); + if (cur_nexthdr != 6) { + /* The Hop-by-Hop Options header, + * when present, must immediately follow the IPv6 header. + */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, cur_nexthdr); return -1; } - - f->net_len = (uint16_t)(f->net_len + ((exthdr->ext.hopbyhop.len + 1) << 3)); + cur_optlen = IPV6_OPTLEN(exthdr->ext.hopbyhop.len); + f->net_len += cur_optlen; if (pico_ipv6_process_hopbyhop(exthdr, f) < 0) return -1; break; case PICO_IPV6_EXTHDR_ROUTING: - f->net_len = (uint16_t)(f->net_len + ((exthdr->ext.routing.len + 1) << 3)); + cur_optlen = IPV6_OPTLEN(exthdr->ext.routing.len); + f->net_len += cur_optlen; if (pico_ipv6_process_routing(exthdr, f) < 0) return -1; break; case PICO_IPV6_EXTHDR_FRAG: - f->net_len = (uint16_t)(f->net_len + 8); /* fixed length */ - if (pico_ipv6_process_frag(exthdr, f) < 0) - return -1; - + frag_hdr = exthdr; + cur_optlen = 8u; + f->net_len += cur_optlen; break; case PICO_IPV6_EXTHDR_DESTOPT: - f->net_len = (uint16_t)(f->net_len + ((exthdr->ext.destopt.len + 1) << 3)); - if (pico_ipv6_process_destopt(exthdr, f) < 0) + cur_optlen = IPV6_OPTLEN(exthdr->ext.destopt.len); + f->net_len += cur_optlen; + if (pico_ipv6_process_destopt(exthdr, f, ptr) < 0) return -1; - break; case PICO_IPV6_EXTHDR_ESP: /* not supported, ignored. */ @@ -661,20 +711,25 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) case PICO_PROTO_ICMP6: f->transport_hdr = f->net_hdr + f->net_len; f->transport_len = (uint16_t)(short_be(hdr->len) - (f->net_len - sizeof(struct pico_ipv6_hdr))); - return nxthdr; + if (!frag_hdr) + return nxthdr; + else { + pico_ipv6_process_frag(frag_hdr, f); + return 0; + } default: - if (is_ipv6_hdr) - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, 6); /* 6 is the pos of next hdr field */ - else - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, ptr); - + /* Invalid next header */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, cur_nexthdr); + return -1; + } + /* If the packet contains extension headers, the payload must be aligned. */ + if ((cur_nexthdr == 6) && (short_be(hdr->len) % 8 ) != 0) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); return -1; } nxthdr = exthdr->nxthdr; - if (!is_ipv6_hdr) - ptr += (uint32_t)sizeof(struct pico_ipv6_exthdr); - - is_ipv6_hdr = 0; + cur_nexthdr = ptr; + ptr += cur_optlen; } } @@ -684,6 +739,7 @@ int pico_ipv6_process_in(struct pico_protocol *self, struct pico_frame *f) struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; IGNORE_PARAMETER(self); + /* TODO: Check hop-by-hop hdr before forwarding */ if (pico_ipv6_is_unicast(&hdr->dst) && !pico_ipv6_link_get(&hdr->dst)) { /* not local, try to forward. */ diff --git a/modules/pico_ipv6.h b/modules/pico_ipv6.h index 08e12f8e0..392274394 100644 --- a/modules/pico_ipv6.h +++ b/modules/pico_ipv6.h @@ -87,11 +87,10 @@ PACKED_STRUCT_DEF pico_ipv6_exthdr { uint8_t segleft; } routing; - PEDANTIC_STRUCT_DEF fragm_s { + PEDANTIC_STRUCT_DEF fragmentation_s { uint8_t res; - uint8_t frm[2]; - uint8_t id[4]; - } fragm; + uint16_t om; + } frag; } ext; }; From 5892670fe9337dbc422484d544a9a0718c568a39 Mon Sep 17 00:00:00 2001 From: Sam Van Den Berge Date: Fri, 13 Mar 2015 08:46:03 +0100 Subject: [PATCH 48/61] Allow make to execute several recipes at once. This can improve compilation times of an application. $ make clean &> /dev/null $ time make &> /dev/null real 0m2.875s user 0m2.229s sys 0m0.714s $ make clean &> /dev/null $ time make -j 4 &> /dev/null real 0m1.212s user 0m3.308s sys 0m0.674s $ --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 887314779..d6f1fac88 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,7 @@ ifeq ($(ARCH),shared) CFLAGS+=-fPIC endif -.c.o: +%.o:%.c deps $(CC) -c $(CFLAGS) -o $@ $< CORE_OBJ= stack/pico_stack.o \ @@ -290,11 +290,11 @@ endif all: mod core lib -core: deps $(CORE_OBJ) +core: $(CORE_OBJ) @mkdir -p $(PREFIX)/lib @mv stack/*.o $(PREFIX)/lib -mod: deps $(MOD_OBJ) +mod: $(MOD_OBJ) @mkdir -p $(PREFIX)/modules @mv modules/*.o $(PREFIX)/modules || echo From 7731efb4a79e471b6968bf59d3087bfc1021493e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2015 12:05:19 +0100 Subject: [PATCH 49/61] IPv6: Receive fragmented packets - first version --- modules/pico_ipv6.c | 37 ++++++++++++++++++++----------------- modules/pico_ipv6.h | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index c3d2b9668..f2d68412a 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -536,8 +536,8 @@ int pico_ipv6_process_routing(struct pico_ipv6_exthdr *routing, struct pico_fram } -#define IP6FRAG_OFF(x) ((short_be(x) & 0xFFF8) >> 3) -#define IP6FRAG_MORE(x) ((short_be(x) & 0x01)) +#define IP6FRAG_OFF(x) ((x & 0xFFF8)) +#define IP6FRAG_MORE(x) ((x & 0x0001)) static int pico_ipv6_frag_compare(void *ka, void *kb) { @@ -550,9 +550,8 @@ static int pico_ipv6_frag_compare(void *ka, void *kb) } PICO_TREE_DECLARE(ipv6_fragments, pico_ipv6_frag_compare); -static struct pico_frame *pico_ipv6_alloc(struct pico_protocol *self, uint16_t size); -static void pico_ipv6_fragments_complete(unsigned int len) +static void pico_ipv6_fragments_complete(unsigned int len, uint8_t proto) { struct pico_tree_node *index, *tmp; struct pico_frame *f; @@ -560,10 +559,14 @@ static void pico_ipv6_fragments_complete(unsigned int len) struct pico_frame *full = NULL; struct pico_frame *first = pico_tree_first(&ipv6_fragments); - full = pico_ipv6_alloc(&pico_proto_ipv6, (uint16_t)(PICO_SIZE_IP6HDR + len)); + full = pico_frame_alloc((uint16_t)(PICO_SIZE_IP6HDR + len)); + full->net_hdr = full->buffer; + full->net_len = PICO_SIZE_IP6HDR; if (full) { - uint8_t proto = ((struct pico_ipv6_hdr *)first->net_hdr)->nxthdr; - memcpy(full->net_hdr, first->net_hdr, first->net_len); + memcpy(full->net_hdr, first->net_hdr, full->net_len); + full->transport_hdr = full->net_hdr + full->net_len; + full->transport_len = (uint16_t)len; + full->dev = first->dev; pico_tree_foreach_safe(index, &ipv6_fragments, tmp) { f = index->keyValue; memcpy(full->transport_hdr + bookmark, f->transport_hdr, f->transport_len); @@ -575,7 +578,7 @@ static void pico_ipv6_fragments_complete(unsigned int len) } } -static void pico_ipv6_fragments_check_complete(void) +static void pico_ipv6_fragments_check_complete(uint8_t proto) { struct pico_tree_node *index; struct pico_frame *cur; @@ -586,16 +589,16 @@ static void pico_ipv6_fragments_check_complete(void) return; bookmark += cur->transport_len; if (!IP6FRAG_MORE(cur->frag)) { - pico_ipv6_fragments_complete(bookmark); + pico_ipv6_fragments_complete(bookmark, proto); } } } -static void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f) +static void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f, uint8_t proto) { - f->frag = frag->ext.frag.om; + f->frag = (uint16_t)((frag->ext.frag.om[0] << 8) + frag->ext.frag.om[1]); pico_tree_insert(&ipv6_fragments, pico_frame_copy(f)); - pico_ipv6_fragments_check_complete(); + pico_ipv6_fragments_check_complete(proto); } static int pico_ipv6_process_destopt(struct pico_ipv6_exthdr *destopt, struct pico_frame *f, uint32_t opt_ptr) @@ -673,14 +676,14 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) return -1; } cur_optlen = IPV6_OPTLEN(exthdr->ext.hopbyhop.len); - f->net_len += cur_optlen; + f->net_len = (uint16_t) (f->net_len + cur_optlen); if (pico_ipv6_process_hopbyhop(exthdr, f) < 0) return -1; break; case PICO_IPV6_EXTHDR_ROUTING: cur_optlen = IPV6_OPTLEN(exthdr->ext.routing.len); - f->net_len += cur_optlen; + f->net_len = (uint16_t) (f->net_len + cur_optlen); if (pico_ipv6_process_routing(exthdr, f) < 0) return -1; @@ -688,11 +691,11 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) case PICO_IPV6_EXTHDR_FRAG: frag_hdr = exthdr; cur_optlen = 8u; - f->net_len += cur_optlen; + f->net_len = (uint16_t) (f->net_len + cur_optlen); break; case PICO_IPV6_EXTHDR_DESTOPT: cur_optlen = IPV6_OPTLEN(exthdr->ext.destopt.len); - f->net_len += cur_optlen; + f->net_len = (uint16_t) (f->net_len + cur_optlen); if (pico_ipv6_process_destopt(exthdr, f, ptr) < 0) return -1; break; @@ -714,7 +717,7 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) if (!frag_hdr) return nxthdr; else { - pico_ipv6_process_frag(frag_hdr, f); + pico_ipv6_process_frag(frag_hdr, f, nxthdr); return 0; } default: diff --git a/modules/pico_ipv6.h b/modules/pico_ipv6.h index 392274394..81a9d55ea 100644 --- a/modules/pico_ipv6.h +++ b/modules/pico_ipv6.h @@ -89,7 +89,7 @@ PACKED_STRUCT_DEF pico_ipv6_exthdr { PEDANTIC_STRUCT_DEF fragmentation_s { uint8_t res; - uint16_t om; + uint8_t om[2]; } frag; } ext; }; From a09cd3b7df6271585e2dceeb017fc3756d22ee1b Mon Sep 17 00:00:00 2001 From: Sam Van Den Berge Date: Fri, 13 Mar 2015 12:56:14 +0100 Subject: [PATCH 50/61] Fixed code layout --- modules/pico_dhcp_client.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/pico_dhcp_client.c b/modules/pico_dhcp_client.c index 948cbd83a..3f148150d 100644 --- a/modules/pico_dhcp_client.c +++ b/modules/pico_dhcp_client.c @@ -668,8 +668,7 @@ struct dhcp_action_entry { static struct dhcp_action_entry dhcp_fsm[] = { /* event |offer |ack |nak |T1 |T2 |lease |retransmit */ -/* state init-reboot */ - { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, +/* state init-reboot */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, /* state rebooting */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, /* state init */ { recv_offer, NULL, NULL, NULL, NULL, NULL, retransmit }, /* state selecting */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, From 5df721ccda70e22c38b3ac2c9bcd8a6f9eb18ec5 Mon Sep 17 00:00:00 2001 From: Sam Van Den Berge Date: Fri, 13 Mar 2015 13:13:38 +0100 Subject: [PATCH 51/61] Removed declaration of unused function --- modules/pico_dhcp_client.h | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/pico_dhcp_client.h b/modules/pico_dhcp_client.h index bf493ae72..02fe7e0f5 100644 --- a/modules/pico_dhcp_client.h +++ b/modules/pico_dhcp_client.h @@ -14,7 +14,6 @@ #include "pico_protocol.h" int pico_dhcp_initiate_negotiation(struct pico_device *device, void (*callback)(void*cli, int code), uint32_t *xid); -void pico_dhcp_process_incoming_message(uint8_t *data, int len); void *pico_dhcp_get_identifier(uint32_t xid); struct pico_ip4 pico_dhcp_get_address(void *cli); struct pico_ip4 pico_dhcp_get_gateway(void *cli); From e47c678aca9a19be91c9018546eddb35759b50f9 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2015 15:26:03 +0100 Subject: [PATCH 52/61] ICMP6 TIME EXCEEDED added for frame reassebly failure. --- modules/pico_icmp6.c | 6 +++- modules/pico_icmp6.h | 1 + modules/pico_ipv6.c | 65 +++++++++++++++++++++++++++++++++++++++++++- modules/pico_ipv6.h | 1 + stack/pico_stack.c | 1 - 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/modules/pico_icmp6.c b/modules/pico_icmp6.c index 5f976cd07..9ca6129c6 100644 --- a/modules/pico_icmp6.c +++ b/modules/pico_icmp6.c @@ -190,7 +190,6 @@ static int pico_icmp6_notify(struct pico_frame *f, uint8_t type, uint8_t code, u icmp6_hdr->msg.err.param_problem.ptr = long_be(ptr); break; - default: return -1; } @@ -241,6 +240,11 @@ int pico_icmp6_parameter_problem(struct pico_frame *f, uint8_t problem, uint32_t return pico_icmp6_notify(f, PICO_ICMP6_PARAM_PROBLEM, problem, ptr); } +int pico_icmp6_frag_expired(struct pico_frame *f) +{ + return pico_icmp6_notify(f, PICO_ICMP6_TIME_EXCEEDED, PICO_ICMP6_TIMXCEED_REASS, 0); +} + /* RFC 4861 $7.2.2: sending neighbor solicitations */ int pico_icmp6_neighbor_solicitation(struct pico_device *dev, struct pico_ip6 *dst, uint8_t type) { diff --git a/modules/pico_icmp6.h b/modules/pico_icmp6.h index 37de5acb3..0e5ce5d5a 100644 --- a/modules/pico_icmp6.h +++ b/modules/pico_icmp6.h @@ -251,6 +251,7 @@ int pico_icmp6_ttl_expired(struct pico_frame *f); int pico_icmp6_packet_filtered(struct pico_frame *f); int pico_icmp6_parameter_problem(struct pico_frame *f, uint8_t problem, uint32_t ptr); int pico_icmp6_pkt_too_big(struct pico_frame *f); +int pico_icmp6_frag_expired(struct pico_frame *f); uint16_t pico_icmp6_checksum(struct pico_frame *f); int pico_icmp6_router_advertisement(struct pico_device *dev, struct pico_ip6 *dst); diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index f2d68412a..c889ea1b4 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -549,6 +549,7 @@ static int pico_ipv6_frag_compare(void *ka, void *kb) return 0; } PICO_TREE_DECLARE(ipv6_fragments, pico_ipv6_frag_compare); +struct pico_timer *ipv6_fragments_timer = NULL; static void pico_ipv6_fragments_complete(unsigned int len, uint8_t proto) @@ -575,6 +576,10 @@ static void pico_ipv6_fragments_complete(unsigned int len, uint8_t proto) pico_frame_discard(f); } pico_transport_receive(full, proto); + if (ipv6_fragments_timer) { + pico_timer_cancel(ipv6_fragments_timer); + ipv6_fragments_timer = NULL; + } } } @@ -594,10 +599,68 @@ static void pico_ipv6_fragments_check_complete(uint8_t proto) } } +static void pico_ipv6_frag_expire(pico_time now, void *arg) +{ + struct pico_tree_node *index, *tmp; + struct pico_frame *f; + struct pico_frame *first = pico_tree_first(&ipv6_fragments); + (void)arg; + (void)now; + if (!first) { + return; + } + + /* Empty the tree */ + pico_tree_foreach_safe(index, &ipv6_fragments, tmp) { + f = index->keyValue; + pico_tree_delete(&ipv6_fragments, f); + if (f != first) + pico_frame_discard(f); /* Later, after ICMP notification...*/ + } + pico_icmp6_frag_expired(first); + pico_frame_discard(first); +} + +#define PICO_IPV6_FRAG_TIMEOUT 60000 + +#define FRAG_ID(x) ((uint32_t)((x->ext.frag.id[0] << 24) + (x->ext.frag.id[1] << 16) + \ + (x->ext.frag.id[2] << 8) + x->ext.frag.id[3])) +static void pico_ipv6_frag_timer_on(void) +{ + ipv6_fragments_timer = pico_timer_add(PICO_IPV6_FRAG_TIMEOUT, pico_ipv6_frag_expire, NULL); +} + +static int pico_ipv6_frag_match(struct pico_frame *a, struct pico_frame *b) +{ + struct pico_ipv6_hdr *ha, *hb; + if (!a || !b) + return 0; + ha = (struct pico_ipv6_hdr *)a->net_hdr; + hb = (struct pico_ipv6_hdr *)b->net_hdr; + if (!ha || !hb) + return 0; + + if (memcmp(ha->src.addr, hb->src.addr, PICO_SIZE_IP6) != 0) + return 0; + + if (memcmp(ha->dst.addr, hb->dst.addr, PICO_SIZE_IP6) != 0) + return 0; + + return 1; +} + static void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f, uint8_t proto) { + struct pico_frame *first = pico_tree_first(&ipv6_fragments); + static uint32_t ipv6_cur_frag_id = 0u; f->frag = (uint16_t)((frag->ext.frag.om[0] << 8) + frag->ext.frag.om[1]); - pico_tree_insert(&ipv6_fragments, pico_frame_copy(f)); + if (!first) { + pico_ipv6_frag_timer_on(); + ipv6_cur_frag_id = FRAG_ID(frag); + } + if (!first || (pico_ipv6_frag_match(f, first) && (FRAG_ID(frag) == ipv6_cur_frag_id))) { + pico_tree_insert(&ipv6_fragments, pico_frame_copy(f)); + } pico_ipv6_fragments_check_complete(proto); } diff --git a/modules/pico_ipv6.h b/modules/pico_ipv6.h index 81a9d55ea..66d78f7cd 100644 --- a/modules/pico_ipv6.h +++ b/modules/pico_ipv6.h @@ -90,6 +90,7 @@ PACKED_STRUCT_DEF pico_ipv6_exthdr { PEDANTIC_STRUCT_DEF fragmentation_s { uint8_t res; uint8_t om[2]; + uint8_t id[4]; } frag; } ext; }; diff --git a/stack/pico_stack.c b/stack/pico_stack.c index ca3ca09c7..25de63a22 100644 --- a/stack/pico_stack.c +++ b/stack/pico_stack.c @@ -174,7 +174,6 @@ int pico_notify_pkt_too_big(struct pico_frame *f) } - /* Transport layer */ int32_t pico_transport_receive(struct pico_frame *f, uint8_t proto) { From fb9ba14d061fc0a3fc6ddf3d868f5e09974777ff Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2015 20:29:59 +0100 Subject: [PATCH 53/61] IPV6: Fixed fragmentation, fixed alignment checks Only 5 failures left in TAHI section 1. --- modules/pico_ipv6.c | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index c889ea1b4..0100e4821 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -516,7 +516,7 @@ int pico_ipv6_process_hopbyhop(struct pico_ipv6_exthdr *hbh, struct pico_frame * } -int pico_ipv6_process_routing(struct pico_ipv6_exthdr *routing, struct pico_frame *f) +int pico_ipv6_process_routing(struct pico_ipv6_exthdr *routing, struct pico_frame *f, uint32_t ptr) { IGNORE_PARAMETER(f); @@ -524,12 +524,13 @@ int pico_ipv6_process_routing(struct pico_ipv6_exthdr *routing, struct pico_fram switch (routing->ext.routing.routtype) { case 0x00: /* deprecated */ - break; + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, ptr + 2); + return -1; case 0x02: /* routing type for MIPv6: not supported yet */ break; default: - /* XXX: ICMP parameter problem (code 0) */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, ptr + 2); return -1; } return 0; @@ -551,7 +552,6 @@ static int pico_ipv6_frag_compare(void *ka, void *kb) PICO_TREE_DECLARE(ipv6_fragments, pico_ipv6_frag_compare); struct pico_timer *ipv6_fragments_timer = NULL; - static void pico_ipv6_fragments_complete(unsigned int len, uint8_t proto) { struct pico_tree_node *index, *tmp; @@ -617,7 +617,8 @@ static void pico_ipv6_frag_expire(pico_time now, void *arg) if (f != first) pico_frame_discard(f); /* Later, after ICMP notification...*/ } - pico_icmp6_frag_expired(first); + if (IP6FRAG_OFF(first->frag) == 0) + pico_icmp6_frag_expired(first); pico_frame_discard(first); } @@ -652,9 +653,23 @@ static int pico_ipv6_frag_match(struct pico_frame *a, struct pico_frame *b) static void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f, uint8_t proto) { struct pico_frame *first = pico_tree_first(&ipv6_fragments); + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; static uint32_t ipv6_cur_frag_id = 0u; f->frag = (uint16_t)((frag->ext.frag.om[0] << 8) + frag->ext.frag.om[1]); + + /* If M-Flag is set, and packet is not 8B aligned, discard and alert */ + if (IP6FRAG_MORE(f->frag) && ((short_be(hdr->len) % 8) != 0)) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + return; + } + if (!first) { + if (ipv6_cur_frag_id && (FRAG_ID(frag) == ipv6_cur_frag_id)) { + /* Discard late arrivals, without firing the timer, + * just to make TAHI happy in-between two consecutive tests + */ + return; + } pico_ipv6_frag_timer_on(); ipv6_cur_frag_id = FRAG_ID(frag); } @@ -723,6 +738,7 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) struct pico_ipv6_exthdr *frag_hdr = NULL; uint16_t cur_optlen; uint32_t cur_nexthdr = 6; + int must_align = 0; f->net_len = sizeof(struct pico_ipv6_hdr); for (;;) { @@ -742,12 +758,13 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) f->net_len = (uint16_t) (f->net_len + cur_optlen); if (pico_ipv6_process_hopbyhop(exthdr, f) < 0) return -1; + must_align = 1; break; case PICO_IPV6_EXTHDR_ROUTING: cur_optlen = IPV6_OPTLEN(exthdr->ext.routing.len); f->net_len = (uint16_t) (f->net_len + cur_optlen); - if (pico_ipv6_process_routing(exthdr, f) < 0) + if (pico_ipv6_process_routing(exthdr, f, ptr) < 0) return -1; break; @@ -761,6 +778,7 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) f->net_len = (uint16_t) (f->net_len + cur_optlen); if (pico_ipv6_process_destopt(exthdr, f, ptr) < 0) return -1; + must_align = 1; break; case PICO_IPV6_EXTHDR_ESP: /* not supported, ignored. */ @@ -770,6 +788,10 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) return 0; case PICO_IPV6_EXTHDR_NONE: /* no next header */ + if ((must_align) && ((short_be(hdr->len) % 8) != 0)) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + return -1; + } return 0; case PICO_PROTO_TCP: @@ -777,6 +799,10 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) case PICO_PROTO_ICMP6: f->transport_hdr = f->net_hdr + f->net_len; f->transport_len = (uint16_t)(short_be(hdr->len) - (f->net_len - sizeof(struct pico_ipv6_hdr))); + if ((must_align) && ((short_be(hdr->len) % 8) != 0)) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + return -1; + } if (!frag_hdr) return nxthdr; else { @@ -788,11 +814,6 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, cur_nexthdr); return -1; } - /* If the packet contains extension headers, the payload must be aligned. */ - if ((cur_nexthdr == 6) && (short_be(hdr->len) % 8 ) != 0) { - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); - return -1; - } nxthdr = exthdr->nxthdr; cur_nexthdr = ptr; ptr += cur_optlen; From e98a3463ef971af3dccb0cfe4a9ffa31ee3fbeda Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 16 Mar 2015 12:12:57 +0100 Subject: [PATCH 54/61] Fix IPv6 routing: do not look for routes on link-local addresses --- modules/pico_ipv6.c | 29 +++++++++++++++++++++++++++++ modules/pico_ipv6.h | 2 ++ modules/pico_ipv6_nd.c | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index 0100e4821..4e0457bc0 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -352,6 +352,9 @@ static struct pico_ipv6_route *pico_ipv6_route_find(const struct pico_ip6 *addr) struct pico_tree_node *index = NULL; int i = 0; + if (pico_ipv6_is_linklocal(addr->addr) || pico_ipv6_is_multicast(addr->addr) || pico_ipv6_is_sitelocal(addr->addr)) + return NULL; + pico_tree_foreach_reverse(index, &IPV6Routes) { r = index->keyValue; @@ -958,12 +961,22 @@ static inline void ipv6_push_hdr_adjust(struct pico_frame *f, struct pico_ipv6_l icmp6_hdr->crc = short_be(pico_icmp6_checksum(f)); break; } +#ifdef PICO_SUPPORT_UDP case PICO_PROTO_UDP: { struct pico_udp_hdr *udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; udp_hdr->crc = pico_udp_checksum_ipv6(f); break; } +#endif +#ifdef PICO_SUPPORT_TCP + case PICO_PROTO_TCP: + { + struct pico_tcp_hdr *tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; + tcp_hdr->crc = pico_tcp_checksum_ipv6(f); + break; + } +#endif default: break; @@ -1092,6 +1105,7 @@ static inline struct pico_ipv6_route *ipv6_route_add_link(struct pico_ip6 gatewa return NULL; } + return r; } @@ -1134,12 +1148,17 @@ int pico_ipv6_route_add(struct pico_ip6 address, struct pico_ip6 netmask, struct new->link = r->link; } + if (new->link && (!pico_ipv6_is_global(new->link->address.addr))) { + new->link = pico_ipv6_global_get(new->link->dev); + } + if (!new->link) { pico_err = PICO_ERR_EINVAL; PICO_FREE(new); return -1; } + pico_tree_insert(&IPV6Routes, new); pico_ipv6_dbg_route(); return 0; @@ -1455,6 +1474,16 @@ struct pico_ipv6_link *pico_ipv6_linklocal_get(struct pico_device *dev) return link; } +struct pico_ipv6_link *pico_ipv6_global_get(struct pico_device *dev) +{ + struct pico_ipv6_link *link = pico_ipv6_link_by_dev(dev); + while (link && !pico_ipv6_is_global(link->address.addr)) { + link = pico_ipv6_link_by_dev_next(dev, link); + } + return link; +} + + int pico_ipv6_dev_routing_enable(struct pico_device *dev) { diff --git a/modules/pico_ipv6.h b/modules/pico_ipv6.h index 66d78f7cd..d1f3d74a2 100644 --- a/modules/pico_ipv6.h +++ b/modules/pico_ipv6.h @@ -122,6 +122,8 @@ struct pico_ip6 *pico_ipv6_source_find(const struct pico_ip6 *dst); struct pico_device *pico_ipv6_source_dev_find(const struct pico_ip6 *dst); struct pico_ipv6_link *pico_ipv6_link_by_dev(struct pico_device *dev); struct pico_ipv6_link *pico_ipv6_link_by_dev_next(struct pico_device *dev, struct pico_ipv6_link *last); +struct pico_ipv6_link *pico_ipv6_global_get(struct pico_device *dev); +struct pico_ipv6_link *pico_ipv6_linklocal_get(struct pico_device *dev); int pico_ipv6_dev_routing_enable(struct pico_device *dev); int pico_ipv6_dev_routing_disable(struct pico_device *dev); #endif diff --git a/modules/pico_ipv6_nd.c b/modules/pico_ipv6_nd.c index d359116e0..e842df769 100644 --- a/modules/pico_ipv6_nd.c +++ b/modules/pico_ipv6_nd.c @@ -493,7 +493,7 @@ static int radv_process(struct pico_frame *f) (struct pico_icmp6_opt_prefix *) nxtopt; if (prefix->val_lifetime > 0) { if (prefix->prefix_len == 64) { - pico_ipv6_route_add(prefix->prefix, netmask, ipv6_hdr->src, 1, NULL); + pico_ipv6_route_add(prefix->prefix, netmask, ipv6_hdr->src, 10, NULL); } else { pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT, (uint32_t)sizeof(struct pico_ipv6_hdr) + (uint32_t)PICO_ICMP6HDR_ROUTER_ADV_SIZE + (uint32_t)(nxtopt - opt_start)); From d3eb939d00c4f6e743f47f843f68bd3c8d019e3b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 16 Mar 2015 12:17:16 +0100 Subject: [PATCH 55/61] Fixed processing of routing type (Tahi 1.38-1.41) --- modules/pico_ipv6.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index 4e0457bc0..9f9322297 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -523,6 +523,9 @@ int pico_ipv6_process_routing(struct pico_ipv6_exthdr *routing, struct pico_fram { IGNORE_PARAMETER(f); + if (routing->ext.routing.segleft == 0) + return 0; + ipv6_dbg("IPv6: routing extension header with len %u\n", routing->ext.routing.len + 2); switch (routing->ext.routing.routtype) { case 0x00: From edd4d4061f28addf33d0ab04bd78a647fd4c175d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 16 Mar 2015 13:46:31 +0100 Subject: [PATCH 56/61] Fixed local routing, fixed ipv6 loopack --- modules/pico_ipv6.c | 10 +++++++--- test/picoapp.c | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index 9f9322297..afc29c492 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -352,7 +352,7 @@ static struct pico_ipv6_route *pico_ipv6_route_find(const struct pico_ip6 *addr) struct pico_tree_node *index = NULL; int i = 0; - if (pico_ipv6_is_linklocal(addr->addr) || pico_ipv6_is_multicast(addr->addr) || pico_ipv6_is_sitelocal(addr->addr)) + if (!pico_ipv6_is_localhost(addr->addr) && (pico_ipv6_is_linklocal(addr->addr) || pico_ipv6_is_multicast(addr->addr) || pico_ipv6_is_sitelocal(addr->addr))) return NULL; pico_tree_foreach_reverse(index, &IPV6Routes) @@ -917,7 +917,7 @@ static inline struct pico_ipv6_route *ipv6_pushed_frame_checks(struct pico_frame } route = pico_ipv6_route_find(dst); - if (!route) { + if (!route && !f->dev) { dbg("IPv6: route not found.\n"); pico_err = PICO_ERR_EHOSTUNREACH; return NULL; @@ -1016,6 +1016,10 @@ int pico_ipv6_frame_push(struct pico_frame *f, struct pico_ip6 *dst, uint8_t pro goto push_final; } + if (pico_ipv6_is_localhost(dst->addr)) { + f->dev = pico_get_device("loop"); + } + route = ipv6_pushed_frame_checks(f, dst); if (!route) { pico_frame_discard(f); @@ -1151,7 +1155,7 @@ int pico_ipv6_route_add(struct pico_ip6 address, struct pico_ip6 netmask, struct new->link = r->link; } - if (new->link && (!pico_ipv6_is_global(new->link->address.addr))) { + if (new->link && (pico_ipv6_is_global(address.addr)) && (!pico_ipv6_is_global(new->link->address.addr))) { new->link = pico_ipv6_global_get(new->link->dev); } diff --git a/test/picoapp.c b/test/picoapp.c index 6c9f92ec2..62306d2e2 100644 --- a/test/picoapp.c +++ b/test/picoapp.c @@ -468,7 +468,6 @@ int main(int argc, char **argv) pico_ipv6_link_add(dev, ipaddr6, netmask6); } pico_ipv6_dev_routing_enable(dev); - #endif } break; From c5d78e3a99ecf3079a8faa353f7b0cdce1ae31dd Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 16 Mar 2015 22:29:20 +0100 Subject: [PATCH 57/61] Fixed parameter problem parsing order: Full RFC2460 compliance --- modules/pico_ipv6.c | 92 +++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 23 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index afc29c492..a96802f24 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -659,15 +659,8 @@ static int pico_ipv6_frag_match(struct pico_frame *a, struct pico_frame *b) static void pico_ipv6_process_frag(struct pico_ipv6_exthdr *frag, struct pico_frame *f, uint8_t proto) { struct pico_frame *first = pico_tree_first(&ipv6_fragments); - struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; static uint32_t ipv6_cur_frag_id = 0u; - f->frag = (uint16_t)((frag->ext.frag.om[0] << 8) + frag->ext.frag.om[1]); - /* If M-Flag is set, and packet is not 8B aligned, discard and alert */ - if (IP6FRAG_MORE(f->frag) && ((short_be(hdr->len) % 8) != 0)) { - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); - return; - } if (!first) { if (ipv6_cur_frag_id && (FRAG_ID(frag) == ipv6_cur_frag_id)) { @@ -735,18 +728,68 @@ static int pico_ipv6_process_destopt(struct pico_ipv6_exthdr *destopt, struct pi #define IPV6_OPTLEN(x) ((uint16_t)(((x + 1) << 3))) +static int pico_ipv6_check_headers_sequence(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + int ptr = sizeof(struct pico_ipv6_hdr); + int cur_nexthdr = 6; /* Starts with nexthdr field in ipv6 pkt */ + uint8_t nxthdr = hdr->nxthdr; + for (;;) { + uint8_t optlen = *(f->net_hdr + ptr + 1); + switch (nxthdr) { + case PICO_IPV6_EXTHDR_DESTOPT: + case PICO_IPV6_EXTHDR_ROUTING: + case PICO_IPV6_EXTHDR_HOPBYHOP: + case PICO_IPV6_EXTHDR_ESP: + case PICO_IPV6_EXTHDR_AUTH: + optlen = (uint8_t)IPV6_OPTLEN(optlen); + break; + case PICO_IPV6_EXTHDR_FRAG: + optlen = 8; + break; + case PICO_IPV6_EXTHDR_NONE: + return 0; + + case PICO_PROTO_TCP: + case PICO_PROTO_UDP: + case PICO_PROTO_ICMP6: + return 0; + default: + /* Invalid next header */ + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, (uint32_t)cur_nexthdr); + return -1; + } + cur_nexthdr = ptr; + nxthdr = *(f->net_hdr + ptr); + ptr += optlen; + } +} + +static int pico_ipv6_check_aligned(struct pico_frame *f) +{ + struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; + if ((short_be(hdr->len) % 8) != 0) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + return -1; + } + return 0; +} + static int pico_ipv6_extension_headers(struct pico_frame *f) { struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)f->net_hdr; uint8_t nxthdr = hdr->nxthdr; - struct pico_ipv6_exthdr *exthdr = NULL; + struct pico_ipv6_exthdr *exthdr = NULL, *frag_hdr = NULL; uint32_t ptr = sizeof(struct pico_ipv6_hdr); - struct pico_ipv6_exthdr *frag_hdr = NULL; uint16_t cur_optlen; uint32_t cur_nexthdr = 6; int must_align = 0; - + f->net_len = sizeof(struct pico_ipv6_hdr); + + if (pico_ipv6_check_headers_sequence(f) < 0) + return -1; + for (;;) { exthdr = (struct pico_ipv6_exthdr *)(f->net_hdr + f->net_len); cur_optlen = 0; @@ -762,9 +805,9 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) } cur_optlen = IPV6_OPTLEN(exthdr->ext.hopbyhop.len); f->net_len = (uint16_t) (f->net_len + cur_optlen); + must_align = 1; if (pico_ipv6_process_hopbyhop(exthdr, f) < 0) return -1; - must_align = 1; break; case PICO_IPV6_EXTHDR_ROUTING: @@ -775,16 +818,22 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) break; case PICO_IPV6_EXTHDR_FRAG: - frag_hdr = exthdr; cur_optlen = 8u; f->net_len = (uint16_t) (f->net_len + cur_optlen); + frag_hdr = exthdr; + f->frag = (uint16_t)((frag_hdr->ext.frag.om[0] << 8) + frag_hdr->ext.frag.om[1]); + /* If M-Flag is set, and packet is not 8B aligned, discard and alert */ + if (IP6FRAG_MORE(f->frag) && ((short_be(hdr->len) % 8) != 0)) { + pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + return -1; + } break; case PICO_IPV6_EXTHDR_DESTOPT: cur_optlen = IPV6_OPTLEN(exthdr->ext.destopt.len); f->net_len = (uint16_t) (f->net_len + cur_optlen); + must_align = 1; if (pico_ipv6_process_destopt(exthdr, f, ptr) < 0) return -1; - must_align = 1; break; case PICO_IPV6_EXTHDR_ESP: /* not supported, ignored. */ @@ -794,27 +843,24 @@ static int pico_ipv6_extension_headers(struct pico_frame *f) return 0; case PICO_IPV6_EXTHDR_NONE: /* no next header */ - if ((must_align) && ((short_be(hdr->len) % 8) != 0)) { - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + if (must_align && (pico_ipv6_check_aligned(f) < 0)) return -1; - } return 0; case PICO_PROTO_TCP: case PICO_PROTO_UDP: case PICO_PROTO_ICMP6: + if (must_align && (pico_ipv6_check_aligned(f) < 0)) + return -1; f->transport_hdr = f->net_hdr + f->net_len; f->transport_len = (uint16_t)(short_be(hdr->len) - (f->net_len - sizeof(struct pico_ipv6_hdr))); - if ((must_align) && ((short_be(hdr->len) % 8) != 0)) { - pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_HDRFIELD, 4); + if (frag_hdr) { + pico_ipv6_process_frag(frag_hdr, f, nxthdr); return -1; - } - if (!frag_hdr) + } else { return nxthdr; - else { - pico_ipv6_process_frag(frag_hdr, f, nxthdr); - return 0; } + break; default: /* Invalid next header */ pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_NXTHDR, cur_nexthdr); From 819454d4401d13defa4718647558dc0efede392b Mon Sep 17 00:00:00 2001 From: Sam Van Den Berge Date: Tue, 17 Mar 2015 17:55:30 +0100 Subject: [PATCH 58/61] Set src address for pico_socket_sendto_extended --- stack/pico_socket.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/stack/pico_socket.c b/stack/pico_socket.c index 6aa7b567f..c97c40150 100644 --- a/stack/pico_socket.c +++ b/stack/pico_socket.c @@ -1052,6 +1052,7 @@ static int pico_socket_xmit_one(struct pico_socket *s, const void *buf, const in if (msginfo) { f->send_ttl = (uint8_t)msginfo->ttl; f->send_tos = (uint8_t)msginfo->tos; + f->dev = msginfo->dev; } memcpy(f->payload, (const uint8_t *)buf, f->payload_len); @@ -1286,6 +1287,17 @@ int MOCKABLE pico_socket_sendto_extended(struct pico_socket *s, const void *buf, src = pico_socket_sendto_get_src(s, dst); if (!src) { +#ifdef PICO_SUPPORT_IPV6 + if((s->net->proto_number == PICO_PROTO_IPV6) + && msginfo && msginfo->dev + && pico_ipv6_is_linklocal(((struct pico_ip6 *)dst)->addr)) + { + src = &(pico_ipv6_linklocal_get(msginfo->dev)->address); + if(!src) + return -1; + } + else +#endif return -1; } From 9bb5d519201ee032615c5c40fbb47e3db8c6ef44 Mon Sep 17 00:00:00 2001 From: Sam Van Den Berge Date: Wed, 18 Mar 2015 10:07:52 +0100 Subject: [PATCH 59/61] Fix checksum calculations --- modules/pico_ipv6.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/modules/pico_ipv6.c b/modules/pico_ipv6.c index a96802f24..15ed37873 100644 --- a/modules/pico_ipv6.c +++ b/modules/pico_ipv6.c @@ -1014,15 +1014,7 @@ static inline void ipv6_push_hdr_adjust(struct pico_frame *f, struct pico_ipv6_l case PICO_PROTO_UDP: { struct pico_udp_hdr *udp_hdr = (struct pico_udp_hdr *) f->transport_hdr; - udp_hdr->crc = pico_udp_checksum_ipv6(f); - break; - } -#endif -#ifdef PICO_SUPPORT_TCP - case PICO_PROTO_TCP: - { - struct pico_tcp_hdr *tcp_hdr = (struct pico_tcp_hdr *) f->transport_hdr; - tcp_hdr->crc = pico_tcp_checksum_ipv6(f); + udp_hdr->crc = short_be(pico_udp_checksum_ipv6(f)); break; } #endif From 9900480e6785e8d49cdec4f48bbfa677ce906b0b Mon Sep 17 00:00:00 2001 From: Maxime Vincent Date: Wed, 18 Mar 2015 12:00:47 +0100 Subject: [PATCH 60/61] align stm32 tassTick definition --- include/arch/pico_stm32.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/arch/pico_stm32.h b/include/arch/pico_stm32.h index 7318f1b6d..d576834dc 100644 --- a/include/arch/pico_stm32.h +++ b/include/arch/pico_stm32.h @@ -5,7 +5,7 @@ #define dbg(...) do {} while(0) /* #define dbg printf */ -extern volatile uint32_t tassTick; +extern volatile unsigned int tassTick; #ifdef PICO_SUPPORT_RTOS #define PICO_SUPPORT_MUTEX From 938f6ac4df36b0f449272fac72e6c2cbbfec95e0 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 18 Mar 2015 15:23:43 +0100 Subject: [PATCH 61/61] Fix ICMP4 replies when id=0 seq=0 --- modules/pico_icmp4.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/pico_icmp4.c b/modules/pico_icmp4.c index a6ae62dcf..9048d2ed4 100644 --- a/modules/pico_icmp4.c +++ b/modules/pico_icmp4.c @@ -47,6 +47,7 @@ static void ping_recv_reply(struct pico_frame *f); static int pico_icmp4_process_in(struct pico_protocol *self, struct pico_frame *f) { struct pico_icmp4_hdr *hdr = (struct pico_icmp4_hdr *) f->transport_hdr; + static int firstpkt = 1; static uint16_t last_id = 0; static uint16_t last_seq = 0; IGNORE_PARAMETER(self); @@ -57,11 +58,12 @@ static int pico_icmp4_process_in(struct pico_protocol *self, struct pico_frame * if (f->dev && f->dev->eth) f->len -= PICO_SIZE_ETHHDR; - if ((hdr->hun.ih_idseq.idseq_id == last_id) && (last_seq == hdr->hun.ih_idseq.idseq_seq)) { + if (!firstpkt && (hdr->hun.ih_idseq.idseq_id == last_id) && (last_seq == hdr->hun.ih_idseq.idseq_seq)) { /* The network duplicated the echo. Do not reply. */ pico_frame_discard(f); return 0; } + firstpkt = 0; last_id = hdr->hun.ih_idseq.idseq_id; last_seq = hdr->hun.ih_idseq.idseq_seq; pico_icmp4_checksum(f);