十、DPDK协议栈之ddos和epoll

同样,我们在之前的代码中进行迭代,增加ddos和epoll实现并发的功能

DDOS

ddos这里从网上“借鉴”一下,不过多的去梳理,能用就行
代码如下:

#if ENABLE_DDOS_DETECT
// 如果启用DDoS攻击检测

#define CAPTURE_WINDOWS		256

static double tresh = 1200.0;  // 阈值,用于判断DDoS攻击的阈值

static uint32_t p_setbits[CAPTURE_WINDOWS] = {0};  // 存储每个窗口的设置位数
static uint32_t p_totbits[CAPTURE_WINDOWS] = {0};  // 存储每个窗口的总位数
static double p_entropy[CAPTURE_WINDOWS] = {0};    // 存储每个窗口的熵
static int pkt_idx = 0;  // 当前窗口的索引

/*
 23651/17408, E(nan)
 1805328/4456448 Entropy(nan), Total_Entropy(4339971.575790)
 
  23526/17408, E(nan)
 1805203/4456448 Entropy(nan), Total_Entropy(4339902.272671)
 
  17847/17408, E(nan)
 1799524/4456448 Entropy(nan), Total_Entropy(4336731.547140)
 
  17670/17408, E(nan)
 1799347/4456448 Entropy(nan), Total_Entropy(4336632.027009)
 
  17774/17408, E(nan)
 1799451/4456448 Entropy(nan), Total_Entropy(4336690.507219)
 
 */ 
// 一些示例数据,可能是用于调试和演示的数据

static double ddos_entropy(double set_bits, double total_bits) {
	// 计算熵的函数
	return ( - set_bits) * (log2(set_bits) - log2(total_bits)) // 1 
	- (total_bits - set_bits) * (log2(total_bits - set_bits) - log2(total_bits))
	+ log2(total_bits);
}

static uint32_t count_bit(uint8_t *msg, const uint32_t length) {
	// 计算位数的函数,根据输入消息和长度返回设置的位数
}

static int ddos_detect(struct rte_mbuf *pkt) {
	// DDoS攻击检测函数,用于检测DDoS攻击

	static char flag = 0; // 1表示DDoS攻击,0表示没有攻击

	uint8_t *msg = rte_pktmbuf_mtod(pkt, uint8_t *);
	uint32_t set_bits = count_bit(msg, pkt->buf_len);  // 计算消息中的设置位数
	uint32_t tot_bits = pkt->buf_len * 8;  // 消息的总位数

	p_setbits[pkt_idx % CAPTURE_WINDOWS] = set_bits;  // 存储设置位数
	p_totbits[pkt_idx % CAPTURE_WINDOWS] = tot_bits;  // 存储总位数
	p_entropy[pkt_idx % CAPTURE_WINDOWS] = ddos_entropy(set_bits, tot_bits);  // 存储熵值

	if (pkt_idx >= CAPTURE_WINDOWS) {
		// 如果达到了窗口大小

		int i = 0;
		uint32_t total_set = 0, total_bit = 0;
		double sum_entropy = 0.0;

		for (i = 0; i < CAPTURE_WINDOWS; i++) {
			total_set += p_setbits[i]; // 累计设置位数
			total_bit += p_totbits[i]; // 累计总位数
			sum_entropy += p_entropy[i]; // 累计熵值
		}

		double entropy = ddos_entropy(total_set, total_bit); // 计算总熵值

		if (tresh <  sum_entropy - entropy) { // 如果总熵值超过阈值,表示DDoS攻击
			if (!flag) { // 如果之前没有检测到攻击
				rte_exit(EXIT_FAILURE, "ddos attack !!! Entropy(%f) < Total_Entropy(%f)\n", 
					entropy, sum_entropy); // 输出攻击信息并退出
			}
			flag = 1; // 设置攻击标志
		} else {
			if (flag) { // 如果之前检测到攻击
				printf("no new !!! Entropy(%f) < Total_Entropy(%f)\n", 
					entropy, sum_entropy); // 输出没有新攻击的信息
			}
			flag = 0; // 清除攻击标志
		}

		pkt_idx = (pkt_idx + 1) % CAPTURE_WINDOWS + CAPTURE_WINDOWS; // 更新窗口索引
	} else {
		pkt_idx++; // 增加窗口索引
	}

	return 0; // 返回检测结果
}

