send() and sendp() not working on PPPoE interface
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 12.6k
- Forks
- 2.2k
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 56
Description
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;
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing Scapy's send() and sendp() AF_PACKET path for PPPoE interfaces, comparing the sockaddr_ll protocol handling with the working C AF_PACKET example in the issue. Reproduce with the IPv6 ICMPv6 packet on ppp0, then verify completion when tcpdump sees the packet and the TX drop counter no longer increases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- linux, python
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100