File Coverage

os-autoinst-openvswitch
Criterion Covered Total %
statement 43 167 25.7
total 43 167 25.7


line stmt code
1   #!/usr/bin/perl
2   # Copyright SUSE LLC
3   # SPDX-License-Identifier: GPL-2.0-or-later
4    
5   =head1 SYNOPSIS
6    
7   os-autoinst-openvswitch [OPTIONS]
8    
9   =head1 OPTIONS
10    
11   =over 4
12    
13   =item B<-h, -?, --help>
14    
15   Show this help.
16    
17   =cut
18    
19    
20   use Mojo::Base 'Net::DBus::Object', -signatures;
21 1 use autodie ':all';
  1  
  1  
22 1 use Getopt::Long;
  1  
  1  
23 1 use Net::DBus::Exporter 'org.opensuse.os_autoinst.switch';
  1  
  1  
24 1 require IPC::System::Simple;
  1  
  1  
25 1 use IPC::Open3;
26 1 use Symbol 'gensym';
  1  
  1  
27 1 use Time::Seconds;
  1  
  1  
28 1  
  1  
  1  
29   eval { require Pod::Usage; Pod::Usage::pod2usage($r) };
30 1 die "cannot display help, install perl(Pod::Usage)\n" if $@;
  1  
  1  
31 1 }
  1  
  1  
32 0  
33   my %options;
34   GetOptions(\%options, 'help|h|?') or usage(1);
35 1 usage(0) if $options{help};
36 1  
37 1 my $self = $class->SUPER::new($service, '/switch');
38   bless $self, $class;
39 0 $self->init_switch;
  0  
  0  
  0  
40 0 return $self;
41 0 }
42 0  
43 0 $self->{BRIDGE} = $ENV{OS_AUTOINST_USE_BRIDGE};
44   $self->{BRIDGE} //= 'br0';
45    
46 0 until (-e "/sys/class/net/$self->{BRIDGE}") {
  0  
  0  
47 0 print "Waiting for bridge '$self->{BRIDGE}' to be created and configured...\n";
48 0 sleep 1;
49   }
50 0 system('ovs-vsctl', 'br-exists', $self->{BRIDGE});
51 0  
52 0 for (my $timeout = $ENV{OS_AUTOINST_OPENVSWITCH_INIT_TIMEOUT} // ONE_MINUTE; $timeout > 0; --$timeout) {
53   my $bridge_conf = `ip addr show $self->{BRIDGE}`;
54 0 $self->{MAC} = $1 if $bridge_conf =~ /ether\s+(([0-9a-f]{2}:){5}[0-9a-f]{2})\s/;
55   $self->{IP} = $1 if $bridge_conf =~ /inet\s+(([0-9]+.){3}[0-9]+\/[0-9]+)\s/;
56 0 last if $self->{IP};
57 0 print "Waiting for IP on bridge '$self->{BRIDGE}', ${timeout}s left ...\n";
58 0 sleep 1;
59 0 }
60 0  
61 0 die "can't parse bridge local port MAC" unless $self->{MAC};
62 0 die "can't parse bridge local port IP" unless $self->{IP};
63    
64   my $local_ip = $ENV{OS_AUTOINST_BRIDGE_LOCAL_IP} // '10.0.2.2';
65 0 my $netmask = $ENV{OS_AUTOINST_BRIDGE_NETMASK} // 15;
66 0 my $rewrite_target = $ENV{OS_AUTOINST_BRIDGE_REWRITE_TARGET} // '10.1.0.0';
67   # we also need a hex-converted form of the rewrite target, thanks
68 0 # https://www.perlmonks.org/?node_id=704295
69 0 my $rewrite_target_hex = unpack('H*', pack('C*', split('\.', $rewrite_target)));
70 0  
71   # the VM have unique MAC that differs in the last 16 bits (see /usr/lib/os-autoinst/backend/qemu.pm)
72   # the IP can conflict across vlans
73 0 # to allow connection from VM to host os-autoinst ($local_ip), we have to do some IP translation
74   # we use simple scheme, e.g.:
75   # MAC 52:54:00:12:XX:YY -> IP 10.1.XX.YY
76    
77   # br0 has IP $local_ip and netmask $netmask. E.g. '/15' covers 10.0.2.2 and 10.1.0.0 ranges
78   # this should be also configured permanently in /etc/sysconfig/network
79   die "bridge local port IP is expected to be $local_ip/$netmask" unless $self->{IP} eq "$local_ip/$netmask";
80    
81   # openflow rules don't survive reboot so they must be installed on each startup
82   for my $rule (
83 0 # openflow ports:
84   # LOCAL = br0
85   # 1,2,3 ... tap devices
86 0  
87   # default: normal action
88   'table=0,priority=0,action=normal',
89    
90   # reply packets from local port are handled by learned rules in table 1
91   'table=0,priority=1,in_port=LOCAL,actions=resubmit(,1)',
92    
93    
94   # arp e.g. 10.0.2.2 - learn rule for handling replies, rewrite ARP sender IP to e.g. 10.1.x.x range and send to local
95   # the learned rule rewrites ARP target to the original IP and sends the packet to the original port
96   "table=0,priority=100,dl_type=0x0806,nw_dst=$local_ip,actions=" .
97   'learn(table=1,priority=100,in_port=LOCAL,dl_type=0x0806,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],output:NXM_OF_IN_PORT[]),' .
98   "load:0x$rewrite_target_hex->NXM_OF_ARP_SPA[],move:NXM_OF_ETH_SRC[0..$netmask]->NXM_OF_ARP_SPA[0..$netmask]," .
99   'local',
100    
101   # tcp to $self->{MAC} syn - learn rule for handling replies, rewrite source IP to e.g. 10.1.x.x range and send to local
102   # the learned rule rewrites DST to the original IP and sends the packet to the original port
103   "table=0,priority=100,dl_type=0x0800,tcp_flags=+syn-ack,dl_dst=$self->{MAC},actions=" .
104   'learn(table=1,priority=100,in_port=LOCAL,dl_type=0x0800,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],output:NXM_OF_IN_PORT[]),' .
105   "mod_nw_src:$rewrite_target,move:NXM_OF_ETH_SRC[0..$netmask]->NXM_OF_IP_SRC[0..$netmask]," .
106   'local',
107    
108   # tcp to $self->{MAC} other - rewrite source IP to e.g. 10.1.x.x range and send to local
109   "table=0,priority=99,dl_type=0x0800,dl_dst=$self->{MAC},actions=" .
110   "mod_nw_src:$rewrite_target,move:NXM_OF_ETH_SRC[0..$netmask]->NXM_OF_IP_SRC[0..$netmask],local",
111   )
112   {
113   system('ovs-ofctl', 'add-flow', $self->{BRIDGE}, $rule);
114   }
115   }
116    
117 0 # Check if tap and vlan are in the correct format.
118   my $return_output;
119   my $return_code = 1;
120    
121   if ($tap !~ /^tap[0-9]+$/) {
122 0 $return_output = "'$tap' does not fit the naming scheme";
  0  
  0  
  0  
  0  
123 0 return ($return_code, $return_output);
124 0 }
125   if ($vlan !~ /^[0-9]+$/) {
126 0 $return_output = "'$vlan' does not fit the naming scheme (only numbers)";
127 0 return ($return_code, $return_output);
128 0 }
129    
130 0 my $check_bridge = `ovs-vsctl port-to-br $tap`;
131 0 chomp $check_bridge;
132 0 if ($check_bridge ne $bridge) {
133   $return_output = "'$tap' is not connected to bridge '$bridge'";
134   return ($return_code, $return_output);
135 0 }
136 0  
137 0 return 0;
138 0 }
139 0  
140   my ($wtr, $rdr, $err);
141   $err = gensym;
142 0  
143   # We need open3 because otherwise STDERR is captured by systemd.
144   # In such way we collect the error and send it back in the dbus call as well.
145 0 my $ovs_vsctl_pid = open3($wtr, $rdr, $err, @args);
  0  
  0  
146 0  
147 0 my @ovs_vsctl_output = <$rdr>;
148   my @ovs_vsctl_error = <$err>;
149   waitpid($ovs_vsctl_pid, 0);
150   my $return_code = $?;
151 0  
152   return $return_code, "@ovs_vsctl_error", "@ovs_vsctl_output";
153 0 }
154 0  
155 0 my $out = `ovs-vsctl --version`;
156 0 return if ($out !~ /\(Open vSwitch\)\s+(\d+\.\d+\.\d+)/m);
157   my @ver = split(/\./, $1);
158 0 my @min_ver = split(/\./, $min_ver);
159   return if (@ver != @min_ver);
160    
161 0 return sprintf("%04d%04d%04d", @ver) >= sprintf("%04d%04d%04d", @min_ver);
  0  
  0  
162 0 }
163 0  
164 0 dbus_method("set_vlan", ["string", "uint32"], ["int32", "string"]);
165 0 my $return_output;
166 0 my $return_code = 1;
167   my $ovs_vsctl_error;
168 0 my $ovs_vsctl_output;
169   my @cmd;
170    
171 0 ($return_code, $return_output) = _ovs_check($tap, $vlan, $self->{BRIDGE});
172 0  
  0  
  0  
  0  
  0  
173 0 unless ($return_code == 0) {
174 0 print STDERR $return_output . "\n";
175 0 return ($return_code, $return_output);
176   }
177 0  
178   @cmd = ('ovs-vsctl', 'set', 'port', $tap, "tag=$vlan");
179 0 if (check_min_ovs_version('2.8.1')) {
180   push(@cmd, 'vlan_mode=dot1q-tunnel');
181 0 }
182 0  
183 0 # Connect tap device to given vlan
184   ($return_code, $ovs_vsctl_error, $ovs_vsctl_output) = _cmd(@cmd);
185    
186 0 print STDERR $ovs_vsctl_error if length($ovs_vsctl_error) > 0;
187 0 print $ovs_vsctl_output if length($ovs_vsctl_output) > 0;
188 0 return $return_code, $ovs_vsctl_error unless $return_code == 0;
189    
190   $return_code = system('ip', 'link', 'set', $tap, 'up');
191   return $return_code, $return_code != 0 ? "Failed to set $tap up " : '';
192 0 }
193    
194 0 dbus_method("unset_vlan", ["string", "uint32"], ["int32", "string"]);
195 0 my $return_output;
196 0 my $return_code = 1;
197   my $ovs_vsctl_error;
198 0 my $ovs_vsctl_output;
199 0  
200   ($return_code, $return_output) = _ovs_check($tap, $vlan, $self->{BRIDGE});
201    
202 0 unless ($return_code == 0) {
203 0 print STDERR $return_output . "\n";
  0  
  0  
  0  
  0  
204 0 return ($return_code, $return_output);
205 0 }
206 0  
207   # Remove tap device to given vlan
208   ($return_code, $ovs_vsctl_error, $ovs_vsctl_output) = _cmd('ovs-vsctl', 'remove', 'port', $tap, 'tag', $vlan);
209 0  
210   print STDERR $ovs_vsctl_error if length($ovs_vsctl_error) > 0;
211 0 print $ovs_vsctl_output if length($ovs_vsctl_output) > 0;
212 0 return $return_code, $return_code != 0 ? $ovs_vsctl_error : '';
213 0 }
214    
215   dbus_method("show", [], ["int32", "string"]);
216   my ($return_code, undef, $ovs_vsctl_output) = _cmd('ovs-vsctl', 'show');
217 0 return $return_code, $ovs_vsctl_output;
218   }
219 0  
220 0 ################################################################################
221 0 use Mojo::Base -strict, -signatures;
222    
223   use Net::DBus;
224 0 use Net::DBus::Reactor;
225 0  
  0  
  0  
226 0 my $bus = Net::DBus->system;
227 0  
228   my $service = $bus->export_service("org.opensuse.os_autoinst.switch");
229   my $object = OVS->new($service);
230    
231   Net::DBus::Reactor->main->run;
232 1  
  1  
  1  
233   exit 0;