Skip to content

Commit

Permalink
ipv6: Add fragment reporting to ipv6_skip_exthdr().
Browse files Browse the repository at this point in the history
While parsing through IPv6 extension headers, fragment headers are
skipped making them invisible to the caller.  This reports the
fragment offset of the last header in order to make it possible to
determine whether the packet is fragmented and, if so whether it is
a first or last fragment.

Signed-off-by: Jesse Gross <[email protected]>
  • Loading branch information
jessegross committed Dec 3, 2011
1 parent 396cf94 commit 75f2811
Show file tree
Hide file tree
Showing 17 changed files with 45 additions and 19 deletions.
2 changes: 1 addition & 1 deletion include/net/ipv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ extern void ipv6_push_frag_opts(struct sk_buff *skb,
u8 *proto);

extern int ipv6_skip_exthdr(const struct sk_buff *, int start,
u8 *nexthdrp);
u8 *nexthdrp, __be16 *frag_offp);

extern int ipv6_ext_hdr(u8 nexthdr);

Expand Down
3 changes: 2 additions & 1 deletion net/bridge/br_multicast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,7 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
const struct ipv6hdr *ip6h;
u8 icmp6_type;
u8 nexthdr;
__be16 frag_off;
unsigned len;
int offset;
int err;
Expand All @@ -1483,7 +1484,7 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
return -EINVAL;

nexthdr = ip6h->nexthdr;
offset = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr);
offset = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr, &frag_off);

if (offset < 0 || nexthdr != IPPROTO_ICMPV6)
return 0;
Expand Down
3 changes: 2 additions & 1 deletion net/bridge/netfilter/ebt_ip6.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ ebt_ip6_mt(const struct sk_buff *skb, struct xt_action_param *par)
return false;
if (info->bitmask & EBT_IP6_PROTO) {
uint8_t nexthdr = ih6->nexthdr;
__be16 frag_off;
int offset_ph;

offset_ph = ipv6_skip_exthdr(skb, sizeof(_ip6h), &nexthdr);
offset_ph = ipv6_skip_exthdr(skb, sizeof(_ip6h), &nexthdr, &frag_off);
if (offset_ph == -1)
return false;
if (FWINV(info->protocol != nexthdr, EBT_IP6_PROTO))
Expand Down
3 changes: 2 additions & 1 deletion net/bridge/netfilter/ebt_log.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ ebt_log_packet(u_int8_t pf, unsigned int hooknum,
const struct ipv6hdr *ih;
struct ipv6hdr _iph;
uint8_t nexthdr;
__be16 frag_off;
int offset_ph;

ih = skb_header_pointer(skb, 0, sizeof(_iph), &_iph);
Expand All @@ -123,7 +124,7 @@ ebt_log_packet(u_int8_t pf, unsigned int hooknum,
printk(" IPv6 SRC=%pI6 IPv6 DST=%pI6, IPv6 priority=0x%01X, Next Header=%d",
&ih->saddr, &ih->daddr, ih->priority, ih->nexthdr);
nexthdr = ih->nexthdr;
offset_ph = ipv6_skip_exthdr(skb, sizeof(_iph), &nexthdr);
offset_ph = ipv6_skip_exthdr(skb, sizeof(_iph), &nexthdr, &frag_off);
if (offset_ph == -1)
goto out;
print_ports(skb, nexthdr, offset_ph);
Expand Down
11 changes: 9 additions & 2 deletions net/ipv6/exthdrs_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,23 @@ int ipv6_ext_hdr(u8 nexthdr)
* it returns NULL.
* - First fragment header is skipped, not-first ones
* are considered as unparsable.
* - Reports the offset field of the final fragment header so it is
* possible to tell whether this is a first fragment, later fragment,
* or not fragmented.
* - ESP is unparsable for now and considered like
* normal payload protocol.
* - Note also special handling of AUTH header. Thanks to IPsec wizards.
*
* --ANK (980726)
*/

int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp)
int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
__be16 *frag_offp)
{
u8 nexthdr = *nexthdrp;

*frag_offp = 0;

while (ipv6_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
int hdrlen;
Expand All @@ -87,7 +93,8 @@ int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp)
if (fp == NULL)
return -1;

if (ntohs(*fp) & ~0x7)
*frag_offp = *fp;
if (ntohs(*frag_offp) & ~0x7)
break;
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
Expand Down
7 changes: 5 additions & 2 deletions net/ipv6/icmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,12 @@ static int is_ineligible(struct sk_buff *skb)
int ptr = (u8 *)(ipv6_hdr(skb) + 1) - skb->data;
int len = skb->len - ptr;
__u8 nexthdr = ipv6_hdr(skb)->nexthdr;
__be16 frag_off;

if (len < 0)
return 1;

ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr);
ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, &frag_off);
if (ptr < 0)
return 0;
if (nexthdr == IPPROTO_ICMPV6) {
Expand Down Expand Up @@ -596,14 +597,16 @@ static void icmpv6_notify(struct sk_buff *skb, u8 type, u8 code, __be32 info)
int inner_offset;
int hash;
u8 nexthdr;
__be16 frag_off;

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

nexthdr = ((struct ipv6hdr *)skb->data)->nexthdr;
if (ipv6_ext_hdr(nexthdr)) {
/* now skip over extension headers */
inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr);
inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr),
&nexthdr, &frag_off);
if (inner_offset<0)
return;
} else {
Expand Down
3 changes: 2 additions & 1 deletion net/ipv6/ip6_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ int ip6_mc_input(struct sk_buff *skb)
u8 *ptr = skb_network_header(skb) + opt->ra;
struct icmp6hdr *icmp6;
u8 nexthdr = hdr->nexthdr;
__be16 frag_off;
int offset;

/* Check if the value of Router Alert
Expand All @@ -293,7 +294,7 @@ int ip6_mc_input(struct sk_buff *skb)
goto out;
}
offset = ipv6_skip_exthdr(skb, sizeof(*hdr),
&nexthdr);
&nexthdr, &frag_off);
if (offset < 0)
goto out;

Expand Down
3 changes: 2 additions & 1 deletion net/ipv6/ip6_output.c
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,11 @@ static int ip6_forward_proxy_check(struct sk_buff *skb)
{
struct ipv6hdr *hdr = ipv6_hdr(skb);
u8 nexthdr = hdr->nexthdr;
__be16 frag_off;
int offset;

if (ipv6_ext_hdr(nexthdr)) {
offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr);
offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr, &frag_off);
if (offset < 0)
return 0;
} else
Expand Down
3 changes: 2 additions & 1 deletion net/ipv6/netfilter/ip6t_REJECT.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ static void send_reset(struct net *net, struct sk_buff *oldskb)
const __u8 tclass = DEFAULT_TOS_VALUE;
struct dst_entry *dst = NULL;
u8 proto;
__be16 frag_off;
struct flowi6 fl6;

if ((!(ipv6_addr_type(&oip6h->saddr) & IPV6_ADDR_UNICAST)) ||
Expand All @@ -58,7 +59,7 @@ static void send_reset(struct net *net, struct sk_buff *oldskb)
}

proto = oip6h->nexthdr;
tcphoff = ipv6_skip_exthdr(oldskb, ((u8*)(oip6h+1) - oldskb->data), &proto);
tcphoff = ipv6_skip_exthdr(oldskb, ((u8*)(oip6h+1) - oldskb->data), &proto, &frag_off);

if ((tcphoff < 0) || (tcphoff > oldskb->len)) {
pr_debug("Cannot get TCP header.\n");
Expand Down
4 changes: 3 additions & 1 deletion net/netfilter/ipset/ip_set_getport.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ ip_set_get_ip6_port(const struct sk_buff *skb, bool src,
{
int protoff;
u8 nexthdr;
__be16 frag_off;

nexthdr = ipv6_hdr(skb)->nexthdr;
protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr);
protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
&frag_off);
if (protoff < 0)
return false;

Expand Down
3 changes: 2 additions & 1 deletion net/netfilter/xt_AUDIT.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ static void audit_ip6(struct audit_buffer *ab, struct sk_buff *skb)
struct ipv6hdr _ip6h;
const struct ipv6hdr *ih;
u8 nexthdr;
__be16 frag_off;
int offset;

ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h);
Expand All @@ -108,7 +109,7 @@ static void audit_ip6(struct audit_buffer *ab, struct sk_buff *skb)

nexthdr = ih->nexthdr;
offset = ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h),
&nexthdr);
&nexthdr, &frag_off);

audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
&ih->saddr, &ih->daddr, nexthdr);
Expand Down
3 changes: 2 additions & 1 deletion net/netfilter/xt_TCPMSS.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,12 @@ tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
{
struct ipv6hdr *ipv6h = ipv6_hdr(skb);
u8 nexthdr;
__be16 frag_off;
int tcphoff;
int ret;

nexthdr = ipv6h->nexthdr;
tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
if (tcphoff < 0)
return NF_DROP;
ret = tcpmss_mangle_packet(skb, par->targinfo,
Expand Down
3 changes: 2 additions & 1 deletion net/netfilter/xt_TCPOPTSTRIP.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ tcpoptstrip_tg6(struct sk_buff *skb, const struct xt_action_param *par)
struct ipv6hdr *ipv6h = ipv6_hdr(skb);
int tcphoff;
u_int8_t nexthdr;
__be16 frag_off;

nexthdr = ipv6h->nexthdr;
tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
if (tcphoff < 0)
return NF_DROP;

Expand Down
3 changes: 2 additions & 1 deletion net/netfilter/xt_hashlimit.c
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ hashlimit_init_dst(const struct xt_hashlimit_htable *hinfo,
{
__be16 _ports[2], *ports;
u8 nexthdr;
__be16 frag_off;
int poff;

memset(dst, 0, sizeof(*dst));
Expand Down Expand Up @@ -480,7 +481,7 @@ hashlimit_init_dst(const struct xt_hashlimit_htable *hinfo,
(XT_HASHLIMIT_HASH_DPT | XT_HASHLIMIT_HASH_SPT)))
return 0;
nexthdr = ipv6_hdr(skb)->nexthdr;
protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr);
protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, &frag_off);
if ((int)protoff < 0)
return -1;
break;
Expand Down
4 changes: 3 additions & 1 deletion net/netfilter/xt_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ extract_icmp6_fields(const struct sk_buff *skb,
struct icmp6hdr *icmph, _icmph;
__be16 *ports, _ports[2];
u8 inside_nexthdr;
__be16 inside_fragoff;
int inside_hdrlen;

icmph = skb_header_pointer(skb, outside_hdrlen,
Expand All @@ -229,7 +230,8 @@ extract_icmp6_fields(const struct sk_buff *skb,
return 1;
inside_nexthdr = inside_iph->nexthdr;

inside_hdrlen = ipv6_skip_exthdr(skb, outside_hdrlen + sizeof(_icmph) + sizeof(_inside_iph), &inside_nexthdr);
inside_hdrlen = ipv6_skip_exthdr(skb, outside_hdrlen + sizeof(_icmph) + sizeof(_inside_iph),
&inside_nexthdr, &inside_fragoff);
if (inside_hdrlen < 0)
return 1; /* hjm: Packet has no/incomplete transport layer headers. */

Expand Down
3 changes: 2 additions & 1 deletion security/lsm_audit.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb,
int offset, ret = 0;
struct ipv6hdr *ip6;
u8 nexthdr;
__be16 frag_off;

ip6 = ipv6_hdr(skb);
if (ip6 == NULL)
Expand All @@ -126,7 +127,7 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb,
offset = skb_network_offset(skb);
offset += sizeof(*ip6);
nexthdr = ip6->nexthdr;
offset = ipv6_skip_exthdr(skb, offset, &nexthdr);
offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off);
if (offset < 0)
return 0;
if (proto)
Expand Down
3 changes: 2 additions & 1 deletion security/selinux/hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -3561,6 +3561,7 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb,
u8 nexthdr;
int ret = -EINVAL, offset;
struct ipv6hdr _ipv6h, *ip6;
__be16 frag_off;

offset = skb_network_offset(skb);
ip6 = skb_header_pointer(skb, offset, sizeof(_ipv6h), &_ipv6h);
Expand All @@ -3573,7 +3574,7 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb,

nexthdr = ip6->nexthdr;
offset += sizeof(_ipv6h);
offset = ipv6_skip_exthdr(skb, offset, &nexthdr);
offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off);
if (offset < 0)
goto out;

Expand Down

0 comments on commit 75f2811

Please sign in to comment.