net/smc: add ipv6 support to CLC layer
authorKarsten Graul <kgraul@linux.vnet.ibm.com>
Fri, 16 Mar 2018 14:06:40 +0000 (15:06 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 16 Mar 2018 18:57:25 +0000 (14:57 -0400)
The CLC layer is updated to support ipv6 proposal messages from peers and
to match incoming proposal messages against the ipv6 addresses of the net
device. struct smc_clc_ipv6_prefix is updated to provide the space for an
ipv6 address (struct was not used before). SMC_CLC_MAX_LEN is updated to
include the size of the proposal prefix. Existing code in net is not
affected, the previous SMC_CLC_MAX_LEN value is large enough to hold ipv4
proposal messages.

Signed-off-by: Karsten Graul <kgraul@linux.vnet.ibm.com>
Signed-off-by: Ursula Braun <ubraun@linux.vnet.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/smc/smc_clc.c
net/smc/smc_clc.h

index dc3a223..64fbc32 100644 (file)
@@ -5,7 +5,7 @@
  *  CLC (connection layer control) handshake over initial TCP socket to
  *  prepare for RDMA traffic
  *
- *  Copyright IBM Corp. 2016
+ *  Copyright IBM Corp. 2016, 2018
  *
  *  Author(s):  Ursula Braun <ubraun@linux.vnet.ibm.com>
  */
@@ -15,6 +15,7 @@
 #include <linux/if_ether.h>
 #include <linux/sched/signal.h>
 
+#include <net/addrconf.h>
 #include <net/sock.h>
 #include <net/tcp.h>
 
@@ -93,12 +94,44 @@ static int smc_clc_prfx_set4_rcu(struct dst_entry *dst, __be32 ipv4,
        return -ENOENT;
 }
 
+/* fill CLC proposal msg with ipv6 prefixes from device */
+static int smc_clc_prfx_set6_rcu(struct dst_entry *dst,
+                                struct smc_clc_msg_proposal_prefix *prop,
+                                struct smc_clc_ipv6_prefix *ipv6_prfx)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       struct inet6_dev *in6_dev = __in6_dev_get(dst->dev);
+       struct inet6_ifaddr *ifa;
+       int cnt = 0;
+
+       if (!in6_dev)
+               return -ENODEV;
+       /* use a maximum of 8 IPv6 prefixes from device */
+       list_for_each_entry(ifa, &in6_dev->addr_list, if_list) {
+               if (ipv6_addr_type(&ifa->addr) & IPV6_ADDR_LINKLOCAL)
+                       continue;
+               ipv6_addr_prefix(&ipv6_prfx[cnt].prefix,
+                                &ifa->addr, ifa->prefix_len);
+               ipv6_prfx[cnt].prefix_len = ifa->prefix_len;
+               cnt++;
+               if (cnt == SMC_CLC_MAX_V6_PREFIX)
+                       break;
+       }
+       prop->ipv6_prefixes_cnt = cnt;
+       if (cnt)
+               return 0;
+#endif
+       return -ENOENT;
+}
+
 /* retrieve and set prefixes in CLC proposal msg */
 static int smc_clc_prfx_set(struct socket *clcsock,
-                           struct smc_clc_msg_proposal_prefix *prop)
+                           struct smc_clc_msg_proposal_prefix *prop,
+                           struct smc_clc_ipv6_prefix *ipv6_prfx)
 {
        struct dst_entry *dst = sk_dst_get(clcsock->sk);
        struct sockaddr_storage addrs;
+       struct sockaddr_in6 *addr6;
        struct sockaddr_in *addr;
        int rc = -ENOENT;
 
@@ -114,11 +147,19 @@ static int smc_clc_prfx_set(struct socket *clcsock,
        /* get address to which the internal TCP socket is bound */
        kernel_getsockname(clcsock, (struct sockaddr *)&addrs);
        /* analyze IP specific data of net_device belonging to TCP socket */
+       addr6 = (struct sockaddr_in6 *)&addrs;
        rcu_read_lock();
        if (addrs.ss_family == PF_INET) {
                /* IPv4 */
                addr = (struct sockaddr_in *)&addrs;
                rc = smc_clc_prfx_set4_rcu(dst, addr->sin_addr.s_addr, prop);
+       } else if (ipv6_addr_v4mapped(&addr6->sin6_addr)) {
+               /* mapped IPv4 address - peer is IPv4 only */
+               rc = smc_clc_prfx_set4_rcu(dst, addr6->sin6_addr.s6_addr32[3],
+                                          prop);
+       } else {
+               /* IPv6 */
+               rc = smc_clc_prfx_set6_rcu(dst, prop, ipv6_prfx);
        }
        rcu_read_unlock();
 out_rel:
@@ -144,12 +185,41 @@ static int smc_clc_prfx_match4_rcu(struct net_device *dev,
        return -ENOENT;
 }
 
+/* match ipv6 addrs of dev against addrs in CLC proposal */
+static int smc_clc_prfx_match6_rcu(struct net_device *dev,
+                                  struct smc_clc_msg_proposal_prefix *prop)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       struct inet6_dev *in6_dev = __in6_dev_get(dev);
+       struct smc_clc_ipv6_prefix *ipv6_prfx;
+       struct inet6_ifaddr *ifa;
+       int i, max;
+
+       if (!in6_dev)
+               return -ENODEV;
+       /* ipv6 prefix list starts behind smc_clc_msg_proposal_prefix */
+       ipv6_prfx = (struct smc_clc_ipv6_prefix *)((u8 *)prop + sizeof(*prop));
+       max = min_t(u8, prop->ipv6_prefixes_cnt, SMC_CLC_MAX_V6_PREFIX);
+       list_for_each_entry(ifa, &in6_dev->addr_list, if_list) {
+               if (ipv6_addr_type(&ifa->addr) & IPV6_ADDR_LINKLOCAL)
+                       continue;
+               for (i = 0; i < max; i++) {
+                       if (ifa->prefix_len == ipv6_prfx[i].prefix_len &&
+                           ipv6_prefix_equal(&ifa->addr, &ipv6_prfx[i].prefix,
+                                             ifa->prefix_len))
+                               return 0;
+               }
+       }
+#endif
+       return -ENOENT;
+}
+
 /* check if proposed prefixes match one of our device prefixes */
 int smc_clc_prfx_match(struct socket *clcsock,
                       struct smc_clc_msg_proposal_prefix *prop)
 {
        struct dst_entry *dst = sk_dst_get(clcsock->sk);
-       int rc = -ENOENT;
+       int rc;
 
        if (!dst) {
                rc = -ENOTCONN;
@@ -162,6 +232,8 @@ int smc_clc_prfx_match(struct socket *clcsock,
        rcu_read_lock();
        if (!prop->ipv6_prefixes_cnt)
                rc = smc_clc_prfx_match4_rcu(dst->dev, prop);
+       else
+               rc = smc_clc_prfx_match6_rcu(dst->dev, prop);
        rcu_read_unlock();
 out_rel:
        dst_release(dst);
@@ -288,21 +360,24 @@ int smc_clc_send_proposal(struct smc_sock *smc,
                          struct smc_ib_device *smcibdev,
                          u8 ibport)
 {
+       struct smc_clc_ipv6_prefix ipv6_prfx[SMC_CLC_MAX_V6_PREFIX];
        struct smc_clc_msg_proposal_prefix pclc_prfx;
        struct smc_clc_msg_proposal pclc;
        struct smc_clc_msg_trail trl;
+       int len, i, plen, rc;
        int reason_code = 0;
-       struct kvec vec[3];
+       struct kvec vec[4];
        struct msghdr msg;
-       int len, plen, rc;
 
        /* retrieve ip prefixes for CLC proposal msg */
-       rc = smc_clc_prfx_set(smc->clcsock, &pclc_prfx);
+       rc = smc_clc_prfx_set(smc->clcsock, &pclc_prfx, ipv6_prfx);
        if (rc)
                return SMC_CLC_DECL_CNFERR; /* configuration error */
 
        /* send SMC Proposal CLC message */
-       plen = sizeof(pclc) + sizeof(pclc_prfx) + sizeof(trl);
+       plen = sizeof(pclc) + sizeof(pclc_prfx) +
+              (pclc_prfx.ipv6_prefixes_cnt * sizeof(ipv6_prfx[0])) +
+              sizeof(trl);
        memset(&pclc, 0, sizeof(pclc));
        memcpy(pclc.hdr.eyecatcher, SMC_EYECATCHER, sizeof(SMC_EYECATCHER));
        pclc.hdr.type = SMC_CLC_PROPOSAL;
@@ -315,14 +390,20 @@ int smc_clc_send_proposal(struct smc_sock *smc,
 
        memcpy(trl.eyecatcher, SMC_EYECATCHER, sizeof(SMC_EYECATCHER));
        memset(&msg, 0, sizeof(msg));
-       vec[0].iov_base = &pclc;
-       vec[0].iov_len = sizeof(pclc);
-       vec[1].iov_base = &pclc_prfx;
-       vec[1].iov_len = sizeof(pclc_prfx);
-       vec[2].iov_base = &trl;
-       vec[2].iov_len = sizeof(trl);
+       i = 0;
+       vec[i].iov_base = &pclc;
+       vec[i++].iov_len = sizeof(pclc);
+       vec[i].iov_base = &pclc_prfx;
+       vec[i++].iov_len = sizeof(pclc_prfx);
+       if (pclc_prfx.ipv6_prefixes_cnt > 0) {
+               vec[i].iov_base = &ipv6_prfx[0];
+               vec[i++].iov_len = pclc_prfx.ipv6_prefixes_cnt *
+                                  sizeof(ipv6_prfx[0]);
+       }
+       vec[i].iov_base = &trl;
+       vec[i++].iov_len = sizeof(trl);
        /* due to the few bytes needed for clc-handshake this cannot block */
-       len = kernel_sendmsg(smc->clcsock, &msg, vec, 3, plen);
+       len = kernel_sendmsg(smc->clcsock, &msg, vec, i, plen);
        if (len < sizeof(pclc)) {
                if (len >= 0) {
                        reason_code = -ENETUNREACH;
index 5ddb0d2..63bf1dc 100644 (file)
@@ -60,10 +60,15 @@ struct smc_clc_msg_local {  /* header2 of clc messages */
        u8 mac[6];              /* mac of ib_device port */
 };
 
+#define SMC_CLC_MAX_V6_PREFIX  8
+
+/* Struct would be 4 byte aligned, but it is used in an array that is sent
+ * to peers and must conform to RFC7609, hence we need to use packed here.
+ */
 struct smc_clc_ipv6_prefix {
-       u8 prefix[4];
+       struct in6_addr prefix;
        u8 prefix_len;
-} __packed;
+} __packed;                    /* format defined in RFC7609 */
 
 struct smc_clc_msg_proposal_prefix {   /* prefix part of clc proposal message*/
        __be32 outgoing_subnet; /* subnet mask */
@@ -79,9 +84,11 @@ struct smc_clc_msg_proposal {        /* clc proposal message sent by Linux */
 } __aligned(4);
 
 #define SMC_CLC_PROPOSAL_MAX_OFFSET    0x28
-#define SMC_CLC_PROPOSAL_MAX_PREFIX    (8 * sizeof(struct smc_clc_ipv6_prefix))
+#define SMC_CLC_PROPOSAL_MAX_PREFIX    (SMC_CLC_MAX_V6_PREFIX * \
+                                        sizeof(struct smc_clc_ipv6_prefix))
 #define SMC_CLC_MAX_LEN                (sizeof(struct smc_clc_msg_proposal) + \
                                 SMC_CLC_PROPOSAL_MAX_OFFSET + \
+                                sizeof(struct smc_clc_msg_proposal_prefix) + \
                                 SMC_CLC_PROPOSAL_MAX_PREFIX + \
                                 sizeof(struct smc_clc_msg_trail))