When testing OVS within Amazon's public EC2 cloud, I found my tests were limited by being unable to use GRE tunnels (the EC2 network transfers only TCP,UDP, and ICMP protocols). Although Capwap can be used, it is not a compete replacement without support for tunnel keys. This patch adds tunnel key support to the capwap vport, so it can be used in place of GRE in such networks.
Testing was done on Fedora 13, Ubuntu 10.10, and Amazon EC2 instances (Amazon 32bit Linux AMI). Testing is limited to connections with other OVS instances, which would not catch mis-reads of the Capwap spec. I still have some testing to do - such as testing fragment handling, but I would appreciate feedback on this approach or technical concerns. Example of an encapsulated packet, with a key of FF00 1100 2200 3300: ethertype IPv4 (0x0800), length 104: 10.0.2.15.58881 > 10.149.11.3.58882: UDP, length 62 0x0000: 4500 005a 0000 4000 4011 18ed 0a00 020f E..Z..@.@....... 0x0010: 0a95 0b03 e601 e602 0046 0000 0030 3c20 .........F...0<. 0x0020: 0000 0000 0b80 0000 ff00 1100 2200 3300 ............".3. 0x0030: ffff ffff ffff 0023 20c9 60f1 0806 0001 .......#..`..... 0x0040: 0800 0604 0001 0023 20c9 60f1 c0a8 0401 .......#..`..... 0x0050: 0000 0000 0000 c0a8 0402 Example of an encapsulated packet on a virtual port with no key specified (compatible with existing capwap implementation): ethertype IPv4 (0x0800), length 92: 10.0.2.15.58881 > 10.149.11.3.58882: UDP, length 50 0x0000: 4500 004e 0000 4000 4011 18f9 0a00 020f E..N..@.@....... 0x0010: 0a95 0b03 e601 e602 003a 0000 0010 0200 .........:...... 0x0020: 0000 0000 ffff ffff ffff 0023 20a8 18cc ...........#.... 0x0030: 0806 0001 0800 0604 0001 0023 20a8 18cc ...........#.... 0x0040: c0a8 0201 0000 0000 0000 c0a8 0202 .............. The primary difference is that bytes 0x1d-0x1f are modified to indicate a larger capwap header, WSI added, along with a modified WBID field (see CAPWAP.txt in patch below), and then a WSI field consisting of 0b80 0000 + [key]. Valient Gough (1): datapath: add key support to CAPWAP tunnel datapath/CAPWAP.txt | 80 +++++++++++++++++++++++++ datapath/vport-capwap.c | 151 ++++++++++++++++++++++++++++++++++++++++------ lib/netdev-vport.c | 13 +++- 3 files changed, 220 insertions(+), 24 deletions(-) create mode 100644 datapath/CAPWAP.txt >From 9c65ab088e7a43928097ab143edd5d8a8fd310c4 Mon Sep 17 00:00:00 2001 From: Valient Gough <vgo...@pobox.com> Date: Tue, 1 Mar 2011 21:24:47 -0800 Subject: [PATCH] datapath: add key support to CAPWAP tunnel Add tunnel key support to CAPWAP vport. Uses the optional WSI field in a CAPWAP header for storing a 64bit key. It can also be used without keys, in which case it is backward compatible with the old code. Documentation about the format of the WSI field is in CAPWAP.txt. Signed-off-by: Valient Gough <vgo...@pobox.com> --- datapath/CAPWAP.txt | 80 +++++++++++++++++++++++++ datapath/vport-capwap.c | 151 ++++++++++++++++++++++++++++++++++++++++------ lib/netdev-vport.c | 13 +++- 3 files changed, 220 insertions(+), 24 deletions(-) create mode 100644 datapath/CAPWAP.txt diff --git a/datapath/CAPWAP.txt b/datapath/CAPWAP.txt new file mode 100644 index 0000000..e59aa71 --- /dev/null +++ b/datapath/CAPWAP.txt @@ -0,0 +1,80 @@ + +References: +* http://www.rfc-editor.org/rfc/rfc5415.txt + + +The CAPWAP header layout is summarized as follows: + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |CAPWAP Preamble| HLEN | RID | WBID |T|F|L|W|M|K|Flags| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Fragment ID | Frag Offset |Rsvd | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | (optional) Radio MAC Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | (optional) Wireless Specific Information | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Payload .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +The spec defines an optional Wireless Specific Information field which can be +used to pass arbitrary data in the encapsulation layer: + + Wireless Specific Information: This optional field may be used to carry + per-packet information. This field is only present if the + 'W' bit is set. The WBID field in the CAPWAP Header is used to + identify the format of the WSI optional field. The HLEN field assumes + 4-byte alignment, and this field MUST be padded with zeroes (0x00) if it + is not 4-byte aligned. + + The Wireless-Specific Information field uses the following format: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | Data... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Length: The 8-bit field contains the length of the data field, + with a maximum size of 255. + + Data: Wireless-specific information, defined by the wireless- + specific binding specified in the CAPWAP Header's WBID field. + + + WBID: A 5-bit field that is the wireless binding identifier. The + identifier will indicate the type of wireless packet associated + with the radio. The following values are defined: + + 0 - Reserved + 1 - IEEE 802.11 + 2 - Reserved + 3 - EPCGlobal [EPCGlobal] + + When OpenVSwitch uses this field, it writes the value: + 30 - OpenVSwitch data + + +OpenVSwitch can make use of this field to pass additional packet routing +information. When needed, it sets the 'W' bit to indicates the WSI field is +added, and fills the field as follows: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | WSI_LEN |K| Flags | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | (optional) 64bit Tunnel Key | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + K - flag bit to identify presence of a 64bit tunnel key. + + +Adding WSI fields: Fields must be written and read in consitent order. New +fields may be added, but the existing fields always come first. + + + diff --git a/datapath/vport-capwap.c b/datapath/vport-capwap.c index 65f1f1b..640a279 100644 --- a/datapath/vport-capwap.c +++ b/datapath/vport-capwap.c @@ -42,21 +42,52 @@ * statically create them and we can do very fast parsing by checking all 12 * fields in one go. */ -#define CAPWAP_BEGIN_HLEN __cpu_to_be32(0x00100000) -#define CAPWAP_BEGIN_WBID __cpu_to_be32(0x00000200) #define CAPWAP_BEGIN_FRAG __cpu_to_be32(0x00000080) #define CAPWAP_BEGIN_LAST __cpu_to_be32(0x00000040) +#define CAPWAP_BEGIN_HLEN_2 __cpu_to_be32(0x00100000) +#define CAPWAP_BEGIN_HLEN_5 __cpu_to_be32(0x00280000) + +/* Old capwap code is hard-coded to look for a WBID value of 2. + * When we insert WSI field, use WBID value of 30, which has been + * proposed for all "experimental" usage - users with no reserved WBID value + * of their own. +*/ +#define CAPWAP_WBID_30 __cpu_to_be32(0x00003C00) +#define CAPWAP_WBID_2 __cpu_to_be32(0x00000200) +#define CAPWAP_WBID_MASK __cpu_to_be32(0x00003F00) -#define NO_FRAG_HDR (CAPWAP_BEGIN_HLEN | CAPWAP_BEGIN_WBID) +/* + * CAPWAP allows an optional 'Wireless Specific Information' field, which is + * length prefixed and can contain any data. If keys are configured for the + * vport, then the key will be placed in the WSI field and recovered by the + * receiver. + */ +#define CAPWAP_FLAG_WSI __cpu_to_be32(0x00000020) + +#define NO_FRAG_HDR 0 #define FRAG_HDR (NO_FRAG_HDR | CAPWAP_BEGIN_FRAG) #define FRAG_LAST_HDR (FRAG_HDR | CAPWAP_BEGIN_LAST) - +#define CAPWAP_FRAG_FLAGS (CAPWAP_BEGIN_FRAG | CAPWAP_BEGIN_LAST) + struct capwaphdr { __be32 begin; __be16 frag_id; __be16 frag_off; }; +/* + * We use the WSI field to hold additional tunnel data. + * The first eight bits store the size of the wsi data in bytes. + */ +struct capwaphdr_wsi { + unsigned char wsi_len; + unsigned char flags; + __be16 reserved_padding; +}; + +/* Flag indicating a 64bit key is stored in WSI data field */ +#define CAPWAP_WSI_FLAG_KEY64 0x80 + static inline struct capwaphdr *capwap_hdr(const struct sk_buff *skb) { return (struct capwaphdr *)(udp_hdr(skb) + 1); @@ -117,18 +148,17 @@ static struct socket *capwap_rcv_socket; static int capwap_hdr_len(const struct tnl_mutable_config *mutable) { + int size = CAPWAP_HLEN; + /* CAPWAP has no checksums. */ if (mutable->flags & TNL_F_CSUM) return -EINVAL; - /* CAPWAP has no keys, so check that the configuration for keys is the - * default if no key-specific attributes are used. - */ - if ((mutable->flags & (TNL_F_IN_KEY_MATCH | TNL_F_OUT_KEY_ACTION)) != - (TNL_F_IN_KEY_MATCH | TNL_F_OUT_KEY_ACTION)) - return -EINVAL; + /* if keys are specified, then add WSI field */ + if (mutable->out_key || (mutable->flags & TNL_F_OUT_KEY_ACTION)) + size += 12; /* wsi framing overhead + 8 byte key */ - return CAPWAP_HLEN; + return size; } static void capwap_build_header(const struct vport *vport, @@ -145,6 +175,35 @@ static void capwap_build_header(const struct vport *vport, cwh->begin = NO_FRAG_HDR; cwh->frag_id = 0; cwh->frag_off = 0; + + if (mutable->out_key || (mutable->flags & TNL_F_OUT_KEY_ACTION)) { + struct capwaphdr_wsi *wsi = (struct capwaphdr_wsi *)(cwh + 1); + __be32 *options = (__be32 *)(wsi + 1); + + cwh->begin |= CAPWAP_FLAG_WSI | CAPWAP_WBID_30; + + wsi->wsi_len = 3; /* initial wsi header */ + wsi->flags = 0; + wsi->reserved_padding = 0; + + if (mutable->out_key) { + __be64 *options64 = (__be64 *)options; + *options64 = mutable->out_key; + options += 2; + + wsi->wsi_len += 8; + wsi->flags |= CAPWAP_WSI_FLAG_KEY64; + } else { + /* space left intentionally blank, to be filled in + by capwap_update_header */ + } + + /* set hlen field, easy since we only support 1 option. */ + cwh->begin |= CAPWAP_BEGIN_HLEN_5; + } else { + /* make packet readable by old capwap code */ + cwh->begin |= CAPWAP_WBID_2 | CAPWAP_BEGIN_HLEN_2; + } } static struct sk_buff *capwap_update_header(const struct vport *vport, @@ -154,6 +213,14 @@ static struct sk_buff *capwap_update_header(const struct vport *vport, { struct udphdr *udph = udp_hdr(skb); + if (mutable->flags & TNL_F_OUT_KEY_ACTION) { + /* first field in WSI is key */ + struct capwaphdr *cwh = (struct capwaphdr *)(udph + 1); + struct capwaphdr_wsi *wsi = (struct capwaphdr_wsi *)(cwh + 1); + __be64 *options = (__be64 *)(wsi + 1); + *options = OVS_CB(skb)->tun_id; + } + udph->len = htons(skb->len - skb_transport_offset(skb)); if (unlikely(skb->len - skb_network_offset(skb) > dst_mtu(dst))) @@ -166,11 +233,11 @@ static inline struct sk_buff *process_capwap_proto(struct sk_buff *skb) { struct capwaphdr *cwh = capwap_hdr(skb); - if (likely(cwh->begin == NO_FRAG_HDR)) + if (likely((cwh->begin & CAPWAP_FRAG_FLAGS) == 0)) return skb; - else if (cwh->begin == FRAG_HDR) + else if (cwh->begin & CAPWAP_BEGIN_FRAG) return defrag(skb, false); - else if (cwh->begin == FRAG_LAST_HDR) + else if (cwh->begin & CAPWAP_BEGIN_LAST) return defrag(skb, true); else { if (net_ratelimit()) @@ -187,25 +254,69 @@ static int capwap_rcv(struct sock *sk, struct sk_buff *skb) struct vport *vport; const struct tnl_mutable_config *mutable; struct iphdr *iph; + struct capwaphdr *cwh; + int hdr_len = CAPWAP_HLEN; + __be64 key = 0; - if (unlikely(!pskb_may_pull(skb, CAPWAP_HLEN + ETH_HLEN))) + if (unlikely(!pskb_may_pull(skb, hdr_len + ETH_HLEN))) goto error; - __skb_pull(skb, CAPWAP_HLEN); - skb_postpull_rcsum(skb, skb_transport_header(skb), CAPWAP_HLEN + ETH_HLEN); - skb = process_capwap_proto(skb); if (unlikely(!skb)) goto out; iph = ip_hdr(skb); - vport = tnl_find_port(iph->daddr, iph->saddr, 0, - TNL_T_PROTO_CAPWAP | TNL_T_KEY_EXACT, &mutable); + cwh = capwap_hdr(skb); + if (cwh->begin & CAPWAP_FLAG_WSI) { + struct capwaphdr_wsi *wsi = (struct capwaphdr_wsi *)(cwh + 1); + __be32 *options; + int remaining_words; + + /* minimum wsi length - 4 bytes */ + if (unlikely(!pskb_may_pull(skb, hdr_len + ETH_HLEN + 4))) + goto error; + + /* len prefix + data must be multiple of 4 bytes */ + if (unlikely((wsi->wsi_len & 0x3) != 0x3)) + goto error; + + hdr_len += 1 + (unsigned int)wsi->wsi_len; + + if (unlikely(!pskb_may_pull(skb, hdr_len + ETH_HLEN))) + goto error; + + /* don't parse WBID types we don't know about */ + if (unlikely((cwh->begin & CAPWAP_WBID_MASK) != CAPWAP_WBID_30)) + goto skip_wsi; + + /* parse wsi field */ + remaining_words = (hdr_len >> 2) - 1; + options = (__be32 *)(wsi + 1); + + if ((wsi->flags & CAPWAP_WSI_FLAG_KEY64) && (remaining_words >= 2) ) { + __be64 *options64 = (__be64 *)options; + key = *options64; + options += 2; + remaining_words -= 2; + } + } + +skip_wsi: + vport = tnl_find_port(iph->daddr, iph->saddr, key, + TNL_T_PROTO_CAPWAP | TNL_T_KEY_EITHER, &mutable); if (unlikely(!vport)) { icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); goto error; } + if (mutable->flags & TNL_F_IN_KEY_MATCH) + OVS_CB(skb)->tun_id = key; + else + OVS_CB(skb)->tun_id = 0; + + __skb_pull(skb, hdr_len); + skb_postpull_rcsum(skb, skb_transport_header(skb), hdr_len + ETH_HLEN); + tnl_rcv(vport, skb); goto out; diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c index ae043c2..e63de90 100644 --- a/lib/netdev-vport.c +++ b/lib/netdev-vport.c @@ -624,6 +624,7 @@ parse_tunnel_config(const char *name, const char *type, { bool is_gre = false; bool is_ipsec = false; + bool supports_keys = false; struct shash_node *node; bool ipsec_mech_set = false; ovs_be32 daddr = htonl(0); @@ -632,11 +633,15 @@ parse_tunnel_config(const char *name, const char *type, flags = TNL_F_PMTUD | TNL_F_HDR_CACHE; if (!strcmp(type, "gre")) { is_gre = true; + supports_keys = true; } else if (!strcmp(type, "ipsec_gre")) { is_gre = true; is_ipsec = true; flags |= TNL_F_IPSEC; flags &= ~TNL_F_HDR_CACHE; + supports_keys = true; + } else if (!strcmp(type, "capwap")) { + supports_keys = true; } SHASH_FOR_EACH (node, args) { @@ -706,9 +711,9 @@ parse_tunnel_config(const char *name, const char *type, || !strcmp(node->name, "private_key") || !strcmp(node->name, "use_ssl_cert"))) { /* Ignore options not used by the netdev. */ - } else if (is_gre && (!strcmp(node->name, "key") && - !strcmp(node->name, "in_key") && - !strcmp(node->name, "out_key"))) { + } else if (supports_keys && (!strcmp(node->name, "key") || + !strcmp(node->name, "in_key") || + !strcmp(node->name, "out_key"))) { /* Handled separately below. */ } else { VLOG_WARN("%s: unknown %s argument '%s'", name, type, node->name); @@ -728,7 +733,7 @@ parse_tunnel_config(const char *name, const char *type, } } - if (is_gre) { + if (supports_keys) { set_key(args, "in_key", ODP_TUNNEL_ATTR_IN_KEY, options); set_key(args, "out_key", ODP_TUNNEL_ATTR_OUT_KEY, options); } -- 1.7.1 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev_openvswitch.org