Adjust to coding style rules
[platform/core/security/vasum.git] / server / netdev.cpp
1 /*
2  *  Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Contact: Mateusz Malicki <m.malicki2@samsung.com>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License
17  */
18
19 /**
20  * @file
21  * @author  Mateusz Malicki (m.malicki2@samsung.com)
22  * @brief   Network devices management functions definition
23  */
24
25 #include "config.hpp"
26 #include "netdev.hpp"
27 #include "netlink/netlink-message.hpp"
28 #include "utils/make-clean.hpp"
29 #include "utils/exception.hpp"
30 #include "utils.hpp"
31 #include "exception.hpp"
32 #include "logger/logger.hpp"
33
34 #include <algorithm>
35 #include <string>
36 #include <cstdint>
37 #include <cstring>
38 #include <cassert>
39 #include <sstream>
40 #include <set>
41
42 #include <boost/algorithm/string/split.hpp>
43 #include <boost/algorithm/string/classification.hpp>
44
45 #include <net/if.h>
46 #include <unistd.h>
47 #include <sys/ioctl.h>
48 #include <arpa/inet.h>
49 #include <ifaddrs.h>
50 #include <linux/rtnetlink.h>
51 #include <linux/veth.h>
52 #include <linux/sockios.h>
53 #include <linux/if_link.h>
54 #include <linux/rtnetlink.h>
55 #include <linux/if_bridge.h>
56
57 //IFLA_BRIDGE_FLAGS and BRIDGE_FLAGS_MASTER
58 //should be defined in linux/if_bridge.h since kernel v3.7
59 #ifndef IFLA_BRIDGE_FLAGS
60 #define IFLA_BRIDGE_FLAGS 0
61 #endif
62 #ifndef BRIDGE_FLAGS_MASTER
63 #define BRIDGE_FLAGS_MASTER 1
64 #endif
65
66 using namespace std;
67 using namespace utils;
68 using namespace vasum::netlink;
69
70 namespace vasum {
71 namespace netdev {
72
73 namespace {
74
75 string getUniqueVethName()
76 {
77     auto find = [](const ifaddrs* ifaddr, const string& name) -> bool {
78         for (const ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
79             if (name == ifa->ifa_name) {
80                 return true;
81             }
82         }
83         return false;
84     };
85
86     ifaddrs* ifaddr;
87     getifaddrs(&ifaddr);
88     string newName;
89     int i = 0;
90     do {
91         newName = "veth0" + to_string(++i);
92     } while (find(ifaddr, newName));
93
94     freeifaddrs(ifaddr);
95     return newName;
96 }
97
98 uint32_t getInterfaceIndex(const string& name) {
99     uint32_t index = if_nametoindex(name.c_str());
100     if (!index) {
101         LOGE("Can't get " << name << " interface index (" << getSystemErrorMessage() << ")");
102         throw ZoneOperationException("Can't find interface");
103     }
104     return index;
105 }
106
107 uint32_t getInterfaceIndex(const string& name, pid_t nsPid) {
108     NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK);
109     ifinfomsg infoPeer = utils::make_clean<ifinfomsg>();
110     infoPeer.ifi_family = AF_UNSPEC;
111     infoPeer.ifi_change = 0xFFFFFFFF;
112     nlm.put(infoPeer)
113         .put(IFLA_IFNAME, name);
114     NetlinkResponse response = send(nlm, nsPid);
115     if (!response.hasMessage()) {
116         throw VasumException("Can't get interface index");
117     }
118
119     response.fetch(infoPeer);
120     return infoPeer.ifi_index;
121 }
122
123 int getIpFamily(const std::string& ip)
124 {
125     return ip.find(':') == std::string::npos ? AF_INET : AF_INET6;
126 }
127
128 void validateNetdevName(const string& name)
129 {
130     if (name.size() <= 1 || name.size() >= IFNAMSIZ) {
131         throw ZoneOperationException("Invalid netdev name format");
132     }
133 }
134
135 void createPipedNetdev(const string& netdev1, const string& netdev2)
136 {
137     validateNetdevName(netdev1);
138     validateNetdevName(netdev2);
139
140     NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK);
141     ifinfomsg infoPeer = utils::make_clean<ifinfomsg>();
142     infoPeer.ifi_family = AF_UNSPEC;
143     infoPeer.ifi_change = 0xFFFFFFFF;
144     nlm.put(infoPeer)
145         .beginNested(IFLA_LINKINFO)
146             .put(IFLA_INFO_KIND, "veth")
147             .beginNested(IFLA_INFO_DATA)
148                 .beginNested(VETH_INFO_PEER)
149                     .put(infoPeer)
150                     .put(IFLA_IFNAME, netdev2)
151                 .endNested()
152             .endNested()
153         .endNested()
154         .put(IFLA_IFNAME, netdev1);
155     send(nlm);
156 }
157
158 void attachToBridge(const string& bridge, const string& netdev)
159 {
160     validateNetdevName(bridge);
161     validateNetdevName(netdev);
162
163     uint32_t index = getInterfaceIndex(netdev);
164     int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
165     if (fd < 0) {
166         LOGE("Can't open socket (" << getSystemErrorMessage() << ")");
167         throw ZoneOperationException("Can't attach to bridge");
168     }
169
170     struct ifreq ifr = utils::make_clean<ifreq>();
171     strncpy(ifr.ifr_name, bridge.c_str(), IFNAMSIZ);
172     ifr.ifr_ifindex = index;
173     int err = ioctl(fd, SIOCBRADDIF, &ifr);
174     if (err < 0) {
175         int error = errno;
176         //TODO: Close can be interrupted. Move util functions from ipc
177         ::close(fd);
178         LOGE("Can't attach to bridge (" + getSystemErrorMessage(error) + ")");
179         throw ZoneOperationException("Can't attach to bridge");
180     }
181     close(fd);
182 }
183
184 int setFlags(const string& name, uint32_t mask, uint32_t flags)
185 {
186     uint32_t index = getInterfaceIndex(name);
187     NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK);
188     ifinfomsg infoPeer = utils::make_clean<ifinfomsg>();
189     infoPeer.ifi_family = AF_UNSPEC;
190     infoPeer.ifi_index = index;
191     infoPeer.ifi_flags = flags;
192     // since kernel v2.6.22 ifi_change is used to change only selected flags;
193     infoPeer.ifi_change = mask;
194     nlm.put(infoPeer);
195     send(nlm);
196     return 0;
197 }
198
199 void up(const string& netdev)
200 {
201     setFlags(netdev, IFF_UP, IFF_UP);
202 }
203
204 void moveToNS(const string& netdev, pid_t pid)
205 {
206     uint32_t index = getInterfaceIndex(netdev);
207     NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK);
208     ifinfomsg infopeer = utils::make_clean<ifinfomsg>();
209     infopeer.ifi_family = AF_UNSPEC;
210     infopeer.ifi_index = index;
211     nlm.put(infopeer)
212         .put(IFLA_NET_NS_PID, pid);
213     send(nlm);
214 }
215
216 void createMacvlan(const string& master, const string& slave, const macvlan_mode& mode)
217 {
218     validateNetdevName(master);
219     validateNetdevName(slave);
220
221     uint32_t index = getInterfaceIndex(master);
222     NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL|NLM_F_ACK);
223     ifinfomsg infopeer = utils::make_clean<ifinfomsg>();
224     infopeer.ifi_family = AF_UNSPEC;
225     infopeer.ifi_change = 0xFFFFFFFF;
226     nlm.put(infopeer)
227         .beginNested(IFLA_LINKINFO)
228             .put(IFLA_INFO_KIND, "macvlan")
229             .beginNested(IFLA_INFO_DATA)
230                 .put(IFLA_MACVLAN_MODE, static_cast<uint32_t>(mode))
231             .endNested()
232         .endNested()
233         .put(IFLA_LINK, index)
234         .put(IFLA_IFNAME, slave);
235     send(nlm);
236 }
237
238 std::vector<Attrs> getIpAddresses(const pid_t nsPid, int family, uint32_t index)
239 {
240     NetlinkMessage nlm(RTM_GETADDR, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
241     ifaddrmsg infoAddr = utils::make_clean<ifaddrmsg>();
242     infoAddr.ifa_family = family;
243     nlm.put(infoAddr);
244     NetlinkResponse response = send(nlm, nsPid);
245     if (!response.hasMessage()) {
246         //There is no interfaces with addresses
247         return std::vector<Attrs>();
248     }
249
250     std::vector<Attrs> addresses;
251     while (response.hasMessage()) {
252         ifaddrmsg addrmsg;
253         response.fetch(addrmsg);
254         if (addrmsg.ifa_index == index) {
255             Attrs attrs;
256             attrs.push_back(make_tuple("prefixlen", std::to_string(addrmsg.ifa_prefixlen)));
257             attrs.push_back(make_tuple("flags", std::to_string(addrmsg.ifa_flags)));
258             attrs.push_back(make_tuple("scope", std::to_string(addrmsg.ifa_scope)));
259             attrs.push_back(make_tuple("family", std::to_string(addrmsg.ifa_family)));
260             while (response.hasAttribute()) {
261                 assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN);
262                 char buf[INET6_ADDRSTRLEN];
263                 in6_addr addr6;
264                 in_addr addr4;
265                 const void* addr = NULL;
266                 int attrType = response.getAttributeType();
267                 switch (attrType) {
268                     case IFA_ADDRESS:
269                         if (family == AF_INET6) {
270                             response.fetch(IFA_ADDRESS, addr6);
271                             addr = &addr6;
272                         } else {
273                             assert(family == AF_INET);
274                             response.fetch(IFA_ADDRESS, addr4);
275                             addr = &addr4;
276                         }
277                         addr = inet_ntop(family, addr, buf, sizeof(buf));
278                         if (addr == NULL) {
279                             LOGE("Can't convert ip address: " << getSystemErrorMessage());
280                             throw VasumException("Can't get ip address");
281                         }
282                         attrs.push_back(make_tuple("ip", buf));
283                         break;
284                     default:
285                         response.skipAttribute();
286                         break;
287                 }
288             }
289             addresses.push_back(std::move(attrs));
290         }
291         response.fetchNextMessage();
292     }
293     return addresses;
294 }
295
296 void  setIpAddresses(const pid_t nsPid,
297                      const uint32_t index,
298                      const Attrs& attrs,
299                      int family)
300 {
301     NetlinkMessage nlm(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK);
302     ifaddrmsg infoAddr = utils::make_clean<ifaddrmsg>();
303     infoAddr.ifa_family = family;
304     infoAddr.ifa_index = index;
305     for (const auto& attr : attrs) {
306         if (get<0>(attr) == "prefixlen") {
307             infoAddr.ifa_prefixlen = stoul(get<1>(attr));
308         }
309         if (get<0>(attr) == "flags") {
310             infoAddr.ifa_flags = stoul(get<1>(attr));
311         }
312         if (get<0>(attr) == "scope") {
313             infoAddr.ifa_scope = stoul(get<1>(attr));
314         }
315     }
316     nlm.put(infoAddr);
317     for (const auto& attr : attrs) {
318         if (get<0>(attr) == "ip") {
319             if (family == AF_INET6) {
320                 in6_addr addr6;
321                 if (inet_pton(AF_INET6, get<1>(attr).c_str(), &addr6) != 1) {
322                     throw VasumException("Can't set ipv4 address");
323                 };
324                 nlm.put(IFA_ADDRESS, addr6);
325                 nlm.put(IFA_LOCAL, addr6);
326             } else {
327                 assert(family == AF_INET);
328                 in_addr addr4;
329                 if (inet_pton(AF_INET, get<1>(attr).c_str(), &addr4) != 1) {
330                     throw VasumException("Can't set ipv6 address");
331                 };
332                 nlm.put(IFA_LOCAL, addr4);
333             }
334         }
335     }
336     send(nlm, nsPid);
337 }
338
339 void deleteIpAddress(const pid_t nsPid,
340                      const uint32_t index,
341                      const std::string& ip,
342                      int prefixlen,
343                      int family)
344 {
345     NetlinkMessage nlm(RTM_DELADDR, NLM_F_REQUEST | NLM_F_ACK);
346     ifaddrmsg infoAddr = utils::make_clean<ifaddrmsg>();
347     infoAddr.ifa_family = family;
348     infoAddr.ifa_index = index;
349     infoAddr.ifa_prefixlen = prefixlen;
350     nlm.put(infoAddr);
351     if (family == AF_INET6) {
352         in6_addr addr6;
353         if (inet_pton(AF_INET6, ip.c_str(), &addr6) != 1) {
354             throw VasumException("Can't delete ipv6 address");
355         };
356         nlm.put(IFA_ADDRESS, addr6);
357         nlm.put(IFA_LOCAL, addr6);
358     } else {
359         assert(family == AF_INET);
360         in_addr addr4;
361         if (inet_pton(AF_INET, ip.c_str(), &addr4) != 1) {
362             throw VasumException("Can't delete ipv4 address");
363         };
364         nlm.put(IFA_LOCAL, addr4);
365     }
366     send(nlm, nsPid);
367 }
368
369 } // namespace
370
371 void createVeth(const pid_t& nsPid, const string& nsDev, const string& hostDev)
372 {
373     string hostVeth = getUniqueVethName();
374     LOGT("Creating veth: bridge: " << hostDev << ", port: " << hostVeth << ", zone: " << nsDev);
375     createPipedNetdev(nsDev, hostVeth);
376     try {
377         attachToBridge(hostDev, hostVeth);
378         up(hostVeth);
379         moveToNS(nsDev, nsPid);
380     } catch(const exception& ex) {
381         try {
382             destroyNetdev(hostVeth);
383         } catch (const exception& ex) {
384             LOGE("Can't destroy netdev pipe: " << hostVeth << ", " << nsDev);
385         }
386         throw;
387     }
388 }
389
390 void createMacvlan(const pid_t& nsPid,
391                    const string& nsDev,
392                    const string& hostDev,
393                    const macvlan_mode& mode)
394 {
395     LOGT("Creating macvlan: host: " << hostDev << ", zone: " << nsDev << ", mode: " << mode);
396     createMacvlan(hostDev, nsDev, mode);
397     try {
398         up(nsDev);
399         moveToNS(nsDev, nsPid);
400     } catch(const exception& ex) {
401         try {
402             destroyNetdev(nsDev);
403         } catch (const exception& ex) {
404             LOGE("Can't destroy netdev: " << nsDev);
405         }
406         throw;
407     }
408 }
409
410 void movePhys(const pid_t& nsPid, const string& devId)
411 {
412     LOGT("Creating phys: dev: " << devId);
413     moveToNS(devId, nsPid);
414 }
415
416 std::vector<std::string> listNetdev(const pid_t& nsPid)
417 {
418     NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST|NLM_F_DUMP|NLM_F_ROOT);
419     ifinfomsg info = utils::make_clean<ifinfomsg>();
420     info.ifi_family = AF_PACKET;
421     nlm.put(info);
422     NetlinkResponse response = send(nlm, nsPid);
423     std::vector<std::string> interfaces;
424     while (response.hasMessage()) {
425         std::string ifName;
426         response.skip<ifinfomsg>();
427         response.fetch(IFLA_IFNAME, ifName);
428         interfaces.push_back(ifName);
429         response.fetchNextMessage();
430     }
431     return interfaces;
432 }
433
434 void destroyNetdev(const string& netdev, const pid_t pid)
435 {
436     LOGT("Destroying netdev: " << netdev);
437     validateNetdevName(netdev);
438
439     NetlinkMessage nlm(RTM_DELLINK, NLM_F_REQUEST|NLM_F_ACK);
440     ifinfomsg infopeer = utils::make_clean<ifinfomsg>();
441     infopeer.ifi_family = AF_UNSPEC;
442     infopeer.ifi_change = 0xFFFFFFFF;
443     nlm.put(infopeer)
444         .put(IFLA_IFNAME, netdev);
445     send(nlm, pid);
446 }
447
448 void createBridge(const string& netdev)
449 {
450     LOGT("Creating bridge: " << netdev);
451     validateNetdevName(netdev);
452
453     NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK);
454     ifinfomsg infoPeer = utils::make_clean<ifinfomsg>();
455     infoPeer.ifi_family = AF_UNSPEC;
456     infoPeer.ifi_change = 0xFFFFFFFF;
457     nlm.put(infoPeer)
458         .beginNested(IFLA_LINKINFO)
459             .put(IFLA_INFO_KIND, "bridge")
460             .beginNested(IFLA_INFO_DATA)
461                 .beginNested(IFLA_AF_SPEC)
462                     .put<uint32_t>(IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_MASTER)
463                 .endNested()
464             .endNested()
465         .endNested()
466         .put(IFLA_IFNAME, netdev);
467     send(nlm);
468 }
469
470 Attrs getAttrs(const pid_t nsPid, const std::string& netdev)
471 {
472     auto joinAddresses = [](const Attrs& attrs) -> std::string {
473         bool first = true;
474         stringstream ss;
475         for (const auto& attr : attrs) {
476             ss << (first ? "" : ",") << get<0>(attr) << ":" << get<1>(attr);
477             first = false;
478         }
479         return ss.str();
480     };
481
482     LOGT("Getting network device informations: " << netdev);
483     validateNetdevName(netdev);
484
485     NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK);
486     ifinfomsg infoPeer = utils::make_clean<ifinfomsg>();
487     infoPeer.ifi_family = AF_UNSPEC;
488     infoPeer.ifi_change = 0xFFFFFFFF;
489     nlm.put(infoPeer)
490         .put(IFLA_IFNAME, netdev);
491     NetlinkResponse response = send(nlm, nsPid);
492     if (!response.hasMessage()) {
493         throw VasumException("Can't get interface information");
494     }
495     response.fetch(infoPeer);
496
497     Attrs attrs;
498     while (response.hasAttribute()) {
499         uint32_t mtu, link;
500         int attrType = response.getAttributeType();
501         switch (attrType) {
502             case IFLA_MTU:
503                 response.fetch(IFLA_MTU, mtu);
504                 attrs.push_back(make_tuple("mtu", std::to_string(mtu)));
505                 break;
506             case IFLA_LINK:
507                 response.fetch(IFLA_LINK, link);
508                 attrs.push_back(make_tuple("link", std::to_string(link)));
509                 break;
510             default:
511                 response.skipAttribute();
512                 break;
513         }
514     }
515     attrs.push_back(make_tuple("flags", std::to_string(infoPeer.ifi_flags)));
516     attrs.push_back(make_tuple("type", std::to_string(infoPeer.ifi_type)));
517     for (const auto& address : getIpAddresses(nsPid, AF_INET, infoPeer.ifi_index)) {
518         attrs.push_back(make_tuple("ipv4", joinAddresses(address)));
519     }
520     for (const auto& address : getIpAddresses(nsPid, AF_INET6, infoPeer.ifi_index)) {
521         attrs.push_back(make_tuple("ipv6", joinAddresses(address)));
522     }
523
524     return attrs;
525 }
526
527 void setAttrs(const pid_t nsPid, const std::string& netdev, const Attrs& attrs)
528 {
529     const set<string> supportedAttrs{"flags", "change", "type", "mtu", "link", "ipv4", "ipv6"};
530
531     LOGT("Setting network device informations: " << netdev);
532     validateNetdevName(netdev);
533     for (const auto& attr : attrs) {
534         if (supportedAttrs.find(get<0>(attr)) == supportedAttrs.end()) {
535             throw VasumException("Unsupported attribute: " + get<0>(attr));
536         }
537     }
538
539     NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK);
540     ifinfomsg infoPeer = utils::make_clean<ifinfomsg>();
541     infoPeer.ifi_family = AF_UNSPEC;
542     infoPeer.ifi_index = getInterfaceIndex(netdev, nsPid);
543     infoPeer.ifi_change = 0xFFFFFFFF;
544     for (const auto& attr : attrs) {
545         if (get<0>(attr) == "flags") {
546             infoPeer.ifi_flags = stoul(get<1>(attr));
547         }
548         if (get<0>(attr) == "change") {
549             infoPeer.ifi_change = stoul(get<1>(attr));
550         }
551         if (get<0>(attr) == "type") {
552             infoPeer.ifi_type = stoul(get<1>(attr));
553         }
554     }
555     nlm.put(infoPeer);
556     for (const auto& attr : attrs) {
557         if (get<0>(attr) == "mtu") {
558             nlm.put<uint32_t>(IFLA_MTU, stoul(get<1>(attr)));
559         }
560         if (get<0>(attr) == "link") {
561             nlm.put<uint32_t>(IFLA_LINK, stoul(get<1>(attr)));
562         }
563     }
564
565     NetlinkResponse response = send(nlm, nsPid);
566     if (!response.hasMessage()) {
567         throw VasumException("Can't set interface information");
568     }
569
570     //TODO: Multiple addresses should be set at once (add support NLM_F_MULTI to NetlinkMessage).
571     vector<string> ipv4;
572     vector<string> ipv6;
573     for (const auto& attr : attrs) {
574         if (get<0>(attr) == "ipv4") {
575             ipv4.push_back(get<1>(attr));
576         }
577         if (get<0>(attr) == "ipv6") {
578             ipv6.push_back(get<1>(attr));
579         }
580     }
581
582     auto setIp = [nsPid](const vector<string>& ips, uint32_t index, int family) -> void {
583         using namespace boost::algorithm;
584         for (const auto& ip : ips) {
585             Attrs attrs;
586             vector<string> params;
587             for (const auto& addrAttr : split(params, ip, is_any_of(","))) {
588                 size_t pos = addrAttr.find(":");
589                 if (pos == string::npos || pos == addrAttr.length()) {
590                     LOGE("Wrong input data format: ill formed address attribute: " << addrAttr);
591                     VasumException("Wrong input data format: ill formed address attribute");
592                 }
593                 attrs.push_back(make_tuple(addrAttr.substr(0, pos), addrAttr.substr(pos + 1)));
594             }
595             setIpAddresses(nsPid, index, attrs, family);
596         }
597     };
598
599     setIp(ipv4, infoPeer.ifi_index, AF_INET);
600     setIp(ipv6, infoPeer.ifi_index, AF_INET6);
601 }
602
603 void deleteIpAddress(const pid_t nsPid,
604                      const std::string& netdev,
605                      const std::string& ip)
606 {
607     uint32_t index = getInterfaceIndex(netdev, nsPid);
608     size_t slash = ip.find('/');
609     if (slash == string::npos) {
610         LOGE("Wrong address format: it is not CIDR notation: can't find '/'");
611         throw VasumException("Wrong address format");
612     }
613     int prefixlen = 0;
614     try {
615         prefixlen = stoi(ip.substr(slash + 1));
616     } catch (const std::exception& ex) {
617         LOGE("Wrong address format: invalid prefixlen");
618         throw VasumException("Wrong address format: invalid prefixlen");
619     }
620     deleteIpAddress(nsPid, index, ip.substr(0, slash), prefixlen, getIpFamily(ip));
621 }
622
623
624 } //namespace netdev
625 } //namespace vasum
626