Skip to content

Commit

Permalink
Merge pull request #1687 from guillaume-philippon/nmstate-ipv6
Browse files Browse the repository at this point in the history
ncm-network: add ipv6 support to the nmstate backend
  • Loading branch information
jrha authored Sep 6, 2024
2 parents f9414a7 + 7167a7a commit 761169e
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 23 deletions.
100 changes: 77 additions & 23 deletions ncm-network/src/main/perl/nmstate.pm
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ sub make_nm_ip_route
};
my %rt;
if ($route->{address} eq 'default') {
$rt{destination} = '0.0.0.0/0';
$self->debug(3, "Route destination is 'default', rewriting to '0.0.0.0/0'");
$rt{destination} = '0.0.0.0/0';
} else {
if ($route->{netmask}){
my $dest_addr = NetAddr::IP->new($route->{address}."/".$route->{netmask});
Expand Down Expand Up @@ -326,7 +327,7 @@ sub find_vlan_id {
# Check if given ip belongs to a network
sub ip_in_network {
my ($self, $check_ip, $ip, $netmask) = @_;
# is the given ip in his ip/netmask.
# is the given ip in this ip/netmask.
my $subnet = NetAddr::IP->new("$ip", "$netmask");
return NetAddr::IP->new("$check_ip")->within($subnet);
}
Expand All @@ -346,7 +347,20 @@ sub generate_alias_ips {
return \@$all_ip;
}


sub generate_ipv6_secondaries {
my ($self, $secondaries) = @_;
my $ips = [];
foreach my $secondary (@$secondaries) {
my $ip = NetAddr::IP->new($secondary);
if (defined($ip)) {
my $ip_list = {};
$ip_list->{ip} = $ip->addr;
$ip_list->{'prefix-length'} = $ip->masklen;
push @$ips, $ip_list;
}
}
return \@$ips;
}

# generates the hashrefs for interface in yaml file format needed by nmstate.
# bulk of the config settings needed by the nmstate yml is done here.
Expand Down Expand Up @@ -415,31 +429,55 @@ sub generate_nmstate_config

if ($eth_bootproto eq 'static') {
$ifaceconfig->{state} = "up";
if ($is_ip) {
if ($is_ip || $iface->{ipv6addr}) {
# If primary IPv4 or primary IPv6 is defined. We allow configuration
# with IPv4 & IPv6 on same interface but also IPv6 only interface.
# if device has manual ip assigned
my $ip_list = {};
my $all_ip = [];
if ($iface->{netmask}) {
my $ip = NetAddr::IP->new($iface->{ip}."/".$iface->{netmask});
$ip_list->{ip} = $ip->addr;
$ip_list->{'prefix-length'} = $ip->masklen;
} else {
$self->error("$name with (IPv4) ip and no netmask configured");
# IPv4 configuration
if ($is_ip) {
if ($iface->{netmask}) {
my $ip = NetAddr::IP->new($iface->{ip} . "/" . $iface->{netmask});
$ip_list->{ip} = $ip->addr;
$ip_list->{'prefix-length'} = $ip->masklen;
} else {
$self->error("$name with (IPv4) ip and no netmask configured");
}
push @$all_ip, $ip_list if scalar $ip_list;
if ($iface->{aliases}) {
# if device has additional alias ipv4 addresses defined. add them to config
$self->verbose("alias ip (ipv4) addr defined for $name, configuring additional ips");
push @$all_ip, @{$self->generate_alias_ips($iface->{aliases})};
}
$ifaceconfig->{ipv4}->{address} = $all_ip;
$ifaceconfig->{ipv4}->{dhcp} = $YFALSE;
$ifaceconfig->{ipv4}->{enabled} = $YTRUE;
}
push @$all_ip, $ip_list if scalar $ip_list;
if ($iface->{aliases}) {
# if device has additional alias ipv4 addresses defined. add them to config
$self->verbose("alias ip (ipv4) addr defined for $name, configuring additional ips");
push @$all_ip, @{$self->generate_alias_ips($iface->{aliases})};
# IPv6 configuration
if ($iface->{ipv6addr}) {
$self->warn("ipv6 addr still under development");
$ifaceconfig->{ipv6}->{enabled} = $YFALSE;
my $ip_list = {};
my $ip = NetAddr::IP->new($iface->{ipv6addr});
if (defined($ip)) {
my $ips = [];
$ip_list->{ip} = $ip->addr;
$ip_list->{'prefix-length'} = $ip->masklen;
push @$ips, $ip_list;

if ($iface->{ipv6addr_secondaries}) {
# If interface has additional ipv6 addresses defined, add them
$self->verbose("additional ip (ipv6) addr defined for $name, configuring additional ips");
push @$ips, @{$self->generate_ipv6_secondaries($iface->{ipv6addr_secondaries})};
}

$ifaceconfig->{ipv6}->{address} = $ips;
$ifaceconfig->{ipv6}->{enabled} = $YTRUE;
} else {
$self->error($iface->{ipv6addr}." invalid format")
}
}
$ifaceconfig->{ipv4}->{address} = $all_ip;
$ifaceconfig->{ipv4}->{dhcp} = $YFALSE;
$ifaceconfig->{ipv4}->{enabled} = $YTRUE;
} elsif ($iface->{ipv6addr}) {
$self->warn("ipv6 addr found but not supported");
$ifaceconfig->{ipv6}->{enabled} = $YFALSE;
# TODO create ipv6.address entries here. i.e
#$ifaceconfig->{ipv6}->{address} = [$ipv6_list];
} else {
$self->error("No ip address defined for static bootproto");
}
Expand All @@ -466,12 +504,27 @@ sub generate_nmstate_config
if ((defined($iface->{ip})) and (defined($iface->{netmask}))) {
my $is_dgw_iface = $self->ip_in_network($default_gw, $iface->{ip}, $iface->{netmask});
if ($is_dgw_iface) {
$self->debug(3, "Adding the default IPv4 gateway to interface '$name'");
$default_rt{destination} = '0.0.0.0/0';
$default_rt{'next-hop-address'} = $default_gw;
$default_rt{'next-hop-interface'} = $device;
}
}
}

my %default_ipv6_rt;
if ($ipv6) {
if (defined($iface->{ipv6addr})) {
my $ip6 = NetAddr::IP->new($iface->{ipv6addr});
my $is_ipv6gw_iface = NetAddr::IP->new($ipv6->{default_gateway}."/128")->within($ip6);
if ($is_ipv6gw_iface) {
$self->debug(3, "Adding the default IPv6 gateway to interface '$name'");
$default_ipv6_rt{destination} = '::/0';
$default_ipv6_rt{'next-hop-address'} = $ipv6->{default_gateway};
$default_ipv6_rt{'next-hop-interface'} = $name;
}
}
}
# combined default route with any policy routing/rule, if any
# combination of default route, plus any additional policy routes.
# read and set by tt module as
Expand All @@ -484,6 +537,7 @@ sub generate_nmstate_config
my $routes = [];
push @$routes, @{$self->make_nm_route_absent($name)};
push @$routes, \%default_rt if scalar %default_rt;
push @$routes, \%default_ipv6_rt if scalar %default_ipv6_rt;
if (defined($iface->{route})) {
$self->verbose("policy route found, nmstate will manage it");
my $route = $iface->{route};
Expand Down
68 changes: 68 additions & 0 deletions ncm-network/src/test/perl/nmstate_ipv6.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use strict;
use warnings;

BEGIN {
*CORE::GLOBAL::sleep = sub {};
}

use Test::More;
use Test::Quattor qw(ipv6);
use Test::MockModule;
use Readonly;

use NCM::Component::nmstate;
my $mock = Test::MockModule->new('NCM::Component::nmstate');
my %executables;
$mock->mock('_is_executable', sub {diag "executables $_[1] ",explain \%executables;return $executables{$_[1]};});

my $cfg = get_config_for_profile('ipv6');
my $cmp = NCM::Component::nmstate->new('network');

Readonly my $ETH0_YML => <<EOF;
# File generated by NCM::Component::nmstate. Do not edit
---
interfaces:
- ipv4:
address:
- ip: 4.3.2.1
prefix-length: 24
dhcp: false
enabled: true
ipv6:
address:
- ip: 2001:678:123:E012:0:0:0:45
prefix-length: 64
- ip: 2001:678:123:E012:0:0:0:46
prefix-length: 64
- ip: 2001:678:123:E012:0:0:0:47
prefix-length: 64
enabled: true
name: eth0
profile-name: eth0
state: up
routes:
config:
- next-hop-interface: eth0
state: absent
- destination: 0.0.0.0/0
next-hop-address: 4.3.2.254
next-hop-interface: eth0
- destination: ::/0
next-hop-address: 2001:678:123:e012::2
next-hop-interface: eth0
EOF

=pod
=head1 DESCRIPTION
Test the C<Configure> method of the component for ipv6 configuration.
=cut

is($cmp->Configure($cfg), 1, "Component runs correctly with a test profile");

my $eth0yml = get_file_contents("/etc/nmstate/eth0.yml");
is($eth0yml, $ETH0_YML, "Exact eth0 route yml config");

done_testing();

0 comments on commit 761169e

Please sign in to comment.