mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-06 07:11:13 +02:00
186 files changed
+1173
-1206
No files matched your search
@@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
IOU server module.
|
||||
"""
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from ..base_manager import BaseManager
|
||||
from .iou_error import IOUError
|
||||
from .iou_vm import IOUVM
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOU(BaseManager):
|
||||
|
||||
_VM_CLASS = IOUVM
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super().__init__()
|
||||
self._free_application_ids = list(range(1, 512))
|
||||
self._used_application_ids = {}
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_vm(self, *args, **kwargs):
|
||||
"""
|
||||
Creates a new IOU VM.
|
||||
|
||||
:returns: IOUVM instance
|
||||
"""
|
||||
|
||||
vm = yield from super().create_vm(*args, **kwargs)
|
||||
try:
|
||||
self._used_application_ids[vm.id] = self._free_application_ids.pop(0)
|
||||
except IndexError:
|
||||
raise IOUError("Cannot create a new IOU VM (limit of 512 VMs reached on this host)")
|
||||
return vm
|
||||
|
||||
@asyncio.coroutine
|
||||
def close_vm(self, vm_id, *args, **kwargs):
|
||||
"""
|
||||
Closes an IOU VM.
|
||||
|
||||
:returns: IOUVM instance
|
||||
"""
|
||||
|
||||
vm = self.get_vm(vm_id)
|
||||
if vm_id in self._used_application_ids:
|
||||
i = self._used_application_ids[vm_id]
|
||||
self._free_application_ids.insert(0, i)
|
||||
del self._used_application_ids[vm_id]
|
||||
yield from super().close_vm(vm_id, *args, **kwargs)
|
||||
return vm
|
||||
|
||||
@asyncio.coroutine
|
||||
def project_committed(self, project):
|
||||
"""
|
||||
Called when a project has been committed.
|
||||
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
# save the configs when the project is committed
|
||||
for vm in self._vms.copy().values():
|
||||
if vm.project.id == project.id:
|
||||
try:
|
||||
vm.save_configs()
|
||||
except IOUError as e:
|
||||
log.warning(e)
|
||||
continue
|
||||
|
||||
def get_application_id(self, vm_id):
|
||||
"""
|
||||
Get an unique application identifier for IOU.
|
||||
|
||||
:param vm_id: IOU VM identifier
|
||||
|
||||
:returns: IOU application identifier
|
||||
"""
|
||||
|
||||
return self._used_application_ids.get(vm_id, 1)
|
||||
|
||||
@staticmethod
|
||||
def get_legacy_vm_workdir(legacy_vm_id, name):
|
||||
"""
|
||||
Returns the name of the legacy working directory (pre 1.3) name for a VM.
|
||||
|
||||
:param legacy_vm_id: legacy VM identifier (integer)
|
||||
:param name: VM name (not used)
|
||||
|
||||
:returns: working directory name
|
||||
"""
|
||||
|
||||
return os.path.join("iou", "device-{}".format(legacy_vm_id))
|
||||
|
||||
def get_images_directory(self):
|
||||
"""
|
||||
Return the full path of the images directory on disk
|
||||
"""
|
||||
return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "IOU")
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
Custom exceptions for the IOU module.
|
||||
"""
|
||||
|
||||
from ..vm_error import VMError
|
||||
|
||||
|
||||
class IOUError(VMError):
|
||||
pass
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,674 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013, 2014 James E. Carpenter
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# 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 socket
|
||||
import sys
|
||||
import os
|
||||
import select
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
import tty
|
||||
except ImportError:
|
||||
# On windows it's not available but this module can be included by the test suite
|
||||
pass
|
||||
import struct
|
||||
import time
|
||||
import argparse
|
||||
import traceback
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Escape characters
|
||||
ESC_CHAR = '^^' # can be overriden from command line
|
||||
ESC_QUIT = 'q'
|
||||
|
||||
# IOU seems to only send *1* byte at a time. If
|
||||
# they ever fix that we'll be ready for it.
|
||||
BUFFER_SIZE = 1024
|
||||
|
||||
# How long to wait before retrying a connection (seconds)
|
||||
RETRY_DELAY = 3
|
||||
|
||||
# How often to test an idle connection (seconds)
|
||||
POLL_TIMEOUT = 3
|
||||
|
||||
|
||||
EXIT_SUCCESS = 0
|
||||
EXIT_FAILURE = 1
|
||||
EXIT_ABORT = 2
|
||||
|
||||
# Mostly from:
|
||||
# https://code.google.com/p/miniboa/source/browse/trunk/miniboa/telnet.py
|
||||
# --[ Telnet Commands ]---------------------------------------------------------
|
||||
SE = 240 # End of sub-negotiation parameters
|
||||
NOP = 241 # No operation
|
||||
DATMK = 242 # Data stream portion of a sync.
|
||||
BREAK = 243 # NVT Character BRK
|
||||
IP = 244 # Interrupt Process
|
||||
AO = 245 # Abort Output
|
||||
AYT = 246 # Are you there
|
||||
EC = 247 # Erase Character
|
||||
EL = 248 # Erase Line
|
||||
GA = 249 # The Go Ahead Signal
|
||||
SB = 250 # Sub-option to follow
|
||||
WILL = 251 # Will; request or confirm option begin
|
||||
WONT = 252 # Wont; deny option request
|
||||
DO = 253 # Do = Request or confirm remote option
|
||||
DONT = 254 # Don't = Demand or confirm option halt
|
||||
IAC = 255 # Interpret as Command
|
||||
SEND = 1 # Sub-process negotiation SEND command
|
||||
IS = 0 # Sub-process negotiation IS command
|
||||
# --[ Telnet Options ]----------------------------------------------------------
|
||||
BINARY = 0 # Transmit Binary
|
||||
ECHO = 1 # Echo characters back to sender
|
||||
RECON = 2 # Reconnection
|
||||
SGA = 3 # Suppress Go-Ahead
|
||||
TMARK = 6 # Timing Mark
|
||||
TTYPE = 24 # Terminal Type
|
||||
NAWS = 31 # Negotiate About Window Size
|
||||
LINEMO = 34 # Line Mode
|
||||
|
||||
|
||||
class FileLock:
|
||||
|
||||
# struct flock { /* from fcntl(2) */
|
||||
# ...
|
||||
# short l_type; /* Type of lock: F_RDLCK,
|
||||
# F_WRLCK, F_UNLCK */
|
||||
# short l_whence; /* How to interpret l_start:
|
||||
# SEEK_SET, SEEK_CUR, SEEK_END */
|
||||
# off_t l_start; /* Starting offset for lock */
|
||||
# off_t l_len; /* Number of bytes to lock */
|
||||
# pid_t l_pid; /* PID of process blocking our lock
|
||||
# (F_GETLK only) */
|
||||
# ...
|
||||
# };
|
||||
_flock = struct.Struct('hhqql')
|
||||
|
||||
def __init__(self, fname=None):
|
||||
self.fd = None
|
||||
self.fname = fname
|
||||
|
||||
def get_lock(self):
|
||||
flk = self._flock.pack(fcntl.F_WRLCK, os.SEEK_SET,
|
||||
0, 0, os.getpid())
|
||||
flk = self._flock.unpack(
|
||||
fcntl.fcntl(self.fd, fcntl.F_GETLK, flk))
|
||||
|
||||
# If it's not locked (or is locked by us) then return None,
|
||||
# otherwise return the PID of the owner.
|
||||
if flk[0] == fcntl.F_UNLCK:
|
||||
return None
|
||||
return flk[4]
|
||||
|
||||
def lock(self):
|
||||
try:
|
||||
self.fd = open('{}.lck'.format(self.fname), 'a')
|
||||
except Exception as e:
|
||||
raise LockError("Couldn't get lock on {}: {}"
|
||||
.format(self.fname, e))
|
||||
|
||||
flk = self._flock.pack(fcntl.F_WRLCK, os.SEEK_SET, 0, 0, 0)
|
||||
try:
|
||||
fcntl.fcntl(self.fd, fcntl.F_SETLK, flk)
|
||||
except BlockingIOError:
|
||||
raise LockError("Already connected. PID {} has lock on {}"
|
||||
.format(self.get_lock(), self.fname))
|
||||
|
||||
# If we got here then we must have the lock. Store the PID.
|
||||
self.fd.truncate(0)
|
||||
self.fd.write('{}\n'.format(os.getpid()))
|
||||
self.fd.flush()
|
||||
|
||||
def unlock(self):
|
||||
if self.fd:
|
||||
# Deleting first prevents a race condition
|
||||
try:
|
||||
os.unlink(self.fd.name)
|
||||
except FileNotFoundError as e:
|
||||
log.debug("{}".format(e))
|
||||
self.fd.close()
|
||||
|
||||
def __enter__(self):
|
||||
self.lock()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.unlock()
|
||||
return False
|
||||
|
||||
|
||||
class Console:
|
||||
|
||||
def fileno(self):
|
||||
raise NotImplementedError("Only routers have fileno()")
|
||||
|
||||
|
||||
class TTY(Console):
|
||||
|
||||
def read(self, fileno, bufsize):
|
||||
return self.fd.read(bufsize)
|
||||
|
||||
def write(self, buf):
|
||||
return self.fd.write(buf)
|
||||
|
||||
def register(self, epoll):
|
||||
self.epoll = epoll
|
||||
epoll.register(self.fd, select.EPOLLIN | select.EPOLLET)
|
||||
|
||||
def unregister(self, epoll):
|
||||
epoll.unregister(self.fd)
|
||||
|
||||
def __enter__(self):
|
||||
try:
|
||||
self.fd = open('/dev/tty', 'r+b', buffering=0)
|
||||
except OSError as e:
|
||||
raise TTYError("Couldn't open controlling TTY: {}".format(e))
|
||||
|
||||
# Save original flags
|
||||
self.termios = termios.tcgetattr(self.fd)
|
||||
self.fcntl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
|
||||
|
||||
# Update flags
|
||||
tty.setraw(self.fd, termios.TCSANOW)
|
||||
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.fcntl | os.O_NONBLOCK)
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
|
||||
# Restore flags to original settings
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, self.termios)
|
||||
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.fcntl)
|
||||
|
||||
self.fd.close()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class TelnetServer(Console):
|
||||
|
||||
def __init__(self, addr, port, stop_event):
|
||||
self.addr = addr
|
||||
self.port = port
|
||||
self.fd_dict = {}
|
||||
self.stop_event = stop_event
|
||||
|
||||
def read(self, fileno, bufsize):
|
||||
# Someone wants to connect?
|
||||
if fileno == self.sock_fd.fileno():
|
||||
self._accept()
|
||||
return None
|
||||
|
||||
self._cur_fileno = fileno
|
||||
|
||||
# Read a maximum of _bufsize_ bytes without blocking. When it
|
||||
# would want to block it means there's no more data. An empty
|
||||
# buffer normally means that we've been disconnected.
|
||||
try:
|
||||
buf = self._read_cur(bufsize, socket.MSG_DONTWAIT)
|
||||
except BlockingIOError:
|
||||
return None
|
||||
except ConnectionResetError:
|
||||
buf = b''
|
||||
if not buf:
|
||||
self._disconnect(fileno)
|
||||
|
||||
# Process and remove any telnet commands from the buffer
|
||||
if IAC in buf:
|
||||
buf = self._IAC_parser(buf)
|
||||
|
||||
return buf
|
||||
|
||||
def write(self, buf):
|
||||
for fd in self.fd_dict.values():
|
||||
fd.send(buf)
|
||||
|
||||
def register(self, epoll):
|
||||
self.epoll = epoll
|
||||
epoll.register(self.sock_fd, select.EPOLLIN)
|
||||
|
||||
def unregister(self, epoll):
|
||||
epoll.unregister(self.sock_fd)
|
||||
|
||||
def _read_block(self, bufsize):
|
||||
buf = self._read_cur(bufsize, socket.MSG_WAITALL)
|
||||
# If we don't get everything we were looking for then the
|
||||
# client probably disconnected.
|
||||
if len(buf) < bufsize:
|
||||
self._disconnect(self._cur_fileno)
|
||||
return buf
|
||||
|
||||
def _read_cur(self, bufsize, flags):
|
||||
return self.fd_dict[self._cur_fileno].recv(bufsize, flags)
|
||||
|
||||
def _write_cur(self, buf):
|
||||
return self.fd_dict[self._cur_fileno].send(buf)
|
||||
|
||||
def _IAC_parser(self, buf):
|
||||
skip_to = 0
|
||||
while not self.stop_event.is_set():
|
||||
# Locate an IAC to process
|
||||
iac_loc = buf.find(IAC, skip_to)
|
||||
if iac_loc < 0:
|
||||
break
|
||||
|
||||
# Get the TELNET command
|
||||
iac_cmd = bytearray([IAC])
|
||||
try:
|
||||
iac_cmd.append(buf[iac_loc + 1])
|
||||
except IndexError:
|
||||
buf.extend(self._read_block(1))
|
||||
iac_cmd.append(buf[iac_loc + 1])
|
||||
|
||||
# Is this just a 2-byte TELNET command?
|
||||
if iac_cmd[1] not in [WILL, WONT, DO, DONT]:
|
||||
if iac_cmd[1] == AYT:
|
||||
log.debug("Telnet server received Are-You-There (AYT)")
|
||||
self._write_cur(
|
||||
b'\r\nYour Are-You-There received. I am here.\r\n'
|
||||
)
|
||||
elif iac_cmd[1] == IAC:
|
||||
# It's data, not an IAC
|
||||
iac_cmd.pop()
|
||||
# This prevents the 0xff from being
|
||||
# interputed as yet another IAC
|
||||
skip_to = iac_loc + 1
|
||||
log.debug("Received IAC IAC")
|
||||
elif iac_cmd[1] == NOP:
|
||||
pass
|
||||
else:
|
||||
log.debug("Unhandled telnet command: "
|
||||
"{0:#x} {1:#x}".format(*iac_cmd))
|
||||
|
||||
# This must be a 3-byte TELNET command
|
||||
else:
|
||||
try:
|
||||
iac_cmd.append(buf[iac_loc + 2])
|
||||
except IndexError:
|
||||
buf.extend(self._read_block(1))
|
||||
iac_cmd.append(buf[iac_loc + 2])
|
||||
# We do ECHO, SGA, and BINARY. Period.
|
||||
if iac_cmd[1] == DO:
|
||||
if iac_cmd[2] not in [ECHO, SGA, BINARY]:
|
||||
self._write_cur(bytes([IAC, WONT, iac_cmd[2]]))
|
||||
log.debug("Telnet WON'T {:#x}".format(iac_cmd[2]))
|
||||
elif iac_cmd[1] == WILL and iac_cmd[2] == BINARY:
|
||||
pass # It's standard negociation we can ignore it
|
||||
else:
|
||||
log.debug("Unhandled telnet command: "
|
||||
"{0:#x} {1:#x} {2:#x}".format(*iac_cmd))
|
||||
|
||||
# Remove the entire TELNET command from the buffer
|
||||
buf = buf.replace(iac_cmd, b'', 1)
|
||||
|
||||
# Return the new copy of the buffer, minus telnet commands
|
||||
return buf
|
||||
|
||||
def _accept(self):
|
||||
fd, addr = self.sock_fd.accept()
|
||||
self.fd_dict[fd.fileno()] = fd
|
||||
self.epoll.register(fd, select.EPOLLIN | select.EPOLLET)
|
||||
|
||||
log.info("Telnet connection from {}:{}".format(addr[0], addr[1]))
|
||||
|
||||
# This is a one-way negotiation. This is very basic so there
|
||||
# shouldn't be any problems with any decent client.
|
||||
fd.send(bytes([IAC, WILL, ECHO,
|
||||
IAC, WILL, SGA,
|
||||
IAC, WILL, BINARY,
|
||||
IAC, DO, BINARY]))
|
||||
|
||||
if args.telnet_limit and len(self.fd_dict) > args.telnet_limit:
|
||||
fd.send(b'\r\nToo many connections\r\n')
|
||||
self._disconnect(fd.fileno())
|
||||
log.warn("Client disconnected because of too many connections. "
|
||||
"(limit currently {})".format(args.telnet_limit))
|
||||
|
||||
def _disconnect(self, fileno):
|
||||
fd = self.fd_dict.pop(fileno)
|
||||
log.info("Telnet client disconnected")
|
||||
try:
|
||||
fd.shutdown(socket.SHUT_RDWR)
|
||||
except OSError as e:
|
||||
log.warn("shutdown: {}".format(e))
|
||||
fd.close()
|
||||
|
||||
def __enter__(self):
|
||||
# Open a socket and start listening
|
||||
|
||||
info = socket.getaddrinfo(self.addr, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
|
||||
if not info:
|
||||
raise TelnetServerError("getaddrinfo returns an empty list on {}:{}".format(self.addr, self.port))
|
||||
for res in info:
|
||||
af, socktype, proto, _, sa = res
|
||||
sock_fd = socket.socket(af, socktype, proto)
|
||||
sock_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
sock_fd.bind(sa)
|
||||
except OSError:
|
||||
raise TelnetServerError("Cannot bind to {}:{}"
|
||||
.format(self.addr, self.port))
|
||||
|
||||
sock_fd.listen(socket.SOMAXCONN)
|
||||
self.sock_fd = sock_fd
|
||||
log.info("Telnet server ready for connections on {}:{}".format(self.addr, self.port))
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
for fileno in list(self.fd_dict.keys()):
|
||||
self._disconnect(fileno)
|
||||
self.sock_fd.close()
|
||||
return False
|
||||
|
||||
|
||||
class IOU:
|
||||
|
||||
def __init__(self, ttyC, ttyS, stop_event):
|
||||
self.ttyC = ttyC
|
||||
self.ttyS = ttyS
|
||||
self.stop_event = stop_event
|
||||
|
||||
def read(self, bufsize):
|
||||
try:
|
||||
buf = self.fd.recv(bufsize)
|
||||
except BlockingIOError:
|
||||
return None
|
||||
return buf
|
||||
|
||||
def write(self, buf):
|
||||
try:
|
||||
self.fd.send(buf)
|
||||
except BlockingIOError:
|
||||
return
|
||||
|
||||
def _open(self):
|
||||
self.fd = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||
self.fd.setblocking(False)
|
||||
|
||||
def _bind(self):
|
||||
try:
|
||||
os.unlink(self.ttyC)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
raise NetioError("Couldn't unlink socket {}: {}".format(self.ttyC, e))
|
||||
|
||||
try:
|
||||
self.fd.bind(self.ttyC)
|
||||
except Exception as e:
|
||||
raise NetioError("Couldn't create socket {}: {}".format(self.ttyC, e))
|
||||
|
||||
def _connect(self):
|
||||
# Keep trying until we connect or die trying
|
||||
while not self.stop_event.is_set():
|
||||
try:
|
||||
self.fd.connect(self.ttyS)
|
||||
except FileNotFoundError:
|
||||
log.debug("Waiting to connect to {}".format(self.ttyS))
|
||||
time.sleep(RETRY_DELAY)
|
||||
except Exception as e:
|
||||
raise NetioError("Couldn't connect to socket {}: {}".format(self.ttyS, e))
|
||||
else:
|
||||
break
|
||||
|
||||
def register(self, epoll):
|
||||
self.epoll = epoll
|
||||
epoll.register(self.fd, select.EPOLLIN | select.EPOLLET)
|
||||
|
||||
def unregister(self, epoll):
|
||||
epoll.unregister(self.fd)
|
||||
|
||||
def fileno(self):
|
||||
return self.fd.fileno()
|
||||
|
||||
def __enter__(self):
|
||||
self._open()
|
||||
self._bind()
|
||||
self._connect()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
os.unlink(self.ttyC)
|
||||
self.fd.close()
|
||||
return False
|
||||
|
||||
|
||||
class IOUConError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LockError(IOUConError):
|
||||
pass
|
||||
|
||||
|
||||
class NetioError(IOUConError):
|
||||
pass
|
||||
|
||||
|
||||
class TTYError(IOUConError):
|
||||
pass
|
||||
|
||||
|
||||
class TelnetServerError(IOUConError):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigError(IOUConError):
|
||||
pass
|
||||
|
||||
|
||||
def mkdir_netio(netio_dir):
|
||||
try:
|
||||
os.mkdir(netio_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
except Exception as e:
|
||||
raise NetioError("Couldn't create directory {}: {}".format(netio_dir, e))
|
||||
|
||||
|
||||
def send_recv_loop(epoll, console, router, esc_char, stop_event):
|
||||
router.register(epoll)
|
||||
console.register(epoll)
|
||||
|
||||
try:
|
||||
router_fileno = router.fileno()
|
||||
esc_quit = bytes(ESC_QUIT.upper(), 'ascii')
|
||||
esc_state = False
|
||||
|
||||
while not stop_event.is_set():
|
||||
event_list = epoll.poll(timeout=POLL_TIMEOUT)
|
||||
|
||||
# When/if the poll times out we send an empty datagram. If IOU
|
||||
# has gone away then this will toss a ConnectionRefusedError
|
||||
# exception.
|
||||
if not event_list:
|
||||
router.write(b'')
|
||||
continue
|
||||
|
||||
for fileno, event in event_list:
|
||||
buf = bytearray()
|
||||
|
||||
# IOU --> tty(s)
|
||||
if fileno == router_fileno:
|
||||
while not stop_event.is_set():
|
||||
data = router.read(BUFFER_SIZE)
|
||||
if not data:
|
||||
break
|
||||
buf.extend(data)
|
||||
console.write(buf)
|
||||
|
||||
# tty --> IOU
|
||||
else:
|
||||
while not stop_event.is_set():
|
||||
data = console.read(fileno, BUFFER_SIZE)
|
||||
if not data:
|
||||
break
|
||||
buf.extend(data)
|
||||
|
||||
# If we just received the escape character then
|
||||
# enter the escape state.
|
||||
#
|
||||
# If we are in the escape state then check for a
|
||||
# quit command. Or if it's the escape character then
|
||||
# send the escape character. Else, send the escape
|
||||
# character we ate earlier and whatever character we
|
||||
# just got. Exit escape state.
|
||||
#
|
||||
# If we're not in the escape state and this isn't an
|
||||
# escape character then just send it to IOU.
|
||||
if esc_state:
|
||||
if buf.upper() == esc_quit:
|
||||
sys.exit(EXIT_SUCCESS)
|
||||
elif buf == esc_char:
|
||||
router.write(esc_char)
|
||||
else:
|
||||
router.write(esc_char)
|
||||
router.write(buf)
|
||||
esc_state = False
|
||||
elif buf == esc_char:
|
||||
esc_state = True
|
||||
else:
|
||||
router.write(buf)
|
||||
finally:
|
||||
router.unregister(epoll)
|
||||
console.unregister(epoll)
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Connect to an IOU console port.')
|
||||
parser.add_argument('-d', '--debug', action='store_true',
|
||||
help='display some debugging information')
|
||||
parser.add_argument('-e', '--escape',
|
||||
help='set escape character (default: %(default)s)',
|
||||
default=ESC_CHAR, metavar='CHAR')
|
||||
parser.add_argument('-t', '--telnet-server',
|
||||
help='start telnet server listening on ADDR:PORT',
|
||||
metavar='ADDR:PORT', default=False)
|
||||
parser.add_argument('-l', '--telnet-limit',
|
||||
help='maximum number of simultaneous '
|
||||
'telnet connections (default: %(default)s)',
|
||||
metavar='LIMIT', type=int, default=1)
|
||||
parser.add_argument('appl_id', help='IOU instance identifier')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_escape_character(escape):
|
||||
|
||||
# Figure out the escape character to use.
|
||||
# Can be any ASCII character or a spelled out control
|
||||
# character, like "^e". The string "none" disables it.
|
||||
if escape.lower() == 'none':
|
||||
esc_char = b''
|
||||
elif len(escape) == 2 and escape[0] == '^':
|
||||
c = ord(escape[1].upper()) - 0x40
|
||||
if not 0 <= c <= 0x1f: # control code range
|
||||
raise ConfigError("Invalid control code")
|
||||
esc_char = bytes([c])
|
||||
elif len(escape) == 1:
|
||||
try:
|
||||
esc_char = bytes(escape, 'ascii')
|
||||
except ValueError as e:
|
||||
raise ConfigError("Invalid escape character") from e
|
||||
else:
|
||||
raise ConfigError("Invalid length for escape character")
|
||||
|
||||
return esc_char
|
||||
|
||||
|
||||
def start_ioucon(cmdline_args, stop_event):
|
||||
|
||||
global args
|
||||
args = cmdline_args
|
||||
|
||||
if args.debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
else:
|
||||
# default logging level
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Create paths for the Unix domain sockets
|
||||
netio = '/tmp/netio{}'.format(os.getuid())
|
||||
ttyC = '{}/ttyC{}'.format(netio, args.appl_id)
|
||||
ttyS = '{}/ttyS{}'.format(netio, args.appl_id)
|
||||
|
||||
try:
|
||||
mkdir_netio(netio)
|
||||
with FileLock(ttyC):
|
||||
esc_char = get_escape_character(args.escape)
|
||||
|
||||
if args.telnet_server:
|
||||
addr, _, port = args.telnet_server.partition(':')
|
||||
nport = 0
|
||||
try:
|
||||
nport = int(port)
|
||||
except ValueError:
|
||||
pass
|
||||
if addr == '' or nport == 0:
|
||||
raise ConfigError('format for --telnet-server must be '
|
||||
'ADDR:PORT (like 127.0.0.1:20000)')
|
||||
|
||||
while not stop_event.is_set():
|
||||
epoll = select.epoll()
|
||||
try:
|
||||
if args.telnet_server:
|
||||
with TelnetServer(addr, nport, stop_event) as console:
|
||||
# We loop inside the Telnet server otherwise the client is disconnected when user use the reload command inside a terminal
|
||||
while not stop_event.is_set():
|
||||
try:
|
||||
with IOU(ttyC, ttyS, stop_event) as router:
|
||||
send_recv_loop(epoll, console, router, b'', stop_event)
|
||||
except ConnectionRefusedError:
|
||||
pass
|
||||
else:
|
||||
with IOU(ttyC, ttyS, stop_event) as router, TTY() as console:
|
||||
send_recv_loop(epoll, console, router, esc_char, stop_event)
|
||||
except ConnectionRefusedError:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(EXIT_ABORT)
|
||||
finally:
|
||||
# Put us at the beginning of a line
|
||||
if not args.telnet_server:
|
||||
print()
|
||||
|
||||
except IOUConError as e:
|
||||
if args.debug:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
else:
|
||||
log.error("ioucon: {}".format(e))
|
||||
sys.exit(EXIT_FAILURE)
|
||||
|
||||
log.info("exiting...")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
import threading
|
||||
stop_event = threading.Event()
|
||||
args = get_args()
|
||||
start_ioucon(args, stop_event)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Whitespace-only changes.
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# To use python v2.7 change the first line to:
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2015 Bernhard Ehlers
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
# This utility is a stripped down version of dynamips' nvram_export,
|
||||
# ported from C to Python, see https://github.com/GNS3/dynamips
|
||||
# nvram_export is (c) 2013 Flávio J. Saraiva
|
||||
|
||||
"""
|
||||
iou_export exports startup/private configuration from IOU NVRAM file.
|
||||
|
||||
usage: iou_export [-h] NVRAM startup-config [private-config]
|
||||
|
||||
positional arguments:
|
||||
NVRAM NVRAM file
|
||||
startup-config startup configuration
|
||||
private-config private configuration
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
# Uncompress data in .Z file format.
|
||||
# Ported from dynamips' fs_nvram.c to python
|
||||
# Adapted from 7zip's ZDecoder.cpp, which is licensed under LGPL 2.1.
|
||||
def uncompress_LZC(data):
|
||||
LZC_NUM_BITS_MIN = 9
|
||||
LZC_NUM_BITS_MAX = 16
|
||||
|
||||
in_data = bytearray(data)
|
||||
in_len = len(in_data)
|
||||
out_data = bytearray()
|
||||
|
||||
if in_len == 0:
|
||||
return out_data
|
||||
if in_len < 3:
|
||||
raise ValueError('invalid length')
|
||||
if in_data[0] != 0x1F or in_data[1] != 0x9D:
|
||||
raise ValueError('invalid header')
|
||||
|
||||
maxbits = in_data[2] & 0x1F
|
||||
numItems = 1 << maxbits
|
||||
blockMode = (in_data[2] & 0x80) != 0
|
||||
if maxbits < LZC_NUM_BITS_MIN or maxbits > LZC_NUM_BITS_MAX:
|
||||
raise ValueError('not supported')
|
||||
|
||||
parents = [0] * numItems
|
||||
suffixes = [0] * numItems
|
||||
|
||||
in_pos = 3
|
||||
numBits = LZC_NUM_BITS_MIN
|
||||
head = 256
|
||||
if blockMode:
|
||||
head += 1
|
||||
|
||||
needPrev = 0
|
||||
bitPos = 0
|
||||
numBufBits = 0
|
||||
|
||||
parents[256] = 0
|
||||
suffixes[256] = 0
|
||||
|
||||
buf_extend = bytearray([0] * 3)
|
||||
|
||||
while True:
|
||||
# fill buffer, when empty
|
||||
if numBufBits == bitPos:
|
||||
buf_len = min(in_len - in_pos, numBits)
|
||||
buf = in_data[in_pos:in_pos + buf_len] + buf_extend
|
||||
numBufBits = buf_len << 3
|
||||
bitPos = 0
|
||||
in_pos += buf_len
|
||||
|
||||
# extract next symbol
|
||||
bytePos = bitPos >> 3
|
||||
symbol = buf[bytePos] | buf[bytePos + 1] << 8 | buf[bytePos + 2] << 16
|
||||
symbol >>= bitPos & 7
|
||||
symbol &= (1 << numBits) - 1
|
||||
bitPos += numBits
|
||||
|
||||
# check for special conditions: end, bad data, re-initialize dictionary
|
||||
if bitPos > numBufBits:
|
||||
break
|
||||
if symbol >= head:
|
||||
raise ValueError('invalid data')
|
||||
if blockMode and symbol == 256:
|
||||
numBufBits = bitPos = 0
|
||||
numBits = LZC_NUM_BITS_MIN
|
||||
head = 257
|
||||
needPrev = 0
|
||||
continue
|
||||
|
||||
# convert symbol to string
|
||||
stack = []
|
||||
cur = symbol
|
||||
while cur >= 256:
|
||||
stack.append(suffixes[cur])
|
||||
cur = parents[cur]
|
||||
stack.append(cur)
|
||||
if needPrev:
|
||||
suffixes[head - 1] = cur
|
||||
if symbol == head - 1:
|
||||
stack[0] = cur
|
||||
stack.reverse()
|
||||
out_data.extend(stack)
|
||||
|
||||
# update parents, check for numBits change
|
||||
if head < numItems:
|
||||
needPrev = 1
|
||||
parents[head] = symbol
|
||||
head += 1
|
||||
if head > (1 << numBits):
|
||||
if numBits < maxbits:
|
||||
numBufBits = bitPos = 0
|
||||
numBits += 1
|
||||
else:
|
||||
needPrev = 0
|
||||
|
||||
return out_data
|
||||
|
||||
|
||||
# extract 16 bit unsigned int from data
|
||||
def get_uint16(data, off):
|
||||
return data[off] << 8 | data[off + 1]
|
||||
|
||||
|
||||
# extract 32 bit unsigned int from data
|
||||
def get_uint32(data, off):
|
||||
return data[off] << 24 | data[off + 1] << 16 | data[off + 2] << 8 | data[off + 3]
|
||||
|
||||
|
||||
# export IOU NVRAM
|
||||
def nvram_export(nvram):
|
||||
nvram = bytearray(nvram)
|
||||
|
||||
# extract startup config
|
||||
offset = 0
|
||||
if len(nvram) < offset + 36:
|
||||
raise ValueError('invalid length')
|
||||
if get_uint16(nvram, offset + 0) != 0xABCD:
|
||||
raise ValueError('no startup config')
|
||||
format = get_uint16(nvram, offset + 2)
|
||||
length = get_uint32(nvram, offset + 16)
|
||||
offset += 36
|
||||
if len(nvram) < offset + length:
|
||||
raise ValueError('invalid length')
|
||||
startup = nvram[offset:offset + length]
|
||||
|
||||
# compressed startup config
|
||||
if format == 2:
|
||||
try:
|
||||
startup = uncompress_LZC(startup)
|
||||
except ValueError as err:
|
||||
raise ValueError('uncompress startup: ' + str(err))
|
||||
|
||||
offset += length
|
||||
# alignment to multiple of 4
|
||||
offset = (offset + 3) & ~3
|
||||
# check for additonal offset of 4
|
||||
if len(nvram) >= offset + 8 and \
|
||||
get_uint16(nvram, offset + 4) == 0xFEDC and \
|
||||
get_uint16(nvram, offset + 6) == 1:
|
||||
offset += 4
|
||||
|
||||
# extract private config
|
||||
private = None
|
||||
if len(nvram) >= offset + 16 and get_uint16(nvram, offset + 0) == 0xFEDC:
|
||||
length = get_uint32(nvram, offset + 12)
|
||||
offset += 16
|
||||
if len(nvram) >= offset + length:
|
||||
private = nvram[offset:offset + length]
|
||||
|
||||
return (startup, private)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Main program
|
||||
|
||||
parser = argparse.ArgumentParser(description='%(prog)s exports startup/private configuration from IOU NVRAM file.')
|
||||
parser.add_argument('nvram', metavar='NVRAM',
|
||||
help='NVRAM file')
|
||||
parser.add_argument('startup', metavar='startup-config',
|
||||
help='startup configuration')
|
||||
parser.add_argument('private', metavar='private-config', nargs='?',
|
||||
help='private configuration')
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
fd = open(args.nvram, 'rb')
|
||||
nvram = fd.read()
|
||||
fd.close()
|
||||
except (IOError, OSError) as err:
|
||||
sys.stderr.write("Error reading file: {}\n".format(err))
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
startup, private = nvram_export(nvram)
|
||||
except ValueError as err:
|
||||
sys.stderr.write("nvram_export: {}\n".format(err))
|
||||
sys.exit(3)
|
||||
|
||||
try:
|
||||
fd = open(args.startup, 'wb')
|
||||
fd.write(startup)
|
||||
fd.close()
|
||||
if args.private is not None:
|
||||
if private is None:
|
||||
sys.stderr.write("Warning: No private config\n")
|
||||
else:
|
||||
fd = open(args.private, 'wb')
|
||||
fd.write(private)
|
||||
fd.close()
|
||||
except (IOError, OSError) as err:
|
||||
sys.stderr.write("Error writing file: {}\n".format(err))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# To use python v2.7 change the first line to:
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2015 Bernhard Ehlers
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
iou_import imports startup/private configuration into IOU NVRAM file.
|
||||
|
||||
usage: iou_import [-h] [-c size] NVRAM startup-config [private-config]
|
||||
|
||||
positional arguments:
|
||||
NVRAM NVRAM file
|
||||
startup-config startup configuration
|
||||
private-config private configuration
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c size, --create size
|
||||
create NVRAM file, size in kByte
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
# extract 16 bit unsigned int from data
|
||||
def get_uint16(data, off):
|
||||
return data[off] << 8 | data[off + 1]
|
||||
|
||||
|
||||
# extract 32 bit unsigned int from data
|
||||
def get_uint32(data, off):
|
||||
return data[off] << 24 | data[off + 1] << 16 | data[off + 2] << 8 | data[off + 3]
|
||||
|
||||
|
||||
# insert 16 bit unsigned int into data
|
||||
def put_uint16(data, off, value):
|
||||
data[off] = (value >> 8) & 0xff
|
||||
data[off + 1] = value & 0xff
|
||||
|
||||
|
||||
# insert 32 bit unsigned int into data
|
||||
def put_uint32(data, off, value):
|
||||
data[off] = (value >> 24) & 0xff
|
||||
data[off + 1] = (value >> 16) & 0xff
|
||||
data[off + 2] = (value >> 8) & 0xff
|
||||
data[off + 3] = value & 0xff
|
||||
|
||||
|
||||
# calculate padding
|
||||
def padding(off, ios):
|
||||
pad = (4 - off % 4) % 4 # padding to alignment of 4
|
||||
if ios <= 0x0F00 and pad != 0: # add 4 if IOS <= 15.0
|
||||
pad += 4
|
||||
return pad
|
||||
|
||||
|
||||
# update checksum
|
||||
def checksum(data, start, end):
|
||||
put_uint16(data, start + 4, 0) # set checksum to 0
|
||||
|
||||
chk = 0
|
||||
idx = start
|
||||
while idx < end - 1:
|
||||
chk += get_uint16(data, idx)
|
||||
idx += 2
|
||||
if idx < end:
|
||||
chk += data[idx] << 8
|
||||
|
||||
while chk >> 16:
|
||||
chk = (chk & 0xffff) + (chk >> 16)
|
||||
|
||||
chk = chk ^ 0xffff
|
||||
put_uint16(data, start + 4, chk) # set checksum
|
||||
|
||||
|
||||
# import IOU NVRAM
|
||||
def nvram_import(nvram, startup, private, size):
|
||||
BASE_ADDRESS = 0x10000000
|
||||
DEFAULT_IOS = 0x0F04 # IOS 15.4
|
||||
|
||||
# check size parameter
|
||||
if size is not None and (size < 8 or size > 1024):
|
||||
raise ValueError('invalid size')
|
||||
|
||||
# create new nvram if nvram is empty or has wrong size
|
||||
if nvram is None or (size is not None and len(nvram) != size * 1024):
|
||||
nvram = bytearray([0] * (size * 1024))
|
||||
else:
|
||||
nvram = bytearray(nvram)
|
||||
|
||||
# check nvram size
|
||||
nvram_len = len(nvram)
|
||||
if nvram_len < 8 * 1024 or nvram_len > 1024 * 1024 or nvram_len % 1024 != 0:
|
||||
raise ValueError('invalid NVRAM length')
|
||||
nvram_len = nvram_len // 2
|
||||
|
||||
# get size of current config
|
||||
config_len = 0
|
||||
ios = None
|
||||
try:
|
||||
if get_uint16(nvram, 0) == 0xABCD:
|
||||
ios = get_uint16(nvram, 6)
|
||||
config_len = 36 + get_uint32(nvram, 16)
|
||||
config_len += padding(config_len, ios)
|
||||
if get_uint16(nvram, config_len) == 0xFEDC:
|
||||
config_len += 16 + get_uint32(nvram, config_len + 12)
|
||||
except IndexError:
|
||||
raise ValueError('unknown nvram format')
|
||||
if config_len > nvram_len:
|
||||
raise ValueError('unknown nvram format')
|
||||
|
||||
# calculate max. config size
|
||||
max_config = nvram_len - 2 * 1024 # reserve 2k for files
|
||||
idx = max_config
|
||||
empty_sector = bytearray([0] * 1024)
|
||||
while True:
|
||||
idx -= 1024
|
||||
if idx < config_len:
|
||||
break
|
||||
# if valid file header:
|
||||
if get_uint16(nvram, idx + 0) == 0xDCBA and \
|
||||
get_uint16(nvram, idx + 4) < 8 and \
|
||||
get_uint16(nvram, idx + 6) <= 992:
|
||||
max_config = idx
|
||||
elif nvram[idx:idx + 1024] != empty_sector:
|
||||
break
|
||||
|
||||
# import startup config
|
||||
startup = bytearray(startup)
|
||||
if ios is None:
|
||||
# Target IOS version is unknown. As some IOU don't work nicely with
|
||||
# the padding of a different version, the startup config is padded
|
||||
# with '\n' to the alignment of 4.
|
||||
ios = DEFAULT_IOS
|
||||
startup.extend([ord('\n')] * ((4 - len(startup) % 4) % 4))
|
||||
new_nvram = bytearray([0] * 36) # startup hdr
|
||||
put_uint16(new_nvram, 0, 0xABCD) # magic
|
||||
put_uint16(new_nvram, 2, 1) # raw data
|
||||
put_uint16(new_nvram, 6, ios) # IOS version
|
||||
put_uint32(new_nvram, 8, BASE_ADDRESS + 36) # start address
|
||||
put_uint32(new_nvram, 12, BASE_ADDRESS + 36 + len(startup)) # end address
|
||||
put_uint32(new_nvram, 16, len(startup)) # length
|
||||
new_nvram.extend(startup)
|
||||
new_nvram.extend([0] * padding(len(new_nvram), ios))
|
||||
|
||||
# import private config
|
||||
if private is None:
|
||||
private = bytearray()
|
||||
else:
|
||||
private = bytearray(private)
|
||||
offset = len(new_nvram)
|
||||
new_nvram.extend([0] * 16) # private hdr
|
||||
put_uint16(new_nvram, 0 + offset, 0xFEDC) # magic
|
||||
put_uint16(new_nvram, 2 + offset, 1) # raw data
|
||||
put_uint32(new_nvram, 4 + offset,
|
||||
BASE_ADDRESS + offset + 16) # start address
|
||||
put_uint32(new_nvram, 8 + offset,
|
||||
BASE_ADDRESS + offset + 16 + len(private)) # end address
|
||||
put_uint32(new_nvram, 12 + offset, len(private)) # length
|
||||
new_nvram.extend(private)
|
||||
|
||||
# add rest
|
||||
if len(new_nvram) > max_config:
|
||||
raise ValueError('NVRAM size too small')
|
||||
new_nvram.extend([0] * (max_config - len(new_nvram)))
|
||||
new_nvram.extend(nvram[max_config:])
|
||||
|
||||
checksum(new_nvram, 0, nvram_len)
|
||||
|
||||
return new_nvram
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Main program
|
||||
|
||||
def check_size(string):
|
||||
try:
|
||||
value = int(string)
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError('invalid int value: ' + string)
|
||||
if value < 8 or value > 1024:
|
||||
raise argparse.ArgumentTypeError('size must be 8..1024')
|
||||
return value
|
||||
|
||||
parser = argparse.ArgumentParser(description='%(prog)s imports startup/private configuration into IOU NVRAM file.')
|
||||
parser.add_argument('-c', '--create', metavar='size', type=check_size,
|
||||
help='create NVRAM file, size in kByte')
|
||||
parser.add_argument('nvram', metavar='NVRAM',
|
||||
help='NVRAM file')
|
||||
parser.add_argument('startup', metavar='startup-config',
|
||||
help='startup configuration')
|
||||
parser.add_argument('private', metavar='private-config', nargs='?',
|
||||
help='private configuration')
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.create is None:
|
||||
fd = open(args.nvram, 'rb')
|
||||
nvram = fd.read()
|
||||
fd.close()
|
||||
else:
|
||||
nvram = None
|
||||
fd = open(args.startup, 'rb')
|
||||
startup = fd.read()
|
||||
fd.close()
|
||||
if args.private is None:
|
||||
private = None
|
||||
else:
|
||||
fd = open(args.private, 'rb')
|
||||
private = fd.read()
|
||||
fd.close()
|
||||
except (IOError, OSError) as err:
|
||||
sys.stderr.write("Error reading file: {}\n".format(err))
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
nvram = nvram_import(nvram, startup, private, args.create)
|
||||
except ValueError as err:
|
||||
sys.stderr.write("nvram_import: {}\n".format(err))
|
||||
sys.exit(3)
|
||||
|
||||
try:
|
||||
fd = open(args.nvram, 'wb')
|
||||
fd.write(nvram)
|
||||
fd.close()
|
||||
except (IOError, OSError) as err:
|
||||
sys.stderr.write("Error writing file: {}\n".format(err))
|
||||
sys.exit(1)
|
||||
Reference in new issue
Block a user