Table of Contents
一、背景:
最近要完成高铁cdn项目里面的智能DNS调度模块。
要实现在收到用户的DNS查询后,会主动向某个服务(集群状态管理服务)查询该用户所在的单车服务器IP,并将单车服务器IP返回给用户,这里其实跟云端做的根据用户所在的IP来分配靠近用户CDN节点类似。
举例说明一下需求:
1、单车的用户要解析域名:www.doopets.cn,于是向中心服务器发起DNS请求;
2、中心服务器的unbound收到请求后,根据用户的源IP来向“集群状态管理服务”查询可用的单车服务器。
3、根据查询结果,把www.doopets.cn域名解析成192.168.**.1(**这需要根据源IP和集群状态来确定)。
二、智能DNS调度流程框图:
1)运行在中心服务器上的DNS调度服务,在收到用户的DNS查询后,会主动向集群状态管理服务查询该用户所在的单车服务器IP,并且返回给用户。该DNS调度服务由unbound1.9.1二次开发完成。
2)运行在中心服务器上的单车集群状态管理服务,用于管理单车集群的健康状态,保证给用户提供一个可用的单车服务器。如果用户所在的单车服务器可用时,直接返回该用户所在的单车服务器IP,反之,返回该用户相邻的单车服务器IP。这个服务由Python完成。
3)运行在单车服务器上的心跳反馈服务,按照一定的周期收集单车服务器的健康状态,主要指标包括CPU使用率,磁盘占用大小、缓存服务的运行情况。在收集完状态信息后,实时上报给集群状态管理服务器。这个服务由Python完成。
三、unbound 二次开发修改源码说明
这里的思想就是利用local-data 这种映射来进行处理。
判断源IP是我们的用户 -》修改映射对里面的IP为我们想要返回的IP。
1)、首先把需要劫持的域名写到unbound配置文件:
需要劫持的域名,只需要添加如下这种格式:
local-data: www.doopets.cn. IN A 192.168.0.103
后面必须192.168开头。
2)如果src ip是我们中心分配的IP(192.168开头),则把local rrset里面的ip(rdata)换成我们查询出来的单车ip。
代码说明:在localzone.c文件中,local_data_answer函数做处理,新增:
is_start_with_special_ip 函数是判断源用户IP用的,这里多亏了EDNS的出现,unbound才能获取到源IP。
danche_num_get(ip_buf); 这个函数是根据源IP来获取一个可用的单车IP
change_rdata(danche_num, (char *)lr->rrset->entry.data, 100); 这个函数则是修改要返回给用的DNS报文。具体代码参考源码。
四、效果展示
这里对www.ccrgt.com这个域名做了劫持,用户访问这个域名的时候,直接返回该用户所在的单车服务器的IP。
五、部分源码分享:
/*
@profile:判断src ip是否为192.168开头的
@input: buf 接收到的内容
buflen 长度
@return: 0代表以192.168开头;
1代表不是192开头
outBuf 源ip
*/
int is_start_with_special_ip(unsigned char *buf, unsigned int buflen, char *outBuf)
{
char pbuf[16]={0};
char strBuf[32] = {0};
int i;
int flag = 1;
for(i = 0; i < buflen; i++)
{
sprintf(pbuf, "%02X", buf[i]);
//printf("char:%s\n", pbuf);
if (0 == strcmp(pbuf, "C0")) {
strcat(strBuf, pbuf);
//16进制ip, 往后添加三个字节
i++;
sprintf(pbuf, "%02X", buf[i]);
if (0 == strcmp(pbuf, "A8")){
flag = 0;
}
strcat(strBuf, pbuf);
i++;
sprintf(pbuf, "%02X", buf[i]);
strcat(strBuf, pbuf);
i++;
sprintf(pbuf, "%02X", buf[i]);
strcat(strBuf, pbuf);
break;
}
}
strcpy(outBuf, strBuf);
return flag;
}
#define BUFSIZE 512
#define DestIp "127.0.0.1"
#define DestPort 8484
//#define ReqLen sizeof(Req)
/*
@获取单车的第三段ip
@in_buf: 16进制源ip
@danche_num_buf 单车的第三段ip
*/
int danche_num_get(char * ip_buf) {
ssize_t i;
int nRequestLen;
int size_recv, total_size = 0;
int ReqLen;
char Req[256] = {0};
char strResponse[BUFSIZE]={0};
char strRequest[BUFSIZE]={0};
int danche_num = 1;
int sockfd, numbytes;
struct sockaddr_in dest_addr; /* connector's address information */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
sprintf(Req, "GET /%s HTTP/1.1\r\nHost:127.0.0.1\r\nConnection: Close\r\n\r\n", ip_buf);
ReqLen = sizeof(Req);
dest_addr.sin_family = AF_INET; /* host byte order */
dest_addr.sin_port = htons(DestPort); /* short, network byte order */
dest_addr.sin_addr.s_addr = inet_addr(DestIp);
/* Create and setup the connection */
if (connect(sockfd, (struct sockaddr *)&dest_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
return 0;
}
/* Send the request */
strncpy(strRequest, Req,ReqLen);
nRequestLen = ReqLen;
if (write(sockfd,strRequest,nRequestLen) == -1) {
perror("write");
return 0;
}
/* Read in the response */
while(1) {
if ((size_recv = recv(sockfd, strResponse, 512, 0) ) == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
printf("recv timeout ...\n");
break;
} else if (errno == EINTR) {
printf("interrupt by signal...\n");
continue;
} else if (errno == ENOENT) {
printf("recv RST segement...\n");
break;
} else {
printf("unknown error: %d\n", errno);
break;
}
} else if (size_recv == 0) {
//printf("peer closed ...\n");
break;
} else {
total_size += size_recv;
}
}
/* Close the connection */
close(sockfd);
//strcpy(danche_num_buf, strResponse);
danche_num = atoi(strResponse);
//printf("strResponse: %s\n" , strResponse);
//printf("danche_num: %d\n" , danche_num);
return danche_num;
}
/** answer local data match */
static int
local_data_answer(struct local_zone* z, struct module_env* env,
struct query_info* qinfo, struct edns_data* edns,
struct comm_reply* repinfo, sldns_buffer* buf,
struct regional* temp, int labs, struct local_data** ldp,
enum localzone_type lz_type, int tag, struct config_strlist** tag_datas,
size_t tag_datas_size, char** tagname, int num_tags)
{
struct local_data key;
struct local_data* ld;
struct local_rrset* lr;
key.node.key = &key;
key.name = qinfo->qname;
key.namelen = qinfo->qname_len;
key.namelabs = labs;
if(lz_type == local_zone_redirect ||
lz_type == local_zone_inform_redirect) {
key.name = z->name;
key.namelen = z->namelen;
key.namelabs = z->namelabs;
if(tag != -1 && (size_t)tag<tag_datas_size && tag_datas[tag]) {
struct ub_packed_rrset_key r;
memset(&r, 0, sizeof(r));
if(find_tag_datas(qinfo, tag_datas[tag], &r, temp)) {
verbose(VERB_ALGO, "redirect with tag data [%d] %s",
tag, (tag<num_tags?tagname[tag]:"null"));
/* If we found a matching alias, we should
* use it as part of the answer, but we can't
* encode it until we complete the alias
* chain. */
if(qinfo->local_alias)
return 1;
return local_encode(qinfo, env, edns, repinfo, buf, temp,
&r, 1, LDNS_RCODE_NOERROR);
}
}
}
ld = (struct local_data*)rbtree_search(&z->data, &key.node);
*ldp = ld;
if(!ld) {
return 0;
}
//qinfo->qtype 1:LDNS_RR_TYPE_A
lr = local_data_find_type(ld, qinfo->qtype, 1);
if(!lr)
return 0;
char ip_buf[32] = {0};
int danche_num = 1;
/* 这里做判断,如果src ip是 192.168开头的,则把local rrset里面的ip(rdata)换成
我们查询出来的单车ip.*/
if (0 == is_start_with_special_ip(buf->_data, 100, ip_buf)) {
//获取用户的单车号;
//替换rrset里面的ip
//danche_num_get();
//struct timeval start, end;
//gettimeofday(&start, NULL);
//获取单车序号,16进制的单车ip,第三个网段;
danche_num = danche_num_get(ip_buf);
//gettimeofday(&end, NULL);
//printf("time: %lu us\n", (end.tv_sec - start.tv_sec)*1000000+ (end.tv_usec - start.tv_usec));
change_rdata(danche_num, (char *)lr->rrset->entry.data, 100);
//arrayToStrs((char *)lr->rrset->entry.data, 100);
}
/* Special case for alias matching. See local_data_answer(). */
if((lz_type == local_zone_redirect ||
lz_type == local_zone_inform_redirect) &&
qinfo->qtype != LDNS_RR_TYPE_CNAME &&
lr->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) {
qinfo->local_alias =
regional_alloc_zero(temp, sizeof(struct local_rrset));
if(!qinfo->local_alias)
return 0; /* out of memory */
qinfo->local_alias->rrset =
regional_alloc_init(temp, lr->rrset, sizeof(*lr->rrset));
if(!qinfo->local_alias->rrset)
return 0; /* out of memory */
qinfo->local_alias->rrset->rk.dname = qinfo->qname;
qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len;
return 1;
}
if(lz_type == local_zone_redirect ||
lz_type == local_zone_inform_redirect) {
/* convert rrset name to query name; like a wildcard */
struct ub_packed_rrset_key r = *lr->rrset;
r.rk.dname = qinfo->qname;
r.rk.dname_len = qinfo->qname_len;
return local_encode(qinfo, env, edns, repinfo, buf, temp, &r, 1,
LDNS_RCODE_NOERROR);
}
return local_encode(qinfo, env, edns, repinfo, buf, temp, lr->rrset, 1,
LDNS_RCODE_NOERROR);
}