mirror of
https://github.com/webmin/webmin.git
synced 2026-06-27 22:40:26 +01:00
Add dhcpcd network backend for Debian and Raspberry Pi OS
This PR adds dhcpcd backend support for Debian and Raspberry Pi OS network configuration. It detects dhcpcd only as a final fallback after Netplan, NetworkManager, and ifupdown, preventing Webmin from incorrectly falling back to `/etc/network/interfaces` on dhcpcd-managed systems. The new backend reads and writes `/etc/dhcpcd.conf`, including DHCP and static IPv4/IPv6 configuration, gateways, static routes, DNS servers, search domains, MTU, and virtual IPv4 aliases. It also supports implicit DHCP-managed interfaces for default dhcpcd setups with no explicit interface blocks, and handles `allowinterfaces` / `denyinterfaces` behavior. This PR also fixes apply/delete flows for dhcpcd-managed interfaces and virtual aliases, avoids rewriting generated `/etc/resolv.conf`, preserves spacing/comments in touched hosts and nsswitch files, and tightens Active Now handling so virtual aliases are treated as IP addresses rather than independent links. https://github.com/webmin/webmin/issues/1607
This commit is contained in:
@@ -16,9 +16,15 @@ foreach $d (@d) {
|
||||
($a) = grep { $_->{'fullname'} eq $d } @acts;
|
||||
$a || &error($text{'daifcs_egone'});
|
||||
&can_iface($a) || &error($text{'ifcs_ecannot_this'});
|
||||
&deactivate_interface($a);
|
||||
if (defined(&delete_active_interface)) {
|
||||
# Config-driven backends may need to remove persistent state too.
|
||||
$err = &delete_active_interface($a);
|
||||
$err && &error("<pre>$err</pre>");
|
||||
}
|
||||
else {
|
||||
&deactivate_interface($a);
|
||||
}
|
||||
}
|
||||
|
||||
&webmin_log("delete", "aifcs", scalar(@d));
|
||||
&redirect("list_ifcs.cgi?mode=active");
|
||||
|
||||
|
||||
@@ -14,27 +14,40 @@ foreach $d (reverse(@d)) {
|
||||
($b) = grep { $_->{'fullname'} eq $d } @boot;
|
||||
$b || &error($text{'daifcs_egone'});
|
||||
&can_iface($b) || &error($text{'ifcs_ecannot_this'});
|
||||
$act = undef;
|
||||
if ($in{'deleteapply'}) {
|
||||
($act) = grep { $_->{'fullname'} eq $b->{'fullname'} } @active;
|
||||
if (!$act && $b->{'virtual'} ne '' && $b->{'address'}) {
|
||||
# ip(8) may renumber unlabelled secondary addresses.
|
||||
($act) = grep { $_->{'virtual'} ne '' &&
|
||||
$_->{'name'} eq $b->{'name'} &&
|
||||
$_->{'address'} eq $b->{'address'} } @active;
|
||||
}
|
||||
}
|
||||
if ($in{'apply'}) {
|
||||
# Make this interface active
|
||||
&activate_interface($b);
|
||||
if (defined(&apply_interface)) {
|
||||
$err = &apply_interface($b);
|
||||
$err && &error("<pre>$err</pre>");
|
||||
}
|
||||
else {
|
||||
&activate_interface($b);
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Deleting
|
||||
if ($in{'deleteapply'}) {
|
||||
# De-activate first
|
||||
($act) = grep { $_->{'fullname'} eq $b->{'fullname'} }
|
||||
@active;
|
||||
if ($act) {
|
||||
if (defined(&unapply_interface)) {
|
||||
$err = &unapply_interface($act);
|
||||
$err && &error("<pre>$err</pre>");
|
||||
}
|
||||
else {
|
||||
&deactivate_interface($act);
|
||||
if(&iface_type($b->{'name'}) eq 'Bonded'){
|
||||
if (($gconfig{'os_type'} eq 'debian-linux') && ($gconfig{'os_version'} >= 5)) {}
|
||||
else {&unload_module($b->{'name'});}
|
||||
}
|
||||
if ($in{'deleteapply'} && $act &&
|
||||
!defined(&unapply_interface_after_delete)) {
|
||||
# De-activate first for legacy immediate-action backends.
|
||||
if (defined(&unapply_interface)) {
|
||||
$err = &unapply_interface($act);
|
||||
$err && &error("<pre>$err</pre>");
|
||||
}
|
||||
else {
|
||||
&deactivate_interface($act);
|
||||
if(&iface_type($b->{'name'}) eq 'Bonded'){
|
||||
if (($gconfig{'os_type'} eq 'debian-linux') && ($gconfig{'os_version'} >= 5)) {}
|
||||
else {&unload_module($b->{'name'});}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,9 +58,14 @@ foreach $d (reverse(@d)) {
|
||||
defined(&delete_module_def)){
|
||||
&delete_module_def($b->{'name'});
|
||||
}
|
||||
if ($in{'deleteapply'} && $act &&
|
||||
defined(&unapply_interface_after_delete)) {
|
||||
# Config-driven backends apply removals after deleting config.
|
||||
$err = &unapply_interface_after_delete($act, $b);
|
||||
$err && &error("<pre>$err</pre>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&webmin_log($in{'apply'} ? "apply" : "delete", "bifcs", scalar(@d));
|
||||
&redirect("list_ifcs.cgi?mode=boot");
|
||||
|
||||
|
||||
1004
net/dhcpcd-lib.pl
Normal file
1004
net/dhcpcd-lib.pl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -128,16 +128,21 @@ else {
|
||||
print &ui_table_row($text{'ifcs_mtu'}, $mtufield);
|
||||
|
||||
# Current status
|
||||
if (!$access{'up'}) {
|
||||
if (($in{'new'} && $in{'virtual'}) || ($a && $a->{'virtual'} ne "")) {
|
||||
# Virtual aliases are IP addresses, not links with independent status.
|
||||
print &ui_hidden("up", 1);
|
||||
}
|
||||
elsif (!$access{'up'}) {
|
||||
# Cannot edit
|
||||
$upfield = !$a ? $text{'ifcs_up'} :
|
||||
$a->{'up'} ? $text{'ifcs_up'} : $text{'ifcs_down'};
|
||||
print &ui_table_row($text{'ifcs_status'}, $upfield);
|
||||
}
|
||||
else {
|
||||
$upfield = &ui_radio("up", $in{'new'} || $a->{'up'} ? 1 : 0,
|
||||
[ [ 1, $text{'ifcs_up'} ], [ 0, $text{'ifcs_down'} ] ]);
|
||||
print &ui_table_row($text{'ifcs_status'}, $upfield);
|
||||
}
|
||||
print &ui_table_row($text{'ifcs_status'}, $upfield);
|
||||
|
||||
# Hardware address, if non-virtual
|
||||
if ((!$a && $in{'virtual'} eq "") ||
|
||||
@@ -192,4 +197,3 @@ else {
|
||||
print &ui_form_end(\@buts);
|
||||
|
||||
&ui_print_footer("list_ifcs.cgi?mode=active", $text{'ifcs_return'});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ index_apply2=Apply Selected Interfaces
|
||||
index_vmin=Virtualmin
|
||||
index_mode=Network config type: $1
|
||||
index_mode_netplan=Netplan
|
||||
index_mode_dhcpcd=dhcpcd
|
||||
index_mode_cygwin=Cygwin
|
||||
index_mode_freebsd=FreeBSD
|
||||
index_mode_macos=MacOS
|
||||
@@ -79,6 +80,7 @@ aifc_err2=Failed to save interface
|
||||
aifc_evirt=Missing or invalid virtual interface number
|
||||
aifc_evirtmin=Virtual interface number must be at least $1
|
||||
aifc_evirtdup=The virtual interface $1 already exists
|
||||
aifc_evirtdown=Virtual interfaces cannot be created with down status
|
||||
aifc_edup=The interface $1 already exists
|
||||
aifc_ename=Missing or invalid interface name
|
||||
aifc_eip='$1' is not a valid IP address
|
||||
|
||||
@@ -252,6 +252,12 @@ if(($a->{'vlan'} == 1) && !(($gconfig{'os_type'} eq 'debian-linux') && ($gconfig
|
||||
if ($?) { &error($vonconfigout); }
|
||||
}
|
||||
|
||||
if (&has_command("ip") && $a->{'virtual'} ne '' && !$a->{'up'}) {
|
||||
# Linux virtual aliases are addresses, not independent links.
|
||||
&deactivate_interface($old) if ($old && $old->{'address'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!&has_command("ifconfig") && &has_command("ip")) {
|
||||
# For a real interface, activate or de-activate the link
|
||||
if ($a->{'virtual'} eq '' && $a->{'up'} && (!$old || !$old->{'up'})) {
|
||||
@@ -942,7 +948,8 @@ close(SWITCH);
|
||||
&open_tempfile(SWITCH, ">/etc/nsswitch.conf");
|
||||
foreach (@switch) {
|
||||
if (/^\s*hosts:\s+/) {
|
||||
&print_tempfile(SWITCH, "hosts:\t$conf->{'order'}\n");
|
||||
&print_tempfile(SWITCH,
|
||||
&linux_nsswitch_hosts_line($_, $conf->{'order'}));
|
||||
}
|
||||
else {
|
||||
&print_tempfile(SWITCH, $_);
|
||||
@@ -1007,4 +1014,20 @@ else {
|
||||
}
|
||||
}
|
||||
|
||||
# linux_nsswitch_hosts_line(line, order)
|
||||
# Returns an updated nsswitch hosts line preserving existing spacing
|
||||
sub linux_nsswitch_hosts_line
|
||||
{
|
||||
my ($line, $order) = @_;
|
||||
$line =~ s/\r?\n$//;
|
||||
my $comment = "";
|
||||
if ($line =~ s/(\s+#.*)$//) {
|
||||
# Keep inline comments while replacing only the lookup order.
|
||||
$comment = $1;
|
||||
}
|
||||
return $1.$2.$order.$comment."\n"
|
||||
if ($line =~ /^(\s*hosts:)(\s+)\S/);
|
||||
return "hosts:\t$order$comment\n";
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# net-detect.pl
|
||||
# Helper functions for choosing the network config backend
|
||||
|
||||
# net_has_network_manager_config([connections-directory])
|
||||
# Returns true if NetworkManager connection profiles exist
|
||||
sub net_has_network_manager_config
|
||||
{
|
||||
my ($dir) = @_;
|
||||
@@ -9,6 +11,8 @@ my @files = glob("$dir/*.nmconnection");
|
||||
return -d $dir && scalar(@files);
|
||||
}
|
||||
|
||||
# net_has_netplan_config([netplan-directory])
|
||||
# Returns true if Netplan is installed and its config directory exists
|
||||
sub net_has_netplan_config
|
||||
{
|
||||
my ($dir) = @_;
|
||||
@@ -17,15 +21,128 @@ return &has_command("netplan") &&
|
||||
-d $dir;
|
||||
}
|
||||
|
||||
# net_has_ifupdown_config([interfaces-file], [&seen-files])
|
||||
# Returns true if ifupdown has any non-loopback iface stanzas
|
||||
sub net_has_ifupdown_config
|
||||
{
|
||||
my ($file, $done) = @_;
|
||||
$file ||= "/etc/network/interfaces";
|
||||
$done ||= { };
|
||||
|
||||
# Avoid loops from recursive source/source-directory includes.
|
||||
return 0 if ($done->{$file}++);
|
||||
open(my $fh, "<", $file) || return 0;
|
||||
while(my $line = <$fh>) {
|
||||
$line =~ s/#.*$//;
|
||||
|
||||
# Loopback alone does not mean ifupdown owns real interfaces.
|
||||
if ($line =~ /^\s*iface\s+(\S+)\s+\S+\s+\S+/ && $1 ne "lo") {
|
||||
close($fh);
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Debian supports including all simple filenames from a directory.
|
||||
if ($line =~ /^\s*source-directory\s+(\S+)/) {
|
||||
my $dir = $1;
|
||||
if (opendir(my $dh, $dir)) {
|
||||
foreach my $name (grep { /^[A-Za-z0-9_-]+$/ } readdir($dh)) {
|
||||
if (&net_has_ifupdown_config("$dir/$name", $done)) {
|
||||
closedir($dh);
|
||||
close($fh);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
closedir($dh);
|
||||
}
|
||||
}
|
||||
|
||||
# A source directive can point to a glob of extra interface files.
|
||||
elsif ($line =~ /^\s*source\s+(\S+)/) {
|
||||
foreach my $src (glob($1)) {
|
||||
if (&net_has_ifupdown_config($src, $done)) {
|
||||
close($fh);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close($fh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
# net_has_dhcpcd_config([dhcpcd-conf], [service-active])
|
||||
# Returns true if dhcpcd appears to own interface startup config
|
||||
sub net_has_dhcpcd_config
|
||||
{
|
||||
my ($file, $service_active) = @_;
|
||||
$file ||= "/etc/dhcpcd.conf";
|
||||
return 0 if (!-r $file);
|
||||
|
||||
# Explicit interface blocks are enough proof even if the daemon is down.
|
||||
open(my $fh, "<", $file) || return 0;
|
||||
while(my $line = <$fh>) {
|
||||
$line =~ s/#.*$//;
|
||||
if ($line =~ /^\s*interface\s+\S+/) {
|
||||
close($fh);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
close($fh);
|
||||
|
||||
# Default dhcpcd configs often have no interface blocks, so require service
|
||||
# evidence before treating the file as the active startup backend.
|
||||
return defined($service_active) ? $service_active :
|
||||
&net_dhcpcd_service_active();
|
||||
}
|
||||
|
||||
# net_dhcpcd_service_active()
|
||||
# Returns true if the dhcpcd service is active or enabled
|
||||
sub net_dhcpcd_service_active
|
||||
{
|
||||
if (&has_command("systemctl")) {
|
||||
foreach my $unit ("dhcpcd.service", "dhcpcd5.service") {
|
||||
my $q = quotemeta($unit);
|
||||
|
||||
# Active means dhcpcd is managing interfaces right now.
|
||||
my $active = &backquote_command(
|
||||
"systemctl is-active $q 2>/dev/null </dev/null");
|
||||
return 1 if ($active =~ /^active\b/);
|
||||
|
||||
# Enabled/static service units mean dhcpcd will manage them at boot.
|
||||
my $enabled = &backquote_command(
|
||||
"systemctl is-enabled $q 2>/dev/null </dev/null");
|
||||
return 1 if ($enabled =~ /^(enabled|static|indirect|alias)\b/);
|
||||
}
|
||||
}
|
||||
|
||||
# Non-systemd systems and some old dhcpcd packages expose a pid file.
|
||||
return 1 if (-r "/run/dhcpcd.pid" || -r "/var/run/dhcpcd.pid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
# net_auto_backend(os-type, [netplan-dir], [nm-dir], [ifupdown-file], [dhcpcd-conf], [dhcpcd-active])
|
||||
# Returns the auto-detected backend name, or undef for the OS default
|
||||
sub net_auto_backend
|
||||
{
|
||||
my ($os_type, $netplan_dir, $nm_conn_dir) = @_;
|
||||
my ($os_type, $netplan_dir, $nm_conn_dir, $ifupdown_file, $dhcpcd_file,
|
||||
$dhcpcd_service_active) = @_;
|
||||
|
||||
# Netplan is the preferred modern Debian/Ubuntu network config backend.
|
||||
return "netplan"
|
||||
if ($os_type eq "debian-linux" &&
|
||||
&net_has_netplan_config($netplan_dir));
|
||||
|
||||
# NetworkManager is common on desktop/server installs and owns its profiles.
|
||||
return "nm"
|
||||
if (($os_type eq "redhat-linux" || $os_type eq "debian-linux") &&
|
||||
&net_has_network_manager_config($nm_conn_dir));
|
||||
|
||||
# dhcpcd is only a final Debian fallback when ifupdown is not configuring
|
||||
# any real interfaces.
|
||||
return "dhcpcd"
|
||||
if ($os_type eq "debian-linux" &&
|
||||
!&net_has_ifupdown_config($ifupdown_file) &&
|
||||
&net_has_dhcpcd_config($dhcpcd_file, $dhcpcd_service_active));
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ elsif ($auto_net_mode eq "nm") {
|
||||
do 'nm-lib.pl';
|
||||
$net_mode = "nm";
|
||||
}
|
||||
elsif ($auto_net_mode eq "dhcpcd") {
|
||||
# Special case for Debian systems managed by dhcpcd
|
||||
do 'dhcpcd-lib.pl';
|
||||
$net_mode = "dhcpcd";
|
||||
}
|
||||
else {
|
||||
do "$gconfig{'os_type'}-lib.pl";
|
||||
$net_mode = $gconfig{'os_type'};
|
||||
@@ -52,18 +57,34 @@ local $line="";
|
||||
&open_readfile(HOSTS, $config{'hosts_file'});
|
||||
while($line=<HOSTS>) {
|
||||
local $comment = 0;
|
||||
local $comment_prefix = "";
|
||||
local $leading = "";
|
||||
local $inline_comment = "";
|
||||
$line =~ s/\r|\n//g;
|
||||
if ($line =~ s/^\s*#+\s*//) {
|
||||
if ($line =~ s/^(\s*#+\s*)//) {
|
||||
$comment = 1;
|
||||
$comment_prefix = $1;
|
||||
}
|
||||
elsif ($line =~ s/^(\s+)//) {
|
||||
# Preserve indentation if this file uses it for host rows.
|
||||
$leading = $1;
|
||||
}
|
||||
if ($line =~ s/(\s+#.*)$//) {
|
||||
# Keep inline comments attached to edited host rows.
|
||||
$inline_comment = $1;
|
||||
}
|
||||
$line =~ s/#.*$//g;
|
||||
$line =~ s/\s+$//g;
|
||||
local @seps = &host_line_separators($line);
|
||||
local @f = split(/\s+/, $line);
|
||||
local $ipaddr = shift(@f);
|
||||
if (check_ipaddress_any($ipaddr)) {
|
||||
push(@rv, { 'address' => $ipaddr,
|
||||
'hosts' => [ @f ],
|
||||
'active' => !$comment,
|
||||
'comment_prefix' => $comment_prefix,
|
||||
'leading' => $leading,
|
||||
'comment' => $inline_comment,
|
||||
'seps' => \@seps,
|
||||
'line', $lnum,
|
||||
'index', scalar(@rv) });
|
||||
}
|
||||
@@ -73,13 +94,35 @@ close(HOSTS);
|
||||
return @rv;
|
||||
}
|
||||
|
||||
# host_line_separators(line)
|
||||
# Returns the field separators from a parsed /etc/hosts line
|
||||
sub host_line_separators
|
||||
{
|
||||
local ($line) = @_;
|
||||
local @seps;
|
||||
while($line =~ /\S+(\s+)/g) {
|
||||
push(@seps, $1);
|
||||
}
|
||||
return @seps;
|
||||
}
|
||||
|
||||
# make_host_line(&host)
|
||||
# Internal function to return a line for the hosts file
|
||||
sub make_host_line
|
||||
{
|
||||
local ($host) = @_;
|
||||
return ($host->{'active'} ? "" : "# ").
|
||||
$host->{'address'}."\t".join(" ",@{$host->{'hosts'}})."\n";
|
||||
local $prefix = $host->{'active'} ? $host->{'leading'} || "" :
|
||||
$host->{'comment_prefix'} || "# ";
|
||||
local @seps = @{$host->{'seps'} || [ ]};
|
||||
local @hosts = @{$host->{'hosts'} || [ ]};
|
||||
local $line = $prefix.$host->{'address'};
|
||||
for(local $i=0; $i<@hosts; $i++) {
|
||||
# Reuse original spacing by field position, then fall back to defaults.
|
||||
local $sep = $seps[$i] || ($i == 0 ? "\t" : " ");
|
||||
$line .= $sep.$hosts[$i];
|
||||
}
|
||||
$line .= $host->{'comment'} if ($host->{'comment'});
|
||||
return $line."\n";
|
||||
}
|
||||
|
||||
# create_host(&host)
|
||||
@@ -485,4 +528,3 @@ return $a->{'virtual'} eq '' ? -1 :
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
@@ -49,14 +49,14 @@ else {
|
||||
# also creating a virtual interface
|
||||
foreach $ea (@acts) {
|
||||
if ($ea->{'name'} eq $1 &&
|
||||
$ea->{'virtual'} eq $3) {
|
||||
$ea->{'virtual'} eq $4) {
|
||||
&error(&text('aifc_evirtdup', &html_escape($in{'name'})));
|
||||
}
|
||||
}
|
||||
$3 >= $min_virtual_number ||
|
||||
$4 >= $min_virtual_number ||
|
||||
&error(&text('aifc_evirtmin', &html_escape($min_virtual_number)));
|
||||
$a->{'name'} = $1;
|
||||
$a->{'virtual'} = $3;
|
||||
$a->{'virtual'} = $4;
|
||||
$a->{'fullname'} = $a->{'name'}.":".$a->{'virtual'};
|
||||
&can_create_iface() || &error($text{'ifcs_ecannot'});
|
||||
&can_iface($a) || &error($text{'ifcs_ecannot'});
|
||||
@@ -131,7 +131,14 @@ else {
|
||||
}
|
||||
|
||||
# Save active flag
|
||||
if (!$access{'up'}) {
|
||||
if ($a->{'virtual'} ne "") {
|
||||
# Virtual aliases are addresses only, so present means up.
|
||||
if ($access{'up'} && defined($in{'up'}) && !$in{'up'}) {
|
||||
&error($text{'aifc_evirtdown'});
|
||||
}
|
||||
$a->{'up'} = 1;
|
||||
}
|
||||
elsif (!$access{'up'}) {
|
||||
$a->{'up'} = $in{'new'} ? 1 : $olda->{'up'};
|
||||
}
|
||||
elsif ($in{'up'}) {
|
||||
@@ -175,7 +182,8 @@ else {
|
||||
delete($a->{'netmask6'});
|
||||
}
|
||||
|
||||
if (!$in{'ether_def'} && $a->{'virtual'} eq "" &&
|
||||
if (defined($in{'ether'}) && $in{'ether'} ne '' &&
|
||||
!$in{'ether_def'} && $a->{'virtual'} eq "" &&
|
||||
&iface_hardware($a->{'name'})) {
|
||||
$in{'ether'} =~ /^[A-Fa-f0-9:]+$/ ||
|
||||
&error(&text('aifc_ehard', &html_escape($in{'ether'})));
|
||||
@@ -191,4 +199,3 @@ else {
|
||||
"aifc", $a->{'fullname'}, $a);
|
||||
}
|
||||
&redirect("list_ifcs.cgi?mode=active");
|
||||
|
||||
|
||||
@@ -17,7 +17,14 @@ if ($in{'delete'} || $in{'unapply'}) {
|
||||
&error_setup($text{'bifc_err4'});
|
||||
@active = &active_interfaces();
|
||||
($act) = grep { $_->{'fullname'} eq $b->{'fullname'} } @active;
|
||||
if ($act) {
|
||||
if (!$act && $b->{'virtual'} ne '' && $b->{'address'}) {
|
||||
# ip(8) may renumber unlabelled secondary addresses.
|
||||
($act) = grep { $_->{'virtual'} ne '' &&
|
||||
$_->{'name'} eq $b->{'name'} &&
|
||||
$_->{'address'} eq $b->{'address'} } @active;
|
||||
}
|
||||
if ($act && !defined(&unapply_interface_after_delete)) {
|
||||
# Legacy backends remove live state before deleting config.
|
||||
if (defined(&unapply_interface)) {
|
||||
$err = &unapply_interface($act);
|
||||
$err && &error("<pre>$err</pre>");
|
||||
@@ -29,6 +36,12 @@ if ($in{'delete'} || $in{'unapply'}) {
|
||||
|
||||
}
|
||||
&delete_interface($b);
|
||||
if ($in{'unapply'} && $act && defined(&unapply_interface_after_delete)) {
|
||||
# Config-driven backends apply removals after deleting config.
|
||||
&error_setup($text{'bifc_err4'});
|
||||
$err = &unapply_interface_after_delete($act, $b);
|
||||
$err && &error("<pre>$err</pre>");
|
||||
}
|
||||
&webmin_log("delete", "bifc", $b->{'fullname'}, $b);
|
||||
}
|
||||
else {
|
||||
@@ -59,19 +72,19 @@ else {
|
||||
&can_create_iface() || &error($text{'ifcs_ecannot'});
|
||||
&can_iface($b) || &error($text{'ifcs_ecannot'});
|
||||
}
|
||||
elsif ($in{'name'} =~ /^([a-z]+\d*(s\d*)?(\.\d+)?):(\d+)$/ ||
|
||||
$in{'name'} =~ /^(en[0-9a-z]+(s\d*)?(\.\d+)?):(\d+)$/) {
|
||||
elsif ($in{'name'} =~ /^((?:[a-z]+\d*(?:s\d*)?(?:\.\d+)?)|(?:en[0-9a-z]+(?:s\d*)?(?:\.\d+)?)):(\d+)$/) {
|
||||
# also creating a virtual interface
|
||||
local ($vname, $vnum) = ($1, $2);
|
||||
foreach $eb (@boot) {
|
||||
if ($eb->{'name'} eq $2 &&
|
||||
$eb->{'virtual'} eq $4) {
|
||||
if ($eb->{'name'} eq $vname &&
|
||||
$eb->{'virtual'} eq $vnum) {
|
||||
&error(&text('bifc_evirtdup', &html_escape($in{'name'})));
|
||||
}
|
||||
}
|
||||
$4 >= $min_virtual_number ||
|
||||
$vnum >= $min_virtual_number ||
|
||||
&error(&text('aifc_evirtmin', &html_escape($min_virtual_number)));
|
||||
$b->{'name'} = $1;
|
||||
$b->{'virtual'} = $4;
|
||||
$b->{'name'} = $vname;
|
||||
$b->{'virtual'} = $vnum;
|
||||
$b->{'fullname'} = $b->{'name'}.":".$b->{'virtual'};
|
||||
}
|
||||
elsif ($in{'bridge'}) {
|
||||
@@ -376,4 +389,3 @@ else {
|
||||
"bifc", $b->{'fullname'}, $b);
|
||||
}
|
||||
&redirect("list_ifcs.cgi?mode=boot");
|
||||
|
||||
|
||||
@@ -124,6 +124,19 @@ if (&foreign_installed("postfix") && $in{'hostname'} ne $old_hostname) {
|
||||
&postfix::set_current_value("mydestination",
|
||||
join(", ", @mydests));
|
||||
}
|
||||
$old_domain = $old_hostname =~ /^[^\.]+\.(.*)$/ ? $1 :
|
||||
$old_fqdn =~ /^[^\.]+\.(.*)$/ ? $1 : undef;
|
||||
$new_domain = $in{'hostname'} =~ /^[^\.]+\.(.*)$/ ? $1 :
|
||||
$new_fqdn =~ /^[^\.]+\.(.*)$/ ? $1 : undef;
|
||||
if ($old_domain && $new_domain &&
|
||||
lc($old_domain) ne lc($new_domain)) {
|
||||
$idx = &indexoflc("localhost.$old_domain", @mydests);
|
||||
if ($idx >= 0) {
|
||||
$mydests[$idx] = "localhost.$new_domain";
|
||||
&postfix::set_current_value("mydestination",
|
||||
join(", ", @mydests));
|
||||
}
|
||||
}
|
||||
|
||||
# Update postfix myorigin
|
||||
$myorigin = &postfix::get_current_value("myorigin");
|
||||
@@ -147,4 +160,3 @@ if (&foreign_installed("postfix") && $in{'hostname'} ne $old_hostname) {
|
||||
|
||||
&webmin_log("dns", undef, undef, \%in);
|
||||
&redirect("");
|
||||
|
||||
|
||||
@@ -99,8 +99,16 @@ my $detect_netplan = "$detect_root/netplan";
|
||||
my $detect_no_netplan = "$detect_root/no-netplan";
|
||||
my $detect_nm = "$detect_root/NetworkManager/system-connections";
|
||||
my $detect_nm_empty = "$detect_root/NetworkManager-empty/system-connections";
|
||||
my $detect_ifupdown = "$detect_root/interfaces";
|
||||
my $detect_ifupdown_empty = "$detect_root/interfaces-empty";
|
||||
my $detect_ifupdown_dir = "$detect_root/interfaces.d";
|
||||
my $detect_dhcpcd = "$detect_root/dhcpcd.conf";
|
||||
make_path($detect_netplan, $detect_nm, $detect_nm_empty);
|
||||
write_text("$detect_nm/eth0.nmconnection", "");
|
||||
make_path($detect_ifupdown_dir);
|
||||
write_text($detect_ifupdown, "source $detect_ifupdown_dir/*\n");
|
||||
write_text($detect_ifupdown_empty, "# empty\n");
|
||||
write_text($detect_dhcpcd, "# default dhcpcd configuration\nhostname\n");
|
||||
|
||||
is(main::net_auto_backend("debian-linux", $detect_netplan, $detect_nm_empty),
|
||||
"netplan", "Debian uses Netplan when the config directory exists");
|
||||
@@ -112,8 +120,27 @@ write_text("$detect_netplan/50-cloud-init.yaml", "");
|
||||
is(main::net_auto_backend("debian-linux", $detect_netplan, $detect_nm),
|
||||
"netplan", "Debian prefers Netplan over NetworkManager when YAML exists");
|
||||
unlink("$detect_netplan/50-cloud-init.yaml");
|
||||
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty),
|
||||
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty,
|
||||
$detect_ifupdown_empty, $detect_dhcpcd, 0),
|
||||
undef, "Debian falls back when no Netplan or NetworkManager config exists");
|
||||
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty,
|
||||
$detect_ifupdown_empty, $detect_dhcpcd, 0),
|
||||
undef, "Debian does not use inactive default dhcpcd config");
|
||||
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty,
|
||||
$detect_ifupdown_empty, $detect_dhcpcd, 1),
|
||||
"dhcpcd", "Debian uses active dhcpcd service with default config");
|
||||
write_text($detect_dhcpcd, "interface eth0\n");
|
||||
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty,
|
||||
$detect_ifupdown_empty, $detect_dhcpcd, 0),
|
||||
"dhcpcd", "Debian uses dhcpcd only as a final configured backend");
|
||||
write_text($detect_ifupdown_empty, "auto lo\niface lo inet loopback\n");
|
||||
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty,
|
||||
$detect_ifupdown_empty, $detect_dhcpcd),
|
||||
"dhcpcd", "Debian loopback-only ifupdown config does not block dhcpcd");
|
||||
write_text("$detect_ifupdown_dir/eth0", "iface eth0 inet dhcp\n");
|
||||
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty,
|
||||
$detect_ifupdown, $detect_dhcpcd),
|
||||
undef, "Debian does not prefer dhcpcd over ifupdown iface stanzas");
|
||||
|
||||
do "$root/net/netplan-lib.pl" || die "netplan-lib.pl: $@ $!";
|
||||
|
||||
@@ -122,6 +149,15 @@ do "$root/net/netplan-lib.pl" || die "netplan-lib.pl: $@ $!";
|
||||
$main::netplan_dir = $tmp;
|
||||
}
|
||||
|
||||
is(main::linux_nsswitch_hosts_line("hosts: files dns\n",
|
||||
"files dns"),
|
||||
"hosts: files dns\n",
|
||||
"Linux DNS save preserves nsswitch hosts spacing");
|
||||
is(main::linux_nsswitch_hosts_line("hosts:\tfiles dns # local policy\n",
|
||||
"files mdns4 dns"),
|
||||
"hosts:\tfiles mdns4 dns # local policy\n",
|
||||
"Linux DNS save preserves nsswitch hosts comments");
|
||||
|
||||
my $netplan = "$tmp/50-cloud-init.yaml";
|
||||
write_text($netplan, <<'YAML');
|
||||
network:
|
||||
@@ -232,4 +268,348 @@ main::save_interface($nmiface, [ $nmiface ]);
|
||||
like(join("\n", @commands), qr/ipv6\.dns/,
|
||||
"NetworkManager save_interface writes IPv6 nameservers");
|
||||
|
||||
do "$root/net/dhcpcd-lib.pl" || die "dhcpcd-lib.pl: $@ $!";
|
||||
my $dhcpcd = "$tmp/dhcpcd.conf";
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
# global option
|
||||
hostname
|
||||
|
||||
interface eth0
|
||||
static ip_address=192.168.1.10/24
|
||||
static routers=192.168.1.1
|
||||
static routes=10.10.0.0/16 192.168.1.254 10.20.30.0/24 192.168.1.253
|
||||
static domain_name_servers=1.1.1.1 2001:4860:4860::8888
|
||||
static domain_search=example.com
|
||||
static ip6_address=2001:db8::10/64
|
||||
mtu 1400
|
||||
noipv6rs
|
||||
|
||||
interface wlan0
|
||||
# DHCP by default
|
||||
DHCPCD
|
||||
|
||||
{
|
||||
no warnings 'once';
|
||||
$main::dhcpcd_config = $dhcpcd;
|
||||
$main::dhcpcd_synthesize_implicit = 0;
|
||||
}
|
||||
|
||||
@boot = main::boot_interfaces();
|
||||
is(scalar(grep { $_->{'virtual'} eq '' } @boot), 2,
|
||||
"dhcpcd parses two real interfaces");
|
||||
my ($dh0) = grep { $_->{'fullname'} eq "eth0" } @boot;
|
||||
is($dh0->{'address'}, "192.168.1.10", "dhcpcd parses static IPv4");
|
||||
is($dh0->{'netmask'}, "255.255.255.0", "dhcpcd parses IPv4 prefix");
|
||||
is($dh0->{'gateway'}, "192.168.1.1", "dhcpcd parses router");
|
||||
is_deeply($dh0->{'routes'},
|
||||
[ "10.10.0.0/16,192.168.1.254",
|
||||
"10.20.30.0/24,192.168.1.253" ],
|
||||
"dhcpcd parses static routes");
|
||||
is_deeply($dh0->{'nameserver'},
|
||||
[ "1.1.1.1", "2001:4860:4860::8888" ],
|
||||
"dhcpcd parses nameservers");
|
||||
is_deeply($dh0->{'address6'}, [ "2001:db8::10" ],
|
||||
"dhcpcd parses static IPv6");
|
||||
my ($wlan0) = grep { $_->{'fullname'} eq "wlan0" } @boot;
|
||||
ok($wlan0->{'dhcp'}, "dhcpcd treats no static address as DHCP");
|
||||
|
||||
$dh0->{'address'} = "192.168.1.20";
|
||||
$dh0->{'gateway'} = "192.168.1.254";
|
||||
$dh0->{'routes'} = [ "172.16.0.0/12,192.168.1.253" ];
|
||||
$dh0->{'nameserver'} = [ "9.9.9.9" ];
|
||||
$dh0->{'search'} = [ "example.net" ];
|
||||
main::save_interface($dh0, \@boot);
|
||||
$saved = read_text($dhcpcd);
|
||||
like($saved, qr/interface eth0\nnoipv6rs\nstatic ip_address=192\.168\.1\.20\/24\nstatic routers=192\.168\.1\.254\nstatic routes=172\.16\.0\.0\/12 192\.168\.1\.253\nstatic domain_name_servers=9\.9\.9\.9\nstatic domain_search=example\.net\nstatic ip6_address=2001:db8::10\/64\nmtu 1400/s,
|
||||
"dhcpcd save_interface rewrites managed values and preserves extras");
|
||||
like($saved, qr/interface wlan0\n# DHCP by default/s,
|
||||
"dhcpcd save_interface preserves sibling DHCP block");
|
||||
|
||||
my ($dhwlan0) = grep { $_->{'fullname'} eq "wlan0" } main::boot_interfaces();
|
||||
push(@boot, { 'name' => 'wlan0',
|
||||
'fullname' => 'wlan0:0',
|
||||
'virtual' => 0,
|
||||
'address' => '192.168.2.10',
|
||||
'netmask' => '255.255.255.0' });
|
||||
main::save_interface($dhwlan0, \@boot);
|
||||
$saved = read_text($dhcpcd);
|
||||
like($saved, qr/interface wlan0\nstatic ip_address=192\.168\.2\.10\/24/s,
|
||||
"dhcpcd writes virtual static address for DHCP parent interface");
|
||||
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
interface enp0s5
|
||||
static ip_address=10.211.55.20/24
|
||||
static domain_name_servers=1.1.1.1 8.8.8.8
|
||||
DHCPCD
|
||||
my ($dhneed, $dhgenerated) = main::os_save_dns_config(
|
||||
{ 'nameserver' => [ "1.1.1.1", "8.8.1.1" ],
|
||||
'domain' => [ "example.test" ] });
|
||||
ok($dhneed, "dhcpcd DNS save requests dhcpcd apply");
|
||||
ok($dhgenerated, "dhcpcd DNS save reports resolv.conf as generated");
|
||||
$saved = read_text($dhcpcd);
|
||||
like($saved, qr/static domain_name_servers=1\.1\.1\.1 8\.8\.1\.1\nstatic domain_search=example\.test/,
|
||||
"dhcpcd DNS save updates interface DNS settings");
|
||||
|
||||
@commands = ( );
|
||||
($dhneed, $dhgenerated) = main::os_save_dns_config(
|
||||
{ 'nameserver' => [ "1.1.1.1", "8.8.1.1" ],
|
||||
'domain' => [ "example.test" ] });
|
||||
ok(!$dhneed, "dhcpcd DNS save skips apply when unchanged");
|
||||
ok($dhgenerated, "dhcpcd unchanged DNS still suppresses resolv.conf rewrite");
|
||||
is_deeply(\@commands, [ ], "dhcpcd unchanged DNS does not rewrite config");
|
||||
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
# default dhcpcd configuration
|
||||
hostname
|
||||
DHCPCD
|
||||
{
|
||||
no warnings 'once';
|
||||
$main::dhcpcd_synthesize_implicit = 1;
|
||||
$main::dhcpcd_test_active_interfaces = [
|
||||
{ 'name' => 'lo', 'fullname' => 'lo', 'virtual' => '',
|
||||
'up' => 1 },
|
||||
{ 'name' => 'enp0s5', 'fullname' => 'enp0s5',
|
||||
'virtual' => '', 'up' => 1 },
|
||||
{ 'name' => 'wlan0', 'fullname' => 'wlan0',
|
||||
'virtual' => '', 'up' => 1 },
|
||||
];
|
||||
}
|
||||
@boot = main::boot_interfaces();
|
||||
is_deeply([ map { $_->{'fullname'} } @boot ], [ "enp0s5", "wlan0" ],
|
||||
"dhcpcd synthesizes implicit DHCP boot interfaces");
|
||||
ok((grep { $_->{'fullname'} eq "enp0s5" && $_->{'dhcp'} &&
|
||||
$_->{'implicit'} } @boot),
|
||||
"dhcpcd marks synthesized interface as implicit DHCP");
|
||||
|
||||
my ($implicit) = grep { $_->{'fullname'} eq "enp0s5" } @boot;
|
||||
main::delete_interface($implicit);
|
||||
$saved = read_text($dhcpcd);
|
||||
like($saved, qr/^denyinterfaces enp0s5\n# default dhcpcd configuration/m,
|
||||
"dhcpcd delete of implicit interface writes denyinterfaces");
|
||||
|
||||
@boot = main::boot_interfaces();
|
||||
is_deeply([ map { $_->{'fullname'} } @boot ], [ "wlan0" ],
|
||||
"dhcpcd does not synthesize denied interfaces");
|
||||
|
||||
my $newif = { 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5',
|
||||
'virtual' => '',
|
||||
'dhcp' => 1,
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ] };
|
||||
main::save_interface($newif, \@boot);
|
||||
$saved = read_text($dhcpcd);
|
||||
unlike($saved, qr/^denyinterfaces/m,
|
||||
"dhcpcd save removes denyinterfaces for managed interface");
|
||||
like($saved, qr/interface enp0s5\n(?:\n|$)/,
|
||||
"dhcpcd save creates explicit DHCP interface block");
|
||||
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
denyinterfaces eth0 wlan0
|
||||
interface eth0
|
||||
static ip_address=192.168.1.30/24
|
||||
DHCPCD
|
||||
{
|
||||
no warnings 'once';
|
||||
$main::dhcpcd_synthesize_implicit = 0;
|
||||
}
|
||||
@boot = main::boot_interfaces();
|
||||
my ($denied_eth0) = grep { $_->{'fullname'} eq "eth0" } @boot;
|
||||
$denied_eth0->{'address'} = "192.168.1.31";
|
||||
main::save_interface($denied_eth0, \@boot);
|
||||
$saved = read_text($dhcpcd);
|
||||
like($saved, qr/^denyinterfaces wlan0\ninterface eth0\nstatic ip_address=192\.168\.1\.31\/24/m,
|
||||
"dhcpcd save removes one denyinterfaces word without shifting block replacement");
|
||||
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
denyinterfaces enp0s6 wlan0
|
||||
interface enp0s6
|
||||
static ip_address=10.211.55.21/24
|
||||
DHCPCD
|
||||
@boot = main::boot_interfaces();
|
||||
my ($removed_enp0s6) = grep { $_->{'fullname'} eq "enp0s6" } @boot;
|
||||
main::delete_interface($removed_enp0s6);
|
||||
$saved = read_text($dhcpcd);
|
||||
like($saved, qr/^denyinterfaces wlan0\n/m,
|
||||
"dhcpcd delete of explicit interface removes stale denyinterfaces word");
|
||||
unlike($saved, qr/^interface enp0s6/m,
|
||||
"dhcpcd delete of explicit interface removes its block entirely");
|
||||
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
allowinterfaces eth0
|
||||
interface eth0
|
||||
DHCPCD
|
||||
@boot = main::boot_interfaces();
|
||||
my $allowed_new = { 'name' => 'wlan0',
|
||||
'fullname' => 'wlan0',
|
||||
'virtual' => '',
|
||||
'dhcp' => 1,
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ] };
|
||||
main::save_interface($allowed_new, \@boot);
|
||||
$saved = read_text($dhcpcd);
|
||||
like($saved, qr/^allowinterfaces eth0 wlan0\ninterface eth0\n\ninterface wlan0/m,
|
||||
"dhcpcd save adds new explicit interface to existing allowinterfaces");
|
||||
|
||||
{
|
||||
no warnings 'once';
|
||||
$main::dhcpcd_test_active_interfaces = [
|
||||
{ 'name' => 'enp0s5', 'fullname' => 'enp0s5',
|
||||
'virtual' => '', 'up' => 1 },
|
||||
];
|
||||
}
|
||||
@commands = ( );
|
||||
like(main::apply_interface({ 'name' => 'enp0s6',
|
||||
'fullname' => 'enp0s6',
|
||||
'virtual' => '' }),
|
||||
qr/Cannot find device "enp0s6"/,
|
||||
"dhcpcd apply reports missing real device");
|
||||
is_deeply(\@commands, [ ], "dhcpcd apply skips restart for missing device");
|
||||
|
||||
@commands = ( );
|
||||
is(main::apply_interface({ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5',
|
||||
'virtual' => '' }),
|
||||
undef, "dhcpcd apply restarts service for existing device");
|
||||
is_deeply(\@commands, [ "/etc/init.d/dhcpcd restart 2>&1 </dev/null" ],
|
||||
"dhcpcd apply runs restart command for existing device");
|
||||
|
||||
@commands = ( );
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
interface enp0s5
|
||||
static ip_address=10.211.55.20/24
|
||||
DHCPCD
|
||||
{
|
||||
no warnings 'redefine';
|
||||
no warnings 'once';
|
||||
$main::dhcpcd_synthesize_implicit = 0;
|
||||
$main::dhcpcd_test_active_interfaces = [
|
||||
{ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5',
|
||||
'virtual' => '',
|
||||
'address' => '10.211.55.20',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 1 },
|
||||
{ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5:1',
|
||||
'virtual' => 1,
|
||||
'address' => '10.211.55.21',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 1 },
|
||||
];
|
||||
local *main::has_command = sub {
|
||||
return $_[0] eq "ip" ? "/sbin/ip" : undef;
|
||||
};
|
||||
is(main::apply_network(), undef,
|
||||
"dhcpcd global apply removes virtual alias missing from config");
|
||||
}
|
||||
is_deeply(\@commands,
|
||||
[ "ip addr del 10\\.211\\.55\\.21\\/24 dev enp0s5 2>&1",
|
||||
"/etc/init.d/dhcpcd restart 2>&1 </dev/null" ],
|
||||
"dhcpcd global apply removes live virtual address before restart");
|
||||
|
||||
@commands = ( );
|
||||
write_text($dhcpcd, <<'DHCPCD');
|
||||
interface enp0s5
|
||||
static ip_address=10.211.55.20/24
|
||||
static ip_address=10.211.55.23/24
|
||||
DHCPCD
|
||||
{
|
||||
no warnings 'redefine';
|
||||
no warnings 'once';
|
||||
$main::dhcpcd_synthesize_implicit = 0;
|
||||
$main::dhcpcd_test_active_interfaces = [
|
||||
{ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5',
|
||||
'virtual' => '',
|
||||
'address' => '10.211.55.20',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 1 },
|
||||
{ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5:0',
|
||||
'virtual' => 0,
|
||||
'address' => '10.211.55.24',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 1 },
|
||||
{ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5:1',
|
||||
'virtual' => 1,
|
||||
'address' => '10.211.55.23',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 1 },
|
||||
];
|
||||
local *main::has_command = sub {
|
||||
return $_[0] eq "ip" ? "/sbin/ip" : undef;
|
||||
};
|
||||
is(main::delete_active_interface($main::dhcpcd_test_active_interfaces->[2]),
|
||||
undef,
|
||||
"dhcpcd active delete removes matching boot virtual alias");
|
||||
}
|
||||
$saved = read_text($dhcpcd);
|
||||
unlike($saved, qr/static ip_address=10\.211\.55\.23\/24/,
|
||||
"dhcpcd active delete removes alias from config");
|
||||
is_deeply(\@commands,
|
||||
[ "ip addr del 10\\.211\\.55\\.23\\/24 dev enp0s5 2>&1" ],
|
||||
"dhcpcd active delete drops only the selected live virtual alias");
|
||||
|
||||
@commands = ( );
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *main::has_command = sub {
|
||||
return $_[0] eq "ip" ? "/sbin/ip" : undef;
|
||||
};
|
||||
local *main::active_interfaces = sub {
|
||||
return ( );
|
||||
};
|
||||
main::activate_interface({ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5:1',
|
||||
'virtual' => 1,
|
||||
'address' => '10.211.55.25',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 0 });
|
||||
}
|
||||
is_deeply(\@commands, [ ],
|
||||
"Linux active virtual interface stays absent when created down");
|
||||
|
||||
@commands = ( );
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *main::has_command = sub {
|
||||
return $_[0] eq "ip" ? "/sbin/ip" : undef;
|
||||
};
|
||||
local *main::active_interfaces = sub {
|
||||
return ({ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5:1',
|
||||
'virtual' => 1,
|
||||
'address' => '10.211.55.25',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 1 });
|
||||
};
|
||||
main::activate_interface({ 'name' => 'enp0s5',
|
||||
'fullname' => 'enp0s5:1',
|
||||
'virtual' => 1,
|
||||
'address' => '10.211.55.25',
|
||||
'netmask' => '255.255.255.0',
|
||||
'address6' => [ ],
|
||||
'netmask6' => [ ],
|
||||
'up' => 0 });
|
||||
}
|
||||
is_deeply(\@commands,
|
||||
[ "ip addr del 10\\.211\\.55\\.25\\/24 dev enp0s5 2>&1" ],
|
||||
"Linux active virtual interface is removed when saved down");
|
||||
|
||||
done_testing();
|
||||
|
||||
Reference in New Issue
Block a user