2 import ipaddress, subprocess, sys, getopt,dbm
3 from configparser import ConfigParser
4 import urllib.request,urllib.parse
5 import logging,os.path,time
6 class DyndnsNetwork(object):
8 This class represents dynamic DNS server and network.
9 It has attributes hostname network server user and password
11 def __init__(self,config_section):
12 self.hostname = config_section['hostname']
13 self.network = ipaddress.ip_network(config_section['network'])
14 self.server = config_section['server']
15 if 'user' in config_section:
16 self.user=config_section["user"]
17 self.password=config_section["password"]
20 def contains(self,address):
21 if self.network.prefixlen==0 and address.is_private:
23 if self.network.version != address.version:
25 return address in self.network
26 def nsupdate(self, address):
28 Sends a get query to specified server.
29 Raises HTTPError if something goes wrong
32 logging.debug("Going to send message to %s",self.server)
33 if hasattr(self,'user'):
34 password_mgr =urllib.request.HTTPPasswordMgrWithDefaultRealm()
35 password_mgr.add_password(None,self.server,self.user,self.password)
36 handler=urllib.request.HTTPBasicAuthHandler(password_mgr)
37 opener = urllib.request.build_opener(handler)
39 opener = urllib.request.build_opener()
40 with opener.open('%s?%s'%(self.server,
41 urllib.parse.urlencode({'hostname':self.hostname,
42 'myip':str(address)}))) as req:
43 req.read().decode("utf-8")
47 def get_current_addresses():
49 logging.debug("Acquire current IPs")
50 for line in subprocess.run(['ip','-o','addr','show'],check=True,stdout=subprocess.PIPE).stdout.decode('utf-8').split("\n"):
53 (no,iface,family,addr,rest)=line.split(maxsplit=4)
54 address=ipaddress.ip_address(addr.split('/')[0])
55 if address.is_loopback or address.is_link_local:
57 result.append(address)
58 logging.debug("Got following addresses: %s",repr(result))
60 def check_for_update():
61 addrlist=get_current_addresses()
62 for name,net in networks.items():
68 old_addr=ipaddress.ip_address(database[name].decode("utf-8"))
70 # Nothing changed go, to next net
71 logging.debug("Address for net %s not changed",name)
75 logging.info("Doing update for net %s with address %s",name, str(a))
78 except urllib.error.HTTPError as e:
79 logging.exception("Http error doing nsupdate code %s",e.code)
82 logging.info("Address for net %s no more found",name)
86 config['dyngo']={'interval':'60','database':'/var/lib/dyngo/dyngo.db',
87 'ca':'/etc/ssl/certs','loglevel':'WARNING'}
88 options=dict(getopt.getopt(sys.argv,"f:")[0])
89 if not '-f' in options:
90 options["-f"]="/etc/dyngo.conf"
91 if len(config.read(options["-f"]))!=1:
92 print("Cannot read config %s"%options["-f"],file=sys.stderr)
95 if conf['loglevel'].isdigit():
96 level=int(conf['loglevel'])
100 level=getattr(logging,conf['loglevel'].upper())
101 except AttributeError:
102 print("Invalid logleevel '%s'"%conf('loglevel'))
105 logging.basicConfig(format="%(asctime)s %(message)s",
106 level=level, stream=sys.stderr)
107 interval=int(conf['interval'])
108 database=dbm.open(conf['database'],"c")
110 if 'ca' in conf and len(conf['ca']):
112 if os.path.isdir(path):
113 https_params['capath']=path
115 http_params['cafile']=path
116 # Convert all other config sections to DyndnsNetwork objects
118 for sect in config.sections():
119 if sect == 'dyngo' or sect == 'DEFAULT':
121 logging.debug("Processing network %s",sect)
122 networks[sect]=DyndnsNetwork(config[sect])
123 # Remove stale items from persistent database, which are no more
124 # mentioned in the config file
125 for i in set([x.decode("utf-8") for x in database.keys()])-set(networks.keys()):
126 logging.info("Removing from persistent state network %s which is no more in config",i)