From 686aabaa0a0ae0190de664517f9a88ead046381e Mon Sep 17 00:00:00 2001
From: Simon Brummer <simon.brummer@posteo.de>
Date: Fri, 11 May 2018 10:06:14 +0200
Subject: [PATCH] gnrc_tcp: handle link local IPv6 addresses correctly

---
 sys/include/net/gnrc/tcp.h                    | 66 ++++++++++---------
 sys/include/net/gnrc/tcp/tcb.h                |  1 +
 sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c   | 38 +++++++----
 .../transport_layer/tcp/gnrc_tcp_eventloop.c  | 20 +++++-
 .../gnrc/transport_layer/tcp/gnrc_tcp_fsm.c   | 13 ++++
 .../gnrc/transport_layer/tcp/gnrc_tcp_pkt.c   | 42 ++++++++++++
 tests/gnrc_tcp_client/Makefile                |  3 +-
 tests/gnrc_tcp_client/main.c                  | 16 ++---
 tests/gnrc_tcp_server/Makefile                |  1 +
 9 files changed, 144 insertions(+), 56 deletions(-)

diff --git a/sys/include/net/gnrc/tcp.h b/sys/include/net/gnrc/tcp.h
index 0902e8d836..6b86c0cf4b 100644
--- a/sys/include/net/gnrc/tcp.h
+++ b/sys/include/net/gnrc/tcp.h
@@ -52,36 +52,37 @@ int gnrc_tcp_init(void);
  */
 void gnrc_tcp_tcb_init(gnrc_tcp_tcb_t *tcb);
 
- /**
-  * @brief Opens a connection actively.
-  *
-  * @pre gnrc_tcp_tcb_init() must have been successfully called.
-  * @pre @p tcb must not be NULL
-  * @pre @p target_addr must not be NULL.
-  * @pre @p target_port must not be 0.
-  *
-  * @note Blocks until a connection has been established or an error occured.
-  *
-  * @param[in,out] tcb              TCB holding the connection information.
-  * @param[in]     address_family   Address family of @p target_addr.
-  * @param[in]     target_addr      Pointer to target address.
-  * @param[in]     target_port      Target port number.
-  * @param[in]     local_port       If zero or PORT_UNSPEC, the connections
-  *                                 source port is randomly chosen. If local_port is non-zero
-  *                                 the local_port is used as source port.
-  *
-  * @returns   Zero on success.
-  *            -EAFNOSUPPORT if @p address_family is not supported.
-  *            -EINVAL if @p address_family is not the same the address_family use by the TCB.
-  *            -EISCONN if TCB is already in use.
-  *            -ENOMEM if the receive buffer for the TCB could not be allocated.
-  *            -EADDRINUSE if @p local_port is already used by another connection.
-  *            -ETIMEDOUT if the connection could not be opened.
-  *            -ECONNREFUSED if the connection was resetted by the peer.
-  */
-int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb,  const uint8_t address_family,
-                         const uint8_t *target_addr, const uint16_t target_port,
-                         const uint16_t local_port);
+/**
+ * @brief Opens a connection actively.
+ *
+ * @pre gnrc_tcp_tcb_init() must have been successfully called.
+ * @pre @p tcb must not be NULL
+ * @pre @p target_addr must not be NULL.
+ * @pre @p target_port must not be 0.
+ *
+ * @note Blocks until a connection has been established or an error occured.
+ *
+ * @param[in,out] tcb              TCB holding the connection information.
+ * @param[in]     address_family   Address family of @p target_addr.
+ * @param[in]     target_addr      Pointer to target address.
+ * @param[in]     target_port      Target port number.
+ * @param[in]     local_port       If zero or PORT_UNSPEC, the connections
+ *                                 source port is randomly chosen. If local_port is non-zero
+ *                                 the local_port is used as source port.
+ *
+ * @returns   Zero on success.
+ *            -EAFNOSUPPORT if @p address_family is not supported.
+ *            -EINVAL if @p address_family is not the same the address_family use by the TCB.
+ *                    or @p target_addr is invalid.
+ *            -EISCONN if TCB is already in use.
+ *            -ENOMEM if the receive buffer for the TCB could not be allocated.
+ *            -EADDRINUSE if @p local_port is already used by another connection.
+ *            -ETIMEDOUT if the connection could not be opened.
+ *            -ECONNREFUSED if the connection was resetted by the peer.
+ */
+int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb,  uint8_t address_family,
+                         char *target_addr, uint16_t target_port,
+                         uint16_t local_port);
 
 /**
  * @brief Opens a connection passively, by waiting for an incomming request.
@@ -105,12 +106,13 @@ int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb,  const uint8_t address_family,
  * @returns   Zero on success.
  *            -EAFNOSUPPORT if local_addr != NULL and @p address_family is not supported.
  *            -EINVAL if @p address_family is not the same the address_family used in TCB.
+ *                    or @p target_addr is invalid.
  *            -EISCONN if TCB is already in use.
  *            -ENOMEM if the receive buffer for the TCB could not be allocated.
  *            Hint: Increase "GNRC_TCP_RCV_BUFFERS".
  */
