Adding new module name ila. This implements ILA tanslation. A netlink interface is used to configure identifier to lacator mappings. Two functions are exported to attempt to perform a translation on input or output path.
Signed-off-by: Tom Herbert <t...@herbertland.com> --- include/net/ila.h | 7 + net/ipv6/Kconfig | 8 + net/ipv6/Makefile | 1 + net/ipv6/ila.c | 496 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 512 insertions(+) create mode 100644 include/net/ila.h create mode 100644 net/ipv6/ila.c diff --git a/include/net/ila.h b/include/net/ila.h new file mode 100644 index 0000000..25660c6 --- /dev/null +++ b/include/net/ila.h @@ -0,0 +1,7 @@ +#ifndef __NET_ILA_H +#define __NET_ILA_H + +int ila_xlat_incoming(struct sk_buff *skb); +int ila_xlat_outgoing(struct sk_buff *skb); + +#endif diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig index 438a73a..609ca1a 100644 --- a/net/ipv6/Kconfig +++ b/net/ipv6/Kconfig @@ -93,6 +93,14 @@ config IPV6_MIP6 If unsure, say N. +config IPV6_ILA + tristate "IPv6: Identifier Locator Addressing (ILA)" + ---help--- + Support for IPv6 Identifier Locator Addressing described in + https://tools.ietf.org/html/draft-herbert-nvo3-ila-00. + + If unsure, say N. + config INET6_XFRM_TUNNEL tristate select INET6_TUNNEL diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 0f3f199..2c900c7 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_INET6_XFRM_MODE_TUNNEL) += xfrm6_mode_tunnel.o obj-$(CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION) += xfrm6_mode_ro.o obj-$(CONFIG_INET6_XFRM_MODE_BEET) += xfrm6_mode_beet.o obj-$(CONFIG_IPV6_MIP6) += mip6.o +obj-$(CONFIG_IPV6_ILA) += ila.o obj-$(CONFIG_NETFILTER) += netfilter/ obj-$(CONFIG_IPV6_VTI) += ip6_vti.o diff --git a/net/ipv6/ila.c b/net/ipv6/ila.c new file mode 100644 index 0000000..54a215d --- /dev/null +++ b/net/ipv6/ila.c @@ -0,0 +1,496 @@ +#include <linux/errno.h> +#include <linux/ip.h> +#include <linux/jhash.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/socket.h> +#include <linux/skbuff.h> +#include <linux/rcupdate.h> +#include <linux/types.h> +#include <net/checksum.h> +#include <net/genetlink.h> +#include <net/ila.h> +#include <net/ip.h> +#include <net/netns/generic.h> +#include <net/protocol.h> +#include <uapi/linux/genetlink.h> +#include <uapi/linux/ila.h> + +struct ila_map { + u64 identifier; + u64 locator; + struct hlist_node hlnode; + struct rcu_head rcu; +}; + +struct ila_map_bucket { + struct hlist_head chain; + spinlock_t lock; +}; + +#define ILA_HASH_TABLE_SIZE 1024 + +struct ila_maps_info { + struct ila_map_bucket buckets[ILA_HASH_TABLE_SIZE]; + unsigned int mask; + size_t size; +}; + +struct ila_cfg { + u64 identifier; + u64 locator; +}; + +static unsigned int ila_net_id; + +struct ila_net { + struct ila_maps_info info; +}; + +static struct genl_family ila_nl_family = { + .id = GENL_ID_GENERATE, + .hdrsize = 0, + .name = ILA_GENL_NAME, + .version = ILA_GENL_VERSION, + .maxattr = ILA_ATTR_MAX, + .netnsok = true, +}; + +static struct nla_policy ila_nl_policy[ILA_ATTR_MAX + 1] = { + [ILA_ATTR_IDENTIFIER] = { .type = NLA_U64, }, + [ILA_ATTR_LOCATOR] = { .type = NLA_U64, }, +}; + +static u32 hashrnd __read_mostly; +static __always_inline void ila_init_secret(void) +{ + net_get_random_once(&hashrnd, sizeof(hashrnd)); +} + +static inline unsigned int ila_hash(u64 id) +{ + u32 *words = (u32 *)&id; + + return jhash_2words(words[0], words[1], hashrnd); +} + +static int parse_nl_config(struct genl_info *info, + struct ila_cfg *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + + if (info->attrs[ILA_ATTR_IDENTIFIER]) { + u64 identifier = nla_get_u64(info->attrs[ILA_ATTR_IDENTIFIER]); + + cfg->identifier = identifier; + } + + if (info->attrs[ILA_ATTR_LOCATOR]) { + u64 locator = nla_get_u64(info->attrs[ILA_ATTR_LOCATOR]); + + cfg->locator = locator; + } + + return 0; +} + +/* Must be called with rcu readlock */ +struct ila_map *ila_lookup(u64 id, struct ila_maps_info *info) +{ + unsigned int hash = ila_hash(id); + unsigned int slot = hash & info->mask; + struct ila_map_bucket *head = &info->buckets[slot]; + struct ila_map *ila; + + hlist_for_each_entry_rcu(ila, &head->chain, hlnode) { + if (ila->identifier == id) + return ila; + } + + return NULL; +} + +/* Called with lock held for hlist */ +static inline void ila_release(struct ila_map *ila) +{ + hlist_del_init_rcu(&ila->hlnode); + kfree_rcu(ila, rcu); +} + +static int ila_del_mapping(struct net *net, struct ila_cfg *cfg) +{ + struct ila_net *ilan = net_generic(net, ila_net_id); + unsigned int hash = ila_hash(cfg->identifier); + struct ila_maps_info *info = &ilan->info; + struct ila_map_bucket *ilab = &info->buckets[hash & info->mask]; + struct ila_map *ila; + + spin_lock(&ilab->lock); + + ila = ila_lookup(cfg->identifier, info); + if (ila) + ila_release(ila); + + spin_unlock(&ilab->lock); + + return 0; +} + +static int ila_add_mapping(struct net *net, struct ila_cfg *cfg) +{ + struct ila_net *ilan = net_generic(net, ila_net_id); + unsigned int hash = ila_hash(cfg->identifier); + struct ila_maps_info *info = &ilan->info; + struct ila_map_bucket *ilab = &info->buckets[hash & info->mask]; + struct ila_map *ila, *ila_tmp; + + ila = kzalloc(sizeof(*ila), GFP_KERNEL); + if (!ila) + return -ENOMEM; + + ila->identifier = cfg->identifier; + ila->locator = cfg->locator; + + spin_lock(&ilab->lock); + + hlist_for_each_entry_rcu(ila_tmp, &ilab->chain, hlnode) { + if (ila_tmp->identifier == cfg->identifier) { + /* There's already a mapping for this locator, + * delete for for now. Later just modify it in place? + */ + ila_release(ila_tmp); + } + } + + if (hlist_unhashed(&ila->hlnode)) + hlist_add_head_rcu(&ila->hlnode, &ilab->chain); + + spin_unlock(&ilab->lock); + + return 0; +} + +static int ila_nl_cmd_add_mapping(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct ila_cfg cfg; + int err; + + err = parse_nl_config(info, &cfg); + if (err) + return err; + + return ila_add_mapping(net, &cfg); +} + +static int ila_nl_cmd_del_mapping(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct ila_cfg cfg; + int err; + + err = parse_nl_config(info, &cfg); + if (err) + return err; + + return ila_del_mapping(net, &cfg); +} + +static int ila_fill_info(struct ila_map *ila, struct sk_buff *msg) +{ + if (nla_put_u64(msg, ILA_ATTR_IDENTIFIER, ila->identifier) || + nla_put_u64(msg, ILA_ATTR_LOCATOR, ila->locator)) + return -1; + + return 0; +} + +static int ila_dump_info(struct ila_map *ila, u32 portid, u32 seq, + u32 flags, struct sk_buff *skb, u8 cmd) +{ + void *hdr; + + hdr = genlmsg_put(skb, portid, seq, &ila_nl_family, flags, cmd); + if (!hdr) + return -ENOMEM; + + if (ila_fill_info(ila, skb) < 0) + goto nla_put_failure; + + genlmsg_end(skb, hdr); + return 0; + +nla_put_failure: + genlmsg_cancel(skb, hdr); + return -EMSGSIZE; +} + +static int ila_nl_cmd_get_mapping(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct ila_net *ilan = net_generic(net, ila_net_id); + struct sk_buff *msg; + struct ila_cfg cfg; + struct ila_map *ila; + int ret; + + ret = parse_nl_config(info, &cfg); + if (ret) + return ret; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + rcu_read_lock(); + + ila = ila_lookup(cfg.identifier, &ilan->info); + if (ila) { + ret = ila_dump_info(ila, info->snd_portid, + info->snd_seq, 0, msg, + info->genlhdr->cmd); + } + + rcu_read_unlock(); + + if (ret < 0) + goto out_free; + + return genlmsg_reply(msg, info); + +out_free: + nlmsg_free(msg); + return ret; +} + +static int ila_nl_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct net *net = sock_net(skb->sk); + struct ila_net *ilan = net_generic(net, ila_net_id); + struct ila_maps_info *info = &ilan->info; + struct ila_map *ila; + int idx = 0; + int i; + + for (i = 0; i < info->size; i++) { + rcu_read_lock(); + hlist_for_each_entry_rcu(ila, &info->buckets[i].chain, hlnode) { + if (idx < cb->args[0]) + goto skip; + if (ila_dump_info(ila, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + skb, ILA_CMD_GET) < 0) + break; +skip: + idx++; + } + rcu_read_unlock(); + } + + cb->args[0] = idx; + return skb->len; +} + +static const struct genl_ops ila_nl_ops[] = { + { + .cmd = ILA_CMD_ADD, + .doit = ila_nl_cmd_add_mapping, + .policy = ila_nl_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = ILA_CMD_DEL, + .doit = ila_nl_cmd_del_mapping, + .policy = ila_nl_policy, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = ILA_CMD_GET, + .doit = ila_nl_cmd_get_mapping, + .dumpit = ila_nl_dump, + .policy = ila_nl_policy, + }, +}; + +#define ILA_HASH_TABLE_SIZE 1024 + +void ila_hashinfo_init(struct ila_maps_info *info) +{ + int i; + + memset(info, 0, sizeof(*info)); + + info->size = ILA_HASH_TABLE_SIZE; + info->mask = ILA_HASH_TABLE_SIZE - 1; + + for (i = 0; i < ILA_HASH_TABLE_SIZE; i++) { + struct ila_map_bucket *ilab = &info->buckets[i]; + + spin_lock_init(&ilab->lock); + INIT_HLIST_HEAD(&ilab->chain); + } +} + +static __net_init int ila_init_net(struct net *net) +{ + struct ila_net *ilan = net_generic(net, ila_net_id); + + ila_hashinfo_init(&ilan->info); + + return 0; +} + +static __net_exit void ila_exit_net(struct net *net) +{ + struct ila_net *ilan = net_generic(net, ila_net_id); + struct ila_maps_info *info = &ilan->info; + struct ila_map *ila; + struct hlist_node *tmp; + int i; + + for (i = 0; i < info->size; i++) { + struct ila_map_bucket *ilab = &info->buckets[i]; + + spin_lock(&ilab->lock); + + hlist_for_each_entry_safe(ila, tmp, &ilab->chain, hlnode) { + ila_release(ila); + } + + spin_unlock(&ilab->lock); + } + + info->size = 0; +} + +static struct pernet_operations ila_net_ops = { + .init = ila_init_net, + .exit = ila_exit_net, + .id = &ila_net_id, + .size = sizeof(struct ila_net), +}; + +static int __init ila_init(void) +{ + int ret; + + ila_init_secret(); + + ret = register_pernet_device(&ila_net_ops); + if (ret) + goto exit; + + ret = genl_register_family_with_ops(&ila_nl_family, + ila_nl_ops); + if (ret < 0) + goto unregister; + + return 0; + +unregister: + unregister_pernet_device(&ila_net_ops); +exit: + return ret; +} + +static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto, + __be32 loc[2], const __be32 new_loc[2]) +{ + int nhoff = sizeof(struct ipv6hdr); + + switch (l4_proto) { + case NEXTHDR_TCP: + if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) { + struct tcphdr *th = (struct tcphdr *) + (skb_network_header(skb) + nhoff); + + inet_proto_csum_replace8(&th->check, skb, + loc, new_loc, 1); + } + break; + case NEXTHDR_UDP: + if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) { + struct udphdr *uh = (struct udphdr *) + (skb_network_header(skb) + nhoff); + + if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) { + inet_proto_csum_replace8(&uh->check, skb, + loc, new_loc, 1); + if (!uh->check) + uh->check = CSUM_MANGLED_0; + } + } + break; + case NEXTHDR_ICMP: + if (likely(pskb_may_pull(skb, + nhoff + sizeof(struct icmp6hdr)))) { + struct icmp6hdr *ih = (struct icmp6hdr *) + (skb_network_header(skb) + nhoff); + + inet_proto_csum_replace8(&ih->icmp6_cksum, + skb, loc, new_loc, 1); + } + break; + } +} + +static void nat_locator(struct sk_buff *skb, struct ipv6hdr *ip6h, u64 locator) +{ + __be32 *words = (__be32 *)&locator; + + update_ipv6_checksum(skb, ip6h->nexthdr, (__be32 *)&ip6h->daddr, words); + memcpy(&ip6h->daddr, words, sizeof(locator)); +} + +static void __exit ila_fini(void) +{ + genl_unregister_family(&ila_nl_family); + unregister_pernet_device(&ila_net_ops); +} + +static int __ila_xlat(struct sk_buff *skb, struct net *net) +{ + struct ila_map *ila; + struct ipv6hdr *ip6h; + struct ila_net *ilan = net_generic(net, ila_net_id); + struct ila_maps_info *info = &ilan->info; + u64 identifier; + + if (skb->protocol != htons(ETH_P_IPV6)) + return 0; + + if (unlikely(!pskb_may_pull(skb, sizeof(*ip6h)))) + return 0; + + ip6h = ipv6_hdr(skb); + if (ip6h->version != 6) + return 0; + + identifier = *(u64 *)&ip6h->daddr.in6_u.u6_addr8[8]; + ila = ila_lookup(identifier, info); + if (ila) + nat_locator(skb, ip6h, ila->locator); + + return 0; +} + +int ila_xlat_outgoing(struct sk_buff *skb) +{ + struct net *net = dev_net(skb_dst(skb)->dev); + + return __ila_xlat(skb, net); +} +EXPORT_SYMBOL(ila_xlat_outgoing); + +int ila_xlat_incoming(struct sk_buff *skb) +{ + struct net *net = dev_net(skb->dev); + + return __ila_xlat(skb, net); +} +EXPORT_SYMBOL(ila_xlat_incoming); + +module_init(ila_init); +module_exit(ila_fini); +MODULE_AUTHOR("Tom Herbert <t...@herbertland.com>"); +MODULE_LICENSE("GPL"); -- 1.8.1 -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html