2 * Copyright (C) 2012 by Darren Reed.
4 * See the IPFILTER.LICENCE file for details on licencing.
6 * $Id: ip_dns_pxy.c,v 1.1.2.10 2012/07/22 08:04:23 darren_r Exp $
12 * map ... proxy port dns/udp 53 { block .cnn.com; }
14 typedef struct ipf_dns_filter {
15 struct ipf_dns_filter *idns_next;
22 typedef struct ipf_dns_softc_s {
23 ipf_dns_filter_t *ipf_p_dns_list;
24 ipfrwlock_t ipf_p_dns_rwlock;
25 u_long ipf_p_dns_compress;
26 u_long ipf_p_dns_toolong;
27 u_long ipf_p_dns_nospace;
30 int ipf_p_dns_allow_query(ipf_dns_softc_t *, dnsinfo_t *);
31 int ipf_p_dns_ctl(ipf_main_softc_t *, void *, ap_ctl_t *);
32 void ipf_p_dns_del(ipf_main_softc_t *, ap_session_t *);
33 int ipf_p_dns_get_name(ipf_dns_softc_t *, char *, int, char *, int);
34 int ipf_p_dns_inout(void *, fr_info_t *, ap_session_t *, nat_t *);
35 int ipf_p_dns_match(fr_info_t *, ap_session_t *, nat_t *);
36 int ipf_p_dns_match_names(ipf_dns_filter_t *, char *, int);
37 int ipf_p_dns_new(void *, fr_info_t *, ap_session_t *, nat_t *);
38 void *ipf_p_dns_soft_create(ipf_main_softc_t *);
39 void ipf_p_dns_soft_destroy(ipf_main_softc_t *, void *);
50 #define DNS_QR(x) ((ntohs(x) & 0x8000) >> 15)
51 #define DNS_OPCODE(x) ((ntohs(x) & 0x7800) >> 11)
52 #define DNS_AA(x) ((ntohs(x) & 0x0400) >> 10)
53 #define DNS_TC(x) ((ntohs(x) & 0x0200) >> 9)
54 #define DNS_RD(x) ((ntohs(x) & 0x0100) >> 8)
55 #define DNS_RA(x) ((ntohs(x) & 0x0080) >> 7)
56 #define DNS_Z(x) ((ntohs(x) & 0x0070) >> 4)
57 #define DNS_RCODE(x) ((ntohs(x) & 0x000f) >> 0)
61 ipf_p_dns_soft_create(ipf_main_softc_t *softc)
63 ipf_dns_softc_t *softd;
65 KMALLOC(softd, ipf_dns_softc_t *);
69 bzero((char *)softd, sizeof(*softd));
70 RWLOCK_INIT(&softd->ipf_p_dns_rwlock, "ipf dns rwlock");
77 ipf_p_dns_soft_destroy(ipf_main_softc_t *softc, void *arg)
79 ipf_dns_softc_t *softd = arg;
80 ipf_dns_filter_t *idns;
82 while ((idns = softd->ipf_p_dns_list) != NULL) {
83 KFREES(idns->idns_name, idns->idns_namelen);
84 idns->idns_name = NULL;
85 idns->idns_namelen = 0;
86 softd->ipf_p_dns_list = idns->idns_next;
89 RW_DESTROY(&softd->ipf_p_dns_rwlock);
96 ipf_p_dns_ctl(ipf_main_softc_t *softc, void *arg, ap_ctl_t *ctl)
98 ipf_dns_softc_t *softd = arg;
99 ipf_dns_filter_t *tmp, *idns, **idnsp;
103 * To make locking easier.
105 KMALLOC(tmp, ipf_dns_filter_t *);
107 WRITE_ENTER(&softd->ipf_p_dns_rwlock);
108 for (idnsp = &softd->ipf_p_dns_list; (idns = *idnsp) != NULL;
109 idnsp = &idns->idns_next) {
110 if (idns->idns_namelen != ctl->apc_dsize)
112 if (!strncmp(ctl->apc_data, idns->idns_name,
117 switch (ctl->apc_cmd)
125 *idnsp = idns->idns_next;
126 idns->idns_next = NULL;
127 KFREES(idns->idns_name, idns->idns_namelen);
128 idns->idns_name = NULL;
129 idns->idns_namelen = 0;
145 idns->idns_namelen = ctl->apc_dsize;
146 idns->idns_name = ctl->apc_data;
147 idns->idns_pass = ctl->apc_arg;
148 idns->idns_next = NULL;
150 ctl->apc_data = NULL;
158 RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
171 ipf_p_dns_new(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat)
179 dlen = fin->fin_dlen - sizeof(udphdr_t);
180 if (dlen < sizeof(ipf_dns_hdr_t)) {
182 * No real DNS packet is smaller than that.
187 aps->aps_psiz = sizeof(dnsinfo_t);
188 KMALLOCS(di, dnsinfo_t *, sizeof(dnsinfo_t));
190 printf("ipf_dns_new:KMALLOCS(%d) failed\n", sizeof(*di));
194 MUTEX_INIT(&di->dnsi_lock, "dns lock");
198 dlen = fin->fin_dlen - sizeof(udphdr_t);
199 COPYDATA(fin->fin_m, fin->fin_hlen + sizeof(udphdr_t),
200 MIN(dlen, sizeof(di->dnsi_buffer)), di->dnsi_buffer);
201 di->dnsi_id = (di->dnsi_buffer[0] << 8) | di->dnsi_buffer[1];
208 ipf_p_dns_del(ipf_main_softc_t *softc, ap_session_t *aps)
211 dnsinfo_t *di = aps->aps_data;
213 MUTEX_DESTROY(&di->dnsi_lock);
215 KFREES(aps->aps_data, aps->aps_psiz);
216 aps->aps_data = NULL;
222 * Tries to match the base string (in our ACL) with the query from a packet.
225 ipf_p_dns_match_names(ipf_dns_filter_t *idns, char *query, int qlen)
230 blen = idns->idns_namelen;
231 base = idns->idns_name;
237 return (strncasecmp(base, query, qlen));
240 * If the base string string is shorter than the query, allow the
241 * tail of the base to match the same length tail of the query *if*:
242 * - the base string starts with a '*' (*cnn.com)
243 * - the base string represents a domain (.cnn.com)
244 * as otherwise it would not be possible to block just "cnn.com"
245 * without also impacting "foocnn.com", etc.
250 } else if (*base != '.')
253 return (strncasecmp(base, query + qlen - blen, blen));
258 ipf_p_dns_get_name(ipf_dns_softc_t *softd, char *start, int len,
259 char *buffer, int buflen)
267 blen = buflen - 1; /* Always make room for trailing \0 */
271 if ((clen & 0xc0) == 0xc0) { /* Doesn't do compression */
272 softd->ipf_p_dns_compress++;
276 softd->ipf_p_dns_toolong++;
277 return (0); /* Does the name run off the end? */
279 if ((clen + 1) > blen) {
280 softd->ipf_p_dns_nospace++;
281 return (0); /* Enough room for name+.? */
298 ipf_p_dns_allow_query(ipf_dns_softc_t *softd, dnsinfo_t *dnsi)
300 ipf_dns_filter_t *idns;
303 len = strlen(dnsi->dnsi_buffer);
305 for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next)
306 if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0)
307 return (idns->idns_pass);
314 ipf_p_dns_inout(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat)
316 ipf_dns_softc_t *softd = arg;
322 if (fin->fin_dlen < sizeof(*dns))
325 dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
327 q = dns->dns_qdcount;
329 data = (char *)(dns + 1);
330 dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t);
334 READ_ENTER(&softd->ipf_p_dns_rwlock);
335 MUTEX_ENTER(&di->dnsi_lock);
337 for (; (dlen > 0) && (q > 0); q--) {
340 len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer,
341 sizeof(di->dnsi_buffer));
346 rc = ipf_p_dns_allow_query(softd, di);
352 MUTEX_EXIT(&di->dnsi_lock);
353 RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
355 return (APR_ERR(rc));
361 ipf_p_dns_match(fr_info_t *fin, ap_session_t *aps, nat_t *nat)
363 dnsinfo_t *di = aps->aps_data;
366 if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG))
369 dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
370 if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id)