]> git.ipfire.org Git - people/ms/libloc.git/blob - src/database.c
database: Load networks from database
[people/ms/libloc.git] / src / database.c
1 /*
2 libloc - A library to determine the location of someone on the Internet
3
4 Copyright (C) 2017 IPFire Development Team <info@ipfire.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15 */
16
17 #include <endian.h>
18 #include <errno.h>
19 #include <stddef.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/mman.h>
25 #include <sys/types.h>
26 #include <time.h>
27 #include <unistd.h>
28
29 #include <loc/libloc.h>
30 #include <loc/as.h>
31 #include <loc/database.h>
32 #include <loc/format.h>
33 #include <loc/private.h>
34 #include <loc/stringpool.h>
35
36 struct loc_database {
37 struct loc_ctx* ctx;
38 int refcount;
39
40 unsigned int version;
41 time_t created_at;
42 off_t vendor;
43 off_t description;
44
45 // ASes in the database
46 struct loc_database_as_v0* as_v0;
47 size_t as_count;
48
49 // Network tree
50 struct loc_database_network_node_v0* network_nodes_v0;
51 size_t network_nodes_count;
52
53 // Networks
54 struct loc_database_network_v0* networks_v0;
55 size_t networks_count;
56
57 struct loc_stringpool* pool;
58 };
59
60 static int loc_database_read_magic(struct loc_database* db, FILE* f) {
61 struct loc_database_magic magic;
62
63 // Read from file
64 size_t bytes_read = fread(&magic, 1, sizeof(magic), f);
65
66 // Check if we have been able to read enough data
67 if (bytes_read < sizeof(magic)) {
68 ERROR(db->ctx, "Could not read enough data to validate magic bytes\n");
69 DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic));
70 return -ENOMSG;
71 }
72
73 // Compare magic bytes
74 if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) {
75 DEBUG(db->ctx, "Magic value matches\n");
76
77 // Parse version
78 db->version = be16toh(magic.version);
79 DEBUG(db->ctx, "Database version is %u\n", db->version);
80
81 return 0;
82 }
83
84 ERROR(db->ctx, "Database format is not compatible\n");
85
86 // Return an error
87 return 1;
88 }
89
90 static int loc_database_read_as_section_v0(struct loc_database* db,
91 FILE* f, const struct loc_database_header_v0* header) {
92 off_t as_offset = be32toh(header->as_offset);
93 size_t as_length = be32toh(header->as_length);
94
95 DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length);
96
97 if (as_length > 0) {
98 db->as_v0 = mmap(NULL, as_length, PROT_READ,
99 MAP_SHARED, fileno(f), as_offset);
100
101 if (db->as_v0 == MAP_FAILED)
102 return -errno;
103 }
104
105 db->as_count = as_length / sizeof(*db->as_v0);
106
107 INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
108
109 return 0;
110 }
111
112 static int loc_database_read_network_nodes_section_v0(struct loc_database* db,
113 FILE* f, const struct loc_database_header_v0* header) {
114 off_t network_nodes_offset = be32toh(header->network_tree_offset);
115 size_t network_nodes_length = be32toh(header->network_tree_length);
116
117 DEBUG(db->ctx, "Reading network nodes section from %jd (%zu bytes)\n",
118 network_nodes_offset, network_nodes_length);
119
120 if (network_nodes_length > 0) {
121 db->network_nodes_v0 = mmap(NULL, network_nodes_length, PROT_READ,
122 MAP_SHARED, fileno(f), network_nodes_offset);
123
124 if (db->network_nodes_v0 == MAP_FAILED)
125 return -errno;
126 }
127
128 db->network_nodes_count = network_nodes_length / sizeof(*db->network_nodes_v0);
129
130 INFO(db->ctx, "Read %zu network nodes from the database\n", db->network_nodes_count);
131
132 return 0;
133 }
134
135 static int loc_database_read_networks_section_v0(struct loc_database* db,
136 FILE* f, const struct loc_database_header_v0* header) {
137 off_t networks_offset = be32toh(header->network_data_offset);
138 size_t networks_length = be32toh(header->network_data_length);
139
140 DEBUG(db->ctx, "Reading networks section from %jd (%zu bytes)\n",
141 networks_offset, networks_length);
142
143 if (networks_length > 0) {
144 db->networks_v0 = mmap(NULL, networks_length, PROT_READ,
145 MAP_SHARED, fileno(f), networks_offset);
146
147 if (db->networks_v0 == MAP_FAILED)
148 return -errno;
149 }
150
151 db->networks_count = networks_length / sizeof(*db->networks_v0);
152
153 INFO(db->ctx, "Read %zu networks from the database\n", db->networks_count);
154
155 return 0;
156 }
157
158 static int loc_database_read_header_v0(struct loc_database* db, FILE* f) {
159 struct loc_database_header_v0 header;
160
161 // Read from file
162 size_t size = fread(&header, 1, sizeof(header), f);
163
164 if (size < sizeof(header)) {
165 ERROR(db->ctx, "Could not read enough data for header\n");
166 return -ENOMSG;
167 }
168
169 // Copy over data
170 db->created_at = be64toh(header.created_at);
171 db->vendor = be32toh(header.vendor);
172 db->description = be32toh(header.description);
173
174 // Open pool
175 off_t pool_offset = be32toh(header.pool_offset);
176 size_t pool_length = be32toh(header.pool_length);
177
178 int r = loc_stringpool_open(db->ctx, &db->pool,
179 f, pool_length, pool_offset);
180 if (r)
181 return r;
182
183 // AS section
184 r = loc_database_read_as_section_v0(db, f, &header);
185 if (r)
186 return r;
187
188 // Network Nodes
189 r = loc_database_read_network_nodes_section_v0(db, f, &header);
190 if (r)
191 return r;
192
193 // Networks
194 r = loc_database_read_networks_section_v0(db, f, &header);
195 if (r)
196 return r;
197
198 return 0;
199 }
200
201 static int loc_database_read_header(struct loc_database* db, FILE* f) {
202 switch (db->version) {
203 case 0:
204 return loc_database_read_header_v0(db, f);
205
206 default:
207 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
208 return 1;
209 }
210 }
211
212 static int loc_database_read(struct loc_database* db, FILE* f) {
213 clock_t start = clock();
214
215 // Read magic bytes
216 int r = loc_database_read_magic(db, f);
217 if (r)
218 return r;
219
220 // Read the header
221 r = loc_database_read_header(db, f);
222 if (r)
223 return r;
224
225 clock_t end = clock();
226
227 INFO(db->ctx, "Opened database in %.8fs\n",
228 (double)(end - start) / CLOCKS_PER_SEC);
229
230 return 0;
231 }
232
233 LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
234 // Fail on invalid file handle
235 if (!f)
236 return -EINVAL;
237
238 struct loc_database* db = calloc(1, sizeof(*db));
239 if (!db)
240 return -ENOMEM;
241
242 // Reference context
243 db->ctx = loc_ref(ctx);
244 db->refcount = 1;
245
246 DEBUG(db->ctx, "Database object allocated at %p\n", db);
247
248 int r = loc_database_read(db, f);
249 if (r) {
250 loc_database_unref(db);
251 return r;
252 }
253
254 *database = db;
255
256 return 0;
257 }
258
259 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
260 db->refcount++;
261
262 return db;
263 }
264
265 static void loc_database_free(struct loc_database* db) {
266 DEBUG(db->ctx, "Releasing database %p\n", db);
267
268 // Removing all ASes
269 if (db->as_v0) {
270 int r = munmap(db->as_v0, db->as_count * sizeof(*db->as_v0));
271 if (r)
272 ERROR(db->ctx, "Could not unmap AS section: %s\n", strerror(errno));
273 }
274
275 loc_stringpool_unref(db->pool);
276
277 loc_unref(db->ctx);
278 free(db);
279 }
280
281 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
282 if (--db->refcount > 0)
283 return NULL;
284
285 loc_database_free(db);
286 return NULL;
287 }
288
289 LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
290 return db->created_at;
291 }
292
293 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
294 return loc_stringpool_get(db->pool, db->vendor);
295 }
296
297 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
298 return loc_stringpool_get(db->pool, db->description);
299 }
300
301 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
302 return db->as_count;
303 }
304
305 // Returns the AS at position pos
306 static int loc_database_fetch_as(struct loc_database* db, struct loc_as** as, off_t pos) {
307 if ((size_t)pos >= db->as_count)
308 return -EINVAL;
309
310 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
311
312 int r;
313 switch (db->version) {
314 case 0:
315 r = loc_as_new_from_database_v0(db->ctx, db->pool, as, db->as_v0 + pos);
316 break;
317
318 default:
319 return -1;
320 }
321
322 if (r == 0) {
323 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
324 }
325
326 return r;
327 }
328
329 // Performs a binary search to find the AS in the list
330 LOC_EXPORT int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number) {
331 off_t lo = 0;
332 off_t hi = db->as_count - 1;
333
334 // Save start time
335 clock_t start = clock();
336
337 while (lo <= hi) {
338 off_t i = (lo + hi) / 2;
339
340 // Fetch AS in the middle between lo and hi
341 int r = loc_database_fetch_as(db, as, i);
342 if (r)
343 return r;
344
345 // Check if this is a match
346 uint32_t as_number = loc_as_get_number(*as);
347 if (as_number == number) {
348 clock_t end = clock();
349
350 // Log how fast this has been
351 DEBUG(db->ctx, "Found AS%u in %.8fs\n", as_number,
352 (double)(end - start) / CLOCKS_PER_SEC);
353
354 return 0;
355 }
356
357 // If it wasn't, we release the AS and
358 // adjust our search pointers
359 loc_as_unref(*as);
360
361 if (as_number < number) {
362 lo = i + 1;
363 } else
364 hi = i - 1;
365 }
366
367 // Nothing found
368 *as = NULL;
369
370 return 1;
371 }