]> git.proxmox.com Git - pve-network.git/blob - PVE/Network/SDN/VxlanPlugin.pm
add format for multicast-address
[pve-network.git] / PVE / Network / SDN / VxlanPlugin.pm
1 package PVE::Network::SDN::VxlanPlugin;
2
3 use strict;
4 use warnings;
5 use PVE::Network::SDN::Plugin;
6 use PVE::Tools qw($IPV4RE);
7 use PVE::INotify;
8
9 use base('PVE::Network::SDN::Plugin');
10
11 PVE::JSONSchema::register_format('pve-sdn-vxlanrange', \&pve_verify_sdn_vxlanrange);
12 sub pve_verify_sdn_vxlanrange {
13 my ($vxlanstr) = @_;
14
15 PVE::Network::SDN::Plugin::parse_tag_number_or_range($vxlanstr, '16777216');
16
17 return $vxlanstr;
18 }
19
20 PVE::JSONSchema::register_format('ipv4-multicast', \&parse_ipv4_multicast);
21 sub parse_ipv4_multicast {
22 my ($ipv4, $noerr) = @_;
23
24 if ($ipv4 !~ m/^(?:$IPV4RE)$/) {
25 return undef if $noerr;
26 die "value does not look like a valid multicast IPv4 address\n";
27 }
28
29 if ($ipv4 =~ m/^(\d+)\.\d+.\d+.\d+/) {
30 if($1 < 224 || $1 > 239) {
31 return undef if $noerr;
32 die "value does not look like a valid multicast IPv4 address\n";
33 }
34 }
35
36 return $ipv4;
37 }
38
39 sub type {
40 return 'vxlan';
41 }
42
43 sub properties {
44 return {
45 'vxlan-allowed' => {
46 type => 'string', format => 'pve-sdn-vxlanrange',
47 description => "Allowed vlan range",
48 },
49 'multicast-address' => {
50 description => "Multicast address.",
51 type => 'string', format => 'ipv4-multicast'
52 },
53 'unicast-address' => {
54 description => "Unicast peers address ip list.",
55 type => 'string', format => 'ip-list'
56 },
57 'vrf' => {
58 description => "vrf name.",
59 type => 'string', #fixme: format
60 },
61 'vrf-vxlan' => {
62 type => 'integer',
63 description => "l3vni.",
64 },
65 'router' => {
66 type => 'string',
67 description => "Frr router name",
68 },
69 };
70 }
71
72 sub options {
73
74 return {
75 'uplink-id' => { optional => 0 },
76 'multicast-address' => { optional => 1 },
77 'unicast-address' => { optional => 1 },
78 'vxlan-allowed' => { optional => 1 },
79 'vrf' => { optional => 1 },
80 'vrf-vxlan' => { optional => 1 },
81 'router' => { optional => 1 },
82 };
83 }
84
85 # Plugin implementation
86 sub generate_sdn_config {
87 my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
88
89 my $tag = $vnet->{tag};
90 my $alias = $vnet->{alias};
91 my $ipv4 = $vnet->{ipv4};
92 my $ipv6 = $vnet->{ipv6};
93 my $mac = $vnet->{mac};
94 my $multicastaddress = $plugin_config->{'multicast-address'};
95 my @unicastaddress = split(',', $plugin_config->{'unicast-address'}) if $plugin_config->{'unicast-address'};
96
97 my $uplink = $plugin_config->{'uplink-id'};
98 my $vxlanallowed = $plugin_config->{'vxlan-allowed'};
99 my $vrf = $plugin_config->{'vrf'};
100 my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
101
102 die "missing vxlan tag" if !$tag;
103 my $iface = "uplink$uplink";
104 my $ifaceip = "";
105
106 if($uplinks->{$uplink}->{name}) {
107 $iface = $uplinks->{$uplink}->{name};
108 $ifaceip = PVE::Network::SDN::Plugin::get_first_local_ipv4_from_interface($iface);
109 }
110
111 my $mtu = 1450;
112 $mtu = $uplinks->{$uplink}->{mtu} - 50 if $uplinks->{$uplink}->{mtu};
113 $mtu = $vnet->{mtu} if $vnet->{mtu};
114
115 #vxlan interface
116 my @iface_config = ();
117 push @iface_config, "vxlan-id $tag";
118
119 if($multicastaddress) {
120 push @iface_config, "vxlan-svcnodeip $multicastaddress";
121 push @iface_config, "vxlan-physdev $iface";
122 } elsif (@unicastaddress) {
123
124 foreach my $address (@unicastaddress) {
125 next if $address eq $ifaceip;
126 push @iface_config, "vxlan_remoteip $address";
127 }
128 } else {
129 push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
130 push @iface_config, "bridge-learning off";
131 push @iface_config, "bridge-arp-nd-suppress on";
132 }
133
134 push @iface_config, "mtu $mtu" if $mtu;
135 push(@{$config->{"vxlan$vnetid"}}, @iface_config) if !$config->{"vxlan$vnetid"};
136
137 #vnet bridge
138 @iface_config = ();
139 push @iface_config, "address $ipv4" if $ipv4;
140 push @iface_config, "address $ipv6" if $ipv6;
141 push @iface_config, "hwaddress $mac" if $mac;
142 push @iface_config, "bridge_ports vxlan$vnetid";
143 push @iface_config, "bridge_stp off";
144 push @iface_config, "bridge_fd 0";
145 push @iface_config, "mtu $mtu" if $mtu;
146 push @iface_config, "alias $alias" if $alias;
147 push @iface_config, "ip-forward on" if $ipv4;
148 push @iface_config, "ip6-forward on" if $ipv6;
149 push @iface_config, "arp-accept on" if $ipv4||$ipv6;
150 push @iface_config, "vrf $vrf" if $vrf;
151 push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
152
153 if ($vrf) {
154 #vrf intreface
155 @iface_config = ();
156 push @iface_config, "vrf-table auto";
157 push(@{$config->{$vrf}}, @iface_config) if !$config->{$vrf};
158
159 if ($vrfvxlan) {
160 #l3vni vxlan interface
161 my $iface_vxlan = "vxlan$vrf";
162 @iface_config = ();
163 push @iface_config, "vxlan-id $vrfvxlan";
164 push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
165 push @iface_config, "bridge-learning off";
166 push @iface_config, "bridge-arp-nd-suppress on";
167 push @iface_config, "mtu $mtu" if $mtu;
168 push(@{$config->{$iface_vxlan}}, @iface_config) if !$config->{$iface_vxlan};
169
170 #l3vni bridge
171 my $brvrf = "br$vrf";
172 @iface_config = ();
173 push @iface_config, "bridge-ports $iface_vxlan";
174 push @iface_config, "bridge_stp off";
175 push @iface_config, "bridge_fd 0";
176 push @iface_config, "mtu $mtu" if $mtu;
177 push @iface_config, "vrf $vrf";
178 push(@{$config->{$brvrf}}, @iface_config) if !$config->{$brvrf};
179 }
180 }
181
182 return $config;
183 }
184
185 sub generate_frr_config {
186 my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
187
188 my $vrf = $plugin_config->{'vrf'};
189 my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
190 my $asn = $router->{asn};
191 my $gatewaynodes = $router->{'gateway-nodes'};
192
193 return if !$vrf || !$vrfvxlan || !$asn;
194
195 #vrf
196 my @router_config = ();
197 push @router_config, "vni $vrfvxlan";
198 push(@{$config->{vrf}->{"$vrf"}}, @router_config);
199
200 @router_config = ();
201
202 my $is_gateway = undef;
203 my $local_node = PVE::INotify::nodename();
204
205 foreach my $gatewaynode (PVE::Tools::split_list($gatewaynodes)) {
206 $is_gateway = 1 if $gatewaynode eq $local_node;
207 }
208
209 if ($is_gateway) {
210
211 @router_config = ();
212 #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
213 #frr 7.1 tag is bugged -> works fine with 7.1 stable branch(20190829-02-g6ba76bbc1)
214 #https://github.com/FRRouting/frr/issues/4905
215 push @router_config, "import vrf $vrf";
216 push(@{$config->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
217 push(@{$config->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
218
219 @router_config = ();
220 #redistribute connected to be able to route to local vms on the gateway
221 push @router_config, "redistribute connected";
222 push(@{$config->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
223 push(@{$config->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
224
225 @router_config = ();
226 #add default originate to announce 0.0.0.0/0 type5 route in evpn
227 push @router_config, "default-originate ipv4";
228 push @router_config, "default-originate ipv6";
229 push(@{$config->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @router_config);
230 }
231
232 return $config;
233 }
234
235 sub on_delete_hook {
236 my ($class, $transportid, $sdn_cfg) = @_;
237
238 # verify that no vnet are associated to this transport
239 foreach my $id (keys %{$sdn_cfg->{ids}}) {
240 my $sdn = $sdn_cfg->{ids}->{$id};
241 die "transport $transportid is used by vnet $id"
242 if ($sdn->{type} eq 'vnet' && defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid);
243 }
244 }
245
246 sub on_update_hook {
247 my ($class, $transportid, $sdn_cfg) = @_;
248
249 my $transport = $sdn_cfg->{ids}->{$transportid};
250
251 # verify that vxlan-allowed don't conflict with another vxlan-allowed transport
252
253 # verify that vxlan-allowed is matching currently vnet tag in this transport
254 my $vxlanallowed = $transport->{'vxlan-allowed'};
255 if ($vxlanallowed) {
256 foreach my $id (keys %{$sdn_cfg->{ids}}) {
257 my $sdn = $sdn_cfg->{ids}->{$id};
258 if ($sdn->{type} eq 'vnet' && defined($sdn->{tag})) {
259 if(defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid) {
260 my $tag = $sdn->{tag};
261 eval {
262 PVE::Network::SDN::Plugin::parse_tag_number_or_range($vxlanallowed, '16777216', $tag);
263 };
264 if($@) {
265 die "vnet $id - vlan $tag is not allowed in transport $transportid";
266 }
267 }
268 }
269 }
270 }
271
272 # verify that router exist
273 if (defined($sdn_cfg->{ids}->{$transportid}->{router})) {
274 my $router = $sdn_cfg->{ids}->{$transportid}->{router};
275 if (!defined($sdn_cfg->{ids}->{$router})) {
276 die "router $router don't exist";
277 } else {
278 die "$router is not a router type" if $sdn_cfg->{ids}->{$router}->{type} ne 'frr';
279 }
280
281 #vrf && vrf-vxlan need to be defined with router
282 my $vrf = $sdn_cfg->{ids}->{$transportid}->{vrf};
283 if (!defined($vrf)) {
284 die "missing vrf option";
285 } else {
286 # verify that vrf is not already declared in another transport
287 foreach my $id (keys %{$sdn_cfg->{ids}}) {
288 next if $id eq $transportid;
289 die "vrf $vrf is already declared in $id"
290 if (defined($sdn_cfg->{ids}->{$id}->{vrf}) && $sdn_cfg->{ids}->{$id}->{vrf} eq $vrf);
291 }
292 }
293
294 my $vrfvxlan = $sdn_cfg->{ids}->{$transportid}->{'vrf-vxlan'};
295 if (!defined($vrfvxlan)) {
296 die "missing vrf-vxlan option";
297 } else {
298 # verify that vrf-vxlan is not already declared in another transport
299 foreach my $id (keys %{$sdn_cfg->{ids}}) {
300 next if $id eq $transportid;
301 die "vrf-vxlan $vrfvxlan is already declared in $id"
302 if (defined($sdn_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $sdn_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan);
303 }
304 }
305 }
306 }
307
308 1;
309
310