23

I have a server, named gamma, constantly up and running at work. Sometimes I connect to it from at home, in which case I use the public IP address 55.22.33.99. Sometimes, I connect to it when I'm at work, and rather than bounce my packets around unnecessarily, I connect via the local IP address, 192.168.1.100.

At the moment, I have them split up into two different entries in ~/.ssh/conf

Host gamma-local
        HostName 192.168.1.100
        Port 22
        User andreas

Host gamma-remote
        HostName 55.22.33.99
        Port 12345
        User andreas

So, if I'm at work, all I have to type is ssh gamma-local and I'm in; if I'm at home (or anywhere else in the world), I run ssh gamma-remote.

When connecting to the server, I would rather not have to type in a different name depending on where I am, I would rather that part be done automatically; for instance, in some cases I have automated scripts that connect who don't know where I am.

There is a question that solves this problem by using a Bash script to "try" to connect to the local one first, and if it doesn't connect, try to connect to the remote IP address. This is nice, but (1) seems inefficient (especially since sometimes you have to "wait" for connections to time out as they don't always send an error back immediately) and (2) requires Bash and lugging around the script.

Is there an alternate way of achieving this that doesn't rely on the use of Bash scripts, nor "testing" to see if the connection works first?

  • What I'm trying to find out is if one can fiddle with /etc/hosts or the SSH config file to achieve this? Or perhaps some way of "detecting" which LAN you are currently connected to? – IQAndreas Dec 22 '14 at 9:30
  • Do you have a separate set of nameservers used by your office network? – Sree Dec 22 '14 at 9:38
  • @Sree Ah, I see where you are heading with this; clever! We aren't running a nameserver at the moment, but I could definitely convert one of the machines into one. – IQAndreas Dec 22 '14 at 10:19
  • @Sree Feel free to elaborate and add that as an answer that begins with the line "If you have a nameserver on your office network..." – IQAndreas Dec 22 '14 at 10:20
  • Did that. Feel free to up/down vote :) – Sree Dec 22 '14 at 12:14
28

If you have a way to recognize which network are you on then you can use the Match keyword in ~/.ssh/config to do what you want. This requires OpenSSH ≥6.5.

I use something similar to

Match originalhost gamma exec "[ x$(/sbin/iwgetid --scheme) != xMyHomeESSID ]"
  HostName 192.168.1.100
  Port 22

Host gamma
  User andreas
  Port 12345
  HostName 55.22.33.99

So I'm using the identifier of the used wifi network to decide whether I'm at home for the purposes of the SSH connection, but checking the IP address assigned to you computer or anything else that differentiates the two networks can be used as well.

  • +1 clever use of Match, thanks! It might be a good idea to wrap the detection into a script exiting with zero or non-zero exit status, since that would make it reusable. – peterph Dec 22 '14 at 15:11
  • @peterph it certainly could be abstracted to an external script, but I only needed in one place until now, so the rule of three has not kicked in yet. – Michał Politowski Dec 22 '14 at 15:23
  • What would happen if you were at work, but you actually want to connect to some other machine? Wouldn't the exec statement match since you're not on WiFi with your HomeESSID, and therefore set the hostname to 192.168.1.100? – gone Oct 31 '17 at 12:25
  • @gone I don't understand the question. The match is also on target host. – Michał Politowski Oct 31 '17 at 12:31
  • 1
    @typelogic Not easily, as far as I know. You could try to use ProxyCommand nc $address ssh, but then eg. do all the devices have the same host key? Ad if you have to set an environment variable anyway, will it not be easier just to give the address to connect to directly as an argument to ssh? – Michał Politowski Mar 8 at 16:33
5

If you happen to have a private nameservers at work and if you use the same laptop from office and home, you can take advantage of that to achieve this:

  1. Modify your nsswitch.conf in your machine to check in the DNS first
  2. Create a DNS entry for gamma to resolve to 192.168.1.100 in your private DNS at office.
  3. Create an entry in the /etc/hosts file of your machine to resolve gamma to 55.22.33.99.

This way, when you ssh gamma from office, it will resolve from the office DNS to the 192.168.1.100 and when you connect from home, it will resolve to 55.22.33.99 from your hosts file.

P.S: This answer assumes that you don't want gamma to have a public DNS entry. Also, if you are SSHing to your server from a Windows machine, I think there should be some place equivalent to nssswitch.conf file to override the hosts file entries.

  • There is - it is the hosts file. And this exactly how I do it - when at home my public domain resolves to my local ip. Excellent answer - it was the first thing I thought of too. Well... today that is, when I set it up a couple of years ago I had to google a lot to find a solution. – mikeserv Dec 22 '14 at 21:03
2

I don't know if it is possible to do this through ~/.ssh/config but another approach would be to connect to one or the other based on your external IP address. Since, presumably, when you're at work, your IP will be 55.22.33.NNN, you can run something like:

