### ### Copyright 2000-2007 University of Illinois Board of Trustees ### All rights reserved. ### ### Network.pm - network configuration module for psgconf ### ### Campus Information Technologies and Educational Services ### University of Illinois at Urbana-Champaign ### package PSGConf::Control::Network; use strict; use NetAddr::IP; use PSGConf::Action::GenerateFile::Literal; use PSGConf::Action::GenerateFile::TLI_hosts; use PSGConf::Action::GenerateFile::hosts; use PSGConf::Action::GenerateFile::netmasks; use PSGConf::Action::GenerateFile::EnvFile; use PSGConf::Action::Remove; use PSGConf::Action::RunCommand; use PSGConf::Data::Boolean; use PSGConf::Data::Hash; use PSGConf::Data::String; use PSGConf::Util; ############################################################################### ### PSGConf::Action::RunCommand plugin for AIX TCP/IP config ############################################################################### sub _check_AIX_net_config { my ($action, $psgconf) = @_; my (@fields, @values, $line, $ct, %settings); ### run mktcpip to get current settings for $if if (!open(MKTCPIP, "/usr/sbin/mktcpip -S $action->{pri_if}->{name} 2>&1 |")) { warn "\n\t!!! cannot run mktcpip: $!\n"; return -1; } $line = ; chomp($line); $line =~ s/^#//; @fields = split(/:/, $line); $line = ; chomp($line); @values = split(/:/, $line); close(MKTCPIP); for ($ct = 0; $ct < @fields; $ct++) { $settings{$fields[$ct]} = $values[$ct]; } ### convert netmask from hex to dotted-decimal if ($settings{mask} =~ m/^0x/) { my $tmp = $settings{mask}; $tmp =~ s/^0x//; $tmp = join('.', unpack('C*', pack('H8', $tmp))); $settings{mask} = $tmp; } ### see if anything needs to be changed if ($settings{host} ne $psgconf->data_obj('hostname')->get() || $settings{addr} ne $action->{pri_if}->{addr} || $settings{mask} ne $action->{pri_if}->{mask} || $settings{gateway} ne $action->{pri_if}->{gateway}) { return 1; } return 0; } ############################################################################### ### internal utility function ############################################################################### ### returns: ### hash containing network name for each interface ### name of default interface ### default network sub _get_if_nets { my ($psgconf, $ifs, $nets, $addrs, $hostname) = @_; my ($net, $if, $addr); my (%if_nets, $default_if, $default_net); foreach $if (keys %$ifs) { next if ( $ifs->{$if} eq 'DHCP' ); $addr = (get_addrs($psgconf, $ifs->{$if}))[0]; $addr = new NetAddr::IP $addr; foreach $net (sort keys %$nets) { if ($nets->{$net}->contains($addr)) { $if_nets{$if} = $net; last; } } ### note primary interface if ($ifs->{$if} eq $hostname) { $default_if = $if; $default_net = $if_nets{$if}; } } return ($default_if, $default_net, %if_nets); } ############################################################################### ### policy methods ############################################################################### ### instantiate networks sub _policy_validate_networks { my ($self, $psgconf) = @_; my ($nets, $net, $netobj); $nets = $psgconf->data_obj('networks')->get(); foreach $net (keys %$nets) { $netobj = new NetAddr::IP $nets->{$net}; if (!defined($netobj)) { warn "\n\t!!!invalid network specification \"$nets->{$net}\"\n"; delete $nets->{$net}; next; } $nets->{$net} = $netobj; } } ### default gateways sub _policy_default_gateways { my ($self, $psgconf) = @_; my ($gws, $nets); $nets = $psgconf->data_obj('networks')->get(); $gws = $psgconf->data_obj('network_gateways')->get(); map { $gws->{$_} = $nets->{$_}->last() if (!defined($gws->{$_})); } keys %$nets; } ### Handle any DHCP interfaces sub _policy_dhcp_interfaces { my ($self, $psgconf) = @_; my ($ifs, $dhcp_enabled); $ifs = $psgconf->data_obj('network_interfaces')->get(); map { if ($ifs->{$_} eq 'DHCP') { $dhcp_enabled = 1; } } keys %$ifs; return if ( ! $dhcp_enabled ); ### Turn off modifying the /etc/resolv.conf files $psgconf->data_obj('dns_servers')->unset(); ### as well as the hosts/ipnodes files as well. $psgconf->data_obj('hosts_enable')->set('false'); } ### network interfaces default to hostname sub _policy_default_interfaces { my ($self, $psgconf) = @_; my ($ifs, $gotcha); $ifs = $psgconf->data_obj('network_interfaces')->get(); map { if (!defined($ifs->{$_})) { die $0 . ": only one network interface can use default address\n" if ($gotcha); $ifs->{$_} = $psgconf->data_obj('hostname')->get(); $gotcha = 1; } } keys %$ifs; } ### add /etc/hosts entries for network interfaces sub _policy_add_etc_hosts { my ($self, $psgconf) = @_; my ($addrs); $addrs = $psgconf->data_obj('host_addrs')->get(); map { $addrs->{$_} = undef if (!exists($addrs->{$_})); } values %{$psgconf->data_obj('network_interfaces')->get()}; } ### look up /etc/hosts entries sub _policy_default_etc_hosts { my ($self, $psgconf) = @_; my ($addrs); $addrs = $psgconf->data_obj('host_addrs')->get(); map { if ( $_ eq 'DHCP' ) { delete $addrs->{$_}; } else { $addrs->{$_} = (get_addrs($psgconf, $_))[0] if (!defined($addrs->{$_})); } } keys %$addrs; } ### update FreeBSD /etc/rc.conf entries sub _policy_modify_rc_vars { my ($self, $psgconf) = @_; my ($rc_vars, $addrs, $nets, $ifs, $gws, $hostname); my (%if_nets, $default_if, $default_net); return if (! $psgconf->data_obj('platform')->match('freebsd') ); $rc_vars = $psgconf->data_obj('rc_vars')->get(); $hostname = $psgconf->data_obj('hostname')->get(); $ifs = $psgconf->data_obj('network_interfaces')->get(); $nets = $psgconf->data_obj('networks')->get(); $addrs = $psgconf->data_obj('host_addrs')->get(); $gws = $psgconf->data_obj('network_gateways')->get(); ($default_if, $default_net, %if_nets) = _get_if_nets($psgconf, $ifs, $nets, $addrs, $hostname); $rc_vars->{'hostname'} = $hostname; $rc_vars->{'defaultrouter'} = $gws->{$default_net}; map { if ( $ifs->{$_} eq 'DHCP' ) { $rc_vars->{'ifconfig_' . $_} = 'DHCP'; } else { $rc_vars->{'ifconfig_' . $_} = 'inet ' . $addrs->{$ifs->{$_}} . ' netmask ' . $nets->{$if_nets{$_}}->mask(); } } (sort keys %$ifs); } ############################################################################### ### decide() method ############################################################################### sub decide { my ($self, $psgconf) = @_; my ($ifs, $nets, $gws, $addrs, $aliases, $etc_inet_dir); my ($default_if, $default_net, %if_nets, %vars); $etc_inet_dir = $psgconf->data_obj('etc_inet_dir')->get(); $nets = $psgconf->data_obj('networks')->get(); $gws = $psgconf->data_obj('network_gateways')->get(); $ifs = $psgconf->data_obj('network_interfaces')->get(); $addrs = $psgconf->data_obj('host_addrs')->get(); $aliases = $psgconf->data_obj('host_aliases')->get(); ($default_if, $default_net, %if_nets) = _get_if_nets($psgconf, $ifs, $nets, $addrs, $psgconf->data_obj('hostname')->get()); ### Set the DHCP client id field from the hostname if ( ! defined $psgconf->data_obj('dhclient_hostname')->get() ) { my ($shost) = $psgconf->data_obj('hostname')->get(); $shost =~ s/\..*$//; $psgconf->data_obj('dhclient_hostname')->set($shost); } ### create /etc/defaultrouter under Solaris $psgconf->register_actions( PSGConf::Action::GenerateFile::Literal->new( name => '/etc/defaultrouter', comment_str => undef, content => $gws->{$default_net} . "\n", requires_reboot => 1 ) ) if ($psgconf->data_obj('platform')->match('solaris') && exists $gws->{$default_net} && $psgconf->data_obj('use_static_routes')->equals('true')); ### create /etc/hosts $psgconf->register_actions( PSGConf::Action::GenerateFile::hosts->new( name => $etc_inet_dir . '/hosts', description => 'Internet host table', requires_reboot => 1, ip_addrs => { map { $addrs->{$_} => [ $_, sort keys %{$aliases->{$_}} ] } keys %$addrs } ) ) if ($psgconf->data_obj('hosts_enable')->equals('true')); if ($psgconf->data_obj('platform')->match('solaris|freebsd')) { ### create /etc/netmasks $psgconf->register_actions( PSGConf::Action::GenerateFile::netmasks->new( name => $etc_inet_dir . '/netmasks', description => 'network address mask table', requires_reboot => 1, networks => { map { $nets->{$_}->addr() => { mask => $nets->{$_}->mask(), comment => $_ } } (values %if_nets) } ) ); } if ($psgconf->data_obj('platform')->match('solaris')) { $psgconf->register_actions( ### create TLI hosts files (map { PSGConf::Action::GenerateFile::TLI_hosts->new( name => "/etc/net/$_/hosts", description => 'RPC hosts', hostname => $psgconf->data_obj('hostname')->get(), requires_reboot => 1 ) } qw(ticlts ticots ticotsord)), ### create /etc/nodename PSGConf::Action::GenerateFile::Literal->new( name => '/etc/nodename', content => $psgconf->data_obj('hostname')->get() . "\n", comment_str => undef, requires_reboot => 1 ), ### create /etc/hostname.* (and maybe /etc/dhcp.*) ### for each interface (map { PSGConf::Action::GenerateFile::Literal->new( name => "/etc/dhcp.$_", comment_str => undef, content => ($_ eq $default_if)? "primary\n": '' ) if ($ifs->{$_} eq 'DHCP'); ### If we are running DHCP, use the short hostname ### as the host-name (#12) we send to the server. PSGConf::Action::GenerateFile::Literal->new( name => "/etc/hostname.$_", content => (($ifs->{$_} eq 'DHCP')? '' : $ifs->{$_}) . "\n", comment_str => undef, backup => 0, requires_reboot => 1 ); } sort keys %$ifs), ### remove any other /etc/hostname.* files (map { PSGConf::Action::Remove->new( name => $_, requires_reboot => 1 ) } grep { m|^/etc/hostname\.(.*)$| && !exists($ifs->{$1}); } glob('/etc/hostname.*')), ### and remove any other /etc/dhcp.* files (map { PSGConf::Action::Remove->new( name => $_, requires_reboot => 1 ) } grep { m|^/etc/dhcp\.(.*)$| && !exists($ifs->{$1}); } glob('/etc/dhcp.*')) ); } elsif ($psgconf->data_obj('platform')->match('aix')) { my ($pri_if); $pri_if = { name => $default_if, addr => $addrs->{$psgconf->data_obj('hostname')->get()}, mask => $nets->{$default_net}->mask(), gateway => $gws->{$default_net} }; $psgconf->register_actions( PSGConf::Action::RunCommand->new( name => 'AIX TCP/IP settings', pri_if => $pri_if, check_func => \&_check_AIX_net_config, command => '/usr/sbin/mktcpip -h ' . $psgconf->data_obj('hostname')->get() . ' -a ' . $pri_if->{addr} . ' -i ' . $pri_if->{name} . ' -m ' . $pri_if->{mask} . ' -g ' . $pri_if->{gateway}, requires_reboot => 1 ) ); } elsif ($psgconf->data_obj('platform')->match('(-rhel-)|(-rhl-)|(-fc-)')) { $vars{'NETWORKING'} = 'yes'; $vars{'HOSTNAME'} = $psgconf->data_obj('hostname')->get() if ( defined $psgconf->data_obj('hostname')->get() ); $vars{'GATEWAY'} = $gws->{$default_net} if ( defined ($gws->{$default_net}) ); $psgconf->register_actions( ### create /etc/sysconfig/network PSGConf::Action::GenerateFile::EnvFile->new( name => '/etc/sysconfig/network', vars => \%vars, comment_str => undef, requires_reboot => 1 ), ### create ifcfg-* for each interface (map { if ( $ifs->{$_} eq 'DHCP' ) { PSGConf::Action::GenerateFile::EnvFile->new( name => "/etc/sysconfig/network-scripts/ifcfg-$_", vars => { DEVICE => $_, BOOTPROTO => 'dhcp', TYPE => 'Ethernet', ONBOOT => 'yes' }, comment_str => undef, backup => 0, requires_reboot => 1 ), PSGConf::Action::GenerateFile::Literal->new( name => '/etc/dhclient.conf', content => 'send host-name "' . $psgconf->data_obj('dhclient_hostname')->get() . "\";\n" . $psgconf->data_obj('dhclient_literal')->get() ) } else { PSGConf::Action::GenerateFile::EnvFile->new( name => "/etc/sysconfig/network-scripts/ifcfg-$_", vars => { DEVICE => $_, BOOTPROTO => 'static', BROADCAST => $nets->{$if_nets{$_}}->broadcast()->addr(), IPADDR => $addrs->{$ifs->{$_}}, NETMASK => $nets->{$if_nets{$_}}->mask(), NETWORK => $nets->{$if_nets{$_}}->addr(), ONBOOT => 'yes' }, comment_str => undef, backup => 0, requires_reboot => 1 ) } } sort keys %$ifs), ### remove any other ifcfg-* files (map { PSGConf::Action::Remove->new( name => $_, requires_reboot => 1 ) } grep { m|^/etc/sysconfig/network-scripts/ifcfg-(.*)$| && $1 ne 'lo' && !exists($ifs->{$1}); } glob('/etc/sysconfig/network-scripts/ifcfg-*')) ); } elsif ($psgconf->data_obj('platform')->match('freebsd')) { (map { $psgconf->register_actions( PSGConf::Action::GenerateFile::Literal->new( name => '/etc/dhclient.conf', content => 'send host-name "' . $psgconf->data_obj('dhclient_hostname')->get() . "\";\n" . $psgconf->data_obj('dhclient_literal')->get() ) ) if ( $ifs->{$_} eq 'DHCP' ); } sort keys %$ifs); } } ############################################################################### ### constructor ############################################################################### sub new { my ($class, $psgconf) = @_; my ($self); $self = {}; bless($self, $class); $self->{name} = 'Network'; $psgconf->register_data( use_static_routes => PSGConf::Data::Boolean->new( value => 'true' ), hosts_enable => PSGConf::Data::Boolean->new( value => 'false' ), host_addrs => PSGConf::Data::Hash->new( value_optional => 1 ), host_aliases => PSGConf::Data::Hash->new( value_type => 'HASH' ), dhclient_hostname => PSGConf::Data::String->new(), dhclient_literal => PSGConf::Data::String->new(), networks => PSGConf::Data::Hash->new(), network_gateways => PSGConf::Data::Hash->new(), network_interfaces => PSGConf::Data::Hash->new( value_optional => 1 ) ); $psgconf->register_policy($self, net_validate_networks => '_policy_validate_networks', net_default_gateways => '_policy_default_gateways', net_default_interfaces => '_policy_default_interfaces', net_add_etc_hosts => '_policy_add_etc_hosts', net_default_etc_hosts => '_policy_default_etc_hosts', net_modify_rc_vars => '_policy_modify_rc_vars', net_dhcp_interfaces => '_policy_dhcp_interfaces' ); return $self; } ############################################################################### ### documentation ############################################################################### 1; __END__ =head1 NAME PSGConf::Control::Network - psgconf control class for network configuration =head1 SYNOPSIS In F: Control PSGConf::Control::Network =head1 DESCRIPTION The B module provides a B control object for configuring system networking. It provides the following methods: =over 4 =item new() The constructor. Its parameter is a reference to the B object. It registers the following data objects: =over 4 =item I A B object that indicates whether the F/hosts> file should be generated. =item I A B object that adds entries for the F/hosts> file. The key is the hostname, and the value is its IP address. If the value is not defined, it will be looked up in DNS. =item I A B object that lists aliases for entries in the I object. The key is the hostname, and the value is a reference to a hash whose keys are the aliases. =item I A B object that represents information about known networks. The key is the name of the network, and the value is a string of the form C. =item I A B object that contains the default gateway for each network. The key is the name of the network, and the value is the IP address of the default gateway for that network. =item I A B object that represents the system's network interfaces. The hash key is the name of the network interface, and the value is the interface's IP address (or hostname, in which case it gets resolved using DNS), or the special token F. If the value is not defined, the default is to use the I data object, which is provided by the B module. =item I A B object to use in the F configuration file (in field called host-name, or Hostname on Solaris). Defaults to the short hostname. =item I A B object to add to the F file, if F has been used on a network interface. =back The constructor also registers the following policy methods: =over 4 =item I Instantiates a B object for each network in the I object. Entries for invalid networks are deleted. =item I For each entry in the I data object that does not also exist in the I data object, add an entry to I for the lowest address on that network. =item I For the first entry in the I data object that does not specify an IP address or hostname, set it to the value of the I data object (supplied by the B module). If more than one entry does not specify an IP address or hostname, die. (Only one interface can have the same IP address.) =item I If there is an interface defined with the F token, disable I and unset I, so that F does not get maintained as well. =item I Adds an entry to I for each entry in I. =item I For each entry in I that does not have a specified IP address, the value is looked up in DNS. =item I On FreeBSD systems, modifies the I data object (provided by B) to include network settings. =back =item decide() Instantiates and registers action objects, as follows: =over 4 =item * Under Solaris, if I is defined, registers a B object to create F. =item * If I is set, registers a B object to create the F/hosts> file. =item * Under Solaris, registers a B object to create F/netmasks>. =item * Under Solaris, registers B objects to create the F, F, F files. =item * Under Solaris, registers a B object to create F. =item * Under Solaris, registers B objects to create F files for each entry in I. Also registers B objects to remove F files for any interfaces not listed in I. =item * Under AIX, registers a B object that runs C to configure TCP/IP networking. =item * Under Linux, registers a B object to create F. =item * Under Linux, registers B objects to create F files for each entry in I. Also registers B objects to remove F files for any interfaces not listed in I. =back =back =head1 SEE ALSO L hosts(4) L L L L L L L L L L L L L L =cut