App-ReslirpTunnel

 view release on metacpan or  search on metacpan

lib/App/ReslirpTunnel.pm  view on Meta::CPAN

        my $domain = $record->{domain};
        my $iface = $record->{iface};
        my $method = "_resolve_remote_iface_dns__". (($self->{remote_os} eq 'windows') ? 'windows' : 'unix');
        if (my @addrs = $self->$method($iface)) {
            $self->_log(debug => "DNS servers for remote iface $iface", join(", ", @addrs));
            for my $addr (@addrs) {
                push @{$self->{forward_dns}{$domain} //= []}, $addr;
                $self->{forward_ipv4}{"$addr/32"} = 1;
            }
        }
        else {
            $self->_warn("Failed to retrieve DNS servers using remote shell, ignoring domain", $record->{domain});
        }
    }
}

sub _resolve_remote_iface_dns__unix {
    my $self = shift;
    $self->_warn('Retrieving by iface DNS servers using the shell on remote Unix hosts is not implemented yet');
    ()
}

sub _resolve_remote_iface_dns__windows {
    my ($self, $iface) = @_;
    my $ssh = $self->{ssh};

    my $out = $ssh->capture({remote_shell=> 'MSWin'}, 'powershell', '-Command', "Get-DnsClientServerAddress | ConvertTo-Json");
    my @addrs;
    eval {
        for my $record (@{JSON::PP::decode_json($out)}) {
            if ($record->{InterfaceAlias} eq $iface and
                $record->{AddressFamily} eq '2') {
                push @addrs, @{$record->{ServerAddresses}};
            }
        }
    };
    unless (@addrs) {
        $self->_warn("Failed to parse JSON output from DnsClientServerAddress", $@);
        $self->_log(debug => "Output was", $out);
    }
    return @addrs;
}


sub _config_net_mappings {
    my $self = shift;
    $self->{net_mapping} //= {};
    $self->{forward_ipv4} //= {};
    $self->_config_net_mappings_net;
    $self->_config_net_mappings_direct;
    $self->_config_net_mappings_local;
    $self->_config_net_mappings_dns;
    $self->_config_net_mappings_ssh;
}

sub _config_net_mappings_net {
    my $self = shift;
    for my $record (@{$self->{args}{route_nets}}) {
        my $addr = $record->{addr};
        my $mask = $record->{mask};
        if ($self->_validate_ipv4($addr) and $self->_validate_netmask($mask)) {
            $self->{forward_ipv4}{"$addr/$mask"} = 1;
        }
        else {
            $self->_warn("Ignoring invalid network", "$addr/$mask");
        }
    }
}

sub _config_net_mappings_direct {
    my $self = shift;
    for my $record (@{$self->{args}{route_hosts}}) {
        my $addrs = $record->{addrs} // [];
        $self->{forward_ipv4}{"$_/32"} = 1 for @$addrs;
        if (defined (my $host = $record->{host})) {
            if ($self->_validate_domain_name($host)) {
                push @{$self->{net_mapping}{$host} //= []}, @$addrs;
            }
            else {
                $self->_warn("Ignoring host with invalid name", $host);
            }
        }
    }
}

sub _config_net_mappings_local {
    my $self = shift;
    for my $host (@{$self->{args}{route_hosts_local}}) {
        my $addr;
        if (is_ipv4($host)) {
            $self->{forward_ipv4}{"$host/32"} = 1;
        }
        elsif ($self->_validate_domain_name($host)) {
            my $good;
            my ($err, @records) = Socket::getaddrinfo($host);
            unless ($err) {
                for my $record (@records) {
                    if ($record->{family} == AF_INET) {
                        my (undef, $packed_ip) = sockaddr_in($record->{addr});
                        my $addr = inet_ntoa($packed_ip);
                        push @{$self->{net_mapping}{$host} //= []}, $addr;
                        $self->{forward_ipv4}{"$addr/32"} = 1;
                        $good = 1;
                    }
                }
            }
            $good or $self->_warn("Failed to resolve host, ignoring it", $host);
        }
        else {
            $self->_warn("Ignoring host with invalid name", $host);
        }
    }
}

sub _validate_ipv4 {
    my ($self, $ipv4) = @_;
    is_ipv4($ipv4) and return 1;
    $self->_log(debug => "Bad IPv4", $ipv4);
    return undef;
}

sub _validate_netmask {
    my ($self, $mask) = @_;
    $mask =~ /\d+/ and $mask >= 1 and $mask <= 32 and return 1;
    $self->_log(debug => "Bad netmask", $mask);
    return undef;
}

sub _validate_domain_name {
    my ($self, $domain) = @_;
    is_hostname($domain, {'domain_private_tld' => 1}) and return 1;
    $self->_log(debug => "Bad domain", $domain);
    return undef;
}

sub _config_net_mappings_dns {
    my $self = shift;
    my $route_hosts = $self->{args}{route_hosts_dns};
    if (@$route_hosts) {
        my $dns = Net::DNS::Resolver->new(nameservers => [$self->{remote_dns}], recurse => 1);
        for my $host (@$route_hosts) {
            if ($self->_validate_domain_name($host)) {
                my $good;
                $self->_log(debug => "Resolving $host using remote DNS");
                my $query = $dns->query($host, 'A');
                if ($query) {
                    for my $rr ($query->answer) {
                        if ($rr->type eq 'A') {
                            my $addr = $rr->address;
                            push @{$self->{net_mapping}{$host} //= []}, $addr;
                            $self->{forward_ipv4}{"$addr/32"} = 1;
                            $good = 1;
                        }
                    }
                }
                $good or $self->_warn("Failed to resolve host using remote DNS, ignoring it", $host);
            }
            else {
                $self->_warn("Ignoring host with invalid name", $host);
            }
        }
    }
}

sub _config_net_mappings_ssh {
    my $self = shift;
    my $route_hosts = $self->{args}{route_hosts_ssh};
    for my $host (@$route_hosts) {
        if ($self->_validate_domain_name($host)) {
            $self->_log(debug => "Resolving $host using remote shell");
            my $method = "_resolve_remote_host_with_shell__" . (($self->{remote_os} eq 'windows') ? 'windows' : 'unix');
            my @addrs = $self->$method($host);
            for my $addr (@addrs) {
                push @{$self->{net_mapping}{$host} //= []}, $addr;
                $self->{forward_ipv4}{"$addr/32"} = 1;
            }
            @addrs or $self->_warn("Failed to resolve host using remote DNS, ignoring it", $host);
        }
        else {
            $self->_warn("Ignoring host with invalid name", $host);
        }
    }
}

sub _resolve_remote_host_with_shell__unix {
    my $self = shift;
    $self->_warn('Resolving using the shell on remote Unix hosts is not implemented yet');
    ()
}

sub _resolve_remote_host_with_shell__windows {
    my ($self, $host) = @_;
    my $ssh = $self->{ssh};

    my $out = $ssh->capture({remote_shell=> 'MSWin'}, 'powershell', '-Command', "Resolve-DnsName $host | ConvertTo-Json");
    my @addrs;
    eval {
        my $records = JSON::PP::decode_json($out);
        my @names = $host;
        for my $r (@$records) {
            push @names, $r->{NameHost} if $r->{Type} == 5;
        }
        for my $r (@$records) {
            push @addrs, $r->{IP4Address} if $r->{Type} == 1
        }
        return @addrs
    };
    unless (@addrs) {
        $self->_warn("Failed to parse JSON output from Resolve-DnsName", $@);
        $self->_log(debug => "Output was", $out);
    }
    return @addrs;
}

sub _init_dnsmasq {
    my $self = shift;
    my $net_mapping = $self->{net_mapping};
    my $forward_dns = $self->{forward_dns};

    if (%$net_mapping or %$forward_dns) {
        $self->_log(info => "Starting dnsmasq");

        my $pid_parent_dir = $self->{xdg}->state_home->child('dnsmasq')->mkdir;
        my $pid_fn = $pid_parent_dir->child($self->{timestamp}.".dnsmasq.pid");
        my $latest_fn = $pid_parent_dir->child("latest.dnsmasq.pid");
        unlink $latest_fn if -e $latest_fn;
        symlink $pid_fn, $latest_fn;
        my $log_fn = $self->{xdg}->state_home->child('logs')->mkdir->child($self->{timestamp}.".dnsmasq.log");
        my $butler = $self->{butler};



( run in 1.894 second using v1.01-cache-2.11-cpan-140bd7fdf52 )