[ovs-dev] [PATCH] datapath: add ipv6 'set' action

Ansis Atteka aatteka at nicira.com
Wed Nov 7 20:45:23 UTC 2012


On Tue, Oct 30, 2012 at 1:11 AM, Jesse Gross <jesse at nicira.com> wrote:
> On Sun, Oct 28, 2012 at 2:21 PM, Ansis Atteka <aatteka at nicira.com> wrote:
>> This patch adds ipv6 set action functionality. It allows to change
>> traffic class, flow label, hop-limit, ipv6 source and destination
>> address fields.
>>
>> Signed-off-by: Ansis Atteka <aatteka at nicira.com>
>
> Can you add an item to NEWS?
>
> I got a few sparse errors when I applied this:
>
>   CHECK   /home/jgross/openvswitch/datapath/linux/actions.c
> /home/jgross/openvswitch/datapath/linux/actions.c:270:12: warning:
> incorrect type in assignment (different base types)
> /home/jgross/openvswitch/datapath/linux/actions.c:270:12:    expected
> restricted __be32 [usertype] fl
> /home/jgross/openvswitch/datapath/linux/actions.c:270:12:    got int
> /home/jgross/openvswitch/datapath/linux/actions.c:273:41: warning:
> incorrect type in argument 2 (different base types)
> /home/jgross/openvswitch/datapath/linux/actions.c:273:41:    expected
> unsigned int [unsigned] [usertype] fl
> /home/jgross/openvswitch/datapath/linux/actions.c:273:41:    got
> restricted __be32 const [usertype] ipv6_label
>
> These look like real problems to me on certain architectures because
> the constants that you are using for bit manipulations will have
> different results depending on endianness.
>
> I think we also need to do some validation of these actions in
> datapath.c similar to what we do currently for IPv4 set.  I think
> right now these actions just get rejected immediately because they
> aren't expected by the validation code.
>
> We could implement the equivalent actions in the userspace datapath as
> well.  There aren't any complex dependencies here so it would be ideal
> to maintain parity.

I am thinking, where in the user-space datapath I should put that code
that parses routing headers. Two options come into my mind:

1. In the parse_ipv6() function, when packets are parsed for the the
first time. This would mean that I would need store this routing
header flag somewhere in struct *flow. And then pass *flow all the way
to my packet_set_ipv6() function. Would it be appropriate to store it
there?
2. Visit routing headers one more time from my packet_set_ipv6()
function. The ugly side of putting this code here, would mean that
ipv6 headers will need to be parsed twice and there will be another
function very similar to parse_ipv6(). Except this one will have to
use ugly pointer arithmetic, because ofpbuf *packet buffer was already
consumed.

