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 }'