]> git.proxmox.com Git - pve-network.git/blame - PVE/Network/SDN/FrrPlugin.pm
make sdn controller plugin generic
[pve-network.git] / PVE / Network / SDN / FrrPlugin.pm
CommitLineData
32602a38
AD
1package PVE::Network::SDN::FrrPlugin;
2
3use strict;
4use warnings;
5use PVE::Network::SDN::Plugin;
6use PVE::Tools;
074d270b
AD
7use PVE::INotify;
8use PVE::JSONSchema qw(get_standard_option);
32602a38
AD
9
10use base('PVE::Network::SDN::Plugin');
11
12sub type {
13 return 'frr';
14}
15
8fb1ee7f
AD
16sub plugindata {
17 return {
18 role => 'controller',
19 };
20}
21
32602a38
AD
22sub properties {
23 return {
24 'asn' => {
25 type => 'integer',
26 description => "autonomous system number",
27 },
28 'peers' => {
29 description => "peers address list.",
fcfca9ef 30 type => 'string', format => 'ip-list'
32602a38 31 },
074d270b
AD
32 'gateway-nodes' => get_standard_option('pve-node-list'),
33 'gateway-external-peers' => {
34 description => "upstream bgp peers address list.",
fcfca9ef 35 type => 'string', format => 'ip-list'
074d270b 36 },
32602a38
AD
37 };
38}
39
40sub options {
41
42 return {
43 'uplink-id' => { optional => 0 },
44 'asn' => { optional => 0 },
45 'peers' => { optional => 0 },
074d270b
AD
46 'gateway-nodes' => { optional => 1 },
47 'gateway-external-peers' => { optional => 1 },
32602a38
AD
48 };
49}
50
51# Plugin implementation
8fb1ee7f 52sub generate_controller_config {
074d270b 53 my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
32602a38 54
32602a38
AD
55 my @peers = split(',', $plugin_config->{'peers'}) if $plugin_config->{'peers'};
56
074d270b 57 my $asn = $plugin_config->{asn};
32602a38 58 my $uplink = $plugin_config->{'uplink-id'};
074d270b
AD
59 my $gatewaynodes = $plugin_config->{'gateway-nodes'};
60 my @gatewaypeers = split(',', $plugin_config->{'gateway-external-peers'}) if $plugin_config->{'gateway-external-peers'};
61
62 return if !$asn;
32602a38 63
32602a38
AD
64 my $iface = "uplink$uplink";
65 my $ifaceip = "";
66
67 if($uplinks->{$uplink}->{name}) {
68 $iface = $uplinks->{$uplink}->{name};
87d8b623 69 $ifaceip = PVE::Network::SDN::Plugin::get_first_local_ipv4_from_interface($iface);
32602a38
AD
70 }
71
074d270b
AD
72 my $is_gateway = undef;
73 my $local_node = PVE::INotify::nodename();
74
75 foreach my $gatewaynode (PVE::Tools::split_list($gatewaynodes)) {
76 $is_gateway = 1 if $gatewaynode eq $local_node;
77 }
17854295 78
93dea3aa
AD
79 my @router_config = ();
80
93dea3aa 81 push @router_config, "bgp router-id $ifaceip";
bc49b410 82 push @router_config, "no bgp default ipv4-unicast";
93dea3aa 83 push @router_config, "coalesce-time 1000";
32602a38
AD
84
85 foreach my $address (@peers) {
86 next if $address eq $ifaceip;
93dea3aa 87 push @router_config, "neighbor $address remote-as $asn";
7d35eaf5 88 }
074d270b
AD
89
90 if ($is_gateway) {
91 foreach my $address (@gatewaypeers) {
92 push @router_config, "neighbor $address remote-as external";
93 }
94 }
8fb1ee7f 95 push(@{$config->{frr}->{router}->{"bgp $asn"}->{""}}, @router_config);
074d270b 96
17854295 97 @router_config = ();
32602a38
AD
98 foreach my $address (@peers) {
99 next if $address eq $ifaceip;
17854295 100 push @router_config, "neighbor $address activate";
32602a38 101 }
17854295 102 push @router_config, "advertise-all-vni";
8fb1ee7f 103 push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"l2vpn evpn"}}, @router_config);
32602a38 104
074d270b
AD
105 if ($is_gateway) {
106
107 @router_config = ();
108 #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
109 #frr 7.1 tag is bugged -> works fine with 7.1 stable branch(20190829-02-g6ba76bbc1)
110 #https://github.com/FRRouting/frr/issues/4905
111 foreach my $address (@gatewaypeers) {
112 push @router_config, "neighbor $address activate";
113 }
8fb1ee7f
AD
114 push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
115 push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
074d270b
AD
116
117 }
118
32602a38
AD
119 return $config;
120}
121
122sub on_delete_hook {
5bda8607 123 my ($class, $routerid, $sdn_cfg) = @_;
32602a38 124
5bda8607
AD
125 # verify that transport is associated to this router
126 foreach my $id (keys %{$sdn_cfg->{ids}}) {
127 my $sdn = $sdn_cfg->{ids}->{$id};
128 die "router $routerid is used by $id"
129 if (defined($sdn->{router}) && $sdn->{router} eq $routerid);
130 }
32602a38
AD
131}
132
133sub on_update_hook {
5bda8607
AD
134 my ($class, $routerid, $sdn_cfg) = @_;
135
136 # verify that asn is not already used by another router
137 my $asn = $sdn_cfg->{ids}->{$routerid}->{asn};
138 foreach my $id (keys %{$sdn_cfg->{ids}}) {
139 next if $id eq $routerid;
140 my $sdn = $sdn_cfg->{ids}->{$id};
141 die "asn $asn is already used by $id"
142 if (defined($sdn->{asn}) && $sdn->{asn} eq $asn);
143 }
32602a38
AD
144}
145
8fb1ee7f
AD
146sub sort_frr_config {
147 my $order = {};
148 $order->{''} = 0;
149 $order->{'vrf'} = 1;
150 $order->{'ipv4 unicast'} = 1;
151 $order->{'ipv6 unicast'} = 2;
152 $order->{'l2vpn evpn'} = 3;
153
154 my $a_val = 100;
155 my $b_val = 100;
156
157 $a_val = $order->{$a} if defined($order->{$a});
158 $b_val = $order->{$b} if defined($order->{$b});
159
160 if($a =~ /bgp (\d+)$/) {
161 $a_val = 2;
162 }
163
164 if($b =~ /bgp (\d+)$/) {
165 $b_val = 2;
166 }
167
168 return $a_val <=> $b_val;
169}
170
171sub generate_frr_recurse{
172 my ($final_config, $content, $parentkey, $level) = @_;
173
174 my $keylist = {};
175 $keylist->{vrf} = 1;
176 $keylist->{'address-family'} = 1;
177 $keylist->{router} = 1;
178
179 my $exitkeylist = {};
180 $exitkeylist->{vrf} = 1;
181 $exitkeylist->{'address-family'} = 1;
182
183 #fix me, make this generic
184 my $paddinglevel = undef;
185 if($level == 1 || $level == 2) {
186 $paddinglevel = $level - 1;
187 } elsif ($level == 3 || $level == 4) {
188 $paddinglevel = $level - 2;
189 }
190
191 my $padding = "";
192 $padding = ' ' x ($paddinglevel) if $paddinglevel;
193
194 if (ref $content eq ref {}) {
195 foreach my $key (sort sort_frr_config keys %$content) {
196 if ($parentkey && defined($keylist->{$parentkey})) {
197 push @{$final_config}, $padding."!";
198 push @{$final_config}, $padding."$parentkey $key";
199 } else {
200 push @{$final_config}, $padding."$key" if $key ne '' && !defined($keylist->{$key});
201 }
202
203 my $option = $content->{$key};
204 generate_frr_recurse($final_config, $option, $key, $level+1);
205
206 push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
207 }
208 }
32602a38 209
8fb1ee7f
AD
210 if (ref $content eq 'ARRAY') {
211 foreach my $value (@$content) {
212 push @{$final_config}, $padding."$value";
213 }
214 }
215}
216
217sub write_controller_config {
218 my ($class, $plugin_config, $config) = @_;
219
220 my $final_config = [];
221 push @{$final_config}, "log syslog informational";
222 push @{$final_config}, "!";
223
224 generate_frr_recurse($final_config, $config->{frr}, undef, 0);
225
226 push @{$final_config}, "!";
227 push @{$final_config}, "line vty";
228 push @{$final_config}, "!";
229
230 my $rawconfig = join("\n", @{$final_config});
231
232
233 return if !$rawconfig;
234 return if !-d "/etc/frr";
235
236 my $frr_config_file = "/etc/frr/frr.conf";
237
238 my $writefh = IO::File->new($frr_config_file,">");
239 print $writefh $rawconfig;
240 $writefh->close();
241}
242
2431;
32602a38 244