mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-27 12:57:53 +00:00
eaafdaa3e9
For IPv6 Jumbo packets, the ipv6_hdr(skb)->payload_len is always 0, and its real payload_len ( > 65535) is saved in hbh exthdr. With 0 length for the jumbo packets, all data and exthdr will be trimmed in nf_ct_skb_network_trim(). This patch is to call nf_ip6_check_hbh_len() to get real pkt_len of the IPv6 packet, similar to br_validate_ipv6(). Signed-off-by: Xin Long <lucien.xin@gmail.com> Reviewed-by: Simon Horman <simon.horman@corigine.com> Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org> Reviewed-by: Aaron Conole <aconole@redhat.com> Signed-off-by: Florian Westphal <fw@strlen.de>
185 lines
4.5 KiB
C
185 lines
4.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Support ct functions for openvswitch and used by OVS and TC conntrack. */
|
|
|
|
#include <net/netfilter/nf_conntrack_helper.h>
|
|
#include <net/netfilter/nf_conntrack_seqadj.h>
|
|
#include <net/netfilter/ipv6/nf_defrag_ipv6.h>
|
|
#include <net/ipv6_frag.h>
|
|
#include <net/ip.h>
|
|
#include <linux/netfilter_ipv6.h>
|
|
|
|
/* 'skb' should already be pulled to nh_ofs. */
|
|
int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct,
|
|
enum ip_conntrack_info ctinfo, u16 proto)
|
|
{
|
|
const struct nf_conntrack_helper *helper;
|
|
const struct nf_conn_help *help;
|
|
unsigned int protoff;
|
|
int err;
|
|
|
|
if (ctinfo == IP_CT_RELATED_REPLY)
|
|
return NF_ACCEPT;
|
|
|
|
help = nfct_help(ct);
|
|
if (!help)
|
|
return NF_ACCEPT;
|
|
|
|
helper = rcu_dereference(help->helper);
|
|
if (!helper)
|
|
return NF_ACCEPT;
|
|
|
|
if (helper->tuple.src.l3num != NFPROTO_UNSPEC &&
|
|
helper->tuple.src.l3num != proto)
|
|
return NF_ACCEPT;
|
|
|
|
switch (proto) {
|
|
case NFPROTO_IPV4:
|
|
protoff = ip_hdrlen(skb);
|
|
proto = ip_hdr(skb)->protocol;
|
|
break;
|
|
case NFPROTO_IPV6: {
|
|
u8 nexthdr = ipv6_hdr(skb)->nexthdr;
|
|
__be16 frag_off;
|
|
int ofs;
|
|
|
|
ofs = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
|
|
&frag_off);
|
|
if (ofs < 0 || (frag_off & htons(~0x7)) != 0) {
|
|
pr_debug("proto header not found\n");
|
|
return NF_ACCEPT;
|
|
}
|
|
protoff = ofs;
|
|
proto = nexthdr;
|
|
break;
|
|
}
|
|
default:
|
|
WARN_ONCE(1, "helper invoked on non-IP family!");
|
|
return NF_DROP;
|
|
}
|
|
|
|
if (helper->tuple.dst.protonum != proto)
|
|
return NF_ACCEPT;
|
|
|
|
err = helper->help(skb, protoff, ct, ctinfo);
|
|
if (err != NF_ACCEPT)
|
|
return err;
|
|
|
|
/* Adjust seqs after helper. This is needed due to some helpers (e.g.,
|
|
* FTP with NAT) adusting the TCP payload size when mangling IP
|
|
* addresses and/or port numbers in the text-based control connection.
|
|
*/
|
|
if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
|
|
!nf_ct_seq_adjust(skb, ct, ctinfo, protoff))
|
|
return NF_DROP;
|
|
return NF_ACCEPT;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nf_ct_helper);
|
|
|
|
int nf_ct_add_helper(struct nf_conn *ct, const char *name, u8 family,
|
|
u8 proto, bool nat, struct nf_conntrack_helper **hp)
|
|
{
|
|
struct nf_conntrack_helper *helper;
|
|
struct nf_conn_help *help;
|
|
int ret = 0;
|
|
|
|
helper = nf_conntrack_helper_try_module_get(name, family, proto);
|
|
if (!helper)
|
|
return -EINVAL;
|
|
|
|
help = nf_ct_helper_ext_add(ct, GFP_KERNEL);
|
|
if (!help) {
|
|
nf_conntrack_helper_put(helper);
|
|
return -ENOMEM;
|
|
}
|
|
#if IS_ENABLED(CONFIG_NF_NAT)
|
|
if (nat) {
|
|
ret = nf_nat_helper_try_module_get(name, family, proto);
|
|
if (ret) {
|
|
nf_conntrack_helper_put(helper);
|
|
return ret;
|
|
}
|
|
}
|
|
#endif
|
|
rcu_assign_pointer(help->helper, helper);
|
|
*hp = helper;
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nf_ct_add_helper);
|
|
|
|
/* Trim the skb to the length specified by the IP/IPv6 header,
|
|
* removing any trailing lower-layer padding. This prepares the skb
|
|
* for higher-layer processing that assumes skb->len excludes padding
|
|
* (such as nf_ip_checksum). The caller needs to pull the skb to the
|
|
* network header, and ensure ip_hdr/ipv6_hdr points to valid data.
|
|
*/
|
|
int nf_ct_skb_network_trim(struct sk_buff *skb, int family)
|
|
{
|
|
unsigned int len;
|
|
|
|
switch (family) {
|
|
case NFPROTO_IPV4:
|
|
len = skb_ip_totlen(skb);
|
|
break;
|
|
case NFPROTO_IPV6:
|
|
len = ntohs(ipv6_hdr(skb)->payload_len);
|
|
if (ipv6_hdr(skb)->nexthdr == NEXTHDR_HOP) {
|
|
int err = nf_ip6_check_hbh_len(skb, &len);
|
|
|
|
if (err)
|
|
return err;
|
|
}
|
|
len += sizeof(struct ipv6hdr);
|
|
break;
|
|
default:
|
|
len = skb->len;
|
|
}
|
|
|
|
return pskb_trim_rcsum(skb, len);
|
|
}
|
|
EXPORT_SYMBOL_GPL(nf_ct_skb_network_trim);
|
|
|
|
/* Returns 0 on success, -EINPROGRESS if 'skb' is stolen, or other nonzero
|
|
* value if 'skb' is freed.
|
|
*/
|
|
int nf_ct_handle_fragments(struct net *net, struct sk_buff *skb,
|
|
u16 zone, u8 family, u8 *proto, u16 *mru)
|
|
{
|
|
int err;
|
|
|
|
if (family == NFPROTO_IPV4) {
|
|
enum ip_defrag_users user = IP_DEFRAG_CONNTRACK_IN + zone;
|
|
|
|
memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
|
|
local_bh_disable();
|
|
err = ip_defrag(net, skb, user);
|
|
local_bh_enable();
|
|
if (err)
|
|
return err;
|
|
|
|
*mru = IPCB(skb)->frag_max_size;
|
|
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
|
|
} else if (family == NFPROTO_IPV6) {
|
|
enum ip6_defrag_users user = IP6_DEFRAG_CONNTRACK_IN + zone;
|
|
|
|
memset(IP6CB(skb), 0, sizeof(struct inet6_skb_parm));
|
|
err = nf_ct_frag6_gather(net, skb, user);
|
|
if (err) {
|
|
if (err != -EINPROGRESS)
|
|
kfree_skb(skb);
|
|
return err;
|
|
}
|
|
|
|
*proto = ipv6_hdr(skb)->nexthdr;
|
|
*mru = IP6CB(skb)->frag_max_size;
|
|
#endif
|
|
} else {
|
|
kfree_skb(skb);
|
|
return -EPFNOSUPPORT;
|
|
}
|
|
|
|
skb_clear_hash(skb);
|
|
skb->ignore_df = 1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nf_ct_handle_fragments);
|