-int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb,  const uint8_t address_family,
-                          const uint8_t *local_addr, const uint16_t local_port);
+int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb, uint8_t address_family,
+                          const char *local_addr, uint16_t local_port);
 
 /**
  * @brief Transmit data to connected peer.
diff --git a/sys/include/net/gnrc/tcp/tcb.h b/sys/include/net/gnrc/tcp/tcb.h
index eb54672ed6..dadb1b7952 100644
--- a/sys/include/net/gnrc/tcp/tcb.h
+++ b/sys/include/net/gnrc/tcp/tcb.h
@@ -53,6 +53,7 @@ typedef struct _transmission_control_block {
 #ifdef MODULE_GNRC_IPV6
     uint8_t local_addr[sizeof(ipv6_addr_t)];  /**< Local IP address */
     uint8_t peer_addr[sizeof(ipv6_addr_t)];   /**< Peer IP address */
+    int8_t  ll_iface;                         /**< Link layer interface id to use. */
 #endif
     uint16_t local_port;   /**< Local connections port number */
     uint16_t peer_port;    /**< Peer connections port number */
diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c
index b72304a826..273d3b3703 100644
--- a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c
+++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c
@@ -112,8 +112,8 @@ static void _setup_timeout(xtimer_t *timer, const uint32_t duration, const xtime
  *            -ETIMEDOUT if the connection opening timed out.
  *            -ECONNREFUSED if the connection was resetted by the peer.
  */
-static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const uint8_t *target_addr, uint16_t target_port,
-                          const uint8_t *local_addr, uint16_t local_port, uint8_t passive)
+static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, char *target_addr, uint16_t target_port,
+                          const char *local_addr, uint16_t local_port, uint8_t passive)
 {
     msg_t msg;
     xtimer_t connection_timeout;
@@ -146,7 +146,10 @@ static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const uint8_t *target_addr, uint1
 #ifdef MODULE_GNRC_IPV6
         /* If local address is specified: Copy it into TCB */
         else if (tcb->address_family == AF_INET6) {
-                memcpy(tcb->local_addr, local_addr, sizeof(ipv6_addr_t));
+            if (ipv6_addr_from_str((ipv6_addr_t *) tcb->local_addr,  local_addr) == NULL) {
+                DEBUG("gnrc_tcp.c : _gnrc_tcp_open() : Invalid peer addr\n");
+                return -EINVAL;
+            }
         }
 #endif
         /* Set port number to listen on */
@@ -154,10 +157,21 @@ static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const uint8_t *target_addr, uint1
     }
     /* Setup active connection */
     else {
-        /* Copy target address and port number into TCB */
+        /* Parse target address and port number into TCB */
  #ifdef MODULE_GNRC_IPV6
         if ((target_addr != NULL) && (tcb->address_family == AF_INET6)) {
-            memcpy(tcb->peer_addr, target_addr, sizeof(ipv6_addr_t));
+
+            /* Extract interface (optional) specifier from target address */
+            int ll_iface = ipv6_addr_split_iface(target_addr);
+            if (ipv6_addr_from_str((ipv6_addr_t *) tcb->peer_addr, target_addr) == NULL) {
+                DEBUG("gnrc_tcp.c : _gnrc_tcp_open() : Invalid peer addr\n");
+                return -EINVAL;
+            }
+
+            /* In case the given address is link-local: Memorize the interface Id if existing. */
+            if ((ll_iface > 0) && ipv6_addr_is_link_local((ipv6_addr_t *) tcb->peer_addr)) {
+                tcb->ll_iface = ll_iface;
+            }
         }
  #endif
         /* Assign port numbers, verfication happens in fsm */
@@ -172,10 +186,10 @@ static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const uint8_t *target_addr, uint1
     /* Call FSM with event: CALL_OPEN */
     ret = _fsm(tcb, FSM_EVENT_CALL_OPEN, NULL, NULL, 0);
     if (ret == -ENOMEM) {
-        DEBUG("gnrc_tcp.c : gnrc_tcp_connect() : Out of receive buffers.\n");
+        DEBUG("gnrc_tcp.c : _gnrc_tcp_open() : Out of receive buffers.\n");
     }
     else if(ret == -EADDRINUSE) {
-        DEBUG("gnrc_tcp.c : gnrc_tcp_connect() : local_port is already in use.\n");
+        DEBUG("gnrc_tcp.c : _gnrc_tcp_open() : local_port is already in use.\n");
     }
 
     /* Wait until a connection was established or closed */
@@ -245,9 +259,9 @@ void gnrc_tcp_tcb_init(gnrc_tcp_tcb_t *tcb)
     mutex_init(&(tcb->function_lock));
 }
 
