* [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support @ 2026-04-28 6:47 Qingfang Deng 2026-04-28 6:47 ` [PATCH net-next v7 2/2] selftests: net: test PPPoE packets in gro.sh Qingfang Deng 2026-04-30 10:01 ` [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support Paolo Abeni 0 siblings, 2 replies; 4+ messages in thread From: Qingfang Deng @ 2026-04-28 6:47 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Simon Horman, Qingfang Deng, Kees Cook, Guillaume Nault, Eric Woudstra, Felix Fietkau, netdev, linux-kernel Cc: linux-ppp, Pablo Neira Ayuso From: Felix Fietkau <nbd@nbd.name> Only handles packets where the pppoe header length field matches the exact packet length. Significantly improves rx throughput. When running NAT traffic through a MediaTek MT7621 devices from a host behind PPPoE to a host directly connected via ethernet, the TCP throughput that the device is able to handle improves from ~130 Mbit/s to ~630 Mbit/s, using fraglist GRO. Signed-off-by: Felix Fietkau <nbd@nbd.name> Signed-off-by: Qingfang Deng <qingfang.deng@linux.dev> --- v7: - Use PPPOE_SES_HLEN macro instead of +2 magic number v6: https://lore.kernel.org/netdev/20260326081127.61229-1-dqfext@gmail.com - avoid phdr->length field overflow - restore skb_is_gso() check - do not register GRO if INET=n - do not check for PPP_IPV6 if IPV6=n - tail call gro_complete drivers/net/ppp/pppoe.c | 165 +++++++++++++++++++++++++++++++++++++++- net/ipv4/af_inet.c | 2 + net/ipv6/ip6_offload.c | 2 + 3 files changed, 168 insertions(+), 1 deletion(-) diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c index bdd61c504a1c..363204e0c49a 100644 --- a/drivers/net/ppp/pppoe.c +++ b/drivers/net/ppp/pppoe.c @@ -77,6 +77,7 @@ #include <net/net_namespace.h> #include <net/netns/generic.h> #include <net/sock.h> +#include <net/gro.h> #include <linux/uaccess.h> @@ -409,7 +410,7 @@ static int pppoe_rcv(struct sk_buff *skb, struct net_device *dev, if (ppp_skb_is_compressed_proto(skb)) goto drop; - if (pskb_trim_rcsum(skb, len)) + if (!skb_is_gso(skb) && pskb_trim_rcsum(skb, len)) goto drop; ph = pppoe_hdr(skb); @@ -1103,6 +1104,164 @@ static struct pernet_operations pppoe_net_ops = { .size = sizeof(struct pppoe_net), }; +static u16 +compare_pppoe_header(const struct pppoe_hdr *phdr, + const struct pppoe_hdr *phdr2) +{ + __be16 proto = *(const __be16 *)(phdr + 1); + __be16 proto2 = *(const __be16 *)(phdr2 + 1); + + return (__force u16)((phdr->sid ^ phdr2->sid) | (proto ^ proto2)); +} + +static __be16 pppoe_hdr_proto(const struct pppoe_hdr *phdr) +{ + __be16 proto = *(const __be16 *)(phdr + 1); + + switch (proto) { + case cpu_to_be16(PPP_IP): + return cpu_to_be16(ETH_P_IP); +#if IS_ENABLED(CONFIG_IPV6) + case cpu_to_be16(PPP_IPV6): + return cpu_to_be16(ETH_P_IPV6); +#endif + default: + return 0; + } +} + +static struct sk_buff *pppoe_gro_receive(struct list_head *head, + struct sk_buff *skb) +{ + const struct packet_offload *ptype; + unsigned int hlen, off_pppoe; + const struct pppoe_hdr *phdr; + struct sk_buff *pp = NULL; + struct sk_buff *p; + int flush = 1; + __be16 type; + + off_pppoe = skb_gro_offset(skb); + hlen = off_pppoe + PPPOE_SES_HLEN; + phdr = skb_gro_header(skb, hlen, off_pppoe); + if (unlikely(!phdr)) + goto out; + + /* filter for session packets (type:1, ver:1, code:0) */ + if (*(const __be16 *)phdr != cpu_to_be16(0x1100)) + goto out; + + /* ignore packets with padding or invalid length */ + if (skb_gro_len(skb) != be16_to_cpu(phdr->length) + sizeof(*phdr)) + goto out; + + type = pppoe_hdr_proto(phdr); + ptype = gro_find_receive_by_type(type); + if (!ptype) + goto out; + + flush = 0; + + list_for_each_entry(p, head, list) { + const struct pppoe_hdr *phdr2; + + if (!NAPI_GRO_CB(p)->same_flow) + continue; + + phdr2 = (const struct pppoe_hdr *)(p->data + off_pppoe); + if (compare_pppoe_header(phdr, phdr2)) + NAPI_GRO_CB(p)->same_flow = 0; + } + + skb_gro_pull(skb, PPPOE_SES_HLEN); + skb_gro_postpull_rcsum(skb, phdr, PPPOE_SES_HLEN); + + pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive, + ipv6_gro_receive, inet_gro_receive, + head, skb); + +out: + skb_gro_flush_final(skb, pp, flush); + + return pp; +} + +static int pppoe_gro_complete(struct sk_buff *skb, int nhoff) +{ + struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb->data + nhoff); + __be16 type = pppoe_hdr_proto(phdr); + struct packet_offload *ptype; + unsigned int len; + + ptype = gro_find_complete_by_type(type); + if (!ptype) + return -ENOENT; + + len = skb->len - (nhoff + sizeof(*phdr)); + len = min(len, 0xFFFFU); + phdr->length = cpu_to_be16(len); + + return INDIRECT_CALL_INET(ptype->callbacks.gro_complete, + ipv6_gro_complete, inet_gro_complete, + skb, nhoff + PPPOE_SES_HLEN); +} + +static struct sk_buff *pppoe_gso_segment(struct sk_buff *skb, + netdev_features_t features) +{ + struct sk_buff *segs = ERR_PTR(-EINVAL); + u16 mac_offset = skb->mac_header; + struct packet_offload *ptype; + u16 mac_len = skb->mac_len; + struct pppoe_hdr *phdr; + __be16 orig_type, type; + int len, nhoff; + + skb_reset_network_header(skb); + nhoff = skb_network_header(skb) - skb_mac_header(skb); + + if (unlikely(!pskb_may_pull(skb, PPPOE_SES_HLEN))) + goto out; + + phdr = (struct pppoe_hdr *)skb_network_header(skb); + type = pppoe_hdr_proto(phdr); + ptype = gro_find_complete_by_type(type); + if (!ptype) + goto out; + + orig_type = skb->protocol; + __skb_pull(skb, PPPOE_SES_HLEN); + segs = ptype->callbacks.gso_segment(skb, features); + if (IS_ERR_OR_NULL(segs)) { + skb_gso_error_unwind(skb, orig_type, PPPOE_SES_HLEN, mac_offset, + mac_len); + goto out; + } + + skb = segs; + do { + phdr = (struct pppoe_hdr *)(skb_mac_header(skb) + nhoff); + len = skb->len - (nhoff + sizeof(*phdr)); + phdr->length = cpu_to_be16(len); + skb->network_header = (u8 *)phdr - skb->head; + skb->protocol = orig_type; + skb_reset_mac_len(skb); + } while ((skb = skb->next)); + +out: + return segs; +} + +static struct packet_offload pppoe_packet_offload __read_mostly = { + .type = cpu_to_be16(ETH_P_PPP_SES), + .priority = 20, + .callbacks = { + .gro_receive = pppoe_gro_receive, + .gro_complete = pppoe_gro_complete, + .gso_segment = pppoe_gso_segment, + }, +}; + static int __init pppoe_init(void) { int err; @@ -1119,6 +1278,8 @@ static int __init pppoe_init(void) if (err) goto out_unregister_pppoe_proto; + if (IS_ENABLED(CONFIG_INET)) + dev_add_offload(&pppoe_packet_offload); dev_add_pack(&pppoes_ptype); dev_add_pack(&pppoed_ptype); register_netdevice_notifier(&pppoe_notifier); @@ -1138,6 +1299,8 @@ static void __exit pppoe_exit(void) unregister_netdevice_notifier(&pppoe_notifier); dev_remove_pack(&pppoed_ptype); dev_remove_pack(&pppoes_ptype); + if (IS_ENABLED(CONFIG_INET)) + dev_remove_offload(&pppoe_packet_offload); unregister_pppox_proto(PX_PROTO_OE); proto_unregister(&pppoe_sk_proto); unregister_pernet_device(&pppoe_net_ops); diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 0e62032e76b1..cbac072633bb 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -1540,6 +1540,7 @@ struct sk_buff *inet_gro_receive(struct list_head *head, struct sk_buff *skb) return pp; } +EXPORT_INDIRECT_CALLABLE(inet_gro_receive); static struct sk_buff *ipip_gro_receive(struct list_head *head, struct sk_buff *skb) @@ -1625,6 +1626,7 @@ int inet_gro_complete(struct sk_buff *skb, int nhoff) out: return err; } +EXPORT_INDIRECT_CALLABLE(inet_gro_complete); static int ipip_gro_complete(struct sk_buff *skb, int nhoff) { diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c index d8072ad6b8c4..78f50c93c536 100644 --- a/net/ipv6/ip6_offload.c +++ b/net/ipv6/ip6_offload.c @@ -297,6 +297,7 @@ INDIRECT_CALLABLE_SCOPE struct sk_buff *ipv6_gro_receive(struct list_head *head, return pp; } +EXPORT_INDIRECT_CALLABLE(ipv6_gro_receive); static struct sk_buff *sit_ip6ip6_gro_receive(struct list_head *head, struct sk_buff *skb) @@ -359,6 +360,7 @@ INDIRECT_CALLABLE_SCOPE int ipv6_gro_complete(struct sk_buff *skb, int nhoff) out: return err; } +EXPORT_INDIRECT_CALLABLE(ipv6_gro_complete); static int sit_gro_complete(struct sk_buff *skb, int nhoff) { -- 2.43.0 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH net-next v7 2/2] selftests: net: test PPPoE packets in gro.sh 2026-04-28 6:47 [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support Qingfang Deng @ 2026-04-28 6:47 ` Qingfang Deng 2026-04-30 10:04 ` Paolo Abeni 2026-04-30 10:01 ` [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support Paolo Abeni 1 sibling, 1 reply; 4+ messages in thread From: Qingfang Deng @ 2026-04-28 6:47 UTC (permalink / raw) To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman, Willem de Bruijn, Petr Machata, Anubhav Singh, Richard Gobert, netdev, linux-kselftest, linux-kernel Cc: linux-ppp, Pablo Neira Ayuso, Qingfang Deng Add PPPoE test-cases to the GRO selftest. Only run a subset of common_tests to avoid changing the hardcoded L3 offsets everywhere. Add a new "pppoe_sid" test case to verify that packets with different PPPoE session IDs are correctly identified as separate flows and not coalesced. Signed-off-by: Qingfang Deng <qingfang.deng@linux.dev> --- v7: - Do not run all the tests for PPPoE - Add a new test for PPPoE v6: https://lore.kernel.org/netdev/20260326081127.61229-2-dqfext@gmail.com tools/testing/selftests/drivers/net/config | 2 + tools/testing/selftests/drivers/net/gro.py | 11 +++ tools/testing/selftests/net/lib/gro.c | 99 ++++++++++++++++++---- 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index fd16994366f4..07e386895b94 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -8,5 +8,7 @@ CONFIG_NETCONSOLE=m CONFIG_NETCONSOLE_DYNAMIC=y CONFIG_NETCONSOLE_EXTENDED_LOG=y CONFIG_NETDEVSIM=m +CONFIG_PPP=y +CONFIG_PPPOE=y CONFIG_VLAN_8021Q=m CONFIG_XDP_SOCKETS=y diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py index 221f27e57147..ad7c80f7ba96 100755 --- a/tools/testing/selftests/drivers/net/gro.py +++ b/tools/testing/selftests/drivers/net/gro.py @@ -313,6 +313,12 @@ def _gro_variants(): "ip_frag6", "ip_v6ext_same", "ip_v6ext_diff", ] + # Tests specific to PPPoE + pppoe_tests = [ + "data_same", "data_lrg_sml", "data_sml_lrg", "data_lrg_1byte", + "data_burst", "pppoe_sid", + ] + for mode in ["sw", "hw", "lro"]: for protocol in ["ipv4", "ipv6", "ipip", "ip6ip6"]: for test_name in common_tests: @@ -325,6 +331,11 @@ def _gro_variants(): for test_name in ipv6_tests: yield mode, protocol, test_name + for mode in ["sw"]: + for protocol in ["pppoev4", "pppoev6"]: + for test_name in pppoe_tests: + yield mode, protocol, test_name + @ksft_variants(_gro_variants()) def test(cfg, mode, protocol, test_name): diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c index 11b16ae5f0e8..0da55b757bcc 100644 --- a/tools/testing/selftests/net/lib/gro.c +++ b/tools/testing/selftests/net/lib/gro.c @@ -67,12 +67,14 @@ #include <errno.h> #include <error.h> #include <getopt.h> +#include <net/ethernet.h> +#include <net/if.h> #include <linux/filter.h> #include <linux/if_packet.h> +#include <linux/if_pppox.h> #include <linux/ipv6.h> #include <linux/net_tstamp.h> -#include <net/ethernet.h> -#include <net/if.h> +#include <linux/ppp_defs.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip6.h> @@ -134,6 +136,7 @@ static int total_hdr_len = -1; static int ethhdr_proto = -1; static bool ipip; static bool ip6ip6; +static bool pppoe; static uint64_t txtime_ns; static int num_flows = 4; static bool order_check; @@ -171,6 +174,22 @@ static void vlog(const char *fmt, ...) } } +static void fill_pppoelayer(void *buf, int payload_len, uint16_t sid) +{ + struct pppoe_ppp_hdr { + struct pppoe_hdr eh; + __be16 proto; + } *ph = buf; + + payload_len += sizeof(struct tcphdr); + ph->eh.type = 1; + ph->eh.ver = 1; + ph->eh.code = 0; + ph->eh.sid = htons(sid); + ph->eh.length = htons(payload_len + sizeof(ph->proto)); + ph->proto = htons(proto == PF_INET ? PPP_IP : PPP_IPV6); +} + static void setup_sock_filter(int fd) { const int dport_off = tcp_offset + offsetof(struct tcphdr, dest); @@ -412,11 +431,15 @@ static void create_packet(void *buf, int seq_offset, int ack_offset, fill_networklayer(buf + inner_ip_off, payload_len, IPPROTO_TCP); if (inner_ip_off > ETH_HLEN) { - int encap_proto = (proto == PF_INET) ? - IPPROTO_IPIP : IPPROTO_IPV6; + if (pppoe) { + fill_pppoelayer(buf + ETH_HLEN, payload_len + ip_hdr_len, 0x1234); + } else { + int encap_proto = (proto == PF_INET) ? + IPPROTO_IPIP : IPPROTO_IPV6; - fill_networklayer(buf + ETH_HLEN, - payload_len + ip_hdr_len, encap_proto); + fill_networklayer(buf + ETH_HLEN, + payload_len + ip_hdr_len, encap_proto); + } } fill_datalinklayer(buf); @@ -526,7 +549,7 @@ static void send_flags(int fd, struct sockaddr_ll *daddr, int psh, int syn, static void send_data_pkts(int fd, struct sockaddr_ll *daddr, int payload_len1, int payload_len2) { - static char buf[ETH_HLEN + IP_MAXPACKET]; + static char buf[MAX_HDR_LEN + IP_MAXPACKET]; create_packet(buf, 0, 0, payload_len1, 0); write_packet(fd, buf, total_hdr_len + payload_len1, daddr); @@ -1071,6 +1094,20 @@ static void send_fragment6(int fd, struct sockaddr_ll *daddr) write_packet(fd, buf, bufpkt_len, daddr); } +static void send_changed_pppoe_sid(int fd, struct sockaddr_ll *daddr) +{ + static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; + int pkt_size = total_hdr_len + PAYLOAD_LEN; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(buf + ETH_HLEN); + + create_packet(buf, 0, 0, PAYLOAD_LEN, 0); + write_packet(fd, buf, pkt_size, daddr); + + create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0); + hdr->sid = htons(0x4321); + write_packet(fd, buf, pkt_size, daddr); +} + static void bind_packetsocket(int fd) { struct sockaddr_ll daddr = {}; @@ -1121,9 +1158,10 @@ static void recv_error(int fd, int rcv_errno) static void check_recv_pkts(int fd, int *correct_payload, int correct_num_pkts) { - static char buffer[IP_MAXPACKET + ETH_HLEN + 1]; - struct iphdr *iph = (struct iphdr *)(buffer + ETH_HLEN); - struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + ETH_HLEN); + static char buffer[IP_MAXPACKET + MAX_HDR_LEN + 1]; + int nhoff = ETH_HLEN + (pppoe ? PPPOE_SES_HLEN : 0); + struct iphdr *iph = (struct iphdr *)(buffer + nhoff); + struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + nhoff); struct tcphdr *tcph; bool bad_packet = false; int tcp_ext_len = 0; @@ -1140,7 +1178,7 @@ static void check_recv_pkts(int fd, int *correct_payload, while (1) { ip_ext_len = 0; - pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0); + pkt_size = recv(fd, buffer, sizeof(buffer), 0); if (pkt_size < 0) recv_error(fd, errno); @@ -1183,9 +1221,10 @@ static void check_recv_pkts(int fd, int *correct_payload, static void check_capacity_pkts(int fd) { - static char buffer[IP_MAXPACKET + ETH_HLEN + 1]; - struct iphdr *iph = (struct iphdr *)(buffer + ETH_HLEN); - struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + ETH_HLEN); + static char buffer[IP_MAXPACKET + MAX_HDR_LEN + 1]; + int nhoff = ETH_HLEN + (pppoe ? PPPOE_SES_HLEN : 0); + struct iphdr *iph = (struct iphdr *)(buffer + nhoff); + struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + nhoff); int num_pkt = 0, num_coal = 0, pkt_idx; const char *fail_reason = NULL; int flow_order[num_flows * 2]; @@ -1203,7 +1242,7 @@ static void check_capacity_pkts(int fd) while (1) { ip_ext_len = 0; - pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0); + pkt_size = recv(fd, buffer, sizeof(buffer), 0); if (pkt_size < 0) recv_error(fd, errno); @@ -1499,6 +1538,12 @@ static void gro_sender(void) usleep(fin_delay_us); write_packet(txfd, fin_pkt, total_hdr_len, &daddr); + /* PPPoE sub-tests */ + } else if (strcmp(testname, "pppoe_sid") == 0) { + send_changed_pppoe_sid(txfd, &daddr); + usleep(fin_delay_us); + write_packet(txfd, fin_pkt, total_hdr_len, &daddr); + } else { error(1, 0, "Unknown testcase: %s", testname); } @@ -1716,6 +1761,12 @@ static void gro_receiver(void) } else if (strcmp(testname, "capacity") == 0) { check_capacity_pkts(rxfd); + } else if (strcmp(testname, "pppoe_sid") == 0) { + correct_payload[0] = PAYLOAD_LEN; + correct_payload[1] = PAYLOAD_LEN; + printf("different PPPoE session ID doesn't coalesce: "); + check_recv_pkts(rxfd, correct_payload, 2); + } else { error(1, 0, "Test case error: unknown testname %s", testname); } @@ -1734,6 +1785,8 @@ static void parse_args(int argc, char **argv) { "ipv6", no_argument, NULL, '6' }, { "ipip", no_argument, NULL, 'e' }, { "ip6ip6", no_argument, NULL, 'E' }, + { "pppoev4", no_argument, NULL, 'p' }, + { "pppoev6", no_argument, NULL, 'P' }, { "num-flows", required_argument, NULL, 'n' }, { "rx", no_argument, NULL, 'r' }, { "saddr", required_argument, NULL, 's' }, @@ -1745,7 +1798,7 @@ static void parse_args(int argc, char **argv) }; int c; - while ((c = getopt_long(argc, argv, "46d:D:eEi:n:rs:S:t:ov", opts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "46d:D:eEi:n:pPrs:S:t:ov", opts, NULL)) != -1) { switch (c) { case '4': proto = PF_INET; @@ -1765,6 +1818,16 @@ static void parse_args(int argc, char **argv) proto = PF_INET6; ethhdr_proto = htons(ETH_P_IPV6); break; + case 'p': + pppoe = true; + proto = PF_INET; + ethhdr_proto = htons(ETH_P_PPP_SES); + break; + case 'P': + pppoe = true; + proto = PF_INET6; + ethhdr_proto = htons(ETH_P_PPP_SES); + break; case 'd': addr4_dst = addr6_dst = optarg; break; @@ -1812,6 +1875,10 @@ int main(int argc, char **argv) } else if (ip6ip6) { tcp_offset = ETH_HLEN + sizeof(struct ipv6hdr) * 2; total_hdr_len = tcp_offset + sizeof(struct tcphdr); + } else if (pppoe) { + tcp_offset = ETH_HLEN + PPPOE_SES_HLEN + + (proto == PF_INET ? sizeof(struct iphdr) : sizeof(struct ipv6hdr)); + total_hdr_len = tcp_offset + sizeof(struct tcphdr); } else if (proto == PF_INET) { tcp_offset = ETH_HLEN + sizeof(struct iphdr); total_hdr_len = tcp_offset + sizeof(struct tcphdr); -- 2.43.0 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net-next v7 2/2] selftests: net: test PPPoE packets in gro.sh 2026-04-28 6:47 ` [PATCH net-next v7 2/2] selftests: net: test PPPoE packets in gro.sh Qingfang Deng @ 2026-04-30 10:04 ` Paolo Abeni 0 siblings, 0 replies; 4+ messages in thread From: Paolo Abeni @ 2026-04-30 10:04 UTC (permalink / raw) To: Qingfang Deng, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Shuah Khan, Simon Horman, Willem de Bruijn, Petr Machata, Anubhav Singh, Richard Gobert, netdev, linux-kselftest, linux-kernel Cc: linux-ppp, Pablo Neira Ayuso On 4/28/26 8:47 AM, Qingfang Deng wrote: > Add PPPoE test-cases to the GRO selftest. Only run a subset of > common_tests to avoid changing the hardcoded L3 offsets everywhere. > Add a new "pppoe_sid" test case to verify that packets with different > PPPoE session IDs are correctly identified as separate flows and not > coalesced. > > Signed-off-by: Qingfang Deng <qingfang.deng@linux.dev> > --- > v7: > - Do not run all the tests for PPPoE > - Add a new test for PPPoE > v6: https://lore.kernel.org/netdev/20260326081127.61229-2-dqfext@gmail.com > > tools/testing/selftests/drivers/net/config | 2 + > tools/testing/selftests/drivers/net/gro.py | 11 +++ > tools/testing/selftests/net/lib/gro.c | 99 ++++++++++++++++++---- > 3 files changed, 96 insertions(+), 16 deletions(-) > > diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config > index fd16994366f4..07e386895b94 100644 > --- a/tools/testing/selftests/drivers/net/config > +++ b/tools/testing/selftests/drivers/net/config > @@ -8,5 +8,7 @@ CONFIG_NETCONSOLE=m > CONFIG_NETCONSOLE_DYNAMIC=y > CONFIG_NETCONSOLE_EXTENDED_LOG=y > CONFIG_NETDEVSIM=m > +CONFIG_PPP=y > +CONFIG_PPPOE=y > CONFIG_VLAN_8021Q=m > CONFIG_XDP_SOCKETS=y > diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py > index 221f27e57147..ad7c80f7ba96 100755 > --- a/tools/testing/selftests/drivers/net/gro.py > +++ b/tools/testing/selftests/drivers/net/gro.py > @@ -313,6 +313,12 @@ def _gro_variants(): > "ip_frag6", "ip_v6ext_same", "ip_v6ext_diff", > ] > > + # Tests specific to PPPoE > + pppoe_tests = [ > + "data_same", "data_lrg_sml", "data_sml_lrg", "data_lrg_1byte", > + "data_burst", "pppoe_sid", > + ] > + > for mode in ["sw", "hw", "lro"]: > for protocol in ["ipv4", "ipv6", "ipip", "ip6ip6"]: > for test_name in common_tests: > @@ -325,6 +331,11 @@ def _gro_variants(): > for test_name in ipv6_tests: > yield mode, protocol, test_name > > + for mode in ["sw"]: > + for protocol in ["pppoev4", "pppoev6"]: > + for test_name in pppoe_tests: > + yield mode, protocol, test_name > + > > @ksft_variants(_gro_variants()) > def test(cfg, mode, protocol, test_name): > diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c > index 11b16ae5f0e8..0da55b757bcc 100644 > --- a/tools/testing/selftests/net/lib/gro.c > +++ b/tools/testing/selftests/net/lib/gro.c > @@ -67,12 +67,14 @@ > #include <errno.h> > #include <error.h> > #include <getopt.h> > +#include <net/ethernet.h> > +#include <net/if.h> > #include <linux/filter.h> > #include <linux/if_packet.h> > +#include <linux/if_pppox.h> > #include <linux/ipv6.h> > #include <linux/net_tstamp.h> > -#include <net/ethernet.h> > -#include <net/if.h> > +#include <linux/ppp_defs.h> > #include <netinet/in.h> > #include <netinet/ip.h> > #include <netinet/ip6.h> > @@ -134,6 +136,7 @@ static int total_hdr_len = -1; > static int ethhdr_proto = -1; > static bool ipip; > static bool ip6ip6; > +static bool pppoe; > static uint64_t txtime_ns; > static int num_flows = 4; > static bool order_check; > @@ -171,6 +174,22 @@ static void vlog(const char *fmt, ...) > } > } > > +static void fill_pppoelayer(void *buf, int payload_len, uint16_t sid) > +{ > + struct pppoe_ppp_hdr { > + struct pppoe_hdr eh; > + __be16 proto; > + } *ph = buf; > + > + payload_len += sizeof(struct tcphdr); > + ph->eh.type = 1; > + ph->eh.ver = 1; > + ph->eh.code = 0; > + ph->eh.sid = htons(sid); > + ph->eh.length = htons(payload_len + sizeof(ph->proto)); > + ph->proto = htons(proto == PF_INET ? PPP_IP : PPP_IPV6); > +} > + > static void setup_sock_filter(int fd) > { > const int dport_off = tcp_offset + offsetof(struct tcphdr, dest); > @@ -412,11 +431,15 @@ static void create_packet(void *buf, int seq_offset, int ack_offset, > > fill_networklayer(buf + inner_ip_off, payload_len, IPPROTO_TCP); > if (inner_ip_off > ETH_HLEN) { > - int encap_proto = (proto == PF_INET) ? > - IPPROTO_IPIP : IPPROTO_IPV6; > + if (pppoe) { > + fill_pppoelayer(buf + ETH_HLEN, payload_len + ip_hdr_len, 0x1234); > + } else { > + int encap_proto = (proto == PF_INET) ? > + IPPROTO_IPIP : IPPROTO_IPV6; > > - fill_networklayer(buf + ETH_HLEN, > - payload_len + ip_hdr_len, encap_proto); > + fill_networklayer(buf + ETH_HLEN, > + payload_len + ip_hdr_len, encap_proto); > + } > } > > fill_datalinklayer(buf); > @@ -526,7 +549,7 @@ static void send_flags(int fd, struct sockaddr_ll *daddr, int psh, int syn, > static void send_data_pkts(int fd, struct sockaddr_ll *daddr, > int payload_len1, int payload_len2) > { > - static char buf[ETH_HLEN + IP_MAXPACKET]; > + static char buf[MAX_HDR_LEN + IP_MAXPACKET]; Why MAX_HDR_LEN? I think you should add a new define alike: #define L2_HLEN_MAX (ETH_HLEN + PPPOE_SES_HLEN) /P ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support 2026-04-28 6:47 [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support Qingfang Deng 2026-04-28 6:47 ` [PATCH net-next v7 2/2] selftests: net: test PPPoE packets in gro.sh Qingfang Deng @ 2026-04-30 10:01 ` Paolo Abeni 1 sibling, 0 replies; 4+ messages in thread From: Paolo Abeni @ 2026-04-30 10:01 UTC (permalink / raw) To: Qingfang Deng, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, David Ahern, Simon Horman, Kees Cook, Guillaume Nault, Eric Woudstra, Felix Fietkau, netdev, linux-kernel Cc: linux-ppp, Pablo Neira Ayuso On 4/28/26 8:47 AM, Qingfang Deng wrote: > diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c > index bdd61c504a1c..363204e0c49a 100644 > --- a/drivers/net/ppp/pppoe.c > +++ b/drivers/net/ppp/pppoe.c > @@ -77,6 +77,7 @@ > #include <net/net_namespace.h> > #include <net/netns/generic.h> > #include <net/sock.h> > +#include <net/gro.h> > > #include <linux/uaccess.h> > > @@ -409,7 +410,7 @@ static int pppoe_rcv(struct sk_buff *skb, struct net_device *dev, > if (ppp_skb_is_compressed_proto(skb)) > goto drop; > > - if (pskb_trim_rcsum(skb, len)) > + if (!skb_is_gso(skb) && pskb_trim_rcsum(skb, len)) > goto drop; > > ph = pppoe_hdr(skb); > @@ -1103,6 +1104,164 @@ static struct pernet_operations pppoe_net_ops = { > .size = sizeof(struct pppoe_net), > }; > > +static u16 > +compare_pppoe_header(const struct pppoe_hdr *phdr, > + const struct pppoe_hdr *phdr2) > +{ > + __be16 proto = *(const __be16 *)(phdr + 1); > + __be16 proto2 = *(const __be16 *)(phdr2 + 1); > + > + return (__force u16)((phdr->sid ^ phdr2->sid) | (proto ^ proto2)); > +} > + > +static __be16 pppoe_hdr_proto(const struct pppoe_hdr *phdr) > +{ > + __be16 proto = *(const __be16 *)(phdr + 1); > + > + switch (proto) { > + case cpu_to_be16(PPP_IP): > + return cpu_to_be16(ETH_P_IP); > +#if IS_ENABLED(CONFIG_IPV6) > + case cpu_to_be16(PPP_IPV6): > + return cpu_to_be16(ETH_P_IPV6); > +#endif > + default: > + return 0; > + } > +} > + > +static struct sk_buff *pppoe_gro_receive(struct list_head *head, > + struct sk_buff *skb) > +{ > + const struct packet_offload *ptype; > + unsigned int hlen, off_pppoe; > + const struct pppoe_hdr *phdr; > + struct sk_buff *pp = NULL; > + struct sk_buff *p; > + int flush = 1; > + __be16 type; > + > + off_pppoe = skb_gro_offset(skb); > + hlen = off_pppoe + PPPOE_SES_HLEN; > + phdr = skb_gro_header(skb, hlen, off_pppoe); > + if (unlikely(!phdr)) > + goto out; > + > + /* filter for session packets (type:1, ver:1, code:0) */ > + if (*(const __be16 *)phdr != cpu_to_be16(0x1100)) > + goto out; > + > + /* ignore packets with padding or invalid length */ > + if (skb_gro_len(skb) != be16_to_cpu(phdr->length) + sizeof(*phdr)) > + goto out; > + > + type = pppoe_hdr_proto(phdr); > + ptype = gro_find_receive_by_type(type); > + if (!ptype) > + goto out; > + > + flush = 0; > + > + list_for_each_entry(p, head, list) { > + const struct pppoe_hdr *phdr2; > + > + if (!NAPI_GRO_CB(p)->same_flow) > + continue; > + > + phdr2 = (const struct pppoe_hdr *)(p->data + off_pppoe); > + if (compare_pppoe_header(phdr, phdr2)) > + NAPI_GRO_CB(p)->same_flow = 0; > + } > + > + skb_gro_pull(skb, PPPOE_SES_HLEN); > + skb_gro_postpull_rcsum(skb, phdr, PPPOE_SES_HLEN); > + > + pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive, > + ipv6_gro_receive, inet_gro_receive, > + head, skb); > + > +out: > + skb_gro_flush_final(skb, pp, flush); > + > + return pp; > +} > + > +static int pppoe_gro_complete(struct sk_buff *skb, int nhoff) > +{ > + struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb->data + nhoff); > + __be16 type = pppoe_hdr_proto(phdr); > + struct packet_offload *ptype; > + unsigned int len; > + > + ptype = gro_find_complete_by_type(type); > + if (!ptype) > + return -ENOENT; > + > + len = skb->len - (nhoff + sizeof(*phdr)); > + len = min(len, 0xFFFFU); > + phdr->length = cpu_to_be16(len); Whoops, I wrongly replied to v6, but comments still apply here: https://lore.kernel.org/netdev/9d7f1bbc-155d-4c18-bcf7-732ebe4cbf67@redhat.com/T/#m6c92ba4367355abf1bdb001f102c4847853ac4f3 /P ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-30 10:04 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-28 6:47 [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support Qingfang Deng 2026-04-28 6:47 ` [PATCH net-next v7 2/2] selftests: net: test PPPoE packets in gro.sh Qingfang Deng 2026-04-30 10:04 ` Paolo Abeni 2026-04-30 10:01 ` [PATCH net-next v7 1/2] net: pppoe: implement GRO/GSO support Paolo Abeni
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox