From 1a909f58f4f4346f6a72c4c71d33b65c092545f5 Mon Sep 17 00:00:00 2001
From: David van Moolenbroek <david@minix3.org>
Date: Mon, 6 Feb 2017 14:12:37 +0000
Subject: [PATCH 1/2] udp: add core-level multicast support for IPv6

So far, the UDP core module implemented only IPv4 multicast support.
This patch extends the module with the features necessary for socket
layers on top to implement IPv6 multicast support as well:

o If a UDP PCB is bound to an IPv6 multicast address, a unicast source
  address is selected and used to send the packet instead, as is
  required (and was the case for IPv4 multicast already).

o Unlike IPv4's IP_MULTICAST_IF socket option, which takes a source
  IPv4 address, the IPV6_MULTICAST_IF socket option (from RFC 3493)
  takes an interface identifier to denote the interface to use for
  outgoing multicast-destined packets. A new pair of UDP PCB API
  calls, udp_[gs]et_multicast_netif_index(), are added to support
  this. The new definition "NETIF_NO_INDEX" may be used to indicate
  that lwIP should pick an interface instead.

  IPv4 socket implementations may now also choose to map the given
  source address to an interface index immediately and use the new
  facility instead of the old udp_[gs]et_multicast_netif_addr() one.
  A side effect of limiting the old facility to IPv4 is that for dual-
  stack configurations with multicast support, the UDP PCB size is
  reduced by (up to) 16 bytes.

o For configurations that enable loopback interface support, the IPv6
  code now also supports multicast loopback (IPV6_MULTICAST_LOOP).

o The LWIP_MULTICAST_TX_OPTIONS opt.h setting now covers both IPv4
  and IPv6, and as such is no longer strictly linked to IGMP. It is
  therefore placed in its own lwIP options subgroup in opt.h.

The IPV6_MULTICAST_HOPS socket option can already be implemented using
the existing IP_MULTICAST_TTL support, and thus requires no additional
changes. Overall, this patch should not break any existing code.
---
 src/api/sockets.c        |  8 +++----
 src/core/init.c          |  5 +---
 src/core/ipv6/ip6.c      |  5 ++++
 src/core/udp.c           | 62 +++++++++++++++++++++++++++++++++---------------
 src/include/lwip/netif.h |  1 +
 src/include/lwip/opt.h   | 32 ++++++++++++++++++-------
 src/include/lwip/udp.h   | 18 ++++++++++----
 7 files changed, 90 insertions(+), 41 deletions(-)

diff --git a/src/api/sockets.c b/src/api/sockets.c
index 12aa680..14a0de3 100644
--- a/src/api/sockets.c
+++ b/src/api/sockets.c
@@ -2020,7 +2020,7 @@ lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *opt
       LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_TOS) = %d\n",
                   s, *(int *)optval));
       break;
-#if LWIP_MULTICAST_TX_OPTIONS
+#if LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS
     case IP_MULTICAST_TTL:
       LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, u8_t);
       if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
@@ -2049,7 +2049,7 @@ lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *opt
       LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_MULTICAST_LOOP) = %d\n",
                   s, *(int *)optval));
       break;
-#endif /* LWIP_MULTICAST_TX_OPTIONS */
+#endif /* LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS */
     default:
       LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, UNIMPL: optname=0x%x, ..)\n",
                   s, optname));
@@ -2389,7 +2389,7 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_
       LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IP, IP_TOS, ..)-> %d\n",
                   s, sock->conn->pcb.ip->tos));
       break;
-#if LWIP_MULTICAST_TX_OPTIONS
+#if LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS
     case IP_MULTICAST_TTL:
       LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, u8_t, NETCONN_UDP);
       udp_set_multicast_ttl(sock->conn->pcb.udp, (u8_t)(*(const u8_t*)optval));
@@ -2410,7 +2410,7 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_
         udp_setflags(sock->conn->pcb.udp, udp_flags(sock->conn->pcb.udp) & ~UDP_FLAGS_MULTICAST_LOOP);
       }
       break;
-#endif /* LWIP_MULTICAST_TX_OPTIONS */
+#endif /* LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS */
 #if LWIP_IGMP
     case IP_ADD_MEMBERSHIP:
     case IP_DROP_MEMBERSHIP:
