1 /*
2  * virmacmap.c: MAC address <-> Domain name mapping
3  *
4  * Copyright (C) 2016 Red Hat, Inc.
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  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library.  If not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "virmacmap.h"
24 #include "virobject.h"
25 #include "virlog.h"
26 #include "virjson.h"
27 #include "virfile.h"
28 #include "virhash.h"
29 #include "virstring.h"
30 #include "viralloc.h"
31 
32 #define VIR_FROM_THIS VIR_FROM_NETWORK
33 
34 VIR_LOG_INIT("util.virmacmap");
35 
36 /**
37  * VIR_MAC_MAP_FILE_SIZE_MAX:
38  *
39  * Macro providing the upper limit on the size of mac maps file
40  */
41 #define VIR_MAC_MAP_FILE_SIZE_MAX (32 * 1024 * 1024)
42 
43 struct virMacMap {
44     virObjectLockable parent;
45 
46     GHashTable *macs;
47 };
48 
49 
50 static virClass *virMacMapClass;
51 
52 
53 static void
virMacMapDispose(void * obj)54 virMacMapDispose(void *obj)
55 {
56     virMacMap *mgr = obj;
57     GHashTableIter htitr;
58     void *value;
59 
60     g_hash_table_iter_init(&htitr,  mgr->macs);
61 
62     while (g_hash_table_iter_next(&htitr, NULL, &value))
63         g_slist_free_full(value, g_free);
64 
65     virHashFree(mgr->macs);
66 }
67 
68 
virMacMapOnceInit(void)69 static int virMacMapOnceInit(void)
70 {
71     if (!VIR_CLASS_NEW(virMacMap, virClassForObjectLockable()))
72         return -1;
73 
74     return 0;
75 }
76 
77 VIR_ONCE_GLOBAL_INIT(virMacMap);
78 
79 
80 static void
virMacMapAddLocked(virMacMap * mgr,const char * domain,const char * mac)81 virMacMapAddLocked(virMacMap *mgr,
82                    const char *domain,
83                    const char *mac)
84 {
85     GSList *orig_list;
86     GSList *list;
87     GSList *next;
88 
89     list = orig_list = g_hash_table_lookup(mgr->macs, domain);
90 
91     for (next = list; next; next = next->next) {
92         if (STREQ((const char *) next->data, mac))
93             return;
94     }
95 
96     list = g_slist_append(list, g_strdup(mac));
97 
98     if (list != orig_list)
99         g_hash_table_insert(mgr->macs, g_strdup(domain), list);
100 }
101 
102 
103 static void
virMacMapRemoveLocked(virMacMap * mgr,const char * domain,const char * mac)104 virMacMapRemoveLocked(virMacMap *mgr,
105                       const char *domain,
106                       const char *mac)
107 {
108     GSList *orig_list;
109     GSList *list;
110     GSList *next;
111 
112     list = orig_list = g_hash_table_lookup(mgr->macs, domain);
113 
114     if (!orig_list)
115         return;
116 
117     for (next = list; next; next = next->next) {
118         if (STREQ((const char *) next->data, mac)) {
119             list = g_slist_remove_link(list, next);
120             g_slist_free_full(next, g_free);
121             break;
122         }
123     }
124 
125     if (list != orig_list) {
126         if (list)
127             g_hash_table_insert(mgr->macs, g_strdup(domain), list);
128         else
129             g_hash_table_remove(mgr->macs, domain);
130     }
131 }
132 
133 
134 static int
virMacMapLoadFile(virMacMap * mgr,const char * file)135 virMacMapLoadFile(virMacMap *mgr,
136                   const char *file)
137 {
138     g_autofree char *map_str = NULL;
139     g_autoptr(virJSONValue) map = NULL;
140     int map_str_len = 0;
141     size_t i;
142 
143     if (virFileExists(file) &&
144         (map_str_len = virFileReadAll(file,
145                                       VIR_MAC_MAP_FILE_SIZE_MAX,
146                                       &map_str)) < 0)
147         return -1;
148 
149     if (map_str_len == 0)
150         return 0;
151 
152     if (!(map = virJSONValueFromString(map_str))) {
153         virReportError(VIR_ERR_INTERNAL_ERROR,
154                        _("invalid json in file: %s"),
155                        file);
156         return -1;
157     }
158 
159     if (!virJSONValueIsArray(map)) {
160         virReportError(VIR_ERR_INTERNAL_ERROR,
161                        _("Malformed file structure: %s"),
162                        file);
163         return -1;
164     }
165 
166     for (i = 0; i < virJSONValueArraySize(map); i++) {
167         virJSONValue *tmp = virJSONValueArrayGet(map, i);
168         virJSONValue *macs;
169         const char *domain;
170         size_t j;
171         GSList *vals = NULL;
172 
173         if (!(domain = virJSONValueObjectGetString(tmp, "domain"))) {
174             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
175                            _("Missing domain"));
176             return -1;
177         }
178 
179         if (!(macs = virJSONValueObjectGetArray(tmp, "macs"))) {
180             virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
181                            _("Missing macs"));
182             return -1;
183         }
184 
185         if (g_hash_table_contains(mgr->macs, domain)) {
186             virReportError(VIR_ERR_INTERNAL_ERROR,
187                            _("duplicate domain '%s'"), domain);
188             return -1;
189 
190         }
191 
192         for (j = 0; j < virJSONValueArraySize(macs); j++) {
193             virJSONValue *macJSON = virJSONValueArrayGet(macs, j);
194 
195             vals = g_slist_prepend(vals, g_strdup(virJSONValueGetString(macJSON)));
196         }
197 
198         vals = g_slist_reverse(vals);
199         g_hash_table_insert(mgr->macs, g_strdup(domain), vals);
200     }
201 
202     return 0;
203 }
204 
205 
206 static int
virMACMapHashDumper(void * payload,const char * name,void * data)207 virMACMapHashDumper(void *payload,
208                     const char *name,
209                     void *data)
210 {
211     g_autoptr(virJSONValue) obj = virJSONValueNewObject();
212     g_autoptr(virJSONValue) arr = virJSONValueNewArray();
213     GSList *macs = payload;
214     GSList *next;
215 
216     for (next = macs; next; next = next->next) {
217         g_autoptr(virJSONValue) m = virJSONValueNewString((const char *) next->data);
218 
219         if (virJSONValueArrayAppend(arr, &m) < 0)
220             return -1;
221     }
222 
223     if (virJSONValueObjectAppendString(obj, "domain", name) < 0 ||
224         virJSONValueObjectAppend(obj, "macs", &arr) < 0)
225         return -1;
226 
227     if (virJSONValueArrayAppend(data, &obj) < 0)
228         return -1;
229 
230     return 0;
231 }
232 
233 
234 static int
virMacMapDumpStrLocked(virMacMap * mgr,char ** str)235 virMacMapDumpStrLocked(virMacMap *mgr,
236                        char **str)
237 {
238     g_autoptr(virJSONValue) arr = virJSONValueNewArray();
239 
240     if (virHashForEachSorted(mgr->macs, virMACMapHashDumper, arr) < 0)
241         return -1;
242 
243     if (!(*str = virJSONValueToString(arr, true)))
244         return -1;
245 
246     return 0;
247 }
248 
249 
250 static int
virMacMapWriteFileLocked(virMacMap * mgr,const char * file)251 virMacMapWriteFileLocked(virMacMap *mgr,
252                          const char *file)
253 {
254     g_autofree char *str = NULL;
255 
256     if (virMacMapDumpStrLocked(mgr, &str) < 0)
257         return -1;
258 
259     if (virFileRewriteStr(file, 0644, str) < 0)
260         return -1;
261 
262     return 0;
263 }
264 
265 
266 char *
virMacMapFileName(const char * dnsmasqStateDir,const char * bridge)267 virMacMapFileName(const char *dnsmasqStateDir,
268                   const char *bridge)
269 {
270     return g_strdup_printf("%s/%s.macs", dnsmasqStateDir, bridge);
271 }
272 
273 
274 #define VIR_MAC_HASH_TABLE_SIZE 10
275 
276 virMacMap *
virMacMapNew(const char * file)277 virMacMapNew(const char *file)
278 {
279     virMacMap *mgr;
280 
281     if (virMacMapInitialize() < 0)
282         return NULL;
283 
284     if (!(mgr = virObjectLockableNew(virMacMapClass)))
285         return NULL;
286 
287     virObjectLock(mgr);
288 
289     mgr->macs = virHashNew(NULL);
290 
291     if (file &&
292         virMacMapLoadFile(mgr, file) < 0)
293         goto error;
294 
295     virObjectUnlock(mgr);
296     return mgr;
297 
298  error:
299     virObjectUnlock(mgr);
300     virObjectUnref(mgr);
301     return NULL;
302 }
303 
304 
305 int
virMacMapAdd(virMacMap * mgr,const char * domain,const char * mac)306 virMacMapAdd(virMacMap *mgr,
307              const char *domain,
308              const char *mac)
309 {
310     virObjectLock(mgr);
311     virMacMapAddLocked(mgr, domain, mac);
312     virObjectUnlock(mgr);
313     return 0;
314 }
315 
316 
317 int
virMacMapRemove(virMacMap * mgr,const char * domain,const char * mac)318 virMacMapRemove(virMacMap *mgr,
319                 const char *domain,
320                 const char *mac)
321 {
322     virObjectLock(mgr);
323     virMacMapRemoveLocked(mgr, domain, mac);
324     virObjectUnlock(mgr);
325     return 0;
326 }
327 
328 
329 /* note that the returned pointer may be invalidated by other APIs in this module */
330 GSList *
virMacMapLookup(virMacMap * mgr,const char * domain)331 virMacMapLookup(virMacMap *mgr,
332                 const char *domain)
333 {
334     GSList *ret;
335 
336     virObjectLock(mgr);
337     ret = virHashLookup(mgr->macs, domain);
338     virObjectUnlock(mgr);
339     return ret;
340 }
341 
342 
343 int
virMacMapWriteFile(virMacMap * mgr,const char * filename)344 virMacMapWriteFile(virMacMap *mgr,
345                    const char *filename)
346 {
347     int ret;
348 
349     virObjectLock(mgr);
350     ret = virMacMapWriteFileLocked(mgr, filename);
351     virObjectUnlock(mgr);
352     return ret;
353 }
354 
355 
356 int
virMacMapDumpStr(virMacMap * mgr,char ** str)357 virMacMapDumpStr(virMacMap *mgr,
358                  char **str)
359 {
360     int ret;
361 
362     virObjectLock(mgr);
363     ret = virMacMapDumpStrLocked(mgr, str);
364     virObjectUnlock(mgr);
365     return ret;
366 }
367