From 370e373b958a92514a504c7b198d1c3fc21879e0 Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Fri, 4 Dec 2015 12:36:47 -0200 Subject: [PATCH] netdev-vport: Add IPv6 support for build/push/pop tunnel header This includes VXLAN, GRE and Geneve. Signed-off-by: Thadeu Lima de Souza Cascardo Signed-off-by: Ben Pfaff --- lib/netdev-vport.c | 230 +++++++++++++++++++++++------------ lib/odp-util.c | 107 +++++++++++----- lib/packets.c | 23 ++++ lib/packets.h | 4 + ofproto/ofproto-dpif-xlate.c | 4 +- ofproto/tunnel.c | 44 +++++-- ofproto/tunnel.h | 3 +- tests/odp.at | 6 + 8 files changed, 298 insertions(+), 123 deletions(-) diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c index 204a896eb..88f5022f4 100644 --- a/lib/netdev-vport.c +++ b/lib/netdev-vport.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "byte-order.h" @@ -57,14 +58,10 @@ static struct vlog_rate_limit err_rl = VLOG_RATE_LIMIT_INIT(60, 5); #define LISP_DST_PORT 4341 #define STT_DST_PORT 7471 -#define VXLAN_HLEN (sizeof(struct eth_header) + \ - sizeof(struct ip_header) + \ - sizeof(struct udp_header) + \ +#define VXLAN_HLEN (sizeof(struct udp_header) + \ sizeof(struct vxlanhdr)) -#define GENEVE_BASE_HLEN (sizeof(struct eth_header) + \ - sizeof(struct ip_header) + \ - sizeof(struct udp_header) + \ +#define GENEVE_BASE_HLEN (sizeof(struct udp_header) + \ sizeof(struct genevehdr)) #define DEFAULT_TTL 64 @@ -868,60 +865,92 @@ ip_hdr(void *eth) return (void *)((char *)eth + sizeof (struct eth_header)); } -static struct gre_base_hdr * -gre_hdr(struct ip_header *ip) +static struct ovs_16aligned_ip6_hdr * +ipv6_hdr(void *eth) { - return (void *)((char *)ip + sizeof (struct ip_header)); + return (void *)((char *)eth + sizeof (struct eth_header)); } static void * -ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl) +ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl, + unsigned int *hlen) { - struct ip_header *nh; + void *nh; + struct ip_header *ip; + struct ovs_16aligned_ip6_hdr *ip6; void *l4; int l3_size; nh = dp_packet_l3(packet); + ip = nh; + ip6 = nh; l4 = dp_packet_l4(packet); if (!nh || !l4) { return NULL; } - if (csum(nh, IP_IHL(nh->ip_ihl_ver) * 4)) { - VLOG_WARN_RL(&err_rl, "ip packet has invalid checksum"); - return NULL; - } - - if (IP_VER(nh->ip_ihl_ver) != 4) { - VLOG_WARN_RL(&err_rl, "ipv4 packet has invalid version (%d)", - IP_VER(nh->ip_ihl_ver)); - return NULL; - } + *hlen = sizeof(struct eth_header); l3_size = dp_packet_size(packet) - ((char *)nh - (char *)dp_packet_data(packet)); - if (ntohs(nh->ip_tot_len) > l3_size) { - VLOG_WARN_RL(&err_rl, "ip packet is truncated (IP length %d, actual %d)", - ntohs(nh->ip_tot_len), l3_size); - return NULL; - } + if (IP_VER(ip->ip_ihl_ver) == 4) { + + ovs_be32 ip_src, ip_dst; + + if (csum(ip, IP_IHL(ip->ip_ihl_ver) * 4)) { + VLOG_WARN_RL(&err_rl, "ip packet has invalid checksum"); + return NULL; + } + + if (ntohs(ip->ip_tot_len) > l3_size) { + VLOG_WARN_RL(&err_rl, "ip packet is truncated (IP length %d, actual %d)", + ntohs(ip->ip_tot_len), l3_size); + return NULL; + } + if (IP_IHL(ip->ip_ihl_ver) * 4 > sizeof(struct ip_header)) { + VLOG_WARN_RL(&err_rl, "ip options not supported on tunnel packets " + "(%d bytes)", IP_IHL(ip->ip_ihl_ver) * 4); + return NULL; + } + + ip_src = get_16aligned_be32(&ip->ip_src); + ip_dst = get_16aligned_be32(&ip->ip_dst); + + tnl->ip_src = ip_src; + tnl->ip_dst = ip_dst; + tnl->ip_tos = ip->ip_tos; + tnl->ip_ttl = ip->ip_ttl; + + *hlen += IP_HEADER_LEN; + + } else if (IP_VER(ip->ip_ihl_ver) == 6) { + + memcpy(tnl->ipv6_src.s6_addr, ip6->ip6_src.be16, sizeof ip6->ip6_src); + memcpy(tnl->ipv6_dst.s6_addr, ip6->ip6_dst.be16, sizeof ip6->ip6_dst); + tnl->ip_tos = 0; + tnl->ip_ttl = ip6->ip6_hlim; + + *hlen += IPV6_HEADER_LEN; - if (IP_IHL(nh->ip_ihl_ver) * 4 > sizeof(struct ip_header)) { - VLOG_WARN_RL(&err_rl, "ip options not supported on tunnel packets " - "(%d bytes)", IP_IHL(nh->ip_ihl_ver) * 4); + } else { + VLOG_WARN_RL(&err_rl, "ipv4 packet has invalid version (%d)", + IP_VER(ip->ip_ihl_ver)); return NULL; } - tnl->ip_src = get_16aligned_be32(&nh->ip_src); - tnl->ip_dst = get_16aligned_be32(&nh->ip_dst); - tnl->ip_tos = nh->ip_tos; - tnl->ip_ttl = nh->ip_ttl; - return l4; } +static bool +is_header_ipv6(const void *header) +{ + const struct eth_header *eth; + eth = header; + return eth->eth_type == htons(ETH_TYPE_IPV6); +} + /* Pushes the 'size' bytes of 'header' into the headroom of 'packet', * reallocating the packet if necessary. 'header' should contain an Ethernet * header, followed by an IPv4 header (without options), and an L4 header. @@ -937,32 +966,45 @@ push_ip_header(struct dp_packet *packet, { struct eth_header *eth; struct ip_header *ip; + struct ovs_16aligned_ip6_hdr *ip6; eth = dp_packet_push_uninit(packet, size); *ip_tot_size = dp_packet_size(packet) - sizeof (struct eth_header); memcpy(eth, header, size); - ip = ip_hdr(eth); - ip->ip_tot_len = htons(*ip_tot_size); - - - ip->ip_csum = recalc_csum16(ip->ip_csum, 0, ip->ip_tot_len); - return ip + 1; + if (is_header_ipv6(header)) { + ip6 = ipv6_hdr(eth); + *ip_tot_size -= IPV6_HEADER_LEN; + ip6->ip6_plen = htons(*ip_tot_size); + return ip6 + 1; + } else { + ip = ip_hdr(eth); + ip->ip_tot_len = htons(*ip_tot_size); + ip->ip_csum = recalc_csum16(ip->ip_csum, 0, ip->ip_tot_len); + *ip_tot_size -= IP_HEADER_LEN; + return ip + 1; + } } static void * -udp_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl) +udp_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl, + unsigned int *hlen) { struct udp_header *udp; - udp = ip_extract_tnl_md(packet, tnl); + udp = ip_extract_tnl_md(packet, tnl, hlen); if (!udp) { return NULL; } if (udp->udp_csum) { - uint32_t csum = packet_csum_pseudoheader(dp_packet_l3(packet)); + uint32_t csum; + if (is_header_ipv6(dp_packet_data(packet))) { + csum = packet_csum_pseudoheader6(dp_packet_l3(packet)); + } else { + csum = packet_csum_pseudoheader(dp_packet_l3(packet)); + } csum = csum_continue(csum, udp, dp_packet_size(packet) - ((const unsigned char *)udp - @@ -1001,13 +1043,17 @@ push_udp_header(struct dp_packet *packet, /* set udp src port */ udp->udp_src = get_src_port(packet); - udp->udp_len = htons(ip_tot_size - sizeof (struct ip_header)); + udp->udp_len = htons(ip_tot_size); if (udp->udp_csum) { - uint32_t csum = packet_csum_pseudoheader(ip_hdr(dp_packet_data(packet))); + uint32_t csum; + if (is_header_ipv6(dp_packet_data(packet))) { + csum = packet_csum_pseudoheader6(ipv6_hdr(dp_packet_data(packet))); + } else { + csum = packet_csum_pseudoheader(ip_hdr(dp_packet_data(packet))); + } - csum = csum_continue(csum, udp, - ip_tot_size - sizeof (struct ip_header)); + csum = csum_continue(csum, udp, ip_tot_size); udp->udp_csum = csum_finish(csum); if (!udp->udp_csum) { @@ -1019,18 +1065,33 @@ push_udp_header(struct dp_packet *packet, static void * udp_build_header(struct netdev_tunnel_config *tnl_cfg, const struct flow *tnl_flow, - struct ovs_action_push_tnl *data) + struct ovs_action_push_tnl *data, + unsigned int *hlen) { struct ip_header *ip; + struct ovs_16aligned_ip6_hdr *ip6; struct udp_header *udp; + bool is_ipv6; - ip = ip_hdr(data->header); - ip->ip_proto = IPPROTO_UDP; + *hlen = sizeof(struct eth_header); + + is_ipv6 = is_header_ipv6(data->header); + + if (is_ipv6) { + ip6 = ipv6_hdr(data->header); + ip6->ip6_nxt = IPPROTO_UDP; + udp = (struct udp_header *) (ip6 + 1); + *hlen += IPV6_HEADER_LEN; + } else { + ip = ip_hdr(data->header); + ip->ip_proto = IPPROTO_UDP; + udp = (struct udp_header *) (ip + 1); + *hlen += IP_HEADER_LEN; + } - udp = (struct udp_header *) (ip + 1); udp->udp_dst = tnl_cfg->dst_port; - if (tnl_flow->tunnel.flags & FLOW_TNL_F_CSUM) { + if (is_ipv6 || tnl_flow->tunnel.flags & FLOW_TNL_F_CSUM) { /* Write a value in now to mark that we should compute the checksum * later. 0xffff is handy because it is transparent to the * calculation. */ @@ -1043,8 +1104,7 @@ udp_build_header(struct netdev_tunnel_config *tnl_cfg, static int gre_header_len(ovs_be16 flags) { - int hlen = sizeof(struct eth_header) + - sizeof(struct ip_header) + 4; + int hlen = 4; if (flags & htons(GRE_CSUM)) { hlen += 4; @@ -1065,8 +1125,9 @@ parse_gre_header(struct dp_packet *packet, const struct gre_base_hdr *greh; ovs_16aligned_be32 *options; int hlen; + unsigned int ulen; - greh = ip_extract_tnl_md(packet, tnl); + greh = ip_extract_tnl_md(packet, tnl, &ulen); if (!greh) { return -EINVAL; } @@ -1079,7 +1140,7 @@ parse_gre_header(struct dp_packet *packet, return -EINVAL; } - hlen = gre_header_len(greh->flags); + hlen = ulen + gre_header_len(greh->flags); if (hlen > dp_packet_size(packet)) { return -EINVAL; } @@ -1125,8 +1186,10 @@ netdev_gre_pop_header(struct dp_packet *packet) { struct pkt_metadata *md = &packet->md; struct flow_tnl *tnl = &md->tunnel; - int hlen = sizeof(struct eth_header) + - sizeof(struct ip_header) + 4; + int hlen = sizeof(struct eth_header) + 4; + + hlen += is_header_ipv6(dp_packet_data(packet)) ? + IPV6_HEADER_LEN : IP_HEADER_LEN; pkt_metadata_init_tnl(md); if (hlen > dp_packet_size(packet)) { @@ -1154,7 +1217,7 @@ netdev_gre_push_header(struct dp_packet *packet, if (greh->flags & htons(GRE_CSUM)) { ovs_be16 *csum_opt = (ovs_be16 *) (greh + 1); - *csum_opt = csum(greh, ip_tot_size - sizeof (struct ip_header)); + *csum_opt = csum(greh, ip_tot_size); } } @@ -1166,18 +1229,28 @@ netdev_gre_build_header(const struct netdev *netdev, struct netdev_vport *dev = netdev_vport_cast(netdev); struct netdev_tunnel_config *tnl_cfg; struct ip_header *ip; + struct ovs_16aligned_ip6_hdr *ip6; struct gre_base_hdr *greh; ovs_16aligned_be32 *options; int hlen; + bool is_ipv6; + + is_ipv6 = is_header_ipv6(data->header); /* XXX: RCUfy tnl_cfg. */ ovs_mutex_lock(&dev->mutex); tnl_cfg = &dev->tnl_cfg; - ip = ip_hdr(data->header); - ip->ip_proto = IPPROTO_GRE; + if (is_ipv6) { + ip6 = ipv6_hdr(data->header); + ip6->ip6_nxt = IPPROTO_GRE; + greh = (struct gre_base_hdr *) (ip6 + 1); + } else { + ip = ip_hdr(data->header); + ip->ip_proto = IPPROTO_GRE; + greh = (struct gre_base_hdr *) (ip + 1); + } - greh = gre_hdr(ip); greh->protocol = htons(ETH_TYPE_TEB); greh->flags = 0; @@ -1199,8 +1272,8 @@ netdev_gre_build_header(const struct netdev *netdev, hlen = (uint8_t *) options - (uint8_t *) greh; - data->header_len = sizeof(struct eth_header) + - sizeof(struct ip_header) + hlen; + data->header_len = sizeof(struct eth_header) + hlen + + (is_ipv6 ? IPV6_HEADER_LEN : IP_HEADER_LEN); data->tnl_type = OVS_VPORT_TYPE_GRE; return 0; } @@ -1211,13 +1284,14 @@ netdev_vxlan_pop_header(struct dp_packet *packet) struct pkt_metadata *md = &packet->md; struct flow_tnl *tnl = &md->tunnel; struct vxlanhdr *vxh; + unsigned int hlen; pkt_metadata_init_tnl(md); - if (VXLAN_HLEN > dp_packet_size(packet)) { + if (VXLAN_HLEN > dp_packet_l4_size(packet)) { return EINVAL; } - vxh = udp_extract_tnl_md(packet, tnl); + vxh = udp_extract_tnl_md(packet, tnl, &hlen); if (!vxh) { return EINVAL; } @@ -1232,7 +1306,7 @@ netdev_vxlan_pop_header(struct dp_packet *packet) tnl->tun_id = htonll(ntohl(get_16aligned_be32(&vxh->vx_vni)) >> 8); tnl->flags |= FLOW_TNL_F_KEY; - dp_packet_reset_packet(packet, VXLAN_HLEN); + dp_packet_reset_packet(packet, hlen + VXLAN_HLEN); return 0; } @@ -1245,18 +1319,19 @@ netdev_vxlan_build_header(const struct netdev *netdev, struct netdev_vport *dev = netdev_vport_cast(netdev); struct netdev_tunnel_config *tnl_cfg; struct vxlanhdr *vxh; + unsigned int hlen; /* XXX: RCUfy tnl_cfg. */ ovs_mutex_lock(&dev->mutex); tnl_cfg = &dev->tnl_cfg; - vxh = udp_build_header(tnl_cfg, tnl_flow, data); + vxh = udp_build_header(tnl_cfg, tnl_flow, data, &hlen); put_16aligned_be32(&vxh->vx_flags, htonl(VXLAN_FLAGS)); put_16aligned_be32(&vxh->vx_vni, htonl(ntohll(tnl_flow->tunnel.tun_id) << 8)); ovs_mutex_unlock(&dev->mutex); - data->header_len = VXLAN_HLEN; + data->header_len = hlen + VXLAN_HLEN; data->tnl_type = OVS_VPORT_TYPE_VXLAN; return 0; } @@ -1267,22 +1342,22 @@ netdev_geneve_pop_header(struct dp_packet *packet) struct pkt_metadata *md = &packet->md; struct flow_tnl *tnl = &md->tunnel; struct genevehdr *gnh; - unsigned int hlen, opts_len; + unsigned int hlen, opts_len, ulen; pkt_metadata_init_tnl(md); - if (GENEVE_BASE_HLEN > dp_packet_size(packet)) { - VLOG_WARN_RL(&err_rl, "geneve packet too small: min header=%u packet size=%u\n", - (unsigned int)GENEVE_BASE_HLEN, dp_packet_size(packet)); + if (GENEVE_BASE_HLEN > dp_packet_l4_size(packet)) { + VLOG_WARN_RL(&err_rl, "geneve packet too small: min header=%u packet size=%"PRIuSIZE"\n", + (unsigned int)GENEVE_BASE_HLEN, dp_packet_l4_size(packet)); return EINVAL; } - gnh = udp_extract_tnl_md(packet, tnl); + gnh = udp_extract_tnl_md(packet, tnl, &ulen); if (!gnh) { return EINVAL; } opts_len = gnh->opt_len * 4; - hlen = GENEVE_BASE_HLEN + opts_len; + hlen = ulen + GENEVE_BASE_HLEN + opts_len; if (hlen > dp_packet_size(packet)) { VLOG_WARN_RL(&err_rl, "geneve packet too small: header len=%u packet size=%u\n", hlen, dp_packet_size(packet)); @@ -1323,12 +1398,13 @@ netdev_geneve_build_header(const struct netdev *netdev, struct genevehdr *gnh; int opt_len; bool crit_opt; + unsigned int hlen; /* XXX: RCUfy tnl_cfg. */ ovs_mutex_lock(&dev->mutex); tnl_cfg = &dev->tnl_cfg; - gnh = udp_build_header(tnl_cfg, tnl_flow, data); + gnh = udp_build_header(tnl_cfg, tnl_flow, data, &hlen); put_16aligned_be32(&gnh->vni, htonl(ntohll(tnl_flow->tunnel.tun_id) << 8)); @@ -1342,7 +1418,7 @@ netdev_geneve_build_header(const struct netdev *netdev, gnh->critical = crit_opt ? 1 : 0; gnh->proto_type = htons(ETH_TYPE_TEB); - data->header_len = GENEVE_BASE_HLEN + opt_len; + data->header_len = hlen + GENEVE_BASE_HLEN + opt_len; data->tnl_type = OVS_VPORT_TYPE_GENEVE; return 0; } diff --git a/lib/odp-util.c b/lib/odp-util.c index 32c813bf4..2937f3b03 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -435,11 +436,8 @@ format_odp_hash_action(struct ds *ds, const struct ovs_action_hash *hash_act) } static const void * -format_udp_tnl_push_header(struct ds *ds, const struct ip_header *ip) +format_udp_tnl_push_header(struct ds *ds, const struct udp_header *udp) { - const struct udp_header *udp; - - udp = (const struct udp_header *) (ip + 1); ds_put_format(ds, "udp(src=%"PRIu16",dst=%"PRIu16",csum=0x%"PRIx16"),", ntohs(udp->udp_src), ntohs(udp->udp_dst), ntohs(udp->udp_csum)); @@ -451,13 +449,13 @@ static void format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data) { const struct eth_header *eth; - const struct ip_header *ip; const void *l3; + const void *l4; + const struct udp_header *udp; eth = (const struct eth_header *)data->header; l3 = eth + 1; - ip = (const struct ip_header *)l3; /* Ethernet */ ds_put_format(ds, "header(size=%"PRIu8",type=%"PRIu8",eth(dst=", @@ -467,19 +465,38 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data) ds_put_format(ds, ETH_ADDR_FMT, ETH_ADDR_ARGS(eth->eth_src)); ds_put_format(ds, ",dl_type=0x%04"PRIx16"),", ntohs(eth->eth_type)); - /* IPv4 */ - ds_put_format(ds, "ipv4(src="IP_FMT",dst="IP_FMT",proto=%"PRIu8 - ",tos=%#"PRIx8",ttl=%"PRIu8",frag=0x%"PRIx16"),", - IP_ARGS(get_16aligned_be32(&ip->ip_src)), - IP_ARGS(get_16aligned_be32(&ip->ip_dst)), - ip->ip_proto, ip->ip_tos, - ip->ip_ttl, - ip->ip_frag_off); + if (eth->eth_type == htons(ETH_TYPE_IP)) { + /* IPv4 */ + const struct ip_header *ip; + ip = (const struct ip_header *) l3; + ds_put_format(ds, "ipv4(src="IP_FMT",dst="IP_FMT",proto=%"PRIu8 + ",tos=%#"PRIx8",ttl=%"PRIu8",frag=0x%"PRIx16"),", + IP_ARGS(get_16aligned_be32(&ip->ip_src)), + IP_ARGS(get_16aligned_be32(&ip->ip_dst)), + ip->ip_proto, ip->ip_tos, + ip->ip_ttl, + ip->ip_frag_off); + l4 = (ip + 1); + } else { + const struct ip6_hdr *ip6; + ip6 = (const struct ip6_hdr *) l3; + ds_put_format(ds, "ipv6(src="); + ipv6_format_addr(&ip6->ip6_src, ds); + ds_put_format(ds, ",dst="); + ipv6_format_addr(&ip6->ip6_dst, ds); + ds_put_format(ds, ",label=%i,proto=%"PRIu8",tclass=0x%"PRIx8 + ",hlimit=%"PRIu8"),", + ntohl(ip6->ip6_flow) & IPV6_LABEL_MASK, ip6->ip6_nxt, + (ntohl(ip6->ip6_flow) >> 20) & 0xff, ip6->ip6_hlim); + l4 = (ip6 + 1); + } + + udp = (const struct udp_header *) l4; if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) { const struct vxlanhdr *vxh; - vxh = format_udp_tnl_push_header(ds, ip); + vxh = format_udp_tnl_push_header(ds, udp); ds_put_format(ds, "vxlan(flags=0x%"PRIx32",vni=0x%"PRIx32")", ntohl(get_16aligned_be32(&vxh->vx_flags)), @@ -487,7 +504,7 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data) } else if (data->tnl_type == OVS_VPORT_TYPE_GENEVE) { const struct genevehdr *gnh; - gnh = format_udp_tnl_push_header(ds, ip); + gnh = format_udp_tnl_push_header(ds, udp); ds_put_format(ds, "geneve(%s%svni=0x%"PRIx32, gnh->oam ? "oam," : "", @@ -505,9 +522,7 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data) } else if (data->tnl_type == OVS_VPORT_TYPE_GRE) { const struct gre_base_hdr *greh; ovs_16aligned_be32 *options; - void *l4; - l4 = ((uint8_t *)l3 + sizeof(struct ip_header)); greh = (const struct gre_base_hdr *) l4; ds_put_format(ds, "gre((flags=0x%"PRIx16",proto=0x%"PRIx16")", @@ -1010,11 +1025,12 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data) { struct eth_header *eth; struct ip_header *ip; + struct ovs_16aligned_ip6_hdr *ip6; struct udp_header *udp; struct gre_base_hdr *greh; uint16_t gre_proto, gre_flags, dl_type, udp_src, udp_dst, csum; ovs_be32 sip, dip; - uint32_t tnl_type = 0, header_len = 0; + uint32_t tnl_type = 0, header_len = 0, ip_len = 0; void *l3, *l4; int n = 0; @@ -1023,8 +1039,8 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data) } eth = (struct eth_header *) data->header; l3 = (data->header + sizeof *eth); - l4 = ((uint8_t *) l3 + sizeof (struct ip_header)); ip = (struct ip_header *) l3; + ip6 = (struct ovs_16aligned_ip6_hdr *) l3; if (!ovs_scan_len(s, &n, "header(size=%"SCNi32",type=%"SCNi32"," "eth(dst="ETH_ADDR_SCAN_FMT",", &data->header_len, @@ -1042,19 +1058,44 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data) } eth->eth_type = htons(dl_type); - /* IPv4 */ - if (!ovs_scan_len(s, &n, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT",proto=%"SCNi8 - ",tos=%"SCNi8",ttl=%"SCNi8",frag=0x%"SCNx16"),", - IP_SCAN_ARGS(&sip), - IP_SCAN_ARGS(&dip), - &ip->ip_proto, &ip->ip_tos, - &ip->ip_ttl, &ip->ip_frag_off)) { - return -EINVAL; + if (eth->eth_type == htons(ETH_TYPE_IP)) { + /* IPv4 */ + if (!ovs_scan_len(s, &n, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT",proto=%"SCNi8 + ",tos=%"SCNi8",ttl=%"SCNi8",frag=0x%"SCNx16"),", + IP_SCAN_ARGS(&sip), + IP_SCAN_ARGS(&dip), + &ip->ip_proto, &ip->ip_tos, + &ip->ip_ttl, &ip->ip_frag_off)) { + return -EINVAL; + } + put_16aligned_be32(&ip->ip_src, sip); + put_16aligned_be32(&ip->ip_dst, dip); + ip_len = sizeof *ip; + } else { + char sip6_s[IPV6_SCAN_LEN + 1]; + char dip6_s[IPV6_SCAN_LEN + 1]; + struct in6_addr sip6, dip6; + uint8_t tclass; + uint32_t label; + if (!ovs_scan_len(s, &n, "ipv6(src="IPV6_SCAN_FMT",dst="IPV6_SCAN_FMT + ",label=%i,proto=%"SCNi8",tclass=0x%"SCNx8 + ",hlimit=%"SCNi8"),", + sip6_s, dip6_s, &label, &ip6->ip6_nxt, + &tclass, &ip6->ip6_hlim) + || (label & ~IPV6_LABEL_MASK) != 0 + || inet_pton(AF_INET6, sip6_s, &sip6) != 1 + || inet_pton(AF_INET6, dip6_s, &dip6) != 1) { + return -EINVAL; + } + put_16aligned_be32(&ip6->ip6_flow, htonl(6 << 28) | + htonl(tclass << 20) | htonl(label)); + memcpy(&ip6->ip6_src, &sip6, sizeof(ip6->ip6_src)); + memcpy(&ip6->ip6_dst, &dip6, sizeof(ip6->ip6_dst)); + ip_len = sizeof *ip6; } - put_16aligned_be32(&ip->ip_src, sip); - put_16aligned_be32(&ip->ip_dst, dip); /* Tunnel header */ + l4 = ((uint8_t *) l3 + ip_len); udp = (struct udp_header *) l4; greh = (struct gre_base_hdr *) l4; if (ovs_scan_len(s, &n, "udp(src=%"SCNi16",dst=%"SCNi16",csum=0x%"SCNx16"),", @@ -1073,13 +1114,13 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data) put_16aligned_be32(&vxh->vx_flags, htonl(vx_flags)); put_16aligned_be32(&vxh->vx_vni, htonl(vni << 8)); tnl_type = OVS_VPORT_TYPE_VXLAN; - header_len = sizeof *eth + sizeof *ip + + header_len = sizeof *eth + ip_len + sizeof *udp + sizeof *vxh; } else if (ovs_scan_len(s, &n, "geneve(")) { struct genevehdr *gnh = (struct genevehdr *) (udp + 1); memset(gnh, 0, sizeof *gnh); - header_len = sizeof *eth + sizeof *ip + + header_len = sizeof *eth + ip_len + sizeof *udp + sizeof *gnh; if (ovs_scan_len(s, &n, "oam,")) { @@ -1158,7 +1199,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data) return -EINVAL; } - header_len = sizeof *eth + sizeof *ip + + header_len = sizeof *eth + ip_len + ((uint8_t *) options - (uint8_t *) greh); } else { return -EINVAL; diff --git a/lib/packets.c b/lib/packets.c index 5ad16b502..c89fbd205 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -1181,3 +1181,26 @@ packet_csum_pseudoheader(const struct ip_header *ip) return partial; } +#ifndef __CHECKER__ +uint32_t +packet_csum_pseudoheader6(const struct ovs_16aligned_ip6_hdr *ip6) +{ + uint32_t partial = 0; + + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_src.be32[0]))); + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_src.be32[1]))); + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_src.be32[2]))); + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_src.be32[3]))); + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_dst.be32[0]))); + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_dst.be32[1]))); + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_dst.be32[2]))); + partial = csum_add32(partial, get_16aligned_be32(&(ip6->ip6_dst.be32[3]))); + + partial = csum_add16(partial, 0); + partial = csum_add16(partial, ip6->ip6_plen); + partial = csum_add16(partial, 0); + partial = csum_add16(partial, ip6->ip6_nxt); + + return partial; +} +#endif diff --git a/lib/packets.h b/lib/packets.h index acd317d8e..892d2eb41 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -771,6 +771,8 @@ struct arp_eth_header { }; BUILD_ASSERT_DECL(ARP_ETH_HEADER_LEN == sizeof(struct arp_eth_header)); +#define IPV6_HEADER_LEN 40 + /* Like struct in6_addr, but whereas that struct requires 32-bit alignment on * most implementations, this one only requires 16-bit alignment. */ union ovs_16aligned_in6_addr { @@ -811,6 +813,8 @@ struct icmp6_header { }; BUILD_ASSERT_DECL(ICMP6_HEADER_LEN == sizeof(struct icmp6_header)); +uint32_t packet_csum_pseudoheader6(const struct ovs_16aligned_ip6_hdr *); + /* Neighbor Discovery option field. * ND options are always a multiple of 8 bytes in size. */ #define ND_OPT_LEN 8 diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index beff14fe5..2d0a1086a 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -2774,6 +2774,7 @@ build_tunnel_send(struct xlate_ctx *ctx, const struct xport *xport, struct ovs_action_push_tnl tnl_push_data; struct xport *out_dev = NULL; ovs_be32 s_ip, d_ip = 0; + struct in6_addr s_ip6; struct eth_addr smac; struct eth_addr dmac; int err; @@ -2820,8 +2821,9 @@ build_tunnel_send(struct xlate_ctx *ctx, const struct xport *xport, " to "ETH_ADDR_FMT" "IP_FMT, ETH_ADDR_ARGS(smac), IP_ARGS(s_ip), ETH_ADDR_ARGS(dmac), IP_ARGS(d_ip)); + in6_addr_set_mapped_ipv4(&s_ip6, s_ip); err = tnl_port_build_header(xport->ofport, flow, - dmac, smac, s_ip, &tnl_push_data); + dmac, smac, &s_ip6, &tnl_push_data); if (err) { return err; } diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c index b655d2db4..5cf6c7540 100644 --- a/ofproto/tunnel.c +++ b/ofproto/tunnel.c @@ -685,40 +685,62 @@ tnl_port_build_header(const struct ofport_dpif *ofport, const struct flow *tnl_flow, const struct eth_addr dmac, const struct eth_addr smac, - ovs_be32 ip_src, struct ovs_action_push_tnl *data) + const struct in6_addr * ipv6_src, + struct ovs_action_push_tnl *data) { struct tnl_port *tnl_port; struct eth_header *eth; struct ip_header *ip; + struct ovs_16aligned_ip6_hdr *ip6; void *l3; int res; + ovs_be32 ip_src; fat_rwlock_rdlock(&rwlock); tnl_port = tnl_find_ofport(ofport); ovs_assert(tnl_port); + ip_src = in6_addr_get_mapped_ipv4(ipv6_src); + /* Build Ethernet and IP headers. */ memset(data->header, 0, sizeof data->header); eth = (struct eth_header *)data->header; eth->eth_dst = dmac; eth->eth_src = smac; - eth->eth_type = htons(ETH_TYPE_IP); + eth->eth_type = ip_src ? htons(ETH_TYPE_IP) : htons(ETH_TYPE_IPV6); l3 = (eth + 1); - ip = (struct ip_header *) l3; - ip->ip_ihl_ver = IP_IHL_VER(5, 4); - ip->ip_tos = tnl_flow->tunnel.ip_tos; - ip->ip_ttl = tnl_flow->tunnel.ip_ttl; - ip->ip_frag_off = (tnl_flow->tunnel.flags & FLOW_TNL_F_DONT_FRAGMENT) ? - htons(IP_DF) : 0; + if (ip_src) { + ip = (struct ip_header *) l3; + + ip->ip_ihl_ver = IP_IHL_VER(5, 4); + ip->ip_tos = tnl_flow->tunnel.ip_tos; + ip->ip_ttl = tnl_flow->tunnel.ip_ttl; + ip->ip_frag_off = (tnl_flow->tunnel.flags & FLOW_TNL_F_DONT_FRAGMENT) ? + htons(IP_DF) : 0; + + put_16aligned_be32(&ip->ip_src, ip_src); + put_16aligned_be32(&ip->ip_dst, tnl_flow->tunnel.ip_dst); + } else { + ip6 = (struct ovs_16aligned_ip6_hdr *) l3; + + ip6->ip6_vfc = 0x60; + ip6->ip6_hlim = tnl_flow->tunnel.ip_ttl; - put_16aligned_be32(&ip->ip_src, ip_src); - put_16aligned_be32(&ip->ip_dst, tnl_flow->tunnel.ip_dst); + /* next header, plen - at netdev_build_header? */ + + memcpy(&ip6->ip6_src, ipv6_src, sizeof(ovs_be32[4])); + memcpy(&ip6->ip6_dst, &tnl_flow->tunnel.ipv6_dst, sizeof(ovs_be32[4])); + } res = netdev_build_header(tnl_port->netdev, data, tnl_flow); - ip->ip_csum = csum(ip, sizeof *ip); + + if (ip_src) { + ip->ip_csum = csum(ip, sizeof *ip); + } + fat_rwlock_unlock(&rwlock); return res; diff --git a/ofproto/tunnel.h b/ofproto/tunnel.h index b2fc57ce2..1f3dfc23d 100644 --- a/ofproto/tunnel.h +++ b/ofproto/tunnel.h @@ -54,6 +54,7 @@ int tnl_port_build_header(const struct ofport_dpif *ofport, const struct flow *tnl_flow, const struct eth_addr dmac, const struct eth_addr smac, - ovs_be32 ip_src, struct ovs_action_push_tnl *data); + const struct in6_addr *ipv6_src, + struct ovs_action_push_tnl *data); #endif /* tunnel.h */ diff --git a/tests/odp.at b/tests/odp.at index 3d699e12b..bc30eba7d 100644 --- a/tests/odp.at +++ b/tests/odp.at @@ -306,6 +306,12 @@ tnl_push(tnl_port(6),header(size=50,type=4,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:1 tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(oam,vni=0x1c7)),out_port(1)) tnl_push(tnl_port(6),header(size=58,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x1c7,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(1)) tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x1c7)),out_port(1)) +tnl_push(tnl_port(4),header(size=62,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1e241)),out_port(1)) +tnl_push(tnl_port(4),header(size=66,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0xa000,proto=0x6558),csum=0x0,key=0x1e241)),out_port(1)) +tnl_push(tnl_port(6),header(size=70,type=4,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0x8000000,vni=0x1c7)),out_port(1)) +tnl_push(tnl_port(6),header(size=70,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0x0),geneve(oam,vni=0x1c7)),out_port(1)) +tnl_push(tnl_port(6),header(size=78,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x1c7,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(1)) +tnl_push(tnl_port(6),header(size=70,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x1c7)),out_port(1)) ct ct(commit) ct(commit,zone=5) -- 2.20.1