mirror of
https://github.com/webmin/webmin.git
synced 2026-06-05 12:50:23 +01:00
* Note: Preserve existing Netplan indentation when updating DNS settings, validate with netplan generate before applying, and surface apply errors to avoid network loss from malformed YAML.
1011 lines
29 KiB
Perl
Executable File
1011 lines
29 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+\.(\d+))@([^ \t\r\n\@]+\d+):/) {
|
|
# 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{'virtual'} = $3;
|
|
}
|
|
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 ($old) = grep { $_->{'fullname'} eq $a->{'fullname'} } &active_interfaces();
|
|
|
|
# For Debian 5.0+ the "vconfig add" command is deprecated, this is handled
|
|
# by ifup.
|
|
if(($a->{'vlan'} == 1) && !(($gconfig{'os_type'} eq 'debian-linux') && ($gconfig{'os_version'} >= 5))) {
|
|
local $vconfigCMD = "vconfig add " .
|
|
quotemeta($a->{'physical'})." ".
|
|
quotemeta($a->{'vlanid'});
|
|
local $vconfigout = &backquote_logged("$vconfigCMD 2>&1");
|
|
if ($?) { &error($vonconfigout); }
|
|
}
|
|
|
|
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'})) {
|
|
# Bring up
|
|
my $cmd = "ip link set dev ".quotemeta($a->{'name'})." 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($a->{'name'})." 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($a->{'name'});
|
|
&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'});
|
|
}
|
|
if($a->{'vlan'} == 1) {
|
|
$cmd .= " dev ".quotemeta($a->{'physical'}).".".
|
|
quotemeta($a->{'vlanid'});
|
|
}
|
|
else {
|
|
$cmd .= " dev ".quotemeta($a->{'name'});
|
|
}
|
|
}
|
|
}
|
|
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 = &backquote_logged("cd / ; $cmd 2>&1");
|
|
if ($?) { &error("$cmd failed : $out"); }
|
|
|
|
# Apply ethernet address
|
|
if ($a->{'ether'} && !&use_ifup_command($a) && &has_command("ifconfig")) {
|
|
# With ifconfig command
|
|
$out = &backquote_logged("ifconfig ".quotemeta($a->{'name'}).
|
|
" hw ether ".quotemeta($a->{'ether'})." 2>&1");
|
|
if ($?) { &error($out); }
|
|
}
|
|
elsif ($a->{'ether'} && !&use_ifup_command($a) && &has_command("ip")) {
|
|
# With ip link command
|
|
$out = &backquote_logged("ip link set dev ".quotemeta($a->{'name'}).
|
|
" address ".quotemeta($a->{'ether'})." 2>&1");
|
|
if ($?) { &error($out); }
|
|
}
|
|
|
|
# Apply MTU
|
|
if ($a->{'mtu'} && !&use_ifup_command($a) && &has_command("ip")) {
|
|
$out = &backquote_logged(
|
|
"ip link set dev ".quotemeta($a->{'name'})." 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($a->{'name'}));
|
|
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($a->{'name'})." 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($a->{'name'})." 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($a->{'name'});
|
|
$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($a->{'name'});
|
|
$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 ($?);
|
|
}
|
|
|
|
# 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, "hosts:\t$conf->{'order'}\n");
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
1;
|