使用 Linux 原始套接字捕获 PTP 数据包
Capturing PTP packets with Linux raw socket
我想实现一个捕获以太网上所有精确时间协议 (PTP) 帧的 C 程序,因此我创建了一个原始套接字并为 PTP 附加了一个过滤器,我使用 recvmsg() 读取数据从插座。
第一个问题是我没有收到任何 PTP 帧,所以我注释掉了过滤器,但现在我也没有收到任何以太网帧。
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <linux/filter.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#define PTP_IF_NAME "enx503eaa33fc9d"
#define PACKET_SIZE 300
#define NUM_OF_BLOCKS_PER_LINE 30
#define SIZE_OF_BYYE_PRINT_BLOCK 3
#define SIZE_OF_PRINT_BUFFER \
( ( NUM_OF_BLOCKS_PER_LINE * SIZE_OF_BYYE_PRINT_BLOCK ) + 1 )
#define PERROR(x, ...) printf( x "stderr:%m\n", ##__VA_ARGS__)
#define INFO(x, ...) printf( x "\n", ##__VA_ARGS__)
#define ERROR(x, ...) printf( x "\n", ##__VA_ARGS__)
int eventSock = -1;
void printBuffer(unsigned char* buffer, size_t len) {
unsigned char outBuffer[SIZE_OF_PRINT_BUFFER];
unsigned char* pOutBuffer = outBuffer;
int usedBytes = 0;
for (int i = 0; i < len; i++) {
if ((usedBytes + SIZE_OF_BYYE_PRINT_BLOCK) > SIZE_OF_PRINT_BUFFER) {
INFO("%s", outBuffer);
memset(outBuffer, 0, sizeof(outBuffer));
usedBytes = 0;
pOutBuffer = outBuffer;
}
sprintf(pOutBuffer, "%02x ", buffer[i]);
usedBytes += SIZE_OF_BYYE_PRINT_BLOCK;
pOutBuffer += SIZE_OF_BYYE_PRINT_BLOCK;
}
INFO("%s", outBuffer);
}
int getInterfaceIndex(char *ifaceName) {
int sockfd;
struct ifreq ifr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
ERROR("Could not retrieve interface index for %s", ifaceName);
return -1;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifaceName, IFNAMSIZ);
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {
PERROR("failed to request hardware address for %s", ifaceName);
close(sockfd);
return -1;
}
close(sockfd);
return ifr.ifr_ifindex;
}
bool InitSocket(char *ifaceName) {
int IfIndex;
struct sockaddr_ll sll;
IfIndex = getInterfaceIndex(ifaceName);
if (IfIndex < 0) {
return false;
}
/* create the socket */
if ((eventSock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
PERROR("failed to initialize raw socket");
return false;
}
/* binding to the interface */
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = IfIndex;
sll.sll_protocol = htons(ETH_P_ALL);
/* Bind the socket to the interface */
if (bind(eventSock, (struct sockaddr *) &sll, sizeof(sll)) < 0) {
PERROR("failed to bind raw event socket");
return false;
}
/* bpf bytecode to filter only PTP packets (ethertype 0x88f7)
* obtained via "tcpdump -dd ether proto 0x88f7" */
// struct sock_filter filter_ptp[] = {
// { 0x28, 0, 0, 0x0000000c }, // ldh [12]
// { 0x15, 0, 1, 0x000088f7 }, // jeq #0x88f7 jt 2 jf 3
// { 0x06, 0, 0, 0x00040000 }, // ret #262144
// { 0x06, 0, 0, 0x00000000 }, // ret #0
// };
//
// struct sock_fprog bpf_ptp = { .len = 4, .filter = filter_ptp, };
//
// if (setsockopt(eventSock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_ptp,
// sizeof(bpf_ptp))) {
// PERROR("failed attaching filter to raw event socket");
// return false;
// }
}
int waitForData(struct timeval * timeout, fd_set *readfds) {
int ret, nfds;
struct timeval tv, *tv_ptr;
if (timeout) {
tv.tv_sec = timeout->tv_sec;
tv.tv_usec = timeout->tv_usec;
tv_ptr = &tv;
} else {
tv_ptr = NULL;
}
FD_ZERO(readfds);
nfds = 0;
FD_SET(eventSock, readfds);
nfds = eventSock;
nfds++;
ret = select(nfds, readfds, 0, 0, tv_ptr);
return ret;
}
void netr(void) {
ssize_t ret = 0;
char buf[1024];
struct msghdr msg;
struct iovec vec[1];
struct sockaddr_in from_addr;
union {
struct cmsghdr cm;
char control[256];
} cmsg_un;
struct cmsghdr *cmsg;
vec[0].iov_base = buf;
vec[0].iov_len = PACKET_SIZE;
memset(&msg, 0, sizeof(msg));
memset(&from_addr, 0, sizeof(from_addr));
memset(buf, 0, PACKET_SIZE);
memset(&cmsg_un, 0, sizeof(cmsg_un));
msg.msg_name = (caddr_t) & from_addr;
msg.msg_namelen = sizeof(from_addr);
msg.msg_iov = vec;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_un.control;
msg.msg_controllen = sizeof(cmsg_un.control);
msg.msg_flags = 0;
ret = recvmsg(eventSock, &msg, MSG_DONTWAIT);
if (ret < 0) {
PERROR("failed to receive message!");
}
INFO("received Bytes: %ld", ret);
printBuffer(msg.msg_iov->iov_base, PACKET_SIZE);
}
int main(void) {
int ret;
fd_set readfds;
struct timeval timeout = { .tv_sec = 1, .tv_usec = 0 };
INFO("main started!!");
InitSocket(PTP_IF_NAME);
while (1) {
INFO("waiting for event!!");
ret = waitForData(&timeout, &readfds);
INFO("select return = %d", ret);
if (ret > 0) {
if (FD_ISSET(eventSock, &readfds)) {
netr();
}
}
}
}
看来你的代码本身没有问题。这可以通过 运行 在本地主机界面上运行您的程序来验证,例如,通过仅更改此行:
#define PTP_IF_NAME "lo"
并通过发送帧,ping 127.0.0.1
。
根据评论中的讨论,当 Wireshark 运行在接口上宁和转储数据包时,您显然可以看到帧,但其余时间不起作用。这可能表明您需要将您的界面设置为“混杂模式”。
混杂模式告诉您的网卡捕获所有数据包,即使是那些未寻址到接口的数据包(目标 MAC 地址不是接口之一的数据包)。当模式关闭时,无论您使用什么 BPF 过滤器或是否使用过滤器,卡都会丢弃不应接收的数据包。在您的情况下,看起来您正在 运行 使用未寻址到 NIC 的数据包进行测试。当 Wireshark 运行s 时,它会将接口设置为混杂,这也会反映在您的程序中并允许您查看帧。当您停止它时,它会将界面恢复为非混杂。
您不必 运行 Wireshark 将接口设置为混杂模式,您可以使用:
$ sudo ip link set enx503eaa33fc9d promisc on
然后在完成后使用 off
禁用。或者,只需发送具有正确目标 MAC 地址的数据包就可以在不更改 NIC 配置的情况下使它们可见 :).
混杂模式效率不高。您需要使用 SIOCADDMULTI ioctl 命令注册 PTP 多播地址。
我想实现一个捕获以太网上所有精确时间协议 (PTP) 帧的 C 程序,因此我创建了一个原始套接字并为 PTP 附加了一个过滤器,我使用 recvmsg() 读取数据从插座。 第一个问题是我没有收到任何 PTP 帧,所以我注释掉了过滤器,但现在我也没有收到任何以太网帧。
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <linux/filter.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#define PTP_IF_NAME "enx503eaa33fc9d"
#define PACKET_SIZE 300
#define NUM_OF_BLOCKS_PER_LINE 30
#define SIZE_OF_BYYE_PRINT_BLOCK 3
#define SIZE_OF_PRINT_BUFFER \
( ( NUM_OF_BLOCKS_PER_LINE * SIZE_OF_BYYE_PRINT_BLOCK ) + 1 )
#define PERROR(x, ...) printf( x "stderr:%m\n", ##__VA_ARGS__)
#define INFO(x, ...) printf( x "\n", ##__VA_ARGS__)
#define ERROR(x, ...) printf( x "\n", ##__VA_ARGS__)
int eventSock = -1;
void printBuffer(unsigned char* buffer, size_t len) {
unsigned char outBuffer[SIZE_OF_PRINT_BUFFER];
unsigned char* pOutBuffer = outBuffer;
int usedBytes = 0;
for (int i = 0; i < len; i++) {
if ((usedBytes + SIZE_OF_BYYE_PRINT_BLOCK) > SIZE_OF_PRINT_BUFFER) {
INFO("%s", outBuffer);
memset(outBuffer, 0, sizeof(outBuffer));
usedBytes = 0;
pOutBuffer = outBuffer;
}
sprintf(pOutBuffer, "%02x ", buffer[i]);
usedBytes += SIZE_OF_BYYE_PRINT_BLOCK;
pOutBuffer += SIZE_OF_BYYE_PRINT_BLOCK;
}
INFO("%s", outBuffer);
}
int getInterfaceIndex(char *ifaceName) {
int sockfd;
struct ifreq ifr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
ERROR("Could not retrieve interface index for %s", ifaceName);
return -1;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifaceName, IFNAMSIZ);
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {
PERROR("failed to request hardware address for %s", ifaceName);
close(sockfd);
return -1;
}
close(sockfd);
return ifr.ifr_ifindex;
}
bool InitSocket(char *ifaceName) {
int IfIndex;
struct sockaddr_ll sll;
IfIndex = getInterfaceIndex(ifaceName);
if (IfIndex < 0) {
return false;
}
/* create the socket */
if ((eventSock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
PERROR("failed to initialize raw socket");
return false;
}
/* binding to the interface */
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = IfIndex;
sll.sll_protocol = htons(ETH_P_ALL);
/* Bind the socket to the interface */
if (bind(eventSock, (struct sockaddr *) &sll, sizeof(sll)) < 0) {
PERROR("failed to bind raw event socket");
return false;
}
/* bpf bytecode to filter only PTP packets (ethertype 0x88f7)
* obtained via "tcpdump -dd ether proto 0x88f7" */
// struct sock_filter filter_ptp[] = {
// { 0x28, 0, 0, 0x0000000c }, // ldh [12]
// { 0x15, 0, 1, 0x000088f7 }, // jeq #0x88f7 jt 2 jf 3
// { 0x06, 0, 0, 0x00040000 }, // ret #262144
// { 0x06, 0, 0, 0x00000000 }, // ret #0
// };
//
// struct sock_fprog bpf_ptp = { .len = 4, .filter = filter_ptp, };
//
// if (setsockopt(eventSock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_ptp,
// sizeof(bpf_ptp))) {
// PERROR("failed attaching filter to raw event socket");
// return false;
// }
}
int waitForData(struct timeval * timeout, fd_set *readfds) {
int ret, nfds;
struct timeval tv, *tv_ptr;
if (timeout) {
tv.tv_sec = timeout->tv_sec;
tv.tv_usec = timeout->tv_usec;
tv_ptr = &tv;
} else {
tv_ptr = NULL;
}
FD_ZERO(readfds);
nfds = 0;
FD_SET(eventSock, readfds);
nfds = eventSock;
nfds++;
ret = select(nfds, readfds, 0, 0, tv_ptr);
return ret;
}
void netr(void) {
ssize_t ret = 0;
char buf[1024];
struct msghdr msg;
struct iovec vec[1];
struct sockaddr_in from_addr;
union {
struct cmsghdr cm;
char control[256];
} cmsg_un;
struct cmsghdr *cmsg;
vec[0].iov_base = buf;
vec[0].iov_len = PACKET_SIZE;
memset(&msg, 0, sizeof(msg));
memset(&from_addr, 0, sizeof(from_addr));
memset(buf, 0, PACKET_SIZE);
memset(&cmsg_un, 0, sizeof(cmsg_un));
msg.msg_name = (caddr_t) & from_addr;
msg.msg_namelen = sizeof(from_addr);
msg.msg_iov = vec;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_un.control;
msg.msg_controllen = sizeof(cmsg_un.control);
msg.msg_flags = 0;
ret = recvmsg(eventSock, &msg, MSG_DONTWAIT);
if (ret < 0) {
PERROR("failed to receive message!");
}
INFO("received Bytes: %ld", ret);
printBuffer(msg.msg_iov->iov_base, PACKET_SIZE);
}
int main(void) {
int ret;
fd_set readfds;
struct timeval timeout = { .tv_sec = 1, .tv_usec = 0 };
INFO("main started!!");
InitSocket(PTP_IF_NAME);
while (1) {
INFO("waiting for event!!");
ret = waitForData(&timeout, &readfds);
INFO("select return = %d", ret);
if (ret > 0) {
if (FD_ISSET(eventSock, &readfds)) {
netr();
}
}
}
}
看来你的代码本身没有问题。这可以通过 运行 在本地主机界面上运行您的程序来验证,例如,通过仅更改此行:
#define PTP_IF_NAME "lo"
并通过发送帧,ping 127.0.0.1
。
根据评论中的讨论,当 Wireshark 运行在接口上宁和转储数据包时,您显然可以看到帧,但其余时间不起作用。这可能表明您需要将您的界面设置为“混杂模式”。
混杂模式告诉您的网卡捕获所有数据包,即使是那些未寻址到接口的数据包(目标 MAC 地址不是接口之一的数据包)。当模式关闭时,无论您使用什么 BPF 过滤器或是否使用过滤器,卡都会丢弃不应接收的数据包。在您的情况下,看起来您正在 运行 使用未寻址到 NIC 的数据包进行测试。当 Wireshark 运行s 时,它会将接口设置为混杂,这也会反映在您的程序中并允许您查看帧。当您停止它时,它会将界面恢复为非混杂。
您不必 运行 Wireshark 将接口设置为混杂模式,您可以使用:
$ sudo ip link set enx503eaa33fc9d promisc on
然后在完成后使用 off
禁用。或者,只需发送具有正确目标 MAC 地址的数据包就可以在不更改 NIC 配置的情况下使它们可见 :).
混杂模式效率不高。您需要使用 SIOCADDMULTI ioctl 命令注册 PTP 多播地址。