Created
August 1, 2014 05:45
-
-
Save rampageX/71c13b38edccad49262d to your computer and use it in GitHub Desktop.
chinadns.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _GNU_SOURCE | |
#define __USE_GNU | |
#include <netinet/in.h> | |
#include <arpa/nameser.h> | |
#include <fcntl.h> | |
#include <netdb.h> | |
#include <resolv.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
#include <unistd.h> | |
#include <arpa/inet.h> | |
#include <sys/select.h> | |
#include <sys/socket.h> | |
#include <sys/types.h> | |
#ifdef HAVE_CONFIG_H | |
#include "config.h" | |
#endif | |
typedef struct { | |
time_t ts; | |
char *buf; | |
size_t buflen; | |
struct sockaddr *addr; | |
socklen_t addrlen; | |
} delay_buf_t; | |
typedef struct { | |
uint16_t id; | |
struct sockaddr *addr; | |
socklen_t addrlen; | |
} id_addr_t; | |
typedef struct { | |
int entries; | |
struct in_addr *ips; | |
} ip_list_t; | |
// avoid malloc and free | |
#define BUF_SIZE 2048 | |
static char global_buf[BUF_SIZE]; | |
static int verbose = 0; | |
#if defined(PACKAGE_STRING) | |
static const char *version = PACKAGE_STRING; | |
#else | |
static const char *version = "ChinaDNS 1.0.3"; | |
#endif | |
static const char *default_dns_servers = | |
"114.114.114.114,8.8.8.8,208.67.222.222:5353"; | |
static char *dns_servers = NULL; | |
static int dns_servers_len; | |
static id_addr_t *dns_server_addrs; | |
static int parse_args(int argc, char **argv); | |
static int setnonblock(int sock); | |
static int resolve_dns_servers(); | |
static const char *default_listen_addr = "0.0.0.0"; | |
static const char *default_listen_port = "53"; | |
static char *listen_addr = NULL; | |
static char *listen_port = NULL; | |
static const char *default_ip_list_file = "iplist.txt"; | |
static char *ip_list_file = NULL; | |
static ip_list_t ip_list; | |
static int parse_ip_list(); | |
static int dns_init_sockets(); | |
static void dns_handle_local(); | |
static void dns_handle_remote(); | |
static const char *hostname_from_question(ns_msg msg); | |
static int should_filter_query(ns_msg msg); | |
static void queue_add(id_addr_t id_addr); | |
static id_addr_t *queue_lookup(uint16_t id); | |
#define ID_ADDR_QUEUE_LEN 128 | |
// use a queue instead of hash here since it's not long | |
static id_addr_t id_addr_queue[ID_ADDR_QUEUE_LEN]; | |
static int id_addr_queue_pos = 0; | |
#define EMPTY_RESULT_DELAY 3 | |
#define DELAY_QUEUE_LEN 32 | |
static delay_buf_t delay_queue[DELAY_QUEUE_LEN]; | |
static void schedule_delay(const char *buf, size_t buflen, | |
struct sockaddr *addr, socklen_t addrlen); | |
static void check_and_send_delay(); | |
static void free_delay(int pos); | |
// next position for first, not used | |
static int delay_queue_first = 0; | |
// current position for last, used | |
static int delay_queue_last = 0; | |
static int local_sock; | |
static int remote_sock; | |
static const char *help_message = | |
"usage: chinadns [-h] [-l IPLIST_FILE] [-b BIND_ADDR] [-p BIND_PORT]\n" | |
" [-s DNS] [-v]\n" | |
"Forward DNS requests.\n" | |
"\n" | |
" -h, --help show this help message and exit\n" | |
" -l IPLIST_FILE path to ip blacklist file\n" | |
" -b BIND_ADDR address that listens, default: 127.0.0.1\n" | |
" -p BIND_PORT port that listens, default: 53\n" | |
" -s DNS DNS servers to use, default:\n" | |
" 114.114.114.114,208.67.222.222:5353,8.8.8.8\n" | |
" -v verbose logging\n" | |
"\n" | |
"Online help: <https://github.com/clowwindy/ChinaDNS-C>\n"; | |
#define __LOG(o, t, v, s...) do { \ | |
time_t now; \ | |
time(&now); \ | |
char *time_str = ctime(&now); \ | |
time_str[strlen(time_str) - 1] = '\0'; \ | |
if (t == 0) { \ | |
if (verbose) { \ | |
fprintf(o, "%s ", time_str); \ | |
printf(s); \ | |
} \ | |
} else if (t == 1) { \ | |
fprintf(o, "%s %s:%d ", time_str, __FILE__, __LINE__); \ | |
perror(v); \ | |
} \ | |
} while (0) | |
#define LOG(s...) __LOG(stdout, 0, "_", s) | |
#define ERR(s) __LOG(stderr, 1, s, "_") | |
#define VERR(s...) __LOG(stderr, 0, "_", s) | |
int main(int argc, char **argv) { | |
fd_set readset, errorset; | |
int max_fd; | |
memset(&id_addr_queue, 0, sizeof(id_addr_queue)); | |
memset(&delay_queue, 0, sizeof(delay_queue)); | |
if (0 != parse_args(argc, argv)) | |
return EXIT_FAILURE; | |
if (0 != parse_ip_list()) | |
return EXIT_FAILURE; | |
if (0 != resolve_dns_servers()) | |
return EXIT_FAILURE; | |
if (0 != dns_init_sockets()) | |
return EXIT_FAILURE; | |
printf("%s\n", version); | |
max_fd = MAX(local_sock, remote_sock) + 1; | |
while (1) { | |
FD_ZERO(&readset); | |
FD_ZERO(&errorset); | |
FD_SET(local_sock, &readset); | |
FD_SET(local_sock, &errorset); | |
FD_SET(remote_sock, &readset); | |
FD_SET(remote_sock, &errorset); | |
struct timeval timeout = { | |
.tv_sec = 1, | |
.tv_usec = 0, | |
}; | |
if (-1 == select(max_fd, &readset, NULL, &errorset, &timeout)) { | |
ERR("select"); | |
return EXIT_FAILURE; | |
} | |
check_and_send_delay(); | |
if (FD_ISSET(local_sock, &errorset)) { | |
// TODO getsockopt(..., SO_ERROR, ...); | |
VERR("local_sock error\n"); | |
return EXIT_FAILURE; | |
} | |
if (FD_ISSET(remote_sock, &errorset)) { | |
// TODO getsockopt(..., SO_ERROR, ...); | |
VERR("remote_sock error\n"); | |
return EXIT_FAILURE; | |
} | |
if (FD_ISSET(local_sock, &readset)) | |
dns_handle_local(); | |
if (FD_ISSET(remote_sock, &readset)) | |
dns_handle_remote(); | |
} | |
return EXIT_SUCCESS; | |
} | |
static int setnonblock(int sock) { | |
int flags; | |
flags = fcntl(local_sock, F_GETFL, 0); | |
if(flags == -1) { | |
ERR("fcntl"); | |
return -1; | |
} | |
fcntl(local_sock, F_SETFL, flags | O_NONBLOCK); | |
if(flags == -1) { | |
ERR("fcntl"); | |
return -1; | |
} | |
return 0; | |
} | |
static int parse_args(int argc, char **argv) { | |
int ch; | |
dns_servers = strdup(default_dns_servers); | |
ip_list_file = strdup(default_ip_list_file); | |
listen_addr = strdup(default_listen_addr); | |
listen_port = strdup(default_listen_port); | |
while ((ch = getopt(argc, argv, "hb:p:s:l:v")) != -1) { | |
switch (ch) { | |
case 'h': | |
printf("%s\n", version); | |
printf("%s", help_message); | |
exit(0); | |
case 'b': | |
listen_addr = strdup(optarg); | |
break; | |
case 'p': | |
listen_port = strdup(optarg); | |
break; | |
case 's': | |
dns_servers = strdup(optarg); | |
printf("Upstream Servers: %s\n", dns_servers); | |
break; | |
case 'l': | |
ip_list_file = strdup(optarg); | |
break; | |
case 'v': | |
verbose = 1; | |
break; | |
default: | |
printf("%s", help_message); | |
exit(1); | |
} | |
} | |
argc -= optind; | |
argv += optind; | |
return 0; | |
} | |
static int resolve_dns_servers() { | |
struct addrinfo hints; | |
struct addrinfo *addr_ip; | |
char* token; | |
int r; | |
int i = 0; | |
char *pch = strchr(dns_servers, ','); | |
dns_servers_len = 1; | |
while (pch != NULL) { | |
dns_servers_len++; | |
pch = strchr(pch + 1, ','); | |
} | |
dns_server_addrs = calloc(dns_servers_len, sizeof(id_addr_t)); | |
memset(&hints, 0, sizeof(hints)); | |
hints.ai_family = AF_INET; | |
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ | |
token = strtok(dns_servers, ","); | |
while (token) { | |
char *port; | |
memset(global_buf, 0, BUF_SIZE); | |
strncpy(global_buf, token, BUF_SIZE - 1); | |
port = (strrchr(global_buf, ':')); | |
if (port) { | |
*port = '\0'; | |
port++; | |
} else { | |
port = "53"; | |
} | |
if (0 != (r = getaddrinfo(global_buf, port, &hints, &addr_ip))) { | |
VERR("%s:%s\n", gai_strerror(r), token); | |
return -1; | |
} | |
dns_server_addrs[i].addr = addr_ip->ai_addr; | |
dns_server_addrs[i].addrlen = addr_ip->ai_addrlen; | |
i++; | |
token = strtok(0, ","); | |
} | |
return 0; | |
} | |
static int cmp_in_addr(const void *a, const void *b) { | |
struct in_addr *ina = (struct in_addr *)a; | |
struct in_addr *inb = (struct in_addr *)b; | |
return ina->s_addr - inb->s_addr; | |
} | |
static int parse_ip_list() { | |
FILE * fp; | |
char * line = NULL; | |
size_t len = 0; | |
ssize_t read; | |
ip_list.entries = 0; | |
int i = 0; | |
fp = fopen(ip_list_file, "rb"); | |
if (fp == NULL) { | |
ERR("fopen"); | |
VERR("Can't open ip list: %s\n", ip_list_file); | |
return -1; | |
} | |
if (verbose) { | |
printf("Loading FAKE IP list:\n"); | |
printf("==================================\n"); | |
} | |
while ((read = getline(&line, &len, fp)) != -1) { | |
ip_list.entries++; | |
if (verbose) | |
printf("%s", line); | |
} | |
if (verbose) { | |
printf("\n=================================="); | |
printf("\n%d IP loaded.\n", ip_list.entries); | |
} | |
if (line) | |
free(line); | |
line = NULL; | |
ip_list.ips = calloc(ip_list.entries, sizeof(struct in_addr)); | |
fseek(fp, 0, SEEK_SET); | |
while ((read = getline(&line, &len, fp)) != -1) { | |
inet_aton(line, &ip_list.ips[i]); | |
i++; | |
} | |
if (line) | |
free(line); | |
qsort(ip_list.ips, ip_list.entries, sizeof(struct in_addr), cmp_in_addr); | |
fclose(fp); | |
return 0; | |
} | |
static int dns_init_sockets() { | |
struct addrinfo addr; | |
struct addrinfo *addr_ip; | |
int r; | |
local_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
if (0 != setnonblock(local_sock)) | |
return -1; | |
memset(&addr, 0, sizeof(addr)); | |
if (0 != (r = getaddrinfo(listen_addr, listen_port, &addr, &addr_ip))) { | |
VERR("%s:%s:%s\n", gai_strerror(r), listen_addr, listen_port); | |
return -1; | |
} | |
if (0 != bind(local_sock, addr_ip->ai_addr, addr_ip->ai_addrlen)) { | |
ERR("bind"); | |
VERR("Can't bind address %s:%s\n", listen_addr, listen_port); | |
return -1; | |
} | |
freeaddrinfo(addr_ip); | |
remote_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
if (0 != setnonblock(remote_sock)) | |
return -1; | |
return 0; | |
} | |
static void dns_handle_local() { | |
struct sockaddr *src_addr = malloc(sizeof(struct sockaddr)); | |
socklen_t src_addrlen = sizeof(struct sockaddr); | |
uint16_t query_id; | |
ssize_t len; | |
int i; | |
const char *question_hostname; | |
ns_msg msg; | |
len = recvfrom(local_sock, global_buf, BUF_SIZE, 0, src_addr, &src_addrlen); | |
if (len > 0) { | |
if (ns_initparse((const u_char *)global_buf, len, &msg) < 0) { | |
ERR("ns_initparse"); | |
free(src_addr); | |
return; | |
} | |
// parse DNS query id | |
// TODO generate id for each request to avoid conflicts | |
query_id = ns_msg_id(msg); | |
question_hostname = hostname_from_question(msg); | |
LOG("request %s\n", question_hostname); | |
id_addr_t id_addr; | |
id_addr.id = query_id; | |
id_addr.addr = src_addr; | |
id_addr.addrlen = src_addrlen; | |
queue_add(id_addr); | |
for (i = 0; i < dns_servers_len; i++) { | |
if (-1 == sendto(remote_sock, global_buf, len, 0, | |
dns_server_addrs[i].addr, dns_server_addrs[i].addrlen)) | |
ERR("sendto"); | |
} | |
} | |
else | |
ERR("recvfrom"); | |
} | |
static void dns_handle_remote() { | |
struct sockaddr *src_addr = malloc(sizeof(struct sockaddr)); | |
socklen_t src_len = sizeof(struct sockaddr); | |
uint16_t query_id; | |
ssize_t len; | |
const char *question_hostname; | |
int r; | |
ns_msg msg; | |
len = recvfrom(remote_sock, global_buf, BUF_SIZE, 0, src_addr, &src_len); | |
if (len > 0) { | |
if (ns_initparse((const u_char *)global_buf, len, &msg) < 0) { | |
ERR("ns_initparse"); | |
free(src_addr); | |
return; | |
} | |
// parse DNS query id | |
// TODO assign new id instead of using id from clients | |
query_id = ns_msg_id(msg); | |
question_hostname = hostname_from_question(msg); | |
if (question_hostname) { | |
LOG("response %s from %s:%d - ", question_hostname, | |
inet_ntoa(((struct sockaddr_in *)src_addr)->sin_addr), | |
htons(((struct sockaddr_in *)src_addr)->sin_port)); | |
} | |
free(src_addr); | |
id_addr_t *id_addr = queue_lookup(query_id); | |
if (id_addr) { | |
id_addr->addr->sa_family = AF_INET; | |
r = should_filter_query(msg); | |
if (r == 0) { | |
if (verbose) | |
printf("pass\n"); | |
if (-1 == sendto(local_sock, global_buf, len, 0, id_addr->addr, | |
id_addr->addrlen)) | |
ERR("sendto"); | |
} else if (r == -1) { | |
schedule_delay(global_buf, len, id_addr->addr, id_addr->addrlen); | |
if (verbose) | |
printf("delay\n"); | |
} else { | |
if (verbose) | |
printf("filter\n"); | |
} | |
} else { | |
if (verbose) | |
printf("skip\n"); | |
} | |
} | |
else | |
ERR("recvfrom"); | |
} | |
static void queue_add(id_addr_t id_addr) { | |
id_addr_queue_pos = (id_addr_queue_pos + 1) % ID_ADDR_QUEUE_LEN; | |
// free next hole | |
id_addr_t old_id_addr = id_addr_queue[id_addr_queue_pos]; | |
free(old_id_addr.addr); | |
id_addr_queue[id_addr_queue_pos] = id_addr; | |
} | |
static id_addr_t *queue_lookup(uint16_t id) { | |
int i; | |
// TODO assign new id instead of using id from clients | |
for (i = 0; i < ID_ADDR_QUEUE_LEN; i++) { | |
if (id_addr_queue[i].id == id) | |
return id_addr_queue + i; | |
} | |
return NULL; | |
} | |
static char *hostname_buf = NULL; | |
static size_t hostname_buflen = 0; | |
static const char *hostname_from_question(ns_msg msg) { | |
ns_rr rr; | |
int rrnum, rrmax; | |
const char *result; | |
int result_len; | |
rrmax = ns_msg_count(msg, ns_s_qd); | |
if (rrmax == 0) | |
return NULL; | |
for (rrnum = 0; rrnum < rrmax; rrnum++) { | |
if (ns_parserr(&msg, ns_s_qd, rrnum, &rr)) { | |
ERR("ns_parserr"); | |
return NULL; | |
} | |
result = ns_rr_name(rr); | |
result_len = strlen(result) + 1; | |
if (result_len > hostname_buflen) { | |
hostname_buflen = result_len << 1; | |
hostname_buf = realloc(hostname_buf, hostname_buflen); | |
} | |
memcpy(hostname_buf, result, result_len); | |
return hostname_buf; | |
} | |
return NULL; | |
} | |
static int should_filter_query(ns_msg msg) { | |
ns_rr rr; | |
int rrnum, rrmax; | |
void *r; | |
rrmax = ns_msg_count(msg, ns_s_an); | |
if (rrmax == 0) | |
return -1; | |
for (rrnum = 0; rrnum < rrmax; rrnum++) { | |
if (ns_parserr(&msg, ns_s_an, rrnum, &rr)) { | |
ERR("ns_parserr"); | |
return 0; | |
} | |
u_int type; | |
const u_char *rd; | |
type = ns_rr_type(rr); | |
rd = ns_rr_rdata(rr); | |
if (type == ns_t_a) { | |
if (verbose) | |
printf("%s, ", inet_ntoa(*(struct in_addr *)rd)); | |
r = bsearch(rd, ip_list.ips, ip_list.entries, sizeof(struct in_addr), | |
cmp_in_addr); | |
if (r) | |
return 1; | |
} | |
} | |
return 0; | |
} | |
static void schedule_delay(const char *buf, size_t buflen, | |
struct sockaddr *addr, socklen_t addrlen) { | |
time_t now; | |
time(&now); | |
delay_buf_t *delay_buf = &delay_queue[delay_queue_last]; | |
delay_buf->ts = now; | |
delay_buf->buf = malloc(buflen); | |
memcpy(delay_buf->buf, buf, buflen); | |
delay_buf->buflen = buflen; | |
delay_buf->addr = malloc(addrlen); | |
memcpy(delay_buf->addr, addr, addrlen); | |
delay_buf->addrlen = addrlen; | |
delay_queue_last = (delay_queue_last + 1) % DELAY_QUEUE_LEN; | |
if (delay_queue_last == delay_queue_first) { | |
free_delay(delay_queue_first); | |
delay_queue_first = (delay_queue_first + 1) % DELAY_QUEUE_LEN; | |
} | |
} | |
static void check_and_send_delay() { | |
time_t ts_limit; | |
int i; | |
time(&ts_limit); | |
ts_limit -= EMPTY_RESULT_DELAY; | |
for (i = delay_queue_first; | |
i != delay_queue_last; | |
i = (i + 1) % DELAY_QUEUE_LEN) { | |
delay_buf_t *delay_buf = &delay_queue[i]; | |
if (delay_buf->ts < ts_limit) { | |
if (-1 == sendto(local_sock, delay_buf->buf, delay_buf->buflen, 0, | |
delay_buf->addr, delay_buf->addrlen)) | |
ERR("sendto"); | |
free_delay(i); | |
delay_queue_first = (delay_queue_first + 1) % DELAY_QUEUE_LEN; | |
} else { | |
break; | |
} | |
} | |
} | |
static void free_delay(int pos) { | |
free(delay_queue[pos].buf); | |
free(delay_queue[pos].addr); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment