我正在尝试通过 C 函数
sendto()
在 Linux 系统中发送帧以太网。该帧不包含任何 IP 级别信息,因为 IP 协议未在我的应用程序中使用。函数 sendto() 的签名
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
传输缓冲区
对于 send() 和 sendto(),消息在 buf 中找到,长度为 len。
目的地地址
如果在连接模式(SOCK_STREAM、SOCK_SEQPACKET)套接字上使用 sendto(),参数 dest_addr 和 addrlen 将被忽略(当它们不为 NULL 和 0 时可能会返回错误 EISCONN),当它们不为 NULL 和 0 时返回错误 ENOTCONN插座实际上没有连接。 否则,目标的地址由 dest_addr 给出,addrlen 指定其大小。
在我的例子中,套接字是通过以下指令创建的:
int sck_dgram = socket(AF_PACKET, SOCK_DGRAM, 0);
所以它不是连接模式套接字。要设置目标地址,我使用以下说明:
struct sockaddr_ll dest_addr = {0};
dest_addr.sll_family = AF_PACKET;
dest_addr.sll_ifindex = ifindex;
dest_addr.sll_halen = ETHER_ADDR_LEN;
memcpy(dest_addr.sll_addr, mac_dest, ETHER_ADDR_LEN);
其中
mac_dest
是包含目标 MAC 地址的数组,ifindex
是用于发送的以太网接口的索引。
总之,要发送
buf[BUFFSIZE]
数据数组,我使用以下sendto()
指令:
sendto(sck_dgram, buf, BUFFSIZE, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr))
使用前面的指令,我只能通过
dest_addr
设置目标MAC地址(帧的0到5字节)而不是帧的源MAC地址(帧的6到11字节)。源 MAC 地址自动设置为等于用于发送的以太网接口的 MAC 地址。
是否可以发送源 MAC 地址与用于发送的以太网接口的 MAC 地址不同的帧以太网?
编辑: 问题的解决方案是通过将
SOCK_DGRAM
替换为 SOCK_RAW
来修改创建套接字的指令。正确的指示是:
int sck_raw = socket(AF_PACKET, SOCK_RAW, 0);
根据之前的指令,我必须修改
sendto()
指令,使buf[]
包含14个八位字节,包括目标mac地址、源mac地址和以太网类型。此数据必须放在 buf[]
的前 14 个字节中。所以我必须增加buf
的大小如下:
char buf[BUFFSIZE+14];
// set buf[0] .. buf[5] with mac dest
// set buf[6] .. buf[11] with mac source
// set buf[12] and buf[13] with ether type (in my case set 0x0200)
// sendto() becomes:
sendto(sck_raw, buf, BUFFSIZE+14, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr))
在这个link可以知道以太网帧结构。
最简单的解决方案是更改以太网卡上的 mac 地址。
如果你愿意,你可以使用 tc & iptables 解决方案。 iptables 根据 udp 端口设置数据包类别,然后您可以使用 tc 更改数据包类别的源 mac 地址。