Skip to content

Instantly share code, notes, and snippets.

@m-hume
Last active June 12, 2022 19:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save m-hume/9e882d83e084ebf08f9f to your computer and use it in GitHub Desktop.
Save m-hume/9e882d83e084ebf08f9f to your computer and use it in GitHub Desktop.
This python script is used to read and modify the configuration of an EdgeMax ubnt router. Specifically it updates the source address of an inbound nat rule that allows remote administration from a dynamic IP address tied to a dynamic DNS account. Highly reusable is the function get_config_object(). Pass to this the textural content of config.bo…
#! /usr/bin/python
# This python script is used to read and modify the configuration of an EdgeMax
# ubnt router.
# Specifically it updates the source address of an inbound nat rule that allows
# remote administration from a dynamic IP address tied to a dynamic DNS account.
# Highly reusable is the function get_config_object(). Pass to this the textural
# content of config.boot and it will convert it into an object for querying.
# Feel free to use it and modify it!
# mh 2015 monkeyr.com
import json
import socket
import os
def get_config_object(s_config):
s_out = '{'
i_current_indent = -1
i_multiplyer = 4
for s_line in s_config.splitlines():
s_striped_line = s_line.strip(' ')
i_spaces = len(s_line) - len(s_striped_line)
i_loop_indent = i_spaces / i_multiplyer
if i_loop_indent == i_current_indent and b_object_has_data:
s_out += ','
if s_striped_line[-1:] == '{': #is object start
b_object_has_data = False
s_out += '"'+s_striped_line[:-2].strip('"')+'":{'
elif s_striped_line[-1:] == '}': #is object end
b_object_has_data = True
s_out += '}'
else :# is property#
b_object_has_data = True
tmp = s_striped_line.split(' ', 1)
if len(tmp) == 1:
tmp.append('"true"')
s_out += '"'+tmp[0]+'":'+json.dumps(tmp[1].strip('"'))
i_current_indent = i_loop_indent
s_out += '}'
#print out
return json.loads(s_out)
s_cfg_path = '/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper'
s_dyna_ip = socket.gethostbyname("my-dynamic-dns-account.no-ip.org")
if s_dyna_ip: # were connected to the internet and can resolve the dynamic host name
# get the config as a string
f_handle = os.popen(s_cfg_path+ ' show')
s_config = f_handle.read()
# get the config as an object
o_config = get_config_object(s_config)
s_rule_name = ''
s_current_ip = ''
# find the right nat rule
for rule, value in o_config['service']['nat'].iteritems():
if value['description'] == 'Remote admin from home':
#record current ip
s_rule_name = rule
s_current_ip = value['source']['address']
break
if s_rule_name and s_current_ip:
if s_dyna_ip == s_current_ip:
print '%s source address is current %s' % (s_rule_name, s_current_ip)
else:
# set the new address
os.system(s_cfg_path+' begin')
os.system(s_cfg_path+' set service nat '+ s_rule_name +' source address '+ s_dyna_ip)
os.system(s_cfg_path+' commit')
os.system(s_cfg_path+' end')
print '%s source address updated from %s to %s' % (s_rule_name, s_current_ip, s_dyna_ip)
@Go2ClassPoorYorick
Copy link

Go2ClassPoorYorick commented Aug 13, 2018

It appears your get_config_object loop is parsing interfaces incorrectly: If an interface has more than one address, this script will only print one of the interfaces.

Example interface:
show interfaces ethernet eth0 { address 10.1.1.1/24 address 10.4.2.1/24 duplex auto speed auto }
Example adapted code:

import json
import socket
import os

def get_config_object(s_config):
	s_out = '{'
	i_current_indent = -1
	i_multiplyer = 4
	for s_line in s_config.splitlines():
		s_striped_line = s_line.strip(' ')
		i_spaces = len(s_line) - len(s_striped_line)
		i_loop_indent = i_spaces / i_multiplyer

		if i_loop_indent == i_current_indent and b_object_has_data:
			s_out += ','

		if s_striped_line[-1:] == '{': #is object start
			b_object_has_data = False
			s_out += '"'+s_striped_line[:-2].strip('"')+'":{'

		elif s_striped_line[-1:] == '}': #is object end
			b_object_has_data = True
			s_out += '}'

		else :# is property#
			b_object_has_data = True
			tmp = s_striped_line.split(' ', 1)
			if len(tmp) == 1:
				tmp.append('"true"')
			s_out += '"'+tmp[0]+'":'+json.dumps(tmp[1].strip('"'))

		i_current_indent = i_loop_indent
	s_out += '}'
	#print out
	return json.loads(s_out)