#endif

main函数中通过调用ddos_detect进行防ddos攻击

#if ENABLE_DDOS_DETECT
		
			unsigned i = 0;
			for (i = 0;i < num_recvd;i ++) {
				ddos_detect(rx[i]);
			}
#endif

EPOLL

在之前的文章中,我们已经实现了完整的协议栈,但是只能用于处理一个客户端的请求,接下来我们就在原有的代码中增加epoll来实现并发的功能

先定义几个用于epoll的结构体

typedef union epoll_data {
    void *ptr;        // 用于存储一个指针,用于关联自定义数据
    int fd;           // 用于存储一个文件描述符
    uint32_t u32;     
    uint64_t u64;     
} epoll_data_t;



// 定义一个名为 'struct epoll_event' 的数据结构,用于描述 epoll 事件的信息

struct epoll_event {
    uint32_t events;  // 表示事件的类型和状态,可以是多个标志的组合

    epoll_data_t data;  // 存储与事件相关的数据,可以是一个指针或文件描述符
};

// 表示 epoll 实例中的事件项

struct epitem {
    RB_ENTRY(epitem) rbn;        // 红黑树节点,用于按套接字进行快速查找
    LIST_ENTRY(epitem) rdlink;   // 链表节点,用于存储就绪的事件项
    int rdy;                     // 是否在链表中的标志(存在于链表中则为非零)

    int sockfd;                  // 套接字描述符
    struct epoll_event event;    // 关联的 epoll 事件
};
// 定义一个名为 'struct eventpoll' 的数据结构,用于管理 epoll 实例的状态

struct eventpoll {
    int fd;  // epoll 实例的文件描述符(fd)

    ep_rb_tree rbr;  // 红黑树,用于存储注册的事件
    int rbcnt;  // 红黑树节点计数

    LIST_HEAD( ,epitem) rdlist;  // 链表,用于存储就绪的事件项
    int rdnum;  // 就绪事件项的数量

    int waiting;  // 表示是否有线程正在等待事件的发生

    pthread_mutex_t mtx;  // 用于对红黑树进行更新的互斥锁
    pthread_spinlock_t lock;  // 用于对就绪事件链表进行更新的自旋锁

    pthread_cond_t cond;  // 用于线程等待事件的条件变量
    pthread_mutex_t cdmtx;  // 用于条件变量的互斥锁
};

接下来是实现epoll的主要函数

#if ENABLE_SINGLE_EPOLL
// epoll 事件回调函数,用于处理套接字事件

int epoll_event_callback(struct eventpoll *ep, int sockid, uint32_t event) {

	// 创建一个临时的 epitem 结构体,用于查找对应套接字的事件项
	struct epitem tmp;
	tmp.sockfd = sockid;
	struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);
	if (!epi) {
		printf("rbtree not exist\n"); // 如果找不到对应的事件项,输出错误信息
		return -1;
	}

	if (epi->rdy) {
		// 如果事件项已经在就绪链表中,更新事件类型并返回 1
		epi->event.events |= event;
		return 1;
	} 

	printf("epoll_event_callback --> %d\n", epi->sockfd);

	// 如果事件项不在就绪链表中,加锁处理
	pthread_spin_lock(&ep->lock);

	epi->rdy = 1; // 设置事件项为就绪状态
	LIST_INSERT_HEAD(&ep->rdlist, epi, rdlink); // 将事件项添加到就绪链表头部
	ep->rdnum ++; // 增加就绪事件数量

	pthread_spin_unlock(&ep->lock); // 解锁就绪链表

	pthread_mutex_lock(&ep->cdmtx); // 加锁条件变量互斥锁

	pthread_cond_signal(&ep->cond); // 发送条件信号,通知等待的线程有新事件就绪
	pthread_mutex_unlock(&ep->cdmtx); // 解锁条件变量互斥锁
}
// 创建一个新的 epoll 实例

