Skip to content

send() and sendp() not working on PPPoE interface #4964

@cinit

Description

@cinit

Brief description

send() and sendp() sets invalid protocol when using AF_PACKET on PPPoE interfaces, causing packets to be dropped by PPP driver (skb->protocol = 0).

Scapy version

2.7.0.dev46

Python version

Python 3.13.5, using IPython 8.35.0

Operating system

Linux 6.12.74+deb13+1-amd64

Additional environment information

When using send(with ScopedIP) and sendp() on PPPoE (ppp0) interface, Scapy sends packets via AF_PACKET without correctly setting the L3 protocol (sll_protocol). As a result, packet_snd assigns skb->protocol = 0 and causes packets to be dropped by PPP driver that rely on skb->protocol (in this case, ppp_start_xmit).

How to reproduce

Create a PPPoE link interface. Make sure it's type 512.

cat /sys/class/net/ppp0/type 
512

Send an IPv6 ICMPv6 echo request by send() and sendp().

>>> pkt = IPv6(dst="ff02::1%ppp0")/ICMPv6EchoRequest()
>>> pkt.show()
###[ IPv6 ]###
  version   = 6
  tc        = 0
  fl        = 0
  plen      = None
  nh        = ICMPv6
  hlim      = 64
  src       = fe80::be24:11c6:ebd1:9865
  dst       = ScopedIP('ff02::1', scope='ppp0')
###[ ICMPv6 Echo Request ]###
     type      = Echo Request
     code      = 0
     cksum     = None
     id        = 0x0
     seq       = 0x0
     data      = b''

>>> sendp(pkt, iface="ppp0")
.
Sent 1 packets.
>>> send(pkt)
.
Sent 1 packets.
>>> 

I also tried pkt = CookedLinux(proto=0x86DD) / IPv6(src="fe80::1", dst="ff02::2") / ICMPv6ND_RS() and pkt = PPP() / IPv6(src="fe80::1", dst="ff02::2") / ICMPv6ND_RS(), none of them worked.

Actual result

Look at tcpdump output, just nothing there.

~# tcpdump -n -i ppp0 'icmp6'
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ppp0, link-type LINUX_SLL (Linux cooked v1), snapshot length 262144 bytes

Look at ifconfig ppp0, there 2 more dropped TX packets.
Before:

