init repo
This commit is contained in:
commit
1472938a30
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
||||
t]
|
||||
Description=Address Python Server
|
||||
After=syslog.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=addressserver
|
||||
Group=addressserver
|
||||
WorkingDirectory=/home/addressserver/address_scanner
|
||||
ExecStart=/usr/bin/python3 /home/addressserver/address_scanner/server.py
|
||||
SyslogIdentifier=address_server
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```
|
||||
apt-get install python3-pip
|
||||
pip3 install netifaces paho-mqtt
|
||||
|
||||
*/2 * * * * /usr/bin/python3 /root/scanner.py -i eth0 -4 192 -6 2a02 --host 192.168.25.221 --username mc8051 --password XXXXXXX -q
|
||||
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
https://www.thomaschristlieb.de/ein-python-script-mit-systemd-als-daemon-systemd-tut-garnicht-weh/
|
||||
https://stackoverflow.com/a/47370156
|
||||
```
|
93
scanner.py
Normal file
93
scanner.py
Normal file
@ -0,0 +1,93 @@
|
||||
import netifaces
|
||||
import optparse
|
||||
import sys
|
||||
import socket
|
||||
import time
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
is_connected = False
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
global is_connected
|
||||
if(rc == 0):
|
||||
is_connected = True
|
||||
|
||||
hostname = socket.gethostname()
|
||||
|
||||
parser = optparse.OptionParser()
|
||||
|
||||
parser.add_option('-i', '--interface', action="store", dest="interface", help="Network Interface", default="")
|
||||
parser.add_option('-q', '--quite', action="store_true", dest="quite", help="No output")
|
||||
parser.add_option('-4', '--match4', action="store", dest="match4", help="IPv4 MUST start with this argument", default="")
|
||||
parser.add_option('-6', '--match6', action="store", dest="match6", help="IPv6 MUST start with this argument", default="")
|
||||
parser.add_option('--host', action="store", dest="mqqt_host", help="MQTT Host", default="")
|
||||
parser.add_option('--port', action="store", dest="mqqt_port", help="MQTT Port", default="1883")
|
||||
parser.add_option('--username', action="store", dest="mqqt_user", help="MQTT Username (optional)", default="")
|
||||
parser.add_option('--password', action="store", dest="mqqt_pass", help="MQTT Password (optional)", default="")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
client_enabled = not(not options.mqqt_host)
|
||||
|
||||
if client_enabled:
|
||||
client = mqtt.Client()
|
||||
client.connect(options.mqqt_host, int(options.mqqt_port), 60)
|
||||
|
||||
if options.mqqt_user and options.mqqt_pass:
|
||||
client.username_pw_set(username=options.mqqt_user, password=options.mqqt_pass)
|
||||
|
||||
client.on_connect = on_connect
|
||||
client.loop_start()
|
||||
else:
|
||||
print("Warning: no MQTT server defined")
|
||||
|
||||
if client_enabled:
|
||||
timeout = 10
|
||||
while (not is_connected) and (timeout > 0):
|
||||
timeout -= 1
|
||||
time.sleep(0.5)
|
||||
|
||||
if timeout <= 0:
|
||||
print("MQTT timeout")
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
addrs = netifaces.ifaddresses(options.interface)
|
||||
except ValueError as ex:
|
||||
print("Invalid interface " + options.interface)
|
||||
sys.exit(-1)
|
||||
|
||||
if not options.quite:
|
||||
print("Checking network interface " + options.interface + " on " + hostname)
|
||||
|
||||
if not options.match4 and not options.quite:
|
||||
print("Warning: no IPv4-Match set")
|
||||
|
||||
if not options.match6 and not options.quite:
|
||||
print("Warning: no IPv6-Match set")
|
||||
|
||||
|
||||
ipv4 = ""
|
||||
for addr in addrs[netifaces.AF_INET]:
|
||||
if addr["addr"].startswith(options.match4):
|
||||
ipv4 = addr["addr"]
|
||||
break
|
||||
|
||||
|
||||
ipv6 = ""
|
||||
for addr in addrs[netifaces.AF_INET6]:
|
||||
if addr["addr"].startswith(options.match6):
|
||||
ipv6 = addr["addr"]
|
||||
break
|
||||
|
||||
|
||||
if not options.quite:
|
||||
print("")
|
||||
print("Found IPv4 " + ipv4 + " and IPv6 " + ipv6)
|
||||
|
||||
if client_enabled:
|
||||
client.publish("network/" + hostname + "/hostname", hostname)
|
||||
client.publish("network/" + hostname + "/ipv4", ipv4)
|
||||
client.publish("network/" + hostname + "/ipv6", ipv6)
|
||||
|
||||
client.loop_stop()
|
420
server.py
Normal file
420
server.py
Normal file
@ -0,0 +1,420 @@
|
||||
import schedule
|
||||
import time
|
||||
import paho.mqtt.client as mqtt
|
||||
from urllib.parse import urlparse
|
||||
import requests
|
||||
import json
|
||||
import socket
|
||||
import CloudFlare
|
||||
import logging
|
||||
import smtplib
|
||||
import ssl
|
||||
import io
|
||||
|
||||
logger = logging.getLogger('dns_updater')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%m/%d/%Y:%H:%M:%S')
|
||||
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
log_capture_string = io.StringIO()
|
||||
ch2 = logging.StreamHandler(log_capture_string)
|
||||
ch2.setFormatter(formatter)
|
||||
logger.addHandler(ch2)
|
||||
|
||||
OPENWRT_PATH = "/cgi-bin/luci/"
|
||||
INTERVAL = 60
|
||||
userdata = dict()
|
||||
mqtt_data = dict()
|
||||
mail_data = dict()
|
||||
openwrt = dict()
|
||||
hosts = dict()
|
||||
dns = dict()
|
||||
firewall = dict()
|
||||
|
||||
def readFile():
|
||||
with open("settings.json") as json_file:
|
||||
global userdata
|
||||
global hosts
|
||||
global dns
|
||||
global firewall
|
||||
global mqtt_data
|
||||
global mail_data
|
||||
global openwrt
|
||||
global OPENWRT_PATH
|
||||
global INTERVAL
|
||||
data = json.load(json_file)
|
||||
|
||||
userdata = {"apikey": data["apikey"], "username": data["user"]}
|
||||
INTERVAL = data["interval"]
|
||||
|
||||
mqtt_data = data["mqtt"]
|
||||
mail_data = data["mail"]
|
||||
openwrt = data["openwrt"]
|
||||
OPENWRT_PATH = "http://" + openwrt["host"] + OPENWRT_PATH
|
||||
|
||||
if "hosts" in data:
|
||||
z = hosts.copy()
|
||||
z.update(data["hosts"])
|
||||
hosts = z
|
||||
|
||||
if "dns" in data:
|
||||
z = dns.copy()
|
||||
z.update(data["dns"])
|
||||
dns = z
|
||||
|
||||
if "firewall" in data:
|
||||
z = firewall.copy()
|
||||
z.update(data["firewall"])
|
||||
firewall = z
|
||||
|
||||
readFile()
|
||||
|
||||
def saveFile():
|
||||
logger.info("Saving settings.json file...")
|
||||
with open('settings.json', 'wt') as out:
|
||||
global userdata
|
||||
global hosts
|
||||
global dns
|
||||
global INTERVAL
|
||||
obj = {
|
||||
"apikey": userdata["apikey"],
|
||||
"user": userdata["username"],
|
||||
"interval": INTERVAL,
|
||||
"dns": dns,
|
||||
"hosts": hosts,
|
||||
"firewall": firewall,
|
||||
"mqtt": mqtt_data,
|
||||
"openwrt": openwrt,
|
||||
"mail": mail_data,
|
||||
}
|
||||
res = json.dump(obj, out, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
logger.info("File saved")
|
||||
|
||||
schedule.every(2).minutes.do(saveFile)
|
||||
|
||||
|
||||
def getPublicIP():
|
||||
r = requests.get("https://ipinfo.io/")
|
||||
data = json.loads(r.text)
|
||||
|
||||
return data.get("ip")
|
||||
|
||||
def do_dns_update(cf, zone_name, zone_id, dns_name, ip_address, ip_address_type):
|
||||
"""Cloudflare API code - example"""
|
||||
logger.info("Update %s to %s" % (dns_name+"."+zone_name, ip_address))
|
||||
|
||||
try:
|
||||
prefix = "" if dns_name == "@" else dns_name+"."
|
||||
params = {'name':prefix+zone_name, 'match':'all', 'type':ip_address_type}
|
||||
dns_records = cf.zones.dns_records.get(zone_id, params=params)
|
||||
except CloudFlare.exceptions.CloudFlareAPIError as e:
|
||||
logger.error('/zones/dns_records %s - %d %s - api call failed' % (dns_name, e, e))
|
||||
|
||||
updated = False
|
||||
changed = False
|
||||
|
||||
# update the record - unless it's already correct
|
||||
for dns_record in dns_records:
|
||||
old_ip_address = dns_record['content']
|
||||
old_ip_address_type = dns_record['type']
|
||||
|
||||
if ip_address_type not in ['A', 'AAAA']:
|
||||
# we only deal with A / AAAA records
|
||||
continue
|
||||
|
||||
if ip_address_type != old_ip_address_type:
|
||||
# only update the correct address type (A or AAAA)
|
||||
# we don't see this becuase of the search params above
|
||||
logger.debug('IGNORED: %s %s ; wrong address family' % (dns_name, old_ip_address))
|
||||
continue
|
||||
|
||||
if ip_address == old_ip_address:
|
||||
logger.debug('UNCHANGED: %s %s' % (dns_name, ip_address))
|
||||
updated = True
|
||||
continue
|
||||
|
||||
# Yes, we need to update this record - we know it's the same address type
|
||||
|
||||
dns_record_id = dns_record['id']
|
||||
dns_record = {
|
||||
'name':dns_name,
|
||||
'type':ip_address_type,
|
||||
'content':ip_address
|
||||
}
|
||||
try:
|
||||
dns_record = cf.zones.dns_records.put(zone_id, dns_record_id, data=dns_record)
|
||||
changed = True
|
||||
except CloudFlare.exceptions.CloudFlareAPIError as e:
|
||||
logger.error('/zones.dns_records.put %s - %d %s - api call failed' % (dns_name, e, e))
|
||||
logger.debug('UPDATED: %s %s -> %s' % (dns_name, old_ip_address, ip_address))
|
||||
updated = True
|
||||
|
||||
if updated:
|
||||
return changed
|
||||
|
||||
# no exsiting dns record to update - so create dns record
|
||||
dns_record = {
|
||||
'name':dns_name,
|
||||
'type':ip_address_type,
|
||||
'content':ip_address
|
||||
}
|
||||
try:
|
||||
dns_record = cf.zones.dns_records.post(zone_id, data=dns_record)
|
||||
changed = True
|
||||
except CloudFlare.exceptions.CloudFlareAPIError as e:
|
||||
logger.error('/zones.dns_records.post %s - %d %s - api call failed' % (dns_name, e, e))
|
||||
logger.debug('CREATED: %s %s' % (dns_name, ip_address))
|
||||
return changed
|
||||
|
||||
|
||||
if openwrt["enabled"] == True:
|
||||
jar = requests.cookies.RequestsCookieJar()
|
||||
fw_login = requests.get(OPENWRT_PATH + "/rpc/auth", cookies=jar, json={
|
||||
"id": 1,
|
||||
"method": "login",
|
||||
"params": [
|
||||
openwrt["username"],
|
||||
openwrt["password"]
|
||||
]
|
||||
})
|
||||
|
||||
if "result" not in fw_login.json() or fw_login.json()["result"] is None:
|
||||
exit("Incorrect OpenWrt Login")
|
||||
|
||||
o = urlparse(OPENWRT_PATH)
|
||||
jar.set('sysauth', fw_login.json()["result"], domain=o.netloc, path=o.path)
|
||||
|
||||
def setFirewall(section, key, new_ipv6):
|
||||
global jar
|
||||
|
||||
logger.info("Updating firewall " + section + "." + key + "=" + new_ipv6)
|
||||
|
||||
if not new_ipv6:
|
||||
logger.debug("Empty IPv6... Skipping...")
|
||||
return
|
||||
|
||||
r = requests.get(OPENWRT_PATH + "/rpc/uci", cookies=jar, json={
|
||||
"id": 1,
|
||||
"method": "get_all",
|
||||
"params": [
|
||||
"firewall",
|
||||
section
|
||||
]
|
||||
})
|
||||
data = r.json()
|
||||
|
||||
if not "result" in data or data["result"] is None:
|
||||
logger.warning("Unknown firewall section %s... Skipping..." % (section))
|
||||
return
|
||||
|
||||
result = data["result"]
|
||||
|
||||
if not "family" in result or result["family"] is None:
|
||||
logger.debug("No family set in firewall section %s... Skipping..." % (section))
|
||||
return
|
||||
|
||||
if not key in result or result[key] is None:
|
||||
logger.debug("No %s set in firewall section %s... Skipping..." % (key, section))
|
||||
return
|
||||
|
||||
if result["family"] != "ipv6":
|
||||
logger.debuginfo("Section %s is no ipv6... Skipping..." % (section))
|
||||
return
|
||||
|
||||
if result[key] == new_ipv6:
|
||||
logger.debug("Section %s has same ipv6... Skipping..." % (section))
|
||||
return
|
||||
|
||||
r = requests.get(OPENWRT_PATH + "/rpc/uci", cookies=jar, json={
|
||||
"id": 1,
|
||||
"method": "set",
|
||||
"params": [
|
||||
"firewall",
|
||||
section,
|
||||
key,
|
||||
new_ipv6
|
||||
]
|
||||
})
|
||||
logger.debug("updated = %s" % r.json()["result"])
|
||||
return True
|
||||
|
||||
def commitFirewall():
|
||||
global jar
|
||||
r = requests.get(OPENWRT_PATH + "/rpc/uci", cookies=jar, json={
|
||||
"id": 1,
|
||||
"method": "commit",
|
||||
"params": [
|
||||
"firewall",
|
||||
]
|
||||
})
|
||||
|
||||
def updateFirewall():
|
||||
global hosts
|
||||
global dns
|
||||
global firewall
|
||||
global commitFirewall
|
||||
global setFirewall
|
||||
logger.info("Updating firewall")
|
||||
changed = False
|
||||
for section in firewall:
|
||||
for key in firewall[section]:
|
||||
value = firewall[section][key]
|
||||
ipv6 = hosts.get(value, dict()).get("ipv6", "")
|
||||
changed = setFirewall(section, key, ipv6)
|
||||
if changed == True:
|
||||
logger.info("Firewall set... Commiting...")
|
||||
commitFirewall()
|
||||
logger.info("Firewall commited")
|
||||
else:
|
||||
logger.info("Nothing changed. Skipping firewall commit...")
|
||||
return changed
|
||||
|
||||
def updateDNS():
|
||||
global hosts
|
||||
global userdata
|
||||
global dns
|
||||
global getPublicIP
|
||||
global do_dns_update
|
||||
|
||||
logger.info("Updating DNS")
|
||||
|
||||
|
||||
cf = CloudFlare.CloudFlare(email=userdata["username"], token=userdata["apikey"])
|
||||
|
||||
PUBLIC = getPublicIP()
|
||||
if not PUBLIC or PUBLIC is None:
|
||||
logger.error("EMPTY PUBLIC IP?!")
|
||||
return False
|
||||
|
||||
changed = False
|
||||
|
||||
for domain in dns:
|
||||
params = {'name':domain}
|
||||
zones = cf.zones.get(params=params)
|
||||
|
||||
zone = zones[0]
|
||||
zone_name = zone['name']
|
||||
zone_id = zone['id']
|
||||
for subdomain in dns[domain]:
|
||||
ipv4_host = dns[domain][subdomain].get("ipv4", "")
|
||||
ipv6_host = dns[domain][subdomain].get("ipv6", "")
|
||||
|
||||
if ipv4_host.lower() == "public":
|
||||
ipv4 = PUBLIC
|
||||
else:
|
||||
ipv4 = hosts.get(ipv4_host, dict()).get("ipv4", "")
|
||||
|
||||
ipv6 = hosts.get(ipv6_host, dict()).get("ipv6", "")
|
||||
|
||||
dns_records = []
|
||||
|
||||
if ipv4:
|
||||
if do_dns_update(cf, zone_name, zone_id, subdomain, ipv4, "A"):
|
||||
changed = True
|
||||
|
||||
if ipv6:
|
||||
if do_dns_update(cf, zone_name, zone_id, subdomain, ipv6, "AAAA"):
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def sendMail(mail_data, title, content):
|
||||
logger.info("Sending mail to %s" % mail_data["receipent"])
|
||||
# Create a secure SSL context
|
||||
context = ssl.create_default_context()
|
||||
|
||||
# Try to log in to server and send email
|
||||
try:
|
||||
server = smtplib.SMTP(mail_data["host"], mail_data["port"])
|
||||
server.ehlo() # Can be omitted
|
||||
server.starttls(context=context) # Secure the connection
|
||||
server.ehlo() # Can be omitted
|
||||
server.login(mail_data["username"], mail_data["password"])
|
||||
|
||||
message = "From: " + mail_data["username"] + "\n"
|
||||
message += "To: " + mail_data["receipent"] + "\n"
|
||||
message += "Subject: "+title+"\n"
|
||||
message += "\n"
|
||||
message += content
|
||||
|
||||
server.sendmail(mail_data["username"], mail_data["receipent"], message)
|
||||
logger.debug("Mail send")
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
finally:
|
||||
server.quit()
|
||||
|
||||
def updateAll():
|
||||
global updateDNS
|
||||
global updateFirewall
|
||||
global openwrt
|
||||
global log_capture_string
|
||||
global mail_data
|
||||
|
||||
log_capture_string.truncate(0)
|
||||
log_capture_string.seek(0)
|
||||
|
||||
changed = False
|
||||
|
||||
if openwrt["enabled"] == True:
|
||||
if updateFirewall():
|
||||
changed = True
|
||||
else:
|
||||
logger.info("Firewall update is disabled")
|
||||
|
||||
if updateDNS():
|
||||
changed = True
|
||||
|
||||
if mail_data and "enabled" in mail_data and mail_data["enabled"] == True:
|
||||
if changed == True:
|
||||
sendMail(mail_data, "Info: DNS/Firewall Address Server", log_capture_string.getvalue())
|
||||
else:
|
||||
logger.debug("No email. nothing changed")
|
||||
else:
|
||||
logger.info("Email is disabled")
|
||||
|
||||
schedule.every(INTERVAL).seconds.do(updateAll)
|
||||
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
logger.debug("Connected with result code "+str(rc))
|
||||
if rc != 0:
|
||||
logger.error("ERROR: Please check MQTT Login")
|
||||
client.subscribe("network/+/hostname")
|
||||
client.subscribe("network/+/ipv4")
|
||||
client.subscribe("network/+/ipv6")
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
global hosts
|
||||
pl = str((msg.payload).decode("utf-8")).lower()
|
||||
logger.debug(msg.topic + ": " + pl)
|
||||
|
||||
if msg.topic.startswith("network/") and (msg.topic.endswith("/ipv4") or msg.topic.endswith("/ipv6")):
|
||||
host = msg.topic.split("/")[1]
|
||||
|
||||
if not host in hosts:
|
||||
hosts[host] = dict()
|
||||
|
||||
hosts[host]["ipv4" if msg.topic.endswith("/ipv4") else "ipv6"] = pl
|
||||
|
||||
client = mqtt.Client()
|
||||
client.username_pw_set(username=mqtt_data["username"], password=mqtt_data["password"])
|
||||
client.connect(mqtt_data["host"], mqtt_data["port"], 60)
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
|
||||
client.loop_start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt as ex:
|
||||
saveFile()
|
||||
|
||||
# https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/documentation/api/modules/luci.model.uci.html#Cursor.get
|
||||
# https://wiki.teltonika.lt/view/UCI_command_usage
|
69
settings.json.sample
Normal file
69
settings.json.sample
Normal file
@ -0,0 +1,69 @@
|
||||
{
|
||||
"apikey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"dns": {
|
||||
"domain2.com": {
|
||||
"@": {
|
||||
"ipv4": "public",
|
||||
"ipv6": "proxy"
|
||||
},
|
||||
"playing": {
|
||||
"ipv6": "playground"
|
||||
}
|
||||
},
|
||||
"domain2.com": {
|
||||
"test": {
|
||||
"ipv4": "public",
|
||||
"ipv6": "proxy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"firewall": {
|
||||
"cf2bd": {
|
||||
"dest_ip": "proxy"
|
||||
},
|
||||
"cfgbd": {
|
||||
"dest_ip": "proxy"
|
||||
},
|
||||
"cfgd": {
|
||||
"dest_ip": "plex"
|
||||
}
|
||||
},
|
||||
"hosts": {
|
||||
"playground": {
|
||||
"ipv4": "192.168.1.12",
|
||||
"ipv6": "2a02:908::8b0b"
|
||||
},
|
||||
"plex": {
|
||||
"ipv4": "192.168.1.11",
|
||||
"ipv6": "2a02:908::d237"
|
||||
},
|
||||
"proxy": {
|
||||
"ipv4": "192.168.1.10",
|
||||
"ipv6": "2a02:908::9867"
|
||||
}
|
||||
},
|
||||
"interval": 600,
|
||||
"mail": {
|
||||
"enabled": true,
|
||||
"host": "mail.example.com",
|
||||
"password": "",
|
||||
"port": 587,
|
||||
"receipent": "user@example.com",
|
||||
"username": "dns-updater@example.com"
|
||||
},
|
||||
"mqtt": {
|
||||
"_comment": "Please secure your MQTT server",
|
||||
"host": "192.168.1.2",
|
||||
"password": "",
|
||||
"port": 1883,
|
||||
"username": "mc8051"
|
||||
},
|
||||
"openwrt": {
|
||||
"_comment": "Please install luci JSON rpc package",
|
||||
"enabled": true,
|
||||
"host": "192.168.1.1",
|
||||
"password": "",
|
||||
"username": "root"
|
||||
},
|
||||
"user": "user@example.com"
|
||||
}
|
Loading…
Reference in New Issue
Block a user