[[ $(wget -qO - http://wtfismyip.com/text) =~ ^'55.22.33.' ]] && 
    ssh 192.168.1.100 ||
    ssh -p 12345 55.22.33.99

An even simpler approach is to use your internal IP. I don't know how your two networks are set up but if it is easy to determine whether you are at work or not through your IP (for example, if you have a specific IP at work, such as 192.168.1.12), you can do (change eth0 to whatever the name of your network card is):

[[ $(ip address show dev eth0 | grep -Po 'inet \K[\d.]+') = '192.168.1.12' ]] && 
    ssh 192.168.1.100 ||
    ssh -p 12345 55.22.33.99

Whichever you decide to use, you can add it as an alias to your shell (add this line to your shell's initialization file, ~/.bashrc if you're using bash):

alias gamma="[[ $(wget -qO - http://wtfismyip.com/text) =~ ^'55.22.33.' ]] && ssh 192.168.1.100 || ssh -p 12345 55.22.33.99

You can also make that into a script if you want other scripts to have access to it (aliases from .bashrc are not read when running a script).

2

You can't achieve this in ~/.ssh/config when using IP addresses as host names. Additional complication is introduced by the fact, that you are not just connecting to different IP addresses but also different ports, since that pretty much rules out any tweaking of your DNS resolver.

I stand corrected - you can use the Match originalhost ... exec ... combo in ~/.ssh/config - see @MichałPolitowski's answer. However, although it will work perfectly fine for OpenSSH, you may not necessarily find similar functionality in other SSH clients.

You can work around the problem using a simple wrapper (either a shell function or a script if you need to use it from various shells) for ssh, which will check what network you are on and use the appropriate Host entry. The big question is, how to reliably detect what network you are on. Local IP address comes to mind, but is not reliable, since you may as well be connecting from a LAN that uses the same subnet as your work network.

If you can have the same port for both local and remote networks, you can edit your /etc/resolv.conf depending on the network you're on - obviously it'd have to be done automatically (most likely from a hook script of your DHCP client). Or - better - run local name server (like for example dnsmasq) and supply it with appropriate configuration. That goes beyond the scope of this question though.

Another option - if you only need to connect interactively - is to use command completion that would scan ~/.ssh/config. That would save you some typing (especially if you have varying enough Host entries). Something like this (for bash initialisation):

# complete session names for ssh
declare -g _ssh_complete_hostlist 2> /dev/null
function _ssh_complete_init () {
    _ssh_complete_hostlist=$( \
        sed -nr '/^\s*Host\s*=/{s/^[^=]+= *//;s/ /\n/g;p}' ~/.ssh/config \
        | sort )
}
_ssh_complete_init

function _ssh_complete () {
    local match=${COMP_WORDS[${COMP_CWORD}]}
    local hosts=
    local default=
    for h in $_ssh_complete_hostlist; do
        if [[ $h =~ ^$match ]]; then
            hosts="$hosts $h"
        fi
    done
    if ! (( ${COMP_CWORD} == ${#COMP_WORDS[@]}-1 )); then
        default=$( compgen -f ${COMP_WORDS[${COMP_CWORD}]} )
    fi
    COMPREPLY=($hosts $default)
}
complete -F _ssh_complete ssh

The first function creates a list from which the hosts are completed (usually it is enough to run this once in every shell), the second one does the actual completion, though in a bit clumsy manner - it only completes the host name when it is the last token on the command line.

All this said, the right way to approach this problem is connecting into the work network through a VPN and thus have the local work IP address accessible as if you were in the office. You can then hard-wire the address into whatever whichever layer you prefer: ~/.ssh/config, /etc/resolv.conf or (imho best option) office name server.

  • The ports aren't set in stone. I could definitely change local SSH port of gamma to match the remote port if it would make things easier. – IQAndreas Dec 22 '14 at 10:18
2

Some years ago, I wrote a program for a similar purpose. It might suite your needs. With that program the ssh configuration could look like this:

Host gamma
    ProxyCommand ssh-multipath-proxy 192.168.1.100:22 55.22.33.99:12345
    User andreas
1

Yet another solution is to use two different config files for SSH. You might consider this slightly less elegant than having everything in one config file, but it's easier to maintain.

You select the config file you want to use with -F <configfile>.

  • Thanks. I like the -F option. – typelogic Mar 8 at 16:09
0

Partial answer:

Many answers above start with "if you can detect which network you are on". For this I use a script that runs when interfaces are connected to start various things (VPNs, usually) when I connect to one of my usual networks. The technique is to use ARP to obtain the MAC address of the gateway:

function identifyConnection {
    gatewayIP=$(route -n | grep -e '^0\.0\.0\.0' | tr -s ' ' | cut -d ' ' -f 2)

    if [[ ! -z "$gatewayIP" ]]
    then
        # Identify the gateway by its MAC (uniqueness...)
        log "Gateway IP address=$gatewayIP"
        log "Obtaining corresponding gateway MAC address"
        gatewayData=($(arp -n $gatewayIP | grep -e $gatewayIP | tr -s ' '))
        if [[ "${gatewayData[1]}" == "(incomplete)" ]]
        then
            log "Status of gateway $gatewayIP "incomplete""
            echo ""
        elif [[ "${gatewayData[2]}" == "--" ]]
        then 
            log "No MAC address found for $gatewayIP"
            echo ""
        else
            log "Gateway MAC address=[${gatewayData[2]}]"
            echo "${gatewayData[2]}"
        fi
    fi
}

And then I have a lookup table to associate network to the gateway MAC. Of course that table can hold several MAC for the same network (typical case is the various Wifi hotspots on a large corporate site). Another lookup table is used to determine the script to run for that network.

In my case, the script is run using the Plasma desktop notifications, so everything is in userland.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.