Initail commit
This commit is contained in:
25
Makefile
Normal file
25
Makefile
Normal file
@@ -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
|
||||||
69
README.md
Normal file
69
README.md
Normal file
@@ -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.
|
||||||
13
build.sh
Executable file
13
build.sh
Executable file
@@ -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"
|
||||||
520
etherip6.c
Normal file
520
etherip6.c
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
#include <linux/etherdevice.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/if_link.h>
|
||||||
|
#include <linux/in6.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/ipv6.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/netdevice.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/percpu.h>
|
||||||
|
#include <linux/rculist.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <linux/skbuff.h>
|
||||||
|
#include <net/ip6_route.h>
|
||||||
|
#include <net/ipv6.h>
|
||||||
|
#include <net/net_namespace.h>
|
||||||
|
#include <net/netns/generic.h>
|
||||||
|
#include <net/protocol.h>
|
||||||
|
#include <net/rtnetlink.h>
|
||||||
|
|
||||||
|
#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");
|
||||||
16
etherip6_uapi.h
Normal file
16
etherip6_uapi.h
Normal file
@@ -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
|
||||||
240
etherip6ctl.c
Normal file
240
etherip6ctl.c
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <linux/if_link.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
507
rfc3378.txt
Normal file
507
rfc3378.txt
Normal file
@@ -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]
|
||||||
|
|
||||||
Reference in New Issue
Block a user