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