From 6f084a4253b8cfd9cbc9301cf2b349aaee92f2fb Mon Sep 17 00:00:00 2001 From: hagbard Date: Mon, 5 Nov 2018 16:31:10 -0800 Subject: T965: Fix Wireguard configuration inconsistencies - pubkey updates now work - removing peers or interfaces work, was related tothe fact that tag nodes are called multiple times --- src/conf_mode/wireguard.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) 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() -- cgit v1.2.3 From f9e12731ec3a24e1dafeac733eb3594d0f049916 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 4 Nov 2018 11:47:51 +0100 Subject: T959: XML/Python rewrite of "protocol igmp-proxy" and op-mode commands Examples: ========= CFG commands: vyos@vyos# set protocols igmp-proxy disable-quickleave vyos@vyos# set protocols igmp-proxy interface eth0 alt-subnet '172.16.35.0/24' vyos@vyos# set protocols igmp-proxy interface eth0 alt-subnet '172.31.0.0/24' vyos@vyos# set protocols igmp-proxy interface eth0 role 'upstream' vyos@vyos# set protocols igmp-proxy interface eth1 role 'downstream' vyos@vyos# show protocols igmp-proxy { disable-quickleave interface eth0 { alt-subnet 172.16.35.0/24 alt-subnet 172.31.0.0/24 role upstream } interface eth1 { role downstream } } OP mode commands: ----------------- vyos@vyos:~$ show ip multicast interface 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 vyos@vyos:~$ show ip multicast mfc Group Origin Pkts Bytes Wrong In Out xxx.x.xx.1 xxx.xx.0.1 10 9.81KB 0 eth0 eth1 xxx.x.xx.2 xxx.xx.0.1 -- (cherry picked from commit 698c5a40b2ece2f3eb41ad932660f7ceb1f80092) --- Makefile | 1 + debian/control | 1 + debian/rules | 4 + interface-definitions/igmp-proxy.xml | 100 +++++++++++++ op-mode-definitions/show-ip-multicast.xml | 30 ++++ src/conf_mode/igmp_proxy.py | 179 ++++++++++++++++++++++ src/etc/init.d/igmpproxy | 166 ++++++++++++++++++++ src/op_mode/show-igmpproxy.py | 241 ++++++++++++++++++++++++++++++ 8 files changed, 722 insertions(+) create mode 100644 interface-definitions/igmp-proxy.xml create mode 100644 op-mode-definitions/show-ip-multicast.xml create mode 100755 src/conf_mode/igmp_proxy.py create mode 100755 src/etc/init.d/igmpproxy create mode 100755 src/op_mode/show-igmpproxy.py diff --git a/Makefile b/Makefile index b7cf53896..a9926137c 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ op_mode_definitions: rm -f $(OP_TMPL_DIR)/set/node.def rm -f $(OP_TMPL_DIR)/show/node.def rm -f $(OP_TMPL_DIR)/show/interfaces/node.def + rm -f $(OP_TMPL_DIR)/show/ip/node.def rm -f $(OP_TMPL_DIR)/reset/node.def rm -f $(OP_TMPL_DIR)/restart/node.def rm -f $(OP_TMPL_DIR)/monitor/node.def diff --git a/debian/control b/debian/control index 05b4f5320..a68598fd5 100644 --- a/debian/control +++ b/debian/control @@ -46,6 +46,7 @@ Depends: python3, keepalived (>=2.0.5), wireguard, tftpd-hpa, + igmpproxy, ${shlibs:Depends}, ${misc:Depends} Description: VyOS configuration scripts and data diff --git a/debian/rules b/debian/rules index 15dfec551..663aff4d9 100755 --- a/debian/rules +++ b/debian/rules @@ -64,3 +64,7 @@ override_dh_auto_install: # Install data files mkdir -p $(DIR)/$(VYOS_DATA_DIR) cp -r data/* $(DIR)/$(VYOS_DATA_DIR) + + # Install etc configuration files + mkdir -p $(DIR)/etc + cp -r src/etc/* $(DIR)/etc diff --git a/interface-definitions/igmp-proxy.xml b/interface-definitions/igmp-proxy.xml new file mode 100644 index 000000000..ab56019b4 --- /dev/null +++ b/interface-definitions/igmp-proxy.xml @@ -0,0 +1,100 @@ + + + + + + + + Internet Group Management Protocol (IGMP) proxy parameters + 740 + + + + + Option to disable IGMP proxy + + + + + + Option to disable "quickleave" + + + + + + Interface for IGMP proxy [REQUIRED] + + + + + + + + Allowed unicast sources for multicast traffic to be proxy'ed + + ipv4net + IPv4 network + + + + + + + + + + Role of this IGMP interface + + upstream downstream disabled + + + upstream + Upstream interface (only 1 allowed) + + + downstream + Downstream interface(s) (default) + + + disabled + Disabled interface + + + (upstream|downstream|disabled) + + + + + + TTL threshold + + 1-255 + TTL threshold for the interfaces (default: 1) + + + + + threshold must be between 1 and 255 + + + + + Group to whitelist + + ipv4net + IPv4 network + + + + + + + + + + + + + + diff --git a/op-mode-definitions/show-ip-multicast.xml b/op-mode-definitions/show-ip-multicast.xml new file mode 100644 index 000000000..07102bfa6 --- /dev/null +++ b/op-mode-definitions/show-ip-multicast.xml @@ -0,0 +1,30 @@ + + + + + + + + + Show IP multicast + + + + + Show multicast interfaces + + if ps -C igmpproxy &>/dev/null; then ${vyos_op_scripts_dir}/show-igmpproxy.py --interface; else echo IGMP proxy not configured; fi + + + + Show multicast fowarding cache + + if ps -C igmpproxy &>/dev/null; then ${vyos_op_scripts_dir}/show-igmpproxy.py --mfc; else echo IGMP proxy not configured; fi + + + + + + + + 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 . +# +# + +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/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 + +# 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/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 . + +# 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) + -- cgit v1.2.3 From a76b2a889a7aa240c5f1c624bcf0791e33915d88 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 8 Nov 2018 16:01:54 +0100 Subject: T974: bugfix dns forwarder not listening on IPv6 addresses By default PowerDNS only allows 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 for incoming DNS queries - we changed this to 0.0.0.0/0 to be reachable by everyone. This only covered the IPv4 address space and any IPv6 related query was not handled by the server. (cherry picked from commit 1682d7167461ab9ef72471b31b199094b335276d) --- src/conf_mode/dns_forwarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 43be9d526..17788f12a 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -36,7 +36,7 @@ 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 -- cgit v1.2.3 From 5860c93c6145ae5ecafcf42e72ea536c33be0051 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 8 Nov 2018 16:04:05 +0100 Subject: cleanup: move files from vyos-build repo to vyos-1x where they are required (cherry picked from commit 15065070b7c22709e259a6ae0cc4a27fd7b59d6b) --- src/etc/init.d/isc-dhcpv6-relay | 50 +++++++++++++++++ src/etc/init.d/isc-dhcpv6-server | 113 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100755 src/etc/init.d/isc-dhcpv6-relay create mode 100755 src/etc/init.d/isc-dhcpv6-server 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 -- cgit v1.2.3 From 46598c867d0cc7d9642056d8743e03e394d67767 Mon Sep 17 00:00:00 2001 From: Geoff Adams Date: Thu, 8 Nov 2018 11:34:47 -0800 Subject: T978: Support PowerDNS Recursor outbound queries over IPv6. This requires adding a query-local-address6 setting to enable outbound IPv6 queries in general, and also formatting upstream nameserver IPv6 addresses in such a way that Recursor can parse them. (cherry picked from commit 5d2e36da657fd2e15f9dc8d5588b06478bd3d55c) --- src/conf_mode/dns_forwarding.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 17788f12a..c21a91a30 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -39,6 +39,8 @@ threads=1 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: -- cgit v1.2.3 From eeb4552c3407f7e918129b57e611dd45487d3e6e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 11 Nov 2018 17:32:07 +0100 Subject: T987: Unclutter L2TP/IPSec RADIUS configuration nodes In other words, remove top level tag nodes from radius-server and introduce a regular "radius" node, thus we can add additional features, too. A migration script is provided in vyos-1x which takes care of this config migration. Change VyOS CLI from: vyos@vyos# show vpn l2tp remote-access { authentication { mode radius radius-server 172.16.100.10 { key barbarbar } radius-server 172.16.100.20 { key foofoofoo } radius-source-address 172.16.254.100 } To: vyos@vyos# show vpn l2tp remote-access { authentication { mode radius radius { server 172.16.100.10 { key barbarbar } server 172.16.100.20 { key foofoofoo } source-address 172.16.254.100 } } (cherry picked from commit 979ad1a92af9ee2150ecfe5309a9d1b43fdad59d) --- src/migration-scripts/l2tp/0-to-1 | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100755 src/migration-scripts/l2tp/0-to-1 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) -- cgit v1.2.3 From beb98d35e3592b362845606fbe8a6cec0dbb1886 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 11 Nov 2018 20:23:04 +0100 Subject: T998: "service dns dynamic" does now honor the "use-web" statement This bug was present since the old Vyatta days as the use-web statement was only put into action when also "use-web skip" was defined. The service https://ipinfo.io/ip does not place any crap in front of the IP address so the skip statement was not used and made no sense. (cherry picked from commit 718d9a123c2ba72b87d7f6e48a5e6d83fa86d494) --- src/conf_mode/dynamic_dns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 -%} -- cgit v1.2.3