Skip to content

Commit

Permalink
ipvs: handle PARTIAL_CHECKSUM
Browse files Browse the repository at this point in the history
Now that LVS can load balance locally generated traffic, packets may come
from the loopback device and thus may have a partial checksum.

The existing code allows for the case where there is no checksum at all for
TCP, however Herbert Xu has confirmed that this is not legal.

Signed-off-by: Simon Horman <horms@verge.net.au>
Acked-by: Julius Volz <juliusv@google.com>
  • Loading branch information
Simon Horman committed Sep 8, 2008
1 parent 178f5e4 commit 503e81f
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 4 deletions.
37 changes: 35 additions & 2 deletions net/ipv4/ipvs/ip_vs_proto_tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,42 @@ tcp_fast_csum_update(int af, struct tcphdr *tcph,
}


static inline void
tcp_partial_csum_update(int af, struct tcphdr *tcph,
const union nf_inet_addr *oldip,
const union nf_inet_addr *newip,
__be16 oldlen, __be16 newlen)
{
#ifdef CONFIG_IP_VS_IPV6
if (af == AF_INET6)
tcph->check =
csum_fold(ip_vs_check_diff16(oldip->ip6, newip->ip6,
ip_vs_check_diff2(oldlen, newlen,
~csum_unfold(tcph->check))));
else
#endif
tcph->check =
csum_fold(ip_vs_check_diff4(oldip->ip, newip->ip,
ip_vs_check_diff2(oldlen, newlen,
~csum_unfold(tcph->check))));
}


static int
tcp_snat_handler(struct sk_buff *skb,
struct ip_vs_protocol *pp, struct ip_vs_conn *cp)
{
struct tcphdr *tcph;
unsigned int tcphoff;
int oldlen;

#ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6)
tcphoff = sizeof(struct ipv6hdr);
else
#endif
tcphoff = ip_hdrlen(skb);
oldlen = skb->len - tcphoff;

/* csum_check requires unshared skb */
if (!skb_make_writable(skb, tcphoff+sizeof(*tcph)))
Expand All @@ -166,7 +189,11 @@ tcp_snat_handler(struct sk_buff *skb,
tcph->source = cp->vport;

/* Adjust TCP checksums */
if (!cp->app && (tcph->check != 0)) {
if (skb->ip_summed == CHECKSUM_PARTIAL) {
tcp_partial_csum_update(cp->af, tcph, &cp->daddr, &cp->vaddr,
htonl(oldlen),
htonl(skb->len - tcphoff));
} else if (!cp->app) {
/* Only port and addr are changed, do fast csum update */
tcp_fast_csum_update(cp->af, tcph, &cp->daddr, &cp->vaddr,
cp->dport, cp->vport);
Expand Down Expand Up @@ -204,13 +231,15 @@ tcp_dnat_handler(struct sk_buff *skb,
{
struct tcphdr *tcph;
unsigned int tcphoff;
int oldlen;

#ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6)
tcphoff = sizeof(struct ipv6hdr);
else
#endif
tcphoff = ip_hdrlen(skb);
oldlen = skb->len - tcphoff;

/* csum_check requires unshared skb */
if (!skb_make_writable(skb, tcphoff+sizeof(*tcph)))
Expand All @@ -235,7 +264,11 @@ tcp_dnat_handler(struct sk_buff *skb,
/*
* Adjust TCP checksums
*/
if (!cp->app && (tcph->check != 0)) {
if (skb->ip_summed == CHECKSUM_PARTIAL) {
tcp_partial_csum_update(cp->af, tcph, &cp->daddr, &cp->vaddr,
htonl(oldlen),
htonl(skb->len - tcphoff));
} else if (!cp->app) {
/* Only port and addr are changed, do fast csum update */
tcp_fast_csum_update(cp->af, tcph, &cp->vaddr, &cp->daddr,
cp->vport, cp->dport);
Expand Down
37 changes: 35 additions & 2 deletions net/ipv4/ipvs/ip_vs_proto_udp.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,42 @@ udp_fast_csum_update(int af, struct udphdr *uhdr,
uhdr->check = CSUM_MANGLED_0;
}

static inline void
udp_partial_csum_update(int af, struct udphdr *uhdr,
const union nf_inet_addr *oldip,
const union nf_inet_addr *newip,
__be16 oldlen, __be16 newlen)
{
#ifdef CONFIG_IP_VS_IPV6
if (af == AF_INET6)
uhdr->check =
csum_fold(ip_vs_check_diff16(oldip->ip6, newip->ip6,
ip_vs_check_diff2(oldlen, newlen,
~csum_unfold(uhdr->check))));
else
#endif
uhdr->check =
csum_fold(ip_vs_check_diff4(oldip->ip, newip->ip,
ip_vs_check_diff2(oldlen, newlen,
~csum_unfold(uhdr->check))));
}


static int
udp_snat_handler(struct sk_buff *skb,
struct ip_vs_protocol *pp, struct ip_vs_conn *cp)
{
struct udphdr *udph;
unsigned int udphoff;
int oldlen;

#ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6)
udphoff = sizeof(struct ipv6hdr);
else
#endif
udphoff = ip_hdrlen(skb);
oldlen = skb->len - udphoff;

/* csum_check requires unshared skb */
if (!skb_make_writable(skb, udphoff+sizeof(*udph)))
Expand All @@ -177,7 +200,11 @@ udp_snat_handler(struct sk_buff *skb,
/*
* Adjust UDP checksums
*/
if (!cp->app && (udph->check != 0)) {
if (skb->ip_summed == CHECKSUM_PARTIAL) {
udp_partial_csum_update(cp->af, udph, &cp->daddr, &cp->vaddr,
htonl(oldlen),
htonl(skb->len - udphoff));
} else if (!cp->app && (udph->check != 0)) {
/* Only port and addr are changed, do fast csum update */
udp_fast_csum_update(cp->af, udph, &cp->daddr, &cp->vaddr,
cp->dport, cp->vport);
Expand Down Expand Up @@ -216,13 +243,15 @@ udp_dnat_handler(struct sk_buff *skb,
{
struct udphdr *udph;
unsigned int udphoff;
int oldlen;

#ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6)
udphoff = sizeof(struct ipv6hdr);
else
#endif
udphoff = ip_hdrlen(skb);
oldlen = skb->len - udphoff;

/* csum_check requires unshared skb */
if (!skb_make_writable(skb, udphoff+sizeof(*udph)))
Expand All @@ -247,7 +276,11 @@ udp_dnat_handler(struct sk_buff *skb,
/*
* Adjust UDP checksums
*/
if (!cp->app && (udph->check != 0)) {
if (skb->ip_summed == CHECKSUM_PARTIAL) {
udp_partial_csum_update(cp->af, udph, &cp->daddr, &cp->vaddr,
htonl(oldlen),
htonl(skb->len - udphoff));
} else if (!cp->app && (udph->check != 0)) {
/* Only port and addr are changed, do fast csum update */
udp_fast_csum_update(cp->af, udph, &cp->vaddr, &cp->daddr,
cp->vport, cp->dport);
Expand Down

0 comments on commit 503e81f

Please sign in to comment.