如何用socket实现以太网帧的封装和发送,附带代码和解析
时间: 2024-03-02 19:51:27 浏览: 154
使用socket实现以太网帧的封装和发送,需要使用到Raw Socket,Raw Socket是一种特殊的Socket类型,它允许应用程序直接访问网络层和链路层协议,可以实现更灵活、更底层的网络编程。
下面是一个简单的C语言代码示例,用于封装并发送一个以太网帧:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <net/if.h>
#define BUF_SIZE 1024
int main(int argc, char* argv[])
{
int sock_fd;
struct sockaddr_ll saddr;
char if_name[IFNAMSIZ];
uint8_t src_mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
uint8_t dst_mac[ETH_ALEN] = {0x00, 0x66, 0x77, 0x88, 0x99, 0xaa};
uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
uint8_t buf[BUF_SIZE];
struct ethhdr* eth_hdr = (struct ethhdr*)buf;
uint16_t data_len = sizeof(data);
// 创建Raw Socket
sock_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock_fd < 0) {
perror("socket");
exit(1);
}
// 设置接口名
strncpy(if_name, "eth0", IFNAMSIZ);
// 获取接口索引
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
if (ioctl(sock_fd, SIOCGIFINDEX, &ifr) == -1) {
perror("ioctl");
exit(1);
}
// 设置目的地址和源地址
memcpy(eth_hdr->h_dest, dst_mac, ETH_ALEN);
memcpy(eth_hdr->h_source, src_mac, ETH_ALEN);
// 设置帧类型
eth_hdr->h_proto = htons(data_len);
// 复制数据
memcpy(buf + sizeof(struct ethhdr), data, data_len);
// 填充sockaddr_ll结构体
memset(&saddr, 0, sizeof(saddr));
saddr.sll_family = AF_PACKET;
saddr.sll_ifindex = ifr.ifr_ifindex;
saddr.sll_halen = ETH_ALEN;
memcpy(saddr.sll_addr, dst_mac, ETH_ALEN);
// 发送数据帧
if (sendto(sock_fd, buf, sizeof(struct ethhdr) + data_len, 0, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
perror("sendto");
exit(1);
}
// 关闭Raw Socket
close(sock_fd);
return 0;
}
```
在上面的代码中,我们使用了socket函数创建了一个Raw Socket,然后设置了目的地址、源地址、帧类型和数据,并将这些信息填入了一个以太网帧中。接着,我们使用sendto函数将这个以太网帧发送出去。最后,我们关闭了Raw Socket。
如果要解析收到的以太网帧,可以使用类似下面的代码:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <net/if.h>
#define BUF_SIZE 1024
int main(int argc, char* argv[])
{
int sock_fd;
struct sockaddr_ll saddr;
char if_name[IFNAMSIZ];
uint8_t buf[BUF_SIZE];
struct ethhdr* eth_hdr;
uint8_t* data;
ssize_t recv_len;
// 创建Raw Socket
sock_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock_fd < 0) {
perror("socket");
exit(1);
}
// 设置接口名
strncpy(if_name, "eth0", IFNAMSIZ);
// 获取接口索引
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
if (ioctl(sock_fd, SIOCGIFINDEX, &ifr) == -1) {
perror("ioctl");
exit(1);
}
// 绑定Raw Socket到指定接口
memset(&saddr, 0, sizeof(saddr));
saddr.sll_family = AF_PACKET;
saddr.sll_ifindex = ifr.ifr_ifindex;
saddr.sll_protocol = htons(ETH_P_ALL);
if (bind(sock_fd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
perror("bind");
exit(1);
}
while (1) {
// 接收数据帧
recv_len = recv(sock_fd, buf, BUF_SIZE, 0);
if (recv_len < 0) {
perror("recv");
exit(1);
}
// 解析以太网帧
eth_hdr = (struct ethhdr*)buf;
data = buf + sizeof(struct ethhdr);
printf("Source MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", eth_hdr->h_source[0], eth_hdr->h_source[1], eth_hdr->h_source[2], eth_hdr->h_source[3], eth_hdr->h_source[4], eth_hdr->h_source[5]);
printf("Dest MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", eth_hdr->h_dest[0], eth_hdr->h_dest[1], eth_hdr->h_dest[2], eth_hdr->h_dest[3], eth_hdr->h_dest[4], eth_hdr->h_dest[5]);
printf("Data Length: %d\n", ntohs(eth_hdr->h_proto));
printf("Data: ");
for (int i = 0; i < ntohs(eth_hdr->h_proto); i++) {
printf("%02x ", data[i]);
}
printf("\n");
}
// 关闭Raw Socket
close(sock_fd);
return 0;
}
```
在上面的代码中,我们使用了类似的方法创建了一个Raw Socket,并使用bind函数将其绑定到指定的接口上。然后,我们使用recv函数从Raw Socket中接收数据帧,并解析出其中的以太网帧头和数据部分。最后,我们将解析结果输出到屏幕上。注意,这个程序是一个死循环,一直等待接收数据帧。如果需要退出程序,可以使用Ctrl+C强制退出。
阅读全文