Skip to content

Commit

Permalink
netfilter: nf_reject: add reject skbuff creation helpers
Browse files Browse the repository at this point in the history
Adds reject skbuff creation helper functions to ipv4/6 nf_reject
infrastructure. Use these functions for reject verdict in bridge
family.

Can be reused by all different families that support reject and
will not inject the reject packet through ip local out.

Signed-off-by: Jose M. Guisado Gomez <guigom@riseup.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
  • Loading branch information
Jose M. Guisado Gomez authored and Pablo Neira Ayuso committed Oct 31, 2020
1 parent 37d38ec commit fa538f7
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 192 deletions.
10 changes: 10 additions & 0 deletions include/net/netfilter/ipv4/nf_reject.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,14 @@ struct iphdr *nf_reject_iphdr_put(struct sk_buff *nskb,
void nf_reject_ip_tcphdr_put(struct sk_buff *nskb, const struct sk_buff *oldskb,
const struct tcphdr *oth);

struct sk_buff *nf_reject_skb_v4_unreach(struct net *net,
struct sk_buff *oldskb,
const struct net_device *dev,
int hook, u8 code);
struct sk_buff *nf_reject_skb_v4_tcp_reset(struct net *net,
struct sk_buff *oldskb,
const struct net_device *dev,
int hook);


#endif /* _IPV4_NF_REJECT_H */
9 changes: 9 additions & 0 deletions include/net/netfilter/ipv6/nf_reject.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ void nf_reject_ip6_tcphdr_put(struct sk_buff *nskb,
const struct sk_buff *oldskb,
const struct tcphdr *oth, unsigned int otcplen);

struct sk_buff *nf_reject_skb_v6_tcp_reset(struct net *net,
struct sk_buff *oldskb,
const struct net_device *dev,
int hook);
struct sk_buff *nf_reject_skb_v6_unreach(struct net *net,
struct sk_buff *oldskb,
const struct net_device *dev,
int hook, u8 code);

#endif /* _IPV6_NF_REJECT_H */
2 changes: 1 addition & 1 deletion net/bridge/netfilter/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ config NFT_BRIDGE_META

config NFT_BRIDGE_REJECT
tristate "Netfilter nf_tables bridge reject support"
depends on NFT_REJECT && NFT_REJECT_IPV4 && NFT_REJECT_IPV6
depends on NFT_REJECT
help
Add support to reject packets.

Expand Down
195 changes: 4 additions & 191 deletions net/bridge/netfilter/nft_reject_bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,6 @@ static void nft_reject_br_push_etherhdr(struct sk_buff *oldskb,
}
}

static int nft_bridge_iphdr_validate(struct sk_buff *skb)
{
struct iphdr *iph;
u32 len;

if (!pskb_may_pull(skb, sizeof(struct iphdr)))
return 0;

iph = ip_hdr(skb);
if (iph->ihl < 5 || iph->version != 4)
return 0;

len = ntohs(iph->tot_len);
if (skb->len < len)
return 0;
else if (len < (iph->ihl*4))
return 0;

if (!pskb_may_pull(skb, iph->ihl*4))
return 0;

return 1;
}

