[libvirt] [RFC] libvirt-TCK scripts to verify spoofing prevention

The following patch mainly adds a set of test case to verify that several spoofing attacks are prevented by the nwfilter subsystem. In order to have a well defined test machine, the patch also includes test scripts to network install a virtual disk from scratch, to boot the virtual test machine prior to running the actual test scripts and to shut it down afterwards. While I have tried to remove as much dependency on my local setup as possible there is still some left, so I am currently more interested in feedback about the general approach, not necessarily actual inclusion into the libvirt-TCK git. For example, I am currently trying to find a suitable location for the kickstart file, and also a suitable place for the common_functions.pl. Comments are appreciated ... thanks, Gerhard Index: libvirt-tck/scripts/network/README =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/README @@ -0,0 +1,14 @@ + +Test cases: + +000-install-image.t creates and install a 2GB fedora virtual disk via kickstart file from the network +001-boot-image.t defines and boots a VM which uses the fedora virtual disk +100-ping-still-working.t verifies the VM is pingable +210-no-mac-spoofing.t verifies mac spoofing is prevented +220-no-ip-spoofing.t verifies ip spoofing is prevented +230-no-mac-broadcast.t verifies mac broadcasting is prevented +240-no-arp-spoofing.t verifies arp spoofing is prevented +999-shutdown-image.t shuts the VM down + + + Index: libvirt-tck/scripts/network/000-install-image.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/000-install-image.t @@ -0,0 +1,181 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/000-install-image.t - install network test image + +=head1 DESCRIPTION + +The test case creates and install a 2GB fedora virtual +disk via kickstart file from the network. + +=cut + +use strict; +use warnings; + +use Test::More tests => 1; + +use Sys::Virt::TCK; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { $tck->cleanup if $tck; } + +# variables which may need to be adapted +my $domain_name ="f12nwtest"; +my $disk_name = "/var/lib/libvirt/images/${domain_name}.img"; +my $disk_size = "2147483648"; + +my $kickstart_file ="http://192.168.122.1/ks.cfg"; +my $cmdline = "ip=dhcp gateway=192.168.122.1 ks=${kickstart_file}"; + +# see if the domain already exits +my $already_defined = 0; +diag "searching if ${domain_name} is already defined"; +my $nnames = $conn->num_of_defined_domains(); +my @names = $conn->list_defined_domain_names($nnames); +foreach (@names){ + if (/${domain_name}/) { + print "$_ already exists, no need to redefine\n"; + $already_defined = 1; + } +} +diag $already_defined; + +# check for installation disk and build it if not exists +my $already_installed = 0; +my $pool = $conn->get_storage_pool_by_name("default"); +my $nnames = $pool->num_of_storage_volumes(); +my @volNames = $pool->list_storage_vol_names($nnames); +foreach (@volNames){ + if (/${domain_name}/) { + print "$_ already exists, no need to install\n"; + $already_installed = 1; + } +} + +my $volumexml = "<volume>". +" <name>${domain_name}.img</name>". +" <key>${disk_name}</key>". +" <source>". +" </source>". +" <capacity>${disk_size}</capacity>". +" <allocation>4096</allocation>". +" <target>". +" <path>${disk_name}</path>". +" <format type='raw'/>". +" <permissions>". +" <mode>0644</mode>". +" <owner>0</owner>". +" <group>0</group>". +" </permissions>". +" </target>". +"</volume>"; + + +# prepare image +if ($already_installed == 0) { + diag "Creating ${disk_name}"; + diag $volumexml; + my $vol = $pool->create_volume($volumexml) +# system("qemu-img create ${disk_name} ${disk_size}"); +} + +my $topxml = " <name>${domain_name}</name>". +" <memory>524288</memory>". +" <currentMemory>524288</currentMemory>". +" <vcpu>1</vcpu>"; + +my $osxml = " <os>". +" <type arch='x86_64' machine='fedora-13'>hvm</type>". +" <kernel>/var/cache/libvirt-tck/os-i686-hvm/vmlinuz</kernel>". +" <initrd>/var/cache/libvirt-tck/os-i686-hvm/initrd</initrd>". +" <cmdline>${cmdline}</cmdline>". +" <boot dev='hd'/>". +" </os>"; + +my $bottomxml = " <features>". +" <acpi/>". +" <apic/>". +" </features>". +" <clock offset='utc'/>". +" <on_poweroff>destroy</on_poweroff>". +" <on_reboot>restart</on_reboot>". +" <on_crash>restart</on_crash>". +" <devices>". +" <emulator>/usr/bin/qemu-kvm</emulator>". +" <disk type='file' device='disk'>". +" <driver name='qemu' type='raw'/>". +" <source file='${disk_name}'/>". +" <target dev='hda' bus='ide'/>". +" </disk>". +" <controller type='ide' index='0'>". +" </controller>". +" <interface type='network'>". +" <source network='default'/>". +" <target dev='vnet0'/>". +" <model type='virtio'/>". +" </interface>". +" <serial type='pty'>". +" <target port='0'/>". +" </serial>". +" <console type='pty'>". +" <target port='0'/>". +" </console>". +" <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='de'/>". +" <video>". +" <model type='cirrus' vram='9216' heads='1'/>". +" </video>". +" </devices>"; + +my $xml = "<domain type='kvm'>" . +$topxml. +$osxml. +$bottomxml. +"</domain>"; + +diag $xml; +diag "Defining an inactive domain config"; +my $dom; + +# no need to start if already installed +if (($already_installed == 0) && ($already_defined == 0)) { + ok_domain(sub { $dom = $conn->define_domain($xml) }, "defined persistent domain config"); + $xml = $dom->get_xml_description; + diag $xml; + diag "Starting inactive domain config"; + $dom->create; + + # wait for completion of installation + diag "wait for installation to finish .. "; + while($dom->is_active()) { + sleep(10); + diag ".. to view progress connect to virtual machine ${domain_name} .. "; + } + sleep(10); + diag " .. done"; + # cleanup + $dom->undefine; +} else { + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the existing domain object"; +} + + + + + Index: libvirt-tck/scripts/network/001-boot-image.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/001-boot-image.t @@ -0,0 +1,133 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/001-boot-image.t - boot installed test image + +=head1 DESCRIPTION + +The test case defines and boots a VM which uses the +fedora virtual disk create ny 000-install-image + +=cut + +use strict; +use warnings; + +use Test::More tests => 2; + +use Sys::Virt::TCK; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { $tck->cleanup if $tck; } + +my $already_defined = 0; +my $domain_name ="f12nwtest"; + +# see if the domain already exits +diag "searching if ${domain_name} is already defined"; +my $nnames = $conn->num_of_defined_domains(); +my @names = $conn->list_defined_domain_names($nnames); +foreach (@names){ + if (/${domain_name}/) { + print "$_ already exists, no need to redefine\n"; + $already_defined = 1; + } +} +diag $already_defined; + +my $dom; +my $xml; + +if ($already_defined == 1) { + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the existing domain object"; +} else { + my $topxml = " <name>${domain_name}</name>". + " <memory>524288</memory>". + " <currentMemory>524288</currentMemory>". + " <vcpu>1</vcpu>"; + + my $osxml = " <os>". + " <type arch='x86_64' machine='fedora-13'>hvm</type>". + " <boot dev='hd'/>". + " </os>"; + + my $bottomxml = " <features>". + " <acpi/>". + " <apic/>". + " </features>". + " <clock offset='utc'/>". + " <on_poweroff>destroy</on_poweroff>". + " <on_reboot>restart</on_reboot>". + " <on_crash>restart</on_crash>". + " <devices>". + " <emulator>/usr/bin/qemu-kvm</emulator>". + " <disk type='file' device='disk'>". + " <driver name='qemu' type='raw'/>". + " <source file='/var/lib/libvirt/images/${domain_name}.img'/>". + " <target dev='hda' bus='ide'/>". + " </disk>". + " <controller type='ide' index='0'>". + " </controller>". + " <interface type='network'>". + " <source network='default'/>". + " <filterref filter='no-spoofing'/>". + " <target dev='vnet0'/>". + " <model type='virtio'/>". + " </interface>". + " <serial type='pty'>". + " <target port='0'/>". + " </serial>". + " <console type='pty'>". + " <target port='0'/>". + " </console>". + " <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='de'/>". + " <video>". + " <model type='cirrus' vram='9216' heads='1'/>". + " </video>". + " </devices>"; + + $xml = "<domain type='kvm'>" . + $topxml. + $osxml. + $bottomxml. + "</domain>"; + + diag $xml; + diag "Defining an inactive domain config"; + ok_domain(sub { $dom = $conn->define_domain($xml) }, "defined persistent domain config"); +} + +# already existing or newly defined, start it up +$dom->create; +my $uuid = $dom->get_uuid_string(); +diag $uuid; +$xml = $dom->get_xml_description(); +diag $xml; +ok($dom->get_id() > 0, "running domain has an ID > 0"); + +my $mac = get_macaddress($xml); +diag $mac; +# wait for guest to boot and request dhcp +sleep(20); +my $ip = get_ip_from_leases($mac); +diag "ip is $ip"; + Index: libvirt-tck/scripts/network/100-ping-still-working.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/100-ping-still-working.t @@ -0,0 +1,71 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/100-ping-still-working.t - verify machines can be pinged from host + +=head1 DESCRIPTION + +The test case validates that it is possible to ping a guest machine from +the host. + +=cut + +use strict; +use warnings; + +use Test::More tests => 4; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# create first domain and start it +diag "Trying domain lookup by name"; +my $dom1; +my $domain_name ="f12nwtest"; + +ok_domain { $dom1 = $conn->get_domain_by_name($domain_name) } "the running domain object"; +my $xml = $dom1->get_xml_description; +diag $xml; +ok($dom1->get_id() > 0, "running domain has an ID > 0"); +my $mac1 = get_macaddress($xml); +diag $mac1; +my $guestip1 = get_ip_from_leases($mac1); +diag "ip is $guestip1"; + +# check ebtables entry +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`; +diag $ebtable1; +# fixme to include mac adress +ok($ebtable1 =~ "vnet0", "check ebtables entry"); + +# ping guest1 +my $ping1 = `ping -c 10 $guestip1`; +diag $ping1; +ok($ping1 =~ "10 received", "ping $guestip1 test"); + +exit 0; Index: libvirt-tck/scripts/network/210-no-mac-spoofing.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/210-no-mac-spoofing.t @@ -0,0 +1,113 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/210-no-mac-spoofing.t - verify MAC spoofing is prevented + +=head1 DESCRIPTION + +The test case validates that MAC spoofing is prevented + +=cut + +use strict; +use warnings; + +use Test::More tests => 5; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# create first domain and start it +diag "Trying domain lookup by name"; +my $domain_name ="f12nwtest"; +my $dom1; +ok_domain { $dom1 = $conn->get_domain_by_name($domain_name) } "the running domain object"; +ok($dom1->get_id() > 0, "running domain has an ID > 0"); +my $xml = $dom1->get_xml_description; +diag $xml; + +# check ebtables entry +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`; +diag $ebtable1; +# fixme to include mac adress +ok($ebtable1 =~ "vnet0", "check ebtables entry"); + +# wait for guest to boot +diag "waiting for guests to boot"; +#system("sleep 20"); +diag "done"; + +# ping guest1 first nic +my $mac1 = get_macaddress($xml); +diag "$mac1"; +my $guestip1 = get_ip_from_leases($mac1); +diag "ip is $guestip1"; +my $gateway = "192.168.122.1"; +my $macfalse = "52:54:00:f9:21:22"; +my $ping1 = `ping -c 10 $guestip1`; +diag $ping1; +ok($ping1 =~ "10 received", "ping $guestip1 test"); + +# log into guest +my $ssh = Net::SSH::Perl->new($guestip1); +$ssh->login("root", "foobar"); + +# now bring eth0 down, change MAC and bring it up again +diag "fiddling with mac"; +my $cmdfile = "echo '" . + "/sbin/ifconfig eth0\n". + "/sbin/ifconfig eth0 down\n". + "/sbin/ifconfig eth0 hw ether ${macfalse}\n". + "/sbin/ifconfig eth0 up\n". + "/sbin/ifconfig eth0\n". + "ping -c 10 ${gateway}\n". + "/sbin/ifconfig eth0 down\n". + "/sbin/ifconfig eth0 hw ether ${mac1}\n". + "/sbin/ifconfig eth0 up\n". + "/sbin/ifconfig eth0\n". + "' > /test.sh"; +diag $cmdfile; +my ($stdout, $stderr, $exit) = $ssh->cmd($cmdfile); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("chmod +x /test.sh"); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("/test.sh > /test.log"); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("cat /test.log"); +diag $stdout; +diag $stderr; +diag $exit; +ok($stdout =~ "100% packet loss", "packet loss expected"); + +exit 0; Index: libvirt-tck/scripts/network/230-no-mac-broadcast.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/230-no-mac-broadcast.t @@ -0,0 +1,102 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/230-no-mac-broadcast.t - verify MAC broadcasts are prevented + +=head1 DESCRIPTION + +The test case validates that MAC broadcasts are prevented + +=cut + +use strict; +use warnings; + +use Test::More tests => 4; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# create first domain and start it +diag "Trying domain lookup by name"; +my $dom1; +ok_domain { $dom1 = $conn->get_domain_by_name("f12nwtest") } "the running domain object"; + +ok($dom1->get_id() > 0, "running domain has an ID > 0"); +my $xml = $dom1->get_xml_description; +diag $xml; +my $mac1 = get_macaddress($xml); +diag "$mac1"; +my $guestip1 = get_ip_from_leases($mac1); +diag "ip is $guestip1"; + +# check ebtables entry +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`; +diag $ebtable1; +# fixme to include mac adress +ok($ebtable1 =~ "vnet0", "check ebtables entry"); + +# prepare tcpdump +diag "prepare tcpdump"; +system("/usr/sbin/tcpdump -v -i virbr0 -n host 255.255.255.255 2> /tmp/tcpdump.log &"); + +# log into guest +my $ssh = Net::SSH::Perl->new($guestip1); +$ssh->login("root", "foobar"); + +# now generate a mac broadcast paket +diag "generate mac broadcast"; +my $cmdfile = "echo '" . + "/bin/ping -c 1 192.168.122.255 -b\n". + "' > /test.sh"; +diag $cmdfile; +my ($stdout, $stderr, $exit) = $ssh->cmd($cmdfile); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("chmod +x /test.sh"); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("/test.sh > /test.log"); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("cat /test.log"); +diag $stdout; +diag $stderr; +diag $exit; + +# now stop tcpdump and verify result +diag "stopping tcpdump"; +system("kill -15 `/sbin/pidof tcpdump`"); +my $tcpdumplog = `cat /tmp/tcpdump.log`; +diag($tcpdumplog); +ok($tcpdumplog =~ "0 packets captured", "tcpdump expected to capture no packets"); + +exit 0; Index: libvirt-tck/scripts/network/240-no-arp-spoofing.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/240-no-arp-spoofing.t @@ -0,0 +1,111 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/240-no-arp-spoofing.t - verify ARP spoofing is prevented + +=head1 DESCRIPTION + +The test case validates that ARP spoofing is prevented + +=cut + +use strict; +use warnings; + +use Test::More tests => 4; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $spoofid = "192.168.122.183"; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# looking up domain +diag "Trying domain lookup by name"; +my $dom1; +my $domain_name ="f12nwtest"; +ok_domain { $dom1 = $conn->get_domain_by_name($domain_name) } "the running domain object"; +ok($dom1->get_id() > 0, "running domain has an ID > 0"); +my $xml = $dom1->get_xml_description; +diag $xml; +my $mac1 = get_macaddress($xml); +diag "$mac1"; +my $guestip1 = get_ip_from_leases($mac1); +diag "ip is $guestip1"; + +# check ebtables entry +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`; +diag $ebtable1; +# check if mac address is listed +ok($ebtable1 =~ "$guestip1", "check ebtables entry"); + +# prepare tcpdump +diag "prepare tcpdump"; +system("/usr/sbin/tcpdump -v -i virbr0 not ip > /tmp/tcpdump.log &"); + +# log into guest +my $ssh = Net::SSH::Perl->new($guestip1); +$ssh->login("root", "foobar"); + +# now generate a arp spoofing packets +diag "generate arpspoof"; +my $cmdfile = "echo '" . + "/usr/bin/yum -y install dsniff\n". + "/usr/sbin/arpspoof ${spoofid} &\n". + "/bin/sleep 10\n". + "kill -15 `/sbin/pidof arpspoof`\n". + "' > /test.sh"; +diag "content of cmdfile:"; +diag $cmdfile; +diag "creating cmdfile"; +my ($stdout, $stderr, $exit) = $ssh->cmd($cmdfile); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("chmod +x /test.sh"); +diag $stdout; +diag $stderr; +diag $exit; +diag "excuting cmdfile"; +($stdout, $stderr, $exit) = $ssh->cmd("/test.sh > /test.log"); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("echo test.log\ncat /test.log"); +diag $stdout; +diag $stderr; +diag $exit; + +# now stop tcpdump and verify result +diag "stopping tcpdump"; +system("kill -15 `/sbin/pidof tcpdump`"); +diag "tcpdump.log:"; +my $tcpdumplog = `cat /tmp/tcpdump.log`; +diag($tcpdumplog); +ok($tcpdumplog !~ "${spoofid} is-at", "tcpdump expected to capture no arp reply packets"); + +exit 0; Index: libvirt-tck/scripts/network/220-no-ip-spoofing.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/220-no-ip-spoofing.t @@ -0,0 +1,101 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/220-no-ip-spoofing.t - verify IP spoofing is prevented + +=head1 DESCRIPTION + +The test case validates that IP spoofing is prevented + +=cut + +use strict; +use warnings; + +use Test::More tests => 4; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# looking up domain +diag "Trying domain lookup by name"; +my $dom1; +my $domain_name ="f12nwtest"; + +ok_domain { $dom1 = $conn->get_domain_by_name($domain_name) } "the running domain object"; +ok($dom1->get_id() > 0, "running domain has an ID > 0"); +my $xml = $dom1->get_xml_description; +diag $xml; +my $mac1 = get_macaddress($xml); +diag "$mac1"; +my $guestip1 = get_ip_from_leases($mac1); +diag "ip is $guestip1"; + +# check ebtables entry +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`; +diag $ebtable1; +# check if IP address is listed +ok($ebtable1 =~ "$guestip1", "check ebtables entry"); + +# log into guest +my $ssh = Net::SSH::Perl->new($guestip1); +$ssh->login("root", "foobar"); + +# now bring eth0 down, change IP and bring it up again +diag "preparing ip spoof"; +my $cmdfile = "echo '" . + "/bin/sleep 1\n". + "/sbin/ifconfig eth0\n". + "/sbin/ifconfig eth0 down\n". + "/sbin/ifconfig eth0 192.168.122.183 netmask 255.255.255.0 up\n". + "/sbin/ifconfig eth0\n". + "/bin/sleep 1\n". + "/bin/ping -c 1 192.168.122.1\n". + "/sbin/ifconfig eth0 down\n". + "/sbin/ifconfig eth0 ${guestip1} netmask 255.255.255.0 up\n". + "/sbin/ifconfig eth0 \n". + "/bin/sleep 1\n". + "' > /test.sh"; +diag $cmdfile; +my ($stdout, $stderr, $exit) = $ssh->cmd($cmdfile); +diag $stdout; +diag $stderr; +diag $exit; +($stdout, $stderr, $exit) = $ssh->cmd("chmod +x /test.sh"); +diag $stdout; +diag $stderr; +diag $exit; +diag "running ip spoof"; +($stdout, $stderr, $exit) = $ssh->cmd("/test.sh"); +diag $stdout; +diag $stderr; +diag $exit; +diag "checking result"; +ok($stdout =~ "100% packet loss", "packet loss expected"); + +exit 0; Index: libvirt-tck/scripts/network/999-shutdown-image.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/999-shutdown-image.t @@ -0,0 +1,59 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/240-no-arp-spoofing.t - verify ARP spoofing is prevented + +=head1 DESCRIPTION + +The test case validates that ARP spoofing is prevented + +=cut + +use strict; +use warnings; + +use Test::More tests => 2; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; + + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# find domain +my $domain_name = "f12nwtest"; +diag "Trying domain lookup by name"; +my $dom; +ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the running domain object"; +ok($dom->get_id() > 0, "running domain has an ID > 0"); + +# cleanup guest +diag "cleaning up"; +$dom->shutdown(); + while($dom->is_active()) { + sleep(1); + diag ".. waiting for virtual machine ${domain_name} to shutdown.. "; + } +#$dom->undefine(); + +exit 0; Index: libvirt-tck/scripts/network/common_functions.pl =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/common_functions.pl @@ -0,0 +1,35 @@ +use utf8; +#no utf8; + +sub get_macaddress { + my $xmldesc = shift; + + my $mac; + my $parser = XML::LibXML->new(); + + my $doc = $parser->parse_string($xmldesc); + + my $rootel = $doc -> getDocumentElement(); + + my @devices = $rootel->getChildrenByTagName("devices"); + foreach my $device(@devices) { + my @interfaces = $device->getChildrenByTagName("interface"); + foreach my $interface(@interfaces) { + my @targets = $interface->getChildrenByTagName("mac"); + foreach my $target(@targets) { + $mac = $target->getAttribute("address"); + } + } + } + utf8::decode($mac); + return $mac; +} + +sub get_ip_from_leases{ + my $mac = shift; + my $tmp = `grep $mac /var/lib/dnsmasq/dnsmasq.leases`; + my @fields = split(/ /, $tmp); + my $ip = $fields[2]; + return $ip; +} +1; -- Best regards, Gerhard Stenzel, ----------------------------------------------------------------------------------------------------------------------------------- IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Martin Jetter Geschäftsführung: Dirk Wittkopp Sitz der Gesellschaft: Böblingen Registergericht: Amtsgericht Stuttgart, HRB 243294

On Thu, Apr 15, 2010 at 02:35:41PM +0200, Gerhard Stenzel wrote:
The following patch mainly adds a set of test case to verify that several spoofing attacks are prevented by the nwfilter subsystem.
In order to have a well defined test machine, the patch also includes test scripts to network install a virtual disk from scratch, to boot the virtual test machine prior to running the actual test scripts and to shut it down afterwards.
While I have tried to remove as much dependency on my local setup as possible there is still some left, so I am currently more interested in feedback about the general approach, not necessarily actual inclusion into the libvirt-TCK git.
Your actual test cases look good, so I'll just put comments about the setup/teardown stuff inline.
For example, I am currently trying to find a suitable location for the kickstart file, and also a suitable place for the common_functions.pl.
The 'lib' directory contains modules which provide common functions & code for the test scripts. In this case I'd suggest creating a file lib/Sys/Virt/TCK/NetworkHelpers.pm (use Sys::Virt::TCK::NetworkHelpers)
Index: libvirt-tck/scripts/network/README =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/README @@ -0,0 +1,14 @@ + +Test cases: + +000-install-image.t creates and install a 2GB fedora virtual disk via kickstart file from the network +001-boot-image.t defines and boots a VM which uses the fedora virtual disk +100-ping-still-working.t verifies the VM is pingable +210-no-mac-spoofing.t verifies mac spoofing is prevented +220-no-ip-spoofing.t verifies ip spoofing is prevented +230-no-mac-broadcast.t verifies mac broadcasting is prevented +240-no-arp-spoofing.t verifies arp spoofing is prevented +999-shutdown-image.t shuts the VM down
One thing about the TCK test cases is that each one should be self-contained, doing all setup & teardown it requires, not reliant on any of the other tests cases or ordering of tests. So instead of having the 000-install-image.t & 0001-boot-image.t scripts that do setup, you'd want to create some library code that can be used to install + boot the guest, and just call that from each test case.
Index: libvirt-tck/scripts/network/000-install-image.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/000-install-image.t @@ -0,0 +1,181 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/000-install-image.t - install network test image + +=head1 DESCRIPTION + +The test case creates and install a 2GB fedora virtual +disk via kickstart file from the network. + +=cut + +use strict; +use warnings; + +use Test::More tests => 1; + +use Sys::Virt::TCK; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { $tck->cleanup if $tck; } + +# variables which may need to be adapted +my $domain_name ="f12nwtest"; +my $disk_name = "/var/lib/libvirt/images/${domain_name}.img"; +my $disk_size = "2147483648";
You want to avoid hardcoding any disk paths in the test cases. There's a convenient helper function if you want to create a scratch disk for a guest my $path = $tck->create_sparse_disk("nwfilter", "root.img", 2147483648);
+my $kickstart_file ="http://192.168.122.1/ks.cfg"; +my $cmdline = "ip=dhcp gateway=192.168.122.1 ks=${kickstart_file}";
In the 'conf/default.cfg' file we list the kernel+initrd images for various distros. We could likely also have an optional kickstart file listed there, so we move this configuration info out of the test cases.
+# see if the domain already exits +my $already_defined = 0; +diag "searching if ${domain_name} is already defined"; +my $nnames = $conn->num_of_defined_domains(); +my @names = $conn->list_defined_domain_names($nnames); +foreach (@names){ + if (/${domain_name}/) { + print "$_ already exists, no need to redefine\n"; + $already_defined = 1; + } +} +diag $already_defined;
All guests created by the TCK test cases should have a name prefix of 'tck' to avoid clashing with existing guests. Each test case should destroy + undefine all guests it creates. The TCK setup code will validate that there are no guests having a name starting with a 'tck' prefix & optionally destroy them. So each of your test cases should assume the guest doesn't already exist + clean it up before exiting. NB, the disk files are not deleted between runs, so you will still avoid the overhead of installation. I'm talking with the libguestfs folks to see if we can re-use their guest appliance + daemon so we can avoid the actual install + ssh stuff at some point in the future.
+# check for installation disk and build it if not exists +my $already_installed = 0; +my $pool = $conn->get_storage_pool_by_name("default"); +my $nnames = $pool->num_of_storage_volumes(); +my @volNames = $pool->list_storage_vol_names($nnames); +foreach (@volNames){ + if (/${domain_name}/) { + print "$_ already exists, no need to install\n"; + $already_installed = 1; + } +} + +my $volumexml = "<volume>". +" <name>${domain_name}.img</name>". +" <key>${disk_name}</key>". +" <source>". +" </source>". +" <capacity>${disk_size}</capacity>". +" <allocation>4096</allocation>". +" <target>". +" <path>${disk_name}</path>". +" <format type='raw'/>". +" <permissions>". +" <mode>0644</mode>". +" <owner>0</owner>". +" <group>0</group>". +" </permissions>". +" </target>". +"</volume>";
If using the create_sparse_disk helper I mentioned, you can avoid this chunk of code. Just do a check to see if $path exists or not.
+# prepare image +if ($already_installed == 0) { + diag "Creating ${disk_name}"; + diag $volumexml; + my $vol = $pool->create_volume($volumexml) +# system("qemu-img create ${disk_name} ${disk_size}"); +} + +my $topxml = " <name>${domain_name}</name>". +" <memory>524288</memory>". +" <currentMemory>524288</currentMemory>". +" <vcpu>1</vcpu>"; + +my $osxml = " <os>". +" <type arch='x86_64' machine='fedora-13'>hvm</type>". +" <kernel>/var/cache/libvirt-tck/os-i686-hvm/vmlinuz</kernel>". +" <initrd>/var/cache/libvirt-tck/os-i686-hvm/initrd</initrd>". +" <cmdline>${cmdline}</cmdline>". +" <boot dev='hd'/>". +" </os>"; + +my $bottomxml = " <features>". +" <acpi/>". +" <apic/>". +" </features>". +" <clock offset='utc'/>". +" <on_poweroff>destroy</on_poweroff>". +" <on_reboot>restart</on_reboot>". +" <on_crash>restart</on_crash>". +" <devices>". +" <emulator>/usr/bin/qemu-kvm</emulator>". +" <disk type='file' device='disk'>". +" <driver name='qemu' type='raw'/>". +" <source file='${disk_name}'/>". +" <target dev='hda' bus='ide'/>". +" </disk>". +" <controller type='ide' index='0'>". +" </controller>". +" <interface type='network'>". +" <source network='default'/>". +" <target dev='vnet0'/>". +" <model type='virtio'/>". +" </interface>". +" <serial type='pty'>". +" <target port='0'/>". +" </serial>". +" <console type='pty'>". +" <target port='0'/>". +" </console>". +" <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='de'/>". +" <video>". +" <model type='cirrus' vram='9216' heads='1'/>". +" </video>". +" </devices>"; + +my $xml = "<domain type='kvm'>" . +$topxml. +$osxml. +$bottomxml. +"</domain>";
There are a couple of helpers for building XML in a portable manner. In the Sys::Virt::TCK class there's a method 'generic_domain' which will find you a bare minimum VM matching the best kernel / arch found in the TCK config file. It returns an instance of the the class Sys::Virt::TCK::DomainBuilder which lets you then add extra XML to the guest. eg, so you could do my $guest = $tck->generic_domain(); $guest->boot_cmdline($cmdline); The 'boot_cmdline' method doesn't exist yet, but feel frree to extend the shared helper modules as needed. Its much nicer to use them than to build up XML manually.
+ +diag $xml; +diag "Defining an inactive domain config"; +my $dom; + +# no need to start if already installed +if (($already_installed == 0) && ($already_defined == 0)) { + ok_domain(sub { $dom = $conn->define_domain($xml) }, "defined persistent domain config"); + $xml = $dom->get_xml_description; + diag $xml; + diag "Starting inactive domain config"; + $dom->create; + + # wait for completion of installation + diag "wait for installation to finish .. "; + while($dom->is_active()) { + sleep(10); + diag ".. to view progress connect to virtual machine ${domain_name} .. "; + } + sleep(10); + diag " .. done"; + # cleanup + $dom->undefine; +} else { + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the existing domain object"; +} + + + + + Index: libvirt-tck/scripts/network/001-boot-image.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/001-boot-image.t @@ -0,0 +1,133 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/001-boot-image.t - boot installed test image + +=head1 DESCRIPTION + +The test case defines and boots a VM which uses the +fedora virtual disk create ny 000-install-image + +=cut + +use strict; +use warnings; + +use Test::More tests => 2; + +use Sys::Virt::TCK; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { $tck->cleanup if $tck; } + +my $already_defined = 0; +my $domain_name ="f12nwtest"; + +# see if the domain already exits +diag "searching if ${domain_name} is already defined"; +my $nnames = $conn->num_of_defined_domains(); +my @names = $conn->list_defined_domain_names($nnames); +foreach (@names){ + if (/${domain_name}/) { + print "$_ already exists, no need to redefine\n"; + $already_defined = 1; + } +} +diag $already_defined; + +my $dom; +my $xml; + +if ($already_defined == 1) { + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the existing domain object"; +} else { + my $topxml = " <name>${domain_name}</name>". + " <memory>524288</memory>". + " <currentMemory>524288</currentMemory>". + " <vcpu>1</vcpu>"; + + my $osxml = " <os>". + " <type arch='x86_64' machine='fedora-13'>hvm</type>". + " <boot dev='hd'/>". + " </os>"; + + my $bottomxml = " <features>". + " <acpi/>". + " <apic/>". + " </features>". + " <clock offset='utc'/>". + " <on_poweroff>destroy</on_poweroff>". + " <on_reboot>restart</on_reboot>". + " <on_crash>restart</on_crash>". + " <devices>". + " <emulator>/usr/bin/qemu-kvm</emulator>". + " <disk type='file' device='disk'>". + " <driver name='qemu' type='raw'/>". + " <source file='/var/lib/libvirt/images/${domain_name}.img'/>". + " <target dev='hda' bus='ide'/>". + " </disk>". + " <controller type='ide' index='0'>". + " </controller>". + " <interface type='network'>". + " <source network='default'/>". + " <filterref filter='no-spoofing'/>". + " <target dev='vnet0'/>". + " <model type='virtio'/>". + " </interface>". + " <serial type='pty'>". + " <target port='0'/>". + " </serial>". + " <console type='pty'>". + " <target port='0'/>". + " </console>". + " <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='de'/>". + " <video>". + " <model type='cirrus' vram='9216' heads='1'/>". + " </video>". + " </devices>"; + + $xml = "<domain type='kvm'>" . + $topxml. + $osxml. + $bottomxml. + "</domain>";
Again, I think you'd be better using the '$tck->generic_domain' method, and adding support to Sys::Virt::TCK::DomainBuilder for adding the '<filterref filter='no-spoofing'/>' bit of the XML.
Index: libvirt-tck/scripts/network/100-ping-still-working.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/100-ping-still-working.t @@ -0,0 +1,71 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/100-ping-still-working.t - verify machines can be pinged from host + +=head1 DESCRIPTION + +The test case validates that it is possible to ping a guest machine from +the host. + +=cut + +use strict; +use warnings; + +use Test::More tests => 4; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# create first domain and start it +diag "Trying domain lookup by name"; +my $dom1; +my $domain_name ="f12nwtest";
This is where you'd want to start the guest domain
+ +ok_domain { $dom1 = $conn->get_domain_by_name($domain_name) } "the running domain object"; +my $xml = $dom1->get_xml_description; +diag $xml; +ok($dom1->get_id() > 0, "running domain has an ID > 0"); +my $mac1 = get_macaddress($xml); +diag $mac1; +my $guestip1 = get_ip_from_leases($mac1); +diag "ip is $guestip1"; + +# check ebtables entry +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`; +diag $ebtable1; +# fixme to include mac adress +ok($ebtable1 =~ "vnet0", "check ebtables entry"); + +# ping guest1 +my $ping1 = `ping -c 10 $guestip1`; +diag $ping1; +ok($ping1 =~ "10 received", "ping $guestip1 test");
And run 'destroy' here. If you use a transient guest when initially booting ($conn->create_domain($xml)) then you'll not have to worry about the undefine step.
+ +sub get_macaddress { + my $xmldesc = shift; + + my $mac; + my $parser = XML::LibXML->new(); + + my $doc = $parser->parse_string($xmldesc); + + my $rootel = $doc -> getDocumentElement(); + + my @devices = $rootel->getChildrenByTagName("devices"); + foreach my $device(@devices) { + my @interfaces = $device->getChildrenByTagName("interface"); + foreach my $interface(@interfaces) { + my @targets = $interface->getChildrenByTagName("mac"); + foreach my $target(@targets) { + $mac = $target->getAttribute("address"); + } + } + } + utf8::decode($mac); + return $mac; +}
You can simplify this using XPath - the $tck object provides a convenient method for performing an XPath query on a $dom object. eg that whole method can just be my $resultt = xpath($dom, "/domain/devices/interface/mac/\@address") my @macaddrs = map { $_->getNodeValue} $result->get_nodelist; Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