-int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb,  const uint8_t address_family,
-                         const uint8_t *target_addr, const uint16_t target_port,
-                         const uint16_t local_port)
+int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb, uint8_t address_family,
+                         char *target_addr, uint16_t target_port,
+                         uint16_t local_port)
 {
     assert(tcb != NULL);
     assert(target_addr != NULL);
@@ -270,8 +284,8 @@ int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb,  const uint8_t address_family,
     return _gnrc_tcp_open(tcb, target_addr, target_port, NULL, local_port, 0);
 }
 
-int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb,  const uint8_t address_family,
-                          const uint8_t *local_addr, const uint16_t local_port)
+int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb, uint8_t address_family,
+                          const char *local_addr, uint16_t local_port)
 {
     assert(tcb != NULL);
     assert(local_port != PORT_UNSPEC);
diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c
index da38da1394..85769f45a7 100644
--- a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c
+++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c
@@ -46,19 +46,35 @@ static msg_t _eventloop_msg_queue[TCP_EVENTLOOP_MSG_QUEUE_SIZE];
  */
 static int _send(gnrc_pktsnip_t *pkt)
 {
+    assert(pkt != NULL);
+
     /* NOTE: In sending direction: pkt = nw, nw->next = tcp, tcp->next = payload */
-    gnrc_pktsnip_t *tcp;
+    gnrc_pktsnip_t *tcp = NULL;
+    gnrc_pktsnip_t *nw = NULL;
 
     /* Search for TCP header */
     LL_SEARCH_SCALAR(pkt, tcp, type, GNRC_NETTYPE_TCP);
+    /* cppcheck-suppress knownConditionTrueFalse */
     if (tcp == NULL) {
         DEBUG("gnrc_tcp_eventloop : _send() : tcp header missing.\n");
         gnrc_pktbuf_release(pkt);
         return -EBADMSG;
     }
 
+    /* Search for network layer */
+#ifdef MODULE_GNRC_IPV6
+    /* Get IPv6 header, discard packet if doesn't contain an ipv6 header */
+    LL_SEARCH_SCALAR(pkt, nw, type, GNRC_NETTYPE_IPV6);
+    if (nw == NULL) {
+        DEBUG("gnrc_tcp_eventloop.c : _send() : pkt contains no ipv6 layer header\n");
+        gnrc_pktbuf_release(pkt);
+        return -EBADMSG;
+    }
+#endif
+
     /* Dispatch packet to network layer */
-    if (!gnrc_netapi_dispatch_send(pkt->type, GNRC_NETREG_DEMUX_CTX_ALL, pkt)) {
+    assert(nw != NULL);
+    if (!gnrc_netapi_dispatch_send(nw->type, GNRC_NETREG_DEMUX_CTX_ALL, pkt)) {
         DEBUG("gnrc_tcp_eventloop : _send() : network layer not found\n");
         gnrc_pktbuf_release(pkt);
     }
diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c
index ddb4598936..c877aa92fc 100644
--- a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c
+++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c
@@ -474,6 +474,19 @@ static int _fsm_rcvd_pkt(gnrc_tcp_tcb_t *tcb, gnrc_pktsnip_t *in_pkt)
             if (snp->type == GNRC_NETTYPE_IPV6 && tcb->address_family == AF_INET6) {
                 memcpy(tcb->local_addr, &((ipv6_hdr_t *)ip)->dst, sizeof(ipv6_addr_t));
                 memcpy(tcb->peer_addr, &((ipv6_hdr_t *)ip)->src, sizeof(ipv6_addr_t));
+
+                /* In case peer_addr is link local: Store interface Id in tcb */
+                if (ipv6_addr_is_link_local((ipv6_addr_t *) tcb->peer_addr)) {
+                    gnrc_pktsnip_t *tmp = NULL;
+                    LL_SEARCH_SCALAR(in_pkt, tmp, type, GNRC_NETTYPE_NETIF);
+                    /* cppcheck-suppress knownConditionTrueFalse */
+                    if (tmp == NULL) {
+                        DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() :\
+                               incomming packet had no netif header\n");
+                        return 0;
+                    }
+                    tcb->ll_iface = ((gnrc_netif_hdr_t *)tmp->data)->if_pid;
+                }
             }
 #else
             DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : Received address was not stored\n");
diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_pkt.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_pkt.c
index d3857ea307..b1f1f1c407 100644
--- a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_pkt.c
+++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_pkt.c
@@ -109,6 +109,30 @@ int _pkt_build_reset_from_pkt(gnrc_pktsnip_t **out_pkt, gnrc_pktsnip_t *in_pkt)
         return -ENOMEM;
     }
     *out_pkt = ip6_snp;
+
+    /* Add netif header in case the receiver addr sent from a link local address */
+    if (ipv6_addr_is_link_local(&ip6_hdr->src)) {
+
+        /* Search for netif header in received packet */
+        gnrc_pktsnip_t *net_snp;
+        LL_SEARCH_SCALAR(in_pkt, net_snp, type, GNRC_NETTYPE_NETIF);
+        gnrc_netif_hdr_t *net_hdr = (gnrc_netif_hdr_t *)net_snp->data;
+
+        /* Allocate new header and set interface id */
+        net_snp = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
+        if (net_snp == NULL) {
+            DEBUG("gnrc_tcp_pkt.c : _pkt_build_reset_from_pkt() :\
+                   Can't alloc buffer for netif Header.\n");
+            gnrc_pktbuf_release(ip6_snp);
+            *(out_pkt) = NULL;
+            return -ENOMEM;
+        }
+        else {
+            ((gnrc_netif_hdr_t *)net_snp->data)->if_pid = net_hdr->if_pid;
+            LL_PREPEND(ip6_snp, net_snp);
+            *(out_pkt) = net_snp;
+        }
+    }
 #else
     DEBUG("gnrc_tcp_pkt.c : _pkt_build_reset_from_pkt() : Network Layer Module Missing\n");
 #endif
@@ -191,6 +215,22 @@ int _pkt_build(gnrc_tcp_tcb_t *tcb, gnrc_pktsnip_t **out_pkt, uint16_t *seq_con,
     else {
         *(out_pkt) = ip6_snp;
     }
+
+    /* Prepend network interface header if an interface id was specified */
+    if (tcb->ll_iface > 0) {
+        gnrc_pktsnip_t *net_snp = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
+        if (net_snp == NULL) {
+            DEBUG("gnrc_tcp_pkt.c : _pkt_build() : Can't allocate buffer for netif header.\n");
+            gnrc_pktbuf_release(ip6_snp);
+            *(out_pkt) = NULL;
+            return -ENOMEM;
+        }
+        else {
+            ((gnrc_netif_hdr_t *)net_snp->data)->if_pid = (kernel_pid_t)tcb->ll_iface;
+            LL_PREPEND(ip6_snp, net_snp);
+            *(out_pkt) = net_snp;
+        }
+    }
 #else
         DEBUG("gnrc_tcp_pkt.c : _pkt_build_reset_from_pkt() : Network Layer Module Missing\n");
 #endif
@@ -441,6 +481,8 @@ uint16_t _pkt_calc_csum(const gnrc_pktsnip_t *hdr, const gnrc_pktsnip_t *pseudo_
             break;
 #endif
         default:
+            /* Suppress compiler warnings */
+            (void) len;
             return 0;
     }
     return ~csum;
diff --git a/tests/gnrc_tcp_client/Makefile b/tests/gnrc_tcp_client/Makefile
index f48c9d3e34..6415f2647b 100644
--- a/tests/gnrc_tcp_client/Makefile
+++ b/tests/gnrc_tcp_client/Makefile
@@ -4,7 +4,7 @@ include ../Makefile.tests_common
 BOARD ?= native
 PORT ?= tap1
 
-TCP_TARGET_ADDR ?= fe80::affe
+TCP_TARGET_ADDR ?= fe80::affe%5
 TCP_TARGET_PORT ?= 80
 TCP_TEST_CYCLES ?= 3
 
@@ -21,6 +21,7 @@ BOARD_INSUFFICIENT_MEMORY := airfy-beacon arduino-duemilanove arduino-mega2560 \
 CFLAGS += -DTARGET_ADDR=\"$(TCP_TARGET_ADDR)\"
 CFLAGS += -DTARGET_PORT=$(TCP_TARGET_PORT)
 CFLAGS += -DCYCLES=$(TCP_TEST_CYCLES)
+CFLAGS += -DGNRC_NETIF_IPV6_GROUPS_NUMOF=3
 
 # Modules to include
 USEMODULE += gnrc_netdev_default
diff --git a/tests/gnrc_tcp_client/main.c b/tests/gnrc_tcp_client/main.c
index 3d0dba905d..90b2f8d3b2 100644
--- a/tests/gnrc_tcp_client/main.c
+++ b/tests/gnrc_tcp_client/main.c
@@ -65,21 +65,19 @@ void *cli_thread(void *arg)
     /* Transmission control block */
     gnrc_tcp_tcb_t tcb;
 
-    /* Target peer address information */
-    ipv6_addr_t target_addr;
-    uint16_t target_port;
-
-    /* Initialize target information */
-    ipv6_addr_from_str(&target_addr, TARGET_ADDR);
-    target_port = TARGET_PORT;
-
     printf("Client running: TID=%d\n", tid);
     while (cycles < CYCLES) {
+
+        /* Copy peer address information. NOTE: This test uses link-local addresses
+         * -> The Device identifier is removed from target_addr in each iteration! */
+        char target_addr[] = TARGET_ADDR;
+        uint16_t target_port = TARGET_PORT;
+
         /* Initialize TCB */
         gnrc_tcp_tcb_init(&tcb);
 
         /* Connect to peer */
-        int ret = gnrc_tcp_open_active(&tcb, AF_INET6, (uint8_t *) &target_addr, target_port, 0);
+        int ret = gnrc_tcp_open_active(&tcb, AF_INET6, target_addr, target_port, 0);
         switch (ret) {
             case 0:
                 DEBUG("TID=%d : gnrc_tcp_open_active() : 0 : ok\n", tid);
diff --git a/tests/gnrc_tcp_server/Makefile b/tests/gnrc_tcp_server/Makefile
index 931775aeec..340a838f5a 100644
--- a/tests/gnrc_tcp_server/Makefile
+++ b/tests/gnrc_tcp_server/Makefile
@@ -24,6 +24,7 @@ RIOTBASE ?= $(CURDIR)/../..
 CFLAGS += -DLOCAL_ADDR=\"$(TCP_LOCAL_ADDR)\"
 CFLAGS += -DLOCAL_PORT=$(TCP_LOCAL_PORT)
 CFLAGS += -DCYCLES=$(TCP_TEST_CYCLES)
+CFLAGS += -DGNRC_NETIF_IPV6_GROUPS_NUMOF=3
 
 # Modules to include
 USEMODULE += gnrc_netdev_default
-- 
GitLab