2015-02-11 15:31:21 +02:00
#
# 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 VM management ( creates command line , processes , files etc . ) in
2015-04-08 20:17:34 +03:00
order to run an IOU VM .
2015-02-11 15:31:21 +02:00
"""
import os
2015-03-12 02:59:57 +02:00
import socket
2015-02-11 15:31:21 +02:00
import re
import asyncio
2015-02-16 18:20:07 +02:00
import subprocess
2015-02-11 15:31:21 +02:00
import shutil
2015-02-11 18:11:18 +02:00
import configparser
2015-03-12 02:59:57 +02:00
import struct
import hashlib
2015-02-13 21:57:20 +02:00
import glob
2015-06-07 00:15:03 +03:00
import binascii
2015-12-06 03:24:08 +02:00
import functools
2015-02-11 15:31:21 +02:00
from . iou_error import IOUError
from . . adapters . ethernet_adapter import EthernetAdapter
from . . adapters . serial_adapter import SerialAdapter
2015-02-24 04:00:34 +02:00
from . . nios . nio_udp import NIOUDP
2016-05-11 20:35:36 +03:00
from . . base_node import BaseNode
2015-06-07 00:15:03 +03:00
from . utils . iou_import import nvram_import
2015-10-21 15:28:39 +03:00
from . utils . iou_export import nvram_export
2020-11-19 06:51:03 +02:00
from gns3server . compute . ubridge . ubridge_error import UbridgeError
2016-06-10 19:26:01 +03:00
from gns3server . utils . file_watcher import FileWatcher
2016-11-08 11:21:20 +02:00
from gns3server . utils . asyncio . telnet_server import AsyncioTelnetServer
2018-08-25 10:10:47 +03:00
from gns3server . utils . asyncio import locking
2015-02-16 18:20:07 +02:00
import gns3server . utils . asyncio
2015-06-17 18:11:25 +03:00
import gns3server . utils . images
2015-02-11 15:31:21 +02:00
import logging
2015-03-16 12:52:22 +02:00
import sys
2021-04-13 12:16:50 +03:00
2015-02-11 15:31:21 +02:00
log = logging . getLogger ( __name__ )
2016-05-11 20:35:36 +03:00
class IOUVM ( BaseNode ) :
2021-04-13 12:16:50 +03:00
module_name = " iou "
2015-02-11 15:31:21 +02:00
"""
2015-04-08 20:17:34 +03:00
IOU VM implementation .
2015-02-11 15:31:21 +02:00
2015-04-08 20:17:34 +03:00
: param name : IOU VM name
2016-05-11 20:35:36 +03:00
: param node_id : Node identifier
2015-02-11 15:31:21 +02:00
: param project : Project instance
2015-04-08 20:17:34 +03:00
: param manager : Manager instance
2015-02-11 15:31:21 +02:00
: param console : TCP console port
2018-03-24 14:11:21 +03:00
: param console_type : console type
2015-02-11 15:31:21 +02:00
"""
2021-04-13 12:16:50 +03:00
def __init__ (
self , name , node_id , project , manager , application_id = None , path = None , console = None , console_type = " telnet "
) :
2015-02-11 15:31:21 +02:00
2018-03-24 14:11:21 +03:00
super ( ) . __init__ ( name , node_id , project , manager , console = console , console_type = console_type )
2015-02-11 15:31:21 +02:00
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: assigned with application ID {application_id} ' . format (
name = self . _name , id = self . _id , application_id = application_id
)
)
2020-02-10 09:20:49 +02:00
2015-02-11 15:31:21 +02:00
self . _iou_process = None
2016-11-08 11:21:20 +02:00
self . _telnet_server = None
2015-02-11 15:31:21 +02:00
self . _iou_stdout_file = " "
self . _started = False
2016-11-06 12:27:49 +02:00
self . _nvram_watcher = None
2018-11-19 10:53:43 +02:00
self . _path = self . manager . get_abs_image_path ( path , project . path )
2018-11-14 10:24:30 +02:00
self . _license_check = True
2015-02-11 15:31:21 +02:00
# IOU settings
2015-02-12 22:02:52 +02:00
self . _ethernet_adapters = [ ]
self . _serial_adapters = [ ]
2015-04-13 00:09:37 +03:00
self . ethernet_adapters = 2 # one adapter = 4 interfaces
self . serial_adapters = 2 # one adapter = 4 interfaces
self . _use_default_iou_values = True # for RAM & NVRAM values
2015-04-15 16:50:34 +03:00
self . _nvram = 128 # Kilobytes
2015-06-07 00:15:03 +03:00
self . _startup_config = " "
self . _private_config = " "
2015-04-15 16:50:34 +03:00
self . _ram = 256 # Megabytes
2018-03-12 08:38:50 +02:00
self . _application_id = application_id
2015-04-13 00:09:37 +03:00
self . _l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes).
2015-02-13 23:16:43 +02:00
2016-06-10 19:26:01 +03:00
def _nvram_changed ( self , path ) :
"""
2016-06-11 07:33:07 +03:00
Called when the NVRAM file has changed
2016-06-10 19:26:01 +03:00
"""
2021-04-13 12:07:58 +03:00
log . debug ( f " NVRAM changed: { path } " )
2016-06-10 19:26:01 +03:00
self . save_configs ( )
2017-01-10 16:50:35 +02:00
self . updated ( )
2016-06-10 19:26:01 +03:00
2018-10-15 13:05:49 +03:00
async def close ( self ) :
2015-04-08 20:17:34 +03:00
"""
Closes this IOU VM .
"""
2015-02-11 15:31:21 +02:00
2018-10-15 13:05:49 +03:00
if not ( await super ( ) . close ( ) ) :
2016-02-29 11:38:30 +02:00
return False
2015-02-11 15:31:21 +02:00
2015-02-24 04:00:34 +02:00
adapters = self . _ethernet_adapters + self . _serial_adapters
for adapter in adapters :
if adapter is not None :
for nio in adapter . ports . values ( ) :
if nio and isinstance ( nio , NIOUDP ) :
2015-03-22 01:19:12 +02:00
self . manager . port_manager . release_udp_port ( nio . lport , self . _project )
2015-02-24 04:00:34 +02:00
2018-10-15 13:05:49 +03:00
await self . stop ( )
2015-02-24 04:00:34 +02:00
2015-02-11 15:31:21 +02:00
@property
2015-02-12 16:20:47 +02:00
def path ( self ) :
2015-04-08 20:17:34 +03:00
"""
Path of the IOU executable .
: returns : path to the IOU image executable
"""
2015-02-11 15:31:21 +02:00
2015-02-12 16:20:47 +02:00
return self . _path
2015-02-11 15:31:21 +02:00
2015-02-12 16:20:47 +02:00
@path.setter
def path ( self , path ) :
2015-02-11 15:31:21 +02:00
"""
2015-04-08 20:17:34 +03:00
Path of the IOU executable .
2015-02-11 15:31:21 +02:00
2015-04-08 20:17:34 +03:00
: param path : path to the IOU image executable
2015-02-11 15:31:21 +02:00
"""
2018-11-19 10:53:43 +02:00
self . _path = self . manager . get_abs_image_path ( path , self . project . path )
2021-04-13 12:07:58 +03:00
log . info ( f ' IOU " { self . _name } " [ { self . _id } ]: IOU image updated to " { self . _path } " ' )
2015-04-07 16:25:53 +03:00
2015-02-11 15:31:21 +02:00
@property
def use_default_iou_values ( self ) :
"""
Returns if this device uses the default IOU image values .
2015-04-08 20:17:34 +03:00
2015-02-11 15:31:21 +02:00
: returns : boolean
"""
return self . _use_default_iou_values
@use_default_iou_values.setter
def use_default_iou_values ( self , state ) :
"""
Sets if this device uses the default IOU image values .
2015-04-08 20:17:34 +03:00
2015-02-11 15:31:21 +02:00
: param state : boolean
"""
self . _use_default_iou_values = state
if state :
2021-04-13 12:07:58 +03:00
log . info ( f ' IOU " { self . _name } " [ { self . _id } ]: uses the default IOU image values ' )
2015-02-11 15:31:21 +02:00
else :
2021-04-13 12:07:58 +03:00
log . info ( f ' IOU " { self . _name } " [ { self . _id } ]: does not use the default IOU image values ' )
2015-02-11 15:31:21 +02:00
2018-10-15 13:05:49 +03:00
async def update_default_iou_values ( self ) :
2017-11-16 11:52:19 +02:00
"""
Finds the default RAM and NVRAM values for the IOU image .
"""
try :
2021-04-13 12:16:50 +03:00
output = await gns3server . utils . asyncio . subprocess_check_output (
self . _path , " -h " , cwd = self . working_dir , stderr = True
)
2019-01-17 13:01:58 +02:00
match = re . search ( r " -n <n> \ s+Size of nvram in Kb \ (default ([0-9]+)KB \ ) " , output )
2017-11-16 11:52:19 +02:00
if match :
self . nvram = int ( match . group ( 1 ) )
2019-01-17 13:01:58 +02:00
match = re . search ( r " -m <n> \ s+Megabytes of router memory \ (default ([0-9]+)MB \ ) " , output )
2017-11-16 11:52:19 +02:00
if match :
self . ram = int ( match . group ( 1 ) )
except ( ValueError , OSError , subprocess . SubprocessError ) as e :
2021-04-13 12:07:58 +03:00
log . warning ( f " could not find default RAM and NVRAM values for { os . path . basename ( self . _path ) } : { e } " )
2017-11-16 11:52:19 +02:00
2018-10-15 13:05:49 +03:00
async def create ( self ) :
2017-11-16 11:52:19 +02:00
2018-10-15 13:05:49 +03:00
await self . update_default_iou_values ( )
2017-11-16 11:52:19 +02:00
2015-02-11 15:31:21 +02:00
def _check_requirements ( self ) :
"""
2016-11-06 12:27:49 +02:00
Checks the IOU image .
2015-02-11 15:31:21 +02:00
"""
2015-04-08 20:17:34 +03:00
2016-11-02 11:20:43 +02:00
if not self . _path :
raise IOUError ( " IOU image is not configured " )
2015-06-19 17:35:19 +03:00
if not os . path . isfile ( self . _path ) or not os . path . exists ( self . _path ) :
if os . path . islink ( self . _path ) :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " IOU image ' { self . _path } ' linked to ' { os . path . realpath ( self . _path ) } ' is not accessible " )
2015-06-19 17:35:19 +03:00
else :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " IOU image ' { self . _path } ' is not accessible " )
2015-06-19 17:35:19 +03:00
try :
with open ( self . _path , " rb " ) as f :
# read the first 7 bytes of the file.
elf_header_start = f . read ( 7 )
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Cannot read ELF header for IOU image ' { self . _path } ' : { e } " )
2015-06-19 17:35:19 +03:00
2017-06-23 13:00:33 +03:00
# IOU images must start with the ELF magic number, be 32-bit or 64-bit, little endian
2015-06-19 17:35:19 +03:00
# and have an ELF version of 1 normal IOS image are big endian!
2021-04-13 12:16:50 +03:00
if elf_header_start != b " \x7f ELF \x01 \x01 \x01 " and elf_header_start != b " \x7f ELF \x02 \x01 \x01 " :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " ' { self . _path } ' is not a valid IOU image " )
2015-06-19 17:35:19 +03:00
if not os . access ( self . _path , os . X_OK ) :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " IOU image ' { self . _path } ' is not executable " )
2015-06-19 17:35:19 +03:00
2021-04-17 17:04:28 +03:00
def asdict ( self ) :
2015-02-11 15:31:21 +02:00
2021-04-13 12:16:50 +03:00
iou_vm_info = {
" name " : self . name ,
" usage " : self . usage ,
" node_id " : self . id ,
" node_directory " : self . working_path ,
" console " : self . _console ,
" console_type " : self . _console_type ,
" status " : self . status ,
" project_id " : self . project . id ,
" path " : self . path ,
2022-04-14 13:01:54 +03:00
" md5sum " : gns3server . utils . images . md5sum ( self . path , self . working_path ) ,
2021-04-13 12:16:50 +03:00
" ethernet_adapters " : len ( self . _ethernet_adapters ) ,
" serial_adapters " : len ( self . _serial_adapters ) ,
" ram " : self . _ram ,
" nvram " : self . _nvram ,
" l1_keepalives " : self . _l1_keepalives ,
" use_default_iou_values " : self . _use_default_iou_values ,
" command_line " : self . command_line ,
" application_id " : self . application_id ,
}
2015-03-11 23:04:11 +02:00
2018-11-19 10:53:43 +02:00
iou_vm_info [ " path " ] = self . manager . get_relative_image_path ( self . path , self . project . path )
2015-03-11 23:04:11 +02:00
return iou_vm_info
2015-02-11 15:31:21 +02:00
2015-02-17 17:40:45 +02:00
@property
def iourc_path ( self ) :
"""
2015-04-08 20:17:34 +03:00
Returns the IOURC file path .
2015-02-17 17:40:45 +02:00
: returns : path to IOURC
"""
2021-04-12 10:32:23 +03:00
iourc_path = self . _manager . config . settings . IOU . iourc_path
2015-03-12 02:59:57 +02:00
if not iourc_path :
2016-11-21 19:16:50 +02:00
# look for the iourc file in the temporary dir.
path = os . path . join ( self . temporary_directory , " iourc " )
if os . path . exists ( path ) :
return path
2015-03-12 02:59:57 +02:00
# look for the iourc file in the user home dir.
path = os . path . join ( os . path . expanduser ( " ~/ " ) , " .iourc " )
if os . path . exists ( path ) :
return path
# look for the iourc file in the current working dir.
path = os . path . join ( self . working_dir , " iourc " )
if os . path . exists ( path ) :
return path
return iourc_path
2015-02-17 17:40:45 +02:00
2015-02-12 22:02:52 +02:00
@property
def ram ( self ) :
"""
2015-04-08 20:17:34 +03:00
Returns the amount of RAM allocated to this IOU VM .
: returns : amount of RAM in MBytes ( integer )
2015-02-12 22:02:52 +02:00
"""
return self . _ram
@ram.setter
def ram ( self , ram ) :
"""
Sets amount of RAM allocated to this IOU instance .
2015-04-08 20:17:34 +03:00
: param ram : amount of RAM in MBytes ( integer )
2015-02-12 22:02:52 +02:00
"""
if self . _ram == ram :
return
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: RAM updated from {old_ram} MB to {new_ram} MB ' . format (
name = self . _name , id = self . _id , old_ram = self . _ram , new_ram = ram
)
)
2015-02-12 22:02:52 +02:00
self . _ram = ram
@property
def nvram ( self ) :
"""
Returns the mount of NVRAM allocated to this IOU instance .
2015-04-08 20:17:34 +03:00
: returns : amount of NVRAM in KBytes ( integer )
2015-02-12 22:02:52 +02:00
"""
return self . _nvram
@nvram.setter
def nvram ( self , nvram ) :
"""
Sets amount of NVRAM allocated to this IOU instance .
2015-04-08 20:17:34 +03:00
: param nvram : amount of NVRAM in KBytes ( integer )
2015-02-12 22:02:52 +02:00
"""
if self . _nvram == nvram :
return
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: NVRAM updated from {old_nvram} KB to {new_nvram} KB ' . format (
name = self . _name , id = self . _id , old_nvram = self . _nvram , new_nvram = nvram
)
)
2015-02-12 22:02:52 +02:00
self . _nvram = nvram
2016-05-11 20:35:36 +03:00
@BaseNode.name.setter
2015-02-13 23:16:43 +02:00
def name ( self , new_name ) :
"""
2015-04-08 20:17:34 +03:00
Sets the name of this IOU VM .
2015-02-13 23:16:43 +02:00
: param new_name : name
"""
2015-06-07 00:15:03 +03:00
if self . startup_config_file :
content = self . startup_config_content
2020-01-07 19:24:47 +02:00
content = re . sub ( r " hostname .+$ " , " hostname " + new_name , content , flags = re . MULTILINE )
2015-06-07 00:15:03 +03:00
self . startup_config_content = content
2015-02-13 23:16:43 +02:00
super ( IOUVM , IOUVM ) . name . __set__ ( self , new_name )
2015-03-17 17:31:45 +02:00
@property
def iourc_content ( self ) :
2015-04-08 20:17:34 +03:00
2015-03-17 23:18:55 +02:00
try :
2015-04-25 20:58:34 +03:00
with open ( os . path . join ( self . temporary_directory , " iourc " ) , " rb " ) as f :
return f . read ( ) . decode ( " utf-8 " )
2015-03-17 23:18:55 +02:00
except OSError :
return None
2015-03-17 17:31:45 +02:00
@iourc_content.setter
def iourc_content ( self , value ) :
2015-04-08 20:17:34 +03:00
2019-05-20 06:51:24 +03:00
if value :
2016-11-11 17:18:39 +02:00
# If we don't save the value in the ~/ the licence is lost at project
# reload
path = os . path . join ( os . path . expanduser ( " ~/ " ) , " .iourc " )
try :
2018-03-12 08:38:50 +02:00
with open ( path , " wb " ) as f :
2016-11-11 17:18:39 +02:00
f . write ( value . encode ( " utf-8 " ) )
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not write the iourc file { path } : { e } " )
2016-11-11 17:18:39 +02:00
2015-03-17 23:18:55 +02:00
path = os . path . join ( self . temporary_directory , " iourc " )
try :
2018-03-12 08:38:50 +02:00
with open ( path , " wb " ) as f :
2015-04-25 20:58:34 +03:00
f . write ( value . encode ( " utf-8 " ) )
2015-03-17 23:18:55 +02:00
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not write the iourc file { path } : { e } " )
2015-03-17 17:31:45 +02:00
2018-11-14 10:24:30 +02:00
@property
def license_check ( self ) :
return self . _license_check
@license_check.setter
def license_check ( self , value ) :
self . _license_check = value
2018-10-15 13:05:49 +03:00
async def _library_check ( self ) :
2015-02-11 15:31:21 +02:00
"""
Checks for missing shared library dependencies in the IOU image .
"""
try :
2018-10-15 13:05:49 +03:00
output = await gns3server . utils . asyncio . subprocess_check_output ( " ldd " , self . _path )
2018-09-11 16:06:01 +03:00
except ( OSError , subprocess . SubprocessError ) as e :
2021-04-13 12:07:58 +03:00
log . warning ( f " Could not determine the shared library dependencies for { self . _path } : { e } " )
2015-02-11 15:31:21 +02:00
return
2019-01-17 13:01:58 +02:00
p = re . compile ( r " ([ \ . \ w]+) \ s=> \ s+not found " )
2015-02-16 18:20:07 +02:00
missing_libs = p . findall ( output )
2015-02-11 15:31:21 +02:00
if missing_libs :
2021-04-13 12:16:50 +03:00
raise IOUError (
" The following shared library dependencies cannot be found for IOU image {} : {} " . format (
self . _path , " , " . join ( missing_libs )
)
)
2015-02-11 15:31:21 +02:00
2018-10-15 13:05:49 +03:00
async def _check_iou_licence ( self ) :
2015-03-12 02:59:57 +02:00
"""
Checks for a valid IOU key in the iourc file ( paranoid mode ) .
"""
2018-11-14 10:24:30 +02:00
# license check is sent by the controller
if self . license_check is False :
return
2017-07-20 17:19:20 +03:00
try :
2018-11-14 10:24:30 +02:00
# we allow license check to be disabled server wide
2021-04-12 10:32:23 +03:00
server_wide_license_check = self . _manager . config . settings . IOU . license_check
2017-07-20 17:19:20 +03:00
except ValueError :
raise IOUError ( " Invalid licence check setting " )
2018-11-14 10:24:30 +02:00
if server_wide_license_check is False :
2019-05-20 06:51:24 +03:00
log . warning ( " License check is explicitly disabled on this server " )
2015-03-14 21:16:27 +02:00
return
2015-03-12 02:59:57 +02:00
config = configparser . ConfigParser ( )
try :
2021-04-13 12:07:58 +03:00
log . info ( f " Checking IOU license in ' { self . iourc_path } ' " )
2015-04-25 20:58:34 +03:00
with open ( self . iourc_path , encoding = " utf-8 " ) as f :
2015-03-12 02:59:57 +02:00
config . read_file ( f )
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not open iourc file { self . iourc_path } : { e } " )
2015-03-12 02:59:57 +02:00
except configparser . Error as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not parse iourc file { self . iourc_path } : { e } " )
2015-04-27 06:15:15 +03:00
except UnicodeDecodeError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Non ascii characters in iourc file { self . iourc_path } , please remove them: { e } " )
2015-03-12 02:59:57 +02:00
if " license " not in config :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " License section not found in iourc file { self . iourc_path } " )
2015-03-12 02:59:57 +02:00
hostname = socket . gethostname ( )
2017-10-24 11:07:32 +03:00
if len ( hostname ) > 15 :
2021-04-13 12:07:58 +03:00
log . warning ( f " Older IOU images may not boot because hostname ' { hostname } ' length is above 15 characters " )
2015-03-12 02:59:57 +02:00
if hostname not in config [ " license " ] :
2021-04-13 12:16:50 +03:00
raise IOUError ( f ' Hostname " { hostname } " not found in iourc file { self . iourc_path } ' )
2015-03-12 02:59:57 +02:00
user_ioukey = config [ " license " ] [ hostname ]
2021-04-13 12:16:50 +03:00
if user_ioukey [ - 1 : ] != " ; " :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " IOU key not ending with ; in iourc file { self . iourc_path } " )
2015-03-12 02:59:57 +02:00
if len ( user_ioukey ) != 17 :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " IOU key length is not 16 characters in iourc file { self . iourc_path } " )
2015-03-12 02:59:57 +02:00
user_ioukey = user_ioukey [ : 16 ]
2015-03-16 12:52:22 +02:00
# We can't test this because it's mean distributing a valid licence key
# in tests or generating one
2015-03-17 20:00:14 +02:00
if not hasattr ( sys , " _called_from_test " ) :
2015-03-16 12:52:22 +02:00
try :
2018-10-15 13:05:49 +03:00
hostid = ( await gns3server . utils . asyncio . subprocess_check_output ( " hostid " ) ) . strip ( )
2015-03-16 12:52:22 +02:00
except FileNotFoundError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not find hostid: { e } " )
2018-09-11 16:06:01 +03:00
except ( OSError , subprocess . SubprocessError ) as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not execute hostid: { e } " )
2015-03-16 12:52:22 +02:00
try :
ioukey = int ( hostid , 16 )
except ValueError :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Invalid hostid detected: { hostid } " )
2015-03-16 12:52:22 +02:00
for x in hostname :
ioukey + = ord ( x )
2021-04-13 12:16:50 +03:00
pad1 = b " \x4B \x58 \x21 \x81 \x56 \x7B \x0D \xF3 \x21 \x43 \x9B \x7E \xAC \x1D \xE6 \x8A "
pad2 = b " \x80 " + 39 * b " \0 "
ioukey = hashlib . md5 ( pad1 + pad2 + struct . pack ( " !I " , ioukey ) + pad1 ) . hexdigest ( ) [ : 16 ]
2015-03-16 12:52:22 +02:00
if ioukey != user_ioukey :
2021-04-13 12:16:50 +03:00
raise IOUError (
" Invalid IOU license key {} detected in iourc file {} for host {} " . format (
user_ioukey , self . iourc_path , hostname
)
)
2015-03-12 02:59:57 +02:00
2016-06-10 19:26:01 +03:00
def _nvram_file ( self ) :
"""
Path to the nvram file
"""
2021-04-13 12:07:58 +03:00
return os . path . join ( self . working_dir , f " nvram_ { self . application_id : 05d } " )
2016-06-10 19:26:01 +03:00
2015-06-07 00:15:03 +03:00
def _push_configs_to_nvram ( self ) :
"""
Push the startup - config and private - config content to the NVRAM .
"""
startup_config_content = self . startup_config_content
if startup_config_content :
2016-06-10 19:26:01 +03:00
nvram_file = self . _nvram_file ( )
2015-06-07 00:15:03 +03:00
try :
if not os . path . exists ( nvram_file ) :
open ( nvram_file , " a " ) . close ( )
2015-06-08 19:07:54 +03:00
nvram_content = None
else :
with open ( nvram_file , " rb " ) as file :
nvram_content = file . read ( )
2015-06-07 00:15:03 +03:00
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Cannot read nvram file { nvram_file } : { e } " )
2015-06-07 00:15:03 +03:00
startup_config_content = startup_config_content . encode ( " utf-8 " )
private_config_content = self . private_config_content
if private_config_content is not None :
private_config_content = private_config_content . encode ( " utf-8 " )
try :
nvram_content = nvram_import ( nvram_content , startup_config_content , private_config_content , self . nvram )
except ValueError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Cannot push configs to nvram { nvram_file } : { e } " )
2015-06-07 00:15:03 +03:00
try :
with open ( nvram_file , " wb " ) as file :
file . write ( nvram_content )
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Cannot write nvram file { nvram_file } : { e } " )
2015-06-07 00:15:03 +03:00
2018-10-15 13:05:49 +03:00
async def start ( self ) :
2015-02-11 15:31:21 +02:00
"""
Starts the IOU process .
"""
self . _check_requirements ( )
if not self . is_running ( ) :
2018-10-15 13:05:49 +03:00
await self . _library_check ( )
2015-02-13 21:57:20 +02:00
2015-05-13 11:16:24 +03:00
try :
self . _rename_nvram_file ( )
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not rename nvram files: { e } " )
2015-02-11 15:31:21 +02:00
2015-02-17 17:40:45 +02:00
iourc_path = self . iourc_path
2015-05-14 01:05:54 +03:00
if not iourc_path :
2019-05-20 06:51:24 +03:00
raise IOUError ( " Could not find an iourc file (IOU license), please configure an IOU license " )
2015-03-26 23:11:36 +02:00
if not os . path . isfile ( iourc_path ) :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " The iourc path ' { iourc_path } ' is not a regular file " )
2015-02-11 15:31:21 +02:00
2018-10-15 13:05:49 +03:00
await self . _check_iou_licence ( )
await self . _start_ubridge ( )
2015-02-11 15:31:21 +02:00
self . _create_netmap_config ( )
2017-11-16 11:52:19 +02:00
if self . use_default_iou_values :
# make sure we have the default nvram amount to correctly push the configs
2018-10-15 13:05:49 +03:00
await self . update_default_iou_values ( )
2015-06-07 00:15:03 +03:00
self . _push_configs_to_nvram ( )
2015-10-13 00:57:37 +03:00
# check if there is enough RAM to run
self . check_available_ram ( self . ram )
2017-01-10 16:50:35 +02:00
self . _nvram_watcher = FileWatcher ( self . _nvram_file ( ) , self . _nvram_changed , delay = 2 )
2016-06-10 19:26:01 +03:00
2015-02-11 15:31:21 +02:00
# created a environment variable pointing to the iourc file.
env = os . environ . copy ( )
2019-05-20 06:51:24 +03:00
if " IOURC " not in os . environ and iourc_path :
2015-02-17 17:40:45 +02:00
env [ " IOURC " ] = iourc_path
2018-12-18 06:42:18 +02:00
# create a symbolic link to the image to avoid IOU error "failed code signing checks"
# on newer images, see https://github.com/GNS3/gns3-server/issues/1484
try :
symlink = os . path . join ( self . working_dir , os . path . basename ( self . path ) )
2019-02-18 18:09:59 +02:00
if os . path . islink ( symlink ) :
os . unlink ( symlink )
os . symlink ( self . path , symlink )
2018-12-18 06:42:18 +02:00
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not create symbolic link: { e } " )
2018-12-18 06:42:18 +02:00
2018-10-15 13:05:49 +03:00
command = await self . _build_command ( )
2015-02-11 15:31:21 +02:00
try :
2021-04-13 12:07:58 +03:00
log . info ( f " Starting IOU: { command } " )
2021-04-13 12:16:50 +03:00
self . command_line = " " . join ( command )
2018-10-15 13:05:49 +03:00
self . _iou_process = await asyncio . create_subprocess_exec (
2016-11-08 11:21:20 +02:00
* command ,
stdout = asyncio . subprocess . PIPE ,
stdin = asyncio . subprocess . PIPE ,
stderr = subprocess . STDOUT ,
cwd = self . working_dir ,
2021-04-13 12:16:50 +03:00
env = env ,
)
2021-04-13 12:07:58 +03:00
log . info ( f " IOU instance { self . _id } started PID= { self . _iou_process . pid } " )
2015-02-11 15:31:21 +02:00
self . _started = True
2015-03-04 17:01:56 +02:00
self . status = " started "
2015-12-06 03:24:08 +02:00
callback = functools . partial ( self . _termination_callback , " IOU " )
gns3server . utils . asyncio . monitor_process ( self . _iou_process , callback )
2015-02-11 15:31:21 +02:00
except FileNotFoundError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not start IOU: { e } : 32-bit binary support is probably not installed " )
2015-02-11 15:31:21 +02:00
except ( OSError , subprocess . SubprocessError ) as e :
iou_stdout = self . read_iou_stdout ( )
2021-04-13 12:07:58 +03:00
log . error ( f " Could not start IOU { self . _path } : { e } \n { iou_stdout } " )
raise IOUError ( f " Could not start IOU { self . _path } : { e } \n { iou_stdout } " )
2015-02-11 15:31:21 +02:00
2020-07-26 11:57:18 +03:00
await self . start_console ( )
2016-11-06 12:27:49 +02:00
# configure networking support
2018-10-15 13:05:49 +03:00
await self . _networking ( )
2016-11-06 12:27:49 +02:00
2020-07-26 11:57:18 +03:00
async def start_console ( self ) :
"""
Start the Telnet server to provide console access .
"""
if self . console and self . console_type == " telnet " :
2021-04-13 12:16:50 +03:00
server = AsyncioTelnetServer (
reader = self . _iou_process . stdout , writer = self . _iou_process . stdin , binary = True , echo = True
)
2020-07-26 11:57:18 +03:00
try :
2021-04-13 12:16:50 +03:00
self . _telnet_server = await asyncio . start_server (
server . run , self . _manager . port_manager . console_host , self . console
)
2020-07-26 11:57:18 +03:00
except OSError as e :
await self . stop ( )
raise IOUError (
2021-04-13 12:16:50 +03:00
" Could not start Telnet server on socket {} : {} : {} " . format (
self . _manager . port_manager . console_host , self . console , e
)
)
2020-07-26 11:57:18 +03:00
async def reset_console ( self ) :
"""
Reset the console .
"""
if self . _telnet_server :
self . _telnet_server . close ( )
await self . _telnet_server . wait_closed ( )
self . _telnet_server = None
await self . start_console ( )
2018-08-25 10:10:47 +03:00
@locking
2018-10-15 13:05:49 +03:00
async def _networking ( self ) :
2016-11-06 12:27:49 +02:00
"""
Configures the IOL bridge in uBridge .
"""
2021-04-13 12:07:58 +03:00
bridge_name = f " IOL-BRIDGE- { self . application_id + 512 } "
2016-11-06 12:27:49 +02:00
try :
# delete any previous bridge if it exists
2021-04-13 12:07:58 +03:00
await self . _ubridge_send ( f " iol_bridge delete { bridge_name } " )
2016-11-06 12:27:49 +02:00
except UbridgeError :
pass
2021-04-13 12:07:58 +03:00
await self . _ubridge_send ( f " iol_bridge create { bridge_name } { self . application_id + 512 } " )
2016-11-06 12:27:49 +02:00
bay_id = 0
for adapter in self . _adapters :
unit_id = 0
for unit in adapter . ports . keys ( ) :
nio = adapter . get_nio ( unit )
if nio and isinstance ( nio , NIOUDP ) :
2021-04-13 12:16:50 +03:00
await self . _ubridge_send (
" iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport} " . format (
name = bridge_name ,
iol_id = self . application_id ,
bay = bay_id ,
unit = unit_id ,
lport = nio . lport ,
rhost = nio . rhost ,
rport = nio . rport ,
)
)
2016-11-06 12:27:49 +02:00
if nio . capturing :
2021-04-13 12:16:50 +03:00
await self . _ubridge_send (
' iol_bridge start_capture {name} " {output_file} " {data_link_type} ' . format (
name = bridge_name ,
output_file = nio . pcap_output_file ,
data_link_type = re . sub ( r " ^DLT_ " , " " , nio . pcap_data_link_type ) ,
)
)
2016-11-06 12:27:49 +02:00
2018-10-15 13:05:49 +03:00
await self . _ubridge_apply_filters ( bay_id , unit_id , nio . filters )
2016-11-06 12:27:49 +02:00
unit_id + = 1
bay_id + = 1
2021-04-13 12:07:58 +03:00
await self . _ubridge_send ( f " iol_bridge start { bridge_name } " )
2015-02-11 18:11:18 +02:00
2015-12-06 03:24:08 +02:00
def _termination_callback ( self , process_name , returncode ) :
2015-03-04 17:01:56 +02:00
"""
2015-05-13 22:53:42 +03:00
Called when the process has stopped .
2015-03-04 17:01:56 +02:00
: param returncode : Process returncode
"""
2015-05-13 22:53:42 +03:00
2015-03-04 17:01:56 +02:00
self . _terminate_process_iou ( )
2015-07-04 23:08:03 +03:00
if returncode != 0 :
2017-03-20 10:46:07 +02:00
if returncode == - 11 :
2021-04-13 12:16:50 +03:00
message = ' IOU VM " {} " process has stopped with return code: {} (segfault). This could be an issue with the IOU image, using a different image may fix this. \n {} ' . format (
self . name , returncode , self . read_iou_stdout ( )
)
2016-10-28 12:25:14 +03:00
else :
2021-04-13 12:16:50 +03:00
message = (
f ' IOU VM " { self . name } " process has stopped with return code: { returncode } \n { self . read_iou_stdout ( ) } '
)
2017-10-22 12:28:54 +03:00
log . warning ( message )
2016-10-28 12:25:14 +03:00
self . project . emit ( " log.error " , { " message " : message } )
2016-11-08 11:21:20 +02:00
if self . _telnet_server :
self . _telnet_server . close ( )
self . _telnet_server = None
2015-03-04 17:01:56 +02:00
2015-02-13 21:57:20 +02:00
def _rename_nvram_file ( self ) :
"""
2015-04-08 20:17:34 +03:00
Before starting the VM , rename the nvram and vlan . dat files with the correct IOU application identifier .
2015-02-13 21:57:20 +02:00
"""
2016-06-10 19:26:01 +03:00
destination = self . _nvram_file ( )
2015-10-07 17:44:50 +03:00
for file_path in glob . glob ( os . path . join ( glob . escape ( self . working_dir ) , " nvram_* " ) ) :
2015-02-13 21:57:20 +02:00
shutil . move ( file_path , destination )
2021-04-13 12:07:58 +03:00
destination = os . path . join ( self . working_dir , f " vlan.dat- { self . application_id : 05d } " )
2015-10-07 17:44:50 +03:00
for file_path in glob . glob ( os . path . join ( glob . escape ( self . working_dir ) , " vlan.dat-* " ) ) :
2015-03-05 18:00:25 +02:00
shutil . move ( file_path , destination )
2015-02-13 21:57:20 +02:00
2018-10-15 13:05:49 +03:00
async def stop ( self ) :
2015-02-11 15:31:21 +02:00
"""
Stops the IOU process .
"""
2018-10-15 13:05:49 +03:00
await self . _stop_ubridge ( )
2016-06-10 19:26:01 +03:00
if self . _nvram_watcher :
self . _nvram_watcher . close ( )
self . _nvram_watcher = None
2016-11-08 11:21:20 +02:00
if self . _telnet_server :
self . _telnet_server . close ( )
self . _telnet_server = None
2015-02-16 18:40:13 +02:00
2016-11-08 11:21:20 +02:00
if self . is_running ( ) :
2015-02-11 18:11:18 +02:00
self . _terminate_process_iou ( )
2015-02-26 12:29:57 +02:00
if self . _iou_process . returncode is None :
try :
2018-10-15 13:05:49 +03:00
await gns3server . utils . asyncio . wait_for_process_termination ( self . _iou_process , timeout = 3 )
2015-02-26 12:29:57 +02:00
except asyncio . TimeoutError :
if self . _iou_process . returncode is None :
2021-04-13 12:07:58 +03:00
log . warning ( f " IOU process { self . _iou_process . pid } is still running... killing it " )
2016-07-04 15:46:06 +03:00
try :
self . _iou_process . kill ( )
except ProcessLookupError :
pass
2015-02-11 15:31:21 +02:00
self . _iou_process = None
2015-02-11 18:11:18 +02:00
2019-03-18 13:05:40 +02:00
try :
symlink = os . path . join ( self . working_dir , os . path . basename ( self . path ) )
if os . path . islink ( symlink ) :
os . unlink ( symlink )
except OSError as e :
2021-04-13 12:07:58 +03:00
log . warning ( f " Could not delete symbolic link: { e } " )
2019-03-18 13:05:40 +02:00
2015-03-25 06:04:48 +02:00
self . _started = False
2016-05-22 03:58:28 +03:00
self . save_configs ( )
2015-02-11 15:31:21 +02:00
2015-02-11 18:11:18 +02:00
def _terminate_process_iou ( self ) :
2015-04-08 20:17:34 +03:00
"""
Terminate the IOU process if running
"""
2015-02-11 15:31:21 +02:00
2015-03-04 17:01:56 +02:00
if self . _iou_process :
2021-04-13 12:07:58 +03:00
log . info ( f ' Stopping IOU process for IOU VM " { self . name } " PID= { self . _iou_process . pid } ' )
2015-03-04 17:01:56 +02:00
try :
self . _iou_process . terminate ( )
# Sometime the process can already be dead when we garbage collect
except ProcessLookupError :
pass
self . _started = False
self . status = " stopped "
2015-02-11 15:31:21 +02:00
2018-10-15 13:05:49 +03:00
async def reload ( self ) :
2015-02-11 15:31:21 +02:00
"""
2015-04-08 20:17:34 +03:00
Reloads the IOU process ( stop & start ) .
2015-02-11 15:31:21 +02:00
"""
2018-10-15 13:05:49 +03:00
await self . stop ( )
await self . start ( )
2015-02-11 15:31:21 +02:00
def is_running ( self ) :
"""
Checks if the IOU process is running
: returns : True or False
"""
2015-03-25 06:04:48 +02:00
if self . _iou_process and self . _iou_process . returncode is None :
2015-02-11 15:31:21 +02:00
return True
return False
2018-03-24 14:11:21 +03:00
@BaseNode.console_type.setter
def console_type ( self , new_console_type ) :
"""
Sets the console type for this IOU VM .
: param new_console_type : console type ( string )
"""
if self . is_running ( ) and self . console_type != new_console_type :
2021-04-13 12:07:58 +03:00
raise IOUError ( f ' " { self . _name } " must be stopped to change the console type to { new_console_type } ' )
2018-03-24 14:11:21 +03:00
super ( IOUVM , IOUVM ) . console_type . __set__ ( self , new_console_type )
2015-02-11 15:31:21 +02:00
def _create_netmap_config ( self ) :
"""
Creates the NETMAP file .
"""
netmap_path = os . path . join ( self . working_dir , " NETMAP " )
try :
2015-04-25 20:58:34 +03:00
with open ( netmap_path , " w " , encoding = " utf-8 " ) as f :
2015-02-11 15:31:21 +02:00
for bay in range ( 0 , 16 ) :
for unit in range ( 0 , 4 ) :
2021-04-13 12:16:50 +03:00
f . write (
" {ubridge_id} : {bay} / {unit} {iou_id:>5d} : {bay} / {unit} \n " . format (
ubridge_id = str ( self . application_id + 512 ) ,
bay = bay ,
unit = unit ,
iou_id = self . application_id ,
)
)
log . info ( " IOU {name} [id= {id} ]: NETMAP file created " . format ( name = self . _name , id = self . _id ) )
2015-02-11 15:31:21 +02:00
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not create { netmap_path } : { e } " )
2015-02-11 15:31:21 +02:00
2018-10-15 13:05:49 +03:00
async def _build_command ( self ) :
2015-02-11 15:31:21 +02:00
"""
Command to start the IOU process .
( to be passed to subprocess . Popen ( ) )
2015-04-08 20:17:34 +03:00
2015-02-11 15:31:21 +02:00
IOU command line :
Usage : < image > [ options ] < application id >
< image > : unix - js - m | unix - is - m | unix - i - m | . . .
< application id > : instance identifier ( 0 < id < = 1024 )
Options :
- e < n > Number of Ethernet interfaces ( default 2 )
- s < n > Number of Serial interfaces ( default 2 )
- n < n > Size of nvram in Kb ( default 64 KB )
- b < string > IOS debug string
- c < name > Configuration file name
- d Generate debug information
- t Netio message trace
- q Suppress informational messages
- h Display this help
- C Turn off use of host clock
- m < n > Megabytes of router memory ( default 256 MB )
- L Disable local console , use remote console
- l Enable Layer 1 keepalive messages
- u < n > UDP port base for distributed networks
- R Ignore options from the IOURC file
- U Disable unix : file system location
- W Disable watchdog timer
- N Ignore the NETMAP file
"""
2015-02-12 16:20:47 +02:00
command = [ self . _path ]
2015-02-11 15:31:21 +02:00
if len ( self . _ethernet_adapters ) != 2 :
command . extend ( [ " -e " , str ( len ( self . _ethernet_adapters ) ) ] )
if len ( self . _serial_adapters ) != 2 :
command . extend ( [ " -s " , str ( len ( self . _serial_adapters ) ) ] )
if not self . use_default_iou_values :
command . extend ( [ " -n " , str ( self . _nvram ) ] )
command . extend ( [ " -m " , str ( self . _ram ) ] )
2015-02-13 23:16:43 +02:00
2015-06-07 00:15:03 +03:00
# do not let IOU create the NVRAM anymore
2021-04-13 12:16:50 +03:00
# startup_config_file = self.startup_config_file
2015-06-17 18:11:25 +03:00
# if startup_config_file:
2015-06-07 00:15:03 +03:00
# command.extend(["-c", os.path.basename(startup_config_file)])
2015-02-11 15:31:21 +02:00
if self . _l1_keepalives :
2018-10-15 13:05:49 +03:00
await self . _enable_l1_keepalives ( command )
2015-02-11 15:31:21 +02:00
command . extend ( [ str ( self . application_id ) ] )
return command
def read_iou_stdout ( self ) :
"""
Reads the standard output of the IOU process .
Only use when the process has been stopped or has crashed .
"""
output = " "
if self . _iou_stdout_file :
try :
2015-04-25 20:58:34 +03:00
with open ( self . _iou_stdout_file , " rb " ) as file :
output = file . read ( ) . decode ( " utf-8 " , errors = " replace " )
2015-02-11 15:31:21 +02:00
except OSError as e :
2021-04-13 12:07:58 +03:00
log . warning ( f " could not read { self . _iou_stdout_file } : { e } " )
2015-02-11 15:31:21 +02:00
return output
2017-07-17 12:21:54 +03:00
@property
def adapters ( self ) :
return self . _adapters
2015-02-12 22:02:52 +02:00
@property
def ethernet_adapters ( self ) :
"""
2015-04-08 20:17:34 +03:00
Returns the number of Ethernet adapters for this IOU VM .
2015-02-12 22:02:52 +02:00
: returns : number of adapters
"""
return len ( self . _ethernet_adapters )
@ethernet_adapters.setter
def ethernet_adapters ( self , ethernet_adapters ) :
"""
2015-04-08 20:17:34 +03:00
Sets the number of Ethernet adapters for this IOU VM .
2015-02-12 22:02:52 +02:00
: param ethernet_adapters : number of adapters
"""
self . _ethernet_adapters . clear ( )
for _ in range ( 0 , ethernet_adapters ) :
2015-02-13 21:57:20 +02:00
self . _ethernet_adapters . append ( EthernetAdapter ( interfaces = 4 ) )
2015-02-12 22:02:52 +02:00
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: number of Ethernet adapters changed to {adapters} ' . format (
name = self . _name , id = self . _id , adapters = len ( self . _ethernet_adapters )
)
)
2015-02-12 22:02:52 +02:00
2015-02-16 21:08:04 +02:00
self . _adapters = self . _ethernet_adapters + self . _serial_adapters
2015-02-12 22:02:52 +02:00
@property
def serial_adapters ( self ) :
"""
2015-04-08 20:17:34 +03:00
Returns the number of Serial adapters for this IOU VM .
2015-02-12 22:02:52 +02:00
: returns : number of adapters
"""
return len ( self . _serial_adapters )
@serial_adapters.setter
def serial_adapters ( self , serial_adapters ) :
"""
2015-04-08 20:17:34 +03:00
Sets the number of Serial adapters for this IOU VM .
2015-02-12 22:02:52 +02:00
: param serial_adapters : number of adapters
"""
self . _serial_adapters . clear ( )
for _ in range ( 0 , serial_adapters ) :
2015-02-13 21:57:20 +02:00
self . _serial_adapters . append ( SerialAdapter ( interfaces = 4 ) )
2015-02-12 22:02:52 +02:00
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: number of Serial adapters changed to {adapters} ' . format (
name = self . _name , id = self . _id , adapters = len ( self . _serial_adapters )
)
)
2015-02-12 22:02:52 +02:00
2015-02-16 21:08:04 +02:00
self . _adapters = self . _ethernet_adapters + self . _serial_adapters
2015-02-12 23:28:12 +02:00
2018-10-15 13:05:49 +03:00
async def adapter_add_nio_binding ( self , adapter_number , port_number , nio ) :
2015-02-12 23:28:12 +02:00
"""
2018-10-27 10:47:17 +03:00
Adds an adapter NIO binding .
2015-04-08 20:17:34 +03:00
: param adapter_number : adapter number
: param port_number : port number
2015-02-16 21:08:04 +02:00
: param nio : NIO instance to add to the adapter / port
2015-02-12 23:28:12 +02:00
"""
try :
2015-02-16 21:08:04 +02:00
adapter = self . _adapters [ adapter_number ]
2015-02-12 23:28:12 +02:00
except IndexError :
2021-04-13 12:16:50 +03:00
raise IOUError (
' Adapter {adapter_number} does not exist for IOU " {name} " ' . format (
name = self . _name , adapter_number = adapter_number
)
)
2015-02-16 11:18:03 +02:00
if not adapter . port_exists ( port_number ) :
2021-04-13 12:16:50 +03:00
raise IOUError (
" Port {port_number} does not exist on adapter {adapter} " . format (
adapter = adapter , port_number = port_number
)
)
2015-02-16 11:18:03 +02:00
adapter . add_nio ( port_number , nio )
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: {nio} added to {adapter_number} / {port_number} ' . format (
name = self . _name , id = self . _id , nio = nio , adapter_number = adapter_number , port_number = port_number
)
)
2016-11-06 12:27:49 +02:00
2016-12-14 13:01:34 +02:00
if self . ubridge :
2021-04-13 12:07:58 +03:00
bridge_name = f " IOL-BRIDGE- { self . application_id + 512 } "
2021-04-13 12:16:50 +03:00
await self . _ubridge_send (
" iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport} " . format (
name = bridge_name ,
iol_id = self . application_id ,
bay = adapter_number ,
unit = port_number ,
lport = nio . lport ,
rhost = nio . rhost ,
rport = nio . rport ,
)
)
2018-10-15 13:05:49 +03:00
await self . _ubridge_apply_filters ( adapter_number , port_number , nio . filters )
2017-07-17 12:21:54 +03:00
2018-10-15 13:05:49 +03:00
async def adapter_update_nio_binding ( self , adapter_number , port_number , nio ) :
2017-07-17 12:21:54 +03:00
"""
2018-10-27 10:47:17 +03:00
Updates an adapter NIO binding .
2017-07-17 12:21:54 +03:00
: param adapter_number : adapter number
: param port_number : port number
: param nio : NIO instance to add to the adapter
"""
if self . ubridge :
2018-10-15 13:05:49 +03:00
await self . _ubridge_apply_filters ( adapter_number , port_number , nio . filters )
2017-07-17 12:21:54 +03:00
2018-10-15 13:05:49 +03:00
async def _ubridge_apply_filters ( self , adapter_number , port_number , filters ) :
2017-07-17 12:21:54 +03:00
"""
Apply filter like rate limiting
: param adapter_number : adapter number
: param port_number : port number
: param filters : Array of filter dictionnary
"""
2021-04-13 12:07:58 +03:00
bridge_name = f " IOL-BRIDGE- { self . application_id + 512 } "
2021-04-13 12:16:50 +03:00
location = " {bridge_name} {bay} {unit} " . format ( bridge_name = bridge_name , bay = adapter_number , unit = port_number )
await self . _ubridge_send ( " iol_bridge reset_packet_filters " + location )
2017-07-17 12:21:54 +03:00
for filter in self . _build_filter_list ( filters ) :
2021-04-13 12:16:50 +03:00
cmd = " iol_bridge add_packet_filter {} {} " . format ( location , filter )
2018-10-15 13:05:49 +03:00
await self . _ubridge_send ( cmd )
2015-02-12 23:28:12 +02:00
2018-10-15 13:05:49 +03:00
async def adapter_remove_nio_binding ( self , adapter_number , port_number ) :
2015-02-12 23:28:12 +02:00
"""
2015-04-08 20:17:34 +03:00
Removes an adapter NIO binding .
: param adapter_number : adapter number
: param port_number : port number
2018-10-27 10:47:17 +03:00
2015-02-12 23:28:12 +02:00
: returns : NIO instance
"""
try :
2015-02-16 21:08:04 +02:00
adapter = self . _adapters [ adapter_number ]
2015-02-12 23:28:12 +02:00
except IndexError :
2021-04-13 12:16:50 +03:00
raise IOUError (
' Adapter {adapter_number} does not exist on IOU " {name} " ' . format (
name = self . _name , adapter_number = adapter_number
)
)
2015-02-16 11:18:03 +02:00
if not adapter . port_exists ( port_number ) :
2021-04-13 12:16:50 +03:00
raise IOUError (
" Port {port_number} does not exist on adapter {adapter} " . format (
adapter = adapter , port_number = port_number
)
)
2015-02-16 11:18:03 +02:00
nio = adapter . get_nio ( port_number )
2015-02-24 04:00:34 +02:00
if isinstance ( nio , NIOUDP ) :
2015-03-22 01:19:12 +02:00
self . manager . port_manager . release_udp_port ( nio . lport , self . _project )
2015-02-16 11:18:03 +02:00
adapter . remove_nio ( port_number )
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: {nio} removed from {adapter_number} / {port_number} ' . format (
name = self . _name , id = self . _id , nio = nio , adapter_number = adapter_number , port_number = port_number
)
)
2016-11-06 12:27:49 +02:00
2016-12-14 13:01:34 +02:00
if self . ubridge :
2021-04-13 12:07:58 +03:00
bridge_name = f " IOL-BRIDGE- { self . application_id + 512 } "
2021-04-13 12:16:50 +03:00
await self . _ubridge_send (
" iol_bridge delete_nio_udp {name} {bay} {unit} " . format (
name = bridge_name , bay = adapter_number , unit = port_number
)
)
2016-11-06 12:27:49 +02:00
2015-02-12 23:28:12 +02:00
return nio
2015-02-13 17:57:35 +02:00
2018-10-27 10:47:17 +03:00
def get_nio ( self , adapter_number , port_number ) :
"""
Gets an adapter NIO binding .
: param adapter_number : adapter number
: param port_number : port number
: returns : NIO instance
"""
try :
adapter = self . _adapters [ adapter_number ]
except IndexError :
2021-04-13 12:16:50 +03:00
raise IOUError (
' Adapter {adapter_number} does not exist on IOU " {name} " ' . format (
name = self . _name , adapter_number = adapter_number
)
)
2018-10-27 10:47:17 +03:00
if not adapter . port_exists ( port_number ) :
2021-04-13 12:16:50 +03:00
raise IOUError (
" Port {port_number} does not exist on adapter {adapter} " . format (
adapter = adapter , port_number = port_number
)
)
2018-10-27 10:47:17 +03:00
nio = adapter . get_nio ( port_number )
if not nio :
2021-04-13 12:16:50 +03:00
raise IOUError (
" NIO {port_number} does not exist on adapter {adapter} " . format ( adapter = adapter , port_number = port_number )
)
2018-10-27 10:47:17 +03:00
return nio
2015-02-13 17:57:35 +02:00
@property
def l1_keepalives ( self ) :
"""
Returns either layer 1 keepalive messages option is enabled or disabled .
2015-04-08 20:17:34 +03:00
2015-02-13 17:57:35 +02:00
: returns : boolean
"""
return self . _l1_keepalives
@l1_keepalives.setter
def l1_keepalives ( self , state ) :
"""
Enables or disables layer 1 keepalive messages .
2015-04-08 20:17:34 +03:00
2015-02-13 17:57:35 +02:00
: param state : boolean
"""
self . _l1_keepalives = state
if state :
2021-04-13 12:07:58 +03:00
log . info ( f ' IOU " { self . _name } " [ { self . _id } ]: has activated layer 1 keepalive messages ' )
2015-02-13 17:57:35 +02:00
else :
2021-04-13 12:07:58 +03:00
log . info ( f ' IOU " { self . _name } " [ { self . _id } ]: has deactivated layer 1 keepalive messages ' )
2015-02-13 17:57:35 +02:00
2018-10-15 13:05:49 +03:00
async def _enable_l1_keepalives ( self , command ) :
2015-02-13 17:57:35 +02:00
"""
Enables L1 keepalive messages if supported .
2015-04-08 20:17:34 +03:00
2015-02-13 17:57:35 +02:00
: param command : command line
"""
env = os . environ . copy ( )
2015-03-12 02:59:57 +02:00
if " IOURC " not in os . environ :
env [ " IOURC " ] = self . iourc_path
2015-02-13 17:57:35 +02:00
try :
2021-04-13 12:16:50 +03:00
output = await gns3server . utils . asyncio . subprocess_check_output (
self . _path , " -h " , cwd = self . working_dir , env = env , stderr = True
)
2019-01-17 13:01:58 +02:00
if re . search ( r " -l \ s+Enable Layer 1 keepalive messages " , output ) :
2015-02-13 17:57:35 +02:00
command . extend ( [ " -l " ] )
else :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " layer 1 keepalive messages are not supported by { os . path . basename ( self . _path ) } " )
2015-02-13 17:57:35 +02:00
except ( OSError , subprocess . SubprocessError ) as e :
2021-04-13 12:16:50 +03:00
log . warning (
f " could not determine if layer 1 keepalive messages are supported by { os . path . basename ( self . _path ) } : { e } "
)
2015-02-13 23:16:43 +02:00
@property
2015-06-07 00:15:03 +03:00
def startup_config_content ( self ) :
"""
Returns the content of the current startup - config file .
"""
config_file = self . startup_config_file
if config_file is None :
return None
try :
with open ( config_file , " rb " ) as f :
return f . read ( ) . decode ( " utf-8 " , errors = " replace " )
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Can ' t read startup-config file ' { config_file } ' : { e } " )
2015-06-07 00:15:03 +03:00
@startup_config_content.setter
def startup_config_content ( self , startup_config ) :
"""
Update the startup config
: param startup_config : content of the startup configuration file
"""
try :
startup_config_path = os . path . join ( self . working_dir , " startup-config.cfg " )
if startup_config is None :
2021-04-13 12:16:50 +03:00
startup_config = " "
2015-06-07 00:15:03 +03:00
# We disallow erasing the startup config file
if len ( startup_config ) == 0 and os . path . exists ( startup_config_path ) :
return
2021-04-13 12:16:50 +03:00
with open ( startup_config_path , " w+ " , encoding = " utf-8 " ) as f :
2015-06-07 00:15:03 +03:00
if len ( startup_config ) == 0 :
2021-04-13 12:16:50 +03:00
f . write ( " " )
2015-06-07 00:15:03 +03:00
else :
startup_config = startup_config . replace ( " % h " , self . _name )
f . write ( startup_config )
2016-06-10 02:47:45 +03:00
2021-04-13 12:07:58 +03:00
vlan_file = os . path . join ( self . working_dir , f " vlan.dat- { self . application_id : 05d } " )
2016-06-10 02:47:45 +03:00
if os . path . exists ( vlan_file ) :
try :
os . remove ( vlan_file )
except OSError as e :
2021-04-13 12:07:58 +03:00
log . error ( f " Could not delete VLAN file ' { vlan_file } ' : { e } " )
2016-06-10 02:47:45 +03:00
2015-06-07 00:15:03 +03:00
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Can ' t write startup-config file ' { startup_config_path } ' : { e } " )
2015-06-07 00:15:03 +03:00
@property
def private_config_content ( self ) :
2015-04-08 20:17:34 +03:00
"""
2015-06-07 00:15:03 +03:00
Returns the content of the current private - config file .
2015-04-08 20:17:34 +03:00
"""
2015-02-13 23:16:43 +02:00
2015-06-07 00:15:03 +03:00
config_file = self . private_config_file
2015-02-13 23:16:43 +02:00
if config_file is None :
return None
try :
2015-04-25 20:58:34 +03:00
with open ( config_file , " rb " ) as f :
return f . read ( ) . decode ( " utf-8 " , errors = " replace " )
2015-02-13 23:16:43 +02:00
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Can ' t read private-config file ' { config_file } ' : { e } " )
2015-02-13 23:16:43 +02:00
2015-06-07 00:15:03 +03:00
@private_config_content.setter
def private_config_content ( self , private_config ) :
2015-02-13 23:16:43 +02:00
"""
2015-06-07 00:15:03 +03:00
Update the private config
2015-02-13 23:16:43 +02:00
2015-06-07 00:15:03 +03:00
: param private_config : content of the private configuration file
2015-02-13 23:16:43 +02:00
"""
try :
2015-06-07 00:15:03 +03:00
private_config_path = os . path . join ( self . working_dir , " private-config.cfg " )
2015-04-06 22:30:57 +03:00
2015-06-07 00:15:03 +03:00
if private_config is None :
2021-04-13 12:16:50 +03:00
private_config = " "
2015-04-09 11:27:50 +03:00
2016-05-22 03:58:28 +03:00
# We disallow erasing the private config file
2015-06-07 00:15:03 +03:00
if len ( private_config ) == 0 and os . path . exists ( private_config_path ) :
2015-04-06 22:30:57 +03:00
return
2021-04-13 12:16:50 +03:00
with open ( private_config_path , " w+ " , encoding = " utf-8 " ) as f :
2015-06-07 00:15:03 +03:00
if len ( private_config ) == 0 :
2021-04-13 12:16:50 +03:00
f . write ( " " )
2015-02-13 23:16:43 +02:00
else :
2015-06-07 00:15:03 +03:00
private_config = private_config . replace ( " % h " , self . _name )
f . write ( private_config )
2015-02-13 23:16:43 +02:00
except OSError as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Can ' t write private-config file ' { private_config_path } ' : { e } " )
2015-02-13 23:16:43 +02:00
@property
2015-06-07 00:15:03 +03:00
def startup_config_file ( self ) :
2015-02-13 23:16:43 +02:00
"""
2015-06-07 00:15:03 +03:00
Returns the startup - config file for this IOU VM .
2015-02-13 23:16:43 +02:00
: returns : path to config file . None if the file doesn ' t exist
"""
2021-04-13 12:16:50 +03:00
path = os . path . join ( self . working_dir , " startup-config.cfg " )
2015-02-13 23:16:43 +02:00
if os . path . exists ( path ) :
return path
else :
return None
2015-02-16 21:08:04 +02:00
2015-02-17 15:52:51 +02:00
@property
2015-06-07 00:15:03 +03:00
def private_config_file ( self ) :
2015-02-17 15:52:51 +02:00
"""
2015-06-07 00:15:03 +03:00
Returns the private - config file for this IOU VM .
2015-02-17 15:52:51 +02:00
: returns : path to config file . None if the file doesn ' t exist
"""
2021-04-13 12:16:50 +03:00
path = os . path . join ( self . working_dir , " private-config.cfg " )
2015-02-17 15:52:51 +02:00
if os . path . exists ( path ) :
2015-06-07 00:15:03 +03:00
return path
2015-02-17 15:52:51 +02:00
else :
return None
2015-06-07 00:15:03 +03:00
@property
def relative_startup_config_file ( self ) :
"""
Returns the startup - config file relative to the project directory .
It ' s compatible with pre 1.3 projects.
: returns : path to startup - config file . None if the file doesn ' t exist
"""
2021-04-13 12:16:50 +03:00
path = os . path . join ( self . working_dir , " startup-config.cfg " )
2015-06-07 00:15:03 +03:00
if os . path . exists ( path ) :
2021-04-13 12:16:50 +03:00
return " startup-config.cfg "
2015-06-07 00:15:03 +03:00
else :
return None
@property
def relative_private_config_file ( self ) :
"""
Returns the private - config file relative to the project directory .
: returns : path to private - config file . None if the file doesn ' t exist
"""
2021-04-13 12:16:50 +03:00
path = os . path . join ( self . working_dir , " private-config.cfg " )
2015-06-07 00:15:03 +03:00
if os . path . exists ( path ) :
2021-04-13 12:16:50 +03:00
return " private-config.cfg "
2015-06-07 00:15:03 +03:00
else :
return None
2017-06-27 11:09:21 +03:00
@property
def application_id ( self ) :
"""
Returns application_id which unique identifier for IOU running script . Value is between 1 and 512.
When it ' s not set returns value from the local manager.
: returns : integer between 1 and 512
"""
2018-03-12 08:38:50 +02:00
2017-06-27 11:09:21 +03:00
return self . _application_id
@application_id.setter
def application_id ( self , application_id ) :
"""
Sets application_id for IOU .
: param : integer between 1 and 512
"""
self . _application_id = application_id
2015-06-07 00:15:03 +03:00
def extract_configs ( self ) :
"""
Gets the contents of the config files
startup - config and private - config from NVRAM .
: returns : tuple ( startup - config , private - config )
"""
2021-04-13 12:07:58 +03:00
nvram_file = os . path . join ( self . working_dir , f " nvram_ { self . application_id : 05d } " )
2015-06-07 00:15:03 +03:00
if not os . path . exists ( nvram_file ) :
return None , None
try :
with open ( nvram_file , " rb " ) as file :
nvram_content = file . read ( )
except OSError as e :
2021-04-13 12:07:58 +03:00
log . warning ( f " Cannot read nvram file { nvram_file } : { e } " )
2015-06-07 00:15:03 +03:00
return None , None
try :
startup_config_content , private_config_content = nvram_export ( nvram_content )
except ValueError as e :
2021-04-13 12:07:58 +03:00
log . warning ( f " Could not export configs from nvram file { nvram_file } : { e } " )
2015-06-07 00:15:03 +03:00
return None , None
return startup_config_content , private_config_content
def save_configs ( self ) :
"""
Saves the startup - config and private - config to files .
"""
if self . startup_config_content or self . private_config_content :
startup_config_content , private_config_content = self . extract_configs ( )
if startup_config_content :
config_path = os . path . join ( self . working_dir , " startup-config.cfg " )
try :
config = startup_config_content . decode ( " utf-8 " , errors = " replace " )
with open ( config_path , " wb " ) as f :
2021-04-13 12:07:58 +03:00
log . info ( f " saving startup-config to { config_path } " )
2015-06-07 00:15:03 +03:00
f . write ( config . encode ( " utf-8 " ) )
except ( binascii . Error , OSError ) as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not save the startup configuration { config_path } : { e } " )
2015-06-07 00:15:03 +03:00
2021-04-13 12:16:50 +03:00
if private_config_content and private_config_content != b " \n end \n " :
2015-06-07 00:15:03 +03:00
config_path = os . path . join ( self . working_dir , " private-config.cfg " )
try :
config = private_config_content . decode ( " utf-8 " , errors = " replace " )
with open ( config_path , " wb " ) as f :
2021-04-13 12:07:58 +03:00
log . info ( f " saving private-config to { config_path } " )
2015-06-07 00:15:03 +03:00
f . write ( config . encode ( " utf-8 " ) )
except ( binascii . Error , OSError ) as e :
2021-04-13 12:07:58 +03:00
raise IOUError ( f " Could not save the private configuration { config_path } : { e } " )
2015-06-07 00:15:03 +03:00
2018-10-15 13:05:49 +03:00
async def start_capture ( self , adapter_number , port_number , output_file , data_link_type = " DLT_EN10MB " ) :
2015-02-16 21:08:04 +02:00
"""
Starts a packet capture .
2015-04-08 20:17:34 +03:00
: param adapter_number : adapter number
: param port_number : port number
2015-02-16 21:08:04 +02:00
: param output_file : PCAP destination file for the capture
: param data_link_type : PCAP data link type ( DLT_ * ) , default is DLT_EN10MB
"""
2018-10-27 10:47:17 +03:00
nio = self . get_nio ( adapter_number , port_number )
2015-02-16 21:08:04 +02:00
if nio . capturing :
2021-04-13 12:16:50 +03:00
raise IOUError (
" Packet capture is already activated on {adapter_number} / {port_number} " . format (
adapter_number = adapter_number , port_number = port_number
)
)
2015-02-16 21:08:04 +02:00
2019-04-01 16:58:18 +03:00
nio . start_packet_capture ( output_file , data_link_type )
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: starting packet capture on {adapter_number} / {port_number} to {output_file} ' . format (
name = self . _name ,
id = self . _id ,
adapter_number = adapter_number ,
port_number = port_number ,
output_file = output_file ,
)
)
2015-02-16 21:08:04 +02:00
2016-12-14 13:01:34 +02:00
if self . ubridge :
2021-04-13 12:07:58 +03:00
bridge_name = f " IOL-BRIDGE- { self . application_id + 512 } "
2021-04-13 12:16:50 +03:00
await self . _ubridge_send (
' iol_bridge start_capture {name} {bay} {unit} " {output_file} " {data_link_type} ' . format (
name = bridge_name ,
bay = adapter_number ,
unit = port_number ,
output_file = output_file ,
data_link_type = re . sub ( r " ^DLT_ " , " " , data_link_type ) ,
)
)
2015-02-16 21:08:04 +02:00
2018-10-15 13:05:49 +03:00
async def stop_capture ( self , adapter_number , port_number ) :
2015-02-16 21:08:04 +02:00
"""
Stops a packet capture .
2015-04-08 20:17:34 +03:00
: param adapter_number : adapter number
: param port_number : port number
2015-02-16 21:08:04 +02:00
"""
2018-10-27 10:47:17 +03:00
nio = self . get_nio ( adapter_number , port_number )
2019-04-01 15:47:31 +03:00
if not nio . capturing :
return
2019-04-01 16:58:18 +03:00
nio . stop_packet_capture ( )
2021-04-13 12:16:50 +03:00
log . info (
' IOU " {name} " [ {id} ]: stopping packet capture on {adapter_number} / {port_number} ' . format (
name = self . _name , id = self . _id , adapter_number = adapter_number , port_number = port_number
)
)
2016-12-14 13:01:34 +02:00
if self . ubridge :
2021-04-13 12:07:58 +03:00
bridge_name = f " IOL-BRIDGE- { self . application_id + 512 } "
2021-04-13 12:16:50 +03:00
await self . _ubridge_send (
" iol_bridge stop_capture {name} {bay} {unit} " . format (
name = bridge_name , bay = adapter_number , unit = port_number
)
)