Daniel, thanks for the comments .. most of them are integrated, but I have a problem with changing MAC addresses, because the domain xml is regenerated every time, so I cannot rely on the interface name being "eth0" as fedora during boot creates a new ethX for each new MAC address .. any good advice? On Thu, 2010-04-15 at 15:27 +0100, Daniel P. Berrange wrote:
On Thu, Apr 15, 2010 at 02:35:41PM +0200, Gerhard Stenzel wrote:
The following patch mainly adds a set of test case to verify that several spoofing attacks are prevented by the nwfilter subsystem.
In order to have a well defined test machine, the patch also includes test scripts to network install a virtual disk from scratch, to boot the virtual test machine prior to running the actual test scripts and to shut it down afterwards.
While I have tried to remove as much dependency on my local setup as possible there is still some left, so I am currently more interested in feedback about the general approach, not necessarily actual inclusion into the libvirt-TCK git.
Your actual test cases look good, so I'll just put comments about the setup/teardown stuff inline.
ok .. sounds good
For example, I am currently trying to find a suitable location for the kickstart file, and also a suitable place for the common_functions.pl.
The 'lib' directory contains modules which provide common functions & code for the test scripts. In this case I'd suggest creating a file
lib/Sys/Virt/TCK/NetworkHelpers.pm (use Sys::Virt::TCK::NetworkHelpers)
ok .. done
Index: libvirt-tck/scripts/network/README =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/README @@ -0,0 +1,14 @@ + +Test cases: + +000-install-image.t creates and install a 2GB fedora virtual disk via kickstart file from the network +001-boot-image.t defines and boots a VM which uses the fedora virtual disk +100-ping-still-working.t verifies the VM is pingable +210-no-mac-spoofing.t verifies mac spoofing is prevented +220-no-ip-spoofing.t verifies ip spoofing is prevented +230-no-mac-broadcast.t verifies mac broadcasting is prevented +240-no-arp-spoofing.t verifies arp spoofing is prevented +999-shutdown-image.t shuts the VM down
One thing about the TCK test cases is that each one should be self-contained, doing all setup & teardown it requires, not reliant on any of the other tests cases or ordering of tests.
So instead of having the 000-install-image.t & 0001-boot-image.t scripts that do setup, you'd want to create some library code that can be used to install + boot the guest, and just call that from each test case.
I am currently trying this .. however, what I am struggling with is that the MAC address is different for every boot.
Index: libvirt-tck/scripts/network/000-install-image.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/000-install-image.t @@ -0,0 +1,181 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/000-install-image.t - install network test image + +=head1 DESCRIPTION + +The test case creates and install a 2GB fedora virtual +disk via kickstart file from the network. + +=cut + +use strict; +use warnings; + +use Test::More tests => 1; + +use Sys::Virt::TCK; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { $tck->cleanup if $tck; } + +# variables which may need to be adapted +my $domain_name ="f12nwtest"; +my $disk_name = "/var/lib/libvirt/images/${domain_name}.img"; +my $disk_size = "2147483648";
You want to avoid hardcoding any disk paths in the test cases.
There's a convenient helper function if you want to create a scratch disk for a guest
my $path = $tck->create_sparse_disk("nwfilter", "root.img", 2147483648);
create_sparse_disk does not work for me, anaconda does not recognize installable disks .. anyway I got rid of the hardcoded names.
+my $kickstart_file ="http://192.168.122.1/ks.cfg"; +my $cmdline = "ip=dhcp gateway=192.168.122.1 ks=${kickstart_file}";
In the 'conf/default.cfg' file we list the kernel+initrd images for various distros. We could likely also have an optional kickstart file listed there, so we move this configuration info out of the test cases.
sounds good .. but not done yet
+# see if the domain already exits +my $already_defined = 0; +diag "searching if ${domain_name} is already defined"; +my $nnames = $conn->num_of_defined_domains(); +my @names = $conn->list_defined_domain_names($nnames); +foreach (@names){ + if (/${domain_name}/) { + print "$_ already exists, no need to redefine\n"; + $already_defined = 1; + } +} +diag $already_defined;
All guests created by the TCK test cases should have a name prefix of 'tck' to avoid clashing with existing guests. Each test case should destroy + undefine all guests it creates. The TCK setup code will validate that there are no guests having a name starting with a 'tck' prefix & optionally destroy them. So each of your test cases should assume the guest doesn't already exist + clean it up before exiting.
ok .. working on that one
NB, the disk files are not deleted between runs, so you will still avoid the overhead of installation.
I'm talking with the libguestfs folks to see if we can re-use their guest appliance + daemon so we can avoid the actual install + ssh stuff at some point in the future.
+# check for installation disk and build it if not exists +my $already_installed = 0; +my $pool = $conn->get_storage_pool_by_name("default"); +my $nnames = $pool->num_of_storage_volumes(); +my @volNames = $pool->list_storage_vol_names($nnames); +foreach (@volNames){ + if (/${domain_name}/) { + print "$_ already exists, no need to install\n"; + $already_installed = 1; + } +} + +my $volumexml = "<volume>". +" <name>${domain_name}.img</name>". +" <key>${disk_name}</key>". +" <source>". +" </source>". +" <capacity>${disk_size}</capacity>". +" <allocation>4096</allocation>". +" <target>". +" <path>${disk_name}</path>". +" <format type='raw'/>". +" <permissions>". +" <mode>0644</mode>". +" <owner>0</owner>". +" <group>0</group>". +" </permissions>". +" </target>". +"</volume>";
If using the create_sparse_disk helper I mentioned, you can avoid this chunk of code. Just do a check to see if $path exists or not.
ok . solved differently with generic_volume()
+# prepare image +if ($already_installed == 0) { + diag "Creating ${disk_name}"; + diag $volumexml; + my $vol = $pool->create_volume($volumexml) +# system("qemu-img create ${disk_name} ${disk_size}"); +} + +my $topxml = " <name>${domain_name}</name>". +" <memory>524288</memory>". +" <currentMemory>524288</currentMemory>". +" <vcpu>1</vcpu>"; + +my $osxml = " <os>". +" <type arch='x86_64' machine='fedora-13'>hvm</type>". +" <kernel>/var/cache/libvirt-tck/os-i686-hvm/vmlinuz</kernel>". +" <initrd>/var/cache/libvirt-tck/os-i686-hvm/initrd</initrd>". +" <cmdline>${cmdline}</cmdline>". +" <boot dev='hd'/>". +" </os>"; + +my $bottomxml = " <features>". +" <acpi/>". +" <apic/>". +" </features>". +" <clock offset='utc'/>". +" <on_poweroff>destroy</on_poweroff>". +" <on_reboot>restart</on_reboot>". +" <on_crash>restart</on_crash>". +" <devices>". +" <emulator>/usr/bin/qemu-kvm</emulator>". +" <disk type='file' device='disk'>". +" <driver name='qemu' type='raw'/>". +" <source file='${disk_name}'/>". +" <target dev='hda' bus='ide'/>". +" </disk>". +" <controller type='ide' index='0'>". +" </controller>". +" <interface type='network'>". +" <source network='default'/>". +" <target dev='vnet0'/>". +" <model type='virtio'/>". +" </interface>". +" <serial type='pty'>". +" <target port='0'/>". +" </serial>". +" <console type='pty'>". +" <target port='0'/>". +" </console>". +" <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='de'/>". +" <video>". +" <model type='cirrus' vram='9216' heads='1'/>". +" </video>". +" </devices>"; + +my $xml = "<domain type='kvm'>" . +$topxml. +$osxml. +$bottomxml. +"</domain>";
There are a couple of helpers for building XML in a portable manner. In the Sys::Virt::TCK class there's a method 'generic_domain' which will find you a bare minimum VM matching the best kernel / arch found in the TCK config file. It returns an instance of the the class Sys::Virt::TCK::DomainBuilder which lets you then add extra XML to the guest.
eg, so you could do
my $guest = $tck->generic_domain();
$guest->boot_cmdline($cmdline);
The 'boot_cmdline' method doesn't exist yet, but feel frree to extend the shared helper modules as needed. Its much nicer to use them than to build up XML manually.
ok .. done
+ +diag $xml; +diag "Defining an inactive domain config"; +my $dom; + +# no need to start if already installed +if (($already_installed == 0) && ($already_defined == 0)) { + ok_domain(sub { $dom = $conn->define_domain($xml) }, "defined persistent domain config"); + $xml = $dom->get_xml_description; + diag $xml; + diag "Starting inactive domain config"; + $dom->create; + + # wait for completion of installation + diag "wait for installation to finish .. "; + while($dom->is_active()) { + sleep(10); + diag ".. to view progress connect to virtual machine ${domain_name} .. "; + } + sleep(10); + diag " .. done"; + # cleanup + $dom->undefine; +} else { + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the existing domain object"; +} + + + + + Index: libvirt-tck/scripts/network/001-boot-image.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/001-boot-image.t @@ -0,0 +1,133 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/001-boot-image.t - boot installed test image + +=head1 DESCRIPTION + +The test case defines and boots a VM which uses the +fedora virtual disk create ny 000-install-image + +=cut + +use strict; +use warnings; + +use Test::More tests => 2; + +use Sys::Virt::TCK; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { $tck->cleanup if $tck; } + +my $already_defined = 0; +my $domain_name ="f12nwtest"; + +# see if the domain already exits +diag "searching if ${domain_name} is already defined"; +my $nnames = $conn->num_of_defined_domains(); +my @names = $conn->list_defined_domain_names($nnames); +foreach (@names){ + if (/${domain_name}/) { + print "$_ already exists, no need to redefine\n"; + $already_defined = 1; + } +} +diag $already_defined; + +my $dom; +my $xml; + +if ($already_defined == 1) { + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the existing domain object"; +} else { + my $topxml = " <name>${domain_name}</name>". + " <memory>524288</memory>". + " <currentMemory>524288</currentMemory>". + " <vcpu>1</vcpu>"; + + my $osxml = " <os>". + " <type arch='x86_64' machine='fedora-13'>hvm</type>". + " <boot dev='hd'/>". + " </os>"; + + my $bottomxml = " <features>". + " <acpi/>". + " <apic/>". + " </features>". + " <clock offset='utc'/>". + " <on_poweroff>destroy</on_poweroff>". + " <on_reboot>restart</on_reboot>". + " <on_crash>restart</on_crash>". + " <devices>". + " <emulator>/usr/bin/qemu-kvm</emulator>". + " <disk type='file' device='disk'>". + " <driver name='qemu' type='raw'/>". + " <source file='/var/lib/libvirt/images/${domain_name}.img'/>". + " <target dev='hda' bus='ide'/>". + " </disk>". + " <controller type='ide' index='0'>". + " </controller>". + " <interface type='network'>". + " <source network='default'/>". + " <filterref filter='no-spoofing'/>". + " <target dev='vnet0'/>". + " <model type='virtio'/>". + " </interface>". + " <serial type='pty'>". + " <target port='0'/>". + " </serial>". + " <console type='pty'>". + " <target port='0'/>". + " </console>". + " <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='de'/>". + " <video>". + " <model type='cirrus' vram='9216' heads='1'/>". + " </video>". + " </devices>"; + + $xml = "<domain type='kvm'>" . + $topxml. + $osxml. + $bottomxml. + "</domain>";
Again, I think you'd be better using the '$tck->generic_domain' method, and adding support to Sys::Virt::TCK::DomainBuilder for adding the '<filterref filter='no-spoofing'/>' bit of the XML.
ok .. done
Index: libvirt-tck/scripts/network/100-ping-still-working.t =================================================================== --- /dev/null +++ libvirt-tck/scripts/network/100-ping-still-working.t @@ -0,0 +1,71 @@ +# -*- perl -*- +# +# Copyright (C) 2010 IBM Corp. +# +# This program is free software; You can redistribute it and/or modify +# it under the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any +# later version +# +# The file "LICENSE" distributed along with this file provides full +# details of the terms and conditions +# + +=pod + +=head1 NAME + +network/100-ping-still-working.t - verify machines can be pinged from host + +=head1 DESCRIPTION + +The test case validates that it is possible to ping a guest machine from +the host. + +=cut + +use strict; +use warnings; + +use Test::More tests => 4; + +use Sys::Virt::TCK; +use Test::Exception; +use Net::SSH::Perl; +use XML::LibXML; + +require 'scripts/network/common_functions.pl'; + +my $tck = Sys::Virt::TCK->new(); +my $conn = eval { $tck->setup(); }; +BAIL_OUT "failed to setup test harness: $@" if $@; +END { + $tck->cleanup if $tck; +} + +# create first domain and start it +diag "Trying domain lookup by name"; +my $dom1; +my $domain_name ="f12nwtest";
This is where you'd want to start the guest domain
+ +ok_domain { $dom1 = $conn->get_domain_by_name($domain_name) } "the running domain object"; +my $xml = $dom1->get_xml_description; +diag $xml; +ok($dom1->get_id() > 0, "running domain has an ID > 0"); +my $mac1 = get_macaddress($xml); +diag $mac1; +my $guestip1 = get_ip_from_leases($mac1); +diag "ip is $guestip1"; + +# check ebtables entry +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`; +diag $ebtable1; +# fixme to include mac adress +ok($ebtable1 =~ "vnet0", "check ebtables entry"); + +# ping guest1 +my $ping1 = `ping -c 10 $guestip1`; +diag $ping1; +ok($ping1 =~ "10 received", "ping $guestip1 test");
And run 'destroy' here. If you use a transient guest when initially booting ($conn->create_domain($xml)) then you'll not have to worry about the undefine step.
+ +sub get_macaddress { + my $xmldesc = shift; + + my $mac; + my $parser = XML::LibXML->new(); + + my $doc = $parser->parse_string($xmldesc); + + my $rootel = $doc -> getDocumentElement(); + + my @devices = $rootel->getChildrenByTagName("devices"); + foreach my $device(@devices) { + my @interfaces = $device->getChildrenByTagName("interface"); + foreach my $interface(@interfaces) { + my @targets = $interface->getChildrenByTagName("mac"); + foreach my $target(@targets) { + $mac = $target->getAttribute("address"); + } + } + } + utf8::decode($mac); + return $mac; +}
You can simplify this using XPath - the $tck object provides a convenient method for performing an XPath query on a $dom object. eg that whole method can just be
my $resultt = xpath($dom, "/domain/devices/interface/mac/\@address") my @macaddrs = map { $_->getNodeValue} $result->get_nodelist;
ok .. but not done yet
Daniel
-- Best regards, Gerhard Stenzel, ----------------------------------------------------------------------------------------------------------------------------------- IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Martin Jetter Geschäftsführung: Dirk Wittkopp Sitz der Gesellschaft: Böblingen Registergericht: Amtsgericht Stuttgart, HRB 243294
participants (2)
-
Daniel P. Berrange
-
Gerhard Stenzel