How I do connect home from remote?
August 1, 2019.
Although not often, I sometimes need to connect to my machine at home from work. In this post, I am going to tell you how I do that through SSH.
To create a SSH connection, I first need to have the address of my machine at home. This is, of course, made of TCP/IP address and port. The problem is that ISPs, in general, work with dynamic IPs. Even if I know my current IP address, this could change at anytime. ISP allocates me, as its customer, an address which is leased to me for some time only. On the other hand, I could demand to have a static IP address, however this comes with a cost. To give a figure, as of today, my ISP charges TRY24.9 per month for such a service. In my opinion, it’s not only the cost, though. To me, it less secure to expose my home network behind a fixed address.
In order to create a remote connection home, the first thing to do is to obtain my address, somehow. If only someone monitored my address constantly at home and let me know every time it changes…
Let’s forget about the IP addresses for a moment. Nobody would memorize a group of numbers that constantly changes. After all, we have domain name servers for this purpose, right? It’s just nothing but an address record that resolves to an IP address.
Some DNS hosting services let you update DNS records programmatically. Instead of going to DNS panel and enter address records manually, you can achieve the same with some API requests to the DNS provider1. This is called Dynamic DNS (or DDNS).
If we use a domain name, we wouldn’t care what the IP address is. A DNS record would abstract the ever-changing IP address.
We have our first milestone right now.
- What? DNS record must be updated every time the IP address changes
- How? Schedule a service (task) that polls the IP address constantly. A Raspberry PI is a great fit to run such a service!
Here is an implementation in Python. See github.com/karakays/ddns for source code.
def main(): ch_addr = None try: with io.open('/tmp/ddns_addr', 'r') as f: ch_addr = f.readline() except IOError: pass curr_addr = resolve_addr() if curr_addr != ch_addr: update_dns(curr_addr)
What this code snippet basically does is it first resolves current public IP address with
resolv_addr(). In case it has changed since the last time, it updates DNS records using Cloudflare API and stores the IP address in somewhere local to compare it for the next time. That’s it.
To schedule the service, I will let systemd timers to manage it. ddns.service unit and its matching timer are shown next. [Service] section in the unit file contains environmental variables that are within the scope of this service only.
[Unit] Description=Update DNS address record of the host [Service] Type=oneshot Environment=CF_API_KEY=changeit Environment=CF_EMAIL=changeit Environment=CF_ZONE=changeit Environment=CF_DOMAIN=changeit ExecStart=/usr/local/bin/ddns
The timer file specifies to run the service every 5 minutes which is expressed with OnCalendar directive. After directive makes sure it’s activated after network services start up and the host gets an IP address.
[Unit] Description=Run ddns.service every 5 minutes After=network.target [Timer] OnCalendar=*:*:0/5 AccuracySec=1min Persistent=true [Install] WantedBy=timers.target
$ sudo mv ddns.timer ddns.service /etc/systemd/system/ $ sudo systemctl enable ddns.timer $ sudo systemctl start ddns.timer
By now, we can resolve our IP address by the domain name, but we still can’t reach our target machine in local network. Any request from Internet will hit the gateway, but my target host is in the LAN so its address needs to be translated into the local network. Here goes our second milestone.
- What? Incoming traffic to the router must be allowed and forwarded to target host.
- How? Port forwarding
This is done in the router panel configuration. Port forwarding is redirecting the incoming traffic from the internet to a target host. The exact configuration may change based on your device vendor, but to set up a forwarding rule, you need at least following details -
- External port number where traffic is coming from
- Internal host and port to redirect the traffic to
- Communication protocol: TCP/UDP
Since internal IP address and port is fixed in the mappings here, the traffic gets redirected always to the same local address. That means we need to make sure our target host always gets the same IP address assigned from the server. To do that, I edit my network configuration details in a static manner. First, I stop NetworkManager.
$ sudo systemctl stop network-manager
allow-hotplug wlp3s0 iface wlp3s0 inet static address <address> netmask <netmask> network <network> broadcast <broadcast> gateway <gateway> wpa-ssid <network-name> wpa-psk <wpa-pw>
To apply the configuration, save the file and reboot the machine.
Some security considerations
In the other end of the connection, SSH server is listening for commands from the Internet. This requires to be a bit cautious. Our target host on port 22 is now exposed to the entire Internet. That means anyone that acquires enough details can connect to your home network, just the same way as you do. This leads to a serious vulnerability. In order to mitigate the risks, we need to take some security measures in the SSH server. The exact details might be in another post but to name a few, we need to -
- restrict bad login attempts
- disable root login
- disable password authentication and use PKI for authentication
And that’s it. This is how I connect home from anywhere.
$ ssh mydomain.com
In case your current DNS hosting provider do not support DDNS, you may want to migrate to another one with such capability. I had my domain records managed in a DNS provider other than my domain name registrar. I updated NS records in the DNS provider that I was migrating from, however, this was a mistake. In order to migrate properly, you should update authoritative name server records in your registrar. ↩︎