diff --git a/Makefile b/Makefile index 12bc6dd5..67b176ec 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -OBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o +OBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o SRCS := $(OBJS:.o=.c) CONF := config.h DEPS := .depend diff --git a/README b/README index 97a845ad..24d00f7f 100644 --- a/README +++ b/README @@ -114,8 +114,6 @@ TODO Test OpenBSD (pf) and FreeBSD (ipfw) and write setup examples for those firewall types. -Allow redirecting of DNS packets. - Author ====== diff --git a/dnstc.c b/dnstc.c new file mode 100644 index 00000000..43881d88 --- /dev/null +++ b/dnstc.c @@ -0,0 +1,259 @@ +/* redsocks - transparent TCP-to-proxy redirector + * Copyright (C) 2007-2011 Leonid Evdokimov + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list.h" +#include "log.h" +#include "parser.h" +#include "main.h" +#include "redsocks.h" +#include "dnstc.h" +#include "utils.h" + +#define dnstc_log_error(prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &clientaddr, &self->config.bindaddr, prio, ## msg) +#define dnstc_log_errno(prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &clientaddr, &self->config.bindaddr, prio, ## msg) + +static void dnstc_fini_instance(dnstc_instance *instance); +static int dnstc_fini(); + +typedef struct dns_header_t { + uint16_t id; + uint8_t qr_opcode_aa_tc_rd; + uint8_t ra_z_rcode; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} PACKED dns_header; + +#define DNS_QR 0x80 +#define DNS_TC 0x02 +#define DNS_Z 0x70 + +/*********************************************************************** + * Logic + */ +static void dnstc_pkt_from_client(int fd, short what, void *_arg) +{ + dnstc_instance *self = _arg; + struct sockaddr_in clientaddr; + union { + char raw[0xFFFF]; // UDP packet can't be larger then that + dns_header h; + } buf; + ssize_t pktlen, outgoing; + + assert(fd == EVENT_FD(&self->listener)); + pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr); + if (pktlen == -1) + return; + + if (pktlen <= sizeof(dns_header)) { + dnstc_log_error(LOG_INFO, "incomplete DNS request"); + return; + } + + if (1 + && (buf.h.qr_opcode_aa_tc_rd & DNS_QR) == 0 /* query */ + && (buf.h.ra_z_rcode & DNS_Z) == 0 /* Z is Zero */ + && buf.h.qdcount /* some questions */ + && !buf.h.ancount && !buf.h.nscount && !buf.h.arcount /* no answers */ + ) { + buf.h.qr_opcode_aa_tc_rd |= DNS_QR; + buf.h.qr_opcode_aa_tc_rd |= DNS_TC; + outgoing = sendto(fd, buf.raw, pktlen, 0, + (struct sockaddr*)&clientaddr, sizeof(clientaddr)); + if (outgoing != pktlen) + dnstc_log_errno(LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", + pktlen, outgoing); + else + dnstc_log_error(LOG_INFO, "sent truncated DNS reply"); + } + else { + dnstc_log_error(LOG_INFO, "malformed DNS request"); + } +} + +/*********************************************************************** + * Init / shutdown + */ +static parser_entry dnstc_entries[] = +{ + { .key = "local_ip", .type = pt_in_addr }, + { .key = "local_port", .type = pt_uint16 }, + { } +}; + +static list_head instances = LIST_HEAD_INIT(instances); + +static int dnstc_onenter(parser_section *section) +{ + dnstc_instance *instance = calloc(1, sizeof(*instance)); + if (!instance) { + parser_error(section->context, "Not enough memory"); + return -1; + } + + INIT_LIST_HEAD(&instance->list); + instance->config.bindaddr.sin_family = AF_INET; + instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = + (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : + (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : + NULL; + section->data = instance; + return 0; +} + +static int dnstc_onexit(parser_section *section) +{ + dnstc_instance *instance = section->data; + + section->data = NULL; + for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) + entry->addr = NULL; + + instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); + + list_add(&instance->list, &instances); + + return 0; +} + +static int dnstc_init_instance(dnstc_instance *instance) +{ + /* FIXME: dnstc_fini_instance is called in case of failure, this + * function will remove instance from instances list - result + * looks ugly. + */ + int error; + int fd = -1; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + log_errno(LOG_ERR, "socket"); + goto fail; + } + + error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); + if (error) { + log_errno(LOG_ERR, "bind"); + goto fail; + } + + error = fcntl_nonblock(fd); + if (error) { + log_errno(LOG_ERR, "fcntl"); + goto fail; + } + + event_set(&instance->listener, fd, EV_READ | EV_PERSIST, dnstc_pkt_from_client, instance); + error = event_add(&instance->listener, NULL); + if (error) { + log_errno(LOG_ERR, "event_add"); + goto fail; + } + + return 0; + +fail: + dnstc_fini_instance(instance); + + if (fd != -1) { + if (close(fd) != 0) + log_errno(LOG_WARNING, "close"); + } + + return -1; +} + +/* Drops instance completely, freeing its memory and removing from + * instances list. + */ +static void dnstc_fini_instance(dnstc_instance *instance) +{ + if (event_initialized(&instance->listener)) { + if (event_del(&instance->listener) != 0) + log_errno(LOG_WARNING, "event_del"); + if (close(EVENT_FD(&instance->listener)) != 0) + log_errno(LOG_WARNING, "close"); + memset(&instance->listener, 0, sizeof(instance->listener)); + } + + list_del(&instance->list); + + memset(instance, 0, sizeof(*instance)); + free(instance); +} + +static int dnstc_init() +{ + dnstc_instance *tmp, *instance = NULL; + + // TODO: init debug_dumper + + list_for_each_entry_safe(instance, tmp, &instances, list) { + if (dnstc_init_instance(instance) != 0) + goto fail; + } + + return 0; + +fail: + dnstc_fini(); + return -1; +} + +static int dnstc_fini() +{ + dnstc_instance *tmp, *instance = NULL; + + list_for_each_entry_safe(instance, tmp, &instances, list) + dnstc_fini_instance(instance); + + return 0; +} + +static parser_section dnstc_conf_section = +{ + .name = "dnstc", + .entries = dnstc_entries, + .onenter = dnstc_onenter, + .onexit = dnstc_onexit +}; + +app_subsys dnstc_subsys = +{ + .init = dnstc_init, + .fini = dnstc_fini, + .conf_section = &dnstc_conf_section, +}; + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ diff --git a/dnstc.h b/dnstc.h new file mode 100644 index 00000000..8aaf28be --- /dev/null +++ b/dnstc.h @@ -0,0 +1,16 @@ +#ifndef DNSTC_H +#define DNSTC_H + +typedef struct dnstc_config_t { + struct sockaddr_in bindaddr; +} dnstc_config; + +typedef struct dnstc_instance_t { + list_head list; + dnstc_config config; + struct event listener; +} dnstc_instance; + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ +#endif /* REDUDP_H */ diff --git a/main.c b/main.c index 45feacaa..5c9b9fe5 100644 --- a/main.c +++ b/main.c @@ -29,11 +29,13 @@ extern app_subsys redsocks_subsys; extern app_subsys base_subsys; extern app_subsys redudp_subsys; +extern app_subsys dnstc_subsys; app_subsys *subsystems[] = { &redsocks_subsys, &base_subsys, &redudp_subsys, + &dnstc_subsys, }; static const char *confname = "redsocks.conf"; diff --git a/redsocks.conf.example b/redsocks.conf.example index 2d352349..8263b45a 100644 --- a/redsocks.conf.example +++ b/redsocks.conf.example @@ -78,4 +78,11 @@ redudp { udp_timeout_stream = 180; } +dnstc { + // fake and really dumb DNS server that returns "truncated answer" to + // every query via UDP + local_ip = 127.0.0.1; + local_port = 5300; +} + // you can add more `redsocks' and `redudp' sections if you need. diff --git a/redudp.c b/redudp.c index 50c58529..7f96edc3 100644 --- a/redudp.c +++ b/redudp.c @@ -80,33 +80,6 @@ static struct evbuffer* socks5_mkassociate(void *p) return socks5_mkcommand_plain(socks5_cmd_udp_associate, &sa); } -static int recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr) -{ - socklen_t addrlen = sizeof(*inaddr); - ssize_t pktlen; - - pktlen = recvfrom(fd, buf, buflen, 0, (struct sockaddr*)inaddr, &addrlen); - if (pktlen == -1) { - log_errno(LOG_WARNING, "recvfrom"); - return -1; - } - - if (addrlen != sizeof(*inaddr)) { - log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr)); - return -1; - } - - if (pktlen >= buflen) { - char buf[INET6_ADDRSTRLEN]; - const char *addr = inet_ntop(inaddr->sin_family, &inaddr->sin_addr, buf, sizeof(buf)); - log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s:%u! impossible! dropping it...", - pktlen, addr ? addr : "?", ntohs(inaddr->sin_port)); - return -1; - } - - return pktlen; -} - /*********************************************************************** * Logic */ @@ -458,11 +431,9 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg) assert(fd == EVENT_FD(&client->udprelay)); - pktlen = recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr); - if (pktlen == -1) { - redudp_log_errno(client, LOG_WARNING, "recv_udp_pkt"); + pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr); + if (pktlen == -1) return; - } if (memcmp(&udprelayaddr, &client->udprelayaddr, sizeof(udprelayaddr)) != 0) { char buf[INET6_ADDRSTRLEN]; @@ -518,10 +489,9 @@ static void redudp_pkt_from_client(int fd, short what, void *_arg) redudp_client *tmp, *client = NULL; assert(fd == EVENT_FD(&self->listener)); - pktlen = recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr); - if (pktlen == -1) { + pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr); + if (pktlen == -1) return; - } // TODO: this lookup may be SLOOOOOW. list_for_each_entry(tmp, &self->clients, list) { diff --git a/utils.c b/utils.c index 878f96c8..8f9aaf68 100644 --- a/utils.c +++ b/utils.c @@ -20,9 +20,37 @@ #include #include #include +#include #include "log.h" #include "utils.h" +int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr) +{ + socklen_t addrlen = sizeof(*inaddr); + ssize_t pktlen; + + pktlen = recvfrom(fd, buf, buflen, 0, (struct sockaddr*)inaddr, &addrlen); + if (pktlen == -1) { + log_errno(LOG_WARNING, "recvfrom"); + return -1; + } + + if (addrlen != sizeof(*inaddr)) { + log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr)); + return -1; + } + + if (pktlen >= buflen) { + char buf[INET6_ADDRSTRLEN]; + const char *addr = inet_ntop(inaddr->sin_family, &inaddr->sin_addr, buf, sizeof(buf)); + log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s:%u! impossible! dropping it...", + pktlen, addr ? addr : "?", ntohs(inaddr->sin_port)); + return -1; + } + + return pktlen; +} + time_t redsocks_time(time_t *t) { time_t retval; diff --git a/utils.h b/utils.h index af217db9..4f136fb9 100644 --- a/utils.h +++ b/utils.h @@ -35,6 +35,7 @@ time_t redsocks_time(time_t *t); struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg); int red_socket_geterrno(struct bufferevent *buffev); int red_is_socket_connected_ok(struct bufferevent *buffev); +int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr); int fcntl_nonblock(int fd);