UDP Server
/*
* 函数名称: udp_server_entry
* 作 者: 黄彦杰 Lenn
* 设计日期: 2024-07-26
* 功能描述: udp server
* 返 回 值: 成功返回0,失败返回-1
*/
static int udp_server_entry(__attribute__((unused)) void* arg, uint16_t port, char* ip) {
int sockfd = nsocket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
nbind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
char buffer[UDP_APP_RECV_BUFFER_SIZE] = {0);
socklen_t addrlen = sizeof(addr);
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(struct sockaddr_in));
while (1) {
if (nrecvfrom(sockfd, buffer, UDP_APP_RECV_BUFFER_SIZE, 0, (struct sockaddr*)&clientaddr, &sizeof(clientaddr)) < 0) {
continue;
}
else {
printf("---> recv from %s:%d, data:%s\n", inet_ntoa(clientaddr.sin_addr.s_addr), ntohs(clientaddr.sin_port), buffer);
nsendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr*)&clientaddr, sizeof(clientaddr));
}
}
nclose(sockfd);
}
和前面的unix_udp
比起来流程几乎没有区别。
首先,函数接收一个无用的参数arg(未使用)、端口号port和IP地址ip。
使用nsocket函数创建一个UDP套接字,返回的文件描述符存储在变量sockfd中。如果创建失败,则返回-1。
初始化一个sockaddr_in结构体变量addr,并设置其成员值。其中,sin_family为AF_INET表示IPv4协议族,sin_port为htons(port)将传入的端口号转换为网络字节序,sin_addr.s_addr为inet_addr(ip)将传入的IP地址转换为32位网络字节序整数。
使用nbind函数将套接字绑定到指定地址和端口。
定义一个缓冲区buffer,用于接收数据。大小为UDP_APP_RECV_BUFFER_SIZE(预定义常量)。
定义一个addrlen变量存储客户端地址结构体的长度。
定义一个客户端地址结构体clientaddr,并初始化为空。
进入无限循环,在循环内部进行UDP数据包的接收和回复操作。
使用nrecvfrom函数从套接字sockfd中接收数据到buffer缓冲区中,并记录实际接收到的数据长度。如果接收失败,则继续循环等待下一次接收。
如果成功接收到数据,则打印客户端地址和端口信息,并将数据发送回客户端使用nsendto函数。
循环回到第8步,继续等待接收和回复操作。
当不再需要服务器时,使用nclose函数关闭套接字sockfd。
UDP Thread
int ln_udp_process(struct rte_mbuf* udpmbuf) {
struct rte_ipv4_hdr* iphdr = rte_pktmbuf_mtod_offset(udpmbuf, struct rte_ipv4_hdr*, sizeof(struct rte_ether_hdr));
struct rte_udp_hdr* udphdr = (struct rte_udp_hdr)(iphdr + 1);
struct localhost* host = get_host_fromip_port(iphdr->dst_addr, udphdr->dst_port, iphdr->next_proto_id);
if (host == NULL) {
rte_pktmbuf_free(udpmbuf);
return -1;
}
struct offload* ol = rte_malloc("offload", sizeof(struct offload), 0);
if (ol == NULL) {
rte_pktmbuf_free(udpmbuf);
return -1;
}
ol->sip = iphdr->src_addr;
ol->dip = iphdr->dst_addr;
ol->sport = udphdr->src_port;
ol->dport = udphdr->dst_port;
ol->protocol = IPPROTO_UDP;
ol->length = ntohs(udphdr->dgram_len);
ol->data = rte_malloc("ol data", ol->length - sizeof(struct rte_udp_hdr), 0);
if (ol->data) {
rte_pktmbuf_free(udpmbuf);
rte_free(ol);
return -1;
}
rte_memcpy(ol->data, (uint8_t*)(udphdr + 1), ol->length - sizeof(struct rte_udp_hdr));
rte_ring_mp_enqueue(host->recvbuf, (void**)&ol);
pthread_mutex_lock(&host->mutex);
pthread_cond_broadcast(&host->cond);
pthread_mutex_unlock(&host->mutex);
}
首先,函数接收一个指向rte_mbuf结构体的指针udpmbuf作为参数。
使用rte_pktmbuf_mtod_offset函数将udpmbuf转换为指向ipv4_hdr结构体的指针iphdr,并跳过以太网头部的大小。
使用udphdr指针指向iphdr后面的位置,即udp头部的起始位置。
调用get_host_fromip_port函数根据目标IP地址和目标端口号获取对应的主机信息(localhost结构体)。如果找不到对应主机,则释放udpmbuf并返回-1。
分配一个offload结构体ol,并检查内存分配是否成功。如果失败,则释放udpmbuf并返回-1。
将源IP地址、目标IP地址、源端口号、目标端口号等信息保存到offload结构体ol中。
设置协议类型为UDP,设置数据长度为从udphdr中提取出来的UDP数据报长度(通过ntohs函数进行字节序转换)。
分配足够大小的内存空间给ol->data,用于存储除去UDP头部之外的UDP数据。如果内存分配失败,则释放udpmbuf和ol,并返回-1。
使用rte_memcpy将数据从udphdr + 1复制到ol->data中,长度为ol->length减去sizeof(struct rte_udp_hdr)。
使用rte_ring_mp_enqueue函数将ol指针放入主机接收缓冲区recvbuf中。
使用互斥锁和条件变量进行线程同步,保证对主机信息的访问是安全的。
UDP 数据包输出
数据包输出
和数据输出
是不同的,这里是将数据包放入ring buffer
等待消费者线程将数据包发送给网卡。
int ln_udp_out(struct rte_mempool* mbuf_pool) {
struct localhost* host;
for(host = lhost; host != NULL; host = host->next) {
struct offload* ol;
rte_ring_mc_dequeue(host->sendbuf, (void**)&ol);
struct in_addr addr;
addr.s_addr = ol->dip;
printf("---> udp_out dst %s:%d %s\n", inet_ntoa(addr), ntohs(ol->dport), ol->data);
uint8_t* dmac = get_mac_from_arp(ol->dip);
if (dmac == NULL) {
struct rte_mbuf* arpbuf = ln_arp_send(mbuf_pool, RTE_ARP_OP_REQUEST, nDefaultArpMac, ol->sip, ol->dip);
struct inout_ring* ring = get_ioring_instance();
rte_ring_mp_enqueue_burst(ring->out, (void**)&arpbuf, 1, NULL);
rte_ring_mp_enqueue(host->sendbuf, ol);
}
else {
struct inout_ring* ring = get_ioring_instance();
struct rte_mbuf* udpbuf = ln_udp_send(mbuf_pool, ol->data, strlen(ol->data));
rte_ring_mp_enqueue_burst(ring->out, (void**)&udpbuf, 1, NULL);
}
}
return 0;
}
首先,函数接收一个rte_mempool结构体指针mbuf_pool作为参数。
定义一个指向localhost结构体的指针host,并从全局变量lhost开始遍历链表。该链表可能包含多个localhost节点。
进入循环,在每次循环中处理一个localhost节点。
定义一个指向offload结构体的指针ol,并使用rte_ring_mc_dequeue函数从host->sendbuf中出队(出队时会将数据存储在ol指针中)。
创建一个in_addr结构体addr,并将其中的s_addr成员设置为ol->dip(目标IP地址)。
使用inet_ntoa函数将目标IP地址转换为字符串格式,并与ol->dport(目标端口号)和ol->data(数据内容)一起打印输出。
调用get_mac_from_arp函数,传入ol->dip(目标IP地址),获取目标MAC地址dmac。
如果无法获取到目标MAC地址,则说明需要发送ARP请求来解析目标MAC地址。
使用ln_arp_send函数创建一个ARP请求的rte_mbuf缓冲区arpbuf,并传入必要的参数。nDefaultArpMac表示本机默认网关的MAC地址,ol->sip是源IP地址,ol->dip是目标IP地址。
通过get_ioring_instance函数获取inout_ring实例ring,并使用rte_ring_mp_enqueue_burst函数将arpbuf缓冲区中的数据批量入队到ring->out队列中。
使用rte_ring_mp_enqueue函数将ol指针重新入队到host->sendbuf中。
如果能够获取到目标MAC地址,则说明可以直接发送UDP数据包。
通过get_ioring_instance函数获取inout_ring实例ring。
使用ln_udp_send函数创建一个UDP数据包的rte_mbuf缓冲区udpbuf,传入ol->data和strlen(ol->data)作为参数。
使用rte_ring_mp_enqueue_burst函数将udpbuf缓冲区中的数据批量入队到ring->out队列中。
循环回到第4步,处理下一个localhost节点。
返回0表示UDP发送函数执行成功结束。
组织UDP APP数据包
/*
* 函数名称: ln_udp_pkt_encode
* 作 者: 黄彦杰 Lenn
* 设计日期: 2024-07-29
* 功能描述: 组织udp_pkt数据包
* 返 回 值: 0
*/
static int ln_udp_pkt_encode(uint8_t* pktbuf, uint32_t sip, uint32_t dip, uint16_t sport, uint16_t dport,
uint8_t* smac, uint8_t* dmac, uint8_t* data, uint16_t total_len) {
struct rte_ether_hdr* ehdr = (struct rte_ether_hdr*)pktbuf;
rte_memcpy(ehdr->d_addr.addr_bytes, dmac, RTE_ETHER_ADDR_LEN);
rte_memcpy(ehdr->s_addr.addr_bytes, smac, RTE_ETHER_ADDR_LEN);
ehdr->ether_type = htons(RTE_ETHER_TYPE_IPV4);
struct rte_ipv4_hdr* iphdr = (struct rte_ipv4_hdr*)(ehdr + 1);
iphdr->version_ihl = 0x45;
iphdr->type_of_service = 0;
iphdr->total_length = htons(total_len - sizeof(struct rte_ether_hdr));
iphdr->packet_id = 0;
iphdr->fragment_offset = 0;
iphdr->time_to_live = 64;
iphdr->next_proto_id = IPPROTO_UDP;
iphdr->src_addr = sip;
iphdr->dst_addr = dip;
iphdr->hdr_checksum = 0;
iphdr->hdr_checksum = rte_ipv4_cksum(iphdr);
struct rte_udp_hdr* udphdr = (struct rte_ipv4_hdr*)(iphdr + 1);
udphdr->src_port = sport;
udphdr->dst_port = dport;
uint16_t udp_len = total_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
udphdr->dgram_len = htons(udp_len);
rte_memcpy((uint8_t*)(udphdr + 1), data, udp_len - sizeof(struct rte_udp_hdr));
udphdr->dgram_cksum = 0;
udphdr->dgram_cksum = rte_ipv4_udptcp_cksum(iphdr, udphdr);
return 0;
}
struct rte_mbuf* ln_udp_pkt_send(struct rte_mempool* mbuf_pool, uint32_t sip, uint32_t dip,
uint16_t sport, uint16_t dport, uint8_t* smac, uint8_t* dmac, uint8_t* msg, uint16_t length) {
const unsigned total_len = length + 42;
struct rte_mbuf* mbuf = rte_pktmbuf_alloc(mbuf_pool);
if (!mbuf) {
rte_exit(EXIT_FAILURE, "Alloc udp app mbuf failed");
}
mbuf->pkt_len = total_len;
mbuf->data_len = total_len;
uint8_t* pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*);
ln_udp_pkt_encode(pktdata, sip, dip, sport, dport, smac, dmac, msg, total_len);
return mbuf;
}
- 增加几个参数,配合
udp_server
使用。
udp线程
效果演示
- 段错误,明天调。
评论