diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
index 12e339cbf791..3b10b3fbadc3 100644
--- a/net/openvswitch/actions.c
+++ b/net/openvswitch/actions.c
@@ -319,20 +319,22 @@ static int pop_nsh(struct sk_buff *skb, struct sw_flow_key *key)
return 0;
}
-static void update_ip_l4_checksum(struct sk_buff *skb, struct iphdr *nh,
- __be32 addr, __be32 new_addr)
+static void update_ip_l4_checksum(struct sk_buff *skb, __be32 addr,
+ __be32 new_addr)
{
- int transport_len = skb->len - skb_transport_offset(skb);
+ struct iphdr *nh = ip_hdr(skb);
if (nh->frag_off & htons(IP_OFFSET))
return;
if (nh->protocol == IPPROTO_TCP) {
- if (likely(transport_len >= sizeof(struct tcphdr)))
+ if (likely(!skb_ensure_writable(skb, skb_transport_offset(skb) +
+ sizeof(struct tcphdr))))
inet_proto_csum_replace4(&tcp_hdr(skb)->check, skb,
addr, new_addr, true);
} else if (nh->protocol == IPPROTO_UDP) {
- if (likely(transport_len >= sizeof(struct udphdr))) {
+ if (likely(!skb_ensure_writable(skb, skb_transport_offset(skb) +
+ sizeof(struct udphdr)))) {
struct udphdr *uh = udp_hdr(skb);
if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {@@ -345,32 +347,34 @@ static void update_ip_l4_checksum(struct sk_buff *skb, struct iphdr *nh,
}
}
-static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh,
- __be32 *addr, __be32 new_addr)
+/* May move the skb head; callers must reload packet pointers. */
+static void set_ip_addr(struct sk_buff *skb, __be32 new_addr, bool saddr)
{
- update_ip_l4_checksum(skb, nh, *addr, new_addr);
- csum_replace4(&nh->check, *addr, new_addr);
+ __be32 old_addr = saddr ? ip_hdr(skb)->saddr : ip_hdr(skb)->daddr;
+
+ update_ip_l4_checksum(skb, old_addr, new_addr);
+ csum_replace4(&ip_hdr(skb)->check, old_addr, new_addr);
+
skb_clear_hash(skb);
ovs_ct_clear(skb, NULL);
- *addr = new_addr;
+
+ if (saddr)
+ ip_hdr(skb)->saddr = new_addr;
+ else
+ ip_hdr(skb)->daddr = new_addr;
}
static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto,
__be32 addr[4], const __be32 new_addr[4])
{
- int transport_len;
-
- if (l4_proto == NEXTHDR_FRAGMENT)
- return;
-
- transport_len = skb->len - skb_transport_offset(skb);
-
if (l4_proto == NEXTHDR_TCP) {
- if (likely(transport_len >= sizeof(struct tcphdr)))
+ if (likely(!skb_ensure_writable(skb, skb_transport_offset(skb) +
+ sizeof(struct tcphdr))))
inet_proto_csum_replace16(&tcp_hdr(skb)->check, skb,
addr, new_addr, true);
} else if (l4_proto == NEXTHDR_UDP) {
- if (likely(transport_len >= sizeof(struct udphdr))) {
+ if (likely(!skb_ensure_writable(skb, skb_transport_offset(skb) +
+ sizeof(struct udphdr)))) {
struct udphdr *uh = udp_hdr(skb);
if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {@@ -381,7 +385,8 @@ static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto,
}
}
} else if (l4_proto == NEXTHDR_ICMP) {
- if (likely(transport_len >= sizeof(struct icmp6hdr)))
+ if (likely(!skb_ensure_writable(skb, skb_transport_offset(skb) +
+ sizeof(struct icmp6hdr))))
inet_proto_csum_replace16(&icmp6_hdr(skb)->icmp6_cksum,
skb, addr, new_addr, true);
}@@ -396,16 +401,28 @@ static void mask_ipv6_addr(const __be32 old[4], const __be32 addr[4],
masked[3] = OVS_MASKED(old[3], addr[3], mask[3]);
}
+/* May move the skb head; callers must reload packet pointers. */
static void set_ipv6_addr(struct sk_buff *skb, u8 l4_proto,
- __be32 addr[4], const __be32 new_addr[4],
+ const __be32 new_addr[4], bool saddr,
bool recalculate_csum)
{
- if (recalculate_csum)
- update_ipv6_checksum(skb, l4_proto, addr, new_addr);
+ struct ipv6hdr *nh = ipv6_hdr(skb);
+ __be32 old_addr[4];
+
+ if (recalculate_csum) {
+ memcpy(old_addr, saddr ? &nh->saddr : &nh->daddr,
+ sizeof(old_addr));
+ update_ipv6_checksum(skb, l4_proto, old_addr, new_addr);
+ nh = ipv6_hdr(skb);
+ }
skb_clear_hash(skb);
ovs_ct_clear(skb, NULL);
- memcpy(addr, new_addr, sizeof(__be32[4]));
+
+ if (saddr)
+ memcpy(&nh->saddr, new_addr, sizeof(__be32[4]));
+ else
+ memcpy(&nh->daddr, new_addr, sizeof(__be32[4]));
}
static void set_ipv6_dsfield(struct sk_buff *skb, struct ipv6hdr *nh, u8 ipv6_tclass, u8 mask)@@ -479,16 +496,18 @@ static int set_ipv4(struct sk_buff *skb, struct sw_flow_key *flow_key,
new_addr = OVS_MASKED(nh->saddr, key->ipv4_src, mask->ipv4_src);
if (unlikely(new_addr != nh->saddr)) {
- set_ip_addr(skb, nh, &nh->saddr, new_addr);
+ set_ip_addr(skb, new_addr, true);
flow_key->ipv4.addr.src = new_addr;
+ nh = ip_hdr(skb);
}
}
if (mask->ipv4_dst) {
new_addr = OVS_MASKED(nh->daddr, key->ipv4_dst, mask->ipv4_dst);
if (unlikely(new_addr != nh->daddr)) {
- set_ip_addr(skb, nh, &nh->daddr, new_addr);
+ set_ip_addr(skb, new_addr, false);
flow_key->ipv4.addr.dst = new_addr;
+ nh = ip_hdr(skb);
}
}
if (mask->ipv4_tos) {@@ -533,10 +552,11 @@ static int set_ipv6(struct sk_buff *skb, struct sw_flow_key *flow_key,
mask_ipv6_addr(saddr, key->ipv6_src, mask->ipv6_src, masked);
if (unlikely(memcmp(saddr, masked, sizeof(masked)))) {
- set_ipv6_addr(skb, flow_key->ip.proto, saddr, masked,
+ set_ipv6_addr(skb, flow_key->ip.proto, masked, true,
true);
memcpy(&flow_key->ipv6.addr.src, masked,
sizeof(flow_key->ipv6.addr.src));
+ nh = ipv6_hdr(skb);
}
}
if (is_ipv6_mask_nonzero(mask->ipv6_dst)) {@@ -555,10 +575,11 @@ static int set_ipv6(struct sk_buff *skb, struct sw_flow_key *flow_key,
NULL, &flags)
!= NEXTHDR_ROUTING);
- set_ipv6_addr(skb, flow_key->ip.proto, daddr, masked,
+ set_ipv6_addr(skb, flow_key->ip.proto, masked, false,
recalc_csum);
memcpy(&flow_key->ipv6.addr.dst, masked,
sizeof(flow_key->ipv6.addr.dst));
+ nh = ipv6_hdr(skb);
}
}
if (mask->ipv6_tclass) {