2
* nwfilter_learnipaddr.c: support for learning IP address used by a VM
5
* Copyright (C) 2010 IBM Corp.
6
* Copyright (C) 2010 Stefan Berger
8
* This library is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU Lesser General Public
10
* License as published by the Free Software Foundation; either
11
* version 2.1 of the License, or (at your option) any later version.
13
* This library is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
* Lesser General Public License for more details.
18
* You should have received a copy of the GNU Lesser General Public
19
* License along with this library; if not, write to the Free Software
20
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
* Author: Stefan Berger <stefanb@us.ibm.com>
32
#include <sys/ioctl.h>
34
#include <arpa/inet.h>
35
#include <net/ethernet.h>
36
#include <netinet/ip.h>
37
#include <netinet/udp.h>
38
#include <net/if_arp.h>
45
#include "datatypes.h"
46
#include "virterror_internal.h"
48
#include "conf/nwfilter_params.h"
49
#include "conf/domain_conf.h"
50
#include "nwfilter_gentech_driver.h"
51
#include "nwfilter_ebiptables_driver.h"
52
#include "nwfilter_learnipaddr.h"
54
#define VIR_FROM_THIS VIR_FROM_NWFILTER
57
/* structure of an ARP request/reply message */
60
uint8_t ar_sha[ETH_ALEN];
62
uint8_t ar_tha[ETH_ALEN];
70
uint8_t value[0]; /* length varies */
74
/* structure representing DHCP message */
90
struct dhcp_option options[0];
93
#define DHCP_MSGT_DHCPOFFER 2
95
struct ether_vlan_header
97
uint8_t dhost[ETH_ALEN];
98
uint8_t shost[ETH_ALEN];
105
static virMutex pendingLearnReqLock;
106
static virHashTablePtr pendingLearnReq;
108
static virMutex ipAddressMapLock;
109
static virNWFilterHashTablePtr ipAddressMap;
113
virNWFilterIPAddrLearnReqFree(virNWFilterIPAddrLearnReqPtr req) {
117
VIR_FREE(req->filtername);
118
virNWFilterHashTableFree(req->filterparams);
127
virNWFilterRegisterLearnReq(virNWFilterIPAddrLearnReqPtr req) {
129
virMutexLock(&pendingLearnReqLock);
131
if (!virHashLookup(pendingLearnReq, req->ifname))
132
res = virHashAddEntry(pendingLearnReq, req->ifname, req);
134
virMutexUnlock(&pendingLearnReqLock);
142
virNWFilterIPAddrLearnReqPtr
143
virNWFilterLookupLearnReq(const char *ifname) {
146
virMutexLock(&pendingLearnReqLock);
148
res = virHashLookup(pendingLearnReq, ifname);
150
virMutexUnlock(&pendingLearnReqLock);
157
freeLearnReqEntry(void *payload, const char *name ATTRIBUTE_UNUSED) {
158
virNWFilterIPAddrLearnReqFree(payload);
164
static virNWFilterIPAddrLearnReqPtr
165
virNWFilterDeregisterLearnReq(const char *ifname) {
166
virNWFilterIPAddrLearnReqPtr res;
168
virMutexLock(&pendingLearnReqLock);
170
res = virHashLookup(pendingLearnReq, ifname);
173
virHashRemoveEntry(pendingLearnReq, ifname, NULL);
175
virMutexUnlock(&pendingLearnReqLock);
183
virNWFilterAddIpAddrForIfname(const char *ifname, char *addr) {
186
virMutexLock(&ipAddressMapLock);
188
ret = virNWFilterHashTablePut(ipAddressMap, ifname, addr, 1);
190
virMutexUnlock(&ipAddressMapLock);
198
virNWFilterDelIpAddrForIfname(const char *ifname) {
200
virMutexLock(&ipAddressMapLock);
202
if (virHashLookup(ipAddressMap->hashTable, ifname))
203
virNWFilterHashTableRemoveEntry(ipAddressMap, ifname);
205
virMutexUnlock(&ipAddressMapLock);
210
virNWFilterGetIpAddrForIfname(const char *ifname) {
213
virMutexLock(&ipAddressMapLock);
215
res = virHashLookup(ipAddressMap->hashTable, ifname);
217
virMutexUnlock(&ipAddressMapLock);
226
procDHCPOpts(struct dhcp *dhcp, int dhcp_opts_len,
227
uint32_t *vmaddr, uint32_t *bcastaddr,
228
enum howDetect *howDetected) {
229
struct dhcp_option *dhcpopt = &dhcp->options[0];
231
while (dhcp_opts_len >= 2) {
233
switch (dhcpopt->code) {
235
case 28: /* Broadcast address */
236
if (dhcp_opts_len >= 6) {
237
uint32_t *tmp = (uint32_t *)&dhcpopt->value;
238
(*bcastaddr) = ntohl(*tmp);
242
case 53: /* Message type */
243
if (dhcp_opts_len >= 3) {
244
uint8_t *val = (uint8_t *)&dhcpopt->value;
246
case DHCP_MSGT_DHCPOFFER:
247
*vmaddr = dhcp->yiaddr;
248
*howDetected = DETECT_DHCP;
253
dhcp_opts_len -= (2 + dhcpopt->len);
254
dhcpopt = (struct dhcp_option*)((char *)dhcpopt + 2 + dhcpopt->len);
260
* learnIPAddressThread
261
* arg: pointer to virNWFilterIPAddrLearnReq structure
263
* Learn the IP address being used on an interface. Use ARP Request and
264
* Reply messages, DHCP offers and the first IP packet being sent from
265
* the VM to detect the IP address it is using. Detects only one IP address
266
* per interface (IP aliasing not supported). The method on how the
267
* IP address is detected can be chosen through flags. DETECT_DHCP will
268
* require that the IP address is detected from a DHCP OFFER, DETECT_STATIC
269
* will require that the IP address was taken from an ARP packet or an IPv4
270
* packet. Both flags can be set at the same time.
273
learnIPAddressThread(void *arg)
275
char errbuf[PCAP_ERRBUF_SIZE] = {0};
277
struct bpf_program fp;
278
struct pcap_pkthdr header;
279
const u_char *packet;
280
struct ether_header *ether_hdr;
281
struct ether_vlan_header *vlan_hdr;
282
virNWFilterIPAddrLearnReqPtr req = arg;
283
uint32_t vmaddr = 0, bcastaddr = 0;
284
unsigned int ethHdrSize;
285
char *listen_if = (strlen(req->linkdev) != 0) ? req->linkdev
287
int to_ms = (strlen(req->linkdev) != 0) ? 1000
290
char macaddr[VIR_MAC_STRING_BUFLEN];
291
virBuffer buf = VIR_BUFFER_INITIALIZER;
294
enum howDetect howDetected = 0;
298
handle = pcap_open_live(listen_if, BUFSIZ, 0, to_ms, errbuf);
300
if (handle == NULL) {
301
VIR_DEBUG("Couldn't open device %s: %s\n", listen_if, errbuf);
302
req->status = ENODEV;
306
virFormatMacAddr(req->macaddr, macaddr);
308
switch (req->howDetect) {
310
virBufferVSprintf(&buf, " ether dst %s"
311
" and src port 67 and dst port 68",
315
virBufferVSprintf(&buf, "ether host %s", macaddr);
318
if (virBufferError(&buf)) {
319
req->status = ENOMEM;
323
filter = virBufferContentAndReset(&buf);
325
if (pcap_compile(handle, &fp, filter, 1, 0) != 0 ||
326
pcap_setfilter(handle, &fp) != 0) {
327
VIR_DEBUG("Couldn't compile or set filter '%s'.\n", filter);
328
req->status = EINVAL;
332
while (req->status == 0 && vmaddr == 0) {
333
packet = pcap_next(handle, &header);
337
/* assuming IF disappeared */
338
req->status = ENODEV;
341
/* listening on linkdev, check whether VM's dev is still there */
342
if (checkIf(req->ifname, req->macaddr)) {
343
req->status = ENODEV;
349
if (header.len >= sizeof(struct ether_header)) {
350
ether_hdr = (struct ether_header*)packet;
352
switch (ntohs(ether_hdr->ether_type)) {
355
ethHdrSize = sizeof(struct ether_header);
356
etherType = ntohs(ether_hdr->ether_type);
360
ethHdrSize = sizeof(struct ether_vlan_header);
361
vlan_hdr = (struct ether_vlan_header *)packet;
362
if (ntohs(vlan_hdr->ether_type) != ETHERTYPE_IP ||
363
header.len < ethHdrSize)
365
etherType = ntohs(vlan_hdr->ether_type);
372
if (memcmp(ether_hdr->ether_shost,
374
VIR_MAC_BUFLEN) == 0) {
375
// packets from the VM
377
if (etherType == ETHERTYPE_IP &&
378
(header.len >= ethHdrSize +
379
sizeof(struct iphdr))) {
380
struct iphdr *iphdr = (struct iphdr*)(packet +
382
vmaddr = iphdr->saddr;
383
// skip eth. bcast and mcast addresses,
384
// and zero address in DHCP Requests
385
if ((ntohl(vmaddr) & 0xc0000000) || vmaddr == 0) {
390
howDetected = DETECT_STATIC;
391
} else if (etherType == ETHERTYPE_ARP &&
392
(header.len >= ethHdrSize +
393
sizeof(struct f_arphdr))) {
394
struct f_arphdr *arphdr = (struct f_arphdr*)(packet +
396
switch (ntohs(arphdr->arphdr.ar_op)) {
398
vmaddr = arphdr->ar_sip;
399
howDetected = DETECT_STATIC;
402
vmaddr = arphdr->ar_tip;
403
howDetected = DETECT_STATIC;
407
} else if (memcmp(ether_hdr->ether_dhost,
409
VIR_MAC_BUFLEN) == 0) {
411
if (etherType == ETHERTYPE_IP &&
412
(header.len >= ethHdrSize +
413
sizeof(struct iphdr))) {
414
struct iphdr *iphdr = (struct iphdr*)(packet +
416
if ((iphdr->protocol == IPPROTO_UDP) &&
417
(header.len >= ethHdrSize +
419
sizeof(struct udphdr))) {
420
struct udphdr *udphdr= (struct udphdr *)
421
((char *)iphdr + iphdr->ihl * 4);
422
if (ntohs(udphdr->source) == 67 &&
423
ntohs(udphdr->dest) == 68 &&
424
header.len >= ethHdrSize +
426
sizeof(struct udphdr) +
427
sizeof(struct dhcp)) {
428
struct dhcp *dhcp = (struct dhcp *)
429
((char *)udphdr + sizeof(udphdr));
430
if (dhcp->op == 2 /* BOOTREPLY */ &&
431
!memcmp(&dhcp->chaddr[0],
434
dhcp_opts_len = header.len -
435
(ethHdrSize + iphdr->ihl * 4 +
436
sizeof(struct udphdr) +
437
sizeof(struct dhcp));
438
procDHCPOpts(dhcp, dhcp_opts_len,
448
if (vmaddr && (req->howDetect & howDetected) == 0) {
460
ebtablesRemoveBasicRules(req->ifname);
462
if (req->status == 0) {
464
char inetaddr[INET_ADDRSTRLEN];
465
inet_ntop(AF_INET, &vmaddr, inetaddr, sizeof(inetaddr));
467
virNWFilterAddIpAddrForIfname(req->ifname, strdup(inetaddr));
469
ret = virNWFilterInstantiateFilterLate(NULL,
477
VIR_DEBUG("Result from applying firewall rules on "
478
"%s with IP addr %s : %d\n", req->ifname, inetaddr, ret);
481
memset(&req->thread, 0x0, sizeof(req->thread));
483
VIR_DEBUG("pcap thread terminating for interface %s\n",req->ifname);
485
virNWFilterDeregisterLearnReq(req->ifname);
487
virNWFilterIPAddrLearnReqFree(req);
494
* virNWFilterLearnIPAddress
495
* @conn: pointer to virConnect object
496
* @ifname: the name of the interface
497
* @linkdev : the name of the link device; currently only used in case of a
499
* @nettype : the type of interface
500
* @macaddr : the MAC address of the interface
501
* @filtername : the name of the top-level filter to apply to the interface
502
* once its IP address has been detected
503
* @driver : the network filter driver
504
* @howDetect : the method on how the thread is supposed to detect the
505
* IP address; must choose any of the available flags
507
* Instruct to learn the IP address being used on a given interface (ifname).
508
* Unless there already is a thread attempting to learn the IP address
509
* being used on the interface, a thread is started that will listen on
510
* the traffic being sent on the interface (or link device) with the
511
* MAC address that is provided. Will then launch the application of the
512
* firewall rules on the interface.
515
virNWFilterLearnIPAddress(const char *ifname,
517
enum virDomainNetType nettype,
518
const unsigned char *macaddr,
519
const char *filtername,
520
virNWFilterHashTablePtr filterparams,
521
virNWFilterDriverStatePtr driver,
522
enum howDetect howDetect) {
524
virNWFilterIPAddrLearnReqPtr req = NULL;
525
virNWFilterHashTablePtr ht = NULL;
530
if (VIR_ALLOC(req) < 0) {
535
ht = virNWFilterHashTableCreate(0);
541
if (virNWFilterHashTablePutAll(filterparams, ht))
544
req->filtername = strdup(filtername);
545
if (req->filtername == NULL) {
550
if (virStrcpyStatic(req->ifname, ifname) == NULL) {
551
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
552
_("Destination buffer for ifname ('%s') "
553
"not large enough"), ifname);
558
if (virStrcpyStatic(req->linkdev, linkdev) == NULL) {
559
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
560
_("Destination buffer for linkdev ('%s') "
561
"not large enough"), linkdev);
565
req->nettype = nettype;
566
memcpy(req->macaddr, macaddr, sizeof(req->macaddr));
567
req->driver = driver;
568
req->filterparams = ht;
570
req->howDetect = howDetect;
572
rc = virNWFilterRegisterLearnReq(req);
579
if (ebtablesApplyDHCPOnlyRules(ifname,
585
if (ebtablesApplyBasicRules(ifname,
591
if (pthread_create(&req->thread,
593
learnIPAddressThread,
595
goto err_remove_rules;
600
ebtablesRemoveBasicRules(ifname);
602
virNWFilterHashTableFree(ht);
604
virNWFilterIPAddrLearnReqFree(req);
612
virNWFilterLearnIPAddress(const char *ifname ATTRIBUTE_UNUSED,
613
const char *linkdev ATTRIBUTE_UNUSED,
614
enum virDomainNetType nettype ATTRIBUTE_UNUSED,
615
const unsigned char *macaddr ATTRIBUTE_UNUSED,
616
const char *filtername ATTRIBUTE_UNUSED,
617
virNWFilterHashTablePtr filterparams ATTRIBUTE_UNUSED,
618
virNWFilterDriverStatePtr driver ATTRIBUTE_UNUSED,
619
enum howDetect howDetect ATTRIBUTE_UNUSED) {
620
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "%s",
621
_("IP parameter must be given since libvirt "
622
"was not compiled with IP address learning "
626
#endif /* HAVE_LIBPCAP */
630
* virNWFilterLearnInit
631
* Initialization of this layer
634
virNWFilterLearnInit(void) {
635
pendingLearnReq = virHashCreate(0);
636
if (!pendingLearnReq) {
641
if (virMutexInit(&pendingLearnReqLock)) {
642
virNWFilterLearnShutdown();
646
ipAddressMap = virNWFilterHashTableCreate(0);
649
virNWFilterLearnShutdown();
653
if (virMutexInit(&ipAddressMapLock)) {
654
virNWFilterLearnShutdown();
663
* virNWFilterLearnShutdown
664
* Shutdown of this layer
667
virNWFilterLearnShutdown(void) {
668
virHashFree(pendingLearnReq, freeLearnReqEntry);
669
pendingLearnReq = NULL;
671
virNWFilterHashTableFree(ipAddressMap);