int nepoll_create(int size) {
    // 参数检查
	if (size <= 0) return -1;

	// 从位图中获取一个可用的文件描述符(fd),用于 epoll 实例
	int epfd = get_fd_frombitmap(); //tcp, udp
	
	// 为 epoll 实例分配内存
	struct eventpoll *ep = (struct eventpoll*)rte_malloc("eventpoll", sizeof(struct eventpoll), 0);
	if (!ep) {
        // 如果内存分配失败,释放已分配的 fd 并返回错误
		set_fd_frombitmap(epfd);
		return -1;
	}

	// 获取全局的 TCP 表格实例,并将 epoll 实例关联到其中
	struct ng_tcp_table *table = tcpInstance();
	table->ep = ep;
	
	// 初始化 epoll 实例的各个字段
	ep->fd = epfd;
	ep->rbcnt = 0;
	RB_INIT(&ep->rbr);
	LIST_INIT(&ep->rdlist);

	// 初始化 epoll 实例的互斥锁
	if (pthread_mutex_init(&ep->mtx, NULL)) {
		free(ep);
		set_fd_frombitmap(epfd);
		return -2;
	}

	// 初始化 epoll 实例的条件变量互斥锁
	if (pthread_mutex_init(&ep->cdmtx, NULL)) {
		pthread_mutex_destroy(&ep->mtx);
		free(ep);
		set_fd_frombitmap(epfd);
		return -2;
	}

	// 初始化 epoll 实例的条件变量
	if (pthread_cond_init(&ep->cond, NULL)) {
		pthread_mutex_destroy(&ep->cdmtx);
		pthread_mutex_destroy(&ep->mtx);
		free(ep);
		set_fd_frombitmap(epfd);
		return -2;
	}

	// 初始化 epoll 实例的自旋锁
	if (pthread_spin_init(&ep->lock, PTHREAD_PROCESS_SHARED)) {
		pthread_cond_destroy(&ep->cond);
		pthread_mutex_destroy(&ep->cdmtx);
		pthread_mutex_destroy(&ep->mtx);
		free(ep);
		set_fd_frombitmap(epfd);
		return -2;
	}

	// 返回创建的 epoll 实例的文件描述符(fd)
	return epfd;
}
// 管理 epoll 实例中的事件关联

