There’s a Linux server whose ISP provides a public IPv6 address, which can be used to SSH into it from outside. That public IPv6 address occasionally changes, though, which causes a lot of trouble for accessing it. To reliably SSH into the server from outside, there are generally two approaches:

  • Dynamic port mapping (DDNS): mapping the server’s port to a port on a remote machine that has a public IP address, and accessing it through that remote machine’s public IP. Budget cloud servers with a public IP address are inexpensive and can do this.
  • Have the server fetch its IPv6 address and send it to a specified mailbox — convenient and quick to implement, and what this article covers.

Automatically emailing a Linux server’s IPv6 address to a specified mailbox involves three steps:

  1. Getting the local IP address with Python
  2. Sending the email with smtp (rather than logging into a mailbox and clicking send)
  3. Automating the whole process with crond

1. Getting the Local IP Address with Python

ipconfig or ip addr can get the local IP address:

Checking the IP with the ip addr command, the inet6 line is the IPv6 address we need. The internal IP address is 192.168.1.6. This is actually one way to get the local IP address. Python has packages like subprocess and os that can run shell scripts and capture their output, but getting exactly the IP address you want out of that requires fairly involved string processing. The cleaner choice is the socket package:

import socket
def get_ip():
    hostname = socket.gethostname()
    addr_infos = socket.getaddrinfo(hostname, None)
    ips = set([addr_info[-1][0] for addr_info in addr_infos])
    global_ips = [ip for ip in ips if ip.startswith("24")]
    return global_ips

socket returns every IP address, but what we need is the public IPv6 address. Public IPv6 addresses generally start with 24, so a filter was added for that. The result is a list, with each element being an IPv6 address string.

2. Sending the Email with smtp

Sending email with Python’s smtp package needs a mailbox that supports smtp (most common mailboxes do). Using a sina.com mailbox as an example:

import smtplib
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from time import asctime

def send_an_email(email_content): # email_content is a string
    mail_host = "smtp.sina.com" # look this up in your mailbox settings; a Sina mailbox is used here
    mail_user = the sending email address
    mail_auth_code = the app-specific auth code, not your mailbox login password
    mail_sender = mail_user # use mail_user as the sender
    mail_receivers = [list of recipients]
    message = MIMEMultipart()
    message['From'] = Header(mail_sender)  # sender
    message['Subject'] = Header("subject line")
    message.attach(MIMEText(asctime(), 'plain', 'utf-8'))
    message.attach(MIMEText(email_content, 'plain', 'utf-8'))
    print("message is {}".format(message.as_string())) # for debugging
    smtpObj = smtplib.SMTP(mail_host)
    # smtpObj.set_debuglevel(1) # also for debugging
    smtpObj.login(mail_user, mail_auth_code) # log in
    smtpObj.sendmail(mail_sender, mail_receivers, message.as_string()) # actually sending the email happens here

Log into your own mailbox, find the settings, and find the POP/IMAP/SMTP client settings. That’s where you’ll find the SMTP server address, and where you can generate an auth code.

You’ll see the auth code there. (Since I’d already applied for one before, what’s shown here is a reset.) If you don’t have one yet, you’ll be asked to apply for one — get the auth code from the mailbox settings.

Once the mailbox is configured, you can call the send_an_email function. Edit email_content and the header fields yourself, and check that sending works.

The email was sent successfully via smtp.

3. Automating This Process

We’ve now implemented automatically fetching the IP address and sending email through Python’s smtp package, but this still falls a bit short of “smart”: the script still has to be run manually every time. What we actually want is: email me as soon as the IPv6 address changes, but don’t email me all the time.

The approach is to monitor the server’s IPv6 address, save the current IPv6 address, check the server’s IPv6 address at a fixed interval, and if it hasn’t changed, don’t send an email — if it has, send one. Following this approach, the IP-fetching code can be adapted like so:

def get_temp_ip(current_ip):
    temp_ip_json_path = "/var/tmp/ip.json"
    if not os.path.exists(temp_ip_json_path):
        print("No {}, dump it.".format(temp_ip_json_path))
        with open(temp_ip_json_path, 'w') as jo:
            json.dump(current_ip, jo)
            return True, current_ip
    else:
        with open(temp_ip_json_path, 'r') as jo:
            origin_ip = json.load(jo)
        if origin_ip == current_ip:
            print("Current ip {} do not change, no need to send".format(current_ip))
            return False, current_ip
        else:
            print("The ip updated from {} to {}, update it.".format(origin_ip, current_ip))
            os.remove(temp_ip_json_path)
            with open(temp_ip_json_path, 'w') as jo:
                json.dump(current_ip, jo)
                return True, current_ip


def get_ip():
    hostname = socket.gethostname()
    addr_infos = socket.getaddrinfo(hostname, None)
    ips = set([addr_info[-1][0] for addr_info in addr_infos])
    global_ips = [ip for ip in ips if ip.startswith("24")]
    whether_to_send, send_ip = get_temp_ip(global_ips)
    send_ip = json.dumps(send_ip)
    return whether_to_send, send_ip

First check whether /var/tmp/ip.json exists. If it doesn’t, save the current IPv6 address and send an email. If it does, read the IPv6 address stored in it — if the current and stored IPv6 addresses match, just wait for the next check; if they don’t match, update ip.json and send an email. You’ll generally also want to add a main guard to the script:

if __name__ == "__main__":
    whether_to_send, global_ips = get_ip()
    if whether_to_send:
        send_an_email(global_ips)
    else:
        print("wait and no send")

Make the script executable:

sudo chmod +x send_ip_to_mailbox.py

Use Linux’s scheduling tool, crond, to schedule the task to run automatically — here it’s configured to check the IPv6 address once every minute.

# create a blank file under /etc/cron.d/
sudo vim /etc/cron.d/ipsync
# with the following content
*/1 * * * * chinglin /abs_path_to_send_ip_to_mailbox.py

Start crond:

sudo systemctl restart crond # start it
sudo systemctl enable crond # enable it on boot

At this point, you can receive the server’s IPv6 address by email.

4. Summary

This article mainly covered automatically sending a Linux server’s IP address to a specified mailbox. The basic steps are: get the IPv6 address with socket, send the email with smtp, and automate the whole thing with crond.