From 428ce85179a468661433d8c182a62d5728517949 Mon Sep 17 00:00:00 2001 From: tuna2134 Date: Mon, 29 Jun 2026 22:44:27 +0900 Subject: [PATCH] Initail commit --- Makefile | 25 +++ README.md | 69 +++++++ build.sh | 13 ++ etherip6.c | 520 ++++++++++++++++++++++++++++++++++++++++++++++++ etherip6_uapi.h | 16 ++ etherip6ctl.c | 240 ++++++++++++++++++++++ rfc3378.txt | 507 ++++++++++++++++++++++++++++++++++++++++++++++ test.sh | 1 + 8 files changed, 1391 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100755 build.sh create mode 100644 etherip6.c create mode 100644 etherip6_uapi.h create mode 100644 etherip6ctl.c create mode 100644 rfc3378.txt create mode 100644 test.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5552778 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +KDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +obj-m += etherip6.o + +.PHONY: all module tools clean + +all: module tools + +module: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +tools: etherip6ctl + +etherip6ctl: etherip6ctl.c etherip6_uapi.h + $(CC) $(CFLAGS) -Wall -Wextra -O2 -o $@ etherip6ctl.c + +clean: + @if [ -d "$(KDIR)" ]; then \ + $(MAKE) -C "$(KDIR)" M="$(PWD)" clean; \ + else \ + $(RM) -r .tmp_versions; \ + $(RM) *.o .*.o *.ko *.mod *.mod.c Module.symvers modules.order .*.cmd; \ + fi + $(RM) etherip6ctl diff --git a/README.md b/README.md new file mode 100644 index 0000000..780edd4 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# etherip-kmod + +Out-of-tree Linux kernel module for RFC3378 EtherIP over IPv6 endpoints. + +The module registers a rtnetlink link kind named `etherip6`. A companion +`etherip6ctl` helper is included because stock iproute2 does not know how to +encode this module's private tunnel attributes. + +## Build + +On Arch Linux, install headers matching the kernel you will load into: + +```sh +sudo pacman -S linux-headers +make +``` + +If you need to build against a specific headers tree: + +```sh +make KDIR=/usr/lib/modules/$(uname -r)/build +``` + +## Load + +```sh +sudo insmod etherip6.ko +``` + +## Create a Tunnel + +```sh +sudo ./etherip6ctl add eip0 local 2001:db8:1::1 remote 2001:db8:1::2 +sudo ip link set eip0 up +sudo ip addr add 192.0.2.1/30 dev eip0 +``` + +For link-local IPv6 endpoints, specify the egress interface: + +```sh +sudo ./etherip6ctl add eip0 local fe80::1 remote fe80::2 link eth0 +``` + +Delete the device with: + +```sh +sudo ./etherip6ctl del eip0 +``` + +## Fragmentation + +The transmit path permits local IPv6 fragmentation for encapsulated EtherIP +packets that exceed the outer path MTU. This lets a larger inner Ethernet +frame be split into multiple outer IPv6 fragments by the local host. + +You can still set a smaller tunnel MTU when you want to avoid IPv6 fragments: + +```sh +sudo ip link set eip0 mtu 1444 +``` + +For a 1500-byte outer link, 1444 leaves room for the 40-byte IPv6 header, +2-byte EtherIP header, and 14-byte Ethernet header carried inside EtherIP. + +## Wire Format + +The outer IPv6 packet uses Next Header 97. The EtherIP header is the RFC3378 +two-byte version/reserved field with version 3, followed by the complete +Ethernet frame. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..77cc350 --- /dev/null +++ b/build.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu + +KVER="$(uname -r)" +KDIR="/lib/modules/$KVER/build" + +if [ ! -d "$KDIR" ]; then + echo "error: kernel headers for running kernel $KVER were not found at $KDIR" >&2 + echo "install matching Arch headers or reboot into the kernel that matches installed headers" >&2 + exit 1 +fi + +make KDIR="$KDIR" diff --git a/etherip6.c b/etherip6.c new file mode 100644 index 0000000..71da8ec --- /dev/null +++ b/etherip6.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "etherip6_uapi.h" + +#ifndef IPPROTO_ETHERIP +#define IPPROTO_ETHERIP 97 +#endif + +#define ETHERIP6_VERSION 3 +#define ETHERIP6_HDR htons(ETHERIP6_VERSION << 12) +#define ETHERIP6_HLEN 2 +#define ETHERIP6_DEFAULT_HOP_LIMIT 64 + +struct etherip6_tunnel { + struct list_head list; + struct net_device *dev; + struct in6_addr local; + struct in6_addr remote; + u32 link; + u8 hop_limit; + struct pcpu_sw_netstats __percpu *stats; + atomic_long_t tx_dropped; + atomic_long_t rx_dropped; +}; + +struct etherip6_net { + struct list_head tunnels; + rwlock_t lock; +}; + +static unsigned int etherip6_net_id; + +static const struct nla_policy etherip6_policy[IFLA_ETHERIP6_MAX + 1] = { + [IFLA_ETHERIP6_LOCAL] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [IFLA_ETHERIP6_REMOTE] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [IFLA_ETHERIP6_LINK] = { .type = NLA_U32 }, + [IFLA_ETHERIP6_HOP_LIMIT] = { .type = NLA_U8 }, +}; + +static struct etherip6_net *etherip6_pernet(struct net *net) +{ + return net_generic(net, etherip6_net_id); +} + +static bool etherip6_addr_equal(const struct in6_addr *a, + const struct in6_addr *b) +{ + return ipv6_addr_equal(a, b); +} + +static struct etherip6_tunnel *etherip6_lookup(struct net *net, + const struct in6_addr *local, + const struct in6_addr *remote, + int iif, bool running_only) +{ + struct etherip6_net *en = etherip6_pernet(net); + struct etherip6_tunnel *tun; + + list_for_each_entry(tun, &en->tunnels, list) { + if (running_only && !netif_running(tun->dev)) + continue; + if (tun->link && tun->link != iif) + continue; + if (!etherip6_addr_equal(&tun->local, local)) + continue; + if (!etherip6_addr_equal(&tun->remote, remote)) + continue; + return tun; + } + + return NULL; +} + +static struct etherip6_tunnel * +etherip6_lookup_unique_rx(struct net *net, const struct in6_addr *local, + const struct in6_addr *remote, int iif, + bool match_local, bool match_remote) +{ + struct etherip6_net *en = etherip6_pernet(net); + struct etherip6_tunnel *tun, *match = NULL; + + list_for_each_entry(tun, &en->tunnels, list) { + if (!netif_running(tun->dev)) + continue; + if (tun->link && tun->link != iif) + continue; + if (match_local && !etherip6_addr_equal(&tun->local, local)) + continue; + if (match_remote && !etherip6_addr_equal(&tun->remote, remote)) + continue; + if (match) + return NULL; + match = tun; + } + + return match; +} + +static struct etherip6_tunnel *etherip6_lookup_rx(struct net *net, + const struct in6_addr *local, + const struct in6_addr *remote, + int iif) +{ + struct etherip6_tunnel *tun; + + tun = etherip6_lookup(net, local, remote, iif, true); + if (tun) + return tun; + + tun = etherip6_lookup_unique_rx(net, local, remote, iif, false, true); + if (tun) + return tun; + + tun = etherip6_lookup_unique_rx(net, local, remote, iif, true, false); + if (tun) + return tun; + + return etherip6_lookup_unique_rx(net, local, remote, iif, false, false); +} + +static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct etherip6_tunnel *tun = netdev_priv(dev); + struct net *net = dev_net(dev); + struct dst_entry *dst; + struct flowi6 fl6 = {}; + struct ipv6hdr *ip6h; + struct pcpu_sw_netstats *stats; + __be16 *etherip; + unsigned int payload_len; + int headroom; + int err; + + if (skb->len + ETHERIP6_HLEN > IPV6_MAXPLEN) + goto tx_error; + + fl6.flowi6_proto = IPPROTO_ETHERIP; + fl6.flowi6_oif = tun->link; + fl6.daddr = tun->remote; + fl6.saddr = tun->local; + + dst = ip6_route_output(net, NULL, &fl6); + if (dst->error) { + dst_release(dst); + goto tx_error; + } + + headroom = LL_RESERVED_SPACE(dst->dev) + sizeof(*ip6h) + ETHERIP6_HLEN; + err = skb_cow_head(skb, headroom); + if (err) { + dst_release(dst); + goto tx_error; + } + + payload_len = skb->len + ETHERIP6_HLEN; + + etherip = skb_push(skb, ETHERIP6_HLEN); + *etherip = ETHERIP6_HDR; + + skb_push(skb, sizeof(*ip6h)); + skb_reset_network_header(skb); + ip6h = ipv6_hdr(skb); + ip6_flow_hdr(ip6h, 0, 0); + ip6h->payload_len = htons(payload_len); + ip6h->nexthdr = IPPROTO_ETHERIP; + ip6h->hop_limit = tun->hop_limit; + ip6h->saddr = tun->local; + ip6h->daddr = tun->remote; + + skb->protocol = htons(ETH_P_IPV6); + skb->dev = dst->dev; + skb->ignore_df = 1; + skb_dst_set(skb, dst); + + stats = this_cpu_ptr(tun->stats); + u64_stats_update_begin(&stats->syncp); + u64_stats_inc(&stats->tx_packets); + u64_stats_add(&stats->tx_bytes, payload_len - ETHERIP6_HLEN); + u64_stats_update_end(&stats->syncp); + + return ip6_local_out(net, NULL, skb) == 0 ? NETDEV_TX_OK : NETDEV_TX_OK; + +tx_error: + atomic_long_inc(&tun->tx_dropped); + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static int etherip6_open(struct net_device *dev) +{ + netif_start_queue(dev); + return 0; +} + +static int etherip6_stop(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +static void etherip6_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *stats) +{ + struct etherip6_tunnel *tun = netdev_priv(dev); + + dev_fetch_sw_netstats(stats, tun->stats); + stats->tx_dropped = atomic_long_read(&tun->tx_dropped); + stats->rx_dropped = atomic_long_read(&tun->rx_dropped); +} + +static void etherip6_uninit(struct net_device *dev); + +static const struct net_device_ops etherip6_netdev_ops = { + .ndo_open = etherip6_open, + .ndo_stop = etherip6_stop, + .ndo_start_xmit = etherip6_xmit, + .ndo_get_stats64 = etherip6_get_stats64, + .ndo_uninit = etherip6_uninit, +}; + +static void etherip6_setup(struct net_device *dev) +{ + ether_setup(dev); + dev->netdev_ops = ðerip6_netdev_ops; + dev->needs_free_netdev = true; + dev->type = ARPHRD_ETHER; + dev->flags |= IFF_NOARP; + dev->features &= ~(NETIF_F_GSO_MASK | NETIF_F_CSUM_MASK); + dev->hw_features = 0; + dev->vlan_features = 0; + dev->min_mtu = ETH_MIN_MTU; + dev->max_mtu = ETH_DATA_LEN; + eth_hw_addr_random(dev); +} + +static int etherip6_validate(struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + if (!data) + return -EINVAL; + if (!data[IFLA_ETHERIP6_LOCAL]) { + NL_SET_ERR_MSG(extack, "local IPv6 address is required"); + return -EINVAL; + } + if (!data[IFLA_ETHERIP6_REMOTE]) { + NL_SET_ERR_MSG(extack, "remote IPv6 address is required"); + return -EINVAL; + } + + return 0; +} + +static int etherip6_newlink(struct net_device *dev, + struct rtnl_newlink_params *params, + struct netlink_ext_ack *extack) +{ + struct nlattr **data = params->data; + struct etherip6_tunnel *tun = netdev_priv(dev); + struct etherip6_net *en = etherip6_pernet(dev_net(dev)); + struct net_device *link_dev; + struct etherip6_tunnel *old; + int err; + + nla_memcpy(&tun->local, data[IFLA_ETHERIP6_LOCAL], sizeof(tun->local)); + nla_memcpy(&tun->remote, data[IFLA_ETHERIP6_REMOTE], sizeof(tun->remote)); + + if (data[IFLA_ETHERIP6_LINK]) + tun->link = nla_get_u32(data[IFLA_ETHERIP6_LINK]); + if (data[IFLA_ETHERIP6_HOP_LIMIT]) + tun->hop_limit = nla_get_u8(data[IFLA_ETHERIP6_HOP_LIMIT]); + else + tun->hop_limit = ETHERIP6_DEFAULT_HOP_LIMIT; + + if (ipv6_addr_any(&tun->local) || ipv6_addr_any(&tun->remote)) { + NL_SET_ERR_MSG(extack, "local and remote must be non-zero IPv6 addresses"); + return -EINVAL; + } + if (ipv6_addr_is_multicast(&tun->remote)) { + NL_SET_ERR_MSG(extack, "remote must not be multicast"); + return -EINVAL; + } + if (!tun->hop_limit) { + NL_SET_ERR_MSG(extack, "hop limit must be non-zero"); + return -EINVAL; + } + if (tun->link) { + link_dev = __dev_get_by_index(dev_net(dev), tun->link); + if (!link_dev) { + NL_SET_ERR_MSG(extack, "link interface does not exist"); + return -ENODEV; + } + } + if ((ipv6_addr_type(&tun->local) & IPV6_ADDR_LINKLOCAL || + ipv6_addr_type(&tun->remote) & IPV6_ADDR_LINKLOCAL) && !tun->link) { + NL_SET_ERR_MSG(extack, "link-local endpoint requires link"); + return -EINVAL; + } + + tun->dev = dev; + atomic_long_set(&tun->tx_dropped, 0); + atomic_long_set(&tun->rx_dropped, 0); + tun->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats); + if (!tun->stats) + return -ENOMEM; + + write_lock_bh(&en->lock); + old = etherip6_lookup(dev_net(dev), &tun->local, &tun->remote, + tun->link, false); + if (old) + err = -EEXIST; + else + list_add_rcu(&tun->list, &en->tunnels); + write_unlock_bh(&en->lock); + + if (old) { + free_percpu(tun->stats); + NL_SET_ERR_MSG(extack, "duplicate local/remote/link tunnel"); + return err; + } + + err = register_netdevice(dev); + if (err) { + write_lock_bh(&en->lock); + list_del_rcu(&tun->list); + write_unlock_bh(&en->lock); + synchronize_rcu(); + free_percpu(tun->stats); + return err; + } + + return 0; +} + +static void etherip6_dellink(struct net_device *dev, struct list_head *head) +{ + unregister_netdevice_queue(dev, head); +} + +static void etherip6_uninit(struct net_device *dev) +{ + struct etherip6_tunnel *tun = netdev_priv(dev); + struct etherip6_net *en = etherip6_pernet(dev_net(dev)); + + write_lock_bh(&en->lock); + list_del_rcu(&tun->list); + write_unlock_bh(&en->lock); + synchronize_rcu(); + free_percpu(tun->stats); +} + +static size_t etherip6_get_size(const struct net_device *dev) +{ + return nla_total_size(sizeof(struct in6_addr)) + + nla_total_size(sizeof(struct in6_addr)) + + nla_total_size(sizeof(u32)) + + nla_total_size(sizeof(u8)); +} + +static int etherip6_fill_info(struct sk_buff *skb, const struct net_device *dev) +{ + const struct etherip6_tunnel *tun = netdev_priv(dev); + + if (nla_put(skb, IFLA_ETHERIP6_LOCAL, sizeof(tun->local), &tun->local) || + nla_put(skb, IFLA_ETHERIP6_REMOTE, sizeof(tun->remote), &tun->remote) || + nla_put_u32(skb, IFLA_ETHERIP6_LINK, tun->link) || + nla_put_u8(skb, IFLA_ETHERIP6_HOP_LIMIT, tun->hop_limit)) + return -EMSGSIZE; + + return 0; +} + +static struct rtnl_link_ops etherip6_link_ops __read_mostly = { + .kind = "etherip6", + .priv_size = sizeof(struct etherip6_tunnel), + .setup = etherip6_setup, + .maxtype = IFLA_ETHERIP6_MAX, + .policy = etherip6_policy, + .validate = etherip6_validate, + .newlink = etherip6_newlink, + .dellink = etherip6_dellink, + .get_size = etherip6_get_size, + .fill_info = etherip6_fill_info, +}; + +static int etherip6_rcv(struct sk_buff *skb) +{ + struct net *net = dev_net(skb->dev); + const struct ipv6hdr *ip6h = ipv6_hdr(skb); + struct etherip6_tunnel *tun; + struct pcpu_sw_netstats *stats; + __be16 etherip; + int iif = skb->dev->ifindex; + + if (!pskb_may_pull(skb, ETHERIP6_HLEN)) + goto drop; + + etherip = *(__be16 *)skb->data; + if (etherip != ETHERIP6_HDR) + goto drop; + + read_lock_bh(ðerip6_pernet(net)->lock); + tun = etherip6_lookup_rx(net, &ip6h->daddr, &ip6h->saddr, iif); + if (tun) + dev_hold(tun->dev); + read_unlock_bh(ðerip6_pernet(net)->lock); + + if (!tun) + goto drop; + + skb_pull(skb, ETHERIP6_HLEN); + if (!pskb_may_pull(skb, ETH_HLEN)) { + atomic_long_inc(&tun->rx_dropped); + dev_put(tun->dev); + goto drop; + } + + skb->dev = tun->dev; + skb_scrub_packet(skb, true); + skb_reset_mac_header(skb); + skb->protocol = eth_type_trans(skb, tun->dev); + skb->pkt_type = PACKET_HOST; + skb_reset_network_header(skb); + + stats = this_cpu_ptr(tun->stats); + u64_stats_update_begin(&stats->syncp); + u64_stats_inc(&stats->rx_packets); + u64_stats_add(&stats->rx_bytes, skb->len); + u64_stats_update_end(&stats->syncp); + + netif_rx(skb); + dev_put(tun->dev); + return 0; + +drop: + kfree_skb(skb); + return 0; +} + +static const struct inet6_protocol etherip6_protocol = { + .handler = etherip6_rcv, + .flags = INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL, +}; + +static int __net_init etherip6_net_init(struct net *net) +{ + struct etherip6_net *en = etherip6_pernet(net); + + INIT_LIST_HEAD(&en->tunnels); + rwlock_init(&en->lock); + return 0; +} + +static struct pernet_operations etherip6_net_ops = { + .init = etherip6_net_init, + .id = ðerip6_net_id, + .size = sizeof(struct etherip6_net), +}; + +static int __init etherip6_init(void) +{ + int err; + + err = register_pernet_subsys(ðerip6_net_ops); + if (err) + return err; + + err = inet6_add_protocol(ðerip6_protocol, IPPROTO_ETHERIP); + if (err) + goto err_pernet; + + err = rtnl_link_register(ðerip6_link_ops); + if (err) + goto err_proto; + + pr_info("etherip6: RFC3378 EtherIP over IPv6 loaded\n"); + return 0; + +err_proto: + inet6_del_protocol(ðerip6_protocol, IPPROTO_ETHERIP); +err_pernet: + unregister_pernet_subsys(ðerip6_net_ops); + return err; +} + +static void __exit etherip6_exit(void) +{ + rtnl_link_unregister(ðerip6_link_ops); + inet6_del_protocol(ðerip6_protocol, IPPROTO_ETHERIP); + unregister_pernet_subsys(ðerip6_net_ops); + pr_info("etherip6: unloaded\n"); +} + +module_init(etherip6_init); +module_exit(etherip6_exit); + +MODULE_AUTHOR("Codex"); +MODULE_DESCRIPTION("RFC3378 EtherIP tunnel endpoint over IPv6"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_RTNL_LINK("etherip6"); diff --git a/etherip6_uapi.h b/etherip6_uapi.h new file mode 100644 index 0000000..051b25a --- /dev/null +++ b/etherip6_uapi.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _ETHERIP6_UAPI_H +#define _ETHERIP6_UAPI_H + +enum { + IFLA_ETHERIP6_UNSPEC, + IFLA_ETHERIP6_LOCAL, + IFLA_ETHERIP6_REMOTE, + IFLA_ETHERIP6_LINK, + IFLA_ETHERIP6_HOP_LIMIT, + __IFLA_ETHERIP6_MAX, +}; + +#define IFLA_ETHERIP6_MAX (__IFLA_ETHERIP6_MAX - 1) + +#endif diff --git a/etherip6ctl.c b/etherip6ctl.c new file mode 100644 index 0000000..8be3f06 --- /dev/null +++ b/etherip6ctl.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "etherip6_uapi.h" + +#define BUF_SIZE 8192 + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage:\n" + " %s add DEV local ADDR remote ADDR [link IFACE] [hoplimit N]\n" + " %s del DEV\n", + prog, prog); +} + +static void addattr(struct nlmsghdr *nlh, size_t maxlen, int type, + const void *data, size_t len) +{ + struct nlattr *nla; + size_t nla_len = NLA_HDRLEN + len; + size_t new_len = NLMSG_ALIGN(nlh->nlmsg_len) + NLA_ALIGN(nla_len); + + if (new_len > maxlen) { + fprintf(stderr, "netlink message too large\n"); + exit(1); + } + + nla = (struct nlattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); + nla->nla_type = type; + nla->nla_len = nla_len; + memcpy((char *)nla + NLA_HDRLEN, data, len); + nlh->nlmsg_len = new_len; +} + +static struct nlattr *nest_start(struct nlmsghdr *nlh, size_t maxlen, int type) +{ + struct nlattr *nla; + + addattr(nlh, maxlen, type, NULL, 0); + nla = (struct nlattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len) - + NLA_ALIGN(NLA_HDRLEN)); + return nla; +} + +static void nest_end(struct nlmsghdr *nlh, struct nlattr *nla) +{ + nla->nla_len = (char *)nlh + nlh->nlmsg_len - (char *)nla; +} + +static void addattr_string(struct nlmsghdr *nlh, size_t maxlen, int type, + const char *str) +{ + addattr(nlh, maxlen, type, str, strlen(str) + 1); +} + +static int rtnl_talk(struct nlmsghdr *nlh) +{ + struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; + char buf[BUF_SIZE]; + struct iovec iov = { .iov_base = nlh, .iov_len = nlh->nlmsg_len }; + struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa), + .msg_iov = &iov, .msg_iovlen = 1 }; + int fd, ret; + + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) + return -1; + + nlh->nlmsg_seq = 1; + if (sendmsg(fd, &msg, 0) < 0) { + ret = -1; + goto out; + } + + ret = recv(fd, buf, sizeof(buf), 0); + if (ret < 0) + goto out; + + for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, ret); + nlh = NLMSG_NEXT(nlh, ret)) { + if (nlh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = NLMSG_DATA(nlh); + + if (err->error) { + errno = -err->error; + ret = -1; + } else { + ret = 0; + } + goto out; + } + } + + ret = 0; +out: + close(fd); + return ret; +} + +static int cmd_add(int argc, char **argv) +{ + char buf[BUF_SIZE] = {}; + struct nlmsghdr *nlh = (struct nlmsghdr *)buf; + struct ifinfomsg *ifi; + struct nlattr *linkinfo, *infodata; + struct in6_addr local = {}, remote = {}; + uint32_t link = 0; + uint8_t hoplimit = 64; + bool have_local = false, have_remote = false; + const char *dev; + int i; + + if (argc < 7) { + usage(argv[0]); + return 1; + } + + dev = argv[2]; + for (i = 3; i < argc; i++) { + if (!strcmp(argv[i], "local") && i + 1 < argc) { + if (inet_pton(AF_INET6, argv[++i], &local) != 1) { + fprintf(stderr, "invalid local IPv6 address\n"); + return 1; + } + have_local = true; + } else if (!strcmp(argv[i], "remote") && i + 1 < argc) { + if (inet_pton(AF_INET6, argv[++i], &remote) != 1) { + fprintf(stderr, "invalid remote IPv6 address\n"); + return 1; + } + have_remote = true; + } else if (!strcmp(argv[i], "link") && i + 1 < argc) { + link = if_nametoindex(argv[++i]); + if (!link) { + perror("if_nametoindex"); + return 1; + } + } else if (!strcmp(argv[i], "hoplimit") && i + 1 < argc) { + unsigned long v = strtoul(argv[++i], NULL, 0); + + if (!v || v > 255) { + fprintf(stderr, "hoplimit must be 1..255\n"); + return 1; + } + hoplimit = (uint8_t)v; + } else { + usage(argv[0]); + return 1; + } + } + + if (!have_local || !have_remote) { + usage(argv[0]); + return 1; + } + + nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)); + nlh->nlmsg_type = RTM_NEWLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL; + ifi = NLMSG_DATA(nlh); + ifi->ifi_family = AF_UNSPEC; + + addattr_string(nlh, sizeof(buf), IFLA_IFNAME, dev); + linkinfo = nest_start(nlh, sizeof(buf), IFLA_LINKINFO); + addattr_string(nlh, sizeof(buf), IFLA_INFO_KIND, "etherip6"); + infodata = nest_start(nlh, sizeof(buf), IFLA_INFO_DATA); + addattr(nlh, sizeof(buf), IFLA_ETHERIP6_LOCAL, &local, sizeof(local)); + addattr(nlh, sizeof(buf), IFLA_ETHERIP6_REMOTE, &remote, sizeof(remote)); + if (link) + addattr(nlh, sizeof(buf), IFLA_ETHERIP6_LINK, &link, sizeof(link)); + addattr(nlh, sizeof(buf), IFLA_ETHERIP6_HOP_LIMIT, &hoplimit, sizeof(hoplimit)); + nest_end(nlh, infodata); + nest_end(nlh, linkinfo); + + if (rtnl_talk(nlh) < 0) { + perror("RTM_NEWLINK"); + return 1; + } + + return 0; +} + +static int cmd_del(int argc, char **argv) +{ + char buf[BUF_SIZE] = {}; + struct nlmsghdr *nlh = (struct nlmsghdr *)buf; + struct ifinfomsg *ifi; + + if (argc != 3) { + usage(argv[0]); + return 1; + } + + nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)); + nlh->nlmsg_type = RTM_DELLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + ifi = NLMSG_DATA(nlh); + ifi->ifi_family = AF_UNSPEC; + ifi->ifi_index = if_nametoindex(argv[2]); + if (!ifi->ifi_index) { + perror("if_nametoindex"); + return 1; + } + + if (rtnl_talk(nlh) < 0) { + perror("RTM_DELLINK"); + return 1; + } + + return 0; +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + usage(argv[0]); + return 1; + } + + if (!strcmp(argv[1], "add")) + return cmd_add(argc, argv); + if (!strcmp(argv[1], "del")) + return cmd_del(argc, argv); + + usage(argv[0]); + return 1; +} diff --git a/rfc3378.txt b/rfc3378.txt new file mode 100644 index 0000000..8dd8477 --- /dev/null +++ b/rfc3378.txt @@ -0,0 +1,507 @@ + + + + + + +Network Working Group R. Housley +Request for Comments: 3378 RSA Laboratories +Category: Informational S. Hollenbeck + VeriSign, Inc. + September 2002 + + + EtherIP: Tunneling Ethernet Frames in IP Datagrams + +Status of this Memo + + This memo provides information for the Internet community. It does + not specify an Internet standard of any kind. Distribution of this + memo is unlimited. + +Abstract + + This document describes the EtherIP, an early tunneling protocol, to + provide informational and historical context for the assignment of IP + protocol 97. EtherIP tunnels Ethernet and IEEE 802.3 media access + control frames in IP datagrams so that non-IP traffic can traverse an + IP internet. The protocol is very lightweight, and it does not + provide protection against infinite loops. + +1. Introduction + + EtherIP was first designed and developed in 1991. This document was + written to document the protocol for informational purposes and to + provide historical context for the assignment of IP protocol 97 by + IANA. + + The IETF Layer Two Tunneling Protocol Extensions (L2TPEXT) Working + Group and IETF Pseudo Wire Emulation Edge-to-Edge (PWE3) Working + Group are developing protocols that overcome the deficiencies of + EtherIP. In general, the standards track protocols produced by these + IETF working groups should be used instead of EtherIP. + + The EtherIP protocol is used to tunnel Ethernet [DIX] and IEEE 802.3 + [CSMA/CD] media access control (MAC) frames (including IEEE 802.1Q + [VLAN] datagrams) across an IP internet. Tunneling is usually + performed when the layer three protocol carried in the MAC frames is + not IP or when encryption obscures the layer three protocol control + information needed for routing. EtherIP may be implemented in an end + station to enable tunneling for that single station, or it may be + implemented in a bridge-like station to enable tunneling for multiple + stations connected to a particular local area network (LAN) segment. + + + + + +Housley & Hollenbeck Informational [Page 1] + +RFC 3378 EtherIP September 2002 + + + EtherIP may be used to enable communications between stations that + implement Ethernet or IEEE 802.3 with a layer three protocol other + than IP. For example, two stations connected to different Ethernet + LANs using the Xerox Network Systems Internetwork Datagram Protocol + (IDP) [XNS] could employ EtherIP to enable communications across the + Internet. + + EtherIP may be used to enable communications between stations that + encrypt the Ethernet or IEEE 802.3 payload. Regardless of the layer + three protocol used, encryption obscures the layer three protocol + control information, making routing impossible. For example, two + stations connected to different Ethernet LANs using IEEE 802.10b + [SDE] could employ EtherIP to enable encrypted communications across + the Internet. + + EtherIP may be implemented in a single station to provide tunneling + of Ethernet or IEEE 802.3 frames for either of the reasons stated + above. Such implementations require processing rules to determine + which MAC frames to tunnel and which MAC frames to ignore. Most + often, these processing rules are based on the destination address or + the EtherType. + + EtherIP may be implemented in a bridge-like station to provide + tunneling services for all stations connected to a particular LAN + segment. Such implementations promiscuously listen to all of the + traffic on the LAN segment, then apply processing rules to determine + which MAC frames to tunnel and which MAC frames to ignore. MAC + frames that require tunneling are encapsulated with EtherIP and IP, + then transmitted to the local IP router for delivery to the bridge- + like station serving the remote LAN. Most often, these processing + rules are based on the source address, the destination address, or + the EtherType. Care in establishing these rules must be exercised to + ensure that the same MAC frame does not get transmitted endlessly + between several bridge-like stations, especially when broadcast or + multicast destination MAC addresses are used as selection criteria. + Infinite loops can result if the topology is not restricted to a + tree, but the construction of the tree is left to the human that is + configuring the bridge-like stations. + +1.1. Conventions Used In This Document + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + + + + + + + +Housley & Hollenbeck Informational [Page 2] + +RFC 3378 EtherIP September 2002 + + +2. Protocol Format + + EtherIP segments are sent and received as internet datagrams. An + Internet Protocol (IP) header carries several information fields, + including an identifier for the next level protocol. An EtherIP + header follows the internet header, providing information specific to + the EtherIP protocol. + + Internet Protocol version 4 (IPv4) [RFC791] defines an 8-bit field + called "Protocol" to identify the next level protocol. The value of + this field MUST be set to 97 decimal (141 octal, 61 hex) to identify + an EtherIP datagram. + + EtherIP datagrams contain a 16-bit header and a variable-length + encapsulated Ethernet or IEEE 802.3 frame that immediately follows IP + fields. + + +-----------------------+-----------------------------+ + | | | | + | IP | EtherIP Header | Encapsulated Ethernet Frame | + | | | | + +-----------------------+-----------------------------+ + + Figure 1: EtherIP Datagram Description + + The 16-bit EtherIP header field consists of two parts: a 4-bit + version field that identifies the EtherIP protocol version and a + 12-bit field reserved for future use. The value of the version field + MUST be 3 (three, '0011' binary). The value of the reserved field + MUST be 0 (zero). Earlier versions of this protocol used an 8-bit + header field. The Xerox Ethernet Tunnel (XET) employed the 8-bit + header. The 16-bit header field provides memory alignment advantages + in some implementation environments. + + In summary, the EtherIP Header has two fields: + + Bits 0-3: Protocol version + Bits 4-15: Reserved for future use + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | + | VERSION | RESERVED | + | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Figure 2: EtherIP Header Format (in bits) + + + + +Housley & Hollenbeck Informational [Page 3] + +RFC 3378 EtherIP September 2002 + + + The encapsulated Ethernet frame field contains a complete Ethernet or + IEEE 802.3 frame of any type less the frame check sequence (FCS) + value. The IP checksum does not provide integrity protection for the + Ethernet/IEEE 802.3 frame, so some higher-layer protocol encapsulated + by the Ethernet/IEEE 802.3 frame is expected to provide the integrity + protection. + +3. Sending Procedures + + This section describes the processing to encapsulate an Ethernet or + IEEE 802.3 MAC frame in an EtherIP datagram. First, the + implementation determines whether the MAC frame requires tunneling. + Then, if tunneling is required, the MAC frame is processed according + to the steps provided in this section. Stations processing VLAN + datagrams MAY need to examine the VLAN header to make appropriate + tunneling decisions. + + An end station that implements EtherIP may tunnel some traffic, but + not all traffic. Thus, the first step in processing a MAC frame is + to determine if the frame needs to be tunneled or not. If the + recipient station is connected to the same LAN as the source station, + then tunneling is not needed. If the network connecting the stations + can route the layer three protocol, then tunneling is not needed. + Other environment specific criteria MAY also be applied. If + tunneling is not needed, skip all EtherIP processing and perform + normal data link layer processing to transmit the MAC frame. + Otherwise, follow the steps described below. + + A bridge-like station promiscuously listens to all of the MAC frames + on the LAN. Each MAC frame read from the LAN is examined to + determine if it needs to be tunneled. If the recipient station is + connected to the same LAN as the source station, then tunneling is + not needed. If the destination MAC address is a router serving the + LAN, then tunneling is not needed. Other environment specific + criteria MAY also be applied. If tunneling is not needed, then + discard the MAC frame. Otherwise, follow the steps described below. + + The EtherIP encapsulation process is as follows: + + 1. Prepend the 16-bit EtherIP header to the MAC frame. The EtherIP + Version field MUST be set to 3 (three), and the EtherIP Reserved + field MUST be set to 0 (zero). The MAC frame MUST NOT include the + FCS. + + 2. Determine the destination IP address of the remote EtherIP + station. This address is usually determined from the destination + MAC address. + + + + +Housley & Hollenbeck Informational [Page 4] + +RFC 3378 EtherIP September 2002 + + + 3. Encapsulate the EtherIP datagram in an IP datagram with the + destination IP address determined in the previous step, and the + IPv4 Protocol field MUST be set to 97 (decimal). + + 4. Transmit the resulting IP datagram to the remote EtherIP station + via the IP router serving the LAN. + +4. Receiving Procedures + + This section describes the processing to decapsulate an Ethernet or + IEEE 802.3 MAC frame from an EtherIP datagram. + + Since a bridge-like station promiscuously listens to all of the MAC + frames on the LAN, it may need to separate the MAC frames that + encapsulate IP datagrams addressed to it from MAC frames that are + candidates for decapsulation. The process for identifying MAC frames + that are candidates for decapsulation is as follows: + + 1. Perform normal data link layer processing to receive a suspected + EtherIP datagram. + + 2. If the recipient station is connected to the same LAN as the + source station, then ignore the frame. In most environments, + frames with a source MAC address other than the IP router serving + the LAN are ignored. + + 3. If the network connecting the stations can route the layer three + protocol, then decapsulation is not needed, and the frame is + ignored. + + 4. Ignore frames that do not contain an IP datagram. + + 5. Examine the IPv4 protocol field to confirm that the value of the + field is 97 (decimal). If not, ignore the frame. + + Other environment specific criteria MAY also be applied. + + Upon reception of an IPv4 datagram with the Protocol field set to 97 + (decimal), the MAC frame is processed as follows: + + 1. Examine the 16-bit EtherIP header. Confirm that the value of the + version field is 3 (three), and that the value of the Reserved + field is 0 (zero). Frames with other values MUST be discarded. + + 2. Extract the encapsulated MAC frame from the EtherIP datagram. + Note that the extracted frame MUST NOT include a FCS value. + + + + + +Housley & Hollenbeck Informational [Page 5] + +RFC 3378 EtherIP September 2002 + + + 3. Perform normal data link layer processing to transmit the + extracted MAC frame to the destination station on the LAN. The + FCS MUST be calculated and appended to the frame as part of the + data link layer transmission processing. + +5. IANA Considerations + + IANA has assigned IP protocol value 97 (decimal) for EtherIP. No + further action or review is required. + +6. Security Considerations + + EtherIP can be used to enable the transfer of encrypted Ethernet or + IEEE 802.3 frame payloads. In this regard, EtherIP can improve + security. However, if a firewall permits EtherIP traffic to pass in + and out of a protected enclave, arbitrary communications are enabled. + Therefore, if a firewall is configured to permit communication using + EtherIP, then additional checking of each frame is probably necessary + to ensure that the security policy that the firewall is installed to + enforce is not violated. + + Further, the addition of EtherIP can expose a particular environment + to additional security threats. Assumptions that might be + appropriate when all communicating nodes are attached to one Ethernet + segment or switch may no longer hold when nodes are attached to + different Ethernet segments or switches are bridged together with + EtherIP. It is outside the scope of this specification, which + documents an existing practice, to fully analyze and review the risks + of Ethernet tunneling. The IETF Pseudo-wire Emulation Working Group + is doing work in this area, and this group is expected to provide + information about general layering as well as specific Ethernet over + IP documents. An example should make the concern clear. A number of + IETF standards employ relatively weak security mechanisms when + communicating nodes are expected to be connected to the same local + area network. The Virtual Router Redundancy Protocol [RFC2338] is + one instance. The relatively weak security mechanisms represent a + greater vulnerability in an emulated Ethernet. One solution is to + protect the IP datagrams that carry EtherIP with IPsec [RFC2401]. + + The IETF Pseudo-wire Emulation Working Group may document other + security mechanisms as well. + + + + + + + + + + +Housley & Hollenbeck Informational [Page 6] + +RFC 3378 EtherIP September 2002 + + +7. Acknowledgements + + This document describes a protocol that was originally designed and + implemented by Xerox Special Information Systems in 1991 and 1992. + An earlier version of the protocol was provided as part of the Xerox + Ethernet Tunnel (XET). + +8. References + + [CSMA/CD] Institute of Electrical and Electronics Engineers: + "Carrier Sense Multiple Access with Collision Detection + (CSMA/CD) Access Method and Physical Layer Specifications", + ANSI/IEEE Std 802.3-1985, 1985. + + [DIX] Digital Equipment Corporation, Intel Corporation, and Xerox + Corporation: "The Ethernet -- A Local Area Network: Data + Link Layer and Physical Layer (Version 2.0)", November + 1982. + + [RFC791] Postel, J., "Internet Protocol", STD 5, RFC 791, September + 1981. + + [RFC2119] Bradner, S., "Key Words for Use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2338] Knight, S., Weaver, D., Whipple, D., Hinden, R., Mitzel, + D., Hunt, P., Higginson, P., Shand, M. and A. Lindem, + "Virtual Router Redundancy Protocol", RFC 2338, April 1998. + + [RFC2401] Kent, S. and R. Atkinson, "Security Architecture for the + Internet Protocol", RFC 2401, November 1998. + + [SDE] Institute of Electrical and Electronics Engineers: + "Interoperable LAN/MAN Security (SILS) Secure Data Exchange + (SDE) (Clause 2)", IEEE Std 802.10b-1992, 1992. + + [XNS] Xerox Corporation: "Internet Transport Protocols", XSIS + 028112, December 1981. + + [VLAN] Institute of Electrical and Electronics Engineers: "IEEE + Standard for Local and Metropolitan Area Networks: Virtual + Bridge Local Area Networks", ANSI/IEEE Std 802.1Q-1998, + 1998. + + + + + + + + +Housley & Hollenbeck Informational [Page 7] + +RFC 3378 EtherIP September 2002 + + +9. Authors' Addresses + + Russell Housley + RSA Laboratories + 918 Spring Knoll Drive + Herndon, VA 20170 + USA + + EMail: rhousley@rsasecurity.com + + + Scott Hollenbeck + VeriSign, Inc. + 21345 Ridgetop Circle + Dulles, VA 20166-6503 + USA + + EMail: shollenbeck@verisign.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Housley & Hollenbeck Informational [Page 8] + +RFC 3378 EtherIP September 2002 + + +10. Full Copyright Statement + + Copyright (C) The Internet Society (2002). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Housley & Hollenbeck Informational [Page 9] + diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..c9fe20f --- /dev/null +++ b/test.sh @@ -0,0 +1 @@ +sudo ./etherip6ctl add eip0 local 2402:2f60:500:beef:be24:11ff:fe12:d655 remote 2401:5e40:1:462:6709:e050:dcc5:d592 \ No newline at end of file