I think solution #1 would be better? What do you think Jesse?
>
>> diff --git a/datapath/actions.c b/datapath/actions.c
>> index ec9b595..7e60b0f 100644
>> --- a/datapath/actions.c
>> +++ b/datapath/actions.c
>> +static void set_ipv6_addr(struct sk_buff *skb, struct ipv6hdr *nh,
>> +                         __be32 addr[4], const __be32 new_addr[4])
>> +{
>> +       int transport_len = skb->len - skb_transport_offset(skb);
>> +
>> +       if (nh->nexthdr == IPPROTO_TCP) {
>> +               if (likely(transport_len >= sizeof(struct tcphdr)))
>> +                       inet_proto_csum_replace16(&tcp_hdr(skb)->check, skb,
>> +                                                 addr, new_addr, 1);
>> +       } else if (nh->nexthdr == IPPROTO_UDP) {
>> +               if (likely(transport_len >= sizeof(struct udphdr))) {
>> +                       struct udphdr *uh = udp_hdr(skb);
>> +
>> +                       if (uh->check ||
>> +                           get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
>> +                               inet_proto_csum_replace16(&uh->check, skb,
>> +                                                         addr, new_addr, 1);
>> +                               if (!uh->check)
>> +                                       uh->check = CSUM_MANGLED_0;
>> +                       }
>> +               }
>> +       }
>
> IPv6 has an extra little twist when it comes to checksums compared to
> IPv4: routing headers.  In an IPv6 packet, the destination IP address
> used for the purposes of checksum calculation in the pseudo header is
> the final destination address.  This means that if you have a routing
> header then it is that address that matters and not one in the IP
> header.  This is likely to be a pretty rare case but we should handle
> it.
>
> Also, due to the presence of extension headers in general, nh->nexthdr
> might not point to the L4 protocol.  We should use the extracted L4
> protocol in the flow instead.
>
>> +static void set_ipv6_tc(struct ipv6hdr *nh, __u8 tc)
>
> In general, I would prefer the versions of the host byte order types
> without underscores (i.e. u8, u32, etc.) for components entirely
> within the kernel.
>
>> +{
>> +       nh->priority = (nh->priority & 0xf0) | (tc >> 4);
>
> nh->priority is only 4 bits, so you shouldn't have to do the mask.
>
>> +       nh->flow_lbl[0] = (nh->flow_lbl[0] & 0x0f) | ((tc & 0x0f) << 4);
>> +}
>> +
>> +static void set_ipv6_fl(struct ipv6hdr *nh, __u32 fl)
>> +{
>> +       nh->flow_lbl[0] = (nh->flow_lbl[0] & 0xf0) | ((fl & 0x0f0000) >> 16);
>> +       nh->flow_lbl[1] = (fl & 0x00ff00) >> 8;
>> +       nh->flow_lbl[2] = fl & 0x0000ff;
>
> I would make all of these hex constants the full 4 bytes.  Whenever I
> see only 3 being used with a 4 byte value I think that I've
> miscounted.
>
>> +static int set_ipv6(struct sk_buff *skb, const struct ovs_key_ipv6 *ipv6_key)
>> +{
>> +       struct ipv6hdr *nh;
>> +       int err;
>> +       __u8 tc;
>> +       __be32 fl;
>
> Based on your usage below, I think that fl is supposed to be in host byte order.
>
>> +       __be32 *saddr;
>> +       __be32 *daddr;
>> +
>> +       err = make_writable(skb, skb_network_offset(skb) +
>> +                                sizeof(struct ipv6hdr));
>> +       if (unlikely(err))
>> +               return err;
>> +
>> +       nh = ipv6_hdr(skb);
>> +       saddr = (__be32 *)&nh->saddr;
>> +       daddr = (__be32 *)&nh->daddr;
>> +
>> +       if (memcmp(ipv6_key->ipv6_src, saddr, sizeof(ipv6_key->ipv6_src)))
>> +               set_ipv6_addr(skb, nh, saddr, ipv6_key->ipv6_src);
>> +
>> +       if (memcmp(ipv6_key->ipv6_dst, daddr, sizeof(ipv6_key->ipv6_dst)))
>> +               set_ipv6_addr(skb, nh, daddr, ipv6_key->ipv6_dst);
>> +
>> +       tc = (nh->priority << 4) | (nh->flow_lbl[0] >> 4);
>> +       if (ipv6_key->ipv6_tclass != tc)
>> +               set_ipv6_tc(nh, ipv6_key->ipv6_tclass);
>> +
>> +       fl = (nh->flow_lbl[0] & 0x0f) << 16 | nh->flow_lbl[1] << 8 |
>> +             nh->flow_lbl[2];
>> +       if (ipv6_key->ipv6_label != fl)
>> +               set_ipv6_fl(nh, ipv6_key->ipv6_label);
>
> There's a mismatch here between the byte order fl and ipv6_key->ipv6_label.
>
>> +       if (ipv6_key->ipv6_hlimit != nh->hop_limit)
>> +               nh->hop_limit = ipv6_key->ipv6_hlimit;
>
> I think you can drop the checks for traffic class, flow label, and hop
> limit and just do the assignments directly.  The cost to do the check
> is basically the same as the actual operation so the check doesn't
> really save us anything even in the common case.
>
>> diff --git a/datapath/compat.h b/datapath/compat.h
>> index 3113b96..fb660d2 100644
>> --- a/datapath/compat.h
>> +++ b/datapath/compat.h
>
> We usually put functions that are backported directly from Linux in
> the corresponding header file in the compat directory.  Normally that
> would be net/checksum.h in this case but this function depends on the
> OVS checksum constants, so it should go in datapath/checksum.h.  It
> should also use the OVS constants like the example of
> inet_proto_csum_replace4() in the same file.
>
>> +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0)
>> +static inline void inet_proto_csum_replace16(__sum16 *sum,
>
> I think this function is coming in 3.7, not 3.6.



More information about the dev mailing list