#!/usr/bin/perl -CSD

use warnings;
use strict;

use Carp;
use Data::Validate::IP;
use English qw( -no_match_vars );
use Getopt::Long;
use IO::Interface;
use IO::Socket;
use IPC::Open3;
use List::Util qw(any max);
use Net::Netmask;
use Pod::Usage;
use Regexp::IPv6 qw($IPv6_re);
use Socket;
use Storable qw(dclone);
use YAML::XS;

our $VERSION = 'UNRELEASED';

# defaults / option handling {{{
my $host = qx(ngcp-hostname);
chomp $host;
length $host or croak "Fatal error retrieving ngcp_hostname [$host]";

my $advertised_ip;
my $bond_miimon;
my $bond_mode;
my $bond_slaves;
my $broadcast;
my $clone_from;
my $clone_to;
my $dbnode;
my $dhcp;
my $v6dhcp;
my @dns_nameservers;
my $gateway;
my $v6gateway;
my $help;
my $hwaddr;
my $inputfile = '/etc/ngcp-config/network.yml';
my $internal_iface;
my $ip;
my $ip_v6;
my $man;
my $manual;
my $v6manual;
my $move_from;
my $move_to;
my $mtu;
my $netmask;
my $netmask_ip_v6;
my $offload_lro;
my $offload_tso;
my $openvpn_ca_file;
my $openvpn_ca_template;
my $openvpn_ca_inline;
my $openvpn_cert_file;
my $openvpn_cert_template;
my $openvpn_cert_inline;
my $openvpn_key_file;
my $openvpn_key_template;
my $openvpn_key_inline;
my $outputfile = $inputfile;
my $peer;
my @remove_host;
my @remove_interface;
my @roles;
my $rtp_ports,
my @set_interface;
my $shared_ip;
my $shared_ip_only;
my $shared_ip_v6;
my $shared_ipv6_only;
my $sysname;
my $sysdescr;
my $syslocation;
my @type;
my $verbose;
my $version;
my $vlan_raw_device;

GetOptions(
    'advertised-ip=s'    => \$advertised_ip,
    'bond-miimon=s'      => \$bond_miimon,
    'bond-mode=s'        => \$bond_mode,
    'bond-slaves=s'      => \$bond_slaves,
    'broadcast=s'        => \$broadcast,
    'clone-from=s'       => \$clone_from,
    'clone-to=s'         => \$clone_to,
    'dbnode:i'           => \$dbnode,
    'dhcp=s'             => \$dhcp,
    'dhcp-ipv6=s'        => \$v6dhcp,
    'dns=s'              => \@dns_nameservers,
    'gateway=s'          => \$gateway,
    'gateway-ipv6=s'     => \$v6gateway,
    'help'               => \$help,
    'host=s'             => \$host,
    'hwaddr=s'           => \$hwaddr,
    'input-file=s'       => \$inputfile,
    'internal-iface=s'   => \$internal_iface,
    'ip=s'               => \$ip,
    'ipv6=s'             => \$ip_v6,
    'man'                => \$man,
    'manual=s'           => \$manual,
    'manual-ipv6=s'      => \$v6manual,
    'move-from=s'        => \$move_from,
    'move-to=s'          => \$move_to,
    'mtu=s'              => \$mtu,
    'netmask=s'          => \$netmask,
    'netmask-ipv6=s'     => \$netmask_ip_v6,
    'offload-lro=s'      => \$offload_lro,
    'offload-tso=s'      => \$offload_tso,
    'openvpn-ca-file=s'  => \$openvpn_ca_file,
    'openvpn-ca-template=s' => \$openvpn_ca_template,
    'openvpn-ca-inline=s' => \$openvpn_ca_inline,
    'openvpn-cert-file=s' => \$openvpn_cert_file,
    'openvpn-cert-template=s' => \$openvpn_cert_template,
    'openvpn-cert-inline=s' => \$openvpn_cert_inline,
    'openvpn-key-file=s' => \$openvpn_key_file,
    'openvpn-key-template=s' => \$openvpn_key_template,
    'openvpn-key-inline=s' => \$openvpn_key_inline,
    'output-file=s'      => \$outputfile,
    'peer=s'             => \$peer,
    'remove-host=s'      => \@remove_host,
    'remove-interface=s' => \@remove_interface,
    'rtp-ports=s'        => \$rtp_ports,
    'role=s'             => \@roles,
    'set-interface=s'    => \@set_interface,
    'shared-ip=s'        => \$shared_ip,
    'shared-ip-only=s'   => \$shared_ip_only,
    'shared-ipv6=s'      => \$shared_ip_v6,
    'shared-ipv6-only=s' => \$shared_ipv6_only,
    'sysname=s'          => \$sysname,
    'sysdescr=s'         => \$sysdescr,
    'syslocation=s'      => \$syslocation,
    'type=s'             => \@type,
    'verbose'            => \$verbose,
    'version'            => \$version,
    'vlan-raw-device=s'  => \$vlan_raw_device,
) or pod2usage(2);

