AppendixΒΆ
This section contains the listing of the files implementing the Telnet service.
yang/vrouter-telnet-server.yang
module vrouter-telnet-server { namespace "urn:mycompany:vrouter/telnet-server"; prefix vrouter-telnet-server; import vrouter { prefix vrouter; } import vrouter-api { prefix vr-api; } import vrouter-inet-types { prefix vr-inet; } import vrouter-extensions { prefix vr-ext; } import vrouter-commands { prefix vr-cmd; } import vrouter-types { prefix vr-types; } organization "My Company"; contact "My Company support - <support@mycompany.com>"; description "Telnet server service."; revision 2018-10-03 { description "Initial version."; reference ""; } identity telnet-server { base vr-types:SERVICE_LOG_ID; description "Telnet server service."; } grouping system-telnet-server-config { description "Configuration data for system telnet configuration."; leaf enabled { type boolean; default "true"; description "Enable or disable the telnet server."; } leaf address { type vr-inet:ip-address; description "The IP address of the interface to listen on. The TELNET server will listen on all interfaces if no value is specified."; } leaf port { type vr-inet:port-number; default "23"; description "The local port number on this interface the telnet server listens on."; } } rpc show-telnet-sessions-text2 { description "Show the active telnet sessions in text format."; input { leaf vrf { type string; default "main"; description "VRF to look into."; vr-ext:nc-cli-completion-xpath "/vrouter:state/vrouter:vrf/vrouter:name"; } } output { uses vr-cmd:long-cmd-status; uses vr-cmd:long-cmd-output; } vr-ext:nc-cli-show "telnet-sessions-text2"; vr-api:internal; } rpc show-telnet-sessions { description "Show the active telnet sessions."; input { leaf vrf { type string; default "main"; description "VRF to look into."; vr-ext:nc-cli-completion-xpath "/vrouter:state/vrouter:vrf/vrouter:name"; } } output { list session { key "local-address local-port remote-address remote-port"; description "List of telnet sessions."; leaf local-address { type vr-inet:ip-address; description "The local IP address of the telnet session."; } leaf local-port { type vr-inet:port-number; description "The local port number of the telnet session."; } leaf remote-address { type vr-inet:ip-address; description "The remote IP address of the telnet session."; } leaf remote-port { type vr-inet:port-number; description "The remote port number of the telnet session."; } } } vr-ext:nc-cli-cmd "show-telnet-sessions"; vr-ext:nc-cli-text-output; } augment "/vrouter:config/vrouter:vrf" { description "Telnet server configuration."; container telnet-server { presence "Makes telnet available"; description "Telnet server configuration."; uses system-telnet-server-config; } } augment "/vrouter:state/vrouter:vrf" { description "Telnet server state."; container telnet-server { description "Telnet server state."; uses system-telnet-server-config { refine "enabled" { vr-ext:pushed; } } } } }
yams/service/telnet_server.py
""" The telnet server service manages telnetd using the inetd service. """ import logging import os import re from yams import util from yams.service import PushStateMixin from yams.service import Service from yams.service import ServiceLoopMixin from yams.service import SummaryGroup from yams.service import completion from yams.service import config_route from yams.service import push_state_route from yams.service import rpc from yams.service import rpc_text from yams.service import state_route from yams.service import summary from yams.system_files import SystemdDropinTemplate from yams.system_files import SystemdUnitClone from yams.util import process from yams.util import templates from yams.util.netns import NetnsCache from yams.util.summary import summary_netns_service_string from yams.util.systemd import Systemd from yams.util.systemd import SystemdUnitCache LOG = logging.getLogger(__name__) #------------------------------------------------------------------------------ class TelnetServerService(ServiceLoopMixin, PushStateMixin, Service): SERVICE = 'inetd.service' NETNS_SERVICE = 'inetd@%s.service' CONF_FILE = '/etc/inetd.conf' NETNS_CONF_FILE = '/etc/netns/%s/inetd.conf' @classmethod def required_modules(cls): return {'vrouter-telnet-server'} @classmethod def system_files(cls, distro): netns_service = cls.NETNS_SERVICE % '' yield SystemdUnitClone( name=netns_service, original=cls.SERVICE, ) yield SystemdDropinTemplate( unit=netns_service, template='inetd-netns.conf', ) def _get_service(self, netns): if netns == 'main': return self.SERVICE return self.NETNS_SERVICE % netns def _get_conf_file(self, netns): if netns == 'main': return self.CONF_FILE return self.NETNS_CONF_FILE % netns def systemd_units(self, netns): yield self._get_service(netns) @summary('telnet-server', group=SummaryGroup.MANAGEMENT) async def get_summary_string(self): """ Display the summary for telnet server. """ return summary_netns_service_string(self._get_service) @config_route("/config/vrf[name=<netns>]/telnet-server") async def apply(self, config, *, netns): """ If telnet_server is enabled, generate the inetd configuration file and restart the service, else stop the service. Configuration example: { 'address': '0.0.0.0', 'port': 23, 'enabled': True, } """ service = self._get_service(netns) conf_file = self._get_conf_file(netns) directory = os.path.dirname(conf_file) os.makedirs(directory, exist_ok=True) if config.get('enabled'): buf = templates.render( 'inetd.conf', **config, proto='tcp' if '.' in config.get('address', '0.0.0.0') else 'tcp6') with open(conf_file, 'w') as f: f.write(buf) await NetnsCache().wait(netns) LOG.info('starting %s', service) await Systemd.restart_unit(service) else: LOG.info('stopping %s', service) await Systemd.stop_unit(service) @state_route("/state/vrf[name=<netns>]/telnet-server") async def get_state(self, *, netns): """ Generates the telnet server state. State example: 'telnet-server': { 'address': '0.0.0.0', 'port': 23, 'enabled': True, } """ enabled = SystemdUnitCache.status(self._get_service(netns)) state = {'enabled': enabled} if enabled: data = await process.run_command(['ss', '-pln'], netns=netns) pattern = r'''^(?P<proto>\S+)\s+ (?P<state>\S+)\s+ (?P<recv_queue>\d+)\s+ (?P<send_queue>\d+)\s+ (?P<local_addr_port>([0-9a-fA-F:\.\]\[\*]*))\s+ (?P<peer_addr_port>([0-9a-fA-F:\.\]\[\*]*))\s+ users:.*\"inetd\".*$''' _re = re.compile(pattern, re.MULTILINE | re.VERBOSE) match = _re.search(data) if match: m = match.groupdict() idx = m['local_addr_port'].rfind(':') if idx != -1: state['port'] = m['local_addr_port'][(idx + 1):] state['address'] = m['local_addr_port'][:idx].replace('[', '').replace(']', '').replace('*', '::') else: state = {'enabled': False} return state @push_state_route("/state/vrf[name=<netns>]/telnet-server") async def push_state(self, *, netns): return {'enabled': SystemdUnitCache.status(self._get_service(netns))} @completion("/state/vrf[name=<netns>]/telnet-server") async def get_completion(self, *, netns): return await self.get_state(netns=netns) @rpc('/vrouter-telnet-server:show-telnet-sessions-text2') async def show_sessions_text2(self, params): netns = params.get('vrf', 'main') args = ['bash', templates.filepath('show-telnet-sessions.sh')] return await util.bgcmd.start_process(args, netns=netns) @rpc('/vrouter-telnet-server:show-telnet-sessions') async def show_sessions(self, params): netns = params.get('vrf', 'main') args = ['bash', templates.filepath('show-telnet-sessions.sh')] buf = await process.run_command(args, netns=netns) pattern = r'''^ (?P<local_addr>(\*|::|\[::\]|[0-9\.]*)) (?P<local_iface>%[^:]+)? :(?P<local_port>\d+) \s+->\s+ (?P<remote_addr>(\*|::|\[::\]|[0-9\.]*)) (?P<remote_iface>%[^:]+)? :(?P<remote_port>\d+) $''' _re = re.compile(pattern, re.MULTILINE | re.VERBOSE) ret = [] for line in buf.splitlines(): match = _re.search(line) if not match: continue d = match.groupdict() ret.append({ 'local-address': d['local_addr'], 'local-port': d['local_port'], 'remote-address': d['remote_addr'], 'remote-port': d['remote_port'], }) return {'session': ret} @rpc_text('/vrouter-telnet-server:show-telnet-sessions') async def show_sessions_text(self, params): sessions = params['output']['session'] if not sessions: return 'No available telnet session\n' out = [] for x, session in enumerate(sessions): listen_local = ':'.join(( session['local-address'], str(session['local-port']))) listen_remote = ':'.join(( session['remote-address'], str(session['remote-port']))) out.append(f'Session {x}: ' f'listening on -> local: {listen_local} | ' f'remote: {listen_remote}') return '{}\n'.format('\n'.join(out))
yams/service/templates/inetd-netns.conf
[Unit] Description=Internet superserver netns %i ConditionPathExists=/etc/netns/%i/inetd.conf [Service] ExecStart= ExecStart=/sbin/ip netns exec %i /usr/sbin/inetd [Install] Alias=
yams/service/templates/inetd.conf
# /etc/inetd.conf: see inetd(8) for further informations. # Generated by yams, do not edit {{ address|d("0.0.0.0") }}:{{ port }} stream {{ proto }} nowait telnetd /usr/sbin/tcpd /usr/sbin/in.telnetd
yams/service/templates/show-telnet-sessions.sh
#!/bin/sh ss -pnt | grep in.telnetd | awk '{ print $5 " -> " $4 }'