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 // fips104.c - FIPS 10-4 2-letter region code -> full text
21
22 #include <config.h>
23 #include "fips104.h"
24
25 #include <gdnsd/alloc.h>
26 #include <gdnsd/dmn.h>
27 #include <gdnsd/log.h>
28 #include <gdnsd/paths.h>
29 #include <gdnsd/misc.h>
30
31 #include <stdio.h>
32 #include <string.h>
33 #include <inttypes.h>
34
35 // Data source URL is http://www.maxmind.com/download/geoip/misc/region_codes.csv
36 // As of this writing (Mar 13, 2014), the file had a Last-Modified header of
37 // "Thu, 30 Jan 2014 18:27:35 GMT" and was 75728 bytes long with 4066 lines/records.
38
39 // I expect the record count to be relatively stable in the long
40 // term, so I'm picking a fixed hash table size that's a bit
41 // over 4x the count and doing an open-address thing.
42 // Must be a power of two for simple masking.
43 #define FIPS_HASH_SIZE 16384
44 #define FIPS_HASH_MASK (FIPS_HASH_SIZE - 1)
45
46 typedef struct {
47 char* val;
48 uint32_t key;
49 } fips_node_t;
50
51 struct _fips_t {
52 fips_node_t table[FIPS_HASH_SIZE];
53 };
54
55 // keys are a uint32_t made of 4 bytes: CCRR (Country/Region)
56 F_CONST
fips_hash(uint32_t key)57 static unsigned fips_hash(uint32_t key) {
58 dmn_assert(key);
59 return gdnsd_lookup2((const uint8_t*)&key, 4) & FIPS_HASH_MASK;
60 }
61
62 // It is assumed there are no duplicates in the input data.
63 F_NONNULL
fips_hash_add(fips_t * fips,const uint32_t key,const char * val)64 static void fips_hash_add(fips_t* fips, const uint32_t key, const char* val) {
65 unsigned jmpby = 1;
66 unsigned slotnum = fips_hash(key);
67 while(fips->table[slotnum].key)
68 slotnum = (slotnum + jmpby++) & FIPS_HASH_MASK;
69 fips->table[slotnum].key = key;
70 fips->table[slotnum].val = strdup(val);
71 }
72
73 F_NONNULL
fips_parse(fips_t * fips,FILE * file)74 static void fips_parse(fips_t* fips, FILE* file) {
75 unsigned line = 0;
76 while(1) {
77 char ccrr[5];
78 char rname[81];
79
80 line++;
81 const int fsf_rv = fscanf(file, "%2[A-Z0-9],%2[A-Z0-9],\"%80[^\"\n]\"\n",
82 ccrr, ccrr + 2, rname);
83
84 if(fsf_rv != 3) {
85 if(fsf_rv != EOF)
86 log_fatal("plugin_geoip: parse error in FIPS region name data, line %u", line);
87 return;
88 }
89
90 uint32_t key = ((unsigned)ccrr[0])
91 + ((unsigned)ccrr[1] << 8U)
92 + ((unsigned)ccrr[2] << 16U)
93 + ((unsigned)ccrr[3] << 24U);
94
95 fips_hash_add(fips, key, rname);
96 }
97 }
98
99 /**** public interface ****/
100
fips_lookup(const fips_t * fips,const uint32_t key)101 const char* fips_lookup(const fips_t* fips, const uint32_t key) {
102 dmn_assert(key);
103
104 unsigned jmpby = 1;
105 unsigned slotnum = fips_hash(key);
106 while(fips->table[slotnum].key) {
107 if(fips->table[slotnum].key == key)
108 return fips->table[slotnum].val;
109 slotnum = (slotnum + jmpby++) & FIPS_HASH_MASK;
110 }
111
112 return NULL;
113 }
114
fips_init(const char * pathname)115 fips_t* fips_init(const char* pathname) {
116 FILE* file = fopen(pathname, "r");
117 if(!file)
118 log_fatal("plugin_geoip: Cannot fopen() FIPS region file '%s' for reading: %s", pathname, dmn_logf_errno());
119 fips_t* fips = xcalloc(1, sizeof(fips_t));
120 fips_parse(fips, file);
121 if(fclose(file))
122 log_fatal("plugin_geoip: fclose() of FIPS region file '%s' failed: %s", pathname, dmn_logf_errno());
123 return fips;
124 }
125