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