s_cfg_path = '/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper'


f_handle = os.popen(s_cfg_path+ ' show')
s_config = f_handle.read()

o_config = get_config_object(s_config)
print o_config['interfaces'] 
for rule, value in o_config['interfaces']['ethernet eth0'].iteritems():
	print rule, value

Output:
{u'ethernet eth4': {u'duplex': u'auto', u'poe': {u'output': u'off'}, u'speed': u'auto', u'address': u'192.168.1.9/24'}, u'ethernet eth1': {u'duplex': u'auto', u'firewall': {u'out': {u'name': u'Wan_Access'}}, u'speed': u'auto', u'address': u'123.123.123.1/24'}, u'loopback lo': {}, u'ethernet eth3': {u'duplex': u'auto', u'speed': u'auto', u'address': u'10.0.1.1/24'}, u'ethernet eth2': {u'duplex': u'auto', u'firewall': {u'out': {u'name': u'Wan_Access'}}, u'speed': u'auto', u'address': u'10.0.0.1/24'}, u'switch switch0': {u'switch-port': {u'vlan-aware': u'disable'}, u'mtu': u'1500'}, u'ethernet eth0': {u'duplex': u'auto', u'speed': u'auto', u'address': u'10.4.2.1/24'}} duplex auto speed auto address 10.4.2.1/24
As you can see, from the poorly formatted code and outputs above, there's only one address being shown, despite having 2 addresses in the config.

I'm not fluent in python so I'll need time to propose a solution, but I just wanted to make sure people where aware of this information.

@Go2ClassPoorYorick
Copy link

Go2ClassPoorYorick commented Aug 14, 2018

I tried to bang something together from scratch and came up with the following. This should return a valid JSON string, and any situation such as the double address gets returned as a value string that can be converted into a list. I'm sure this isn't the most effective code, but I digress.

import socket
import os
import re
import shlex
from itertools import tee, islice, izip_longest

def get_next(some_iterable, window=1):
    items, nexts = tee(some_iterable, 2)
    nexts = islice(nexts, window, None)
    return izip_longest(items, nexts)

def get_config_object(s_config):
	s_out = '{'
	stripped = re.sub('[ ]{2,}','',s_config)
	looped_values=[]
	looped_key = []
	for s_line, s_next_line in get_next(stripped.splitlines()):
		if s_line.endswith('{'):
				s_out += '"'+s_line.split(' {')[0]+'":'+' {\n'
		elif s_line.endswith('}'):
			if s_next_line is None:
				s_out += '}\n'
			elif s_next_line.endswith('{'):
				s_out += '},\n'
			elif s_next_line.endswith('}'):
				s_out += '}\n'
			else:
				s_out += '},\n'
		else:
			key_current= shlex.split(s_line)
			key_next = shlex.split(s_next_line)
			if key_current[0] != key_next[0] and key_current[0] != looped_key:
				if len(key_current) == 1:
					if s_next_line.endswith('}'):
						s_out += '"'+key_current[0]+'": ""\n'
					else:
						s_out += '"'+key_current[0]+'": "",\n'
				else:
					if s_next_line.endswith('}'):
						s_out += '"'+key_current[0]+'": "'+key_current[1]+'"\n'
					else:
						s_out += '"'+key_current[0]+'": "'+key_current[1]+'",\n'
			else:
				looped_key = key_current[0]
				looped_values.append(key_current[1])
				if key_next[0] != key_current[0]:
					s_out += '"'+looped_key+'": "'+str(looped_values)+'"'
					if s_next_line.endswith('}'):
						s_out += '\n'
					else:
						s_out += ',\n'
					looped_key = []
					looped_values = []
	s_out += '}'
	return json.loads(s_out)


s_cfg_path = '/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper'


f_handle = os.popen(s_cfg_path+ ' show')
s_config = f_handle.read()
# get the config as an object
o_config = get_config_object(s_config)```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment