Merge branch 'flow_dissector-fixes'

Tom Herbert says:

====================
flow_dissector: Flow dissector fixes

This patch set fixes some basic issues with __skb_flow_dissect function.

Items addressed:
  - Cleanup control flow in the function; in particular eliminate a
    bunch of goto's and implement a simplified control flow model
  - Add limits for number of encapsulations and headers that can be
    dissected

v2:
  - Simplify the logic for limits on flow dissection. Just set the
    limit based on the number of headers the flow dissector can
    processes. The accounted headers includes encapsulation headers,
    extension headers, or other shim headers.

Tested:

Ran normal traffic, GUE, and VXLAN traffic.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2017-09-05 11:40:08 -07:00
Родитель 2c08ab3f25 1eed4dfb81
Коммит c4492d8ade
2 изменённых файлов: 172 добавлений и 78 удалений

Просмотреть файл

@ -19,6 +19,14 @@ struct flow_dissector_key_control {
#define FLOW_DIS_FIRST_FRAG BIT(1) #define FLOW_DIS_FIRST_FRAG BIT(1)
#define FLOW_DIS_ENCAPSULATION BIT(2) #define FLOW_DIS_ENCAPSULATION BIT(2)
enum flow_dissect_ret {
FLOW_DISSECT_RET_OUT_GOOD,
FLOW_DISSECT_RET_OUT_BAD,
FLOW_DISSECT_RET_PROTO_AGAIN,
FLOW_DISSECT_RET_IPPROTO_AGAIN,
FLOW_DISSECT_RET_CONTINUE,
};
/** /**
* struct flow_dissector_key_basic: * struct flow_dissector_key_basic:
* @thoff: Transport header offset * @thoff: Transport header offset

Просмотреть файл

@ -115,12 +115,6 @@ __be32 __skb_flow_get_ports(const struct sk_buff *skb, int thoff, u8 ip_proto,
} }
EXPORT_SYMBOL(__skb_flow_get_ports); EXPORT_SYMBOL(__skb_flow_get_ports);
enum flow_dissect_ret {
FLOW_DISSECT_RET_OUT_GOOD,
FLOW_DISSECT_RET_OUT_BAD,
FLOW_DISSECT_RET_OUT_PROTO_AGAIN,
};
static enum flow_dissect_ret static enum flow_dissect_ret
__skb_flow_dissect_mpls(const struct sk_buff *skb, __skb_flow_dissect_mpls(const struct sk_buff *skb,
struct flow_dissector *flow_dissector, struct flow_dissector *flow_dissector,
@ -341,7 +335,7 @@ __skb_flow_dissect_gre(const struct sk_buff *skb,
if (flags & FLOW_DISSECTOR_F_STOP_AT_ENCAP) if (flags & FLOW_DISSECTOR_F_STOP_AT_ENCAP)
return FLOW_DISSECT_RET_OUT_GOOD; return FLOW_DISSECT_RET_OUT_GOOD;
return FLOW_DISSECT_RET_OUT_PROTO_AGAIN; return FLOW_DISSECT_RET_PROTO_AGAIN;
} }
static void static void
@ -402,6 +396,18 @@ __skb_flow_dissect_ipv6(const struct sk_buff *skb,
key_ip->ttl = iph->hop_limit; key_ip->ttl = iph->hop_limit;
} }
/* Maximum number of protocol headers that can be parsed in
* __skb_flow_dissect
*/
#define MAX_FLOW_DISSECT_HDRS 15
static bool skb_flow_dissect_allowed(int *num_hdrs)
{
++*num_hdrs;
return (*num_hdrs <= MAX_FLOW_DISSECT_HDRS);
}
/** /**
* __skb_flow_dissect - extract the flow_keys struct and return it * __skb_flow_dissect - extract the flow_keys struct and return it
* @skb: sk_buff to extract the flow from, can be NULL if the rest are specified * @skb: sk_buff to extract the flow from, can be NULL if the rest are specified
@ -431,7 +437,9 @@ bool __skb_flow_dissect(const struct sk_buff *skb,
struct flow_dissector_key_icmp *key_icmp; struct flow_dissector_key_icmp *key_icmp;
struct flow_dissector_key_tags *key_tags; struct flow_dissector_key_tags *key_tags;
struct flow_dissector_key_vlan *key_vlan; struct flow_dissector_key_vlan *key_vlan;
enum flow_dissect_ret fdret;
bool skip_vlan = false; bool skip_vlan = false;
int num_hdrs = 0;
u8 ip_proto = 0; u8 ip_proto = 0;
bool ret; bool ret;
@ -482,14 +490,19 @@ bool __skb_flow_dissect(const struct sk_buff *skb,
} }
proto_again: proto_again:
fdret = FLOW_DISSECT_RET_CONTINUE;
switch (proto) { switch (proto) {
case htons(ETH_P_IP): { case htons(ETH_P_IP): {
const struct iphdr *iph; const struct iphdr *iph;
struct iphdr _iph; struct iphdr _iph;
ip:
iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph); iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph);
if (!iph || iph->ihl < 5) if (!iph || iph->ihl < 5) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
nhoff += iph->ihl * 4; nhoff += iph->ihl * 4;
ip_proto = iph->protocol; ip_proto = iph->protocol;
@ -509,19 +522,25 @@ ip:
key_control->flags |= FLOW_DIS_IS_FRAGMENT; key_control->flags |= FLOW_DIS_IS_FRAGMENT;
if (iph->frag_off & htons(IP_OFFSET)) { if (iph->frag_off & htons(IP_OFFSET)) {
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
} else { } else {
key_control->flags |= FLOW_DIS_FIRST_FRAG; key_control->flags |= FLOW_DIS_FIRST_FRAG;
if (!(flags & FLOW_DISSECTOR_F_PARSE_1ST_FRAG)) if (!(flags &
goto out_good; FLOW_DISSECTOR_F_PARSE_1ST_FRAG)) {
fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
}
} }
} }
__skb_flow_dissect_ipv4(skb, flow_dissector, __skb_flow_dissect_ipv4(skb, flow_dissector,
target_container, data, iph); target_container, data, iph);
if (flags & FLOW_DISSECTOR_F_STOP_AT_L3) if (flags & FLOW_DISSECTOR_F_STOP_AT_L3) {
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
}
break; break;
} }
@ -529,10 +548,11 @@ ip:
const struct ipv6hdr *iph; const struct ipv6hdr *iph;
struct ipv6hdr _iph; struct ipv6hdr _iph;
ipv6:
iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph); iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph);
if (!iph) if (!iph) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
ip_proto = iph->nexthdr; ip_proto = iph->nexthdr;
nhoff += sizeof(struct ipv6hdr); nhoff += sizeof(struct ipv6hdr);
@ -561,15 +581,17 @@ ipv6:
target_container); target_container);
key_tags->flow_label = ntohl(flow_label); key_tags->flow_label = ntohl(flow_label);
} }
if (flags & FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL) if (flags & FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL) {
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
}
} }
__skb_flow_dissect_ipv6(skb, flow_dissector, __skb_flow_dissect_ipv6(skb, flow_dissector,
target_container, data, iph); target_container, data, iph);
if (flags & FLOW_DISSECTOR_F_STOP_AT_L3) if (flags & FLOW_DISSECTOR_F_STOP_AT_L3)
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break; break;
} }
@ -585,12 +607,17 @@ ipv6:
if (!vlan_tag_present || eth_type_vlan(skb->protocol)) { if (!vlan_tag_present || eth_type_vlan(skb->protocol)) {
vlan = __skb_header_pointer(skb, nhoff, sizeof(_vlan), vlan = __skb_header_pointer(skb, nhoff, sizeof(_vlan),
data, hlen, &_vlan); data, hlen, &_vlan);
if (!vlan) if (!vlan) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
proto = vlan->h_vlan_encapsulated_proto; proto = vlan->h_vlan_encapsulated_proto;
nhoff += sizeof(*vlan); nhoff += sizeof(*vlan);
if (skip_vlan) if (skip_vlan) {
goto proto_again; fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
}
} }
skip_vlan = true; skip_vlan = true;
@ -613,7 +640,8 @@ ipv6:
} }
} }
goto proto_again; fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
} }
case htons(ETH_P_PPP_SES): { case htons(ETH_P_PPP_SES): {
struct { struct {
@ -621,18 +649,27 @@ ipv6:
__be16 proto; __be16 proto;
} *hdr, _hdr; } *hdr, _hdr;
hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr); hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr);
if (!hdr) if (!hdr) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
proto = hdr->proto; proto = hdr->proto;
nhoff += PPPOE_SES_HLEN; nhoff += PPPOE_SES_HLEN;
switch (proto) { switch (proto) {
case htons(PPP_IP): case htons(PPP_IP):
goto ip; proto = htons(ETH_P_IP);
fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
case htons(PPP_IPV6): case htons(PPP_IPV6):
goto ipv6; proto = htons(ETH_P_IPV6);
fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
default: default:
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
} }
break;
} }
case htons(ETH_P_TIPC): { case htons(ETH_P_TIPC): {
struct { struct {
@ -640,8 +677,10 @@ ipv6:
__be32 srcnode; __be32 srcnode;
} *hdr, _hdr; } *hdr, _hdr;
hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr); hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr);
if (!hdr) if (!hdr) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
if (dissector_uses_key(flow_dissector, if (dissector_uses_key(flow_dissector,
FLOW_DISSECTOR_KEY_TIPC_ADDRS)) { FLOW_DISSECTOR_KEY_TIPC_ADDRS)) {
@ -651,56 +690,64 @@ ipv6:
key_addrs->tipcaddrs.srcnode = hdr->srcnode; key_addrs->tipcaddrs.srcnode = hdr->srcnode;
key_control->addr_type = FLOW_DISSECTOR_KEY_TIPC_ADDRS; key_control->addr_type = FLOW_DISSECTOR_KEY_TIPC_ADDRS;
} }
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
} }
case htons(ETH_P_MPLS_UC): case htons(ETH_P_MPLS_UC):
case htons(ETH_P_MPLS_MC): case htons(ETH_P_MPLS_MC):
mpls: fdret = __skb_flow_dissect_mpls(skb, flow_dissector,
switch (__skb_flow_dissect_mpls(skb, flow_dissector,
target_container, data, target_container, data,
nhoff, hlen)) { nhoff, hlen);
case FLOW_DISSECT_RET_OUT_GOOD: break;
goto out_good;
case FLOW_DISSECT_RET_OUT_BAD:
default:
goto out_bad;
}
case htons(ETH_P_FCOE): case htons(ETH_P_FCOE):
if ((hlen - nhoff) < FCOE_HEADER_LEN) if ((hlen - nhoff) < FCOE_HEADER_LEN) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
nhoff += FCOE_HEADER_LEN; nhoff += FCOE_HEADER_LEN;
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
case htons(ETH_P_ARP): case htons(ETH_P_ARP):
case htons(ETH_P_RARP): case htons(ETH_P_RARP):
switch (__skb_flow_dissect_arp(skb, flow_dissector, fdret = __skb_flow_dissect_arp(skb, flow_dissector,
target_container, data, target_container, data,
nhoff, hlen)) { nhoff, hlen);
break;
default:
fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
/* Process result of proto processing */
switch (fdret) {
case FLOW_DISSECT_RET_OUT_GOOD: case FLOW_DISSECT_RET_OUT_GOOD:
goto out_good; goto out_good;
case FLOW_DISSECT_RET_PROTO_AGAIN:
if (skb_flow_dissect_allowed(&num_hdrs))
goto proto_again;
goto out_good;
case FLOW_DISSECT_RET_CONTINUE:
case FLOW_DISSECT_RET_IPPROTO_AGAIN:
break;
case FLOW_DISSECT_RET_OUT_BAD: case FLOW_DISSECT_RET_OUT_BAD:
default: default:
goto out_bad; goto out_bad;
} }
default:
goto out_bad;
}
ip_proto_again: ip_proto_again:
fdret = FLOW_DISSECT_RET_CONTINUE;
switch (ip_proto) { switch (ip_proto) {
case IPPROTO_GRE: case IPPROTO_GRE:
switch (__skb_flow_dissect_gre(skb, key_control, flow_dissector, fdret = __skb_flow_dissect_gre(skb, key_control, flow_dissector,
target_container, data, target_container, data,
&proto, &nhoff, &hlen, flags)) { &proto, &nhoff, &hlen, flags);
case FLOW_DISSECT_RET_OUT_GOOD: break;
goto out_good;
case FLOW_DISSECT_RET_OUT_BAD:
goto out_bad;
case FLOW_DISSECT_RET_OUT_PROTO_AGAIN:
goto proto_again;
}
case NEXTHDR_HOP: case NEXTHDR_HOP:
case NEXTHDR_ROUTING: case NEXTHDR_ROUTING:
case NEXTHDR_DEST: { case NEXTHDR_DEST: {
@ -711,13 +758,16 @@ ip_proto_again:
opthdr = __skb_header_pointer(skb, nhoff, sizeof(_opthdr), opthdr = __skb_header_pointer(skb, nhoff, sizeof(_opthdr),
data, hlen, &_opthdr); data, hlen, &_opthdr);
if (!opthdr) if (!opthdr) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
ip_proto = opthdr[0]; ip_proto = opthdr[0];
nhoff += (opthdr[1] + 1) << 3; nhoff += (opthdr[1] + 1) << 3;
goto ip_proto_again; fdret = FLOW_DISSECT_RET_IPPROTO_AGAIN;
break;
} }
case NEXTHDR_FRAGMENT: { case NEXTHDR_FRAGMENT: {
struct frag_hdr _fh, *fh; struct frag_hdr _fh, *fh;
@ -728,8 +778,10 @@ ip_proto_again:
fh = __skb_header_pointer(skb, nhoff, sizeof(_fh), fh = __skb_header_pointer(skb, nhoff, sizeof(_fh),
data, hlen, &_fh); data, hlen, &_fh);
if (!fh) if (!fh) {
goto out_bad; fdret = FLOW_DISSECT_RET_OUT_BAD;
break;
}
key_control->flags |= FLOW_DIS_IS_FRAGMENT; key_control->flags |= FLOW_DIS_IS_FRAGMENT;
@ -738,34 +790,50 @@ ip_proto_again:
if (!(fh->frag_off & htons(IP6_OFFSET))) { if (!(fh->frag_off & htons(IP6_OFFSET))) {
key_control->flags |= FLOW_DIS_FIRST_FRAG; key_control->flags |= FLOW_DIS_FIRST_FRAG;
if (flags & FLOW_DISSECTOR_F_PARSE_1ST_FRAG) if (flags & FLOW_DISSECTOR_F_PARSE_1ST_FRAG) {
goto ip_proto_again; fdret = FLOW_DISSECT_RET_IPPROTO_AGAIN;
break;
} }
goto out_good; }
fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
} }
case IPPROTO_IPIP: case IPPROTO_IPIP:
proto = htons(ETH_P_IP); proto = htons(ETH_P_IP);
key_control->flags |= FLOW_DIS_ENCAPSULATION; key_control->flags |= FLOW_DIS_ENCAPSULATION;
if (flags & FLOW_DISSECTOR_F_STOP_AT_ENCAP) if (flags & FLOW_DISSECTOR_F_STOP_AT_ENCAP) {
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
}
fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
goto ip;
case IPPROTO_IPV6: case IPPROTO_IPV6:
proto = htons(ETH_P_IPV6); proto = htons(ETH_P_IPV6);
key_control->flags |= FLOW_DIS_ENCAPSULATION; key_control->flags |= FLOW_DIS_ENCAPSULATION;
if (flags & FLOW_DISSECTOR_F_STOP_AT_ENCAP) if (flags & FLOW_DISSECTOR_F_STOP_AT_ENCAP) {
goto out_good; fdret = FLOW_DISSECT_RET_OUT_GOOD;
break;
}
fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
goto ipv6;
case IPPROTO_MPLS: case IPPROTO_MPLS:
proto = htons(ETH_P_MPLS_UC); proto = htons(ETH_P_MPLS_UC);
goto mpls; fdret = FLOW_DISSECT_RET_PROTO_AGAIN;
break;
case IPPROTO_TCP: case IPPROTO_TCP:
__skb_flow_dissect_tcp(skb, flow_dissector, target_container, __skb_flow_dissect_tcp(skb, flow_dissector, target_container,
data, nhoff, hlen); data, nhoff, hlen);
break; break;
default: default:
break; break;
} }
@ -787,6 +855,24 @@ ip_proto_again:
key_icmp->icmp = skb_flow_get_be16(skb, nhoff, data, hlen); key_icmp->icmp = skb_flow_get_be16(skb, nhoff, data, hlen);
} }
/* Process result of IP proto processing */
switch (fdret) {
case FLOW_DISSECT_RET_PROTO_AGAIN:
if (skb_flow_dissect_allowed(&num_hdrs))
goto proto_again;
break;
case FLOW_DISSECT_RET_IPPROTO_AGAIN:
if (skb_flow_dissect_allowed(&num_hdrs))
goto ip_proto_again;
break;
case FLOW_DISSECT_RET_OUT_GOOD:
case FLOW_DISSECT_RET_CONTINUE:
break;
case FLOW_DISSECT_RET_OUT_BAD:
default:
goto out_bad;
}
out_good: out_good:
ret = true; ret = true;