# Module: VyattaMisc.pm # # Author: Marat # Date: 2007 # Description: Implements miscellaneous commands # **** License **** # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # This code was originally developed by Vyatta, Inc. # Portions created by Vyatta are Copyright (C) 2006, 2007, 2008 Vyatta, Inc. # All Rights Reserved. # **** End License **** package Vyatta::Misc; use strict; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(get_sysfs_value getInterfaces getNetAddIP isIpAddress is_ip_v4_or_v6 is_dhcp_enabled is_address_enabled); our @EXPORT_OK = qw(get_sysfs_value getNetAddIP isIpAddress is_ip_v4_or_v6 getInterfacesIPadresses getPortRuleString); use Vyatta::Config; use Vyatta::Interface; use NetAddr::IP; sub get_sysfs_value { my ($intf, $name) = @_; open (my $statf, '<', "/sys/class/net/$intf/$name") or die "Can't open statistics file /sys/class/net/$intf/$name"; my $value = <$statf>; chomp $value if defined $value; close $statf; return $value; } # check if interface is configured to get an IP address using dhcp sub is_dhcp_enabled { my ($name, $outside_cli) = @_; my $intf = new Vyatta::Interface($name); return unless $intf; my $config = new Vyatta::Config; $config->{_active_dir_base} = "/opt/vyatta/config/active/" if ($outside_cli); $config->setLevel($intf->path()); foreach my $addr ($config->returnOrigValues('address')) { return 1 if ($addr && $addr eq "dhcp"); } # return undef } # check if any non-dhcp addresses configured sub is_address_enabled { my $name = shift; my $intf = new Vyatta::Interface($name); $intf or return; my $config = new Vyatta::Config; $config->setLevel($intf->path()); foreach my $addr ($config->returnOrigValues('address')) { return 1 if ($addr && $addr ne 'dhcp'); } # return undefined (ie false) } # return dhclient related files for interface sub generate_dhclient_intf_files { my $intf = shift; my $dhclient_dir = '/var/lib/dhcp3/'; $intf =~ s/\./_/g; my $intf_config_file = $dhclient_dir . 'dhclient_' . $intf . '.conf'; my $intf_process_id_file = $dhclient_dir . 'dhclient_' . $intf . '.pid'; my $intf_leases_file = $dhclient_dir . 'dhclient_' . $intf . '.leases'; return ($intf_config_file, $intf_process_id_file, $intf_leases_file); } sub getInterfaces { opendir (my $sys_class, '/sys/class/net') or die "can't open /sys/class/net: $!"; my @interfaces = grep { ( !/^\./ ) && ( $_ ne 'bonding_masters' ) } readdir $sys_class; closedir $sys_class; return @interfaces; } sub getIP { my ($name, $type) = @_; my @addresses; open my $ipcmd, '-|' or exec qw(ip addr show dev), $name or die "ip addr command failed: $!"; <$ipcmd>; while (<$ipcmd>) { my ($proto, $addr) = split; next unless ($proto =~ /inet/); if ($type) { next if ($proto eq 'inet6' && $type != 6); next if ($proto eq 'inet' && $type != 4); } push @addresses, $addr; } close $ipcmd; return @addresses; } my %type_hash = ( 'broadcast' => 'is_broadcast', 'multicast' => 'is_multicast', 'pointtopoint' => 'is_pointtopoint', 'loopback' => 'is_loopback', ); # getInterfacesIPadresses() returns IPv4 addresses for the interface type # possible type of interfaces : 'broadcast', 'pointtopoint', 'multicast', 'all' # and 'loopback' sub getInterfacesIPadresses { my $type = shift; my $type_func; my @ips; $type or die "Interface type not defined"; if ($type ne 'all') { $type_func = $type_hash{$type}; die "Invalid type specified to retreive IP addresses for: $type" unless $type_func; } foreach my $name (getInterfaces()) { my $intf = new Vyatta::Interface($name); next unless $intf; if (defined $type_func) { next unless $intf->$type_func(); } my @addresses = $intf->address(4); push @ips, @addresses; } return @ips; } sub getNetAddrIP { my $name = shift; my $intf = new Vyatta::Interface($name); $intf or return; foreach my $addr ($intf->addresses()) { my $ip = new NetAddr::IP $addr; next unless ($ip && ip->version() == 4); return $ip; } # default return of undefined (ie false) } sub is_ip_v4_or_v6 { my $addr = shift; my $ip = new NetAddr::IP $addr; return unless defined $ip; my $vers = $ip->version(); if ($vers == 4) { # # NetAddr::IP will accept short forms 1.1 and hostnames # so check if all 4 octets are defined return 4 unless ($addr !~ /\d+\.\d+\.\d+\.\d+/); # undef } elsif ($vers == 6) { return 6; } # default return of undefined (ie false) } sub isIpAddress { my $ip = shift; return unless $ip =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/; return unless ($1 > 0 && $1 < 256); return unless ($2 >= 0 && $2 < 256); return unless ($3 >= 0 && $3 < 256); return unless ($4 >= 0 && $4 < 256); return 1; } sub isClusterIP { my ($vc, $ip) = @_; return unless $ip; # undef my @cluster_groups = $vc->listNodes('cluster group'); foreach my $cluster_group (@cluster_groups) { my @services = $vc->returnValues("cluster group $cluster_group service"); foreach my $service (@services) { if ($ip eq substr($service,0,index($service,'/'))) { return 1; } } } return; } sub remove_ip_prefix { my @addr_nets = @_; s/\/\d+$// for @addr_nets; return @addr_nets; } sub is_ip_in_list { my ($ip, @list) = @_; @list = remove_ip_prefix(@list); my %list_hash = map { $_ => 1 } @list; return $list_hash{$ip}; } sub isIPinInterfaces { my ($vc, $ip_addr, @interfaces) = @_; return unless $ip_addr; # undef == false foreach my $name (@interfaces) { return 1 if (is_ip_in_list($ip_addr, getIP($name))); } # false (undef) } sub isClusteringEnabled { my ($vc) = @_; return $vc->exists('cluster'); } # $str: string representing a port number # returns ($success, $err) # $success: 1 if success. otherwise undef # $err: error message if failure. otherwise undef sub isValidPortNumber { my $str = shift; return (undef, "\"$str\" is not a valid port number") if (!($str =~ /^\d+$/)); return (undef, "invalid port \"$str\" (must be between 1 and 65535)") if ($str < 1 || $str > 65535); return (1, undef); } # $str: string representing a port range # $sep: separator for range # returns ($success, $err) # $success: 1 if success. otherwise undef # $err: error message if failure. otherwise undef sub isValidPortRange { my $str = shift; my $sep = shift; return (undef, "\"$str\" is not a valid port range") if (!($str =~ /^(\d+)$sep(\d+)$/)); my ($start, $end) = ($1, $2); my ($success, $err) = isValidPortNumber($start); return (undef, $err) if (!defined($success)); ($success, $err) = isValidPortNumber($end); return (undef, $err) if (!defined($success)); return (undef, "invalid port range ($end is not greater than $start)") if ($end <= $start); return (1, undef); } # $str: string representing a port name # $proto: protocol to check # returns ($success, $err) # $success: 1 if success. otherwise undef # $err: error message if failure. otherwise undef sub isValidPortName { my $str = shift; my $proto = shift; return (undef, "\"\" is not a valid port name for protocol \"$proto\"") if ($str eq ''); my $port = getservbyname($str, $proto); return (1, undef) if $port; return (undef, "\"$str\" is not a valid port name for protocol \"$proto\""); } sub getPortRuleString { my $port_str = shift; my $can_use_port = shift; my $prefix = shift; my $proto = shift; my $negate = ''; if ($port_str =~ /^!(.*)$/) { $port_str = $1; $negate = '! '; } $port_str =~ s/(\d+)-(\d+)/$1:$2/g; my $num_ports = 0; my @port_specs = split /,/, $port_str; foreach my $port_spec (@port_specs) { my ($success, $err) = (undef, undef); if ($port_spec =~ /:/) { ($success, $err) = isValidPortRange($port_spec, ':'); if (defined($success)) { $num_ports += 2; next; } else { return (undef, $err); } } if ($port_spec =~ /^\d/) { ($success, $err) = isValidPortNumber($port_spec); if (defined($success)) { $num_ports += 1; next; } else { return (undef, $err); } } ($success, $err) = isValidPortName($port_spec, $proto); if (defined($success)) { $num_ports += 1; next; } else { return (undef, $err); } } my $rule_str = ''; if (($num_ports > 0) && (!$can_use_port)) { return (undef, "ports can only be specified when protocol is \"tcp\" " . "or \"udp\" (currently \"$proto\")"); } if ($num_ports > 15) { return (undef, "source/destination port specification only supports " . "up to 15 ports (port range counts as 2)"); } if ($num_ports > 1) { $rule_str = " -m multiport --${prefix}ports ${negate}${port_str}"; } elsif ($num_ports > 0) { $rule_str = " --${prefix}port ${negate}${port_str}"; } return ($rule_str, undef); } 1;