mirror of
https://github.com/webmin/webmin.git
synced 2026-07-25 10:50:31 +01:00
ⓘ Create and detect VLAN devices before applying live configuration, parse live VLANs with their physical parent instead of a virtual alias, normalize persisted dotted VLAN names when the parent device exists, consistently target the real VLAN device across the ip, ifup and vconfig paths, skip recreating existing devices or activating absent disabled ones, and add regression coverage. https://github.com/webmin/webmin/pull/2785
1122 lines
32 KiB
Perl
Executable File
1122 lines
32 KiB
Perl
Executable File
# linux-lib.pl
|
|
# Active interface functions for all flavours of linux
|
|
|
|
# active_interfaces([include-no-ipv4-address])
|
|
# Returns a list of currently ifconfig'd interfaces
|
|
sub active_interfaces
|
|
{
|
|
local ($empty) = @_;
|
|
my @rv;
|
|
my $ethtool = &has_command("ethtool");
|
|
|
|
if (&has_command("ip")) {
|
|
# Get status from new ip command
|
|
&clean_language();
|
|
&open_execute_command(IFC, "ip addr", 1, 1);
|
|
my @lines;
|
|
while(<IFC>) {
|
|
s/\r|\n//g;
|
|
if (/^\S+/) { push(@lines, $_); }
|
|
else { $lines[$#lines] .= $_; }
|
|
}
|
|
close(IFC);
|
|
|
|
&reset_environment();
|
|
foreach my $l (@lines) {
|
|
my %ifc;
|
|
my $iface_name = $ifc{'name'};
|
|
my $iface_auto_ip;
|
|
if ($l =~ /^\d+:\s+([^ \t\r\n\@]+\.(\d+))@([^ \t\r\n\@]+):/) {
|
|
# Line like :
|
|
# 3: eth0.99@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
|
$ifc{'fullname'} = $1;
|
|
$ifc{'name'} = $ifc{'fullname'};
|
|
$ifc{'vlanid'} = $2;
|
|
$ifc{'physical'} = $3;
|
|
$ifc{'vlan'} = 1;
|
|
$ifc{'virtual'} = '';
|
|
}
|
|
elsif ($l =~ /^\d+:\s+([^ \t\r\n\@]+):/) {
|
|
# Line like :
|
|
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
|
|
$ifc{'fullname'} = $1;
|
|
$ifc{'name'} = $ifc{'fullname'}
|
|
}
|
|
elsif ($l =~ /^\d+:\s+([^ \t\r\n\@]+\d+)@([^ \t\r\n\@]+\d+):/) {
|
|
# Line like :
|
|
# 3: eth0@if0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
|
$ifc{'fullname'} = $1;
|
|
$ifc{'name'} = $ifc{'fullname'};
|
|
$ifc{'ifname'} = $2;
|
|
}
|
|
else {
|
|
# Unknown line!
|
|
next;
|
|
}
|
|
if ($l =~ /\sinet\s+([0-9\.]+)\s+peer\s+([0-9\.]+)\/(\d+)(\s+brd\s+([0-9\.]+))?\s+scope\s+global(\s+(noprefixroute|dynamic))*\s+(\Q$iface_name\E)/) {
|
|
# Line like :
|
|
# inet 193.9.101.120 peer 193.9.101.104/32 brd 193.9.101.120 scope global eth0
|
|
$ifc{'address'} = $1;
|
|
$ifc{'netmask'} = &prefix_to_mask("$3");
|
|
}
|
|
elsif ($l =~ /\sinet\s+([0-9\.]+)\/(\d+)\s+(?:metric\s+\d+\s+|)brd\s+(\S+)\s+scope\s+global(?:\s+secondary|)\s(dynamic)\s+.*?(\Q$iface_name\E)/) {
|
|
# Line like :
|
|
# inet 193.9.101.120/24 brd 193.9.101.255 scope global secondary dynamic br0
|
|
# inet 10.211.55.81/24 metric 100 brd 10.211.55.255 scope global secondary dynamic enp0s5
|
|
# inet 10.211.55.65/24 brd 10.211.55.255 scope global secondary dynamic noprefixroute enp0s5
|
|
# inet 10.211.55.10/24 brd 10.211.55.255 scope global dynamic enp0s5
|
|
$iface_auto_ip = $1;
|
|
$ifc{'address'} = $1;
|
|
$ifc{'netmask'} = &prefix_to_mask("$2");
|
|
}
|
|
elsif ($l =~ /\sinet\s+([0-9\.]+)\/(\d+)(\s+metric\s+\d+)?(\s+brd\s+(\S+))?\s+scope\s+global(\s+(noprefixroute|secondary))*\s+(\Q$iface_name\E)/) {
|
|
# Line like :
|
|
# inet 193.9.101.120/24 brd 193.9.101.255 scope global br0
|
|
$ifc{'address'} = $1;
|
|
$ifc{'netmask'} = &prefix_to_mask("$2");
|
|
}
|
|
elsif ($l =~ /\sinet\s+([0-9\.]+)\s+peer\s+([0-9\.]+)\/(\d+)/) {
|
|
# Line like :
|
|
# inet 46.4.13.87 peer 46.4.13.65/32
|
|
$ifc{'address'} = $1;
|
|
$ifc{'netmask'} = &prefix_to_mask("$3");
|
|
}
|
|
elsif ($l =~ /\sinet\s+([0-9\.]+)\/(\d+)(\s+brd\s+(\S+))?\s+scope\s+host\s+(\Q$iface_name\E)/) {
|
|
# Line like :
|
|
# inet 127.0.0.1/8 scope host lo
|
|
$ifc{'address'} = $1;
|
|
$ifc{'netmask'} = &prefix_to_mask("$2");
|
|
}
|
|
if ($l =~ /\sbrd\s+([0-9\.]+)/) {
|
|
$ifc{'broadcast'} = $1;
|
|
}
|
|
if ($l =~ /\smtu\s+(\d+)/) {
|
|
$ifc{'mtu'} = $1;
|
|
}
|
|
if ($l =~ /ether\s+([0-9a-f:]+)/i) {
|
|
$ifc{'ether'} = $1;
|
|
}
|
|
|
|
my (@address6, @netmask6, @scope6);
|
|
while($l =~ s/inet6\s+(\S+)\/(\d+)\s+scope\s+(\S+)//i) {
|
|
local ($address6, $netmask6, $scope6) = ($1, $2, $3);
|
|
push(@address6, $address6);
|
|
push(@netmask6, $netmask6);
|
|
push(@scope6, $scope6);
|
|
}
|
|
$ifc{'address6'} = \@address6;
|
|
$ifc{'netmask6'} = \@netmask6;
|
|
$ifc{'scope6'} = \@scope6;
|
|
|
|
$ifc{'up'}++ if ($l =~ /\sUP\s|<\S*UP\S*>/);
|
|
$ifc{'promisc'}++ if ($l =~ /\sPROMISC\s/);
|
|
$ifc{'edit'} = ($ifc{'name'} !~ /^ppp/);
|
|
$ifc{'index'} = scalar(@rv);
|
|
push(@rv, \%ifc);
|
|
|
|
# Strip off the line for the primary IP, but only if there is one
|
|
if ($ifc{'address'}) {
|
|
$l =~ s/\sinet\s+([0-9\.]+)\s+peer//;
|
|
if ($l =~ /\sinet\s+([0-9\.]+)\/(\d+)/) {
|
|
# If primary IP equals dynamic or no
|
|
# dynamic IP is found stirp it off
|
|
if (!$iface_auto_ip || $iface_auto_ip eq $1) {
|
|
$l =~ s/\sinet\s+([0-9\.]+)\/(\d+)//;
|
|
}
|
|
# Just strip off dynamic from the list
|
|
elsif ($iface_auto_ip) {
|
|
$l =~ s/\sinet\s+(\Q$iface_auto_ip\E)\/(\d+)//;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Add extra IPs as fake virtual interfaces, from lines like
|
|
my $i = 0;
|
|
my $bn = $ifc{'name'};
|
|
while($l =~ s/\sinet\s+([0-9\.]+)\/(\d+).*?\s\Q$bn\E:(\d+)// ||
|
|
$l =~ s/\sinet\s+([0-9\.]+)\/(\d+).*?(?:secondary|dynamic|global).*?\s\Q$bn\E//) {
|
|
my %vifc;
|
|
my $vn = defined($3) ? $3 : $i;
|
|
$vifc{'name'} = $ifc{'name'};
|
|
$vifc{'fullname'} = $ifc{'name'}.":".$vn;
|
|
$vifc{'address'} = $1;
|
|
$vifc{'netmask'} = &prefix_to_mask("$2");
|
|
$vifc{'broadcast'} = &compute_broadcast(
|
|
$vifc{'address'}, $vifc{'netmask'});
|
|
$vifc{'mtu'} = $ifc{'mtu'};
|
|
$vifc{'up'} = $ifc{'up'};
|
|
$vifc{'virtual'} = $vn;
|
|
$vifc{'edit'} = ($vifc{'name'} !~ /^ppp/);
|
|
$vifc{'index'} = scalar(@rv);
|
|
push(@rv, \%vifc);
|
|
$i = $vn + 1;
|
|
}
|
|
}
|
|
}
|
|
elsif (&has_command("ifconfig")) {
|
|
&clean_language();
|
|
&open_execute_command(IFC, "ifconfig -a", 1, 1);
|
|
my @lines;
|
|
while(<IFC>) {
|
|
s/\r|\n//g;
|
|
if (/^\S+/) { push(@lines, $_); }
|
|
else { $lines[$#lines] .= $_; }
|
|
}
|
|
close(IFC);
|
|
&reset_environment();
|
|
foreach my $l (@lines) {
|
|
my %ifc;
|
|
$l =~ /^([^:\s]+)/ || next;
|
|
$ifc{'name'} = $1;
|
|
$l =~ /^(\S+)/;
|
|
$ifc{'fullname'} = $1;
|
|
$ifc{'fullname'} =~ s/:$//;
|
|
if ($l =~ /^(\S+):(\d+)/) { $ifc{'virtual'} = $2; }
|
|
if ($l =~ /^(\S+)\.(\d+)/) { $ifc{'vlanid'} = $2; }
|
|
if ($l =~ /inet addr:(\S+)/) { $ifc{'address'} = $1; }
|
|
elsif ($l =~ /inet (\S+)/) { $ifc{'address'} = $1; }
|
|
elsif (!$empty) { next; }
|
|
if ($l =~ /Mask:(\S+)/) { $ifc{'netmask'} = $1; }
|
|
elsif ($l =~ /netmask (\S+)/) { $ifc{'netmask'} = $1; }
|
|
if ($l =~ /Bcast:(\S+)/) { $ifc{'broadcast'} = $1; }
|
|
elsif ($l =~ /broadcast (\S+)/) { $ifc{'broadcast'} = $1; }
|
|
if ($l =~ /HWaddr (\S+)/) { $ifc{'ether'} = $1; }
|
|
elsif ($l =~ /ether (\S+)/) { $ifc{'ether'} = $1; }
|
|
if ($l =~ /MTU:(\d+)/) { $ifc{'mtu'} = $1; }
|
|
elsif ($l =~ /mtu (\d+)/) { $ifc{'mtu'} = $1; }
|
|
if ($l =~ /P-t-P:(\S+)/) { $ifc{'ptp'} = $1; }
|
|
elsif ($l =~ /ptp (\S+)/) { $ifc{'ptp'} = $1; }
|
|
$ifc{'up'}++ if ($l =~ /\sUP\s|<\S*UP\S*>/);
|
|
$ifc{'promisc'}++ if ($l =~ /\sPROMISC\s/);
|
|
|
|
my (@address6, @netmask6, @scope6);
|
|
while($l =~ s/inet6 addr:\s*(\S+)\/(\d+)\s+Scope:(Global)//i) {
|
|
local ($address6, $netmask6, $scope6) = ($1, $2, $3);
|
|
push(@address6, $address6);
|
|
push(@netmask6, $netmask6);
|
|
push(@scope6, $scope6);
|
|
}
|
|
while($l =~ s/inet6 (\S+)\s+prefixlen (\d+)\s+scopeid\s+(\S+)<global>//i) {
|
|
local ($address6, $netmask6, $scope6) = ($1, $2, $3);
|
|
push(@address6, $address6);
|
|
push(@netmask6, $netmask6);
|
|
push(@scope6, $scope6);
|
|
}
|
|
$ifc{'address6'} = \@address6;
|
|
$ifc{'netmask6'} = \@netmask6;
|
|
$ifc{'scope6'} = \@scope6;
|
|
|
|
$ifc{'edit'} = ($ifc{'name'} !~ /^ppp/);
|
|
$ifc{'index'} = scalar(@rv);
|
|
push(@rv, \%ifc);
|
|
}
|
|
}
|
|
|
|
else {
|
|
&error("Both the ifconfig and ip commands are missing");
|
|
}
|
|
|
|
foreach my $ifc (@rv) {
|
|
# For each ethernet interface, merge in data from ethtool
|
|
if (&iface_type($ifc->{'fullname'}) eq 'Ethernet' &&
|
|
$ifc->{'virtual'} eq '' && $ethtool) {
|
|
my $out = &backquote_command(
|
|
"$ethtool $ifc->{'fullname'} 2>/dev/null");
|
|
if ($out =~ /Speed:\s+(\S+)/i && $1 ne "Unknown!") {
|
|
$ifc->{'speed'} = $1;
|
|
}
|
|
if ($out =~ /Duplex:\s+(\S+)/i && $1 ne "Unknown!") {
|
|
$ifc->{'duplex'} = $1;
|
|
}
|
|
if ($out =~ /Link\s+detected:\s+(\S+)/i) {
|
|
$ifc->{'link'} = lc($1) eq 'yes' ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return @rv;
|
|
}
|
|
|
|
# activate_interface(&details)
|
|
# Create or modify an interface
|
|
sub activate_interface
|
|
{
|
|
my ($a) = @_;
|
|
|
|
my @active = &active_interfaces(1);
|
|
|
|
# Some boot-time backends preserve only the conventional dotted VLAN name.
|
|
# Only infer a VLAN when the parent device really exists, as a dotted name
|
|
# alone can belong to an interface of any type.
|
|
if (!$a->{'vlan'} &&
|
|
(!defined($a->{'virtual'}) || $a->{'virtual'} eq '') &&
|
|
$a->{'fullname'} =~ /^(.+)\.(\d+)$/) {
|
|
my ($physical, $vlanid) = ($1, $2);
|
|
if (grep { $_->{'fullname'} eq $physical } @active) {
|
|
$a = { %$a,
|
|
'vlan' => 1,
|
|
'physical' => $physical,
|
|
'vlanid' => $vlanid,
|
|
'virtual' => '' };
|
|
}
|
|
}
|
|
my ($old) = grep { $_->{'fullname'} eq $a->{'fullname'} } @active;
|
|
my $devname = $a->{'vlan'} ? $a->{'physical'}.".".$a->{'vlanid'}
|
|
: $a->{'name'};
|
|
|
|
# A disabled VLAN that does not exist has no live state to update.
|
|
return if ($a->{'vlan'} && !$a->{'up'} && !$old);
|
|
|
|
# Create a missing VLAN device before assigning addresses to it.
|
|
if ($a->{'vlan'} && $a->{'up'} && !$old) {
|
|
if (&has_command("ip")) {
|
|
my $cmd = "ip link add link ".quotemeta($a->{'physical'}).
|
|
" name ".quotemeta($devname).
|
|
" type vlan id ".quotemeta($a->{'vlanid'});
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&error("Failed to create VLAN device : $out") if ($?);
|
|
}
|
|
# For Debian 5.0+ VLAN creation is handled by ifup.
|
|
elsif (!(($gconfig{'os_type'} eq 'debian-linux') &&
|
|
($gconfig{'os_version'} >= 5))) {
|
|
my $cmd = "vconfig add ".quotemeta($a->{'physical'})." ".
|
|
quotemeta($a->{'vlanid'});
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&error($out) if ($?);
|
|
}
|
|
}
|
|
|
|
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("ip") && $a->{'bond'} && $a->{'up'} && !$old) {
|
|
# Create the bond before assigning addresses to it.
|
|
my $bcmd = "ip link add ".quotemeta($a->{'name'})." type bond";
|
|
if (defined($a->{'mode'}) && $a->{'mode'} ne '') {
|
|
$bcmd .= " mode ".quotemeta(&bond_mode_name($a->{'mode'}));
|
|
}
|
|
if ($a->{'miimon'}) {
|
|
$bcmd .= " miimon ".quotemeta($a->{'miimon'});
|
|
}
|
|
if ($a->{'updelay'}) {
|
|
$bcmd .= " updelay ".quotemeta($a->{'updelay'});
|
|
}
|
|
if ($a->{'downdelay'}) {
|
|
$bcmd .= " downdelay ".quotemeta($a->{'downdelay'});
|
|
}
|
|
my $out = &backquote_logged("$bcmd 2>&1");
|
|
&error("Failed to create bond device : $out") if ($?);
|
|
foreach my $slave (grep { $_ ne '' } split(/\s+/, $a->{'partner'})) {
|
|
$bcmd = "ip link set dev ".quotemeta($slave)." down";
|
|
$out = &backquote_logged("$bcmd 2>&1");
|
|
&error("Failed to bring down bond slave : $out") if ($?);
|
|
$bcmd = "ip link set dev ".quotemeta($slave)." master ".
|
|
quotemeta($a->{'name'});
|
|
$out = &backquote_logged("$bcmd 2>&1");
|
|
&error("Failed to add bond slave : $out") if ($?);
|
|
$bcmd = "ip link set dev ".quotemeta($slave)." up";
|
|
$out = &backquote_logged("$bcmd 2>&1");
|
|
&error("Failed to bring up bond slave : $out") if ($?);
|
|
}
|
|
if ($a->{'primary'}) {
|
|
$bcmd = "ip link set dev ".quotemeta($a->{'name'}).
|
|
" type bond primary ".quotemeta($a->{'primary'});
|
|
$out = &backquote_logged("$bcmd 2>&1");
|
|
&error("Failed to set bond primary interface : $out") if ($?);
|
|
}
|
|
}
|
|
|
|
if (($a->{'bond'} || $a->{'vlan'} || !&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'})) {
|
|
# Bring up
|
|
my $cmd = "ip link set dev ".quotemeta($devname)." up";
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&error("Failed to bring up link : $out") if ($?);
|
|
}
|
|
elsif ($a->{'virtual'} eq '' && !$a->{'up'} && $old && $old->{'up'}) {
|
|
# Take down
|
|
my $cmd = "ip link set dev ".quotemeta($devname)." down";
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&error("Failed to bring down link : $out") if ($?);
|
|
}
|
|
}
|
|
|
|
my $cmd;
|
|
if (&has_command("ip")) {
|
|
# If the IP is changing, first remove it then re-add
|
|
my $readd = 0;
|
|
if ($old && $old->{'address'}) {
|
|
if ($old->{'address'} ne $a->{'address'} ||
|
|
$old->{'netmask'} ne $a->{'netmask'}) {
|
|
my $rcmd = "ip addr del ".quotemeta($old->{'address'}).
|
|
"/".&mask_to_prefix($old->{'netmask'}).
|
|
" dev ".quotemeta($devname);
|
|
&system_logged("$rcmd >/dev/null 2>&1");
|
|
$readd = 1;
|
|
}
|
|
}
|
|
else {
|
|
$readd = 1;
|
|
}
|
|
|
|
# Build ip command to add the new IP
|
|
if ($readd && $a->{'address'}) {
|
|
$cmd .= "ip addr add ".quotemeta($a->{'address'});
|
|
if ($a->{'netmask'}) {
|
|
$cmd .= "/".&mask_to_prefix($a->{'netmask'});
|
|
}
|
|
if ($a->{'broadcast'}) {
|
|
$cmd .= " broadcast ".quotemeta($a->{'broadcast'});
|
|
}
|
|
$cmd .= " dev ".quotemeta($devname);
|
|
}
|
|
}
|
|
elsif (&use_ifup_command($a)) {
|
|
# Use Debian / Redhat ifup command
|
|
if($a->{'vlan'} == 1) {
|
|
# Name and fullname for VLAN tagged interfaces are "auto" so
|
|
# we need to ifup using physical and vlanid.
|
|
if ($a->{'up'}) {
|
|
if(($a->{'mtu'}) && (($gconfig{'os_type'} eq 'redhat-linux') && ($gconfig{'os_version'} >= 13))) {
|
|
my $cmd2 = "ifconfig ".quotemeta($a->{'physical'})." mtu ".quotemeta($a->{'mtu'});
|
|
my $out = &backquote_logged("$cmd2 2>&1");
|
|
if ($?) { &error($out); }
|
|
}
|
|
$cmd .= "ifup ".quotemeta($a->{'physical'}).".".
|
|
quotemeta($a->{'vlanid'});
|
|
}
|
|
else {
|
|
$cmd .= "ifdown ".quotemeta($a->{'physical'}).".".
|
|
quotemeta($a->{'vlanid'});
|
|
}
|
|
}
|
|
elsif ($a->{'up'}) {
|
|
$cmd .= "ifdown ".quotemeta($a->{'fullname'}).
|
|
"; ifup ".quotemeta($a->{'fullname'});
|
|
}
|
|
else {
|
|
$cmd .= "ifdown ".quotemeta($a->{'fullname'});
|
|
}
|
|
}
|
|
elsif (&has_command("ifconfig")) {
|
|
# Build ifconfig command manually
|
|
if($a->{'vlan'} == 1) {
|
|
$cmd .= "ifconfig ".quotemeta($a->{'physical'}).".".
|
|
quotemeta($a->{'vlanid'});
|
|
}
|
|
else {
|
|
$cmd .= "ifconfig ".quotemeta($a->{'name'});
|
|
if ($a->{'virtual'} ne "") {
|
|
$cmd .= ":".quotemeta($a->{'virtual'});
|
|
}
|
|
}
|
|
$cmd .= " ".quotemeta($a->{'address'});
|
|
if ($a->{'netmask'}) {
|
|
$cmd .= " netmask ".quotemeta($a->{'netmask'});
|
|
}
|
|
if ($a->{'broadcast'}) {
|
|
$cmd .= " broadcast ".quotemeta($a->{'broadcast'});
|
|
}
|
|
if ($a->{'mtu'} && $a->{'virtual'} eq "") {
|
|
$cmd .= " mtu ".quotemeta($a->{'mtu'});
|
|
}
|
|
if ($a->{'up'}) {
|
|
$cmd .= " up";
|
|
}
|
|
else {
|
|
$cmd .= " down";
|
|
}
|
|
}
|
|
else {
|
|
&error("Both the ifconfig and ip commands are missing");
|
|
}
|
|
my $out;
|
|
if ($cmd) {
|
|
$out = &backquote_logged("cd / ; $cmd 2>&1");
|
|
if ($?) { &error("$cmd failed : $out"); }
|
|
}
|
|
|
|
# Apply settings directly when a VLAN was activated using ip, even on
|
|
# systems where its boot-time configuration would normally use ifup.
|
|
my $apply_link_settings = !&use_ifup_command($a) ||
|
|
($a->{'vlan'} && &has_command("ip") &&
|
|
($a->{'up'} || $old));
|
|
|
|
# Apply ethernet address
|
|
if ($a->{'ether'} && $apply_link_settings && &has_command("ifconfig")) {
|
|
# With ifconfig command
|
|
$out = &backquote_logged("ifconfig ".quotemeta($devname).
|
|
" hw ether ".quotemeta($a->{'ether'})." 2>&1");
|
|
if ($?) { &error($out); }
|
|
}
|
|
elsif ($a->{'ether'} && $apply_link_settings && &has_command("ip")) {
|
|
# With ip link command
|
|
$out = &backquote_logged("ip link set dev ".quotemeta($devname).
|
|
" address ".quotemeta($a->{'ether'})." 2>&1");
|
|
if ($?) { &error($out); }
|
|
}
|
|
|
|
# Apply MTU
|
|
if ($a->{'mtu'} && $apply_link_settings && &has_command("ip")) {
|
|
$out = &backquote_logged(
|
|
"ip link set dev ".quotemeta($devname)." mtu ".
|
|
quotemeta($a->{'mtu'})." 2>&1");
|
|
if ($?) { &error($out); }
|
|
}
|
|
|
|
if ($a->{'virtual'} eq '' && &has_command("ifconfig")) {
|
|
# Remove old IPv6 addresses
|
|
local $l = &backquote_command("ifconfig ".quotemeta($devname));
|
|
while($l =~ s/inet6 addr:\s*(\S+)\/(\d+)\s+Scope:(\S+)// ||
|
|
$l =~ s/inet6\s+(\S+)\s+prefixlen\s+(\d+)\s+scopeid\s+\S+//) {
|
|
my $cmd = "ifconfig ".quotemeta($devname)." inet6 del ".
|
|
quotemeta("$1/$2")." 2>&1";
|
|
$out = &backquote_logged($cmd);
|
|
&error("Failed to remove old IPv6 address : $out") if ($?);
|
|
}
|
|
|
|
# Add IPv6 addresses
|
|
for(my $i=0; $i<@{$a->{'address6'}}; $i++) {
|
|
my $cmd = "ifconfig ".quotemeta($devname)." inet6 add ".
|
|
quotemeta($a->{'address6'}->[$i])."/".
|
|
quotemeta($a->{'netmask6'}->[$i])." 2>&1";
|
|
$out = &backquote_logged($cmd);
|
|
&error("Failed to add IPv6 address with $cmd : $out") if ($?);
|
|
}
|
|
}
|
|
elsif ($a->{'virtual'} eq '' && &has_command("ip")) {
|
|
# Remove old IPv6 addresses
|
|
if ($old) {
|
|
for(my $i=0; $i<@{$old->{'address6'}}; $i++) {
|
|
my $cmd = "ip -6 addr del ".
|
|
quotemeta($old->{'address6'}->[$i])."/".
|
|
quotemeta($old->{'netmask6'}->[$i])." dev ".
|
|
quotemeta($devname);
|
|
$out = &backquote_logged("$cmd 2>&1");
|
|
&error("Failed to remove old IPv6 address : $out") if ($?);
|
|
}
|
|
}
|
|
|
|
# Add IPv6 addresses
|
|
for(my $i=0; $i<@{$a->{'address6'}}; $i++) {
|
|
my $cmd = "ip -6 addr add ".
|
|
quotemeta($a->{'address6'}->[$i])."/".
|
|
quotemeta($a->{'netmask6'}->[$i])." dev ".
|
|
quotemeta($devname);
|
|
$out = &backquote_logged("$cmd 2>&1");
|
|
&error("Failed to add IPv6 address with $cmd : $out") if ($?);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# deactivate_interface(&details)
|
|
# Shutdown some active interface
|
|
sub deactivate_interface
|
|
{
|
|
my ($a) = @_;
|
|
if (&has_command("ip")) {
|
|
# Use new ip command to remove all IPs
|
|
my @del;
|
|
if ($a->{'address'}) {
|
|
push(@del, $a->{'address'}."/".
|
|
&mask_to_prefix($a->{'netmask'}));
|
|
}
|
|
for(my $i=0; $i<@{$a->{'address6'}}; $i++) {
|
|
push(@del, $a->{'address6'}->[$i]."/".
|
|
$a->{'netmask6'}->[$i]);
|
|
}
|
|
foreach my $d (@del) {
|
|
my $cmd = "ip addr del ".quotemeta($d)." dev ".
|
|
quotemeta($a->{'name'});
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&error("Failed to remove old address : $out") if ($?);
|
|
}
|
|
|
|
if ($a->{'virtual'} eq '') {
|
|
my $cmd = "ip link set dev ".quotemeta($a->{'name'})." down";
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&error("<pre>".&html_escape($out)."</pre>") if ($?);
|
|
}
|
|
}
|
|
elsif (&has_command("ifconfig")) {
|
|
# Use old ifconfig command
|
|
my $name = $a->{'name'}.
|
|
($a->{'virtual'} ne "" ? ":$a->{'virtual'}" : "");
|
|
my $address = $a->{'address'}.
|
|
($a->{'virtual'} ne "" ? ":$a->{'virtual'}" : "");
|
|
my $netmask = $a->{'netmask'};
|
|
|
|
if ($a->{'virtual'} ne "") {
|
|
# Shutdown virtual interface by setting address to 0
|
|
&system_logged("ifconfig $name 0 >/dev/null 2>&1");
|
|
}
|
|
# Delete all v6 addresses
|
|
for(my $i=0; $i<@{$a->{'address6'}}; $i++) {
|
|
my $cmd = "ifconfig ".quotemeta($a->{'name'})." inet6 del ".
|
|
quotemeta($a->{'address6'}->[$i])."/".
|
|
quotemeta($a->{'netmask6'}->[$i]);
|
|
&backquote_logged("$cmd 2>&1");
|
|
}
|
|
|
|
# Check if still up somehow
|
|
my ($still) = grep { $_->{'fullname'} eq $name } &active_interfaces();
|
|
if ($still) {
|
|
# Old version of ifconfig or non-virtual interface.. down it
|
|
my $out;
|
|
if (&use_ifup_command($a)) {
|
|
$out = &backquote_logged(
|
|
"ifdown ".quotemeta($name)." 2>&1");
|
|
}
|
|
else {
|
|
$out = &backquote_logged(
|
|
"ifconfig ".quotemeta($name)." down 2>&1");
|
|
}
|
|
my ($still) = grep { $_->{'fullname'} eq $name }
|
|
&active_interfaces();
|
|
if ($still && $still->{'up'}) {
|
|
&error($out ? "<pre>".&html_escape($out)."</pre>"
|
|
: "Interface is still active even after ".
|
|
"being shut down");
|
|
}
|
|
if (&iface_type($name) =~ /^(.*) (VLAN)$/) {
|
|
$out = &backquote_logged(
|
|
"vconfig rem ".quotemeta($name)." 2>&1");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
&error("Both the ifconfig and ip commands are missing");
|
|
}
|
|
}
|
|
|
|
# use_ifup_command(&iface)
|
|
# Returns 1 if the ifup command must be used to bring up some interface.
|
|
# True on Debian 5.0+ for non-ethernet, typically bonding and VLAN tagged interfaces.
|
|
sub use_ifup_command
|
|
{
|
|
local ($iface) = @_;
|
|
return &has_command("ifup") &&
|
|
($gconfig{'os_type'} eq 'debian-linux' &&
|
|
$gconfig{'os_version'} >= 5 ||
|
|
$gconfig{'os_type'} eq 'redhat-linux' &&
|
|
$gconfig{'os_version'} >= 13) &&
|
|
($iface->{'name'} !~ /^(eth|em|eno|ens|enp|enx|enX|lo|br)/ ||
|
|
$iface->{'name'} =~ /^(\S+)\.(\d+)/) &&
|
|
$iface->{'virtual'} eq '';
|
|
}
|
|
|
|
# iface_type(name)
|
|
# Returns a human-readable interface type name
|
|
sub iface_type
|
|
{
|
|
my ($name) = @_;
|
|
if ($name =~ /^(.*)\.(\d+)$/) {
|
|
return &iface_type("$1")." VLAN";
|
|
}
|
|
return "PPP" if ($name =~ /^ppp/);
|
|
return "SLIP" if ($name =~ /^sl/);
|
|
return "PLIP" if ($name =~ /^plip/);
|
|
return "Ethernet" if ($name =~ /^eth|em|eno|ens|enp|enx|enX|p\d+p\d+|vtnet/);
|
|
return "Wireless Ethernet" if ($name =~ /^(wlan|ath)/);
|
|
return "Arcnet" if ($name =~ /^arc/);
|
|
return "Token Ring" if ($name =~ /^tr/);
|
|
return "Pocket/ATP" if ($name =~ /^atp/);
|
|
return "Loopback" if ($name =~ /^lo/);
|
|
return "ISDN rawIP" if ($name =~ /^isdn/);
|
|
return "ISDN syncPPP" if ($name =~ /^ippp/);
|
|
return "CIPE" if ($name =~ /^cip/);
|
|
return "VmWare" if ($name =~ /^vmnet/);
|
|
return "Wireless" if ($name =~ /^wlan/);
|
|
return "Bonded" if ($name =~ /^bond/);
|
|
return "OpenVZ" if ($name =~ /^venet/);
|
|
return "Bridge" if ($name =~ /^(br|xenbr|virbr)/);
|
|
return $text{'ifcs_unknown'};
|
|
}
|
|
|
|
# list_routes()
|
|
# Returns a list of active routes
|
|
sub list_routes
|
|
{
|
|
local @rv;
|
|
if (&has_command("netstat")) {
|
|
# Use old netstat command if installed
|
|
&open_execute_command(ROUTES, "netstat -rn", 1, 1);
|
|
while(<ROUTES>) {
|
|
s/\s+$//;
|
|
if (/^([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+\S+\s+\S+\s+\S+\s*\S+\s+(\S+)$/) {
|
|
push(@rv, { 'dest' => $1,
|
|
'gateway' => $2,
|
|
'netmask' => $3,
|
|
'iface' => $4 });
|
|
}
|
|
}
|
|
close(ROUTES);
|
|
&open_execute_command(ROUTES, "netstat -rn -A inet6", 1, 1);
|
|
while(<ROUTES>) {
|
|
s/\s+$//;
|
|
if (/^([0-9a-z:]+)\/([0-9]+)\s+([0-9a-z:]+)\s+\S+\s+\S+\s+\S+\s*\S+\s+(\S+)$/) {
|
|
push(@rv, { 'dest' => $1,
|
|
'gateway' => $3,
|
|
'netmask' => $2,
|
|
'iface' => $4 });
|
|
}
|
|
}
|
|
close(ROUTES);
|
|
}
|
|
elsif (&has_command("ip")) {
|
|
# Use new ip command
|
|
&open_execute_command(ROUTES, "ip route", 1, 1);
|
|
while(<ROUTES>) {
|
|
s/\r|\n//g;
|
|
if (/^(\S+)\s+/) {
|
|
my $r = { 'dest' => $1 };
|
|
if ($r->{'dest'} =~ /^(\S+)\/(\d+)/) {
|
|
$r->{'dest'} = $1;
|
|
$r->{'netmask'} = &prefix_to_mask("$2");
|
|
}
|
|
if (/\sdev\s+(\S+)/) {
|
|
$r->{'iface'} = $1;
|
|
}
|
|
if (/\svia\s+(\S+)/) {
|
|
$r->{'gateway'} = $1;
|
|
}
|
|
push(@rv, $r);
|
|
}
|
|
}
|
|
close(ROUTES);
|
|
}
|
|
else {
|
|
&error("Both the ip and netstat commands are missing!");
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# load_module(&details)
|
|
# Load or modify a loaded module
|
|
sub load_module
|
|
{
|
|
local $a = $_[0];
|
|
local $cmd = "modprobe bonding";
|
|
|
|
if ($a->{'mode'}) {
|
|
$cmd .= " mode=".quotemeta($a->{'mode'});
|
|
}
|
|
if ($a->{'miimon'}) {
|
|
$cmd .= " miimon=".quotemeta($a->{'miimon'});
|
|
}
|
|
if ($a->{'downdelay'}) {
|
|
$cmd .= " downdelay=".quotemeta($a->{'downdelay'});
|
|
}
|
|
if ($a->{'updelay'}) {
|
|
$cmd .= " updelay=".quotemeta($a->{'updelay'});
|
|
}
|
|
|
|
local $out = &backquote_logged("$cmd 2>&1");
|
|
&error($out) if ($?);
|
|
}
|
|
|
|
# bond_mode_name(mode)
|
|
# Convert Webmin's numeric bonding mode to the name expected by ip(8).
|
|
sub bond_mode_name
|
|
{
|
|
my ($mode) = @_;
|
|
my @modes = ("balance-rr", "active-backup", "balance-xor", "broadcast",
|
|
"802.3ad", "balance-tlb", "balance-alb");
|
|
if ($mode =~ /^\d+$/ && defined($modes[$mode])) {
|
|
return $modes[$mode];
|
|
}
|
|
return $mode eq "activebackup" ? "active-backup" : $mode;
|
|
}
|
|
|
|
# Tries to unload the module
|
|
# unload_module(name)
|
|
sub unload_module
|
|
{
|
|
my ($name) = @_;
|
|
my $cmd = "modprobe -r bonding";
|
|
local $out = &backquote_logged("$cmd 2>&1");
|
|
&error($out) if ($?);
|
|
}
|
|
|
|
# list_interfaces()
|
|
# Returns a list of active interface names
|
|
sub list_interfaces
|
|
{
|
|
my @ret;
|
|
$cmd = "ifconfig -a";
|
|
local $out = &backquote_logged("$cmd 2>&1");
|
|
&error($out) if ($?);
|
|
|
|
my @lines = split("\n", $out);
|
|
foreach my $line (@lines) {
|
|
$line =~ /^([\w|.]*)/m;
|
|
if ($1) {
|
|
push(@ret, $1);
|
|
}
|
|
}
|
|
return @ret;
|
|
}
|
|
|
|
# delete_route(&route)
|
|
# Delete one active route, as returned by list_routes. Returns an error message
|
|
# on failure, or undef on success
|
|
sub delete_route
|
|
{
|
|
my ($route) = @_;
|
|
my $cmd;
|
|
my $proto = &check_ip6address($route->{'dest'}) ||
|
|
$route->{'dest'} eq '::' ? 6 : 4;
|
|
if (&has_command("route")) {
|
|
# Use old route command
|
|
$cmd = "route ".
|
|
($proto == 6 ? "-A inet6" : "-A inet")." del ";
|
|
if (!$route->{'dest'} || $route->{'dest'} eq '0.0.0.0' ||
|
|
$route->{'dest'} eq '::') {
|
|
$cmd .= " default";
|
|
}
|
|
elsif ($route->{'netmask'} eq '255.255.255.255') {
|
|
$cmd .= " -host ".quotemeta($route->{'dest'});
|
|
}
|
|
elsif (!&check_ip6address($route->{'dest'})) {
|
|
$cmd .= " -net ".quotemeta($route->{'dest'});
|
|
if ($route->{'netmask'} && $route->{'netmask'} ne '0.0.0.0') {
|
|
$cmd .= " netmask ".quotemeta($route->{'netmask'});
|
|
}
|
|
}
|
|
else {
|
|
$cmd .= quotemeta("$route->{'dest'}/$route->{'netmask'}");
|
|
}
|
|
if ($route->{'gateway'}) {
|
|
$cmd .= " gw ".quotemeta($route->{'gateway'});
|
|
}
|
|
elsif ($route->{'iface'}) {
|
|
$cmd .= " dev ".quotemeta($route->{'iface'});
|
|
}
|
|
}
|
|
elsif (&has_command("ip")) {
|
|
# Use new ip command
|
|
$cmd = "ip -".$proto." route delete";
|
|
if (!$route->{'dest'} || $route->{'dest'} eq '0.0.0.0' ||
|
|
$route->{'dest'} eq '::') {
|
|
$cmd .= " default";
|
|
}
|
|
else {
|
|
$cmd .= " ".quotemeta($route->{'dest'});
|
|
if ($route->{'netmask'} && $route->{'netmask'} ne '0.0.0.0' &&
|
|
$route->{'netmask'} != 32) {
|
|
if ($route->{'netmask'} =~ /^\d+$/) {
|
|
$cmd .= "/".quotemeta($route->{'netmask'});
|
|
}
|
|
else {
|
|
$cmd .= "/".&mask_to_prefix($route->{'netmask'});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return "Missing the route and ip commands";
|
|
}
|
|
my $out = &backquote_logged("$cmd 2>&1 </dev/null");
|
|
return $? ? $out : undef;
|
|
}
|
|
|
|
# create_route(&route)
|
|
# Adds a new active route
|
|
sub create_route
|
|
{
|
|
local ($route) = @_;
|
|
my $cmd;
|
|
my $proto = &check_ip6address($route->{'dest'}) ||
|
|
&check_ip6address($route->{'gateway'}) ? 6 : 4;
|
|
if (&has_command("route")) {
|
|
# Use old route command
|
|
$cmd = "route ".($proto == 6 ? "-A inet6" : "-A inet")." add";
|
|
if (!$route->{'dest'} || $route->{'dest'} eq '0.0.0.0' ||
|
|
$route->{'dest'} eq '::') {
|
|
$cmd .= " default";
|
|
}
|
|
elsif ($route->{'netmask'} eq '255.255.255.255') {
|
|
$cmd .= " -host ".quotemeta($route->{'dest'});
|
|
}
|
|
elsif (!&check_ip6address($route->{'dest'})) {
|
|
$cmd .= " -net ".quotemeta($route->{'dest'});
|
|
if ($route->{'netmask'} && $route->{'netmask'} ne '0.0.0.0') {
|
|
$cmd .= " netmask ".quotemeta($route->{'netmask'});
|
|
}
|
|
}
|
|
else {
|
|
$cmd .= quotemeta("$route->{'dest'}/$route->{'netmask'}");
|
|
}
|
|
if ($route->{'gateway'}) {
|
|
$cmd .= " gw ".quotemeta($route->{'gateway'});
|
|
}
|
|
elsif ($route->{'iface'}) {
|
|
$cmd .= " dev ".quotemeta($route->{'iface'});
|
|
}
|
|
}
|
|
elsif (&has_command("ip")) {
|
|
# Use new ip command
|
|
$cmd = "ip -".$proto." route add";
|
|
if (!$route->{'dest'} || $route->{'dest'} eq '0.0.0.0' ||
|
|
$route->{'dest'} eq '::') {
|
|
$cmd .= " default";
|
|
}
|
|
else {
|
|
$cmd .= " ".quotemeta($route->{'dest'});
|
|
if ($route->{'netmask'} && $route->{'netmask'} ne '0.0.0.0' &&
|
|
$route->{'netmask'} != 32) {
|
|
if ($route->{'netmask'} =~ /^\d+$/) {
|
|
$cmd .= "/".quotemeta($route->{'netmask'});
|
|
}
|
|
else {
|
|
$cmd .= "/".&mask_to_prefix($route->{'netmask'});
|
|
}
|
|
}
|
|
}
|
|
if ($route->{'gateway'}) {
|
|
$cmd .= " via ".quotemeta($route->{'gateway'});
|
|
}
|
|
if ($route->{'iface'}) {
|
|
$cmd .= " dev ".quotemeta($route->{'iface'});
|
|
}
|
|
}
|
|
else {
|
|
return "Missing the route and ip commands";
|
|
}
|
|
my $out = &backquote_logged("$cmd 2>&1 </dev/null");
|
|
return $? ? $out : undef;
|
|
}
|
|
|
|
# iface_hardware(name)
|
|
# Does some interface have an editable hardware address
|
|
sub iface_hardware
|
|
{
|
|
return $_[0] =~ /^(eth|em|eno|ens|enp|enx|enX)/;
|
|
}
|
|
|
|
# allow_interface_clash()
|
|
# Returns 0 to indicate that two virtual interfaces with the same IP
|
|
# are not allowed
|
|
sub allow_interface_clash
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
# get_dns_config()
|
|
# Returns a hashtable containing keys nameserver, domain, search & order
|
|
# XXX factor out to os_ functions
|
|
sub get_dns_config
|
|
{
|
|
local $dns = { };
|
|
local $rc;
|
|
local $dnsfile;
|
|
if ($use_suse_dns && ($rc = &parse_rc_config()) && $rc->{'NAMESERVER'}) {
|
|
# Special case - get DNS settings from SuSE config
|
|
local @ns = split(/\s+/, $rc->{'NAMESERVER'}->{'value'});
|
|
$dns->{'nameserver'} = [ grep { $_ ne "YAST_ASK" } @ns ];
|
|
local $src = $rc->{'SEARCHLIST'};
|
|
$dns->{'domain'} = [ split(/\s+/, $src->{'value'}) ] if ($src);
|
|
$dnsfile = $rc_config;
|
|
}
|
|
elsif ($gconfig{'os_type'} eq 'debian-linux' && -l "/etc/resolv.conf" &&
|
|
$netplan_dir) {
|
|
# On Ubuntu 18+, /etc/resolv.conf is auto-generated from netplan config
|
|
my @boot = &boot_interfaces();
|
|
foreach my $b (@boot) {
|
|
if ($b->{'nameserver'}) {
|
|
$dns->{'nameserver'} = $b->{'nameserver'};
|
|
$dns->{'domain'} = $b->{'search'};
|
|
$dnsfile = $b->{'file'};
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
elsif ($gconfig{'os_type'} eq 'debian-linux' && -l "/etc/resolv.conf") {
|
|
# On Ubuntu 12+, /etc/resolv.conf is auto-generated from network
|
|
# interface config
|
|
my @ifaces = &get_interface_defs();
|
|
foreach my $i (@ifaces) {
|
|
local ($ns) = grep { $_->[0] eq 'dns-nameservers' } @{$i->[3]};
|
|
local @dom = grep { $_->[0] eq 'dns-domain' ||
|
|
$_->[0] eq 'dns-search' } @{$i->[3]};
|
|
if ($ns) {
|
|
$dns->{'nameserver'} = [ split(/\s+/, $ns->[1]) ];
|
|
if (@dom) {
|
|
$dns->{'domain'} =
|
|
[ map { split(/\s+/, $_->[1]) } @dom ];
|
|
}
|
|
$dnsfile = "/etc/network/interfaces";
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
if (!$dnsfile) {
|
|
# Just read resolv.conf
|
|
&open_readfile(RESOLV, "/etc/resolv.conf");
|
|
while(<RESOLV>) {
|
|
s/\r|\n//g;
|
|
s/#.*$//;
|
|
s/;.*$//;
|
|
if (/nameserver\s+(.*)/) {
|
|
push(@{$dns->{'nameserver'}}, split(/\s+/, $1));
|
|
}
|
|
elsif (/domain\s+(\S+)/) {
|
|
$dns->{'domain'} = [ $1 ];
|
|
}
|
|
elsif (/search\s+(.*)/) {
|
|
$dns->{'domain'} = [ split(/\s+/, $1) ];
|
|
}
|
|
}
|
|
close(RESOLV);
|
|
$dnsfile = "/etc/resolv.conf";
|
|
}
|
|
&open_readfile(SWITCH, "/etc/nsswitch.conf");
|
|
while(<SWITCH>) {
|
|
s/\r|\n//g;
|
|
if (/^\s*hosts:\s+(.*)/) {
|
|
$dns->{'order'} = $1;
|
|
}
|
|
}
|
|
close(SWITCH);
|
|
$dns->{'files'} = [ $dnsfile, "/etc/nsswitch.conf" ];
|
|
return $dns;
|
|
}
|
|
|
|
# save_dns_config(&config)
|
|
# Writes out the resolv.conf and nsswitch.conf files
|
|
sub save_dns_config
|
|
{
|
|
local ($conf) = @_;
|
|
local ($need_apply, $generated_resolv) = (0, 0);
|
|
|
|
# Call OS-specific function to update resolvers elsewhere
|
|
if (defined(&os_save_dns_config)) {
|
|
($need_apply, $generated_resolv) = &os_save_dns_config($conf);
|
|
}
|
|
|
|
if (!$generated_resolv) {
|
|
# Update standard resolv.conf file
|
|
&lock_file("/etc/resolv.conf");
|
|
&open_readfile(RESOLV, "/etc/resolv.conf");
|
|
local @resolv = <RESOLV>;
|
|
close(RESOLV);
|
|
&open_tempfile(RESOLV, ">/etc/resolv.conf");
|
|
foreach (@{$conf->{'nameserver'}}) {
|
|
&print_tempfile(RESOLV, "nameserver $_\n");
|
|
}
|
|
if ($conf->{'domain'}) {
|
|
if ($conf->{'domain'}->[1]) {
|
|
&print_tempfile(RESOLV,
|
|
"search ",join(" ", @{$conf->{'domain'}}),"\n");
|
|
}
|
|
else {
|
|
&print_tempfile(RESOLV,
|
|
"domain $conf->{'domain'}->[0]\n");
|
|
}
|
|
}
|
|
foreach (@resolv) {
|
|
&print_tempfile(RESOLV, $_)
|
|
if (!/^\s*(nameserver|domain|search)\s+/);
|
|
}
|
|
&close_tempfile(RESOLV);
|
|
&unlock_file("/etc/resolv.conf");
|
|
}
|
|
|
|
# Update resolution order in nsswitch.conf
|
|
&lock_file("/etc/nsswitch.conf");
|
|
&open_readfile(SWITCH, "/etc/nsswitch.conf");
|
|
local @switch = <SWITCH>;
|
|
close(SWITCH);
|
|
&open_tempfile(SWITCH, ">/etc/nsswitch.conf");
|
|
foreach (@switch) {
|
|
if (/^\s*hosts:\s+/) {
|
|
&print_tempfile(SWITCH,
|
|
&linux_nsswitch_hosts_line($_, $conf->{'order'}));
|
|
}
|
|
else {
|
|
&print_tempfile(SWITCH, $_);
|
|
}
|
|
}
|
|
&close_tempfile(SWITCH);
|
|
&unlock_file("/etc/nsswitch.conf");
|
|
|
|
# Update SuSE config file for resolution order
|
|
if ($use_suse_dns) {
|
|
&lock_file($rc_config);
|
|
local $rc = &parse_rc_config();
|
|
if ($rc->{'USE_NIS_FOR_RESOLVING'}) {
|
|
if ($conf->{'order'} =~ /nis/) {
|
|
&save_rc_config($rc, "USE_NIS_FOR_RESOLVING", "yes");
|
|
}
|
|
else {
|
|
&save_rc_config($rc, "USE_NIS_FOR_RESOLVING", "no");
|
|
}
|
|
}
|
|
&unlock_file($rc_config);
|
|
}
|
|
|
|
# Update resolv.conf from network interfaces config
|
|
if ($need_apply) {
|
|
my $err = &apply_network();
|
|
&error("<pre>".&html_escape($err)."</pre>") if ($err);
|
|
}
|
|
}
|
|
|
|
$max_dns_servers = 3;
|
|
|
|
# order_input(&dns)
|
|
# Returns HTML for selecting the name resolution order
|
|
sub order_input
|
|
{
|
|
my @o = split(/\s+/, $_[0]->{'order'});
|
|
@o = map { s/nis\+/nisplus/; s/yp/nis/; $_; } @o;
|
|
my @opts = ( [ "files", "Hosts file" ], [ "dns", "DNS" ], [ "nis", "NIS" ],
|
|
[ "nisplus", "NIS+" ], [ "ldap", "LDAP" ], [ "db", "DB" ],
|
|
[ "mdns4", "Multicast DNS" ], [ "myhostname", "Local hostname" ] );
|
|
if (&indexof("mdns4_minimal", @o) >= 0) {
|
|
push(@opts, [ "mdns4_minimal", "Multicast DNS (minimal)" ]);
|
|
}
|
|
return &common_order_input("order", join(" ", @o), \@opts);
|
|
}
|
|
|
|
# parse_order(&dns)
|
|
# Parses the form created by order_input()
|
|
sub parse_order
|
|
{
|
|
if (defined($in{'order'})) {
|
|
$in{'order'} =~ /\S/ || &error($text{'dns_eorder'});
|
|
$_[0]->{'order'} = $in{'order'};
|
|
}
|
|
else {
|
|
local($i, @order);
|
|
for($i=0; defined($in{"order_$i"}); $i++) {
|
|
push(@order, $in{"order_$i"}) if ($in{"order_$i"});
|
|
}
|
|
$_[0]->{'order'} = join(" ", @order);
|
|
}
|
|
}
|
|
|
|
# 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;
|