int nepoll_ctl(int epfd, int op, int sockid, struct epoll_event *event) {
	
    // 从文件描述符获取 epoll 实例的信息
	struct eventpoll *ep = (struct eventpoll*)get_hostinfo_fromfd(epfd);
	
    // 如果 epoll 实例无效或事件为空且操作不是删除操作,返回错误
	if (!ep || (!event && op != EPOLL_CTL_DEL)) {
		errno = -EINVAL; // 设置错误码为无效参数
		return -1;
	}

    // 如果操作是添加操作
	if (op == EPOLL_CTL_ADD) {
        // 获取 epoll 实例的互斥锁,用于保护对 epoll 实例的操作
		pthread_mutex_lock(&ep->mtx);

		// 创建一个临时的事件项,用于查找是否已存在相同的套接字
		struct epitem tmp;
		tmp.sockfd = sockid;
		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);

		// 如果已经存在相同的套接字,解锁互斥锁并返回错误
		if (epi) {
			pthread_mutex_unlock(&ep->mtx);
			return -1;
		}

		// 分配内存以存储事件项
		epi = (struct epitem*)rte_malloc("epitem", sizeof(struct epitem), 0);

        // 如果内存分配失败,解锁互斥锁并返回错误
		if (!epi) {
			pthread_mutex_unlock(&ep->mtx);
			rte_errno = -ENOMEM; // 设置错误码为内存不足
			return -1;
		}
		
		epi->sockfd = sockid;
		memcpy(&epi->event, event, sizeof(struct epoll_event));

        // 在红黑树中插入事件项,表示注册了新的套接字
		epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, epi);

        // 增加 epoll 实例中的事件项计数
		ep->rbcnt++;
		
        // 解锁互斥锁
		pthread_mutex_unlock(&ep->mtx);

	} else if (op == EPOLL_CTL_DEL) {
        // 如果操作是删除操作

        // 获取 epoll 实例的互斥锁
		pthread_mutex_lock(&ep->mtx);

		// 创建一个临时的事件项,用于查找要删除的套接字
		struct epitem tmp;
		tmp.sockfd = sockid;
		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);

        // 如果未找到要删除的套接字,解锁互斥锁并返回错误
		if (!epi) {
			pthread_mutex_unlock(&ep->mtx);
			return -1;
		}
		
        // 从红黑树中移除事件项,表示取消了套接字的注册
		epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, epi);
		if (!epi) {
			
			pthread_mutex_unlock(&ep->mtx);
			return -1;
		}
        // 减少 epoll 实例中的事件项计数
		ep->rbcnt--;

        // 释放事件项的内存
		free(epi);
		
        // 解锁互斥锁
		pthread_mutex_unlock(&ep->mtx);

	} else if (op == EPOLL_CTL_MOD) {
        // 如果操作是修改操作

        // 创建一个临时的事件项,用于查找要修改的套接字
		struct epitem tmp;
		tmp.sockfd = sockid;
		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);

        // 如果找到要修改的套接字
		if (epi) {
            // 更新套接字的事件类型
			epi->event.events = event->events;
            // 同时添加 EPOLLERR 和 EPOLLHUP 标志,表示关注错误和挂起事件
			epi->event.events |= EPOLLERR | EPOLLHUP;
		} else {
			rte_errno = -ENOENT; // 设置错误码为未找到
			return -1;
		}

	} 

	return 0; // 操作成功,返回 0
}

// 等待 epoll 事件的发生
int nepoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) {
	
	// 从文件描述符获取 epoll 实例的信息
	struct eventpoll *ep = (struct eventpoll*)get_hostinfo_fromfd(epfd);
	
	// 如果 epoll 实例无效、事件数组为空或 maxevents 不合法,返回错误
	if (!ep || !events || maxevents <= 0) {
		rte_errno = -EINVAL; // 设置错误码为无效参数
		return -1;
	}

	// 尝试获取 epoll 实例的条件变量互斥锁
	if (pthread_mutex_lock(&ep->cdmtx)) {
		// 如果获取锁失败,根据错误码输出相应信息
		if (rte_errno == EDEADLK) {
			printf("epoll lock blocked\n"); // 输出锁阻塞信息
		}
	}

	// 当没有就绪的事件项并且超时时间不为零时,进入等待循环
	while (ep->rdnum == 0 && timeout != 0) {

		ep->waiting = 1; // 表示有线程正在等待事件的发生
		if (timeout > 0) {
			// 计算绝对超时时间

			struct timespec deadline;

			clock_gettime(CLOCK_REALTIME, &deadline);
			if (timeout >= 1000) {
				int sec;
				sec = timeout / 1000;
				deadline.tv_sec += sec;
				timeout -= sec * 1000;
			}

			deadline.tv_nsec += timeout * 1000000;

			if (deadline.tv_nsec >= 1000000000) {
				deadline.tv_sec++;
				deadline.tv_nsec -= 1000000000;
			}

			// 使用条件变量的超时等待
			int ret = pthread_cond_timedwait(&ep->cond, &ep->cdmtx, &deadline);
			if (ret && ret != ETIMEDOUT) {
				printf("pthread_cond_timewait\n");
				pthread_mutex_unlock(&ep->cdmtx);
				return -1;
			}
			timeout = 0; // 清除超时标志
		} else if (timeout < 0) {
			// 使用条件变量的等待,直到事件发生
			int ret = pthread_cond_wait(&ep->cond, &ep->cdmtx);
			if (ret) {
				printf("pthread_cond_wait\n");
				pthread_mutex_unlock(&ep->cdmtx);
				return -1;
			}
		}
		ep->waiting = 0; // 表示等待结束
	}

	// 解锁条件变量互斥锁
	pthread_mutex_unlock(&ep->cdmtx);

	// 使用自旋锁对就绪事件链表进行操作
	pthread_spin_lock(&ep->lock);

	int cnt = 0; // 记录就绪事件的数量
	int num = (ep->rdnum > maxevents ? maxevents : ep->rdnum); // 计算最多处理的事件数
	int i = 0;
	
	// 遍历就绪事件链表,将事件拷贝到用户提供的事件数组中
	while (num != 0 && !LIST_EMPTY(&ep->rdlist)) {

		struct epitem *epi = LIST_FIRST(&ep->rdlist);
		LIST_REMOVE(epi, rdlink);
		epi->rdy = 0;

		memcpy(&events[i++], &epi->event, sizeof(struct epoll_event));
		
		num --;
		cnt ++;
		ep->rdnum --;
	}
	
	// 解锁自旋锁
	pthread_spin_unlock(&ep->lock);

	return cnt; // 返回处理的就绪事件数量
}