~# ifconfig ppp0
ppp0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1492
        inet 100.64.10.201  netmask 255.255.255.255  destination 100.64.0.1
        inet6 fe80::be24:11c6:ebd1:9865  prefixlen 128  scopeid 0x20<link>
        ppp  txqueuelen 3  (Point-to-Point Protocol)
        RX packets 4752172  bytes 3692861302 (3.4 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4389765  bytes 935034757 (891.7 MiB)
        TX errors 0  dropped 388 overruns 0  carrier 0  collisions 0

After:

~# ifconfig ppp0
ppp0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1492
        inet 100.64.10.201  netmask 255.255.255.255  destination 100.64.0.1
        inet6 fe80::be24:11c6:ebd1:9865  prefixlen 128  scopeid 0x20<link>
        ppp  txqueuelen 3  (Point-to-Point Protocol)
        RX packets 4752398  bytes 3692927445 (3.4 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4390064  bytes 935094677 (891.7 MiB)
        TX errors 0  dropped 390 overruns 0  carrier 0  collisions 0

Run dropwatch -l kas start + scapy while True:sendp(pkt, iface="ppp0") and it shows the following:

...
1 drops at tcp_validate_incoming+1a3 (0xffffffffa21125e3) [software]
1 drops at __netif_receive_skb_core.constprop.0+144 (0xffffffffa2034584) [software]
1 drops at __netif_receive_skb_core.constprop.0+144 (0xffffffffa2034584) [software]
1 drops at __init_scratch_end+1c80a73c (0xffffffffc100a73c) [software]
12 drops at __init_scratch_end+1c80c9b0 (0xffffffffc100c9b0) [software]
10 drops at __init_scratch_end+1c80c9b0 (0xffffffffc100c9b0) [software]
...

And trace it:

bpftrace -e 'tracepoint:skb:kfree_skb /args->location == 0xffffffffc100c9b0/ { print(kstack); }'
Attaching 1 probe...

        sk_skb_reason_drop+148
        ppp_start_xmit+112
        dev_hard_start_xmit+100
        sch_direct_xmit+159
        __dev_queue_xmit+2540
        packet_sendmsg+4285
        __sys_sendto+479
        __x64_sys_sendto+36
        do_syscall_64+130
        entry_SYSCALL_64_after_hwframe+118


        sk_skb_reason_drop+148
        ppp_start_xmit+112
        dev_hard_start_xmit+100
        sch_direct_xmit+159
        __dev_queue_xmit+2540
        packet_sendmsg+4285
        __sys_sendto+479
        __x64_sys_sendto+36
        do_syscall_64+130
        entry_SYSCALL_64_after_hwframe+118

It seems that ppp_start_xmit+112 is dropping the TX packet.

Trace skb->protocol:

# bpftrace -e '
tracepoint:skb:kfree_skb
{
    printf("protocol=0x%x\n", args->protocol);
}'

protocol=0x800
protocol=0x800
protocol=0x800
protocol=0x800
protocol=0x800
protocol=0x800
protocol=0x0
protocol=0x0
protocol=0x800
protocol=0x0
protocol=0x0
protocol=0x800
protocol=0x0

There are lots of protocol=0x0 when I run scapy while True:sendp(pkt, iface="ppp0")

Packets sent via sendp() on ppp0:

  • Are transmitted via AF_PACKET
  • End up with skb->protocol = 0
  • Are dropped in kernel:
ppp_start_xmit()
  -> ethertype_to_npindex(ntohs(skb->protocol)) -> -1
  -> drop

No packets are visible on the wire.

Expected result

The ping request should at lease appear on tcpdump output, and the ifconfig TX drop counter should not change when I send()/sendp().

Expected something like this (with my simple AF_PACKET test program):

# tcpdump -n -i ppp0 'icmp6'
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ppp0, link-type LINUX_SLL (Linux cooked v1), snapshot length 262144 bytes
13:14:02.186611 IP6 fe80::be24:11c6:ebd1:9865 > ff02::1: ICMP6, echo request, id 16494, seq 1, length 16
13:14:02.188890 IP6 fe80::[REDACTED] > fe80::be24:11c6:ebd1:9865: ICMP6, echo reply, id 16494, seq 1, length 16
^C
2 packets captured
2 packets received by filter
0 packets dropped by kernel

Related resources

packet_snd: https://elixir.bootlin.com/linux/v6.19.11/source/net/packet/af_packet.c#L2939
packet_snd is where skb->protocol assigned.

ppp_start_xmit: https://elixir.bootlin.com/linux/v6.19.11/source/drivers/net/ppp/ppp_generic.c#L1451
ppp_start_xmit is where packets are droped.

My test AF_PACKET program (written by GPT for test purpose only, compiles on debian 13 x86_64 GCC):

// ppp_ping_demo.c
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <linux/if_ether.h>

static uint16_t csum16(const void *data, size_t len) {
    const uint8_t *p = (const uint8_t *)data;
    uint32_t sum = 0;
    while (len > 1) {
        sum += (p[0] << 8) | p[1];
        p += 2;
        len -= 2;
    }
    if (len) sum += p[0] << 8;
    while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
    return (uint16_t)(~sum);
}

static uint16_t icmp6_checksum(const struct in6_addr *src,
                               const struct in6_addr *dst,
                               const void *icmp, size_t icmp_len) {
    // Pseudo-header
    struct {
        struct in6_addr src;
        struct in6_addr dst;
        uint32_t len;
        uint8_t zero[3];
        uint8_t next;
    } ph;

    memset(&ph, 0, sizeof(ph));
    ph.src = *src;
    ph.dst = *dst;
    ph.len = htonl((uint32_t)icmp_len);
    ph.next = IPPROTO_ICMPV6;

    // sum pseudo + icmp
    uint32_t sum = 0;
    sum += (~csum16(&ph, sizeof(ph)) & 0xFFFF);
    sum += (~csum16(icmp, icmp_len) & 0xFFFF);

    while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
    return (uint16_t)(~sum);
}

static void usage(const char *p) {
    fprintf(stderr, "Usage: %s <ifname> <src-ll> <dst-addr>\n", p);
    fprintf(stderr, "Example: %s ppp0 fe80::1 ff02::1\n", p);
}

int main(int argc, char **argv) {
    if (argc != 4) {
        usage(argv[0]);
        return 1;
    }

    const char *ifname = argv[1];
    const char *src_str = argv[2];
    const char *dst_str = argv[3];

    int ifindex = if_nametoindex(ifname);
    if (!ifindex) {
        perror("if_nametoindex");
        return 1;
    }

    // fill in address (supports link local)
    struct in6_addr src, dst;
    if (inet_pton(AF_INET6, src_str, &src) != 1) {
        fprintf(stderr, "invalid src\n");
        return 1;
    }

    char dst_buf[256];
    memset(dst_buf, 0, sizeof(dst_buf));
    // for link-local, add %iface
    if (strchr(dst_str, '%') == NULL &&
        strncmp(dst_str, "fe80:", 5) == 0) {
        snprintf(dst_buf, sizeof(dst_buf), "%s%%%s", dst_str, ifname);
    } else {
        snprintf(dst_buf, sizeof(dst_buf), "%s", dst_str);
    }

    struct sockaddr_in6 dst_in6;
    memset(&dst_in6, 0, sizeof(dst_in6));
    dst_in6.sin6_family = AF_INET6;
    if (inet_pton(AF_INET6, dst_buf, &dst) != 1) {
        // inet_pton not supports %scope
        char tmp[256];
        strncpy(tmp, dst_buf, sizeof(tmp)-1);
        char *pct = strchr(tmp, '%');
        if (pct) *pct = '\0';
        if (inet_pton(AF_INET6, tmp, &dst) != 1) {
            fprintf(stderr, "invalid dst\n");
            return 1;
        }
    }

    // PF_PACKET + SOCK_DGRAM (set skb->protocol here)
    int fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6));
    if (fd < 0) {
        perror("socket(AF_PACKET)");
        return 1;
    }

    // set dest ll address
    struct sockaddr_ll sll;
    memset(&sll, 0, sizeof(sll));
    sll.sll_family   = AF_PACKET;
    sll.sll_ifindex  = ifindex;
    sll.sll_protocol = htons(ETH_P_IPV6);
    // PPP has no L2 addr, so no sll_addr

    // construct IPv6 + ICMPv6 Echo Request
    uint8_t buf[1500];
    memset(buf, 0, sizeof(buf));

    struct ip6_hdr *ip6 = (struct ip6_hdr *)buf;
    struct icmp6_hdr *icmp = (struct icmp6_hdr *)(buf + sizeof(struct ip6_hdr));

    const char payload[] = "ppp-demo";
    size_t payload_len = sizeof(payload) - 1;

    // IPv6 header
    ip6->ip6_flow = htonl((6 << 28) | 0); // version + tc/flow
    ip6->ip6_plen = htons(sizeof(struct icmp6_hdr) + payload_len);
    ip6->ip6_nxt  = IPPROTO_ICMPV6;
    ip6->ip6_hlim = 255; // for ND/LL. hlim should be 255
    ip6->ip6_src  = src;
    ip6->ip6_dst  = dst;

    // ICMPv6 Echo
    icmp->icmp6_type = ICMP6_ECHO_REQUEST;
    icmp->icmp6_code = 0;
    icmp->icmp6_id   = htons((uint16_t)getpid());
    icmp->icmp6_seq  = htons(1);

    uint8_t *data = (uint8_t *)(icmp + 1);
    memcpy(data, payload, payload_len);

    // checksum
    icmp->icmp6_cksum = 0;
    icmp->icmp6_cksum = htons(icmp6_checksum(&src, &dst, icmp,
                                  sizeof(struct icmp6_hdr) + payload_len));

    size_t pkt_len = sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr) + payload_len;

    // sendp
    ssize_t n = sendto(fd, buf, pkt_len, 0,
                       (struct sockaddr *)&sll, sizeof(sll));
    if (n < 0) {
        perror("sendto");
        close(fd);
        return 1;
    }

    printf("sent %zd bytes on %s (proto=0x86dd)\n", n, ifname);
    close(fd);
    return 0;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions