]>
Commit | Line | Data |
---|---|---|
1 | package PVE::Network::SDN::Zones::QinQPlugin; | |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use PVE::Network::SDN::Zones::Plugin; | |
6 | use PVE::Exception qw(raise raise_param_exc); | |
7 | ||
8 | use base('PVE::Network::SDN::Zones::Plugin'); | |
9 | ||
10 | sub type { | |
11 | return 'qinq'; | |
12 | } | |
13 | ||
14 | sub properties { | |
15 | return { | |
16 | tag => { | |
17 | type => 'integer', | |
18 | minimum => 0, | |
19 | description => "Service-VLAN Tag", | |
20 | }, | |
21 | mtu => { | |
22 | type => 'integer', | |
23 | description => "MTU", | |
24 | optional => 1, | |
25 | }, | |
26 | 'vlan-protocol' => { | |
27 | type => 'string', | |
28 | enum => ['802.1q', '802.1ad'], | |
29 | default => '802.1q', | |
30 | optional => 1, | |
31 | } | |
32 | }; | |
33 | } | |
34 | ||
35 | sub options { | |
36 | ||
37 | return { | |
38 | nodes => { optional => 1}, | |
39 | 'tag' => { optional => 0 }, | |
40 | 'bridge' => { optional => 0 }, | |
41 | 'mtu' => { optional => 1 }, | |
42 | 'vlan-protocol' => { optional => 1 }, | |
43 | dns => { optional => 1 }, | |
44 | reversedns => { optional => 1 }, | |
45 | dnszone => { optional => 1 }, | |
46 | ipam => { optional => 0 }, | |
47 | }; | |
48 | } | |
49 | ||
50 | # Plugin implementation | |
51 | sub generate_sdn_config { | |
52 | my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $subnet_cfg, $interfaces_config, $config) = @_; | |
53 | ||
54 | my $stag = $plugin_config->{tag}; | |
55 | my $mtu = $plugin_config->{mtu}; | |
56 | my $bridge = $plugin_config->{'bridge'}; | |
57 | my $vlanprotocol = $plugin_config->{'vlan-protocol'}; | |
58 | my $ctag = $vnet->{tag}; | |
59 | my $alias = $vnet->{alias}; | |
60 | die "can't find bridge $bridge" if !-d "/sys/class/net/$bridge"; | |
61 | ||
62 | my $vlan_aware = PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering"); | |
63 | my $is_ovs = !-d "/sys/class/net/$bridge/brif"; | |
64 | ||
65 | my @iface_config = (); | |
66 | my $vnet_bridge_ports = ""; | |
67 | ||
68 | if($is_ovs) { | |
69 | ||
70 | #ovs--->ovsintport(dot1q-tunnel tag)------->vlanawarebrige-----(tag)--->vnet | |
71 | ||
72 | $vlanprotocol = "802.1q" if !$vlanprotocol; | |
73 | my $svlan_iface = "sv_".$zoneid; | |
74 | my $zone = "z_$zoneid"; | |
75 | ||
76 | #ovs dot1q-tunnel port | |
77 | @iface_config = (); | |
78 | push @iface_config, "ovs_type OVSIntPort"; | |
79 | push @iface_config, "ovs_bridge $bridge"; | |
80 | push @iface_config, "ovs_mtu $mtu" if $mtu; | |
81 | push @iface_config, "ovs_options vlan_mode=dot1q-tunnel tag=$stag other_config:qinq-ethtype=$vlanprotocol"; | |
82 | push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; | |
83 | ||
84 | #redefine main ovs bridge, ifupdown2 will merge ovs_ports | |
85 | @iface_config = (); | |
86 | push @iface_config, "ovs_ports $svlan_iface"; | |
87 | push(@{$config->{$bridge}}, @iface_config); | |
88 | ||
89 | #zone vlan aware bridge | |
90 | @iface_config = (); | |
91 | push @iface_config, "mtu $mtu" if $mtu; | |
92 | push @iface_config, "bridge-stp off"; | |
93 | push @iface_config, "bridge-ports $svlan_iface"; | |
94 | push @iface_config, "bridge-fd 0"; | |
95 | push @iface_config, "bridge-vlan-aware yes"; | |
96 | push @iface_config, "bridge-vids 2-4094"; | |
97 | push(@{$config->{$zone}}, @iface_config) if !$config->{$zone}; | |
98 | ||
99 | $vnet_bridge_ports = "$zone.$ctag"; | |
100 | ||
101 | } elsif ($vlan_aware) { | |
102 | ||
103 | #vlanawarebrige-(tag)----->vlanwarebridge-(tag)----->vnet | |
104 | ||
105 | my $zone = "z_$zoneid"; | |
106 | ||
107 | if($vlanprotocol) { | |
108 | @iface_config = (); | |
109 | push @iface_config, "bridge-vlan-protocol $vlanprotocol"; | |
110 | push(@{$config->{$bridge}}, @iface_config) if !$config->{$bridge}; | |
111 | } | |
112 | ||
113 | #zone vlan bridge | |
114 | @iface_config = (); | |
115 | push @iface_config, "mtu $mtu" if $mtu; | |
116 | push @iface_config, "bridge-stp off"; | |
117 | push @iface_config, "bridge-ports $bridge.$stag"; | |
118 | push @iface_config, "bridge-fd 0"; | |
119 | push @iface_config, "bridge-vlan-aware yes"; | |
120 | push @iface_config, "bridge-vids 2-4094"; | |
121 | push(@{$config->{$zone}}, @iface_config) if !$config->{$zone}; | |
122 | ||
123 | $vnet_bridge_ports = "$zone.$ctag"; | |
124 | ||
125 | } else { | |
126 | ||
127 | #eth--->eth.x(svlan)--->eth.x.y(cvlan)---->vnet | |
128 | ||
129 | my @bridge_ifaces = (); | |
130 | my $dir = "/sys/class/net/$bridge/brif"; | |
131 | PVE::Tools::dir_glob_foreach($dir, '(((eth|bond)\d+|en[^.]+)(\.\d+)?)', sub { | |
132 | push @bridge_ifaces, $_[0]; | |
133 | }); | |
134 | ||
135 | foreach my $bridge_iface (@bridge_ifaces) { | |
136 | ||
137 | # use named vlan interface to avoid too long names | |
138 | my $svlan_iface = "sv_$vnetid"; | |
139 | my $cvlan_iface = "cv_$vnetid"; | |
140 | ||
141 | #svlan | |
142 | @iface_config = (); | |
143 | push @iface_config, "vlan-raw-device $bridge_iface"; | |
144 | push @iface_config, "vlan-id $stag"; | |
145 | push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol; | |
146 | push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; | |
147 | ||
148 | #cvlan | |
149 | @iface_config = (); | |
150 | push @iface_config, "vlan-raw-device $svlan_iface"; | |
151 | push @iface_config, "vlan-id $ctag"; | |
152 | push(@{$config->{$cvlan_iface}}, @iface_config) if !$config->{$cvlan_iface}; | |
153 | ||
154 | $vnet_bridge_ports .= " $cvlan_iface"; | |
155 | } | |
156 | } | |
157 | ||
158 | #vnet bridge | |
159 | @iface_config = (); | |
160 | push @iface_config, "bridge_ports $vnet_bridge_ports"; | |
161 | push @iface_config, "bridge_stp off"; | |
162 | push @iface_config, "bridge_fd 0"; | |
163 | if($vnet->{vlanaware}) { | |
164 | push @iface_config, "bridge-vlan-aware yes"; | |
165 | push @iface_config, "bridge-vids 2-4094"; | |
166 | } | |
167 | push @iface_config, "mtu $mtu" if $mtu; | |
168 | push @iface_config, "alias $alias" if $alias; | |
169 | push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; | |
170 | ||
171 | } | |
172 | ||
173 | sub status { | |
174 | my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; | |
175 | ||
176 | my $bridge = $plugin_config->{bridge}; | |
177 | my $err_msg = []; | |
178 | ||
179 | if (!-d "/sys/class/net/$bridge") { | |
180 | push @$err_msg, "missing $bridge"; | |
181 | return $err_msg; | |
182 | } | |
183 | ||
184 | my $vlan_aware = PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering"); | |
185 | my $is_ovs = !-d "/sys/class/net/$bridge/brif"; | |
186 | ||
187 | my $tag = $vnet->{tag}; | |
188 | my $vnet_uplink = "ln_".$vnetid; | |
189 | my $vnet_uplinkpeer = "pr_".$vnetid; | |
190 | ||
191 | # ifaces to check | |
192 | my $ifaces = [ $vnetid, $bridge ]; | |
193 | if($is_ovs) { | |
194 | my $svlan_iface = "sv_".$zone; | |
195 | my $zonebridge = "z_$zone"; | |
196 | push @$ifaces, $svlan_iface; | |
197 | push @$ifaces, $zonebridge; | |
198 | } elsif ($vlan_aware) { | |
199 | my $zonebridge = "z_$zone"; | |
200 | push @$ifaces, $zonebridge; | |
201 | } else { | |
202 | my $svlan_iface = "sv_$vnetid"; | |
203 | my $cvlan_iface = "cv_$vnetid"; | |
204 | push @$ifaces, $svlan_iface; | |
205 | push @$ifaces, $cvlan_iface; | |
206 | } | |
207 | ||
208 | foreach my $iface (@{$ifaces}) { | |
209 | if (!$status->{$iface}->{status}) { | |
210 | push @$err_msg, "missing $iface"; | |
211 | } elsif ($status->{$iface}->{status} ne 'pass') { | |
212 | push @$err_msg, "error $iface"; | |
213 | } | |
214 | } | |
215 | return $err_msg; | |
216 | } | |
217 | ||
218 | sub vnet_update_hook { | |
219 | my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; | |
220 | ||
221 | my $vnet = $vnet_cfg->{ids}->{$vnetid}; | |
222 | my $tag = $vnet->{tag}; | |
223 | ||
224 | raise_param_exc({ tag => "missing vlan tag"}) if !defined($vnet->{tag}); | |
225 | raise_param_exc({ tag => "vlan tag max value is 4096"}) if $vnet->{tag} > 4096; | |
226 | ||
227 | # verify that tag is not already defined in another vnet on same zone | |
228 | foreach my $id (keys %{$vnet_cfg->{ids}}) { | |
229 | next if $id eq $vnetid; | |
230 | my $othervnet = $vnet_cfg->{ids}->{$id}; | |
231 | my $other_tag = $othervnet->{tag}; | |
232 | next if $vnet->{zone} ne $othervnet->{zone}; | |
233 | raise_param_exc({ tag => "tag $tag already exist in vnet $id"}) if $other_tag && $tag eq $other_tag; | |
234 | } | |
235 | } | |
236 | ||
237 | 1; | |
238 | ||
239 |