├── README.md ├── active_vpn.py ├── dhcp_leases.py └── firewall.py /README.md: -------------------------------------------------------------------------------- 1 | # Python Scripts for Pushing VyOS data into InfluxDB/Grafana 2 | 3 | Note, all of these scrips require the InfluxDB Python Connector 4 | 5 | https://github.com/influxdata/influxdb-python 6 | 7 | However, the connector doesn't work with the version of Python that comes on the current release of VyOS (Python 2.6). To get it working, in line_protocol.py, edit lines 69 & 77 to read 8 | 9 | `return "\"{0}\"".format(value` 10 | -------------------------------------------------------------------------------- /active_vpn.py: -------------------------------------------------------------------------------- 1 | import time 2 | from influxdb import InfluxDBClient 3 | import commands 4 | 5 | db_host = '10.0.0.1' 6 | port = 8086 7 | user = 'influx' 8 | password = 'password' 9 | dbname = 'active-vpn-connections' 10 | client = InfluxDBClient(db_host, port, user, password, dbname) 11 | 12 | # Reads the output from the Perl script that runs when you call "show vnp remote access" 13 | # Pasring CLI output wasn't my first choice, but looking at the Perl there didn't seem to be an easy way to get all this data in one place 14 | 15 | def save_influx(line): 16 | json_body = [ 17 | { 18 | "measurement": "connection", 19 | "tags": { 20 | "host": "router", 21 | }, 22 | "fields": line 23 | } 24 | ] 25 | print("Write points: {0}".format(json_body)) 26 | client.write_points(json_body) 27 | 28 | 29 | while True: 30 | connections = [] 31 | 32 | client.drop_database("active-vpn-connections") 33 | client.create_database("active-vpn-connections") 34 | output = commands.getstatusoutput('/opt/vyatta/sbin/vyatta-show-ravpn.pl')[1] 35 | output = output.split('\n') 36 | if len(output[0])>1: # If only one line is returned, assume no active connections 37 | del output [0:4] # Remove header rows 38 | # Assumes that each filed is a fixed width. 39 | # Looking at the Perl, I believe this is a safe assumption 40 | for line in output: 41 | connection = {} 42 | connection['user'] = line[0:16].replace(" ","") 43 | connection['type'] = line[16:22].replace(" ","") 44 | connection['interface'] = line[22:32].replace(" ","") 45 | connection['ip_address'] = line[32:50].replace(" ","") 46 | connection['tx_byte'] = line[50:58].replace(" ","") 47 | connection['rx_byte'] = line[58:65].replace(" ","") 48 | connection['connection_time'] = line[65:75].replace(" ","") 49 | 50 | connections.append(connection) 51 | for connection in connections: 52 | save_influx(connection) 53 | time.sleep(15) 54 | -------------------------------------------------------------------------------- /dhcp_leases.py: -------------------------------------------------------------------------------- 1 | import time 2 | from influxdb import InfluxDBClient 3 | 4 | db_host = '10.0.0.1' 5 | port = 8086 6 | user = 'influx' 7 | password = 'password' 8 | dbname = 'dhcp-leases' 9 | client = InfluxDBClient(db_host, port, user, password, dbname) 10 | 11 | # Reads the /config/dhcpd.leases file 12 | 13 | def xstr(s): 14 | return '' if s is None else str(s) 15 | 16 | def save_influx(line): 17 | json_body = [ 18 | { 19 | "measurement": "active-lease", 20 | "tags": { 21 | "host": "router", 22 | }, 23 | "fields": line 24 | } 25 | ] 26 | print("Write points: {0}".format(json_body)) 27 | client.write_points(json_body) 28 | 29 | while True: 30 | leases = open('/config/dhcpd.leases','r') 31 | leases_final_dict = {} 32 | for line in leases: 33 | # Entry Starts with a "{" 34 | if "{" in line: 35 | lease_dict = {} 36 | lease_dict['ip'] = line.replace("lease ", "").replace(" {", "")[:-1] 37 | 38 | # Find Hostname and clean the data 39 | if "client-hostname" in line: 40 | lease_dict['hostname'] = line.replace(" client-hostname ", "").replace(" ", "").replace('"',"").replace(";","")[:-1] 41 | 42 | # Find MAC and clean the data 43 | if "hardware ethernet" in line: 44 | lease_dict['mac'] = line.replace(" hardware ethernet ", "").replace(" ", "").replace('"',"").replace(";","")[:-1] 45 | 46 | # I only care about active leases right now... 47 | if "binding state active" in line: 48 | lease_dict['active'] = True 49 | 50 | # Find the lease end time and clean the data 51 | if "ends" in line: 52 | lease_dict['expires'] = line.replace(" ends ", "").replace("", "").replace('"',"").replace(";","")[3:-1] 53 | 54 | # End of Entry 55 | if "}" in line: 56 | # The dhcpd.leases can sometimes contain multiple declaration for the same lease 57 | # This keeps track of them by the ip/mac and makes sure only the last entry is saved 58 | # Info here: https://linux.die.net/man/5/dhcpd.leases 59 | leases_final_dict[xstr(lease_dict.get('ip'))+xstr(lease_dict.get('mac'))] = lease_dict 60 | print lease_dict 61 | leases.close() 62 | client.drop_database("dhcp-leases") 63 | client.create_database("dhcp-leases") 64 | for lease in leases_final_dict.values(): 65 | if lease.get('active'): 66 | save_influx(lease) 67 | time.sleep(30) 68 | -------------------------------------------------------------------------------- /firewall.py: -------------------------------------------------------------------------------- 1 | import time 2 | import socket 3 | import urllib2 4 | import json 5 | import os 6 | from influxdb import InfluxDBClient 7 | 8 | db_host = '10.0.0.1' 9 | port = 8086 10 | user = 'influx' 11 | password = 'password' 12 | dbname = 'firewall' 13 | client = InfluxDBClient(db_host, port, user, password, dbname) 14 | 15 | def save_influx(line): 16 | json_body = [ 17 | { 18 | "measurement": "wan-local-blocked", 19 | "tags": { 20 | "host": "router", 21 | #The Grafana map plugin requires the two letter country code to be a tag not a field 22 | "country": line.get("geo_code") 23 | }, 24 | "fields": line 25 | } 26 | ] 27 | print("Write points: {0}".format(json_body)) 28 | client.write_points(json_body) 29 | 30 | 31 | def parse_line(line): 32 | # I seem to get a lot of entries that seem to be valid trafic coming in on 443/SSL (Netflix/Apple...) 33 | # I'm filtering them out because I'm not smart enough to know why the firewall is catching them 34 | if line and line.get("SPT") != '443': 35 | final_dict = {} 36 | final_dict['src_ip'] = line.get("SRC") 37 | final_dict['dest_port'] = line.get("DPT") 38 | #Do a reverse DNS lookup on the IP to try to get a domain name 39 | try: 40 | final_dict['rvs_dns'] = socket.gethostbyaddr(final_dict.get('src_ip'))[0] 41 | except: 42 | pass 43 | # Hit the freegeoip API to get a country / state code for the map 44 | try: 45 | response = urllib2.urlopen('http://freegeoip.net/json/'+final_dict.get('src_ip')) 46 | geo_data = json.load(response) 47 | final_dict['geo_code'] = str(geo_data.get("country_code")) 48 | if geo_data.get("country_code") == "US": 49 | final_dict['state'] = str(geo_data.get("region_code")) 50 | except: 51 | pass 52 | # Try to get a service name from the port number 53 | try: 54 | final_dict['service'] = socket.getservbyport(int(final_dict['dest_port'])) 55 | except: 56 | pass 57 | save_influx(final_dict) 58 | 59 | # Finds lines that match a log entry for the firewall rule "OUTSIDE-LOCAL" 60 | #splits the, into key, value pairs in a dictionary 61 | def process(line): 62 | final_dict = {} 63 | if "[OUTSIDE-LOCAL-default-D]" in line: 64 | line = line.replace("[OUTSIDE-LOCAL-default-D]", "") 65 | line = line.split(" ") 66 | for item in line: 67 | if "=" in item: 68 | item = item.split("=") 69 | final_dict[item[0]] = item[1] 70 | return final_dict 71 | 72 | # Keeps track of the rotating log files and feeds new lines into the above 73 | # https://stackoverflow.com/a/43547769 74 | file_name = '/var/log/messages' 75 | seek_end = True 76 | while True: # handle moved/truncated files by allowing to reopen 77 | with open(file_name) as f: 78 | if seek_end: # reopened files must not seek end 79 | f.seek(0, 2) 80 | while True: # line reading loop 81 | line = f.readline() 82 | if not line: 83 | try: 84 | if f.tell() > os.path.getsize(file_name): 85 | # rotation occurred (copytruncate/create) 86 | f.close() 87 | seek_end = False 88 | break 89 | except Exception as e: 90 | # rotation occurred but new file still not created 91 | print e 92 | pass # wait 1 second and retry 93 | time.sleep(1) 94 | parse_line(process(line)) 95 | --------------------------------------------------------------------------------