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; |