#endif

接下来修改我们之前的tcp服务器入口函数

// TCP 服务器入口函数

static int tcp_server_entry(__attribute__((unused))  void *arg)  {
	
	// 创建 TCP 套接字用于监听连接请求
	int listenfd = nsocket(AF_INET, SOCK_STREAM, 0);
	if (listenfd == -1) {
		return -1;
	}

	// 配置服务器监听地址
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(struct sockaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(9999);
	
	// 将套接字绑定到监听地址
	nbind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

	// 监听连接请求,最大等待队列长度为 10
	nlisten(listenfd, 10);

	// 创建 epoll 实例,用于事件管理
	int epfd = nepoll_create(1); // event poll

	// 初始化要监听的事件,关注可读事件
	struct epoll_event ev, events[128];
	ev.events = EPOLLIN;
	ev.data.fd = listenfd;

	// 将监听套接字添加到 epoll 实例中
	nepoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
	
	char buff[BUFFER_SIZE] = {0};
	while(1) {

		// 使用 epoll 等待就绪事件
		int nready = nepoll_wait(epfd, events, 128, 5);
		if (nready < 0) continue;
		
		int i = 0;
		for (i = 0;i < nready;i ++) {

			if (listenfd == events[i].data.fd) {
				// 如果监听套接字有事件,表示有新的客户端连接请求

				struct sockaddr_in client;
				socklen_t len = sizeof(client);

				// 接受客户端连接请求,创建新的连接套接字
				int connfd = naccept(listenfd, (struct sockaddr*)&client, &len);

				// 初始化要监听的事件,关注可读事件
				struct epoll_event ev;
				ev.events = EPOLLIN;
				ev.data.fd = connfd;

				// 将连接套接字添加到 epoll 实例中
				nepoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

			} else { // clientfd

				int connfd = events[i].data.fd;
				
				// 接收客户端发送的数据
				int n = nrecv(connfd, buff, BUFFER_SIZE, 0); // 阻塞

				if (n > 0) {
					// 如果成功接收数据,打印并回传给客户端
					printf("recv: %s\n", buff);
					nsend(connfd, buff, n, 0);

				} else {

					// 如果连接断开,从 epoll 实例中删除连接套接字并关闭连接
					nepoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
					nclose(connfd);
					
				} 

			}

		}
	}

}

这次更新完,代码就有点多了,不在这里展示了,完整链接,方便自己以后回顾

结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写一封情书

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值