IB/core: Validate route when we init ah
[cascardo/linux.git] / drivers / infiniband / core / sa_query.c
index 270faaa..e364a42 100644 (file)
@@ -49,7 +49,9 @@
 #include <net/netlink.h>
 #include <uapi/rdma/ib_user_sa.h>
 #include <rdma/ib_marshall.h>
+#include <rdma/ib_addr.h>
 #include "sa.h"
+#include "core_priv.h"
 
 MODULE_AUTHOR("Roland Dreier");
 MODULE_DESCRIPTION("InfiniBand subnet administration query support");
@@ -996,7 +998,8 @@ int ib_init_ah_from_path(struct ib_device *device, u8 port_num,
 {
        int ret;
        u16 gid_index;
-       int force_grh;
+       int use_roce;
+       struct net_device *ndev = NULL;
 
        memset(ah_attr, 0, sizeof *ah_attr);
        ah_attr->dlid = be16_to_cpu(rec->dlid);
@@ -1006,16 +1009,71 @@ int ib_init_ah_from_path(struct ib_device *device, u8 port_num,
        ah_attr->port_num = port_num;
        ah_attr->static_rate = rec->rate;
 
-       force_grh = rdma_cap_eth_ah(device, port_num);
+       use_roce = rdma_cap_eth_ah(device, port_num);
+
+       if (use_roce) {
+               struct net_device *idev;
+               struct net_device *resolved_dev;
+               struct rdma_dev_addr dev_addr = {.bound_dev_if = rec->ifindex,
+                                                .net = rec->net ? rec->net :
+                                                        &init_net};
+               union {
+                       struct sockaddr     _sockaddr;
+                       struct sockaddr_in  _sockaddr_in;
+                       struct sockaddr_in6 _sockaddr_in6;
+               } sgid_addr, dgid_addr;
+
+               if (!device->get_netdev)
+                       return -EOPNOTSUPP;
+
+               rdma_gid2ip(&sgid_addr._sockaddr, &rec->sgid);
+               rdma_gid2ip(&dgid_addr._sockaddr, &rec->dgid);
+
+               /* validate the route */
+               ret = rdma_resolve_ip_route(&sgid_addr._sockaddr,
+                                           &dgid_addr._sockaddr, &dev_addr);
+               if (ret)
+                       return ret;
 
-       if (rec->hop_limit > 1 || force_grh) {
-               struct net_device *ndev = ib_get_ndev_from_path(rec);
+               if ((dev_addr.network == RDMA_NETWORK_IPV4 ||
+                    dev_addr.network == RDMA_NETWORK_IPV6) &&
+                   rec->gid_type != IB_GID_TYPE_ROCE_UDP_ENCAP)
+                       return -EINVAL;
+
+               idev = device->get_netdev(device, port_num);
+               if (!idev)
+                       return -ENODEV;
+
+               resolved_dev = dev_get_by_index(dev_addr.net,
+                                               dev_addr.bound_dev_if);
+               if (resolved_dev->flags & IFF_LOOPBACK) {
+                       dev_put(resolved_dev);
+                       resolved_dev = idev;
+                       dev_hold(resolved_dev);
+               }
+               ndev = ib_get_ndev_from_path(rec);
+               rcu_read_lock();
+               if ((ndev && ndev != resolved_dev) ||
+                   (resolved_dev != idev &&
+                    !rdma_is_upper_dev_rcu(idev, resolved_dev)))
+                       ret = -EHOSTUNREACH;
+               rcu_read_unlock();
+               dev_put(idev);
+               dev_put(resolved_dev);
+               if (ret) {
+                       if (ndev)
+                               dev_put(ndev);
+                       return ret;
+               }
+       }
 
+       if (rec->hop_limit > 1 || use_roce) {
                ah_attr->ah_flags = IB_AH_GRH;
                ah_attr->grh.dgid = rec->dgid;
 
-               ret = ib_find_cached_gid(device, &rec->sgid, rec->gid_type, ndev,
-                                        &port_num, &gid_index);
+               ret = ib_find_cached_gid_by_port(device, &rec->sgid,
+                                                rec->gid_type, port_num, ndev,
+                                                &gid_index);
                if (ret) {
                        if (ndev)
                                dev_put(ndev);
@@ -1029,9 +1087,10 @@ int ib_init_ah_from_path(struct ib_device *device, u8 port_num,
                if (ndev)
                        dev_put(ndev);
        }
-       if (force_grh) {
+
+       if (use_roce)
                memcpy(ah_attr->dmac, rec->dmac, ETH_ALEN);
-       }
+
        return 0;
 }
 EXPORT_SYMBOL(ib_init_ah_from_path);