if ($help) {
    pod2usage(0);
}

if ($version) {
    print "$PROGRAM_NAME, v$version\n";
    exit 0;
}

if ($man) {
    pod2usage( -exitstatus => 0, -verbose => 2 );
}

# validate input
if ($ip) {
    logger("validating specified IP address $ip");
    if ( is_ipv4($ip) || $ip =~ /^(auto|none|delete)$/msx ) {
        logger('valid IPv4 address');
    }
    else {
        croak "Specified IP $ip is not a valid IPv4 address";
    }
}

if ($ip_v6) {
    logger("validating specified IP address $ip_v6");
    if ( is_ipv6($ip_v6) || $ip_v6 =~ /^(auto|none|delete)$/msx ) {
        logger('valid IPv6 address');
    }
    else {
        croak "Specified IP $ip_v6 is not a valid IPv6 address";
    }
}

foreach my $opt (
    $bond_miimon,      $bond_mode,     $broadcast, $clone_from,
    $clone_to,         $dbnode,        @dns_nameservers,
    $gateway,          @set_interface, $host,      $hwaddr,
    $internal_iface,   $ip,            $ip_v6,     $move_from,
    $move_to,          $netmask,       $peer,      @remove_host,
    @remove_interface, @roles,         @type,      $vlan_raw_device,
    $netmask_ip_v6,    $v6gateway,     $mtu,       $v6dhcp,
    $manual,           $v6manual,      $shared_ip_only,
    $shared_ipv6_only
  )
{
    if ( defined $opt && $opt =~ /\s/msx ) {
        logger("invalid option argument $opt");
        croak "Command line option does not accept whitespace in '$opt'.";
    }
}

my ($rtp_port_min, $rtp_port_max);
if (defined $rtp_ports) {
    if ($rtp_ports =~ /^none$|^delete$/msx) {
        $rtp_port_min = "none";
        $rtp_port_max = "none";
    }
    elsif ($rtp_ports =~ /^(\d+),(\d+)$/msx) {
        ($rtp_port_min, $rtp_port_max) = ($1, $2);
        if ($rtp_port_min <= 0 || $rtp_port_max <= 0
                || $rtp_port_min > 65535 || $rtp_port_max > 65535
                || $rtp_port_min >= $rtp_port_max) {
            logger("invalid RTP port range $rtp_ports");
            croak "Invalid RTP port range '$rtp_ports'";
        }
    }
    else {
        logger("invalid RTP port range $rtp_ports");
        croak "Invalid RTP port range '$rtp_ports'";
    }
}

# }}}

logger("reading input file $inputfile");

my $yaml = YAML::XS::LoadFile($inputfile)
  or croak "File $inputfile could not be read";

if ( -e "$outputfile" && !-w "$outputfile" ) {
    croak "Could not open $outputfile for writing (are you user root?)";
}

my $spce;
if ( defined $yaml->{hosts}->{self} ) {
    logger('host "self" identified and set, assuming spce system');
    $host = 'self';
    $spce = 1;
}

# helper functions {{{
sub logger {
    if ($verbose) {
        print "@_\n" or croak 'Error sending log output';
    }
    return;
}

sub get_interface_details {
    my $interface = shift or croak "Usage: $PROGRAM_NAME <interface> <setting>";
    my $setting   = shift or croak "Usage: $PROGRAM_NAME <interface> <setting>";

    my $s = IO::Socket::INET->new( Proto => 'tcp' );
    return $s->$setting($interface);
}

sub get_ip6_addrs {

    my $interface = shift or croak 'Usage: get_ip6_addrs <interface>';
    my $cmd = 'ip';
    my @args = ( '-6', 'addr', 'show', 'dev', $interface, 'scope', 'global' );

    logger("get_ip6_addr for device $interface");
    logger("$cmd @args");

    my $childpid = open3 'HIS_WRITE', 'HIS_OUT', 'HIS_ERR', $cmd, @args;
    my @stderr   = <HIS_ERR>;
    my @stdout   = <HIS_OUT>;

    logger("stdout: @stdout");
    logger("stderr: @stderr");

    close HIS_OUT or croak 'Failed to close stdout';
    close HIS_ERR or croak 'Failed to close stderr';

    waitpid $childpid, 0;
    if ($CHILD_ERROR) {
        croak "Problem with execution [return code $CHILD_ERROR]:\n@stderr";
    }

    return @stdout;
}

sub get_ip6_addr {

    my @stdout = get_ip6_addrs(@_);

    foreach my $line (@stdout) {
        if ( $line =~ /^\s*inet6\s+($IPv6_re)\/\d+.*scope.*global.*$/msx ) {
            return $1;
        }
    }

    return;
}

sub get_ip6_mask {

    my @stdout = get_ip6_addrs(@_);

    foreach my $line (@stdout) {
        if ( $line =~ /^\s*inet6\s+($IPv6_re)\/(\d+).*scope.*global.*$/msx ) {
            return $2;
        }
    }

    return;
}

sub set_interface {
    my $iface = shift;

    logger("set_interface: interface = $iface");
    logger("set_interface: host = $host");

    if ( defined $ip && $ip =~ /^auto$/msx ) {
        logger("get_interface_details( $iface, 'if_addr' );");
        $ip = get_interface_details( $iface, 'if_addr' );
    }

    if ( defined $ip_v6 && $ip_v6 =~ /^auto$/msx ) {
        logger("get_ip6_addr( $iface );");
        $ip_v6 = get_ip6_addr($iface);
    }

    if ( defined $netmask && $netmask =~ /^auto$/msx ) {
        logger("get_interface_details( $iface, 'if_netmask' );");
        $netmask = get_interface_details( $iface, 'if_netmask' );
    }

    if ( defined $netmask_ip_v6 && $netmask_ip_v6 =~ /^auto$/msx ) {
        logger("get_ip6_mask( $iface );");
        $netmask_ip_v6 = get_ip6_mask($iface);
    }

    if ( defined $hwaddr && $hwaddr =~ /^auto$/msx ) {
        logger("get_interface_details( $iface, 'if_hwaddr' );");
        $hwaddr = get_interface_details( $iface, 'if_hwaddr' );
    }

    my $settings = {
        'advertised_ip'   => $advertised_ip,
        'bond_miimon'     => $bond_miimon,
        'bond_mode'       => $bond_mode,
        'bond_slaves'     => $bond_slaves,
        'broadcast'       => $broadcast,
        'dhcp'            => $dhcp,
        'gateway'         => $gateway,
        'hwaddr'          => $hwaddr,
        'ip'              => $ip,
        'manual'          => $manual,
        'mtu'             => $mtu,
        'netmask'         => $netmask,
        'offload_lro'     => $offload_lro,
        'offload_tso'     => $offload_tso,
        'openvpn_ca_file' => $openvpn_ca_file,
        'openvpn_ca_template' => $openvpn_ca_template,
        'openvpn_ca_inline' => $openvpn_ca_inline,
        'openvpn_cert_file' => $openvpn_cert_file,
        'openvpn_cert_template' => $openvpn_cert_template,
        'openvpn_cert_inline' => $openvpn_cert_inline,
        'openvpn_key_file' => $openvpn_key_file,
        'openvpn_key_template' => $openvpn_key_template,
        'openvpn_key_inline' => $openvpn_key_inline,
        'v6netmask'       => $netmask_ip_v6,
        'rtp_port_max'    => $rtp_port_max,
        'rtp_port_min'    => $rtp_port_min,
        'shared_ip'       => $shared_ip,
        'shared_ip_only'  => $shared_ip_only,
        'shared_v6ip'     => $shared_ip_v6,
        'shared_v6ip_only'=> $shared_ipv6_only,
        'v6dhcp'          => $v6dhcp,
        'v6gateway'       => $v6gateway,
        'v6ip'            => $ip_v6,
        'v6manual'        => $v6manual,
        'vlan_raw_device' => $vlan_raw_device,
    };

    foreach my $k ( keys %{$settings} ) {
        if ( defined $settings->{$k} ) {
            if ( $settings->{$k} =~ /^none$/msx ) {
                logger("unsetting entry $k");
                undef $yaml->{hosts}->{$host}->{$iface}->{$k};
            }
            elsif ( $settings->{$k} =~ /^delete$/msx ) {
                logger("deleting entry $k");
                delete $yaml->{hosts}->{$host}->{$iface}->{$k};
            }
            else {
                if($k eq 'shared_ip' || $k eq 'shared_v6ip' || $k eq 'advertised_ip') {
                    if (my ($matched) = grep { $_ eq $settings->{$k} } @{ $yaml->{hosts}->{$host}->{$iface}->{$k} }) {
                        logger("not setting $k to $matched to avoid duplicates");
                    } else {
                        logger("adding IP entry $k: $settings->{$k}");
                        push @{ $yaml->{hosts}->{$host}->{$iface}->{$k} }, $settings->{$k}
                    }
                }
                else {
                    logger("adding entry $k: $settings->{$k}");
                    $yaml->{hosts}->{$host}->{$iface}->{$k} = $settings->{$k};
                }
            }
        }
    }

    if (@dns_nameservers) {
        foreach my $dns (@dns_nameservers) {
            logger("set_iface_config( $iface, 'dns_nameservers', $dns)");
            set_iface_config( $iface, 'dns_nameservers', $dns );
        }
    }

    if (@type) {
        foreach my $type (@type) {
            logger("set_iface_config( $iface, 'type', $type)");
            set_iface_config( $iface, 'type', $type );
        }
    }

    # add interface to list of available interfaces
    my $ifaces = $yaml->{hosts}->{$host}->{interfaces};
    if ( !defined $ifaces ) {
        logger("no interfaces defined yet, adding interface $iface");
        $yaml->{hosts}->{$host}->{interfaces}->[0] = "$iface";
    }
    else {
        logger("interface = $iface");

        if ( any { /^${iface}$/msx } @{$ifaces} ) {
            logger("interface $iface already listed on host $host");
        }
        else {
            push @{$ifaces}, $iface;
            logger("adding $iface to list of interfaces on host $host");
        }
    }

    return;
}

sub remove_interface {
    my $rem_iface = shift or croak 'Usage: remove_interface <interface>';

    delete $yaml->{hosts}->{$host}->{$rem_iface};

    my $ifaces = $yaml->{hosts}->{$host}->{interfaces};
    if ( defined $ifaces ) {
        logger("removing interface $rem_iface from @$ifaces as requested");
        @{$yaml->{hosts}->{$host}->{interfaces}} = grep {$_ ne $rem_iface} @$ifaces;
    }
    return;
}

sub set_host_key {
    my $key   = shift or croak 'Usage: set_host_key <key> <value>';
    my $value = shift or croak 'Usage: set_host_key <key> <value>';

    $yaml->{hosts}->{$host}->{$key} = "$value";
    logger("\$yaml->{hosts}->{\$host}->{$key} = $value");
    return;
}

sub set_host_key_cond {
    my ($key, $value) = @_;

    if ( defined $value ) {
        logger("set_host_key('$key', $value)");
        set_host_key($key, $value);
    }
}

sub set_iface_config {
    my $iface = shift
      or croak 'Usage: set_iface_config <iface> <setting> <value>';
    my $setting = shift
      or croak 'Usage: set_iface_config <iface> <setting> <value>';
    my $value = shift
      or croak 'Usage: set_iface_config <iface> <setting> <value>';

    if ( any { /^${value}$/msx }
        @{ $yaml->{hosts}->{$host}->{$iface}->{$setting} } )
    {
        logger("$value for $setting on $iface already defined in host $host");
    }
    else {
        push @{ $yaml->{hosts}->{$host}->{$iface}->{$setting} }, $value;
        logger(
            "\$yaml->{hosts}->{\$host}->{$iface}->{$setting} => $value");
    }

    return;
}

sub set_role {
    my $new_role = shift or croak 'Usage: set_role <value>';

    if ( any { /^${new_role}$/msx } @{ $yaml->{hosts}->{$host}->{role} } )
    {
        logger("role $new_role already defined in host $host");
    }
    else {
        push @{ $yaml->{hosts}->{$host}->{role} }, $new_role;
        logger("\$yaml->{hosts}->{\$host}->{role} => $new_role");
    }

    return;
}

sub set_dbnode {
    my $new_dbnode = shift;
    my @nodes = ( 0, );

    foreach my $h (keys %{ $yaml->{hosts} }) {
        if($h ne $host && defined $yaml->{hosts}->{$h}->{dbnode}) {
            push @nodes, $yaml->{hosts}->{$h}->{dbnode};
        }
    }
    # if no value is given we have 0
    if($new_dbnode > 0) {
        if (scalar(@nodes) > 0 && any { $_ == $new_dbnode } @nodes ) {
            croak "dbnode already in use";
        }
    }
    else {
        my $max = max @nodes;
        $new_dbnode = $max + 1;
        logger("use $new_dbnode");
    }
    set_host_key('dbnode', $new_dbnode);
    return;
}

sub clone_settings {
    my $from = shift;
    my $to   = shift;

    if ( defined $from && !defined $to ) {
        croak '--clone-from option must be used with --clone-to option together';
    }

    if ( !defined $from && defined $to ) {
        croak '--clone-to option must be used with --clone-from option together';
    }

    # ha, nothing to do for us
    if ( !defined $from && !defined $to ) {
        return;
    }

    logger("clone from = $from");
    logger("clone to = $to");

    if ( !defined $yaml->{hosts}->{$from} ) {
      croak "Host '$from' doesn\'t exist, refusing to clone settings.";
    }

    $yaml->{hosts}->{$to} = dclone($yaml->{hosts}->{$from});

    # adjust peer on-the-fly to prevent user mistakes
    if ( defined $yaml->{hosts}->{$from}->{peer} ) {

      if ( $to !~ /[ab]$/ ) {
        carp "Target host $from not ending with 'a' or 'b', skipping automatic peer config.";
      } else {
        logger('Target host matching ".*[ab]$" pattern');

        my $other_node;
        if ( $to =~ /a$/ ) {
          $other_node = $to =~ s/a$/b/r;
        } else {
          $other_node = $to =~ s/b$/a/r;
        }
        print "Setting peer for host '$to' to '$other_node'.\n";
        $yaml->{hosts}->{$to}->{peer} = $other_node;
      }
    }

    print "Finished cloning host section '$from' to '$to'.\n" or croak 'Output error';
    print "Please do not forget to manually adjust '$outputfile'!\n" or croak 'Output error';

    return;
}


sub move_settings {
    my $from = shift;
    my $to   = shift;

    if ( defined $from && !defined $to ) {
        croak '--move-from option must be used with --move-to option together';
    }

    if ( !defined $from && defined $to ) {
        croak '--move-to option must be used with --move-from option together';
    }

    # ha, nothing to do for us
    if ( !defined $from && !defined $to ) {
        return;
    }

    logger("from = $from");
    logger("to = $to");

    if (@roles) {
        foreach my $role (@roles) {
            logger("role = $role");

            # get rid of the entry from the old section
            my @tmp =
              grep { !/^${role}$/msx }
              @{ $yaml->{hosts}->{$from}->{role} };
            $yaml->{hosts}->{$from}->{role} = [];
            push @{ $yaml->{hosts}->{$from}->{role} }, @tmp;

            # add it to its new place
            push @{ $yaml->{hosts}->{$to}->{role} }, $role;
        }
    }

    if (@type) {
        foreach my $type (@type) {
            logger("type = $type");

            # get rid of the entry from the old section
            my @tmp =
              grep { !/^${type}$/msx }
              @{ $yaml->{hosts}->{$host}->{$from}->{type} };
            $yaml->{hosts}->{$host}->{$from}->{type} = [];
            push @{ $yaml->{hosts}->{$host}->{$from}->{type} }, @tmp;

            # add it to its new place, but only if not already defined yet
            if ( any { /^${type}$/msx }
                @{ $yaml->{hosts}->{$host}->{$to}->{type} } )
            {
                logger("type $type is already defined on host $host, interface $to");
            }
            else {
                push @{ $yaml->{hosts}->{$host}->{$to}->{type} }, $type;
            }
        }
    }

    return;
}

# }}}

# main execution {{{
if (@set_interface) {
    foreach my $interface (@set_interface) {
        logger("set_interface($interface)");
        set_interface($interface);
    }
}

if (@remove_host) {
    foreach my $rhost (@remove_host) {
        logger("removing host $rhost");
        delete $yaml->{hosts}->{$rhost};
    }
}

if (@remove_interface) {
    foreach my $riface (@remove_interface) {
        logger("remove_interface($riface)");
        remove_interface($riface);
    }
}

if ( !defined $move_from || !defined $move_to ) {
    if ( @roles ) {
        foreach my $role (@roles) {
            logger("set_role($role)");
            set_role($role);
        }
    }

    set_host_key_cond('peer', $peer);
    set_host_key_cond('internal_iface', $internal_iface);

    if ( defined $dbnode ) {
        logger("set_dbnode($dbnode)");
        set_dbnode($dbnode);
    }

    set_host_key_cond('sysname', $sysname);
    set_host_key_cond('sysdescr', $sysdescr);
    set_host_key_cond('syslocation', $syslocation);
}


move_settings( $move_from, $move_to );
clone_settings ( $clone_from, $clone_to );

logger("writing output file $outputfile");
YAML::XS::DumpFile($outputfile, $yaml)
  or croak "Could not write YAML to $outputfile";

# }}}

__END__

=head1 NAME

ngcp-network - command line interface to ngcp's network configuration settings

=head1 SYNOPSIS

B<ngcp-network> [I<options>...]

=head1 DESCRIPTION

B<This program> will read the given input file(s) and do something
useful with the contents thereof.

=head1 OPTIONS

=over 8

=item B<--advertised-ip>=I<IP>

Set advertised_ip configuration to specified argument.

=item B<--bond-miimon>=I<setting>

Set bond_miimon configuration to specified argument.

=item B<--bond-mode>=I<mode>

Set bond_mode configuration to specified argument.

=item B<--bond-slaves>=I<slaves>

Set bond_slaves configuration to specified argument.

=item B<--broadcast>=I<IP>

Set broadcast configuration to specified argument.

=item B<--clone-from>=I<HOST>

Clone specified I<HOST> section, using specified I<HOST> setting as its source.
Please do not forget to manually adjust the resulting configuration file, no
further checks like duplicated IPs are performed. Needs to be used in
combination with the B<--clone-to> option.

This option is useful especially in carrier setups with plenty of similar
web/db/proxy/.... systems, where the host definition of a host like B<prx01a>
should be used as base for another host B<prx02a>. Usage example:
B<--clone-from=prx01a --clone-to=prx02a>.

=item B<--clone-to>=I<HOST>

Clone specified I<HOST> section, using specified I<HOST> setting as its
destination.
Refer to B<--clone-from> for further information.

=item B<--dbnode>=I<ID>

Set dbnode configuration to specified number argument. If no value
is provide it will use the next value available (max + 1).

=item B<--dhcp>=I<yes|no>

Set dhcp to yes or no, to use dhcp to set up this interface.

=item B<--dhcp-ipv6>=I<yes|no>

Set dhcp to yes or no, to use dhcp to set up this interface with IPv6.

=item B<--dns>=I<nameserver>

Set dns_nameservers configuration to specified argument.
Can be specified multiple times in one single command line.

=item B<--gateway>=I<IP>

Set gateway configuration to specified argument.

=item B<--gateway-ipv6>=I<IP>

Set gateway configuration to specified argument.

=item B<--host>=I<name>

Apply configuration changes for specified host entry instead of using the
hostname of the currently running system.  NOTE: If running the sip:provider CE
edition this configuration option can't be changed as the only configured host
is set to and supposed to be B<self> there.

=item B<--hwaddr>=I<MAC_address>

Set hwaddr configuration (MAC address of network device) to specified argument.

=item B<--input-file>=I<filename>

Use specified file as input, defaults to F</etc/ngcp-config/network.yml> if
unset.

=item B<--internal-iface>=I<name>

Set internal-iface configuration to specified argument.

=item B<--ip>=I<IP>

Set ip configuration (IPv4 address) to specified argument. If set to B<auto>
and the selected interface is available on the running host then the IP
address will be determined based on its current settings.

=item B<--ipv6>=I<IP>

Set ip configuration (IPv6 address) to specified argument. If set to B<auto>
and the selected interface is available on the running host then the IP
address will be determined based on its current settings.

=item B<--manual>=I<yes|no>

Set interface method set-up to "manual" (man interfaces(5)) for IPv4.

=item B<--manual-ipv6>=I<yes|no>

Set interface method set-up to "manual" (man interfaces(5)) for IPv6.

=item B<--move-from>=I<source>

Move item from specified level (being host for B<--role> and interface for
B<--type>). The item needs to be chosen via B<--type> or B<--role>. To be
used in combination with B<--move-to>.

=item B<--move-to>=I<target>

Move item to specified level (being host for B<--role> and interface for
B<--type>). The item needs to be chosen via B<--type> or B<--role>. To be
used in combination with B<--move-to>.

=item B<--mtu>=I<MTU>

Set MTU configuration (Maximum Transmission Unit of the network device) to specified argument.

=item B<--netmask>=I<IP>

Set netmask configuration to specified argument.

=item B<--netmask-ipv6>=I<length>

Set IPv6 netmask (prefix length) configuration to specified argument.

=item B<--offload-lro>=I<yes|no>

Set Large Receive Offload (LRO) to yes or no (on/off) for this interface.

=item B<--offload-tso>=I<yes|no>

Set TCP Segmentation Offload (TSO) to yes or no (on/off) for this interface.

=item B<--openvpn->I<what>B<->I<how>=I<value>

Set one of the OpenVPN config options. I<what> can be one of B<ca> (certificate authority PEM),
B<cert> (certificate PEM), or B<key> (private key PEM). I<how> can be one of B<file>
(name of a local file including full path), B<template> (same as B<file> but the name can include
placeholders, such as B<%h>, B<%c>, etc), or B<inline> (the actual contents of a PEM file).

A PEM specified as B<inline> takes precedence over a file specified via B<file>, which in turn
takes precendce over a file specified via B<template>.

Usage examples:

  --openvpn-ca-file=/etc/openvpn/foobar.bundle
  --openvpn-cert-template=/etc/openvpn/%c-%h.crt
  --openvpn-key-inline="$(</root/openvpn.key)"

=item B<--output-file>=I<filename>

Store resulting file under specified argument. If unset defaults to
F</etc/ngcp-config/network.yml>.

=item B<--peer>=I<nodename>

Set peer configuration (being the corresponding other node in a PRO setup) to
specified argument.

=item B<--remove-host>=I<host>

Remove the specified host from the configuration file.
Can be specified multiple times (B<--remove-host=sp1 --remove-host=sp2 ...>).

=item B<--remove-interface>=I<interface>

Remove the specified interface from the configuration file.
Can be specified multiple times (B<--remove-interface=eth5 --remove-interface=eth6 ...>).

=item B<--role>=I<role>

Set role configuration for host to specified argument.
Can be specified multiple times (B<--role=lb --role=proxy ...>).

=item B<--rtp-ports>=I<min>,I<max>B<|none|delete>

Set a port range to be used for RTP media if different from the globally set
port range. Set to B<none> or B<delete> to remove the setting and revert to the
global port range.

=item B<--set-interface>=I<name>

Add specified network interface. Can be combined with options like B<--hwaddr>,
B<--ip>, B<--netmask>,... to set specified arguments as configuration options
for the given network interface.

NOTE: If multiple B<--set-interface> options are specified in one command line
(e.g. 'B<--set-interface=lo --set-interface=eth0 --set-interface=eth1>') then
options like B<--hwaddr> cannot be sanely combined with different settings on
multiple interfaces. Instead invoke ngcp-network with the B<--set-interface>
option multiple times.

=item B<--shared-ip>=I<IP>

Set shared_ip configuration to specified argument.

=item B<--shared-ip-only>=I<yes|no>

Allow or disallow usage of having just a shared IP address configured without
static IP address.

=item B<--shared-ipv6>=I<IP>

Set shared_v6ip configuration to specified argument.

=item B<--shared-ipv6-only>=I<yes|no>

Same as B<--shared-ip-only> but for IPv6 addresses.

=item B<--sysname>=I<name>

The SNMP system name for this host. If not specified, the SNMP daemon will
default to use a name based on the hostname.

=item B<--sysdescr>=I<descr>

The SNMP system description for this host. If not specified the SNMP daemon
will default to a description including the current NGCP version, and
the current host roles.

=item B<--syslocation>=I<location>

The SNMP system location for this host. If not specified the SNMP daemon
will default to a generic location.

=item B<--type>=I<name>

Set type configuration to specified argument.
Can be specified multiple times in one single command line.

=item B<--vlan-raw-device>=I<device>

Set vlan_raw_device configuration to specified argument.

=item B<--verbose>

Be more verbose about execution.

=item B<--version>

Display program version and exit.

=item B<--man>

Prints the manual page and exits.

=item B<--help>

Print the help message and exit.

=back

=head1 REQUIRED ARGUMENTS

Depending on the setting you are trying to apply there are different sets of
required arguments. Some examples:

  --move-from=INTERFACE1 --move-to=INTERFACE2 --type=XXX

Move specified type setting XXX from INTERFACE1 section to INTERFACE2.

  --move-from=HOST1 --move-to=HOST2 --role=XXX

Move specified role setting XXX from HOST1 to HOST2.

  --set-interface=INTERFACE --ip=[203.0.113.42|auto|none|delete] --netmask=[255.25.255.0|auto|none|delete] ...

Configure IP, netmask,... on specified INTERFACE.

=head1 DIAGNOSTICS

=head2 Unknown option: ...

The specified command line option is not support.

=head2 File ... could not be read ...

The specified input file doesn't exist or can't be read.

=head2 Fatal error retrieving ngcp_hostname [...]

No valid ngcp_hostname could be retrieved from ngcp-hostname.

=head2 Specified IP ... is not a valid IPv4 address

The specified IP address is not considered a valid IPv4 address.

=head2 Specified IP ... is not a valid IPv6 address

The specified IP address is not considered a valid IPv6 address.

=head2 Command line option does not accept whitespace in ...

An argument to a command line option contains whitespace(s),
which isn't supported to avoid problems with the YAML file format.

=head2 Could not open [...] for writing (are you user root?)

The configuration file couldn't be stored, usually caused by user executing the
program not having write permissions on the file.

=head2 Error sending log output

Using the --verbose option the more detailed information couldn't be printed to
the console.

=head2 --move-from option must be used with --move-to option together

The --move-from option was specified in the command line but the --move-to
option is missing.

=head2 --move-to option must be used with --move-from option together

The --move-to option was specified in the command line but the  --move-from
option is missing.

=head2 Could not open ... for writing

The file can't be written, usually caused because the user doesn't have write
permissions on the file.

=head2 Could not write YAML to ...

There is an error in storing the configuration in the YAML format.

=head1 EXIT STATUS

Exit code 0 means that everything should have went fine.
Exit code 2 means something with the command line options or retrieving default settings went wrong.
Exit code 9 means the specified argument to a command line option is not valid.

=head1 CONFIGURATION

There's no configuration file for the ngcp-network script itself supported at the moment.
The main configuration file ngcp-network operates on is /etc/ngcp-config/network.yml.

=head1 EXAMPLE

Usage examples useful especially on sip:provider CE systems:

  ngcp-network --set-interface=eth0 --ip=192.168.23.42 --netmask=255.25.255.248
  ngcp-network --set-interface=eth1 --ip=auto --netmask=auto

Usage examples useful especially on sip:provider PRO systems:

  ngcp-network --set-interface=lo --set-interface=eth0 --set-interface=eth1 --ip=auto --netmask=auto

  ngcp-network --peer=sp2
  ngcp-network --host=sp2 --peer=sp1

  ngcp-network --set-interface=eth1 --ip=auto --netmask=auto
  ngcp-network --move-from=lo --move-to=eth1 --type=ha_int

  ngcp-network --set-interface=eth1 --host=sp2 --ip=192.168.255.252 --netmask=255.255.255.248 --type=ha_int

Usage examples useful especially on sip:carrier systems:

  ngcp-network --host=proxy2 --set-interface=eth0 --ip=192.168.10.42 --netmask=255.255.255.0
  ngcp-network --clone-from=prx01a --clone-to=prx02a

=head1 BUGS AND LIMITATIONS

Please report problems you notice to the Sipwise Development Team <support@sipwise.com>.

=head1 AUTHOR

Michael Prokop <mprokop@sipwise.com>

=head1 LICENSE

GPL-3+, Sipwise GmbH, Austria

=cut