diff --git a/src/core/init.c b/src/core/init.c
index ee415a9..1f89997 100644
--- a/src/core/init.c
+++ b/src/core/init.c
@@ -96,7 +96,7 @@ PACK_STRUCT_END
   #error "If you want to use DHCP, you have to define LWIP_UDP=1 in your lwipopts.h"
 #endif
 #if (!LWIP_UDP && LWIP_MULTICAST_TX_OPTIONS)
-  #error "If you want to use IGMP/LWIP_MULTICAST_TX_OPTIONS, you have to define LWIP_UDP=1 in your lwipopts.h"
+  #error "If you want to use LWIP_MULTICAST_TX_OPTIONS, you have to define LWIP_UDP=1 in your lwipopts.h"
 #endif
 #if (!LWIP_UDP && LWIP_DNS)
   #error "If you want to use DNS, you have to define LWIP_UDP=1 in your lwipopts.h"
@@ -123,9 +123,6 @@ PACK_STRUCT_END
 #if (LWIP_IGMP && !LWIP_IPV4)
   #error "IGMP needs LWIP_IPV4 enabled in your lwipopts.h"
 #endif
-#if (LWIP_MULTICAST_TX_OPTIONS && !LWIP_IPV4)
-  #error "LWIP_MULTICAST_TX_OPTIONS needs LWIP_IPV4 enabled in your lwipopts.h"
-#endif
 #if ((LWIP_NETCONN || LWIP_SOCKET) && (MEMP_NUM_TCPIP_MSG_API<=0))
   #error "If you want to use Sequential API, you have to define MEMP_NUM_TCPIP_MSG_API>=1 in your lwipopts.h"
 #endif
diff --git a/src/core/ipv6/ip6.c b/src/core/ipv6/ip6.c
index bb26db1..eb7a46a 100644
--- a/src/core/ipv6/ip6.c
+++ b/src/core/ipv6/ip6.c
@@ -1024,6 +1024,11 @@ ip6_output_if_src(struct pbuf *p, const ip6_addr_t *src, const ip6_addr_t *dest,
       }
     }
   }
+#if LWIP_MULTICAST_TX_OPTIONS
+  if ((p->flags & PBUF_FLAG_MCASTLOOP) != 0) {
+    netif_loop_output(netif, p);
+  }
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
 #endif /* ENABLE_LOOPBACK */
 #if LWIP_IPV6_FRAG
   /* don't fragment if interface has mtu set to 0 [loopif] */
