Commit 63dca2c0 authored by Jesper Dangaard Brouer's avatar Jesper Dangaard Brouer Committed by Simon Horman

ipvs: Fix faulty IPv6 extension header handling in IPVS

IPv6 packets can contain extension headers, thus its wrong to assume
that the transport/upper-layer header, starts right after (struct
ipv6hdr) the IPv6 header.  IPVS uses this false assumption, and will
write SNAT & DNAT modifications at a fixed pos which will corrupt the
message.

To fix this, proper header position must be found before modifying
packets.  Introducing ip_vs_fill_iph_skb(), which uses ipv6_find_hdr()
to skip the exthdrs. It finds (1) the transport header offset, (2) the
protocol, and (3) detects if the packet is a fragment.

Note, that fragments in IPv6 is represented via an exthdr.  Thus, this
is detected while skipping through the exthdrs.

This patch depends on commit 84018f55:
 "netfilter: ip6_tables: add flags parameter to ipv6_find_hdr()"
This also adds a dependency to ip6_tables.

Originally based on patch from: Hans Schillstrom

kABI notes:
Changing struct ip_vs_iphdr is a potential minor kABI breaker,
because external modules can be compiled with another version of
this struct.  This should not matter, as they would most-likely
be using a compiled-in version of ip_vs_fill_iphdr().  When
recompiled, they will notice ip_vs_fill_iphdr() no longer exists,
and they have to used ip_vs_fill_iph_skb() instead.
Signed-off-by: default avatarJesper Dangaard Brouer <brouer@redhat.com>
Acked-by: default avatarJulian Anastasov <ja@ssi.bg>
Signed-off-by: default avatarSimon Horman <horms@verge.net.au>
parent a638e514
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/ipv6.h> /* for struct ipv6hdr */ #include <linux/ipv6.h> /* for struct ipv6hdr */
#include <net/ipv6.h> #include <net/ipv6.h>
#if IS_ENABLED(CONFIG_IPV6)
#include <linux/netfilter_ipv6/ip6_tables.h>
#endif
#if IS_ENABLED(CONFIG_NF_CONNTRACK) #if IS_ENABLED(CONFIG_NF_CONNTRACK)
#include <net/netfilter/nf_conntrack.h> #include <net/netfilter/nf_conntrack.h>
#endif #endif
...@@ -103,30 +106,79 @@ static inline struct net *seq_file_single_net(struct seq_file *seq) ...@@ -103,30 +106,79 @@ static inline struct net *seq_file_single_net(struct seq_file *seq)
/* Connections' size value needed by ip_vs_ctl.c */ /* Connections' size value needed by ip_vs_ctl.c */
extern int ip_vs_conn_tab_size; extern int ip_vs_conn_tab_size;
struct ip_vs_iphdr { struct ip_vs_iphdr {
int len; __u32 len; /* IPv4 simply where L4 starts
__u8 protocol; IPv6 where L4 Transport Header starts */
__u16 fragoffs; /* IPv6 fragment offset, 0 if first frag (or not frag)*/
__s16 protocol;
__s32 flags;
union nf_inet_addr saddr; union nf_inet_addr saddr;
union nf_inet_addr daddr; union nf_inet_addr daddr;
}; };
static inline void static inline void
ip_vs_fill_iphdr(int af, const void *nh, struct ip_vs_iphdr *iphdr) ip_vs_fill_ip4hdr(const void *nh, struct ip_vs_iphdr *iphdr)
{
const struct iphdr *iph = nh;
iphdr->len = iph->ihl * 4;
iphdr->fragoffs = 0;
iphdr->protocol = iph->protocol;
iphdr->saddr.ip = iph->saddr;
iphdr->daddr.ip = iph->daddr;
}
/* This function handles filling *ip_vs_iphdr, both for IPv4 and IPv6.
* IPv6 requires some extra work, as finding proper header position,
* depend on the IPv6 extension headers.
*/
static inline void
ip_vs_fill_iph_skb(int af, const struct sk_buff *skb, struct ip_vs_iphdr *iphdr)
{ {
#ifdef CONFIG_IP_VS_IPV6 #ifdef CONFIG_IP_VS_IPV6
if (af == AF_INET6) { if (af == AF_INET6) {
const struct ipv6hdr *iph = nh; const struct ipv6hdr *iph =
iphdr->len = sizeof(struct ipv6hdr); (struct ipv6hdr *)skb_network_header(skb);
iphdr->protocol = iph->nexthdr;
iphdr->saddr.in6 = iph->saddr; iphdr->saddr.in6 = iph->saddr;
iphdr->daddr.in6 = iph->daddr; iphdr->daddr.in6 = iph->daddr;
/* ipv6_find_hdr() updates len, flags */
iphdr->len = 0;
iphdr->flags = 0;
iphdr->protocol = ipv6_find_hdr(skb, &iphdr->len, -1,
&iphdr->fragoffs,
&iphdr->flags);
} else } else
#endif #endif
{ {
const struct iphdr *iph = nh; const struct iphdr *iph =
iphdr->len = iph->ihl * 4; (struct iphdr *)skb_network_header(skb);
iphdr->protocol = iph->protocol; iphdr->len = iph->ihl * 4;
iphdr->fragoffs = 0;
iphdr->protocol = iph->protocol;
iphdr->saddr.ip = iph->saddr;
iphdr->daddr.ip = iph->daddr;
}
}
/* This function is a faster version of ip_vs_fill_iph_skb().
* Where we only populate {s,d}addr (and avoid calling ipv6_find_hdr()).
* This is used by the some of the ip_vs_*_schedule() functions.
* (Mostly done to avoid ABI breakage of external schedulers)
*/
static inline void
ip_vs_fill_iph_addr_only(int af, const struct sk_buff *skb,
struct ip_vs_iphdr *iphdr)
{
#ifdef CONFIG_IP_VS_IPV6
if (af == AF_INET6) {
const struct ipv6hdr *iph =
(struct ipv6hdr *)skb_network_header(skb);
iphdr->saddr.in6 = iph->saddr;
iphdr->daddr.in6 = iph->daddr;
} else {
#endif
const struct iphdr *iph =
(struct iphdr *)skb_network_header(skb);
iphdr->saddr.ip = iph->saddr; iphdr->saddr.ip = iph->saddr;
iphdr->daddr.ip = iph->daddr; iphdr->daddr.ip = iph->daddr;
} }
......
...@@ -28,6 +28,7 @@ if IP_VS ...@@ -28,6 +28,7 @@ if IP_VS
config IP_VS_IPV6 config IP_VS_IPV6
bool "IPv6 support for IPVS" bool "IPv6 support for IPVS"
depends on IPV6 = y || IP_VS = IPV6 depends on IPV6 = y || IP_VS = IPV6
select IP6_NF_IPTABLES
---help--- ---help---
Add IPv6 support to IPVS. This is incomplete and might be dangerous. Add IPv6 support to IPVS. This is incomplete and might be dangerous.
......
This diff is collapsed.
...@@ -215,7 +215,7 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb) ...@@ -215,7 +215,7 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb)
struct ip_vs_dh_bucket *tbl; struct ip_vs_dh_bucket *tbl;
struct ip_vs_iphdr iph; struct ip_vs_iphdr iph;
ip_vs_fill_iphdr(svc->af, skb_network_header(skb), &iph); ip_vs_fill_iph_addr_only(svc->af, skb, &iph);
IP_VS_DBG(6, "%s(): Scheduling...\n", __func__); IP_VS_DBG(6, "%s(): Scheduling...\n", __func__);
......
...@@ -479,7 +479,7 @@ ip_vs_lblc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb) ...@@ -479,7 +479,7 @@ ip_vs_lblc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb)
struct ip_vs_dest *dest = NULL; struct ip_vs_dest *dest = NULL;
struct ip_vs_lblc_entry *en; struct ip_vs_lblc_entry *en;
ip_vs_fill_iphdr(svc->af, skb_network_header(skb), &iph); ip_vs_fill_iph_addr_only(svc->af, skb, &iph);
IP_VS_DBG(6, "%s(): Scheduling...\n", __func__); IP_VS_DBG(6, "%s(): Scheduling...\n", __func__);
......
...@@ -649,7 +649,7 @@ ip_vs_lblcr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb) ...@@ -649,7 +649,7 @@ ip_vs_lblcr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb)
struct ip_vs_dest *dest = NULL; struct ip_vs_dest *dest = NULL;
struct ip_vs_lblcr_entry *en; struct ip_vs_lblcr_entry *en;
ip_vs_fill_iphdr(svc->af, skb_network_header(skb), &iph); ip_vs_fill_iph_addr_only(svc->af, skb, &iph);
IP_VS_DBG(6, "%s(): Scheduling...\n", __func__); IP_VS_DBG(6, "%s(): Scheduling...\n", __func__);
......
...@@ -73,7 +73,7 @@ ip_vs_sip_fill_param(struct ip_vs_conn_param *p, struct sk_buff *skb) ...@@ -73,7 +73,7 @@ ip_vs_sip_fill_param(struct ip_vs_conn_param *p, struct sk_buff *skb)
const char *dptr; const char *dptr;
int retc; int retc;
ip_vs_fill_iphdr(p->af, skb_network_header(skb), &iph); ip_vs_fill_iph_skb(p->af, skb, &iph);
/* Only useful with UDP */ /* Only useful with UDP */
if (iph.protocol != IPPROTO_UDP) if (iph.protocol != IPPROTO_UDP)
......
...@@ -18,7 +18,7 @@ sctp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, ...@@ -18,7 +18,7 @@ sctp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd,
sctp_sctphdr_t *sh, _sctph; sctp_sctphdr_t *sh, _sctph;
struct ip_vs_iphdr iph; struct ip_vs_iphdr iph;
ip_vs_fill_iphdr(af, skb_network_header(skb), &iph); ip_vs_fill_iph_skb(af, skb, &iph);
sh = skb_header_pointer(skb, iph.len, sizeof(_sctph), &_sctph); sh = skb_header_pointer(skb, iph.len, sizeof(_sctph), &_sctph);
if (sh == NULL) if (sh == NULL)
...@@ -72,12 +72,14 @@ sctp_snat_handler(struct sk_buff *skb, ...@@ -72,12 +72,14 @@ sctp_snat_handler(struct sk_buff *skb,
struct sk_buff *iter; struct sk_buff *iter;
__be32 crc32; __be32 crc32;
struct ip_vs_iphdr iph;
ip_vs_fill_iph_skb(cp->af, skb, &iph);
sctphoff = iph.len;
#ifdef CONFIG_IP_VS_IPV6 #ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6) if (cp->af == AF_INET6 && iph.fragoffs)
sctphoff = sizeof(struct ipv6hdr); return 1;
else
#endif #endif
sctphoff = ip_hdrlen(skb);
/* csum_check requires unshared skb */ /* csum_check requires unshared skb */
if (!skb_make_writable(skb, sctphoff + sizeof(*sctph))) if (!skb_make_writable(skb, sctphoff + sizeof(*sctph)))
...@@ -116,12 +118,14 @@ sctp_dnat_handler(struct sk_buff *skb, ...@@ -116,12 +118,14 @@ sctp_dnat_handler(struct sk_buff *skb,
struct sk_buff *iter; struct sk_buff *iter;
__be32 crc32; __be32 crc32;
struct ip_vs_iphdr iph;
ip_vs_fill_iph_skb(cp->af, skb, &iph);
sctphoff = iph.len;
#ifdef CONFIG_IP_VS_IPV6 #ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6) if (cp->af == AF_INET6 && iph.fragoffs)
sctphoff = sizeof(struct ipv6hdr); return 1;
else
#endif #endif
sctphoff = ip_hdrlen(skb);
/* csum_check requires unshared skb */ /* csum_check requires unshared skb */
if (!skb_make_writable(skb, sctphoff + sizeof(*sctph))) if (!skb_make_writable(skb, sctphoff + sizeof(*sctph)))
......
...@@ -40,7 +40,7 @@ tcp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, ...@@ -40,7 +40,7 @@ tcp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd,
struct tcphdr _tcph, *th; struct tcphdr _tcph, *th;
struct ip_vs_iphdr iph; struct ip_vs_iphdr iph;
ip_vs_fill_iphdr(af, skb_network_header(skb), &iph); ip_vs_fill_iph_skb(af, skb, &iph);
th = skb_header_pointer(skb, iph.len, sizeof(_tcph), &_tcph); th = skb_header_pointer(skb, iph.len, sizeof(_tcph), &_tcph);
if (th == NULL) { if (th == NULL) {
...@@ -136,12 +136,14 @@ tcp_snat_handler(struct sk_buff *skb, ...@@ -136,12 +136,14 @@ tcp_snat_handler(struct sk_buff *skb,
int oldlen; int oldlen;
int payload_csum = 0; int payload_csum = 0;
struct ip_vs_iphdr iph;
ip_vs_fill_iph_skb(cp->af, skb, &iph);
tcphoff = iph.len;
#ifdef CONFIG_IP_VS_IPV6 #ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6) if (cp->af == AF_INET6 && iph.fragoffs)
tcphoff = sizeof(struct ipv6hdr); return 1;
else
#endif #endif
tcphoff = ip_hdrlen(skb);
oldlen = skb->len - tcphoff; oldlen = skb->len - tcphoff;
/* csum_check requires unshared skb */ /* csum_check requires unshared skb */
...@@ -216,12 +218,14 @@ tcp_dnat_handler(struct sk_buff *skb, ...@@ -216,12 +218,14 @@ tcp_dnat_handler(struct sk_buff *skb,
int oldlen; int oldlen;
int payload_csum = 0; int payload_csum = 0;
struct ip_vs_iphdr iph;
ip_vs_fill_iph_skb(cp->af, skb, &iph);
tcphoff = iph.len;
#ifdef CONFIG_IP_VS_IPV6 #ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6) if (cp->af == AF_INET6 && iph.fragoffs)
tcphoff = sizeof(struct ipv6hdr); return 1;
else
#endif #endif
tcphoff = ip_hdrlen(skb);
oldlen = skb->len - tcphoff; oldlen = skb->len - tcphoff;
/* csum_check requires unshared skb */ /* csum_check requires unshared skb */
......
...@@ -37,7 +37,7 @@ udp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, ...@@ -37,7 +37,7 @@ udp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd,
struct udphdr _udph, *uh; struct udphdr _udph, *uh;
struct ip_vs_iphdr iph; struct ip_vs_iphdr iph;
ip_vs_fill_iphdr(af, skb_network_header(skb), &iph); ip_vs_fill_iph_skb(af, skb, &iph);
uh = skb_header_pointer(skb, iph.len, sizeof(_udph), &_udph); uh = skb_header_pointer(skb, iph.len, sizeof(_udph), &_udph);
if (uh == NULL) { if (uh == NULL) {
...@@ -133,12 +133,14 @@ udp_snat_handler(struct sk_buff *skb, ...@@ -133,12 +133,14 @@ udp_snat_handler(struct sk_buff *skb,
int oldlen; int oldlen;
int payload_csum = 0; int payload_csum = 0;
struct ip_vs_iphdr iph;
ip_vs_fill_iph_skb(cp->af, skb, &iph);
udphoff = iph.len;
#ifdef CONFIG_IP_VS_IPV6 #ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6) if (cp->af == AF_INET6 && iph.fragoffs)
udphoff = sizeof(struct ipv6hdr); return 1;
else
#endif #endif
udphoff = ip_hdrlen(skb);
oldlen = skb->len - udphoff; oldlen = skb->len - udphoff;
/* csum_check requires unshared skb */ /* csum_check requires unshared skb */
...@@ -218,12 +220,14 @@ udp_dnat_handler(struct sk_buff *skb, ...@@ -218,12 +220,14 @@ udp_dnat_handler(struct sk_buff *skb,
int oldlen; int oldlen;
int payload_csum = 0; int payload_csum = 0;
struct ip_vs_iphdr iph;
ip_vs_fill_iph_skb(cp->af, skb, &iph);
udphoff = iph.len;
#ifdef CONFIG_IP_VS_IPV6 #ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6) if (cp->af == AF_INET6 && iph.fragoffs)
udphoff = sizeof(struct ipv6hdr); return 1;
else
#endif #endif
udphoff = ip_hdrlen(skb);
oldlen = skb->len - udphoff; oldlen = skb->len - udphoff;
/* csum_check requires unshared skb */ /* csum_check requires unshared skb */
......
...@@ -228,7 +228,7 @@ ip_vs_sh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb) ...@@ -228,7 +228,7 @@ ip_vs_sh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb)
struct ip_vs_sh_bucket *tbl; struct ip_vs_sh_bucket *tbl;
struct ip_vs_iphdr iph; struct ip_vs_iphdr iph;
ip_vs_fill_iphdr(svc->af, skb_network_header(skb), &iph); ip_vs_fill_iph_addr_only(svc->af, skb, &iph);
IP_VS_DBG(6, "ip_vs_sh_schedule(): Scheduling...\n"); IP_VS_DBG(6, "ip_vs_sh_schedule(): Scheduling...\n");
......
...@@ -679,14 +679,15 @@ ip_vs_nat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, ...@@ -679,14 +679,15 @@ ip_vs_nat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
struct rt6_info *rt; /* Route to the other host */ struct rt6_info *rt; /* Route to the other host */
int mtu; int mtu;
int local; int local;
struct ip_vs_iphdr iph;
EnterFunction(10); EnterFunction(10);
ip_vs_fill_iph_skb(cp->af, skb, &iph);
/* check if it is a connection of no-client-port */ /* check if it is a connection of no-client-port */
if (unlikely(cp->flags & IP_VS_CONN_F_NO_CPORT)) { if (unlikely(cp->flags & IP_VS_CONN_F_NO_CPORT)) {
__be16 _pt, *p; __be16 _pt, *p;
p = skb_header_pointer(skb, sizeof(struct ipv6hdr), p = skb_header_pointer(skb, iph.len, sizeof(_pt), &_pt);
sizeof(_pt), &_pt);
if (p == NULL) if (p == NULL)
goto tx_error; goto tx_error;
ip_vs_conn_fill_cport(cp, *p); ip_vs_conn_fill_cport(cp, *p);
......
...@@ -67,7 +67,7 @@ ipvs_mt(const struct sk_buff *skb, struct xt_action_param *par) ...@@ -67,7 +67,7 @@ ipvs_mt(const struct sk_buff *skb, struct xt_action_param *par)
goto out; goto out;
} }
ip_vs_fill_iphdr(family, skb_network_header(skb), &iph); ip_vs_fill_iph_skb(family, skb, &iph);
if (data->bitmask & XT_IPVS_PROTO) if (data->bitmask & XT_IPVS_PROTO)
if ((iph.protocol == data->l4proto) ^ if ((iph.protocol == data->l4proto) ^
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment