Created
May 4, 2022 02:46
-
-
Save afresh1/d8c68e40d9bc48c5a76598027beb72c7 to your computer and use it in GitHub Desktop.
Provide a helper to ssh into an OpenBSD vmm vm by name without knowing what IP vmm assigned. It doesn't allow scp or things like that, haven't figured out how yet.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
use v5.30; | |
use warnings; | |
use OpenBSD::Pledge; | |
use OpenBSD::Unveil; | |
# Copyright (c) 2021 Andrew Hewus Fresh <[email protected]> | |
# | |
# Permission to use, copy, modify, and distribute this software for any | |
# purpose with or without fee is hereby granted, provided that the above | |
# copyright notice and this permission notice appear in all copies. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
# To use this, you copy this to the vm-server, | |
# add something like this to your .ssh/config | |
# and then, `ssh vm-whatever` will connect to the vm-server | |
# which will then `ssh vm`. | |
# | |
# Host vm | |
# RequestTTY yes | |
# HostName vm-server | |
# RemoteCommand /usr/sbin/vmctl-ssh %n | |
my %cmd = ( | |
ifconfig => '/sbin/ifconfig', | |
ssh => '/usr/bin/ssh', | |
vmctl => '/usr/sbin/vmctl', | |
); | |
unveil( $_, 'x' ) for values %cmd; | |
pledge( qw< proc exec > ); | |
my ($host, @args) = @ARGV; | |
($host) = $host =~ /^([-.\w]+)$/ if $host; # untaint, but also security | |
die "Usage $0 vm" unless $host; | |
my $vmid = do { | |
open my $fh, "-|", $cmd{vmctl}, show => $host | |
or die "Unable to spawn vmctl: $!"; | |
readline $fh; # header | |
$_ = readline $fh; | |
/^\s*(\d+)\s/ && $1 if $_; | |
}; | |
die "Can't find VM $host\n" unless defined $vmid; | |
# vmd/priv.c says: | |
# * 1. Set the address prefix and mask, 100.64.0.0/10 by default. | |
# * 2. Encode the VM ID as a per-VM subnet range N, 100.64.N.0/24. | |
# Can't seem to ask vmctl for the prefix, so we assume the default. | |
my $vmnet = ip2num("100.64.$vmid.0"); | |
my $vmmask = 0xff_ff_ff_00; | |
# * 3. Assign a /31 subnet M per VM interface, 100.64.N.M/31. | |
# * Each subnet contains exactly two IP addresses; skip the | |
# * first subnet to avoid a gateway address ending with .0. | |
# Which means we could calculate what the IP it should be | |
# directly from the above vmnet, but instead we look on | |
# the TAP interfaces to find out whether we have | |
# IP that matches this available. | |
my @tap_ips = | |
map { /\s(inet) (\S+) (netmask) (\S+)/ ? { @{^CAPTURE} } : () } | |
do { | |
open my $fh, "-|", $cmd{ifconfig}, 'tap' | |
or die "Unable to spawn ifconfig: $!"; | |
readline $fh; | |
}; | |
my $ip; | |
for (@tap_ips) { | |
my $gw = ip2num( $_->{inet} ); | |
# Look for the first interface in vmnet for this vm | |
next unless ( $gw & $vmmask ) == $vmnet; | |
# Again from vmd/priv.c | |
# * 4. Use the first address for the gateway, the second for the VM. | |
my $vm = $gw + 1; | |
my $nm = hex $_->{netmask}; | |
# Make sure the $vm ip is actually in the $gw network | |
next unless ( $vm & $nm ) == ( $gw & $nm ); | |
$ip = num2ip($vm); | |
last; | |
} | |
die "Unable to find IP for $host\n" unless $ip; | |
exec $cmd{ssh}, $ip, @args; | |
die "Unable to exec ssh $ip @args\n"; | |
sub ip2num { | |
my @o = split /\./, $_[0]; | |
return ( $o[0] << 24 ) | |
+ ( $o[1] << 16 ) | |
+ ( $o[2] << 8 ) | |
+ ( $o[3] << 0 ); | |
} | |
sub num2ip { join '.', map { 0xff & $_[0] >> $_ } 24, 16, 8, 0 } | |
sub netmask2prefix { | |
my $mask = shift; | |
my $prefix = 32; | |
my $full = 2 ** $prefix - 1; | |
$prefix-- while $prefix | |
&& $mask != ( $full & $full << ( 32 - $prefix ) ); | |
return $prefix; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment