I'm implementing a non-standard / unpublished Ethernet protocol on Linux. The Ethernet frames have EtherType 0x5eeb
and are multicast to 77:68:76:68:76:69
.
I'm struggling to write a simple program that can receive these frames while bound to a single interface. I've attempted this in several languages now; here's the C version:
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
const unsigned short eth_type = 0x5eeb;
int main() {
/* Open a socket */
int fd = socket(AF_PACKET, SOCK_RAW, htons(eth_type));
if (fd < 0) {
perror("socket");
exit(1);
}
/* Find out the interface index number */
struct ifreq ifr;
const char * if_name = "eth0";
size_t if_name_len = strlen (if_name);
memcpy(ifr.ifr_name, if_name, if_name_len);
ioctl(fd, SIOCGIFINDEX, &ifr);
printf("Interface has index %d\n", ifr.ifr_ifindex);
/* Add the interface to the multicast group */
char mcast[] = {0x77, 0x68, 0x76, 0x68, 0x76, 0x69, 0, 0};
struct packet_mreq mreq = {0};
mreq.mr_ifindex = ifr.ifr_ifindex;
mreq.mr_type = PACKET_MR_MULTICAST;
memcpy(mreq.mr_address, mcast, 8);
mreq.mr_alen = 6;
if(setsockopt(fd, SOL_SOCKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
perror("setsockopt");
exit(1);
}
/* Bind to the interface */
struct sockaddr_ll addr = {0};
addr.sll_family = AF_PACKET;
addr.sll_ifindex = ifr.ifr_ifindex;
addr.sll_protocol = htons(eth_type); // Defaults to the socket protocol anyway
addr.sll_pkttype = PACKET_MULTICAST;
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
exit(1);
}
/* Receive a frame and output some details */
char buf [2048];
struct sockaddr_ll src_addr;
socklen_t src_addr_len = sizeof(src_addr);
ssize_t count = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&src_addr, &src_addr_len);
if (count == -1) {
perror("recvfrom");
exit(1);
} else {
printf("Received frame.\n");
printf("Dest MAC: ");
for (int ii = 0; ii < 5; ii++) {
printf("%02hhx:", buf[ii]);
}
printf("%02hhx\n", buf[5]);
printf("Src MAC: ");
for (int ii = 6; ii < 11; ii++) {
printf("%02hhx:", buf[ii]);
}
printf("%02hhx\n", buf[11]);
}
}
If I remove the section titled Bind to the interface
then I receive the frames alright, I just receive them from every interface on the system, not just the one I'm trying to listen to. If I change either of the places where eth_type
is used to ETH_P_ALL
then I get all the frames arriving on the bound interface, not just the ones with my EtherType (and not just multicast frames, for that matter; bind
doesn't seem to respect PACKET_MULTICAST
in this case).
Is there some way to make bind
and specific EtherTypes play nicely together?
I've tried this on Linux 5.11 and 4.9. If anyone wants to do some testing, here's a short Python 3 program that spits out the frames. You need the pyroute2
package.
import socket
from pyroute2 import IPDB
import sys
import struct
import binascii
import time
ip = IPDB()
SMAC=bytes.fromhex(ip.interfaces[sys.argv[1]]['address'].replace(':', ''))
DMAC=bytes.fromhex('776876687669')
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
s.bind((sys.argv[1], 0x5eeb))
#s.bind((sys.argv[1], 0))
dgram = struct.pack("!6s6sHH", DMAC, SMAC, 0x5eeb, 0x7668)
print(' '.join('{:02x}'.format(x) for x in dgram))
while True:
s.send(dgram)
time.sleep(0.1)
The problem here was actually the PACKET_ADD_MULTICAST
socket option, which is a SOL_PACKET
option, not a SOL_SOCKET
option like I was attempting. I'm not exactly sure what SOL_SOCKET
+ PACKET_ADD_MEMBERSHIP
does, but whatever it is, it doesn't return an error and it breaks things.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.