diff options
Diffstat (limited to 'lib/network/connection/ethernet.pm')
-rw-r--r-- | lib/network/connection/ethernet.pm | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/lib/network/connection/ethernet.pm b/lib/network/connection/ethernet.pm new file mode 100644 index 0000000..39752f5 --- /dev/null +++ b/lib/network/connection/ethernet.pm @@ -0,0 +1,512 @@ +package network::connection::ethernet; # $Id: ethernet.pm 147431 2007-03-21 17:06:09Z blino $ + +use base qw(network::connection); + +use strict; +use common; + +our @dhcp_clients = qw(dhclient dhcpcd pump dhcpxd); + +sub get_type_name() { N("Ethernet") } +sub get_type_icon() { 'ethernet-24.png' } + +sub get_devices() { + #require list_modules; + #- FIXME: try to use list_modules::ethernet_categories() (but remove wireless stuff) + require detect_devices; + my @devices = detect_devices::probe_category('network/main|gigabit|pcmcia|tokenring|usb|firewire'); + my @lan = grep { detect_devices::is_lan_interface($_) && !detect_devices::is_wireless_interface($_) } detect_devices::get_all_net_devices(); + @devices, get_unlisted_devices(\@lan, \@devices); +} + +sub get_unlisted_devices { + my ($interfaces, $listed_devices) = @_; + my @unlisted_interfaces = sort(difference2($interfaces, [ map { device_to_interface($_) } @$listed_devices ])); + map { interface_to_device($_) || +{ description => $_, interface => $_ } } @unlisted_interfaces; +} + +sub handles_ifcfg { + my ($_class, $ifcfg) = @_; + require detect_devices; + detect_devices::is_lan_interface($ifcfg->{DEVICE}); +} + +sub is_gigabit { + my ($self) = @_; + require list_modules; + member($self->get_driver, list_modules::category2modules('network/gigabit')); +} + +sub get_metric { + my ($self) = @_; + $self->is_gigabit ? 5 : 10; +} + +sub get_interface { + my ($self) = @_; + $self->{device}{interface} || device_to_interface($self->{device}); +} + +sub check_device { + my ($self) = @_; + if (!$self->get_interface) { + $self->{device}{error} = N("Unable to find network interface for selected device (using %s driver).", $self->get_driver); + return 0; + } + return 1; +} + +sub get_protocols() { + { + static => N("Manual configuration"), + dhcp => N("Automatic IP (BOOTP/DHCP)"), + }; +} + +sub load_interface_settings { + my ($self) = @_; + + $self->network::connection::load_interface_settings; + + $self->{protocol} = $self->{ifcfg}{BOOTPROTO}; + + $self->{address}{needhostname} = $self->get_ifcfg_bool('NEEDHOSTNAME'); + $self->{address}{peerdns} = $self->get_ifcfg_bool('PEERDNS'); + $self->{address}{peeryp} = $self->get_ifcfg_bool('PEERYP'); + $self->{address}{peerntpd} = $self->get_ifcfg_bool('PEERNTPD'); + + $self->{address}{dhcp_client} = $self->{ifcfg}{DHCP_CLIENT}; + $self->{address}{dhcp_hostname} = $self->{ifcfg}{DHCP_HOSTNAME}; + $self->{address}{dhcp_timeout} = $self->{ifcfg}{DHCP_TIMEOUT}; + $self->{address}{ip_address} = $self->{ifcfg}{IPADDR}; + $self->{address}{netmask} = $self->{ifcfg}{NETMASK}; + $self->{address}{gateway} = $self->{ifcfg}{GATEWAY}; + $self->{address}{dns1} = $self->{ifcfg}{DNS1} || $self->{ifcfg}{MS_DNS1}; + $self->{address}{dns2} = $self->{ifcfg}{DNS2} || $self->{ifcfg}{MS_DNS2}; + $self->{address}{domain} = $self->{ifcfg}{DOMAIN}; + + my $blacklist_ifplugd = $self->get_ifcfg_bool('MII_NOT_SUPPORTED'); + $self->{control}{use_ifplugd} = !$blacklist_ifplugd if defined $blacklist_ifplugd; + $self->{control}{ipv6_tunnel} = $self->get_ifcfg_bool('IPV6TO4INIT'); +} + +sub guess_protocol { + my ($self) = @_; + $self->{protocol} ||= 'dhcp'; +} + +sub guess_address_settings { + my ($self) = @_; + $self->{address}{dhcp_client} ||= find { -x "$::prefix/sbin/$_" } @dhcp_clients; + $self->{address}{peerdns} = 1 if !defined $self->{address}{peerdns}; + $self->{address}{peeryp} = 1 if !defined $self->{address}{peeryp}; + $self->supplement_address_settings; +} + +sub supplement_address_settings { + my ($self) = @_; + if ($self->{protocol} eq 'static' && network::network::is_ip($self->{address}{ip_address})) { + require network::network; + $self->{address}{netmask} ||= network::network::netmask($self->{address}{ip_address}); + $self->{address}{gateway} ||= network::network::gateway($self->{address}{ip_address}); + $self->{address}{dns1} ||= network::network::dns($self->{address}{ip_address}); + } +} + +sub get_address_settings_label { N("IP settings") } + +sub get_address_settings { + my ($self, $o_show_all) = @_; + my $auto_dns = sub { $self->{protocol} eq 'dhcp' && $self->{address}{peerdns} }; + my $not_static = sub { $self->{protocol} ne 'static' }; + my $not_dhcp = sub { $self->{protocol} ne 'dhcp' }; + [ + if_($self->{protocol} eq 'static' || $o_show_all, + { label => N("IP address"), val => \$self->{address}{ip_address}, disabled => $not_static, + focus_out => sub { + $self->supplement_address_settings if $self->can('supplement_address_settings'); + }, + help => N("Please enter the IP configuration for this machine. +Each item should be entered as an IP address in dotted-decimal +notation (for example, 1.2.3.4).") }, + { label => N("Netmask"), val => \$self->{address}{netmask}, disabled => $not_static }, + { label => N("Gateway"), val => \$self->{address}{gateway}, disabled => $not_static }, + ), + if_($self->{protocol} eq 'dhcp' || $o_show_all, + { text => N("Get DNS servers from DHCP"), val => \$self->{address}{peerdns}, type => "bool", disabled => $not_dhcp }, + ), + { label => N("DNS server 1"), val => \$self->{address}{dns1}, disabled => $auto_dns }, + { label => N("DNS server 2"), val => \$self->{address}{dns2}, disabled => $auto_dns }, + { label => N("Search domain"), val => \$self->{address}{domain}, disabled => $auto_dns, advanced => 1, + help => N("By default search domain will be set from the fully-qualified host name") }, + if_($self->{protocol} eq 'dhcp', + { label => N("DHCP client"), val => \$self->{address}{dhcp_client}, list => \@dhcp_clients, advanced => 1 }, + { label => N("DHCP timeout (in seconds)"), val => \$self->{address}{dhcp_timeout}, advanced => 1 }, + { text => N("Get YP servers from DHCP"), val => \$self->{address}{peeryp}, type => "bool", advanced => 1 }, + { text => N("Get NTPD servers from DHCP"), val => \$self->{address}{peerntpd}, type => "bool", advanced => 1 }, + { label => N("DHCP host name"), val => \$self->{address}{dhcp_hostname}, advanced => 1 }, + #- FIXME: install zcip if not checked + if_(0, { text => N("Do not fallback to Zeroconf (169.254.0.0 network)"), type => "bool", val => \$self->{address}{skip_zeroconf}, advanced => 1 }), + ), + ]; +} + +sub check_address_settings { + my ($self, $net) = @_; + + if ($self->{protocol} eq 'static') { + require network::network; + if (!network::network::is_ip($self->{address}{ip_address})) { + $self->{address}{error}{message} = N("IP address should be in format 1.2.3.4"); + $self->{address}{error}{field} = \$self->{address}{ip_address}; + return 0; + } + if (!network::network::is_ip($self->{address}{netmask})) { + $self->{address}{error}{message} = N("Netmask should be in format 255.255.224.0"); + $self->{address}{error}{field} = \$self->{address}{netmask}; + return 0; + } + if (network::network::is_ip_forbidden($self->{address}{ip_address})) { + $self->{address}{error}{message} = N("Warning: IP address %s is usually reserved!", $self->{address}{ip_address}); + $self->{address}{error}{field} = \$self->{address}{ip_address}; + return 0; + } + #- test if IP address is already used + if (find { text2bool($_->{ONBOOT}) && $_->{DEVICE} ne $self->get_interface && $_->{IPADDR} eq $self->{address}{ip_address} } values %{$net->{ifcfg}}) { + $self->{address}{error}{message} = N("%s already in use\n", $self->{address}{ip_address}); + $self->{address}{error}{field} = \$self->{address}{ip_address}; + return 0; + } + } + + return 1; +} + +sub guess_hostname_settings { + my ($self) = @_; + $self->{address}{needhostname} = 0 if !defined $self->{address}{needhostname}; + if (!defined $self->{address}{hostname}) { + require network::network; + my $network = network::network::read_conf($::prefix . $network::network::network_file); + $self->{address}{hostname} = $network->{HOSTNAME}; + } +} + +sub get_hostname_settings { + my ($self) = @_; + my $auto_hostname = sub { $self->{protocol} eq 'dhcp' && $self->{address}{needhostname} }; + [ + if_($self->{protocol} eq 'dhcp', + { text => N("Assign host name from DHCP address"), val => \$self->{address}{needhostname}, type => "bool" }, + ), + { label => N("Host name"), val => \$self->{address}{hostname}, disabled => $auto_hostname }, + ]; +} + +sub guess_control_settings { + my ($self) = @_; + + $self->network::connection::guess_control_settings($self); + + $self->{control}{onboot} = 1 if !defined $self->{control}{onboot}; + $self->{control}{use_ifplugd} = !is_ifplugd_blacklisted($self->get_driver) + if !defined $self->{control}{use_ifplugd}; +} + +sub get_control_settings { + my ($self) = @_; + [ + @{$self->network::connection::get_control_settings}, + { text => N("Network Hotplugging"), val => \$self->{control}{use_ifplugd}, type => "bool", + #- FIXME: force ifplugd if wireless roaming is enabled + disabled => sub { $self->{control}{force_ifplugd} }, advanced => 1, }, + #- FIXME: $need_network_restart = $ipv6_tunnel ^ text2bool($ethntf->{IPV6TO4INIT}); + { text => N("Enable IPv6 to IPv4 tunnel"), val => \$self->{control}{ipv6_tunnel}, type => "bool", advanced => 1 }, + ]; +} + +sub install_packages { + my ($self, $in) = @_; + if ($self->{protocol} eq 'dhcp') { + install_dhcp_client($in, $self->{address}{dhcp_client}) or return; + } + 1; +} + +sub build_ifcfg_settings { + my ($self, $o_options) = @_; + my $settings = put_in_hash($o_options, { + BOOTPROTO => $self->{protocol}, + IPADDR => $self->{address}{ip_address}, + GATEWAY => $self->{address}{gateway}, + NETMASK => $self->{address}{netmask}, + NEEDHOSTNAME => bool2yesno($self->{address}{needhostname}), + PEERYP => bool2yesno($self->{address}{peeryp}), + PEERDNS => bool2yesno($self->{address}{peerdns}), + RESOLV_MODS => bool2yesno($self->{address}{dns1} || $self->{address}{dns2}), + PEERNTPD => bool2yesno($self->{address}{peerntpd}), + DHCP_CLIENT => $self->{address}{dhcp_client}, + DHCP_HOSTNAME => $self->{address}{dhcp_hostname}, + DHCP_TIMEOUT => $self->{address}{dhcp_timeout}, + MII_NOT_SUPPORTED => bool2yesno(!$self->{control}{use_ifplugd}), + IPV6INIT => bool2yesno($self->{control}{ipv6_tunnel}), + IPV6TO4INIT => bool2yesno($self->{control}{ipv6_tunnel}), + DNS1 => $self->{address}{dns1}, + DNS2 => $self->{address}{dns2}, + DOMAIN => $self->{address}{domain}, + LINK_DETECTION_DELAY => $self->get_link_detection_delay, + }); + $self->network::connection::build_ifcfg_settings($settings); +} + +sub write_settings { + my ($self, $o_net, $o_modules_conf) = @_; + $o_modules_conf->set_alias($self->get_interface, $self->get_driver) if $o_modules_conf; + $self->SUPER::write_settings($o_net, $o_modules_conf); +} + +sub get_status_message { + my ($self, $status) = @_; + my $interface = $self->get_interface; + { + link_up => N("Link beat detected on interface %s", $interface), + link_down => N("Link beat lost on interface %s", $interface), + map { ( + "$_->[0]_request" => N("Requesting a network address on interface %s (%s protocol)...", $interface, $_->[1]), + "$_->[0]_success" => N("Got a network address on interface %s (%s protocol)", $interface, $_->[1]), + "$_->[0]_failure" => N("Failed to get a network address on interface %s (%s protocol)", $interface, $_->[1]), + ) } ([ 'dhcp', 'DHCP' ], [ 'zcip', 'ZeroConf' ]), + }->{$status} || $self->network::connection::get_status_message($status); +} + +use c; +use detect_devices; +use common; +use run_program; + +sub install_dhcp_client { + my ($in, $client) = @_; + my %packages = ( + "dhclient" => "dhcp-client", + ); + #- use default dhcp client if none is provided + $client ||= $dhcp_clients[0]; + $client = $packages{$client} if exists $packages{$client}; + $in->do_pkgs->ensure_is_installed($client, undef, 1); +} + +my @hwaddr_fields = qw(pci_domain pci_bus pci_device pci_function); + +sub are_same_HwIDs { + my ($device1, $device2) = @_; + every { $device1->{$_} == $device2->{$_} } @hwaddr_fields; +} + +sub parse_hwaddr { + my ($hw_addr) = @_; + return if !$hw_addr; + my %device; + @device{@hwaddr_fields} = map { hex($_) } ($hw_addr =~ /([0-9a-f]+):([0-9a-f]+):([0-9a-f]+)\.([0-9a-f]+)/); + (every { defined $_ } keys %device) ? \%device : undef; +} + +sub mapIntfToDevice { + my ($interface) = @_; + my $hw_addr = c::getHwIDs($interface); + return {} if $hw_addr =~ /^usb/; + my $device = parse_hwaddr($hw_addr); + $device ? grep { are_same_HwIDs($_, $device) } detect_devices::probeall__real() : {}; +} + +sub device_matches_interface_HwIDs { + my ($device, $interface) = @_; + my $hw_addr = c::getHwIDs($interface); + $hw_addr =~ /^usb/ and return; + my ($device2) = parse_hwaddr($hw_addr); + return if !$device2; + are_same_HwIDs($device, $device2); +} + +sub get_interface_sysfs_path { + my ($interface) = @_; + my $dev_path = "/sys/class/net/$interface/device"; + my $bus = detect_devices::get_sysfs_field_from_link($dev_path, 'bus'); + if ($bus eq 'ieee1394') { + my $child = first(glob("$dev_path/host_id/*-*")); + $dev_path = $child if $child; + } + $dev_path; +} + +sub get_interface_ids { + my ($interface) = @_; + detect_devices::get_ids_from_sysfs_device(get_interface_sysfs_path($interface)); +} + +sub device_matches_interface { + my ($device, $interface) = @_; + detect_devices::device_matches_sysfs_ids($device, get_interface_ids($interface)); +} + +sub device_to_interface { + my ($device) = @_; + my @all_interfaces = detect_devices::get_net_interfaces(); + find { + device_matches_interface_HwIDs($device, $_) || + device_matches_interface($device, $_); + } @all_interfaces; +} + +sub interface_to_device { + my ($interface) = @_; + my $sysfs_ids = get_interface_ids($interface); + find { detect_devices::device_matches_sysfs_ids($_, $sysfs_ids) } detect_devices::probeall__real(); +} + +sub interface_to_driver { + my ($interface) = @_; + my $dev_path = get_interface_sysfs_path($interface); + #- FIXME: use $bus and move in get_interface_sysfs_path if possible + my $child = -f "$dev_path/idVendor" && first(glob("$dev_path/*-*:*.*")); + $dev_path = $child if -f "$child/driver"; + detect_devices::get_sysfs_field_from_link($dev_path, 'driver'); +} + +# return list of [ intf_name, module, device_description ] tuples such as: +# [ "eth0", "3c59x", "3Com Corporation|3c905C-TX [Fast Etherlink]" ] +# +# this function try several method in order to get interface's driver and description in order to support both: +# - hotplug managed devices (USB, firewire) +# - special interfaces (IP aliasing, VLAN) +sub get_eth_cards { + my ($modules_conf) = @_; + my @all_cards = detect_devices::get_lan_interfaces(); + + my @devs = detect_devices::pcmcia_probe(); + my $saved_driver; + # compute device description and return (interface, driver, description) tuples: + return map { + my $interface = $_; + my $description; + # 1) get interface's driver through ETHTOOL ioctl: + my ($a, $detected_through_ethtool); + $a = c::getNetDriver($interface); + if ($a) { + $detected_through_ethtool = 1; + } else { + # 2) get interface's driver through module aliases: + $a = $modules_conf->get_alias($interface); + } + + # workaround buggy drivers that returns a bogus driver name for the GDRVINFO command of the ETHTOOL ioctl: + my %fixes = ( + "p80211_prism2_cs" => 'prism2_cs', + "p80211_prism2_pci" => 'prism2_pci', + "p80211_prism2_usb" => 'prism2_usb', + "ip1394" => "eth1394", + "DL2K" => "dl2k", + "orinoco" => undef, #- should be orinoco_{cs,nortel,pci,plx,tmd} + "hostap" => undef, #- should be hostap_{cs,pci,plx} + ); + if (exists $fixes{$a}) { + $a = $fixes{$a}; + $a or undef $detected_through_ethtool; + } + + # 3) try to match a PCMCIA device for device description: + if (my $b = find { $_->{device} eq $interface } @devs) { # PCMCIA case + $a = $b->{driver}; + $description = $b->{description}; + } else { + # 4) try to lookup a device by hardware address for device description: + # maybe should have we try sysfs first for robustness? + ($description) = (mapIntfToDevice($interface))[0]->{description}; + } + # 5) try to match a device through sysfs for driver & device description: + # (eg: ipw2100 driver for intel centrino do not support ETHTOOL) + if (!$description || !$a) { + my $drv = interface_to_driver($interface); + $a = $drv if $drv && !$detected_through_ethtool; + my $card = interface_to_device($interface); + $description ||= $card->{description} if $card; + } + # 6) try to match a device by driver for device description: + # (eg: madwifi, ndiswrapper, ...) + if (!$description) { + my @cards = grep { $_->{driver} eq ($a || $saved_driver) } detect_devices::probeall__real(); + $description = $cards[0]{description} if @cards == 1; + } + $a and $saved_driver = $a; # handle multiple cards managed by the same driver + [ $interface, $saved_driver, if_($description, $description) ]; + } @all_cards; +} + +sub get_eth_cards_names { + my (@all_cards) = @_; + map { $_->[0] => join(': ', $_->[0], $_->[2]) } @all_cards; +} + +#- returns (link_type, mac_address) +sub get_eth_card_mac_address { + my ($intf) = @_; + #- don't look for 6 bytes addresses only because of various non-standard MAC addresses + `$::prefix/sbin/ip -o link show $intf 2>/dev/null` =~ m|.*link/(\S+)\s((?:[0-9a-f]{2}:?)+)\s|; +} + +#- write interfaces MAC address in iftab +sub update_iftab() { + #- skip aliases interfaces + foreach my $intf (grep { !/:\d+$/ } detect_devices::get_lan_interfaces()) { + my ($link_type, $mac_address) = get_eth_card_mac_address($intf) or next; + #- do not write zeroed MAC addresses in iftab, it confuses ifrename + $mac_address =~ /^[0:]+$/ and next; + # ifrename supports alsa IEEE1394, EUI64 and IRDA + member($link_type, 'ether', 'ieee1394', 'irda', '[27]') or next; + substInFile { + s/^$intf\s+.*\n//; + s/^.*\s+$mac_address\n//; + $_ .= qq($intf mac $mac_address\n) if eof; + } "$::prefix/etc/iftab"; + } +} + +sub update_udev_net_config() { + my $lib = arch() =~ /x86_64/ ? "lib64" : "lib"; + my $net_name_helper = "/$lib/udev/net_name_helper"; + my $udev_net_config = "/etc/udev/rules.d/61-net_config.rules"; + my @old_config = cat_($udev_net_config); + #- skip aliases and vlan interfaces + foreach my $intf (grep { !/[:.]\d+$/ } detect_devices::get_lan_interfaces()) { + (undef, my $mac_address) = get_eth_card_mac_address($intf) or next; + #- do not write zeroed MAC addresses + $mac_address =~ /^[0:]+$/ and next; + #- skip already configured addresses + any { !/^\s*#/ && /"$mac_address"/ } @old_config and next; + local $ENV{INTERFACE} = $intf; + local $ENV{SUBSYSTEM} = 'net'; + run_program::rooted($::prefix, $net_name_helper, '>', '/dev/null', $mac_address); + } +} + +# automatic net aliases configuration +sub configure_eth_aliases { + my ($modules_conf) = @_; + foreach my $card (get_eth_cards($modules_conf)) { + $modules_conf->set_alias($card->[0], $card->[1]); + } + $::isStandalone and $modules_conf->write; + update_iftab(); + update_udev_net_config(); +} + +sub get_link_detection_delay { + my ($self) = @_; + member($self->get_driver, qw(forcedeth r8169 skge sky2 tg3)) && 6; +} + +sub is_ifplugd_blacklisted { + my ($module) = @_; + !$module || member($module, qw(via-velocity)); +} + +1; |