/* We cannot use oldskb->dev, it can be either bridge device (NF_BRIDGE INPUT)
* or the bridge port (NF_BRIDGE PREROUTING).
*/
Expand All @@ -72,29 +48,11 @@ static void nft_reject_br_send_v4_tcp_reset(struct net *net,
int hook)
{
struct sk_buff *nskb;
struct iphdr *niph;
const struct tcphdr *oth;
struct tcphdr _oth;

if (!nft_bridge_iphdr_validate(oldskb))
return;

oth = nf_reject_ip_tcphdr_get(oldskb, &_oth, hook);
if (!oth)
return;

nskb = alloc_skb(sizeof(struct iphdr) + sizeof(struct tcphdr) +
LL_MAX_HEADER, GFP_ATOMIC);
nskb = nf_reject_skb_v4_tcp_reset(net, oldskb, dev, hook);
if (!nskb)
return;

skb_reserve(nskb, LL_MAX_HEADER);
niph = nf_reject_iphdr_put(nskb, oldskb, IPPROTO_TCP,
net->ipv4.sysctl_ip_default_ttl);
nf_reject_ip_tcphdr_put(nskb, oldskb, oth);
niph->tot_len = htons(nskb->len);
ip_send_check(niph);

nft_reject_br_push_etherhdr(oldskb, nskb);

br_forward(br_port_get_rcu(dev), nskb, false, true);
Expand All @@ -106,189 +64,44 @@ static void nft_reject_br_send_v4_unreach(struct net *net,
int hook, u8 code)
{
struct sk_buff *nskb;
struct iphdr *niph;
struct icmphdr *icmph;
unsigned int len;
__wsum csum;
u8 proto;

if (!nft_bridge_iphdr_validate(oldskb))
return;

/* IP header checks: fragment. */
if (ip_hdr(oldskb)->frag_off & htons(IP_OFFSET))
return;

/* RFC says return as much as we can without exceeding 576 bytes. */
len = min_t(unsigned int, 536, oldskb->len);

if (!pskb_may_pull(oldskb, len))
return;

if (pskb_trim_rcsum(oldskb, ntohs(ip_hdr(oldskb)->tot_len)))
return;

proto = ip_hdr(oldskb)->protocol;

if (!skb_csum_unnecessary(oldskb) &&
nf_reject_verify_csum(proto) &&
nf_ip_checksum(oldskb, hook, ip_hdrlen(oldskb), proto))
return;

nskb = alloc_skb(sizeof(struct iphdr) + sizeof(struct icmphdr) +
LL_MAX_HEADER + len, GFP_ATOMIC);
nskb = nf_reject_skb_v4_unreach(net, oldskb, dev, hook, code);
if (!nskb)
return;

skb_reserve(nskb, LL_MAX_HEADER);
niph = nf_reject_iphdr_put(nskb, oldskb, IPPROTO_ICMP,
net->ipv4.sysctl_ip_default_ttl);

skb_reset_transport_header(nskb);
icmph = skb_put_zero(nskb, sizeof(struct icmphdr));
icmph->type = ICMP_DEST_UNREACH;
icmph->code = code;

skb_put_data(nskb, skb_network_header(oldskb), len);

csum = csum_partial((void *)icmph, len + sizeof(struct icmphdr), 0);
icmph->checksum = csum_fold(csum);

niph->tot_len = htons(nskb->len);
ip_send_check(niph);

nft_reject_br_push_etherhdr(oldskb, nskb);

br_forward(br_port_get_rcu(dev), nskb, false, true);
}

static int nft_bridge_ip6hdr_validate(struct sk_buff *skb)
{
struct ipv6hdr *hdr;
u32 pkt_len;

if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
return 0;

hdr = ipv6_hdr(skb);
if (hdr->version != 6)
return 0;

pkt_len = ntohs(hdr->payload_len);
if (pkt_len + sizeof(struct ipv6hdr) > skb->len)
return 0;

return 1;
}

static void nft_reject_br_send_v6_tcp_reset(struct net *net,
struct sk_buff *oldskb,
const struct net_device *dev,
int hook)
{
struct sk_buff *nskb;
const struct tcphdr *oth;
struct tcphdr _oth;
unsigned int otcplen;
struct ipv6hdr *nip6h;

if (!nft_bridge_ip6hdr_validate(oldskb))
return;

oth = nf_reject_ip6_tcphdr_get(oldskb, &_oth, &otcplen, hook);
if (!oth)
return;

nskb = alloc_skb(sizeof(struct ipv6hdr) + sizeof(struct tcphdr) +
LL_MAX_HEADER, GFP_ATOMIC);
nskb = nf_reject_skb_v6_tcp_reset(net, oldskb, dev, hook);
if (!nskb)
return;

skb_reserve(nskb, LL_MAX_HEADER);
nip6h = nf_reject_ip6hdr_put(nskb, oldskb, IPPROTO_TCP,
net->ipv6.devconf_all->hop_limit);
nf_reject_ip6_tcphdr_put(nskb, oldskb, oth, otcplen);
nip6h->payload_len = htons(nskb->len - sizeof(struct ipv6hdr));

nft_reject_br_push_etherhdr(oldskb, nskb);

br_forward(br_port_get_rcu(dev), nskb, false, true);
}

static bool reject6_br_csum_ok(struct sk_buff *skb, int hook)
{
const struct ipv6hdr *ip6h = ipv6_hdr(skb);
int thoff;
__be16 fo;
u8 proto = ip6h->nexthdr;

if (skb_csum_unnecessary(skb))
return true;

if (ip6h->payload_len &&
pskb_trim_rcsum(skb, ntohs(ip6h->payload_len) + sizeof(*ip6h)))
return false;

ip6h = ipv6_hdr(skb);
thoff = ipv6_skip_exthdr(skb, ((u8*)(ip6h+1) - skb->data), &proto, &fo);
if (thoff < 0 || thoff >= skb->len || (fo & htons(~0x7)) != 0)
return false;

if (!nf_reject_verify_csum(proto))
return true;

return nf_ip6_checksum(skb, hook, thoff, proto) == 0;
}

static void nft_reject_br_send_v6_unreach(struct net *net,
struct sk_buff *oldskb,
const struct net_device *dev,
int hook, u8 code)
{
struct sk_buff *nskb;
struct ipv6hdr *nip6h;
struct icmp6hdr *icmp6h;
unsigned int len;

if (!nft_bridge_ip6hdr_validate(oldskb))
return;

/* Include "As much of invoking packet as possible without the ICMPv6
* packet exceeding the minimum IPv6 MTU" in the ICMP payload.
*/
len = min_t(unsigned int, 1220, oldskb->len);

if (!pskb_may_pull(oldskb, len))
return;

if (!reject6_br_csum_ok(oldskb, hook))
return;

nskb = alloc_skb(sizeof(struct ipv6hdr) + sizeof(struct icmp6hdr) +
LL_MAX_HEADER + len, GFP_ATOMIC);
nskb = nf_reject_skb_v6_unreach(net, oldskb, dev, hook, code);
if (!nskb)
return;

skb_reserve(nskb, LL_MAX_HEADER);
nip6h = nf_reject_ip6hdr_put(nskb, oldskb, IPPROTO_ICMPV6,
net->ipv6.devconf_all->hop_limit);

skb_reset_transport_header(nskb);
icmp6h = skb_put_zero(nskb, sizeof(struct icmp6hdr));
icmp6h->icmp6_type = ICMPV6_DEST_UNREACH;
icmp6h->icmp6_code = code;

skb_put_data(nskb, skb_network_header(oldskb), len);
nip6h->payload_len = htons(nskb->len - sizeof(struct ipv6hdr));

icmp6h->icmp6_cksum =
csum_ipv6_magic(&nip6h->saddr, &nip6h->daddr,
nskb->len - sizeof(struct ipv6hdr),
IPPROTO_ICMPV6,
csum_partial(icmp6h,
nskb->len - sizeof(struct ipv6hdr),
0));

nft_reject_br_push_etherhdr(oldskb, nskb);

br_forward(br_port_get_rcu(dev), nskb, false, true);
Expand Down
Loading

0 comments on commit fa538f7

Please sign in to comment.