-
Notifications
You must be signed in to change notification settings - Fork 22
/
gandi_ddns.py
executable file
·131 lines (98 loc) · 3.81 KB
/
gandi_ddns.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import configparser as configparser
import sys
import os
import requests
import json
import ipaddress
from datetime import datetime
import time
config_file = "config.txt"
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
DEFAULT_RETRIES = 3
DEFAULT_IPIFY_API = "https://api.ipify.org"
class GandiDdnsError(Exception):
pass
def get_ip_inner(ipify_api):
# Get external IP
try:
# Could be any service that just gives us a simple raw ASCII IP address (not HTML etc)
r = requests.get(ipify_api, timeout=3)
except requests.exceptions.RequestException:
raise GandiDdnsError('Failed to retrieve external IP.')
if r.status_code != 200:
raise GandiDdnsError(
'Failed to retrieve external IP.'
' Server responded with status_code: %d' % r.status_code)
ip = r.text.rstrip() # strip \n and any trailing whitespace
if not(ipaddress.IPv4Address(ip)): # check if valid IPv4 address
raise GandiDdnsError('Got invalid IP: ' + ip)
return ip
def get_ip(ipify_api, retries):
# Get external IP with retries
# Start at 5 seconds, double on every retry.
retry_delay_time = 5
for attempt in range(retries):
try:
return get_ip_inner(ipify_api)
except GandiDdnsError as e:
print('Getting external IP failed: %s' % e)
print('Waiting for %d seconds before trying again' % retry_delay_time)
time.sleep(retry_delay_time)
# Double retry time, cap at 60s.
retry_delay_time = min(60, 2 * retry_delay_time)
print('Exhausted retry attempts')
sys.exit(2)
def read_config(config_path):
# Read configuration file
cfg = configparser.ConfigParser()
cfg.read(config_path)
return cfg
def get_record(url, headers):
# Get existing record
r = requests.get(url, headers=headers)
return r
def update_record(url, headers, payload):
# Add record
r = requests.put(url, headers=headers, json=payload)
if r.status_code != 201:
print(('Record update failed with status code: %d' % r.status_code))
print((r.text))
sys.exit(2)
print('Zone record updated.')
return r
def main():
path = config_file
if not path.startswith('/'):
path = os.path.join(SCRIPT_DIR, path)
config = read_config(path)
if not config:
sys.exit("Please fill in the 'config.txt' file.")
for section in config.sections():
print('%s - section %s' % (str(datetime.now()), section))
# Retrieve API key
apikey = config.get(section, 'apikey')
# Set headers
headers = {'Content-Type': 'application/json', 'Authorization': 'Apikey %s' % apikey}
# Set URL
url = '%sdomains/%s/records/%s/A' % (config.get(section, 'gandi_api'),
config.get(section, 'domain'), config.get(section, 'a_name'))
print(url)
# Discover External IP
ipify_api = config.get(section, 'ipify_api', fallback=DEFAULT_IPIFY_API)
retries = int(config.get(section, 'retries', fallback=DEFAULT_RETRIES))
external_ip = get_ip(ipify_api, retries)
print(('External IP is: %s' % external_ip))
# Prepare record
payload = {'rrset_ttl': config.get(section, 'ttl'), 'rrset_values': [external_ip]}
# Check current record
record = get_record(url, headers)
if record.status_code == 200:
print(('Current record value is: %s' % json.loads(record.text)['rrset_values'][0]))
if(json.loads(record.text)['rrset_values'][0] == external_ip):
print('No change in IP address. Goodbye.')
continue
else:
print('No existing record. Adding...')
update_record(url, headers, payload)
if __name__ == "__main__":
main()