Query Name Blacklist是抵抗对DNS服务器的DDoS攻击的一种filter,攻击者在发起攻击时通常使用形如www.FIXED-QUERY.com或RANDOM-STRING.FIXED-SUFFIX.com的域名,即这些攻击域名中存在一定共同部分,通过发现这些共同部分,可以将攻击请求在到达DNS服务器真正被处理之前过滤掉,从而达到抵抗DDoS的目的。Query Name Blacklist的思路来自DDIDD项目DDoS Defense In Depth for DNS (DDIDD)
分为三个环节
存在以下问题
首先尝试实现DDoS的模拟,
方案一:使用trafgen,配置文件如下
# full random
{
0x02, 0x42, 0xac, 0x11, 0x00, 0x02 # MAC Destination
drnd(6), # MAC Source
const16(0x0800),
/* IPv4 Version, IHL, TOS */
0b01000101, 0,
/* IPv4 Total Len */
const16(56),
/* IPv4 Ident */
drnd(2),
/* IPv4 Flags, Frag Off */
0b01000000, 0,
/* IPv4 TTL */
64,
/* Proto UDP */
0x11,
/* IPv4 Checksum (IP header from, to) */
csumip(14, 33),
/* NEED ADJUST */
172, 17, 0, 3, # Source IP
172, 17, 0, 2, # Dest IP
# UDP
drnd(2), const16(33340),
const16(36), csumudp(14,34),
# DNS ID
drnd(2),
# Flags
0x01, 0x00,
# Questions
0x00, 0x01,
# Answer RRs
0x00, 0x00,
#Authoritative RRs
0x00, 0x00,
#Aditional RRs
0x00, 0x00,
# Questions Start
0x03, "www",
0x07, drnd(7),
0x00, 0x01,
0x00, 0x01
}
# name fixed
{
0x02, 0x42, 0xac, 0x11, 0x00, 0x02 # MAC Destination
0x02, 0x42, 0xac, 0x11, 0x00, 0x03, # MAC Source
const16(0x0800),
/* IPv4 Version, IHL, TOS */
0b01000101, 0,
/* IPv4 Total Len */
const16(56),
/* IPv4 Ident */
drnd(2),
/* IPv4 Flags, Frag Off */
0b01000000, 0,
/* IPv4 TTL */
64,
/* Proto UDP */
0x11,
/* IPv4 Checksum (IP header from, to) */
csumip(14, 33),
/* NEED ADJUST */
drnd(4), # Source IP
172, 17, 0, 2, # Dest IP
# UDP
drnd(2), const16(33340),
const16(40), csumudp(14,34),
# DNS ID
drnd(2),
# Flags
0x01, 0x00,
# Questions
0x00, 0x01,
# Answer RRs
0x00, 0x00,
#Authoritative RRs
0x00, 0x00,
#Aditional RRs
0x00, 0x00,
# Questions Start
0x03, "www",
0x07, "fxddmnm",
0x03, "com",
0x00, 0x01,
0x00, 0x01
}
缺点是随机功能不好用,无法限制范围,导致源mac随机的结果可能非法,不能发送,DNS Question内容也很大可能非法。优点是高性能,明确使用多核。
方案二:python scapy
RandMAC,RandIP,RandString函数解决了trafgen随机的缺陷之处,一个简单的将重复次数和随机部分长度作为参数的py脚本见下
from scapy.all import Ether, IP, UDP, sendp, hexdump, RandMAC, RandIP
from scapy.layers.dns import DNS, DNSQR
from scapy.volatile import RandString, RandShort
import getopt
import sys
import time
t1 = time.perf_counter()
n = 3
l = 7
try:
opts, args = getopt.getopt(sys.argv[1:],"n:l:")
except getopt.GetoptError:
print("Illegal Opts")
sys.exit(2)
for opt, arg in opts:
if opt == '-n':
n = int(arg)
elif opt == '-l':
l = int(arg)
pack_list = []
for i in range(n):
randdns = \
Ether(dst='02:42:ac:11:00:03', src=RandMAC())/\
IP(dst='172.17.0.3', src=RandIP())/\
UDP(dport=33340)/\
DNS(id=1, qr=0, opcode=0, tc=0, rd=1, qdcount=1,ancount=0,nscount=0,arcount=0,
qd=DNSQR(qname='www.'+str(RandString(l))+'.com',qtype=1,qclass=1))
pack_list.append(randdns)
t2 = time.perf_counter()
print("Pack Gen Interval:", (t2-t1)*1000000)
sendp(pack_list)
t3 = time.perf_counter()
print("Pack Send Interval:", (t3-t2)*1000000)
print("Total Interval:", (t3-t1)*1000000)
需要注意的是RandXXX函数返回的是scapy的volatile对象,每次取值(包括对构建时使用RandXXX的包调用send/sendp)值都会变化。
fullrand发送1000个包(70Byte/packet)
trafgen:32us == 17500Gb/s
scapy:451624us == 1.24Gb/s
发送10000个包
trafgen:2379us == 2353.9Gb/s
scapy:3325426us == 1.69Gb/s
发送100000个包
trafgen:32108us == 1744.1Gb/s
scapy:33156602us == 1.69Gb/s
scapy提升性能可能需要引入多线程
使用libpcap实现
/* qname_cap.c */
#include <pcap.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include "dns.h"
// save函数可修改为存入文件等
void save(const char * str, int len){
printf("%s|%d\n", str, len);
}
void save_qr_raw(const u_char *packet, int packlen){
void * ptr = (void *)packet;
// ETH Header
uint16_t e_type;
struct ethhdr *eth;
eth = (struct ethhdr *)ptr;
e_type = ntohs(eth->h_proto);
ptr = (void *)((struct ethhdr *)ptr + 1);
if (e_type != ETH_P_IP) {
return;
}
// IP Header
struct iphdr *ip;
ip = (struct iphdr *)ptr;
e_type = ntohs(ip->protocol);
ptr = ptr + ip->ihl*4;
// UDP Header
struct udphdr *udp;
udp = (struct udphdr *)ptr;
ptr = (void *)((struct udphdr *)ptr + 1);
// DNS
struct dnshdr *dns;
dns = (struct dnshdr *)ptr;
dnsqr_list ql = get_qr_list(dns);
dnsqr_list head = ql;
if(!ql){
return;
}
while(ql){
save(ql->qn.start, ql->qn.len);
ql = ql->next;
}
free_qr_list(head);
}
void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
{
int * id = (int *)arg;
printf("id: %d\n", ++(*id));
printf("Packet length: %d\n", pkthdr->len);
printf("Number of bytes: %d\n", pkthdr->caplen);
printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));
int i;
for(i=0; i<pkthdr->len; ++i)
{
printf(" %02x", packet[i]);
if( (i + 1) % 16 == 0 )
{
printf("\n");
}
}
printf("\n\n");
save_qr_raw(packet, pkthdr->caplen);
printf("\n\n");
}
int main()
{
char errBuf[PCAP_ERRBUF_SIZE];
pcap_if_t *it;
int r;
/* get a device */
r = pcap_findalldevs(&it, errBuf);
if(r == -1){
return -1;
}
while(it){
if(!strcmp(it->name, "eth0")){
break;
}
it = it->next;
}
if(!it)
return 0;
/* open a device, wait until a packet arrives */
//pcap_t * device = pcap_open_offline("test.pcap",errBuf); //读取本地文件作为网络包数据
pcap_t * device = pcap_open_live(it->name, 65535, 1, 5000, errBuf);
if(!device)
{
printf("error: pcap_open_live(): %s\n", errBuf);
exit(1);
}
printf("Capturing on dev %s\n", it->name);
/* construct a filter */
struct bpf_program filter;
pcap_compile(device, &filter, "udp port 33340", 0, 0);
pcap_setfilter(device, &filter);
/* wait loop forever */
int id = 0;
pcap_loop(device, -3, getPacket, (u_char*)&id);
pcap_close(device);
return 0;
}
/* dns.h 实现dns相关函数,提取包头和QR等 */
#ifndef DNS
#define DNS
#include <stdint.h>
#include <sys/types.h>
struct dnshdr
{
uint16_t id;
uint16_t flags;
uint16_t QRn;
uint16_t AnRn;
uint16_t ArRn;
uint16_t AdRn;
};
typedef struct qname{
int len;
char* start;
}qname_t;
struct dnsqr
{
qname_t qn;
uint16_t type;
uint16_t dclass;
struct dnsqr* next;
};
typedef struct dnsqr * dnsqr_list;
int get_qr(u_char *start_pos, struct dnsqr *qr){
u_char *ptr = start_pos;
uint16_t ttlen = 0;
int dcnt;
while(*ptr){
if(*ptr > 63)
return -1;
ttlen += (*ptr) + 1;
ptr += (*ptr) + 1;
}
qr->type = ntohs(*(uint16_t *)(++ptr));
ptr += 2;
qr->dclass = ntohs(*(uint16_t *)ptr);
qr->qn.len = ttlen;
qr->qn.start = malloc(ttlen);
ptr = start_pos;
char* writer = qr->qn.start;
while(*ptr){
dcnt = *ptr;
while(dcnt--){
*(writer++) = *(++ptr);
}
*(writer++) = '.';
++ptr;
}
*(writer-1) = '\0';
return ttlen+4;
}
dnsqr_list get_qr_list(struct dnshdr *dns){
dnsqr_list begin = NULL;
dnsqr_list end = NULL;
int qrn = ntohs(dns->QRn);
u_char *qstart = (u_char *)(dns+1);
int qrlen = 0;
dnsqr_list qr;
while(qrn--){
qr = malloc(sizeof(struct dnsqr));
qr->next = NULL;
qrlen = get_qr(qstart, qr);
if(qrlen < 0){
return NULL;
}
if(!end){
end = qr;
begin = qr;
}else{
end->next = qr;
}
qstart += qrlen;
}
return begin;
}
void free_qr_list(dnsqr_list ls){
dnsqr_list nx;
while(ls){
nx = ls->next;
free(ls->qn.start);
free(ls);
ls = nx;
}
}
#endif