summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/accel_pppoe.py363
-rwxr-xr-xsrc/conf_mode/dhcp_server.py19
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py14
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py4
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py179
-rwxr-xr-xsrc/conf_mode/wireguard.py33
-rwxr-xr-xsrc/etc/init.d/igmpproxy166
-rwxr-xr-xsrc/etc/init.d/isc-dhcpv6-relay50
-rwxr-xr-xsrc/etc/init.d/isc-dhcpv6-server113
-rwxr-xr-xsrc/migration-scripts/l2tp/0-to-158
-rwxr-xr-xsrc/op_mode/cpu_summary.py16
-rwxr-xr-xsrc/op_mode/dynamic_dns.py16
-rwxr-xr-xsrc/op_mode/maya_date.py7
-rwxr-xr-xsrc/op_mode/powerctrl.py22
-rwxr-xr-xsrc/op_mode/show-igmpproxy.py241
15 files changed, 1276 insertions, 25 deletions
diff --git a/src/conf_mode/accel_pppoe.py b/src/conf_mode/accel_pppoe.py
new file mode 100755
index 000000000..4aea84c44
--- /dev/null
+++ b/src/conf_mode/accel_pppoe.py
@@ -0,0 +1,363 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+import os
+import re
+import subprocess
+import jinja2
+import socket
+import time
+import syslog as sl
+
+from vyos.config import Config
+from vyos import ConfigError
+
+pidfile = r'/var/run/accel_pppoe.pid'
+pppoe_cnf_dir = r'/etc/accel-ppp/pppoe'
+chap_secrets = pppoe_cnf_dir + '/chap-secrets'
+pppoe_conf = pppoe_cnf_dir + '/pppoe.config'
+# accel-pppd -d -c /etc/accel-ppp/pppoe/pppoe.config -p /var/run/accel_pppoe.pid
+
+### config path creation
+if not os.path.exists(pppoe_cnf_dir):
+ os.makedirs(pppoe_cnf_dir)
+ sl.syslog(sl.LOG_NOTICE, pppoe_cnf_dir + " created")
+
+pppoe_config = '''
+### generated by accel_pppoe.py ###
+[modules]
+log_syslog
+pppoe
+ippool
+chap-secrets
+auth_pap
+auth_chap_md5
+auth_mschap_v1
+auth_mschap_v2
+pppd_compat
+shaper
+net-snmp
+connlimit
+{% if authentication['mode'] == 'radius' %}
+radius
+{% endif %}
+
+[core]
+thread-count={{thread_cnt}}
+
+[log]
+syslog=accel-pppoe,daemon
+copy=1
+level=5
+
+[snmp]
+master=1
+
+[client-ip-range]
+disable
+
+[ip-pool]
+{% if client_ip_pool %}
+{{client_ip_pool}}
+{% endif %}
+gw-ip-address={{ppp_gw}}
+
+{% if dns %}
+[dns]
+{% if dns[0] %}
+dns1={{dns[0]}}
+{% endif %}
+{% if dns[1] %}
+dns2={{dns[1]}}
+{% endif %}
+{% endif %}
+
+{% if wins %}
+[wins]
+{% if wins[0] %}
+wins1={{wins[0]}}
+{% endif %}
+{% if wins[1] %}
+wins2={{wins[1]}}
+{% endif %}
+{% endif %}
+
+{% if authentication['mode'] == 'local' %}
+[chap-secrets]
+chap-secrets=/etc/accel-ppp/pppoe/chap-secrets
+{% endif %}
+
+{% if authentication['mode'] == 'radius' %}
+[radius]
+{% for rsrv in authentication['radiussrv']: %}
+server={{rsrv}},{{authentication['radiussrv'][rsrv]}}
+{% endfor %}
+timeout=10
+acct-timeout=3
+gw-ip-address={{ppp_gw}}
+verbose=1
+{% endif %}
+
+[ppp]
+verbose=1
+min-mtu={{mtu}}
+mtu={{mtu}}
+mru=1400
+check-ip=1
+mppe=prefer
+ipv4=require
+check-ip=1
+single-session=replace
+mppe=prefer
+lcp-echo-interval=30
+lcp-echo-failure=3
+
+[pppoe]
+verbose=1
+{% if concentrator %}
+ac-name={{concentrator}}
+{% endif %}
+{% if interface %}
+{% for int in interface %}
+interface={{int}}
+{% endfor %}
+{% endif %}
+{% if svc_name %}
+service-name={{svc_name}}
+{% endif %}
+
+
+[connlimit]
+limit=10/min
+burst=3
+timeout=60
+
+[cli]
+tcp=127.0.0.1:2001
+'''
+
+### pppoe chap secrets
+chap_secrets_conf = '''
+# username server password acceptable local IP addresses
+{% for user in authentication['local-users'] %}
+{% if authentication['local-users'][user]['state'] == 'enabled' %}
+{{user}}\t*\t{{authentication['local-users'][user]['passwd']}}\t{{authentication['local-users'][user]['ip']}}
+{% endif %}
+{% endfor %}
+'''
+###
+# inline helper functions
+###
+# depending on hw and threads, daemon needs a little to start
+# if it takes longer than 100 * 0.5 secs, exception is being raised
+# not sure if that's the best way to check it, but it worked so far quite well
+###
+def chk_con():
+ cnt = 0
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ while True:
+ try:
+ s.connect(("127.0.0.1", 2001))
+ break
+ except ConnectionRefusedError:
+ time.sleep(0.5)
+ cnt +=1
+ if cnt == 100:
+ raise("failed to start pppoe server")
+ break
+
+### chap_secrets file if auth mode local
+def write_chap_secrets(c):
+ tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True)
+ chap_secrets_txt = tmpl.render(c)
+ old_umask = os.umask(0o077)
+ open(chap_secrets,'w').write(chap_secrets_txt)
+ os.umask(old_umask)
+ sl.syslog(sl.LOG_NOTICE, chap_secrets + ' written')
+
+def accel_cmd(cmd=''):
+ if not cmd:
+ return None
+ try:
+ ret = subprocess.check_output(['/usr/bin/accel-cmd',cmd]).decode().strip()
+ return ret
+ except:
+ return 1
+
+###
+# inline helper functions end
+###
+
+def get_config():
+ c = Config()
+ if not c.exists('service pppoe-server'):
+ return None
+
+ config_data = {
+ 'concentrator' : 'vyos-ac',
+ 'authentication' : {
+ 'local-users' : {
+ },
+ 'mode' : 'local',
+ 'radiussrv' : {}
+ },
+ 'client_ip_pool' : '',
+ 'interface' : [],
+ 'ppp_gw' : '',
+ 'svc_name' : '',
+ 'dns' : [],
+ 'wins' : [],
+ 'mtu' : '1492'
+ }
+
+ c.set_level('service pppoe-server')
+
+ if c.exists('access-concentrator'):
+ config_data['concentrator'] = c.return_value('access-concentrator')
+ if c.exists('service-name'):
+ config_data['svc_name'] = c.return_value('service-name')
+ if c.exists('interface'):
+ config_data['interface'] = c.return_values('interface')
+ if c.exists('local-ip'):
+ config_data['ppp_gw'] = c.return_value('local-ip')
+ if c.exists('dns-servers'):
+ if c.return_value('dns-servers server-1'):
+ config_data['dns'].append(c.return_value('dns-servers server-1'))
+ if c.return_value('dns-servers server-2'):
+ config_data['dns'].append(c.return_value('dns-servers server-2'))
+ if c.exists('wins-servers'):
+ if c.return_value('wins-servers server-1'):
+ config_data['wins'].append(c.return_value('wins-servers server-1'))
+ if c.return_value('wins-servers server-2'):
+ config_data['wins'].append(c.return_value('wins-servers server-2'))
+ if c.exists('client-ip-pool'):
+ if c.exists('client-ip-pool start'):
+ config_data['client_ip_pool'] = c.return_value('client-ip-pool start')
+ if c.exists('client-ip-pool stop'):
+ config_data['client_ip_pool'] += '-' + re.search('[0-9]+$', c.return_value('client-ip-pool stop')).group(0)
+ else:
+ raise ConfigError('client ip pool stop required')
+
+ #### authentication mode local
+ if c.exists('authentication'):
+ if c.return_value('authentication mode') == 'local':
+ if c.exists('authentication local-users username'):
+ for usr in c.list_nodes('authentication local-users username'):
+ config_data['authentication']['local-users'].update(
+ {
+ usr : {
+ 'passwd' : '',
+ 'state' : 'enabled',
+ 'ip' : '*'
+ }
+ }
+ )
+ if c.exists('authentication local-users username ' + usr + ' password'):
+ config_data['authentication']['local-users'][usr]['passwd'] = c.return_value('authentication local-users username ' + usr + ' password')
+ if c.exists('authentication local-users username ' + usr + ' disable'):
+ config_data['authentication']['local-users'][usr]['state'] = 'disable'
+ if c.exists('authentication local-users username ' + usr + ' static-ip'):
+ config_data['authentication']['local-users'][usr]['ip'] = c.return_value('authentication local-users username ' + usr + ' static-ip')
+
+ ### authentication mode radius
+ if c.return_value('authentication mode') == 'radius':
+ config_data['authentication']['mode'] = 'radius'
+ rsrvs = c.list_nodes('authentication radius-server')
+ for rsrv in rsrvs:
+ config_data['authentication']['radiussrv'].update(
+ {
+ rsrv : str(c.return_value('authentication radius-server ' + rsrv + ' key'))
+ }
+ )
+
+ if c.exists('mtu'):
+ config_data['mtu'] = c.return_value('mtu')
+
+ return config_data
+
+def verify(c):
+ if c == None:
+ return None
+
+ for usr in c['authentication']['local-users']:
+ if not c['authentication']['local-users'][usr]:
+ raise ConfigError('user ' + usr + ' has no password set')
+
+ if not c['ppp_gw']:
+ raise ConfigError('pppoe gateway-ip required')
+
+ if c['authentication']['mode'] == 'radius':
+ if len(c['authentication']['radiussrv']) == 0:
+ raise ConfigError('radius server required')
+
+def generate(c):
+ if c == None:
+ return None
+
+ ### accel-cmd reload doesn't work so any change results in a restart of the daemon
+ try:
+ if os.cpu_count() == 1:
+ c['thread_cnt'] = 1
+ else:
+ c['thread_cnt'] = int(os.cpu_count()/2)
+ except KeyError:
+ if os.cpu_count() == 1:
+ c['thread_cnt'] = 1
+ else:
+ c['thread_cnt'] = int(os.cpu_count()/2)
+
+ tmpl = jinja2.Template(pppoe_config, trim_blocks=True)
+ config_text = tmpl.render(c)
+ open(pppoe_conf,'w').write(config_text)
+ sl.syslog(sl.LOG_NOTICE, pppoe_config + ' written')
+
+ return c
+
+def apply(c):
+ if c == None:
+ if os.path.exists(pidfile):
+ accel_cmd('shutdown hard')
+ if os.path.exists(pidfile):
+ os.remove(pidfile)
+ return None
+
+ if not os.path.exists(pidfile):
+ ret = subprocess.call(['/usr/sbin/accel-pppd','-c',pppoe_conf,'-p',pidfile,'-d'])
+ chk_con()
+ if ret !=0 and os.path.exists(pidfile):
+ os.remove(pidfile)
+ raise ConfigError('accel-pppd failed to start')
+ else:
+ accel_cmd('restart')
+ sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart")
+
+ #if c['state'] == 'update':
+ # accel_cmd('restart')
+ # sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart")
+ # ## check that config reload actually works
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 2a2b1fe6c..560c80e7f 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -42,14 +42,6 @@ config_tmpl = """
# log-facility local7;
{% if hostfile_update %}
-on commit {
- set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
- set ClientIp = binary-to-ascii(10, 8, ".", leased-address);
- set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
- set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!");
- execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain);
-}
-
on release {
set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
set ClientIp = binary-to-ascii(10, 8, ".",leased-address);
@@ -210,7 +202,16 @@ shared-network {{ network.name }} {
{%- endif %}
}
{%- endfor %}
- on commit { set shared-networkname = "{{ network.name }}"; }
+ on commit {
+ set shared-networkname = "{{ network.name }}";
+ {% if hostfile_update -%}
+ set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
+ set ClientIp = binary-to-ascii(10, 8, ".", leased-address);
+ set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
+ set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!");
+ execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain);
+ {% endif -%}
+ }
}
{%- endif %}
{% endfor %}
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 43be9d526..c21a91a30 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -36,9 +36,11 @@ config_tmpl = """
# Non-configurable defaults
daemon=yes
threads=1
-allow-from=0.0.0.0/0
+allow-from=0.0.0.0/0, ::/0
log-common-errors=yes
non-local-bind=yes
+query-local-address=0.0.0.0
+query-local-address6=::
# cache-size
max-cache-entries={{ cache_size }}
@@ -114,10 +116,10 @@ def get_config():
if conf.exists('domain'):
for node in conf.list_nodes('domain'):
- server = conf.return_values("domain {0} server".format(node))
+ servers = conf.return_values("domain {0} server".format(node))
domain = {
"name": node,
- "servers": server
+ "servers": bracketize_ipv6_addrs(servers)
}
dns['domains'].append(domain)
@@ -138,6 +140,8 @@ def get_config():
dns['name_servers'] = dns['name_servers'] + system_name_servers
conf.set_level('service dns forwarding')
+ dns['name_servers'] = bracketize_ipv6_addrs(dns['name_servers'])
+
if conf.exists('listen-address'):
dns['listen_on'] = conf.return_values('listen-address')
@@ -193,6 +197,10 @@ def get_config():
return dns
+def bracketize_ipv6_addrs(addrs):
+ """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched."""
+ return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs]
+
def verify(dns):
# bail out early - looks like removal from running config
if dns is None:
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index 60efcaae2..afebc5d0e 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -38,8 +38,8 @@ cache=/var/cache/ddclient/ddclient.cache
#
# ddclient configuration for interface "{{ interface.interface }}":
#
-{% if interface.web_url and interface.web_skip -%}
-use=web, web={{ interface.web_url}}, web-skip={{ interface.web_skip }}
+{% if interface.web_url -%}
+use=web, web={{ interface.web_url}} {%- if interface.web_skip %}, web-skip={{ interface.web_skip }}{% endif %}
{% else -%}
use=if, if={{ interface.interface }}
{% endif -%}
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
new file mode 100755
index 000000000..b994369af
--- /dev/null
+++ b/src/conf_mode/igmp_proxy.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+import os
+import jinja2
+
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file = r'/etc/igmpproxy.conf'
+
+# Please be careful if you edit the template.
+config_tmpl = """
+########################################################
+#
+# autogenerated by igmp_proxy.py
+#
+# The configuration file must define one upstream
+# interface, and one or more downstream interfaces.
+#
+# If multicast traffic originates outside the
+# upstream subnet, the "altnet" option can be
+# used in order to define legal multicast sources.
+# (Se example...)
+#
+# The "quickleave" should be used to avoid saturation
+# of the upstream link. The option should only
+# be used if it's absolutely nessecary to
+# accurately imitate just one Client.
+#
+########################################################
+
+{% if not disable_quickleave -%}
+quickleave
+{% endif -%}
+
+{% for i in interface %}
+# Configuration for {{ i.interface }} ({{ i.role }} interface)
+{% if i.role == 'disabled' -%}
+phyint {{ i.interface }} disabled
+{%- else -%}
+phyint {{ i.interface }} {{ i.role }} ratelimit 0 threshold {{ i.threshold }}
+{%- endif -%}
+{%- for subnet in i.alt_subnet %}
+ altnet {{ subnet }}
+{%- endfor %}
+{%- for subnet in i.whitelist %}
+ whitelist {{ subnet }}
+{%- endfor %}
+{% endfor %}
+"""
+
+default_config_data = {
+ 'disable': False,
+ 'disable_quickleave': False,
+ 'interface': [],
+}
+
+def get_config():
+ igmp_proxy = default_config_data
+ conf = Config()
+ if not conf.exists('protocols igmp-proxy'):
+ return None
+ else:
+ conf.set_level('protocols igmp-proxy')
+
+ # Network interfaces to listen on
+ if conf.exists('disable'):
+ igmp_proxy['disable'] = True
+
+ # Option to disable "quickleave"
+ if conf.exists('disable-quickleave'):
+ igmp_proxy['disable_quickleave'] = True
+
+ for intf in conf.list_nodes('interface'):
+ conf.set_level('protocols igmp-proxy interface {0}'.format(intf))
+ interface = {
+ 'interface': intf,
+ 'alt_subnet': [],
+ 'role': 'downstream',
+ 'threshold': '1',
+ 'whitelist': []
+ }
+
+ if conf.exists('alt-subnet'):
+ interface['alt_subnet'] = conf.return_values('alt-subnet')
+
+ if conf.exists('role'):
+ interface['role'] = conf.return_value('role')
+
+ if conf.exists('threshold'):
+ interface['threshold'] = conf.return_value('threshold')
+
+ if conf.exists('whitelist'):
+ interface['whitelist'] = conf.return_values('whitelist')
+
+ # Append interface configuration to global configuration list
+ igmp_proxy['interface'].append(interface)
+
+ return igmp_proxy
+
+def verify(igmp_proxy):
+ # bail out early - looks like removal from running config
+ if igmp_proxy is None:
+ return None
+
+ # bail out early - service is disabled
+ if igmp_proxy['disable']:
+ return None
+
+ # at least two interfaces are required, one upstream and one downstream
+ if len(igmp_proxy['interface']) < 2:
+ raise ConfigError('Must define an upstream and at least 1 downstream interface!')
+
+ upstream = 0
+ for i in igmp_proxy['interface']:
+ if "upstream" == i['role']:
+ upstream += 1
+
+ if upstream == 0:
+ raise ConfigError('At least 1 upstream interface is required!')
+ elif upstream > 1:
+ raise ConfigError('Only 1 upstream interface allowed!')
+
+ return None
+
+def generate(igmp_proxy):
+ # bail out early - looks like removal from running config
+ if igmp_proxy is None:
+ return None
+
+ # bail out early - service is disabled, but inform user
+ if igmp_proxy['disable']:
+ print('Warning: IGMP Proxy will be deactivated because it is disabled')
+ return None
+
+ tmpl = jinja2.Template(config_tmpl)
+ config_text = tmpl.render(igmp_proxy)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+
+ return None
+
+def apply(igmp_proxy):
+ if igmp_proxy is None or igmp_proxy['disable']:
+ # IGMP Proxy support is removed in the commit
+ os.system('sudo systemctl stop igmpproxy.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
+ else:
+ os.system('sudo systemctl restart igmpproxy.service')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/wireguard.py b/src/conf_mode/wireguard.py
index 5d390f39f..353528aba 100755
--- a/src/conf_mode/wireguard.py
+++ b/src/conf_mode/wireguard.py
@@ -159,11 +159,37 @@ def apply(c):
c_eff = Config()
c_eff.set_level('interfaces wireguard')
- ### deletion of specific interface
+ ### deletion of a specific interface
for intf in c['interfaces']:
if c['interfaces'][intf]['status'] == 'delete':
sl.syslog(sl.LOG_NOTICE, "removing interface " + intf)
subprocess.call(['ip l d dev ' + intf + ' &>/dev/null'], shell=True)
+
+
+ ### peer deletion
+ peer_eff = c_eff.list_effective_nodes( intf + ' peer')
+ peer_cnf = []
+ try:
+ for p in c['interfaces'][intf]['peer']:
+ peer_cnf.append(p)
+ except KeyError:
+ pass
+
+ peer_rem = list(set(peer_eff) - set(peer_cnf))
+ for p in peer_rem:
+ pkey = c_eff.return_effective_value( intf + ' peer ' + p +' pubkey')
+ remove_peer(intf, pkey)
+
+ ### peer pubkey update
+ ### wg identifies peers by its pubky, so we have to remove the peer first
+ ### it will recreated it then below with the new key from the cli config
+ for p in peer_eff:
+ if p in peer_cnf:
+ ekey = c_eff.return_effective_value( intf + ' peer ' + p +' pubkey')
+ nkey = c['interfaces'][intf]['peer'][p]['pubkey']
+ if nkey != ekey:
+ sl.syslog(sl.LOG_NOTICE, "peer " + p + ' changed pubkey from ' + ekey + 'to key ' + nkey + ' on interface ' + intf)
+ remove_peer(intf, ekey)
### new config
if c['interfaces'][intf]['status'] == 'create':
@@ -304,6 +330,11 @@ def del_addr(intf, addr):
ret = subprocess.call(['ip a d dev ' + intf + ' ' + addr + ' &>/dev/null'], shell=True)
sl.syslog(sl.LOG_NOTICE, "ip a d dev " + intf + " " + addr)
+def remove_peer(intf, peer_key):
+ cmd = 'sudo wg set ' + str(intf) + ' peer ' + peer_key + ' remove &>/dev/null'
+ ret = subprocess.call([cmd], shell=True)
+ sl.syslog(sl.LOG_NOTICE, "peer " + peer_key + " removed from " + intf)
+
if __name__ == '__main__':
try:
check_kmod()
diff --git a/src/etc/init.d/igmpproxy b/src/etc/init.d/igmpproxy
new file mode 100755
index 000000000..4a2c94a4d
--- /dev/null
+++ b/src/etc/init.d/igmpproxy
@@ -0,0 +1,166 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: igmpproxy
+# Required-Start: $local_fs $network $remote_fs $syslog
+# Required-Stop: $local_fs $network $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: IGMP multicast routing daemon
+# Description: IGMPproxy is a simple dynamic Multicast Routing Daemon
+# using only IGMP signalling. It's intended for simple
+# forwarding of Multicast traffic between networks.
+### END INIT INFO
+
+# Author: Pali Rohár <pali.rohar@gmail.com>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="igmpproxy"
+NAME=igmpproxy
+DAEMON=/sbin/igmpproxy
+DAEMON_ARGS="/etc/igmpproxy.conf"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -b -m -- \
+ $DAEMON_OPTS $DAEMON_ARGS \
+ || return 2
+ # The above code will not work for interpreted scripts, use the next
+ # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+ #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME --test > /dev/null \
+ # || return 1
+ #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME -- $DAEMON_ARGS \
+ # || return 2
+
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/src/etc/init.d/isc-dhcpv6-relay b/src/etc/init.d/isc-dhcpv6-relay
new file mode 100755
index 000000000..5a8ce620c
--- /dev/null
+++ b/src/etc/init.d/isc-dhcpv6-relay
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+#
+
+### BEGIN INIT INFO
+# Provides: isc-dhcpv6-relay
+# Required-Start: $remote_fs $network
+# Required-Stop: $remote_fs $network
+# Should-Start: $local_fs
+# Should-Stop: $local_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: IPv6 DHCP relay
+# Description: Dynamic Host Configuration Protocol Relay for IPv6
+### END INIT INFO
+
+# It is not safe to start if we don't have a default configuration...
+if [ ! -f /etc/default/isc-dhcpv6-relay ]; then
+ echo "/etc/default/isc-dhcpv6-relay does not exist! - Aborting..."
+ exit 1
+fi
+
+# Source init functions
+. /lib/lsb/init-functions
+
+# Read init script configuration (interfaces the daemon should listen on
+# and the DHCP server we should forward requests to.)
+[ -f /etc/default/isc-dhcpv6-relay ] && . /etc/default/isc-dhcpv6-relay
+
+DHCRELAYPID=/var/run/dhcv6relay.pid
+
+case "$1" in
+ start)
+ start-stop-daemon --start --quiet --pidfile $DHCRELAYPID \
+ --exec /usr/sbin/dhcrelay -- -q $OPTIONS -pf $DHCRELAYPID
+ ;;
+ stop)
+ start-stop-daemon --stop --quiet --pidfile $DHCRELAYPID
+ ;;
+ restart | force-reload)
+ $0 stop
+ sleep 2
+ $0 start
+ ;;
+ *)
+ echo "Usage: /etc/init.d/isc-dhcpv6-relay {start|stop|restart|force-reload}"
+ exit 1
+esac
+
+exit 0
diff --git a/src/etc/init.d/isc-dhcpv6-server b/src/etc/init.d/isc-dhcpv6-server
new file mode 100755
index 000000000..441827d5f
--- /dev/null
+++ b/src/etc/init.d/isc-dhcpv6-server
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+#
+
+### BEGIN INIT INFO
+# Provides: isc-dhcpv6-server
+# Required-Start: $remote_fs $network $syslog
+# Required-Stop: $remote_fs $network $syslog
+# Should-Start: $local_fs slapd $named
+# Should-Stop: $local_fs slapd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: IPv6 DHCP server
+# Description: Dynamic Host Configuration Protocol Server for IPv6
+### END INIT INFO
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+
+test -f /usr/sbin/dhcpd || exit 0
+
+DHCPD_DEFAULT="${DHCPD_DEFAULT:-/etc/default/isc-dhcpv6-server}"
+
+# It is not safe to start if we don't have a default configuration...
+if [ ! -f "$DHCPD_DEFAULT" ]; then
+ echo "$DHCPD_DEFAULT does not exist! - Aborting..."
+ exit 0
+fi
+
+. /lib/lsb/init-functions
+
+# Read init script configuration
+[ -f "$DHCPD_DEFAULT" ] && . "$DHCPD_DEFAULT"
+
+NAME=dhcpd
+DESC="ISC DHCP server"
+# fallback to default config file
+DHCPD_CONF=${DHCPD_CONF:-/etc/dhcp/dhcpd.conf}
+# try to read pid file name from config file, with fallback to /var/run/dhcpd.pid
+if [ -z "$DHCPD_PID" ]; then
+ DHCPD_PID=$(sed -n -e 's/^[ \t]*pid-file-name[ \t]*"(.*)"[ \t]*;.*$/\1/p' < "$DHCPD_CONF" 2>/dev/null | head -n 1)
+fi
+DHCPD_PID="${DHCPD_PID:-/var/run/dhcpd.pid}"
+
+test_config()
+{
+ if ! /usr/sbin/dhcpd -t $OPTIONS -q -cf "$DHCPD_CONF" > /dev/null 2>&1; then
+ echo "dhcpd self-test failed. Please fix $DHCPD_CONF."
+ echo "The error was: "
+ /usr/sbin/dhcpd -t $OPTIONS -cf "$DHCPD_CONF"
+ exit 1
+ fi
+ touch /var/lib/dhcp/dhcpd.leases
+}
+
+# single arg is -v for messages, -q for none
+check_status()
+{
+ if [ ! -r "$DHCPD_PID" ]; then
+ test "$1" != -v || echo "$NAME is not running."
+ return 3
+ fi
+ if read pid < "$DHCPD_PID" && ps -p "$pid" > /dev/null 2>&1; then
+ test "$1" != -v || echo "$NAME is running."
+ return 0
+ else
+ test "$1" != -v || echo "$NAME is not running but $DHCPD_PID exists."
+ return 1
+ fi
+}
+
+case "$1" in
+ start)
+ test_config
+ log_daemon_msg "Starting $DESC" "$NAME"
+ start-stop-daemon --start --quiet --pidfile "$DHCPD_PID" \
+ --exec /usr/sbin/dhcpd -- \
+ -q $OPTIONS -cf "$DHCPD_CONF" -pf "$DHCPD_PID" $INTERFACES
+ sleep 2
+
+ if check_status -q; then
+ log_end_msg 0
+ else
+ log_failure_msg "check syslog for diagnostics."
+ log_end_msg 1
+ exit 1
+ fi
+ ;;
+ stop)
+ log_daemon_msg "Stopping $DESC" "$NAME"
+ start-stop-daemon --stop --quiet --pidfile "$DHCPD_PID"
+ log_end_msg $?
+ rm -f "$DHCPD_PID"
+ ;;
+ restart | force-reload)
+ test_config
+ $0 stop
+ sleep 2
+ $0 start
+ if [ "$?" != "0" ]; then
+ exit 1
+ fi
+ ;;
+ status)
+ echo -n "Status of $DESC: "
+ check_status -v
+ exit "$?"
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|restart|force-reload|status}"
+ exit 1
+esac
+
+exit 0
diff --git a/src/migration-scripts/l2tp/0-to-1 b/src/migration-scripts/l2tp/0-to-1
new file mode 100755
index 000000000..65adbbe77
--- /dev/null
+++ b/src/migration-scripts/l2tp/0-to-1
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+# Delete "set service dhcp-relay relay-options port" option
+# Delete "set service dhcpv6-relay listen-port" option
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+cfg_base = ['vpn', 'l2tp', 'remote-access', 'authentication']
+if not config.exists(cfg_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Migrate "vpn l2tp authentication radius-source-address" to new
+ # "vpn l2tp authentication radius source-address"
+ if config.exists(cfg_base + ['radius-source-address']):
+ address = config.return_value(cfg_base + ['radius-source-address'])
+ # delete old configuration node
+ config.delete(cfg_base + ['radius-source-address'])
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'source-address'], value=address)
+
+ # Migrate "vpn l2tp authentication radius-server" tag node to new
+ # "vpn l2tp authentication radius server" tag node
+ for server in config.list_nodes(cfg_base + ['radius-server']):
+ base_server = cfg_base + ['radius-server', server]
+ key = config.return_value(base_server + ['key'])
+
+ # delete old configuration node
+ config.delete(base_server)
+ # write new configuration node
+ config.set(cfg_base + ['radius', 'server', server, 'key'], value=key)
+
+ # format as tag node
+ config.set_tag(cfg_base + ['radius', 'server'])
+
+ # delete top level tag node
+ if config.exists(cfg_base + ['radius-server']):
+ config.delete(cfg_base + ['radius-server'])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/cpu_summary.py b/src/op_mode/cpu_summary.py
index 7324c75ad..cfd321522 100755
--- a/src/op_mode/cpu_summary.py
+++ b/src/op_mode/cpu_summary.py
@@ -1,10 +1,22 @@
#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
-
from vyos.util import colon_separated_to_dict
-
FILE_NAME = '/proc/cpuinfo'
with open(FILE_NAME, 'r') as f:
diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py
index 7ac3dfe9f..0d457e247 100755
--- a/src/op_mode/dynamic_dns.py
+++ b/src/op_mode/dynamic_dns.py
@@ -1,4 +1,19 @@
#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
import os
import argparse
import jinja2
@@ -19,7 +34,6 @@ update-status: {{ entry.status }}
{% endfor -%}
"""
-
def show_status():
# Do nothing if service is not configured
c = Config()
diff --git a/src/op_mode/maya_date.py b/src/op_mode/maya_date.py
index 7d8aefc91..847b543e0 100755
--- a/src/op_mode/maya_date.py
+++ b/src/op_mode/maya_date.py
@@ -27,17 +27,16 @@ class MayaDate(object):
It represents the number of days passed
since some date in the past the Maya believed is the day
our world was created.
-
+
Tzolkin calendar is for religious purposes, it has
two independent cycles of 13 and 20 days, where 13 day
cycle days are numbered, and 20 day cycle days are named.
-
+
Haab calendar is for agriculture and daily life, it's a
365 day calendar with 18 months 20 days each, and 5
nameless days.
The smallest unit of the long count calendar is one day (kin).
-
"""
""" The long count calendar uses five different base 18 or base 20
@@ -131,7 +130,7 @@ class MayaDate(object):
""" Seconds in day, for conversion from timestamp """
seconds_in_day = 60 * 60 * 24
-
+
def __init__(self, timestamp):
if timestamp is None:
self.days = self.start_days
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index 5a0345fbf..2f6112fb7 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -1,11 +1,27 @@
#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
import os
import sys
import argparse
import subprocess
+import re
+
from datetime import datetime, timedelta, time as type_time, date as type_date
from subprocess import check_output, CalledProcessError, STDOUT
-import re
def yn(msg, default=False):
default_msg = "[Y/n]" if default else "[y/N]"
@@ -70,10 +86,10 @@ def execute_shutdown(time, reboot = True, ask=True):
action = "-r" if reboot else "-P"
if len(time) == 0:
- ### T870 legacy reboot job support
+ ### T870 legacy reboot job support
chk_vyatta_based_reboots()
###
-
+
cmd = check_output(["/sbin/shutdown",action,"now"],stderr=STDOUT)
print(cmd.decode().split(",",1)[0])
return
diff --git a/src/op_mode/show-igmpproxy.py b/src/op_mode/show-igmpproxy.py
new file mode 100755
index 000000000..a021fcdde
--- /dev/null
+++ b/src/op_mode/show-igmpproxy.py
@@ -0,0 +1,241 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# File: show-igmpproxy
+# Purpose:
+# Display istatistics from IPv4 IGMP proxy.
+# Used by the "run show ip multicast" command tree.
+
+import sys
+import jinja2
+import argparse
+import ipaddress
+import socket
+
+import vyos.config
+
+# Output Template for "show ip multicast interface" command
+#
+# Example:
+# Interface BytesIn PktsIn BytesOut PktsOut Local
+# eth0 0.0b 0 0.0b 0 xxx.xxx.xxx.65
+# eth1 0.0b 0 0.0b 0 xxx.xxx.xx.201
+# eth0.3 0.0b 0 0.0b 0 xxx.xxx.x.7
+# tun1 0.0b 0 0.0b 0 xxx.xxx.xxx.2
+vif_out_tmpl = """
+{%- for r in data %}
+{{ "%-10s"|format(r.interface) }} {{ "%-12s"|format(r.bytes_in) }} {{ "%-12s"|format(r.pkts_in) }} {{ "%-12s"|format(r.bytes_out) }} {{ "%-12s"|format(r.pkts_out) }} {{ "%-15s"|format(r.loc) }}
+{%- endfor %}
+"""
+
+# Output Template for "show ip multicast mfc" command
+#
+# Example:
+# Group Origin In Out Pkts Bytes Wrong
+# xxx.xxx.xxx.250 xxx.xx.xxx.75 --
+# xxx.xxx.xx.124 xx.xxx.xxx.26 --
+mfc_out_tmpl = """
+{%- for r in data %}
+{{ "%-15s"|format(r.group) }} {{ "%-15s"|format(r.origin) }} {{ "%-12s"|format(r.pkts) }} {{ "%-12s"|format(r.bytes) }} {{ "%-12s"|format(r.wrong) }} {{ "%-10s"|format(r.iif) }} {{ "%-20s"|format(r.oifs|join(', ')) }}
+{%- endfor %}
+"""
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--interface", action="store_true", help="Interface Statistics")
+parser.add_argument("--mfc", action="store_true", help="Multicast Forwarding Cache")
+
+def byte_string(size):
+ # convert size to integer
+ size = int(size)
+
+ # One Terrabyte
+ s_TB = 1024 * 1024 * 1024 * 1024
+ # One Gigabyte
+ s_GB = 1024 * 1024 * 1024
+ # One Megabyte
+ s_MB = 1024 * 1024
+ # One Kilobyte
+ s_KB = 1024
+ # One Byte
+ s_B = 1
+
+ if size > s_TB:
+ return str(round((size/s_TB), 2)) + 'TB'
+ elif size > s_GB:
+ return str(round((size/s_GB), 2)) + 'GB'
+ elif size > s_MB:
+ return str(round((size/s_MB), 2)) + 'MB'
+ elif size > s_KB:
+ return str(round((size/s_KB), 2)) + 'KB'
+ else:
+ return str(round((size/s_B), 2)) + 'b'
+
+ return None
+
+def kernel2ip(addr):
+ """
+ Convert any given addr from Linux Kernel to a proper, IPv4 address
+ using the correct host byte order.
+ """
+
+ # Convert from hex 'FE000A0A' to decimal '4261415434'
+ addr = int(addr, 16)
+ # Kernel ABI _always_ uses network byteorder
+ addr = socket.ntohl(addr)
+
+ return ipaddress.IPv4Address( addr )
+
+def do_mr_vif():
+ """
+ Read contents of file /proc/net/ip_mr_vif and print a more human
+ friendly version to the command line. IPv4 addresses present as
+ 32bit integers in hex format are converted to IPv4 notation, too.
+ """
+
+ with open('/proc/net/ip_mr_vif', 'r') as f:
+ lines = len(f.readlines())
+ if lines < 2:
+ return None
+
+ result = {
+ 'data': []
+ }
+
+ # Build up table format string
+ table_format = {
+ 'interface': 'Interface',
+ 'pkts_in' : 'PktsIn',
+ 'pkts_out' : 'PktsOut',
+ 'bytes_in' : 'BytesIn',
+ 'bytes_out': 'BytesOut',
+ 'loc' : 'Local'
+ }
+ result['data'].append(table_format)
+
+ # read and parse information from /proc filesystema
+ with open('/proc/net/ip_mr_vif', 'r') as f:
+ header_line = next(f)
+ for line in f:
+ data = {
+ 'interface': line.split()[1],
+ 'pkts_in' : line.split()[3],
+ 'pkts_out' : line.split()[5],
+
+ # convert raw byte number to something more human readable
+ # Note: could be replaced by Python3 hurry.filesize module
+ 'bytes_in' : byte_string( line.split()[2] ),
+ 'bytes_out': byte_string( line.split()[4] ),
+
+ # convert IP address from hex 'FE000A0A' to decimal '4261415434'
+ 'loc' : kernel2ip( line.split()[7] ),
+ }
+ result['data'].append(data)
+
+ return result
+
+def do_mr_mfc():
+ """
+ Read contents of file /proc/net/ip_mr_cache and print a more human
+ friendly version to the command line. IPv4 addresses present as
+ 32bit integers in hex format are converted to IPv4 notation, too.
+ """
+
+ with open('/proc/net/ip_mr_cache', 'r') as f:
+ lines = len(f.readlines())
+ if lines < 2:
+ return None
+
+ # We need this to convert from interface index to a real interface name
+ # Thus we also skip the format identifier on list index 0
+ vif = do_mr_vif()['data'][1:]
+
+ result = {
+ 'data': []
+ }
+
+ # Build up table format string
+ table_format = {
+ 'group' : 'Group',
+ 'origin': 'Origin',
+ 'iif' : 'In',
+ 'oifs' : ['Out'],
+ 'pkts' : 'Pkts',
+ 'bytes' : 'Bytes',
+ 'wrong' : 'Wrong'
+ }
+ result['data'].append(table_format)
+
+ # read and parse information from /proc filesystem
+ with open('/proc/net/ip_mr_cache', 'r') as f:
+ header_line = next(f)
+ for line in f:
+ data = {
+ # convert IP address from hex 'FE000A0A' to decimal '4261415434'
+ 'group' : kernel2ip( line.split()[0] ),
+ 'origin': kernel2ip( line.split()[1] ),
+
+ 'iif' : '--',
+ 'pkts' : '',
+ 'bytes' : '',
+ 'wrong' : '',
+ 'oifs' : []
+ }
+
+ iif = int( line.split()[2] )
+ if not ((iif == -1) or (iif == 65535)):
+ data['pkts'] = line.split()[3]
+ data['bytes'] = byte_string( line.split()[4] )
+ data['wrong'] = line.split()[5]
+
+ # convert index to real interface name
+ data['iif'] = vif[iif]['interface']
+
+ # convert each output interface index to a real interface name
+ for oif in line.split()[6:]:
+ idx = int( oif.split(':')[0] )
+ data['oifs'].append( vif[idx]['interface'] )
+
+ result['data'].append(data)
+
+ return result
+
+if __name__ == '__main__':
+ args = parser.parse_args()
+
+ # Do nothing if service is not configured
+ c = vyos.config.Config()
+ if not c.exists_effective('protocols igmp-proxy'):
+ print("IGMP proxy is not configured")
+ sys.exit(0)
+
+ if args.interface:
+ data = do_mr_vif()
+ if data:
+ tmpl = jinja2.Template(vif_out_tmpl)
+ print(tmpl.render(data))
+
+ sys.exit(0)
+ elif args.mfc:
+ data = do_mr_mfc()
+ if data:
+ tmpl = jinja2.Template(mfc_out_tmpl)
+ print(tmpl.render(data))
+
+ sys.exit(0)
+ else:
+ parser.print_help()
+ sys.exit(1)
+