Update A record for domain using OVH API

I recently noticed that the external IP of my broadband connection doesn’t actually change very often, once every one or two months. So I figured I could just point one of my domains to my ‘home server’; no need to use a ‘dynamic DNS’ provider.

If you use the ‘Pong script’ which I posted earlier, then this Python script can update the A record of your domain’s nameserver to point to your current external IP address, given that you use the OVH nameservers. Just call it with a cron job.

Note: You need install python-ovh:

pip install python-ovh

And you need to generate API access keys for the script: https://eu.api.ovh.com/createToken/

For this step be careful to grant three permissions:

GET /domain/zone/<YOUR DOMAIN>/record
GET /domain/zone/<YOUR DOMAIN>/record/*
PUT /domain/zone/<YOUR DOMAIN>/record/*

And here’s the script:

import json
import ovh
import sys
import socket
import re


DOMAIN = "example.com"
OVH_ENDPOINT = "ovh-eu"
OVH_APP_KEY = ""
OVH_APP_SEC = ""
OVH_CON_KEY = ""

# Using https://floki.cc/2019/10/netcat_pong
# (adjust get_current_ip() method if you're using
# something else)
PONG_HOST = "External IP"
PONG_PORT = 12345


def get_current_ip():
    """
    Get the current external IP using the 'PONG_HOST'.
    :return: See above
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((PONG_HOST, PONG_PORT))
    data = s.recv(1024)
    s.close()
    new_ip = data.decode("utf-8").strip()
    if not re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$").match(new_ip):
        raise Exception("Could not get current IP")
    return new_ip

def get_current_arecord_ip(client):
    """
    Get the current IP and record ID which is set in the
    A record of the nameserver for the domain
    :param client: The OVH API client
    :return: (record_id, ip) tuple
    """
    path = "/domain/zone/{}/record".format(DOMAIN)
    result = client.get(path,
                        fieldType='A',
                        subDomain='' # you can use a subdomain too
                        )

    if len(result) != 1:
        raise Exception("Could not get current A record IP")

    record_id = result[0]
    path = "/domain/zone/{}/record/{}".format(DOMAIN,record_id)
    result = client.get(path)
    return record_id, result['target']

def update_arecord(client, record_id, ip):
    """
    Update the A record with the given ID with the provided IP
    :param client: The OVH API client
    :param record_id: The ID of the A record
    :param ip: The IP to update the record with
    :return:
    """
    path = "/domain/zone/{}/record/{}".format(DOMAIN,record_id)
    result = client.put(path,
                        target=ip
                        )

def main():
    ip = get_current_ip()

    client = ovh.Client(
        endpoint=OVH_ENDPOINT,
        application_key=OVH_APP_KEY,
        application_secret=OVH_APP_SEC,
        consumer_key=OVH_CON_KEY,
    )
    record_id, old_ip = get_current_arecord_ip(client)

    if ip != old_ip:
        print("Updating A record to IP {}".format(ip))
        update_arecord(client, record_id, ip)
    else:
        print("No update required")


if __name__ == "__main__":
    main()