Extend pedit to enable the user setting offset relative to network headers. This change would enable to work with more complex header schemes (vs the simple IPv4 case) where setting a fixed offset relative to the network header is not enough. It is also forward looking to enable hardware offloading of pedit.
The header type is embedded in the 8 MSB of the u32 key->shift which were never used till now. Therefore backward compatibility is being kept. Usage example: $ tc filter add dev enp0s9 protocol ip parent ffff: \ flower \ ip_proto tcp \ dst_port 80 \ action pedit munge tcp dport set 8080 pipe \ action mirred egress redirect dev veth0 Will forward tcp port whose original dest port is 80, while modifying the destination port to 8080. Signed-off-by: Amir Vadai <a...@vadai.me> Reviewed-by: Or Gerlitz <ogerl...@mellanox.com> --- include/uapi/linux/tc_act/tc_pedit.h | 17 ++++++++++ net/sched/act_pedit.c | 65 +++++++++++++++++++++++++++++------- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/include/uapi/linux/tc_act/tc_pedit.h b/include/uapi/linux/tc_act/tc_pedit.h index 6389959a5157..604e6729ad38 100644 --- a/include/uapi/linux/tc_act/tc_pedit.h +++ b/include/uapi/linux/tc_act/tc_pedit.h @@ -32,4 +32,21 @@ struct tc_pedit_sel { }; #define tc_pedit tc_pedit_sel +#define PEDIT_TYPE_SHIFT 24 +#define PEDIT_TYPE_MASK 0xff + +#define PEDIT_TYPE_GET(_val) \ + (((_val) >> PEDIT_TYPE_SHIFT) & PEDIT_TYPE_MASK) +#define PEDIT_SHIFT_GET(_val) ((_val) & 0xff) + +enum pedit_header_type { + PEDIT_HDR_TYPE_RAW = 0, + + PEDIT_HDR_TYPE_ETH = 1, + PEDIT_HDR_TYPE_IP4 = 2, + PEDIT_HDR_TYPE_IP6 = 3, + PEDIT_HDR_TYPE_TCP = 4, + PEDIT_HDR_TYPE_UDP = 5, +}; + #endif diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c index b27c4daec88f..4b9c7184c752 100644 --- a/net/sched/act_pedit.c +++ b/net/sched/act_pedit.c @@ -119,18 +119,45 @@ static bool offset_valid(struct sk_buff *skb, int offset) return true; } +static int pedit_skb_hdr_offset(struct sk_buff *skb, + enum pedit_header_type htype, int *hoffset) +{ + int ret = -1; + + switch (htype) { + case PEDIT_HDR_TYPE_ETH: + if (skb_mac_header_was_set(skb)) { + *hoffset = skb_mac_offset(skb); + ret = 0; + } + break; + case PEDIT_HDR_TYPE_RAW: + case PEDIT_HDR_TYPE_IP4: + case PEDIT_HDR_TYPE_IP6: + *hoffset = skb_network_offset(skb); + ret = 0; + break; + case PEDIT_HDR_TYPE_TCP: + case PEDIT_HDR_TYPE_UDP: + if (skb_transport_header_was_set(skb)) { + *hoffset = skb_transport_offset(skb); + ret = 0; + } + break; + }; + + return ret; +} + static int tcf_pedit(struct sk_buff *skb, const struct tc_action *a, struct tcf_result *res) { struct tcf_pedit *p = to_pedit(a); int i; - unsigned int off; if (skb_unclone(skb, GFP_ATOMIC)) return p->tcf_action; - off = skb_network_offset(skb); - spin_lock(&p->tcf_lock); tcf_lastuse_update(&p->tcf_tm); @@ -141,20 +168,32 @@ static int tcf_pedit(struct sk_buff *skb, const struct tc_action *a, for (i = p->tcfp_nkeys; i > 0; i--, tkey++) { u32 *ptr, _data; int offset = tkey->off; + int hoffset; + int rc; + enum pedit_header_type htype = + PEDIT_TYPE_GET(tkey->shift); + + rc = pedit_skb_hdr_offset(skb, htype, &hoffset); + if (rc) { + pr_info("tc filter pedit bad header type specified (0x%x)\n", + htype); + goto bad; + } if (tkey->offmask) { char *d, _d; - if (!offset_valid(skb, off + tkey->at)) { + if (!offset_valid(skb, hoffset + tkey->at)) { pr_info("tc filter pedit 'at' offset %d out of bounds\n", - off + tkey->at); + hoffset + tkey->at); goto bad; } - d = skb_header_pointer(skb, off + tkey->at, 1, - &_d); + d = skb_header_pointer(skb, + hoffset + tkey->at, + 1, &_d); if (!d) goto bad; - offset += (*d & tkey->offmask) >> tkey->shift; + offset += (*d & tkey->offmask) >> PEDIT_SHIFT_GET(tkey->shift); } if (offset % 4) { @@ -163,19 +202,21 @@ static int tcf_pedit(struct sk_buff *skb, const struct tc_action *a, goto bad; } - if (!offset_valid(skb, off + offset)) { + if (!offset_valid(skb, hoffset + offset)) { pr_info("tc filter pedit offset %d out of bounds\n", - offset); + hoffset + offset); goto bad; } - ptr = skb_header_pointer(skb, off + offset, 4, &_data); + ptr = skb_header_pointer(skb, + hoffset + offset, + 4, &_data); if (!ptr) goto bad; /* just do it, baby */ *ptr = ((*ptr & tkey->mask) ^ tkey->val); if (ptr == &_data) - skb_store_bits(skb, off + offset, ptr, 4); + skb_store_bits(skb, hoffset + offset, ptr, 4); } goto done; -- 2.11.0