--- /dev/null
+DYNGO
+=====
+
+I like when I can log into my notebook via ssh using some easily
+remembered name.
+
+But it is not always a case in the world of NATs and VPNs.
+
+If machine always have public IP address, than using some dynamic DNS
+service may help (short of neccessity to use full domain name. But
+${HOME}/.ssh/config may help). But what if you log into your office
+workstation via corporate VPN, and want ssh back home?
+
+If you have enough power to run (or persuade system administrator to
+run) dyndns service in your office network, dyndns can help even there.
+
+But your setup need to know different dyndns servers depending on IP
+address.
+
+So, I wrote DYNGO is dynamic DNS client which can handle lots of
+dynamic DNS servers simultaneously. It reads config file, which
+specifies which network ranges correspond to which server, and
+then runs as a daemon, periodically polling IP addresses of all
+interfaces of the machine. If it finds change, it makes http(s) request
+to specified URL.
+
+See [manual page](dyngo.md) for more info.
+
+
+
--- /dev/null
+#!/usr/bin/env python3
+import ipaddress, subprocess, sys, getopt,dbm
+from configparser import ConfigParser
+class DyndnsNetwork(object):
+ """
+ This class represents dynamic DNS server and network.
+ It has attributes hostname network server user and password
+ """
+ def __init__(self,config_section):
+ self.hostname = config_section['hostname']
+ self.network = ipaddress.ip_network(config_section['network'])
+ self.server = config_section['server']
+ if user in config_section:
+ self.user=config_section["user"]
+ self.password=config_section["password"]
+
+
+ def contains(self,address):
+ if self.network.prefixlen==0 and address.is_private:
+ return False
+ if self.network.version != address.version:
+ return False
+ return address in self.network
+ def nsupdate(self, address):
+ raise NotImplementedError
+
+def get_current_addresses():
+ result=[]
+ for line in subprocess.run(['ip','-o','addr','show'],check=True,stdout=subprocess.PIPE).stdout.decode('utf-8').split("\n"):
+ if not len(line):
+ continue
+ (no,iface,family,addr,rest)=line.split(maxsplit=4)
+ address=ipaddress.ip_address(addr.split('/')[0])
+ if address.is_loopback or address.is_link_local:
+ continue
+ result.append(address)
+ return result
+def check_for_update():
+ addrlist=get_current_addresses()
+ for name,net in networks.items():
+ found=False
+ for a in addrlist:
+ if net.contains(a):
+ found =True
+ if name in database:
+ old_addr=ipaddress.ip_address(database[name].decode("utf-8"))
+ if old_addr==a:
+ # Nothing changed go, to next net
+ break
+ # address changed
+ net.nsupdate(a)
+ database[name]=str(a)
+ if not found:
+ del data[name]
+
+
+
+
+config=ConfigPaser()
+config['dyngo']={'interval':'60','database','/var/lib/dyngo/dyngo.db'}
+options=dict(getopt.getopt(sys.argv,"f:")[0])
+if not '-f' in options:
+ options["-f"]="/etc/dyngo.conf"
+if len(config.read(options["-f"]))!=1:
+ print("Cannot read config %s"%options["-f"],file=sys.stderr)
+ sys.exit(1)
+
+conf=config['dyngo']
+interval=int(conf['interval'])
+database=dbm.open(conf['database'],"c")
+# Convert all other config sections to DyndnsNetwork objects
+networks={}
+for sect in config.sections():
+ if sect == 'dyngo' or sect= 'DEFAULT':
+ continue
+ networks[sect]=DyndnsNetwork(config[sect])
+# Remove stale items from persistent database, which are no more
+# mentioned in the config file
+for i in set([x.decode("utf-8") for x database.keys()])-set(network.keys()):
+ del database[i]
+
+
+while True:
+ check_for_update()
+ time.sleep(interval)
+
--- /dev/null
+# Config file for dingo dyndns client
+# Section name can be anything - it is for your convinience
+# parameters - hostname - name to send to server
+# network - IP network where this name belonks to
+# server - url of dyndns server
+[dingo]
+interval=60
+database=/var/lib/dingo/dingo.db
+[public ipv6]
+hostname=antares.wagner.pp.ru
+network=::/0
+server=http://www.wagner.pp.ru/cgi-bin/dyndns.cgi
+user=
+[postgrespro local]
+hostname=antares.local.vm
+network=192.168.24.0/21
+server=http://fafnir.l.posgrespro.ru/dyndns
+
--- /dev/null
+% dingo(8)
+% Victor Wagner <vitus@wagner.pp.ru>
+% September 2019
+
+NAME
+====
+
+dingo - multi-network dynamic DNS client
+
+SYNOPSIS
+========
+
+**dyngo** [ **-f** *configuration-file* ]
+
+DESCRIPTION
+===========
+
+**dyngo** is dynamic DNS client which periodically tries to check if
+some of IP addresses of the current machine change and report to
+appropriate dynamic DNS server if so.
+
+It is primarily intended to let machine be visible in the local DNS
+of office or home local networks, VPNs and so on. So it expects that
+there could be private dynamic dns servers. Couple of dynamic DNS
+scripts is included into distribution to make it easier to set up
+dynamic dns.
+
+But nothing prevent user from defining some of networks as global
+ones and put URL of public dyndns server into it.
+
+If network is defined as global, i.e have netmask /0, private ip ranges
+wouldn't be reported to corresponding DNS server.
+
+CONFIGURATION FILE
+==================
+
+Configuration file **dyngo.conf** is ini-style file. It contains section
+**dyngo** with global service parameters and section per dyndns server.
+
+Names of server-description sections are arbitrary, but should be
+unique, because they are used as keys into persistent database.
+
+
+
+