1 /* Copyright © 2012 Brandon L Black <blblack@gmail.com>
2  *
3  * This file is part of gdnsd.
4  *
5  * gdnsd-plugin-geoip is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * gdnsd-plugin-geoip is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with gdnsd.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include <config.h>
21 #include "nets.h"
22 
23 #include <gdnsd/log.h>
24 #include <gdnsd/net.h>
25 
26 #include <inttypes.h>
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 // Check whether the passed network is a subnet
32 //  of (or the entirety of) any of the "undefined"
33 //  v4-like subspaces in our ntree databases...
34 
35 F_NONNULL F_PURE
v6_subnet_of(const uint8_t * check,const unsigned check_mask,const uint8_t * v4,const unsigned v4_mask)36 static bool v6_subnet_of(const uint8_t* check, const unsigned check_mask, const uint8_t* v4, const unsigned v4_mask) {
37     dmn_assert(!(v4_mask & 7)); // all v4_mask are whole byte masks
38 
39     bool rv = false;
40 
41     if(check_mask >= v4_mask)
42         rv = !memcmp(check, v4, (v4_mask >> 3));
43 
44     return rv;
45 }
46 
47 F_NONNULL F_PURE
check_v4_issues(const uint8_t * ipv6,const unsigned mask)48 static bool check_v4_issues(const uint8_t* ipv6, const unsigned mask) {
49     dmn_assert(mask < 129);
50 
51     return (
52           v6_subnet_of(ipv6, mask, start_v4mapped, 96)
53        || v6_subnet_of(ipv6, mask, start_siit, 96)
54        || v6_subnet_of(ipv6, mask, start_wkp, 96)
55        || v6_subnet_of(ipv6, mask, start_teredo, 32)
56        || v6_subnet_of(ipv6, mask, start_6to4, 16)
57     );
58 }
59 
60 // arguably, with at least some of the v4-like spaces we could simply translate and hope to de-dupe,
61 //   if we upgraded nlist_normalize1 to de-dupe matching dclists instead of failing them
62 F_NONNULL
nets_parse(vscf_data_t * nets_cfg,dclists_t * dclists,const char * map_name,nlist_t * nl)63 static bool nets_parse(vscf_data_t* nets_cfg, dclists_t* dclists, const char* map_name, nlist_t* nl) {
64     bool rv = false;
65 
66     const unsigned input_nnets = vscf_hash_get_len(nets_cfg);
67 
68     for(unsigned i = 0; i < input_nnets; i++) {
69         // convert 192.0.2.0/24 -> anysin_t w/ mask in port field
70         unsigned net_str_len = 0;
71         const char* net_str_cfg = vscf_hash_get_key_byindex(nets_cfg, i, &net_str_len);
72         char net_str[net_str_len + 1];
73         memcpy(net_str, net_str_cfg, net_str_len + 1);
74 
75         char* mask_str = strchr(net_str, '/');
76         if(!mask_str) {
77             log_err("plugin_geoip: map '%s': nets entry '%s' does not parse as addr/mask", map_name, net_str);
78             rv = true;
79             break;
80         }
81         *mask_str++ = '\0';
82         dmn_anysin_t tempsin;
83         int addr_err = gdnsd_anysin_getaddrinfo(net_str, mask_str, &tempsin);
84         if(addr_err) {
85             log_err("plugin_geoip: map '%s': nets entry '%s/%s' does not parse as addr/mask: %s", map_name, net_str, mask_str, gai_strerror(addr_err));
86             rv = true;
87             break;
88         }
89 
90         unsigned mask;
91         uint8_t ipv6[16];
92 
93         // now store the anysin data into net_t
94         if(tempsin.sa.sa_family == AF_INET6) {
95             mask = ntohs(tempsin.sin6.sin6_port);
96             if(mask > 128) {
97                 log_err("plugin_geoip: map '%s': nets entry '%s/%s': illegal IPv6 mask (>128)", map_name, net_str, mask_str);
98                 rv = true;
99                 break;
100             }
101             memcpy(ipv6, tempsin.sin6.sin6_addr.s6_addr, 16);
102             if(check_v4_issues(ipv6, mask)) {
103                 log_err("plugin_geoip: map '%s': 'nets' entry '%s/%s' covers illegal IPv4-like space, see the documentation for more info", map_name, net_str, mask_str);
104                 rv = true;
105                 break;
106             }
107         }
108         else {
109             dmn_assert(tempsin.sa.sa_family == AF_INET);
110             mask = ntohs(tempsin.sin.sin_port) + 96U;
111             if(mask > 128) {
112                 log_err("plugin_geoip: map '%s': nets entry '%s/%s': illegal IPv4 mask (>32)", map_name, net_str, mask_str);
113                 rv = true;
114                 break;
115             }
116             memset(ipv6, 0, 16);
117             memcpy(&ipv6[12], &tempsin.sin.sin_addr.s_addr, 4);
118         }
119 
120         // get dclist integer from rhs
121         vscf_data_t* dc_cfg = vscf_hash_get_data_byindex(nets_cfg, i);
122         const uint32_t dclist = dclists_find_or_add_vscf(dclists, dc_cfg, map_name, false);
123         dmn_assert(dclist <= DCLIST_MAX); // auto not allowed here
124         nlist_append(nl, ipv6, mask, dclist);
125     }
126 
127     return rv;
128 }
129 
nets_make_list(vscf_data_t * nets_cfg,dclists_t * dclists,const char * map_name)130 nlist_t* nets_make_list(vscf_data_t* nets_cfg, dclists_t* dclists, const char* map_name) {
131     nlist_t* nl = nlist_new(map_name, false);
132 
133     if(nets_cfg) {
134         dmn_assert(vscf_is_hash(nets_cfg));
135         if(nets_parse(nets_cfg, dclists, map_name, nl)) {
136             nlist_destroy(nl);
137             nl = NULL;
138         }
139     }
140 
141     if(nl) {
142         // This masks out the 5x v4-like spaces that we *never*
143         //   lookup directly.  These "NN_UNDEF" dclists will
144         //   never be seen by runtime lookups.  The only
145         //   reason these exist is so that supernets and
146         //   adjacent networks get proper masks.  Otherwise
147         //   lookups in these nearby spaces might return
148         //   oversized edns-client-subnet masks and cause
149         //   the cache to affect lookup of these spaces...
150         nlist_append(nl, start_v4mapped, 96, NN_UNDEF);
151         nlist_append(nl, start_siit, 96, NN_UNDEF);
152         nlist_append(nl, start_wkp, 96, NN_UNDEF);
153         nlist_append(nl, start_6to4, 16, NN_UNDEF);
154         nlist_append(nl, start_teredo, 32, NN_UNDEF);
155         nlist_finish(nl);
156     }
157 
158     return nl;
159 }
160