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比起来流程几乎没有区别。

  1. 首先,函数接收一个无用的参数arg(未使用)、端口号port和IP地址ip。

  2. 使用nsocket函数创建一个UDP套接字,返回的文件描述符存储在变量sockfd中。如果创建失败,则返回-1。

  3. 初始化一个sockaddr_in结构体变量addr,并设置其成员值。其中,sin_family为AF_INET表示IPv4协议族,sin_port为htons(port)将传入的端口号转换为网络字节序,sin_addr.s_addr为inet_addr(ip)将传入的IP地址转换为32位网络字节序整数。

  4. 使用nbind函数将套接字绑定到指定地址和端口。

  5. 定义一个缓冲区buffer,用于接收数据。大小为UDP_APP_RECV_BUFFER_SIZE(预定义常量)。

  6. 定义一个addrlen变量存储客户端地址结构体的长度。

  7. 定义一个客户端地址结构体clientaddr,并初始化为空。

  8. 进入无限循环,在循环内部进行UDP数据包的接收和回复操作。

  9. 使用nrecvfrom函数从套接字sockfd中接收数据到buffer缓冲区中,并记录实际接收到的数据长度。如果接收失败,则继续循环等待下一次接收。

  10. 如果成功接收到数据,则打印客户端地址和端口信息,并将数据发送回客户端使用nsendto函数。

  11. 循环回到第8步,继续等待接收和回复操作。

  12. 当不再需要服务器时,使用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);

}
  1. 首先,函数接收一个指向rte_mbuf结构体的指针udpmbuf作为参数。

  2. 使用rte_pktmbuf_mtod_offset函数将udpmbuf转换为指向ipv4_hdr结构体的指针iphdr,并跳过以太网头部的大小。

  3. 使用udphdr指针指向iphdr后面的位置,即udp头部的起始位置。

  4. 调用get_host_fromip_port函数根据目标IP地址和目标端口号获取对应的主机信息(localhost结构体)。如果找不到对应主机,则释放udpmbuf并返回-1。

  5. 分配一个offload结构体ol,并检查内存分配是否成功。如果失败,则释放udpmbuf并返回-1。

  6. 将源IP地址、目标IP地址、源端口号、目标端口号等信息保存到offload结构体ol中。

  7. 设置协议类型为UDP,设置数据长度为从udphdr中提取出来的UDP数据报长度(通过ntohs函数进行字节序转换)。

  8. 分配足够大小的内存空间给ol->data,用于存储除去UDP头部之外的UDP数据。如果内存分配失败,则释放udpmbuf和ol,并返回-1。

  9. 使用rte_memcpy将数据从udphdr + 1复制到ol->data中,长度为ol->length减去sizeof(struct rte_udp_hdr)。

  10. 使用rte_ring_mp_enqueue函数将ol指针放入主机接收缓冲区recvbuf中。

  11. 使用互斥锁和条件变量进行线程同步,保证对主机信息的访问是安全的。

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;
}
  1. 首先,函数接收一个rte_mempool结构体指针mbuf_pool作为参数。

  2. 定义一个指向localhost结构体的指针host,并从全局变量lhost开始遍历链表。该链表可能包含多个localhost节点。

  3. 进入循环,在每次循环中处理一个localhost节点。

  4. 定义一个指向offload结构体的指针ol,并使用rte_ring_mc_dequeue函数从host->sendbuf中出队(出队时会将数据存储在ol指针中)。

  5. 创建一个in_addr结构体addr,并将其中的s_addr成员设置为ol->dip(目标IP地址)。

  6. 使用inet_ntoa函数将目标IP地址转换为字符串格式,并与ol->dport(目标端口号)和ol->data(数据内容)一起打印输出。

  7. 调用get_mac_from_arp函数,传入ol->dip(目标IP地址),获取目标MAC地址dmac。

  8. 如果无法获取到目标MAC地址,则说明需要发送ARP请求来解析目标MAC地址。

  9. 使用ln_arp_send函数创建一个ARP请求的rte_mbuf缓冲区arpbuf,并传入必要的参数。nDefaultArpMac表示本机默认网关的MAC地址,ol->sip是源IP地址,ol->dip是目标IP地址。

  10. 通过get_ioring_instance函数获取inout_ring实例ring,并使用rte_ring_mp_enqueue_burst函数将arpbuf缓冲区中的数据批量入队到ring->out队列中。

  11. 使用rte_ring_mp_enqueue函数将ol指针重新入队到host->sendbuf中。

  12. 如果能够获取到目标MAC地址,则说明可以直接发送UDP数据包。

  13. 通过get_ioring_instance函数获取inout_ring实例ring。

  14. 使用ln_udp_send函数创建一个UDP数据包的rte_mbuf缓冲区udpbuf,传入ol->data和strlen(ol->data)作为参数。

  15. 使用rte_ring_mp_enqueue_burst函数将udpbuf缓冲区中的数据批量入队到ring->out队列中。

  16. 循环回到第4步,处理下一个localhost节点。

  17. 返回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线程

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240729154044.png

效果演示

https://imagehyj.oss-cn-hangzhou.aliyuncs.com/blog/20240729163411.png

  • 段错误,明天调。

项目地址

项目地址