diff --git a/src/core/udp.c b/src/core/udp.c
index 5bdb3cd..25a04ad 100644
--- a/src/core/udp.c
+++ b/src/core/udp.c
@@ -514,7 +514,7 @@ udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
 {
 #endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
   struct netif *netif;
-  const ip_addr_t *dst_ip_route = dst_ip;
+  const ip_addr_t *src_ip_route;
 
   if ((pcb == NULL) || (dst_ip == NULL) || !IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
     return ERR_VAL;
@@ -522,8 +522,31 @@ udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
 
   LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n"));
 
-#if LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS
-  if (ip_addr_ismulticast(dst_ip_route)) {
+  if (IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
+    /* Don't call ip_route() with IP_ANY_TYPE */
+    src_ip_route = IP46_ADDR_ANY(IP_GET_TYPE(dst_ip));
+  } else {
+    src_ip_route = &pcb->local_ip;
+  }
+
+#if LWIP_MULTICAST_TX_OPTIONS
+  netif = NULL;
+  if (ip_addr_ismulticast(dst_ip)) {
+    /* For IPv6, the interface to use for packets with a multicast destination
+     * is specified using an interface index. The same approach may be used for
+     * IPv4 as well, in which case it overrides the IPv4 multicast override
+     * address below. Here we have to look up the netif by going through the
+     * list, but by doing so we skip a route lookup. If the interface index has
+     * gone stale, we fall through and do the regular route lookup after all. */
+    if (pcb->mcast_ifindex != NETIF_NO_INDEX) {
+      for (netif = netif_list; netif != NULL; netif = netif->next) {
+        if (pcb->mcast_ifindex == netif_num_to_index(netif)) {
+          break; /* found! */
+        }
+      }
+    }
+#if LWIP_IPV4
+    else
 #if LWIP_IPV6
     if (IP_IS_V4(dst_ip))
 #endif /* LWIP_IPV6 */
@@ -531,21 +554,21 @@ udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
       /* IPv4 does not use source-based routing by default, so we use an
          administratively selected interface for multicast by default.
          However, this can be overridden by setting an interface address
-         in pcb->multicast_ip that is used for routing. */
-      if (!ip_addr_isany_val(pcb->multicast_ip) &&
-          !ip4_addr_cmp(ip_2_ip4(&pcb->multicast_ip), IP4_ADDR_BROADCAST)) {
-        dst_ip_route = &pcb->multicast_ip;
+         in pcb->mcast_ip4 that is used for routing. If this routing lookup
+         fails, we try regular routing as though no override was set. */
+      if (!ip4_addr_isany_val(pcb->mcast_ip4) &&
+          !ip4_addr_cmp(&pcb->mcast_ip4, IP4_ADDR_BROADCAST)) {
+        netif = ip4_route_src(ip_2_ip4(src_ip_route), &pcb->mcast_ip4);
       }
     }
+#endif /* LWIP_IPV4 */
   }
-#endif /* LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS */
 
-  /* find the outgoing network interface for this packet */
-  if(IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
-    /* Don't call ip_route() with IP_ANY_TYPE */
-    netif = ip_route(IP46_ADDR_ANY(IP_GET_TYPE(dst_ip_route)), dst_ip_route);
-  } else {
-    netif = ip_route(&pcb->local_ip, dst_ip_route);
+  if (netif == NULL)
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+  {
+    /* find the outgoing network interface for this packet */
+    netif = ip_route(src_ip_route, dst_ip);
   }
 
   /* no outgoing network interface could be found? */
@@ -604,10 +627,11 @@ udp_sendto_if_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_i
     return ERR_VAL;
   }
 
-  /* PCB local address is IP_ANY_ADDR? */
+  /* PCB local address is IP_ANY_ADDR or multicast? */
 #if LWIP_IPV6
   if (IP_IS_V6(dst_ip)) {
-    if (ip6_addr_isany(ip_2_ip6(&pcb->local_ip))) {
+    if (ip6_addr_isany(ip_2_ip6(&pcb->local_ip)) ||
+        ip6_addr_ismulticast(ip_2_ip6(&pcb->local_ip))) {
       src_ip = ip6_select_source_address(netif, ip_2_ip6(dst_ip));
       if (src_ip == NULL) {
         /* No suitable source address was found. */
@@ -737,11 +761,11 @@ udp_sendto_if_src_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *d
   udphdr->chksum = 0x0000;
 
   /* Multicast Loop? */
-#if (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) || (LWIP_IPV6 && LWIP_IPV6_MLD)
+#if LWIP_MULTICAST_TX_OPTIONS
   if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) && ip_addr_ismulticast(dst_ip)) {
     q->flags |= PBUF_FLAG_MCASTLOOP;
   }
-#endif /* (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
 
   LWIP_DEBUGF(UDP_DEBUG, ("udp_send: sending datagram of length %"U16_F"\n", q->tot_len));
 
@@ -917,7 +941,7 @@ udp_bind(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
    * This is legacy support: scope-aware callers should always provide properly
    * zoned source addresses. Do the zone selection before the address-in-use
    * check below; as such we have to make a temporary copy of the address. */
-  if (IP_IS_V6(ipaddr) && ip6_addr_lacks_zone(ip_2_ip6(ipaddr), IP6_UNICAST)) {
+  if (IP_IS_V6(ipaddr) && ip6_addr_lacks_zone(ip_2_ip6(ipaddr), IP6_UNKNOWN)) {
     ip_addr_copy(zoned_ipaddr, *ipaddr);
     ip6_addr_select_zone(ip_2_ip6(&zoned_ipaddr), ip_2_ip6(&zoned_ipaddr));
     ipaddr = &zoned_ipaddr;
diff --git a/src/include/lwip/netif.h b/src/include/lwip/netif.h
index 7241b42..0b47ec0 100644
--- a/src/include/lwip/netif.h
+++ b/src/include/lwip/netif.h
@@ -504,6 +504,7 @@ char * netif_index_to_name(u8_t idx, char *name);
 /* Interface indexes always start at 1 per RFC 3493, section 4, num starts at 0 */
 #define netif_num_to_index(netif)   ((netif)->num + 1)
 #define netif_index_to_num(index)   ((index) - 1)
+#define NETIF_NO_INDEX              (0)
 
 #ifdef __cplusplus
 }
diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h
index 7f3eced..5fe34dd 100644
--- a/src/include/lwip/opt.h
+++ b/src/include/lwip/opt.h
@@ -972,7 +972,29 @@
 
 /*
    ----------------------------------
-   ----- Multicast/IGMP options -----
+   -------- Multicast options -------
+   ----------------------------------
+*/
+/**
+ * @defgroup lwip_opts_multicast Multicast
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MULTICAST_TX_OPTIONS==1: Enable multicast TX support like the socket options
+ * IP_MULTICAST_TTL/IP_MULTICAST_IF/IP_MULTICAST_LOOP, as well as (currently only)
+ * core support for the corresponding IPv6 options.
+ */
+#if !defined LWIP_MULTICAST_TX_OPTIONS || defined __DOXYGEN__
+#define LWIP_MULTICAST_TX_OPTIONS       ((LWIP_IGMP || LWIP_IPV6_MLD) && LWIP_UDP)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ----------------------------------
+   ---------- IGMP options ----------
    ----------------------------------
 */
 /**
@@ -990,14 +1012,6 @@
 #undef LWIP_IGMP
 #define LWIP_IGMP                       0
 #endif
-
-/**
- * LWIP_MULTICAST_TX_OPTIONS==1: Enable multicast TX support like the socket options
- * IP_MULTICAST_TTL/IP_MULTICAST_IF/IP_MULTICAST_LOOP
- */
-#if !defined LWIP_MULTICAST_TX_OPTIONS || defined __DOXYGEN__
-#define LWIP_MULTICAST_TX_OPTIONS       (LWIP_IGMP && LWIP_UDP)
-#endif
 /**
  * @}
  */
diff --git a/src/include/lwip/udp.h b/src/include/lwip/udp.h
index b929907..30cfb33 100644
--- a/src/include/lwip/udp.h
+++ b/src/include/lwip/udp.h
@@ -91,8 +91,12 @@ struct udp_pcb {
   u16_t local_port, remote_port;
 
 #if LWIP_MULTICAST_TX_OPTIONS
-  /** outgoing network interface for multicast packets */
-  ip_addr_t multicast_ip;
+#if LWIP_IPV4
+  /** outgoing network interface for multicast packets, by IPv4 address (if not 'any') */
+  ip4_addr_t mcast_ip4;
+#endif /* LWIP_IPV4 */
+  /** outgoing network interface for multicast packets, by interface index (if nonzero) */
+  u8_t mcast_ifindex;
   /** TTL for outgoing multicast packets */
   u8_t mcast_ttl;
 #endif /* LWIP_MULTICAST_TX_OPTIONS */
@@ -159,9 +163,13 @@ void             udp_init       (void);
 #define udp_new_ip6() udp_new_ip_type(IPADDR_TYPE_V6)
 
 #if LWIP_MULTICAST_TX_OPTIONS
-#define udp_set_multicast_netif_addr(pcb, ip4addr) ip_addr_copy_from_ip4((pcb)->multicast_ip, *(ip4addr))
-#define udp_get_multicast_netif_addr(pcb)          ip_2_ip4(&(pcb)->multicast_ip)
-#define udp_set_multicast_ttl(pcb, value)      do { (pcb)->mcast_ttl = value; } while(0)
+#if LWIP_IPV4
+#define udp_set_multicast_netif_addr(pcb, ip4addr) ip4_addr_copy((pcb)->mcast_ip4, *(ip4addr))
+#define udp_get_multicast_netif_addr(pcb)          (&(pcb)->mcast_ip4)
+#endif /* LWIP_IPV4 */
+#define udp_set_multicast_netif_index(pcb, idx)    ((pcb)->mcast_ifindex = (idx))
+#define udp_get_multicast_netif_index(pcb)         ((pcb)->mcast_ifindex)
+#define udp_set_multicast_ttl(pcb, value)          ((pcb)->mcast_ttl = (value))
 #define udp_get_multicast_ttl(pcb)                 ((pcb)->mcast_ttl)
 #endif /* LWIP_MULTICAST_TX_OPTIONS */
 
-- 
2.5.2

