/* * VXLAN: Virtual eXtensible Local Area Network * * Copyright (c) 2012-2013 Vyatta Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h>#include <linux/types.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/slab.h>#include <linux/skbuff.h>#include <linux/rculist.h>#include <linux/netdevice.h>#include <linux/in.h>#include <linux/ip.h>#include <linux/udp.h>#include <linux/igmp.h>#include <linux/etherdevice.h>#include <linux/if_ether.h>#include <linux/if_vlan.h>#include <linux/hash.h>#include <linux/ethtool.h>#include <net/arp.h>#include <net/ndisc.h>#include <net/ip.h>#include <net/ip_tunnels.h>#include <net/icmp.h>#include <net/udp.h>#include <net/rtnetlink.h>#include <net/route.h>#include <net/dsfield.h>#include <net/inet_ecn.h>#include <net/net_namespace.h>#include <net/netns/generic.h>#include <net/vxlan.h>#if IS_ENABLED(CONFIG_IPV6)#include <net/ipv6.h>#include <net/addrconf.h>#include <net/ip6_tunnel.h>#include <net/ip6_checksum.h>#endif #define VXLAN_VERSION "0.1" #define PORT_HASH_BITS 8#define PORT_HASH_SIZE (1<<PORT_HASH_BITS)#define VNI_HASH_BITS 10#define VNI_HASH_SIZE (1<<VNI_HASH_BITS)#define FDB_HASH_BITS 8#define FDB_HASH_SIZE (1<<FDB_HASH_BITS)#define FDB_AGE_DEFAULT 300 /* 5 min */#define FDB_AGE_INTERVAL (10 * HZ) /* rescan interval */ #define VXLAN_N_VID (1u << 24)#define VXLAN_VID_MASK (VXLAN_N_VID - 1)#define VXLAN_HLEN (sizeof(struct udphdr) + sizeof(struct vxlanhdr)) #define VXLAN_FLAGS 0x08000000 /* struct vxlanhdr.vx_flags required value. */ /* VXLAN protocol header */struct vxlanhdr { __be32 vx_flags; __be32 vx_vni;}; /* UDP port for VXLAN traffic. * The IANA assigned port is 4789, but the Linux default is 8472 * for compatibility with early adopters. */static unsigned short vxlan_port __read_mostly = 8472;module_param_named(udp_port, vxlan_port, ushort, 0444);MODULE_PARM_DESC(udp_port, "Destination UDP port"); static bool log_ecn_error = true;module_param(log_ecn_error, bool, 0644);MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN"); static int vxlan_net_id; static const u8 all_zeros_mac[ETH_ALEN]; /* per-network namespace private data for this module */struct vxlan_net { struct list_head vxlan_list; struct hlist_head sock_list[PORT_HASH_SIZE]; spinlock_t sock_lock;}; union vxlan_addr { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr sa;}; struct vxlan_rdst { union vxlan_addr remote_ip; __be16 remote_port; u32 remote_vni; u32 remote_ifindex; struct list_head list; struct rcu_head rcu;}; /* Forwarding table entry */struct vxlan_fdb { struct hlist_node hlist; /* linked list of entries */ struct rcu_head rcu; unsigned long updated; /* jiffies */ unsigned long used; struct list_head remotes; u16 state; /* see ndm_state */ u8 flags; /* see ndm_flags */ u8 eth_addr[ETH_ALEN];}; /* Pseudo network device */struct vxlan_dev { struct hlist_node hlist; /* vni hash table */ struct list_head next; /* vxlan's per namespace list */ struct vxlan_sock *vn_sock; /* listening socket */ struct net_device *dev; struct vxlan_rdst default_dst; /* default destination */ union vxlan_addr saddr; /* source address */ __be16 dst_port; __u16 port_min; /* source port range */ __u16 port_max; __u8 tos; /* TOS override */ __u8 ttl; u32 flags; /* VXLAN_F_* below */ struct work_struct sock_work; struct work_struct igmp_join; struct work_struct igmp_leave; unsigned long age_interval; struct timer_list age_timer; spinlock_t hash_lock; unsigned int addrcnt; unsigned int addrmax; struct hlist_head fdb_head[FDB_HASH_SIZE];}; #define VXLAN_F_LEARN 0x01#define VXLAN_F_PROXY 0x02#define VXLAN_F_RSC 0x04#define VXLAN_F_L2MISS 0x08#define VXLAN_F_L3MISS 0x10#define VXLAN_F_IPV6 0x20 /* internal flag */ /* salt for hash table */static u32 vxlan_salt __read_mostly;static struct workqueue_struct *vxlan_wq; static void vxlan_sock_work(struct work_struct *work); #if IS_ENABLED(CONFIG_IPV6)static inlinebool vxlan_addr_equal(const union vxlan_addr *a, const union vxlan_addr *b){ if (a->sa.sa_family != b->sa.sa_family) return false; if (a->sa.sa_family == AF_INET6) return ipv6_addr_equal(&a->sin6.sin6_addr, &b->sin6.sin6_addr); else return a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr;} static inline bool vxlan_addr_any(const union vxlan_addr *ipa){ if (ipa->sa.sa_family == AF_INET6) return ipv6_addr_any(&ipa->sin6.sin6_addr); else return ipa->sin.sin_addr.s_addr == htonl(INADDR_ANY);} static inline bool vxlan_addr_multicast(const union vxlan_addr *ipa){ if (ipa->sa.sa_family == AF_INET6) return ipv6_addr_is_multicast(&ipa->sin6.sin6_addr); else return IN_MULTICAST(ntohl(ipa->sin.sin_addr.s_addr));} static int vxlan_nla_get_addr(union vxlan_addr *ip, struct nlattr *nla){ if (nla_len(nla) >= sizeof(struct in6_addr)) { nla_memcpy(&ip->sin6.sin6_addr, nla, sizeof(struct in6_addr)); ip->sa.sa_family = AF_INET6; return 0; } else if (nla_len(nla) >= sizeof(__be32)) { ip->sin.sin_addr.s_addr = nla_get_be32(nla); ip->sa.sa_family = AF_INET; return 0; } else { return -EAFNOSUPPORT; }} static int vxlan_nla_put_addr(struct sk_buff *skb, int attr, const union vxlan_addr *ip){ if (ip->sa.sa_family == AF_INET6) return nla_put(skb, attr, sizeof(struct in6_addr), &ip->sin6.sin6_addr); else return nla_put_be32(skb, attr, ip->sin.sin_addr.s_addr);} #else /* !CONFIG_IPV6 */ static inlinebool vxlan_addr_equal(const union vxlan_addr *a, const union vxlan_addr *b){ return a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr;} static inline bool vxlan_addr_any(const union vxlan_addr *ipa){ return ipa->sin.sin_addr.s_addr == htonl(INADDR_ANY);} static inline bool vxlan_addr_multicast(const union vxlan_addr *ipa){ return IN_MULTICAST(ntohl(ipa->sin.sin_addr.s_addr));} static int vxlan_nla_get_addr(union vxlan_addr *ip, struct nlattr *nla){ if (nla_len(nla) >= sizeof(struct in6_addr)) { return -EAFNOSUPPORT; } else if (nla_len(nla) >= sizeof(__be32)) { ip->sin.sin_addr.s_addr = nla_get_be32(nla); ip->sa.sa_family = AF_INET; return 0; } else { return -EAFNOSUPPORT; }} static int vxlan_nla_put_addr(struct sk_buff *skb, int attr, const union vxlan_addr *ip){ return nla_put_be32(skb, attr, ip->sin.sin_addr.s_addr);}#endif /* Virtual Network hash table head */static inline struct hlist_head *vni_head(struct vxlan_sock *vs, u32 id){ return &vs->vni_list[hash_32(id, VNI_HASH_BITS)];} /* Socket hash table head */static inline struct hlist_head *vs_head(struct net *net, __be16 port){ struct vxlan_net *vn = net_generic(net, vxlan_net_id); return &vn->sock_list[hash_32(ntohs(port), PORT_HASH_BITS)];} /* First remote destination for a forwarding entry. * Guaranteed to be non-NULL because remotes are never deleted. */static inline struct vxlan_rdst *first_remote_rcu(struct vxlan_fdb *fdb){ return list_entry_rcu(fdb->remotes.next, struct vxlan_rdst, list);} static inline struct vxlan_rdst *first_remote_rtnl(struct vxlan_fdb *fdb){ return list_first_entry(&fdb->remotes, struct vxlan_rdst, list);} /* Find VXLAN socket based on network namespace and UDP port */static struct vxlan_sock *vxlan_find_sock(struct net *net, __be16 port){ struct vxlan_sock *vs; hlist_for_each_entry_rcu(vs, vs_head(net, port), hlist) { if (inet_sk(vs->sock->sk)->inet_sport == port) return vs; } return NULL;} static struct vxlan_dev *vxlan_vs_find_vni(struct vxlan_sock *vs, u32 id){ struct vxlan_dev *vxlan; hlist_for_each_entry_rcu(vxlan, vni_head(vs, id), hlist) { if (vxlan->default_dst.remote_vni == id) return vxlan; } return NULL;} /* Look up VNI in a per net namespace table */static struct vxlan_dev *vxlan_find_vni(struct net *net, u32 id, __be16 port){ struct vxlan_sock *vs; vs = vxlan_find_sock(net, port); if (!vs) return NULL; return vxlan_vs_find_vni(vs, id);} /* Fill in neighbour message in skbuff. */static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan, const struct vxlan_fdb *fdb, u32 portid, u32 seq, int type, unsigned int flags, const struct vxlan_rdst *rdst){ unsigned long now = jiffies; struct nda_cacheinfo ci; struct nlmsghdr *nlh; struct ndmsg *ndm; bool send_ip, send_eth; nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); if (nlh == NULL) return -EMSGSIZE; ndm = nlmsg_data(nlh); memset(ndm, 0, sizeof(*ndm)); send_eth = send_ip = true; if (type == RTM_GETNEIGH) { ndm->ndm_family = AF_INET; send_ip = !vxlan_addr_any(&rdst->remote_ip); send_eth = !is_zero_ether_addr(fdb->eth_addr); } else ndm->ndm_family = AF_BRIDGE; ndm->ndm_state = fdb->state; ndm->ndm_ifindex = vxlan->dev->ifindex; ndm->ndm_flags = fdb->flags; ndm->ndm_type = NDA_DST; if (send_eth && nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->eth_addr)) goto nla_put_failure; if (send_ip && vxlan_nla_put_addr(skb, NDA_DST, &rdst->remote_ip)) goto nla_put_failure; if (rdst->remote_port && rdst->remote_port != vxlan->dst_port && nla_put_be16(skb, NDA_PORT, rdst->remote_port)) goto nla_put_failure; if (rdst->remote_vni != vxlan->default_dst.remote_vni && nla_put_u32(skb, NDA_VNI, rdst->remote_vni)) goto nla_put_failure; if (rdst->remote_ifindex && nla_put_u32(skb, NDA_IFINDEX, rdst->remote_ifindex)) goto nla_put_failure; ci.ndm_used = jiffies_to_clock_t(now - fdb->used); ci.ndm_confirmed = 0; ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated); ci.ndm_refcnt = 0; if (nla_put(skb, NDA_CACHEINFO, sizeof(ci), &ci)) goto nla_put_failure; return nlmsg_end(skb, nlh); nla_put_failure: nlmsg_cancel(skb, nlh); return -EMSGSIZE;} static inline size_t vxlan_nlmsg_size(void){ return NLMSG_ALIGN(sizeof(struct ndmsg)) + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ + nla_total_size(sizeof(struct in6_addr)) /* NDA_DST */ + nla_total_size(sizeof(__be16)) /* NDA_PORT */ + nla_total_size(sizeof(__be32)) /* NDA_VNI */ + nla_total_size(sizeof(__u32)) /* NDA_IFINDEX */ + nla_total_size(sizeof(struct nda_cacheinfo));} static void vxlan_fdb_notify(struct vxlan_dev *vxlan, struct vxlan_fdb *fdb, int type){ struct net *net = dev_net(vxlan->dev); struct sk_buff *skb; int err = -ENOBUFS; skb = nlmsg_new(vxlan_nlmsg_size(), GFP_ATOMIC); if (skb == NULL) goto errout; err = vxlan_fdb_info(skb, vxlan, fdb, 0, 0, type, 0, first_remote_rtnl(fdb)); if (err < 0) { /* -EMSGSIZE implies BUG in vxlan_nlmsg_size() */ WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC); return;errout: if (err < 0) rtnl_set_sk_err(net, RTNLGRP_NEIGH, err);} static void vxlan_ip_miss(struct net_device *dev, union vxlan_addr *ipa){ struct vxlan_dev *vxlan = netdev_priv(dev); struct vxlan_fdb f = { .state = NUD_STALE, }; struct vxlan_rdst remote = { .remote_ip = *ipa, /* goes to NDA_DST */ .remote_vni = VXLAN_N_VID, }; INIT_LIST_HEAD(&f.remotes); list_add_rcu(&remote.list, &f.remotes); vxlan_fdb_notify(vxlan, &f, RTM_GETNEIGH);} static void vxlan_fdb_miss(struct vxlan_dev *vxlan, const u8 eth_addr[ETH_ALEN]){ struct vxlan_fdb f = { .state = NUD_STALE, }; INIT_LIST_HEAD(&f.remotes); memcpy(f.eth_addr, eth_addr, ETH_ALEN); vxlan_fdb_notify(vxlan, &f, RTM_GETNEIGH);} /* Hash Ethernet address */static u32 eth_hash(const unsigned char *addr){ u64 value = get_unaligned((u64 *)addr); /* only want 6 bytes */#ifdef __BIG_ENDIAN value >>= 16;#else value <<= 16;#endif return hash_64(value, FDB_HASH_BITS);} /* Hash chain to use given mac address */static inline struct hlist_head *vxlan_fdb_head(struct vxlan_dev *vxlan, const u8 *mac){ return &vxlan->fdb_head[eth_hash(mac)];} /* Look up Ethernet address in forwarding table */static struct vxlan_fdb *__vxlan_find_mac(struct vxlan_dev *vxlan, const u8 *mac) { struct hlist_head *head = vxlan_fdb_head(vxlan, mac); struct vxlan_fdb *f; hlist_for_each_entry_rcu(f, head, hlist) { if (ether_addr_equal(mac, f->eth_addr)) return f; } return NULL;} static struct vxlan_fdb *vxlan_find_mac(struct vxlan_dev *vxlan, const u8 *mac){ struct vxlan_fdb *f; f = __vxlan_find_mac(vxlan, mac); if (f) f->used = jiffies; return f;} /* caller should hold vxlan->hash_lock */static struct vxlan_rdst *vxlan_fdb_find_rdst(struct vxlan_fdb *f, union vxlan_addr *ip, __be16 port, __u32 vni, __u32 ifindex){Lines 1–500 of 2707. Load more
/* * VXLAN: Virtual eXtensible Local Area Network * * Copyright (c) 2012-2013 Vyatta Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h> #include <linux/types.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/slab.h> #include <linux/skbuff.h> #include <linux/rculist.h> #include <linux/netdevice.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/udp.h> #include <linux/igmp.h> #include <linux/etherdevice.h> #include <linux/if_ether.h> #include <linux/if_vlan.h> #include <linux/hash.h> #include <linux/ethtool.h> #include <net/arp.h> #include <net/ndisc.h> #include <net/ip.h> #include <net/ip_tunnels.h> #include <net/icmp.h> #include <net/udp.h> #include <net/rtnetlink.h> #include <net/route.h> #include <net/dsfield.h> #include <net/inet_ecn.h> #include <net/net_namespace.h> #include <net/netns/generic.h> #include <net/vxlan.h> #if IS_ENABLED(CONFIG_IPV6) #include <net/ipv6.h> #include <net/addrconf.h> #include <net/ip6_tunnel.h> #include <net/ip6_checksum.h> #endif #define VXLAN_VERSION "0.1" #define PORT_HASH_BITS 8 #define PORT_HASH_SIZE (1<<PORT_HASH_BITS) #define VNI_HASH_BITS 10 #define VNI_HASH_SIZE (1<<VNI_HASH_BITS) #define FDB_HASH_BITS 8 #define FDB_HASH_SIZE (1<<FDB_HASH_BITS) #define FDB_AGE_DEFAULT 300 /* 5 min */ #define FDB_AGE_INTERVAL (10 * HZ) /* rescan interval */ #define VXLAN_N_VID (1u << 24) #define VXLAN_VID_MASK (VXLAN_N_VID - 1) #define VXLAN_HLEN (sizeof(struct udphdr) + sizeof(struct vxlanhdr)) #define VXLAN_FLAGS 0x08000000 /* struct vxlanhdr.vx_flags required value. */ /* VXLAN protocol header */ struct vxlanhdr { __be32 vx_flags; __be32 vx_vni; }; /* UDP port for VXLAN traffic. * The IANA assigned port is 4789, but the Linux default is 8472 * for compatibility with early adopters. */ static unsigned short vxlan_port __read_mostly = 8472; module_param_named(udp_port, vxlan_port, ushort, 0444); MODULE_PARM_DESC(udp_port, "Destination UDP port"); static bool log_ecn_error = true; module_param(log_ecn_error, bool, 0644); MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN"); static int vxlan_net_id; static const u8 all_zeros_mac[ETH_ALEN]; /* per-network namespace private data for this module */ struct vxlan_net { struct list_head vxlan_list; struct hlist_head sock_list[PORT_HASH_SIZE]; spinlock_t sock_lock; }; union vxlan_addr { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr sa; }; struct vxlan_rdst { union vxlan_addr remote_ip; __be16 remote_port; u32 remote_vni; u32 remote_ifindex; struct list_head list; struct rcu_head rcu; }; /* Forwarding table entry */ struct vxlan_fdb { struct hlist_node hlist; /* linked list of entries */ struct rcu_head rcu; unsigned long updated; /* jiffies */ unsigned long used; struct list_head remotes; u16 state; /* see ndm_state */ u8 flags; /* see ndm_flags */ u8 eth_addr[ETH_ALEN]; }; /* Pseudo network device */ struct vxlan_dev { struct hlist_node hlist; /* vni hash table */ struct list_head next; /* vxlan's per namespace list */ struct vxlan_sock *vn_sock; /* listening socket */ struct net_device *dev; struct vxlan_rdst default_dst; /* default destination */ union vxlan_addr saddr; /* source address */ __be16 dst_port; __u16 port_min; /* source port range */ __u16 port_max; __u8 tos; /* TOS override */ __u8 ttl; u32 flags; /* VXLAN_F_* below */ struct work_struct sock_work; struct work_struct igmp_join; struct work_struct igmp_leave; unsigned long age_interval; struct timer_list age_timer; spinlock_t hash_lock; unsigned int addrcnt; unsigned int addrmax; struct hlist_head fdb_head[FDB_HASH_SIZE]; }; #define VXLAN_F_LEARN 0x01 #define VXLAN_F_PROXY 0x02 #define VXLAN_F_RSC 0x04 #define VXLAN_F_L2MISS 0x08 #define VXLAN_F_L3MISS 0x10 #define VXLAN_F_IPV6 0x20 /* internal flag */ /* salt for hash table */ static u32 vxlan_salt __read_mostly; static struct workqueue_struct *vxlan_wq; static void vxlan_sock_work(struct work_struct *work); #if IS_ENABLED(CONFIG_IPV6) static inline bool vxlan_addr_equal(const union vxlan_addr *a, const union vxlan_addr *b) { if (a->sa.sa_family != b->sa.sa_family) return false; if (a->sa.sa_family == AF_INET6) return ipv6_addr_equal(&a->sin6.sin6_addr, &b->sin6.sin6_addr); else return a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr; } static inline bool vxlan_addr_any(const union vxlan_addr *ipa) { if (ipa->sa.sa_family == AF_INET6) return ipv6_addr_any(&ipa->sin6.sin6_addr); else return ipa->sin.sin_addr.s_addr == htonl(INADDR_ANY); } static inline bool vxlan_addr_multicast(const union vxlan_addr *ipa) { if (ipa->sa.sa_family == AF_INET6) return ipv6_addr_is_multicast(&ipa->sin6.sin6_addr); else return IN_MULTICAST(ntohl(ipa->sin.sin_addr.s_addr)); } static int vxlan_nla_get_addr(union vxlan_addr *ip, struct nlattr *nla) { if (nla_len(nla) >= sizeof(struct in6_addr)) { nla_memcpy(&ip->sin6.sin6_addr, nla, sizeof(struct in6_addr)); ip->sa.sa_family = AF_INET6; return 0; } else if (nla_len(nla) >= sizeof(__be32)) { ip->sin.sin_addr.s_addr = nla_get_be32(nla); ip->sa.sa_family = AF_INET; return 0; } else { return -EAFNOSUPPORT; } } static int vxlan_nla_put_addr(struct sk_buff *skb, int attr, const union vxlan_addr *ip) { if (ip->sa.sa_family == AF_INET6) return nla_put(skb, attr, sizeof(struct in6_addr), &ip->sin6.sin6_addr); else return nla_put_be32(skb, attr, ip->sin.sin_addr.s_addr); } #else /* !CONFIG_IPV6 */ static inline bool vxlan_addr_equal(const union vxlan_addr *a, const union vxlan_addr *b) { return a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr; } static inline bool vxlan_addr_any(const union vxlan_addr *ipa) { return ipa->sin.sin_addr.s_addr == htonl(INADDR_ANY); } static inline bool vxlan_addr_multicast(const union vxlan_addr *ipa) { return IN_MULTICAST(ntohl(ipa->sin.sin_addr.s_addr)); } static int vxlan_nla_get_addr(union vxlan_addr *ip, struct nlattr *nla) { if (nla_len(nla) >= sizeof(struct in6_addr)) { return -EAFNOSUPPORT; } else if (nla_len(nla) >= sizeof(__be32)) { ip->sin.sin_addr.s_addr = nla_get_be32(nla); ip->sa.sa_family = AF_INET; return 0; } else { return -EAFNOSUPPORT; } } static int vxlan_nla_put_addr(struct sk_buff *skb, int attr, const union vxlan_addr *ip) { return nla_put_be32(skb, attr, ip->sin.sin_addr.s_addr); } #endif /* Virtual Network hash table head */ static inline struct hlist_head *vni_head(struct vxlan_sock *vs, u32 id) { return &vs->vni_list[hash_32(id, VNI_HASH_BITS)]; } /* Socket hash table head */ static inline struct hlist_head *vs_head(struct net *net, __be16 port) { struct vxlan_net *vn = net_generic(net, vxlan_net_id); return &vn->sock_list[hash_32(ntohs(port), PORT_HASH_BITS)]; } /* First remote destination for a forwarding entry. * Guaranteed to be non-NULL because remotes are never deleted. */ static inline struct vxlan_rdst *first_remote_rcu(struct vxlan_fdb *fdb) { return list_entry_rcu(fdb->remotes.next, struct vxlan_rdst, list); } static inline struct vxlan_rdst *first_remote_rtnl(struct vxlan_fdb *fdb) { return list_first_entry(&fdb->remotes, struct vxlan_rdst, list); } /* Find VXLAN socket based on network namespace and UDP port */ static struct vxlan_sock *vxlan_find_sock(struct net *net, __be16 port) { struct vxlan_sock *vs; hlist_for_each_entry_rcu(vs, vs_head(net, port), hlist) { if (inet_sk(vs->sock->sk)->inet_sport == port) return vs; } return NULL; } static struct vxlan_dev *vxlan_vs_find_vni(struct vxlan_sock *vs, u32 id) { struct vxlan_dev *vxlan; hlist_for_each_entry_rcu(vxlan, vni_head(vs, id), hlist) { if (vxlan->default_dst.remote_vni == id) return vxlan; } return NULL; } /* Look up VNI in a per net namespace table */ static struct vxlan_dev *vxlan_find_vni(struct net *net, u32 id, __be16 port) { struct vxlan_sock *vs; vs = vxlan_find_sock(net, port); if (!vs) return NULL; return vxlan_vs_find_vni(vs, id); } /* Fill in neighbour message in skbuff. */ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan, const struct vxlan_fdb *fdb, u32 portid, u32 seq, int type, unsigned int flags, const struct vxlan_rdst *rdst) { unsigned long now = jiffies; struct nda_cacheinfo ci; struct nlmsghdr *nlh; struct ndmsg *ndm; bool send_ip, send_eth; nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); if (nlh == NULL) return -EMSGSIZE; ndm = nlmsg_data(nlh); memset(ndm, 0, sizeof(*ndm)); send_eth = send_ip = true; if (type == RTM_GETNEIGH) { ndm->ndm_family = AF_INET; send_ip = !vxlan_addr_any(&rdst->remote_ip); send_eth = !is_zero_ether_addr(fdb->eth_addr); } else ndm->ndm_family = AF_BRIDGE; ndm->ndm_state = fdb->state; ndm->ndm_ifindex = vxlan->dev->ifindex; ndm->ndm_flags = fdb->flags; ndm->ndm_type = NDA_DST; if (send_eth && nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->eth_addr)) goto nla_put_failure; if (send_ip && vxlan_nla_put_addr(skb, NDA_DST, &rdst->remote_ip)) goto nla_put_failure; if (rdst->remote_port && rdst->remote_port != vxlan->dst_port && nla_put_be16(skb, NDA_PORT, rdst->remote_port)) goto nla_put_failure; if (rdst->remote_vni != vxlan->default_dst.remote_vni && nla_put_u32(skb, NDA_VNI, rdst->remote_vni)) goto nla_put_failure; if (rdst->remote_ifindex && nla_put_u32(skb, NDA_IFINDEX, rdst->remote_ifindex)) goto nla_put_failure; ci.ndm_used = jiffies_to_clock_t(now - fdb->used); ci.ndm_confirmed = 0; ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated); ci.ndm_refcnt = 0; if (nla_put(skb, NDA_CACHEINFO, sizeof(ci), &ci)) goto nla_put_failure; return nlmsg_end(skb, nlh); nla_put_failure: nlmsg_cancel(skb, nlh); return -EMSGSIZE; } static inline size_t vxlan_nlmsg_size(void) { return NLMSG_ALIGN(sizeof(struct ndmsg)) + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ + nla_total_size(sizeof(struct in6_addr)) /* NDA_DST */ + nla_total_size(sizeof(__be16)) /* NDA_PORT */ + nla_total_size(sizeof(__be32)) /* NDA_VNI */ + nla_total_size(sizeof(__u32)) /* NDA_IFINDEX */ + nla_total_size(sizeof(struct nda_cacheinfo)); } static void vxlan_fdb_notify(struct vxlan_dev *vxlan, struct vxlan_fdb *fdb, int type) { struct net *net = dev_net(vxlan->dev); struct sk_buff *skb; int err = -ENOBUFS; skb = nlmsg_new(vxlan_nlmsg_size(), GFP_ATOMIC); if (skb == NULL) goto errout; err = vxlan_fdb_info(skb, vxlan, fdb, 0, 0, type, 0, first_remote_rtnl(fdb)); if (err < 0) { /* -EMSGSIZE implies BUG in vxlan_nlmsg_size() */ WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC); return; errout: if (err < 0) rtnl_set_sk_err(net, RTNLGRP_NEIGH, err); } static void vxlan_ip_miss(struct net_device *dev, union vxlan_addr *ipa) { struct vxlan_dev *vxlan = netdev_priv(dev); struct vxlan_fdb f = { .state = NUD_STALE, }; struct vxlan_rdst remote = { .remote_ip = *ipa, /* goes to NDA_DST */ .remote_vni = VXLAN_N_VID, }; INIT_LIST_HEAD(&f.remotes); list_add_rcu(&remote.list, &f.remotes); vxlan_fdb_notify(vxlan, &f, RTM_GETNEIGH); } static void vxlan_fdb_miss(struct vxlan_dev *vxlan, const u8 eth_addr[ETH_ALEN]) { struct vxlan_fdb f = { .state = NUD_STALE, }; INIT_LIST_HEAD(&f.remotes); memcpy(f.eth_addr, eth_addr, ETH_ALEN); vxlan_fdb_notify(vxlan, &f, RTM_GETNEIGH); } /* Hash Ethernet address */ static u32 eth_hash(const unsigned char *addr) { u64 value = get_unaligned((u64 *)addr); /* only want 6 bytes */ #ifdef __BIG_ENDIAN value >>= 16; #else value <<= 16; #endif return hash_64(value, FDB_HASH_BITS); } /* Hash chain to use given mac address */ static inline struct hlist_head *vxlan_fdb_head(struct vxlan_dev *vxlan, const u8 *mac) { return &vxlan->fdb_head[eth_hash(mac)]; } /* Look up Ethernet address in forwarding table */ static struct vxlan_fdb *__vxlan_find_mac(struct vxlan_dev *vxlan, const u8 *mac) { struct hlist_head *head = vxlan_fdb_head(vxlan, mac); struct vxlan_fdb *f; hlist_for_each_entry_rcu(f, head, hlist) { if (ether_addr_equal(mac, f->eth_addr)) return f; } return NULL; } static struct vxlan_fdb *vxlan_find_mac(struct vxlan_dev *vxlan, const u8 *mac) { struct vxlan_fdb *f; f = __vxlan_find_mac(vxlan, mac); if (f) f->used = jiffies; return f; } /* caller should hold vxlan->hash_lock */ static struct vxlan_rdst *vxlan_fdb_find_rdst(struct vxlan_fdb *f, union vxlan_addr *ip, __be16 port, __u32 vni, __u32 ifindex) {