diff options
| author | Ansis Atteka <aatteka@nicira.com> | 2012-11-13 18:44:14 -0500 |
|---|---|---|
| committer | Jesse Gross <jesse@nicira.com> | 2012-11-13 18:57:33 -0500 |
| commit | 3fdbd1ce11e5c0d7cafbe44c942c5cad61113d7b (patch) | |
| tree | fb3c6207ea4a38c35ecc1017dce97254b36da5f5 /net/openvswitch | |
| parent | 9195bb8e381d81d5a315f911904cdf0cfcc919b8 (diff) | |
openvswitch: add ipv6 'set' action
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@nicira.com>
Signed-off-by: Jesse Gross <jesse@nicira.com>
Diffstat (limited to 'net/openvswitch')
| -rw-r--r-- | net/openvswitch/actions.c | 93 | ||||
| -rw-r--r-- | net/openvswitch/datapath.c | 20 |
2 files changed, 113 insertions, 0 deletions
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index 08114478cb85..a58ed276f70d 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c | |||
| @@ -28,6 +28,7 @@ | |||
| 28 | #include <linux/if_arp.h> | 28 | #include <linux/if_arp.h> |
| 29 | #include <linux/if_vlan.h> | 29 | #include <linux/if_vlan.h> |
| 30 | #include <net/ip.h> | 30 | #include <net/ip.h> |
| 31 | #include <net/ipv6.h> | ||
| 31 | #include <net/checksum.h> | 32 | #include <net/checksum.h> |
| 32 | #include <net/dsfield.h> | 33 | #include <net/dsfield.h> |
| 33 | 34 | ||
| @@ -162,6 +163,53 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh, | |||
| 162 | *addr = new_addr; | 163 | *addr = new_addr; |
| 163 | } | 164 | } |
| 164 | 165 | ||
| 166 | static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto, | ||
| 167 | __be32 addr[4], const __be32 new_addr[4]) | ||
| 168 | { | ||
| 169 | int transport_len = skb->len - skb_transport_offset(skb); | ||
| 170 | |||
| 171 | if (l4_proto == IPPROTO_TCP) { | ||
| 172 | if (likely(transport_len >= sizeof(struct tcphdr))) | ||
| 173 | inet_proto_csum_replace16(&tcp_hdr(skb)->check, skb, | ||
| 174 | addr, new_addr, 1); | ||
| 175 | } else if (l4_proto == IPPROTO_UDP) { | ||
| 176 | if (likely(transport_len >= sizeof(struct udphdr))) { | ||
| 177 | struct udphdr *uh = udp_hdr(skb); | ||
| 178 | |||
| 179 | if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) { | ||
| 180 | inet_proto_csum_replace16(&uh->check, skb, | ||
| 181 | addr, new_addr, 1); | ||
| 182 | if (!uh->check) | ||
| 183 | uh->check = CSUM_MANGLED_0; | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | static void set_ipv6_addr(struct sk_buff *skb, u8 l4_proto, | ||
| 190 | __be32 addr[4], const __be32 new_addr[4], | ||
| 191 | bool recalculate_csum) | ||
| 192 | { | ||
| 193 | if (recalculate_csum) | ||
| 194 | update_ipv6_checksum(skb, l4_proto, addr, new_addr); | ||
| 195 | |||
| 196 | skb->rxhash = 0; | ||
| 197 | memcpy(addr, new_addr, sizeof(__be32[4])); | ||
| 198 | } | ||
| 199 | |||
| 200 | static void set_ipv6_tc(struct ipv6hdr *nh, u8 tc) | ||
| 201 | { | ||
| 202 | nh->priority = tc >> 4; | ||
| 203 | nh->flow_lbl[0] = (nh->flow_lbl[0] & 0x0F) | ((tc & 0x0F) << 4); | ||
| 204 | } | ||
| 205 | |||
| 206 | static void set_ipv6_fl(struct ipv6hdr *nh, u32 fl) | ||
| 207 | { | ||
| 208 | nh->flow_lbl[0] = (nh->flow_lbl[0] & 0xF0) | (fl & 0x000F0000) >> 16; | ||
| 209 | nh->flow_lbl[1] = (fl & 0x0000FF00) >> 8; | ||
| 210 | nh->flow_lbl[2] = fl & 0x000000FF; | ||
| 211 | } | ||
| 212 | |||
| 165 | static void set_ip_ttl(struct sk_buff *skb, struct iphdr *nh, u8 new_ttl) | 213 | static void set_ip_ttl(struct sk_buff *skb, struct iphdr *nh, u8 new_ttl) |
| 166 | { | 214 | { |
| 167 | csum_replace2(&nh->check, htons(nh->ttl << 8), htons(new_ttl << 8)); | 215 | csum_replace2(&nh->check, htons(nh->ttl << 8), htons(new_ttl << 8)); |
| @@ -195,6 +243,47 @@ static int set_ipv4(struct sk_buff *skb, const struct ovs_key_ipv4 *ipv4_key) | |||
| 195 | return 0; | 243 | return 0; |
| 196 | } | 244 | } |
| 197 | 245 | ||
| 246 | static int set_ipv6(struct sk_buff *skb, const struct ovs_key_ipv6 *ipv6_key) | ||
| 247 | { | ||
| 248 | struct ipv6hdr *nh; | ||
| 249 | int err; | ||
| 250 | __be32 *saddr; | ||
| 251 | __be32 *daddr; | ||
| 252 | |||
| 253 | err = make_writable(skb, skb_network_offset(skb) + | ||
| 254 | sizeof(struct ipv6hdr)); | ||
| 255 | if (unlikely(err)) | ||
| 256 | return err; | ||
| 257 | |||
| 258 | nh = ipv6_hdr(skb); | ||
| 259 | saddr = (__be32 *)&nh->saddr; | ||
| 260 | daddr = (__be32 *)&nh->daddr; | ||
| 261 | |||
| 262 | if (memcmp(ipv6_key->ipv6_src, saddr, sizeof(ipv6_key->ipv6_src))) | ||
| 263 | set_ipv6_addr(skb, ipv6_key->ipv6_proto, saddr, | ||
| 264 | ipv6_key->ipv6_src, true); | ||
| 265 | |||
| 266 | if (memcmp(ipv6_key->ipv6_dst, daddr, sizeof(ipv6_key->ipv6_dst))) { | ||
| 267 | unsigned int offset = 0; | ||
| 268 | int flags = IP6_FH_F_SKIP_RH; | ||
| 269 | bool recalc_csum = true; | ||
| 270 | |||
| 271 | if (ipv6_ext_hdr(nh->nexthdr)) | ||
| 272 | recalc_csum = ipv6_find_hdr(skb, &offset, | ||
| 273 | NEXTHDR_ROUTING, NULL, | ||
| 274 | &flags) != NEXTHDR_ROUTING; | ||
| 275 | |||
| 276 | set_ipv6_addr(skb, ipv6_key->ipv6_proto, daddr, | ||
| 277 | ipv6_key->ipv6_dst, recalc_csum); | ||
| 278 | } | ||
| 279 | |||
| 280 | set_ipv6_tc(nh, ipv6_key->ipv6_tclass); | ||
| 281 | set_ipv6_fl(nh, ntohl(ipv6_key->ipv6_label)); | ||
| 282 | nh->hop_limit = ipv6_key->ipv6_hlimit; | ||
| 283 | |||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 198 | /* Must follow make_writable() since that can move the skb data. */ | 287 | /* Must follow make_writable() since that can move the skb data. */ |
| 199 | static void set_tp_port(struct sk_buff *skb, __be16 *port, | 288 | static void set_tp_port(struct sk_buff *skb, __be16 *port, |
| 200 | __be16 new_port, __sum16 *check) | 289 | __be16 new_port, __sum16 *check) |
| @@ -347,6 +436,10 @@ static int execute_set_action(struct sk_buff *skb, | |||
| 347 | err = set_ipv4(skb, nla_data(nested_attr)); | 436 | err = set_ipv4(skb, nla_data(nested_attr)); |
| 348 | break; | 437 | break; |
| 349 | 438 | ||
| 439 | case OVS_KEY_ATTR_IPV6: | ||
| 440 | err = set_ipv6(skb, nla_data(nested_attr)); | ||
| 441 | break; | ||
| 442 | |||
| 350 | case OVS_KEY_ATTR_TCP: | 443 | case OVS_KEY_ATTR_TCP: |
| 351 | err = set_tcp(skb, nla_data(nested_attr)); | 444 | err = set_tcp(skb, nla_data(nested_attr)); |
| 352 | break; | 445 | break; |
diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 4c4b62ccc7d7..fd4a6a4ce3cd 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c | |||
| @@ -479,6 +479,7 @@ static int validate_set(const struct nlattr *a, | |||
| 479 | 479 | ||
| 480 | switch (key_type) { | 480 | switch (key_type) { |
| 481 | const struct ovs_key_ipv4 *ipv4_key; | 481 | const struct ovs_key_ipv4 *ipv4_key; |
| 482 | const struct ovs_key_ipv6 *ipv6_key; | ||
| 482 | 483 | ||
| 483 | case OVS_KEY_ATTR_PRIORITY: | 484 | case OVS_KEY_ATTR_PRIORITY: |
| 484 | case OVS_KEY_ATTR_ETHERNET: | 485 | case OVS_KEY_ATTR_ETHERNET: |
| @@ -500,6 +501,25 @@ static int validate_set(const struct nlattr *a, | |||
| 500 | 501 | ||
| 501 | break; | 502 | break; |
| 502 | 503 | ||
| 504 | case OVS_KEY_ATTR_IPV6: | ||
| 505 | if (flow_key->eth.type != htons(ETH_P_IPV6)) | ||
| 506 | return -EINVAL; | ||
| 507 | |||
| 508 | if (!flow_key->ip.proto) | ||
| 509 | return -EINVAL; | ||
| 510 | |||
| 511 | ipv6_key = nla_data(ovs_key); | ||
| 512 | if (ipv6_key->ipv6_proto != flow_key->ip.proto) | ||
| 513 | return -EINVAL; | ||
| 514 | |||
| 515 | if (ipv6_key->ipv6_frag != flow_key->ip.frag) | ||
| 516 | return -EINVAL; | ||
| 517 | |||
| 518 | if (ntohl(ipv6_key->ipv6_label) & 0xFFF00000) | ||
| 519 | return -EINVAL; | ||
| 520 | |||
| 521 | break; | ||
| 522 | |||
| 503 | case OVS_KEY_ATTR_TCP: | 523 | case OVS_KEY_ATTR_TCP: |
| 504 | if (flow_key->ip.proto != IPPROTO_TCP) | 524 | if (flow_key->ip.proto != IPPROTO_TCP) |
| 505 | return -EINVAL; | 525 | return -EINVAL; |
