mirror of
https://git.rtems.org/rtems-tools/
synced 2025-06-04 06:24:17 +08:00
misc/tftpproxy: Add a proxy TFTP server.
- Uses a config INI file to map clients to servers - Handle a number of requests to a single server's TFTP port (69) and multiplex to a non-su ports or different servers. - Supports running rtems-test to more than one hardware device using TFTP at once.
This commit is contained in:
parent
30a5cd65cc
commit
deb54b6145
42
misc/rtems-tftp-proxy
Executable file
42
misc/rtems-tftp-proxy
Executable file
@ -0,0 +1,42 @@
|
||||
#! /bin/sh
|
||||
#
|
||||
# RTEMS Tools Project (http://www.rtems.org/)
|
||||
# Copyright 2019 Chris Johns (chrisj@rtems.org)
|
||||
# All rights reserved.
|
||||
#
|
||||
# This file is part of the RTEMS Tools package in 'rtems-tools'.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
set -e
|
||||
base=$(dirname $(dirname $0))
|
||||
cmd=misc/tools/cmd-tftpproxy.py
|
||||
PYTHON_WRAPPER=rtemstoolkit/python-wrapper.sh
|
||||
if test -f ${base}/${PYTHON_WRAPPER}; then
|
||||
PYTHON_CMD=${base}/${cmd}
|
||||
. ${base}/${PYTHON_WRAPPER}
|
||||
elif test -f ${base}/share/rtems/${PYTHON_WRAPPER}; then
|
||||
PYTHON_CMD=${base}/share/rtems/${cmd}
|
||||
. ${base}/share/rtems/${PYTHON_WRAPPER}
|
||||
fi
|
||||
echo "error: RTEMS Toolkit python wrapper not found, please report"
|
44
misc/tools/cmd-tftpproxy.py
Executable file
44
misc/tools/cmd-tftpproxy.py
Executable file
@ -0,0 +1,44 @@
|
||||
#
|
||||
# RTEMS Tools Project (http://www.rtems.org/)
|
||||
# Copyright 2019 Chris Johns (chrisj@rtems.org)
|
||||
# All rights reserved.
|
||||
#
|
||||
# This file is part of the RTEMS Tools package in 'rtems-tools'.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys, os
|
||||
|
||||
base = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
|
||||
rtems = os.path.dirname(base)
|
||||
sys.path = [rtems] + sys.path
|
||||
|
||||
try:
|
||||
import tftpproxy
|
||||
tftpproxy.run(sys.argv[1:], command_path = base)
|
||||
except ImportError:
|
||||
print("Incorrect RTEMS Tools installation", file = sys.stderr)
|
||||
sys.exit(1)
|
21
misc/tools/getmac/LICENSE
Normal file
21
misc/tools/getmac/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Christopher Goes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
2
misc/tools/getmac/__init__.py
Normal file
2
misc/tools/getmac/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .getmac import __version__, get_mac_address
|
||||
__all__ = ['get_mac_address']
|
67
misc/tools/getmac/__main__.py
Normal file
67
misc/tools/getmac/__main__.py
Normal file
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from . import getmac
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
'getmac', description='Get the MAC address of system network '
|
||||
'interfaces or remote hosts on the LAN')
|
||||
parser.add_argument(
|
||||
'--version', action='version',
|
||||
version='getmac %s' % getmac.__version__)
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='store_true',
|
||||
help='Enable output messages')
|
||||
parser.add_argument(
|
||||
'-d', '--debug', action='count',
|
||||
help='Enable debugging output. Add characters to '
|
||||
'increase verbosity of output, e.g. \'-dd\'.')
|
||||
parser.add_argument(
|
||||
'-N', '--no-net', '--no-network-requests',
|
||||
action='store_true', dest='NO_NET',
|
||||
help='Do not send a UDP packet to refresh the ARP table')
|
||||
|
||||
group = parser.add_mutually_exclusive_group(required=False)
|
||||
group.add_argument(
|
||||
'-i', '--interface', type=str, default=None,
|
||||
help='Name of a network interface on the system')
|
||||
group.add_argument(
|
||||
'-4', '--ip', type=str, default=None,
|
||||
help='IPv4 address of a remote host')
|
||||
group.add_argument(
|
||||
'-6', '--ip6', type=str, default=None,
|
||||
help='IPv6 address of a remote host')
|
||||
group.add_argument(
|
||||
'-n', '--hostname', type=str, default=None,
|
||||
help='Hostname of a remote host')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug or args.verbose:
|
||||
logging.basicConfig(format='%(levelname)-8s %(message)s',
|
||||
level=logging.DEBUG, stream=sys.stderr)
|
||||
if args.debug:
|
||||
getmac.DEBUG = args.debug
|
||||
|
||||
mac = getmac.get_mac_address(
|
||||
interface=args.interface, ip=args.ip,
|
||||
ip6=args.ip6, hostname=args.hostname,
|
||||
network_request=not args.NO_NET)
|
||||
|
||||
if mac is not None:
|
||||
print(mac) # noqa: T001
|
||||
sys.exit(0) # Exit success!
|
||||
else:
|
||||
sys.exit(1) # Exit with error since it failed to find a MAC
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
603
misc/tools/getmac/getmac.py
Normal file
603
misc/tools/getmac/getmac.py
Normal file
@ -0,0 +1,603 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# http://multivax.com/last_question.html
|
||||
|
||||
"""Get the MAC address of remote hosts or network interfaces.
|
||||
|
||||
It provides a platform-independent interface to get the MAC addresses of:
|
||||
|
||||
* System network interfaces (by interface name)
|
||||
* Remote hosts on the local network (by IPv4/IPv6 address or hostname)
|
||||
|
||||
It provides one function: `get_mac_address()`
|
||||
|
||||
Examples:
|
||||
|
||||
from getmac import get_mac_address
|
||||
eth_mac = get_mac_address(interface="eth0")
|
||||
win_mac = get_mac_address(interface="Ethernet 3")
|
||||
ip_mac = get_mac_address(ip="192.168.0.1")
|
||||
ip6_mac = get_mac_address(ip6="::1")
|
||||
host_mac = get_mac_address(hostname="localhost")
|
||||
updated_mac = get_mac_address(ip="10.0.0.1", network_request=True)
|
||||
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shlex
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import traceback
|
||||
from subprocess import check_output
|
||||
|
||||
try: # Python 3
|
||||
from subprocess import DEVNULL # type: ignore
|
||||
except ImportError: # Python 2
|
||||
DEVNULL = open(os.devnull, 'wb') # type: ignore
|
||||
|
||||
# Configure logging
|
||||
log = logging.getLogger('getmac')
|
||||
log.addHandler(logging.NullHandler())
|
||||
|
||||
__version__ = '0.8.1'
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
# Configurable settings
|
||||
DEBUG = 0
|
||||
PORT = 55555
|
||||
|
||||
# Platform identifiers
|
||||
_SYST = platform.system()
|
||||
if _SYST == 'Java':
|
||||
try:
|
||||
import java.lang
|
||||
_SYST = str(java.lang.System.getProperty("os.name"))
|
||||
except ImportError:
|
||||
log.critical("Can't determine OS: couldn't import java.lang on Jython")
|
||||
WINDOWS = _SYST == 'Windows'
|
||||
DARWIN = _SYST == 'Darwin'
|
||||
OPENBSD = _SYST == 'OpenBSD'
|
||||
FREEBSD = _SYST == 'FreeBSD'
|
||||
BSD = OPENBSD or FREEBSD # Not including Darwin for now
|
||||
WSL = False # Windows Subsystem for Linux (WSL)
|
||||
LINUX = False
|
||||
if _SYST == 'Linux':
|
||||
if 'Microsoft' in platform.version():
|
||||
WSL = True
|
||||
else:
|
||||
LINUX = True
|
||||
|
||||
PATH = os.environ.get('PATH', os.defpath).split(os.pathsep)
|
||||
if not WINDOWS:
|
||||
PATH.extend(('/sbin', '/usr/sbin'))
|
||||
|
||||
# Use a copy of the environment so we don't
|
||||
# modify the process's current environment.
|
||||
ENV = dict(os.environ)
|
||||
ENV['LC_ALL'] = 'C' # Ensure ASCII output so we parse correctly
|
||||
|
||||
# Constants
|
||||
IP4 = 0
|
||||
IP6 = 1
|
||||
INTERFACE = 2
|
||||
HOSTNAME = 3
|
||||
|
||||
MAC_RE_COLON = r'([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})'
|
||||
MAC_RE_DASH = r'([0-9a-fA-F]{2}(?:-[0-9a-fA-F]{2}){5})'
|
||||
MAC_RE_DARWIN = r'([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2}){5})'
|
||||
|
||||
# Used for mypy (a data type analysis tool)
|
||||
# If you're copying the code, this section can be safely removed
|
||||
try:
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import Optional
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def get_mac_address(
|
||||
interface=None, ip=None, ip6=None,
|
||||
hostname=None, network_request=True
|
||||
):
|
||||
# type: (Optional[str], Optional[str], Optional[str], Optional[str], bool) -> Optional[str]
|
||||
"""Get a Unicast IEEE 802 MAC-48 address from a local interface or remote host.
|
||||
|
||||
You must only use one of the first four arguments. If none of the arguments
|
||||
are selected, the default network interface for the system will be used.
|
||||
|
||||
Exceptions will be handled silently and returned as a None.
|
||||
For the time being, it assumes you are using Ethernet.
|
||||
|
||||
NOTES:
|
||||
* You MUST provide str-typed arguments, REGARDLESS of Python version.
|
||||
* localhost/127.0.0.1 will always return '00:00:00:00:00:00'
|
||||
|
||||
Args:
|
||||
interface (str): Name of a local network interface (e.g "Ethernet 3", "eth0", "ens32")
|
||||
ip (str): Canonical dotted decimal IPv4 address of a remote host (e.g 192.168.0.1)
|
||||
ip6 (str): Canonical shortened IPv6 address of a remote host (e.g ff02::1:ffe7:7f19)
|
||||
hostname (str): DNS hostname of a remote host (e.g "router1.mycorp.com", "localhost")
|
||||
network_request (bool): Send a UDP packet to a remote host to populate
|
||||
the ARP/NDP tables for IPv4/IPv6. The port this packet is sent to can
|
||||
be configured using the module variable `getmac.PORT`.
|
||||
Returns:
|
||||
Lowercase colon-separated MAC address, or None if one could not be
|
||||
found or there was an error.
|
||||
"""
|
||||
if (hostname and hostname == 'localhost') or (ip and ip == '127.0.0.1'):
|
||||
return '00:00:00:00:00:00'
|
||||
|
||||
# Resolve hostname to an IP address
|
||||
if hostname:
|
||||
ip = socket.gethostbyname(hostname)
|
||||
|
||||
# Populate the ARP table by sending a empty UDP packet to a high port
|
||||
if network_request and (ip or ip6):
|
||||
if ip:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
else:
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
try:
|
||||
if ip:
|
||||
s.sendto(b'', (ip, PORT))
|
||||
else:
|
||||
s.sendto(b'', (ip6, PORT))
|
||||
except Exception:
|
||||
log.error("Failed to send ARP table population packet")
|
||||
if DEBUG:
|
||||
log.debug(traceback.format_exc())
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
# Setup the address hunt based on the arguments specified
|
||||
if ip6:
|
||||
if not socket.has_ipv6:
|
||||
log.error("Cannot get the MAC address of a IPv6 host: "
|
||||
"IPv6 is not supported on this system")
|
||||
return None
|
||||
elif ':' not in ip6:
|
||||
log.error("Invalid IPv6 address: %s", ip6)
|
||||
return None
|
||||
to_find = ip6
|
||||
typ = IP6
|
||||
elif ip:
|
||||
to_find = ip
|
||||
typ = IP4
|
||||
else: # Default to searching for interface
|
||||
typ = INTERFACE
|
||||
if interface:
|
||||
to_find = interface
|
||||
else:
|
||||
# Default to finding MAC of the interface with the default route
|
||||
if WINDOWS and network_request:
|
||||
to_find = _fetch_ip_using_dns()
|
||||
typ = IP4
|
||||
elif WINDOWS:
|
||||
to_find = 'Ethernet'
|
||||
elif BSD:
|
||||
if OPENBSD:
|
||||
to_find = _get_default_iface_openbsd() # type: ignore
|
||||
else:
|
||||
to_find = _get_default_iface_freebsd() # type: ignore
|
||||
if not to_find:
|
||||
to_find = 'em0'
|
||||
else:
|
||||
to_find = _hunt_linux_default_iface() # type: ignore
|
||||
if not to_find:
|
||||
to_find = 'en0'
|
||||
|
||||
mac = _hunt_for_mac(to_find, typ, network_request)
|
||||
log.debug("Raw MAC found: %s", mac)
|
||||
|
||||
# Check and format the result to be lowercase, colon-separated
|
||||
if mac is not None:
|
||||
mac = str(mac)
|
||||
if not PY2: # Strip bytestring conversion artifacts
|
||||
mac = mac.replace("b'", '').replace("'", '')\
|
||||
.replace('\\n', '').replace('\\r', '')
|
||||
mac = mac.strip().lower().replace(' ', '').replace('-', ':')
|
||||
|
||||
# Fix cases where there are no colons
|
||||
if ':' not in mac and len(mac) == 12:
|
||||
log.debug("Adding colons to MAC %s", mac)
|
||||
mac = ':'.join(mac[i:i + 2] for i in range(0, len(mac), 2))
|
||||
|
||||
# Pad single-character octets with a leading zero (e.g Darwin's ARP output)
|
||||
elif len(mac) < 17:
|
||||
log.debug("Length of MAC %s is %d, padding single-character "
|
||||
"octets with zeros", mac, len(mac))
|
||||
parts = mac.split(':')
|
||||
new_mac = []
|
||||
for part in parts:
|
||||
if len(part) == 1:
|
||||
new_mac.append('0' + part)
|
||||
else:
|
||||
new_mac.append(part)
|
||||
mac = ':'.join(new_mac)
|
||||
|
||||
# MAC address should ALWAYS be 17 characters before being returned
|
||||
if len(mac) != 17:
|
||||
log.warning("MAC address %s is not 17 characters long!", mac)
|
||||
mac = None
|
||||
elif mac.count(':') != 5:
|
||||
log.warning("MAC address %s is missing ':' characters", mac)
|
||||
mac = None
|
||||
return mac
|
||||
|
||||
|
||||
def _search(regex, text, group_index=0):
|
||||
# type: (str, str, int) -> Optional[str]
|
||||
match = re.search(regex, text)
|
||||
if match:
|
||||
return match.groups()[group_index]
|
||||
return None
|
||||
|
||||
|
||||
def _popen(command, args):
|
||||
# type: (str, str) -> str
|
||||
for directory in PATH:
|
||||
executable = os.path.join(directory, command)
|
||||
if (os.path.exists(executable)
|
||||
and os.access(executable, os.F_OK | os.X_OK)
|
||||
and not os.path.isdir(executable)):
|
||||
break
|
||||
else:
|
||||
executable = command
|
||||
if DEBUG >= 3:
|
||||
log.debug("Running: '%s %s'", executable, args)
|
||||
return _call_proc(executable, args)
|
||||
|
||||
|
||||
def _call_proc(executable, args):
|
||||
# type: (str, str) -> str
|
||||
if WINDOWS:
|
||||
cmd = executable + ' ' + args # type: ignore
|
||||
else:
|
||||
cmd = [executable] + shlex.split(args) # type: ignore
|
||||
output = check_output(cmd, stderr=DEVNULL, env=ENV)
|
||||
if DEBUG >= 4:
|
||||
log.debug("Output from '%s' command: %s", executable, str(output))
|
||||
if not PY2 and isinstance(output, bytes):
|
||||
return str(output, 'utf-8')
|
||||
else:
|
||||
return str(output)
|
||||
|
||||
|
||||
def _windows_ctypes_host(host):
|
||||
# type: (str) -> Optional[str]
|
||||
if not PY2: # Convert to bytes on Python 3+ (Fixes GitHub issue #7)
|
||||
host = host.encode() # type: ignore
|
||||
try:
|
||||
inetaddr = ctypes.windll.wsock32.inet_addr(host) # type: ignore
|
||||
if inetaddr in (0, -1):
|
||||
raise Exception
|
||||
except Exception:
|
||||
hostip = socket.gethostbyname(host)
|
||||
inetaddr = ctypes.windll.wsock32.inet_addr(hostip) # type: ignore
|
||||
|
||||
buffer = ctypes.c_buffer(6)
|
||||
addlen = ctypes.c_ulong(ctypes.sizeof(buffer))
|
||||
|
||||
send_arp = ctypes.windll.Iphlpapi.SendARP # type: ignore
|
||||
if send_arp(inetaddr, 0, ctypes.byref(buffer), ctypes.byref(addlen)) != 0:
|
||||
return None
|
||||
|
||||
# Convert binary data into a string.
|
||||
macaddr = ''
|
||||
for intval in struct.unpack('BBBBBB', buffer): # type: ignore
|
||||
if intval > 15:
|
||||
replacestr = '0x'
|
||||
else:
|
||||
replacestr = 'x'
|
||||
macaddr = ''.join([macaddr, hex(intval).replace(replacestr, '')])
|
||||
return macaddr
|
||||
|
||||
|
||||
def _fcntl_iface(iface):
|
||||
# type: (str) -> str
|
||||
import fcntl
|
||||
if not PY2:
|
||||
iface = iface.encode() # type: ignore
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
# 0x8927 = SIOCGIFADDR
|
||||
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', iface[:15]))
|
||||
if PY2:
|
||||
return ':'.join(['%02x' % ord(char) for char in info[18:24]])
|
||||
else:
|
||||
return ':'.join(['%02x' % ord(chr(char)) for char in info[18:24]])
|
||||
|
||||
|
||||
def _uuid_ip(ip):
|
||||
# type: (str) -> Optional[str]
|
||||
from uuid import _arp_getnode # type: ignore
|
||||
backup = socket.gethostbyname
|
||||
try:
|
||||
socket.gethostbyname = lambda x: ip
|
||||
mac1 = _arp_getnode()
|
||||
if mac1 is not None:
|
||||
mac1 = _uuid_convert(mac1)
|
||||
mac2 = _arp_getnode()
|
||||
mac2 = _uuid_convert(mac2)
|
||||
if mac1 == mac2:
|
||||
return mac1
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
socket.gethostbyname = backup
|
||||
return None
|
||||
|
||||
|
||||
def _uuid_lanscan_iface(iface):
|
||||
# type: (str) -> Optional[str]
|
||||
from uuid import _find_mac # type: ignore
|
||||
if not PY2:
|
||||
iface = bytes(iface, 'utf-8') # type: ignore
|
||||
mac = _find_mac('lanscan', '-ai', [iface], lambda i: 0)
|
||||
if mac:
|
||||
return _uuid_convert(mac)
|
||||
return None
|
||||
|
||||
|
||||
def _uuid_convert(mac):
|
||||
# type: (int) -> str
|
||||
return ':'.join(('%012X' % mac)[i:i+2] for i in range(0, 12, 2))
|
||||
|
||||
|
||||
def _read_sys_iface_file(iface):
|
||||
# type: (str) -> Optional[str]
|
||||
data = _read_file('/sys/class/net/' + iface + '/address')
|
||||
# Sometimes this can be empty or a single newline character
|
||||
return None if data is not None and len(data) < 17 else data
|
||||
|
||||
|
||||
def _read_arp_file(host):
|
||||
# type: (str) -> Optional[str]
|
||||
data = _read_file('/proc/net/arp')
|
||||
if data is not None and len(data) > 1:
|
||||
# Need a space, otherwise a search for 192.168.16.2
|
||||
# will match 192.168.16.254 if it comes first!
|
||||
return _search(re.escape(host) + r' .+' + MAC_RE_COLON, data)
|
||||
return None
|
||||
|
||||
|
||||
def _read_file(filepath):
|
||||
# type: (str) -> Optional[str]
|
||||
try:
|
||||
with open(filepath) as f:
|
||||
return f.read()
|
||||
except (OSError, IOError): # This is IOError on Python 2.7
|
||||
log.debug("Could not find file: '%s'", filepath)
|
||||
return None
|
||||
|
||||
|
||||
def _hunt_for_mac(to_find, type_of_thing, net_ok=True):
|
||||
# type: (Optional[str], int, bool) -> Optional[str]
|
||||
"""Tries a variety of methods to get a MAC address.
|
||||
|
||||
Format of method lists:
|
||||
Tuple: (regex, regex index, command, command args)
|
||||
Command args is a list of strings to attempt to use as arguments
|
||||
lambda: Function to call
|
||||
"""
|
||||
if to_find is None:
|
||||
log.warning("_hunt_for_mac() failed: to_find is None")
|
||||
return None
|
||||
if not PY2 and isinstance(to_find, bytes):
|
||||
to_find = str(to_find, 'utf-8')
|
||||
|
||||
if WINDOWS and type_of_thing == INTERFACE:
|
||||
methods = [
|
||||
# getmac - Connection Name
|
||||
(r'\r\n' + to_find + r'.*' + MAC_RE_DASH + r'.*\r\n',
|
||||
0, 'getmac.exe', ['/NH /V']),
|
||||
|
||||
# ipconfig
|
||||
(to_find + r'(?:\n?[^\n]*){1,8}Physical Address[ .:]+' + MAC_RE_DASH + r'\r\n',
|
||||
0, 'ipconfig.exe', ['/all']),
|
||||
|
||||
# getmac - Network Adapter (the human-readable name)
|
||||
(r'\r\n.*' + to_find + r'.*' + MAC_RE_DASH + r'.*\r\n',
|
||||
0, 'getmac.exe', ['/NH /V']),
|
||||
|
||||
# wmic - WMI command line utility
|
||||
lambda x: _popen('wmic.exe', 'nic where "NetConnectionID = \'%s\'" get '
|
||||
'MACAddress /value' % x).strip().partition('=')[2],
|
||||
]
|
||||
elif (WINDOWS or WSL) and type_of_thing in [IP4, IP6, HOSTNAME]:
|
||||
methods = [
|
||||
# arp -a - Parsing result with a regex
|
||||
(MAC_RE_DASH, 0, 'arp.exe', ['-a %s' % to_find]),
|
||||
]
|
||||
|
||||
# Add methods that make network requests
|
||||
# Insert it *after* arp.exe since that's probably faster.
|
||||
if net_ok and type_of_thing != IP6 and not WSL:
|
||||
methods.insert(1, _windows_ctypes_host)
|
||||
elif (DARWIN or FREEBSD) and type_of_thing == INTERFACE:
|
||||
methods = [
|
||||
(r'ether ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', [to_find]),
|
||||
|
||||
# Alternative match for ifconfig if it fails
|
||||
(to_find + r'.*ether ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', ['']),
|
||||
|
||||
(MAC_RE_COLON,
|
||||
0, 'networksetup', ['-getmacaddress %s' % to_find]),
|
||||
]
|
||||
elif FREEBSD and type_of_thing in [IP4, IP6, HOSTNAME]:
|
||||
methods = [
|
||||
(r'\(' + re.escape(to_find) + r'\)\s+at\s+' + MAC_RE_COLON,
|
||||
0, 'arp', [to_find])
|
||||
]
|
||||
elif OPENBSD and type_of_thing == INTERFACE:
|
||||
methods = [
|
||||
(r'lladdr ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', [to_find]),
|
||||
]
|
||||
elif OPENBSD and type_of_thing in [IP4, IP6, HOSTNAME]:
|
||||
methods = [
|
||||
(re.escape(to_find) + r'[ ]+' + MAC_RE_COLON,
|
||||
0, 'arp', ['-an']),
|
||||
]
|
||||
elif type_of_thing == INTERFACE:
|
||||
methods = [
|
||||
_read_sys_iface_file,
|
||||
_fcntl_iface,
|
||||
|
||||
# Fast modern Ubuntu ifconfig
|
||||
(r'ether ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', [to_find]),
|
||||
|
||||
# Fast ifconfig
|
||||
(r'HWaddr ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', [to_find]),
|
||||
|
||||
# ip link (Don't use 'list' due to SELinux [Android 24+])
|
||||
(to_find + r'.*\n.*link/ether ' + MAC_RE_COLON,
|
||||
0, 'ip', ['link %s' % to_find, 'link']),
|
||||
|
||||
# netstat
|
||||
(to_find + r'.*HWaddr ' + MAC_RE_COLON,
|
||||
0, 'netstat', ['-iae']),
|
||||
|
||||
# More variations of ifconfig
|
||||
(to_find + r'.*ether ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', ['']),
|
||||
(to_find + r'.*HWaddr ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', ['', '-a', '-v']),
|
||||
|
||||
# Tru64 ('-av')
|
||||
(to_find + r'.*Ether ' + MAC_RE_COLON,
|
||||
0, 'ifconfig', ['-av']),
|
||||
_uuid_lanscan_iface,
|
||||
]
|
||||
elif type_of_thing in [IP4, IP6, HOSTNAME]:
|
||||
esc = re.escape(to_find)
|
||||
methods = [
|
||||
_read_arp_file,
|
||||
lambda x: _popen('ip', 'neighbor show %s' % x)
|
||||
.partition(x)[2].partition('lladdr')[2].strip().split()[0],
|
||||
|
||||
(r'\(' + esc + r'\)\s+at\s+' + MAC_RE_COLON,
|
||||
0, 'arp', [to_find, '-an', '-an %s' % to_find]),
|
||||
|
||||
# Darwin oddness
|
||||
(r'\(' + esc + r'\)\s+at\s+' + MAC_RE_DARWIN,
|
||||
0, 'arp', [to_find, '-a', '-a %s' % to_find]),
|
||||
_uuid_ip,
|
||||
]
|
||||
else:
|
||||
log.critical("Reached end of _hunt_for_mac() if-else chain!")
|
||||
return None
|
||||
return _try_methods(methods, to_find)
|
||||
|
||||
|
||||
def _try_methods(methods, to_find=None):
|
||||
# type: (list, Optional[str]) -> Optional[str]
|
||||
"""Runs the methods specified by _hunt_for_mac().
|
||||
|
||||
We try every method and see if it returned a MAC address. If it returns
|
||||
None or raises an exception, we continue and try the next method.
|
||||
"""
|
||||
found = None
|
||||
for m in methods:
|
||||
try:
|
||||
if isinstance(m, tuple):
|
||||
for arg in m[3]: # list(str)
|
||||
if DEBUG:
|
||||
log.debug("Trying: '%s %s'", m[2], arg)
|
||||
# Arguments: (regex, _popen(command, arg), regex index)
|
||||
found = _search(m[0], _popen(m[2], arg), m[1])
|
||||
if DEBUG:
|
||||
log.debug("Result: %s\n", found)
|
||||
if found: # Skip remaining args AND remaining methods
|
||||
break
|
||||
elif callable(m):
|
||||
if DEBUG:
|
||||
log.debug("Trying: '%s' (to_find: '%s')", m.__name__, str(to_find))
|
||||
if to_find is not None:
|
||||
found = m(to_find)
|
||||
else:
|
||||
found = m()
|
||||
if DEBUG:
|
||||
log.debug("Result: %s\n", found)
|
||||
else:
|
||||
log.critical("Invalid type '%s' for method '%s'", type(m), str(m))
|
||||
except Exception as ex:
|
||||
if DEBUG:
|
||||
log.debug("Exception: %s", str(ex))
|
||||
if DEBUG >= 2:
|
||||
log.debug(traceback.format_exc())
|
||||
continue
|
||||
if found: # Skip remaining methods
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
def _get_default_iface_linux():
|
||||
# type: () -> Optional[str]
|
||||
"""Get the default interface by reading /proc/net/route.
|
||||
|
||||
This is the same source as the `route` command, however it's much
|
||||
faster to read this file than to call `route`. If it fails for whatever
|
||||
reason, we can fall back on the system commands (e.g for a platform
|
||||
that has a route command, but maybe doesn't use /proc?).
|
||||
"""
|
||||
data = _read_file('/proc/net/route')
|
||||
if data is not None and len(data) > 1:
|
||||
for line in data.split('\n')[1:-1]:
|
||||
iface_name, dest = line.split('\t')[:2]
|
||||
if dest == '00000000':
|
||||
return iface_name
|
||||
return None
|
||||
|
||||
|
||||
def _hunt_linux_default_iface():
|
||||
# type: () -> Optional[str]
|
||||
# NOTE: for now, we check the default interface for WSL using the
|
||||
# same methods as POSIX, since those parts of the net stack work fine.
|
||||
methods = [
|
||||
_get_default_iface_linux,
|
||||
lambda: _popen('route', '-n').partition('0.0.0.0')[2].partition('\n')[0].split()[-1],
|
||||
lambda: _popen('ip', 'route list 0/0').partition('dev')[2].partition('proto')[0].strip(),
|
||||
]
|
||||
return _try_methods(methods)
|
||||
|
||||
|
||||
def _get_default_iface_openbsd():
|
||||
# type: () -> Optional[str]
|
||||
methods = [
|
||||
lambda: _popen('route', '-nq show -inet -gateway -priority 1')
|
||||
.partition('127.0.0.1')[0].strip().rpartition(' ')[2],
|
||||
]
|
||||
return _try_methods(methods)
|
||||
|
||||
|
||||
def _get_default_iface_freebsd():
|
||||
# type: () -> Optional[str]
|
||||
methods = [
|
||||
(r'default[ ]+\S+[ ]+\S+[ ]+(\S+)\n',
|
||||
0, 'netstat', ['-r']),
|
||||
]
|
||||
return _try_methods(methods)
|
||||
|
||||
|
||||
def _fetch_ip_using_dns():
|
||||
# type: () -> str
|
||||
"""Determines the IP address of the default network interface.
|
||||
|
||||
Sends a UDP packet to Cloudflare's DNS (1.1.1.1), which should go through
|
||||
the default interface. This populates the source address of the socket,
|
||||
which we then inspect and return.
|
||||
"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(('1.1.1.1', 53))
|
||||
ip = s.getsockname()[0]
|
||||
s.close() # NOTE: sockets don't have context manager in 2.7 :(
|
||||
return ip
|
423
misc/tools/tftpproxy.py
Normal file
423
misc/tools/tftpproxy.py
Normal file
@ -0,0 +1,423 @@
|
||||
#
|
||||
# Copyright 2019 Chris Johns (chris@contemporary.software)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#
|
||||
# The TFTP proxy redirects a TFTP session to another host. If you have a
|
||||
# farm of boards you can configure them to point to this proxy and it will
|
||||
# redirect the requests to another machine that is testing it.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
try:
|
||||
import socketserver
|
||||
except:
|
||||
import SocketServer as socketserver
|
||||
|
||||
from rtemstoolkit import configuration
|
||||
from rtemstoolkit import error
|
||||
from rtemstoolkit import log
|
||||
from rtemstoolkit import version
|
||||
|
||||
import getmac
|
||||
|
||||
def host_port_split(ip_port):
|
||||
ips = ip_port.split(':')
|
||||
port = 0
|
||||
if len(ips) >= 1:
|
||||
ip = ips[0]
|
||||
if len(ips) == 2:
|
||||
port = int(ips[1])
|
||||
else:
|
||||
raise error.general('invalid host:port: %s' % (ip_port))
|
||||
return ip, port
|
||||
|
||||
class tftp_session(object):
|
||||
|
||||
opcodes = ['nul', 'RRQ', 'WRQ', 'DATA', 'ACK', 'ERROR', 'OACK']
|
||||
|
||||
def __init__(self):
|
||||
self.packets = []
|
||||
self.block = 0
|
||||
self.block_size = 512
|
||||
self.timeout = 0
|
||||
self.finished = True
|
||||
|
||||
def __str__(self):
|
||||
return os.linesep.join([self.decode(p[0], p[1], p[2]) for p in self.packets])
|
||||
|
||||
def data(self, host, port, data):
|
||||
finished = False
|
||||
self.packets += [(host, port, data)]
|
||||
opcode = (data[0] << 8) | data[1]
|
||||
if opcode == 1 or opcode == 2:
|
||||
self.block = 0
|
||||
self.finished = False
|
||||
value = self.get_option('timeout', data)
|
||||
if value is not None:
|
||||
self.timeout = int(value)
|
||||
value = self.get_option('blksize', data)
|
||||
if value is not None:
|
||||
self.block_size = int(value)
|
||||
else:
|
||||
self.block_size = 512
|
||||
elif opcode == 3:
|
||||
self.block = (data[2] << 8) | data[3]
|
||||
if len(data) - 4 < self.block_size:
|
||||
self.finished = True
|
||||
elif opcode == 4:
|
||||
self.block = (data[2] << 8) | data[3]
|
||||
if self.finished:
|
||||
finished = True
|
||||
return finished
|
||||
|
||||
def decode(self, host, port, data):
|
||||
s = ''
|
||||
dlen = len(data)
|
||||
if dlen > 2:
|
||||
opcode = (data[0] << 8) | data[1]
|
||||
if opcode < len(self.opcodes):
|
||||
if opcode == 1 or opcode == 2:
|
||||
s += ' ' + self.opcodes[opcode] + ', '
|
||||
i = 2
|
||||
while data[i] != 0:
|
||||
s += chr(data[i])
|
||||
i += 1
|
||||
while i < dlen - 1:
|
||||
s += ', '
|
||||
i += 1
|
||||
while data[i] != 0:
|
||||
s += chr(data[i])
|
||||
i += 1
|
||||
elif opcode == 3:
|
||||
block = (data[2] << 8) | data[3]
|
||||
s += ' ' + self.opcodes[opcode] + ', '
|
||||
s += '#' + str(block) + ', '
|
||||
if dlen > 4:
|
||||
s += '%02x%02x..%02x%02x' % (data[4], data[5], data[-2], data[-1])
|
||||
else:
|
||||
s += '%02x%02x%02x%02x' % (data[4], data[5], data[6], data[6])
|
||||
s += ',' + str(dlen - 4)
|
||||
elif opcode == 4:
|
||||
block = (data[2] << 8) | data[3]
|
||||
s += ' ' + self.opcodes[opcode] + ' ' + str(block)
|
||||
elif opcode == 5:
|
||||
s += 'E ' + self.opcodes[opcode] + ', '
|
||||
s += str((data[2] << 8) | (data[3]))
|
||||
i = 2
|
||||
while data[i] != 0:
|
||||
s += chr(data[i])
|
||||
i += 1
|
||||
elif opcode == 6:
|
||||
s += ' ' + self.opcodes[opcode]
|
||||
i = 1
|
||||
while i < dlen - 1:
|
||||
s += ', '
|
||||
i += 1
|
||||
while data[i] != 0:
|
||||
s += chr(data[i])
|
||||
i += 1
|
||||
else:
|
||||
s += 'E INV(%d)' % (opcode)
|
||||
else:
|
||||
s += 'E INVALID LENGTH'
|
||||
return s[:2] + '[%s:%d] ' % (host, port) + s[2:]
|
||||
|
||||
def get_option(self, option, data):
|
||||
dlen = len(data)
|
||||
opcode = (data[0] << 8) | data[1]
|
||||
next_option = False
|
||||
if opcode == 1 or opcode == 2:
|
||||
i = 1
|
||||
while i < dlen - 1:
|
||||
o = ''
|
||||
i += 1
|
||||
while data[i] != 0:
|
||||
o += chr(data[i])
|
||||
i += 1
|
||||
if o == option:
|
||||
next_option = True
|
||||
elif next_option:
|
||||
return o
|
||||
return None
|
||||
|
||||
def get_timeout(self, default_timeout, timeout_guard):
|
||||
if self.timeout == 0:
|
||||
return self.timeout + timeout_guard
|
||||
return default_timeout
|
||||
|
||||
def get_block_size(self):
|
||||
return self.block_size
|
||||
|
||||
class udp_handler(socketserver.BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
client_ip = self.client_address[0]
|
||||
client_port = self.client_address[1]
|
||||
client = '%s:%i' % (client_ip, client_port)
|
||||
session = tftp_session()
|
||||
finished = session.data(client_ip, client_port, self.request[0])
|
||||
if not finished:
|
||||
timeout = session.get_timeout(self.server.proxy.session_timeout, 1)
|
||||
host = self.server.proxy.get_host(client_ip)
|
||||
if host is not None:
|
||||
session_count = self.server.proxy.get_session_count()
|
||||
log.notice(' ] %6d: session: %s -> %s: start' % (session_count,
|
||||
client,
|
||||
host))
|
||||
host_ip, host_server_port = host_port_split(host)
|
||||
host_port = host_server_port
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.settimeout(timeout)
|
||||
log.trace(' > ' + session.decode(client_ip,
|
||||
client_port,
|
||||
self.request[0]))
|
||||
sock.sendto(self.request[0], (host_ip, host_port))
|
||||
while not finished:
|
||||
try:
|
||||
data, address = sock.recvfrom(16 * 1024)
|
||||
except socket.error as se:
|
||||
log.notice(' ] session: %s -> %s: error: %s' % (client,
|
||||
host,
|
||||
se))
|
||||
return
|
||||
except socket.gaierror as se:
|
||||
log.notice(' ] session: %s -> %s: error: %s' % (client,
|
||||
host,
|
||||
se))
|
||||
return
|
||||
except:
|
||||
return
|
||||
finished = session.data(address[0], address[1], data)
|
||||
if address[0] == host_ip:
|
||||
if host_port == host_server_port:
|
||||
host_port = address[1]
|
||||
if address[1] == host_port:
|
||||
log.trace(' < ' + session.decode(address[0],
|
||||
address[1],
|
||||
data))
|
||||
sock.sendto(data, (client_ip, client_port))
|
||||
elif address[0] == client_ip and address[1] == client_port:
|
||||
log.trace(' > ' + session.decode(address[0],
|
||||
address[1],
|
||||
data))
|
||||
sock.sendto(data, (host_ip, host_port))
|
||||
log.notice(' ] %6d: session: %s -> %s: end' % (session_count,
|
||||
client,
|
||||
host))
|
||||
else:
|
||||
mac = getmac.get_mac_address(ip = client_ip)
|
||||
log.trace(' . request: host not found: %s (%s)' % (client, mac))
|
||||
|
||||
class udp_server(socketserver.ThreadingMixIn, socketserver.UDPServer):
|
||||
pass
|
||||
|
||||
class proxy_server(object):
|
||||
def __init__(self, config, host, port):
|
||||
self.lock = threading.Lock()
|
||||
self.session_timeout = 10
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.server = None
|
||||
self.clients = { }
|
||||
self.config = configuration.configuration()
|
||||
self._load(config)
|
||||
self.session_counter = 0
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
|
||||
def _lock(self):
|
||||
self.lock.acquire()
|
||||
|
||||
def _unlock(self):
|
||||
self.lock.release()
|
||||
|
||||
def _load_client(self, client, depth = 0):
|
||||
if depth > 32:
|
||||
raise error.general('\'clients\'" nesting too deep; circular?')
|
||||
if not self.config.has_section(client):
|
||||
raise error.general('client not found: %s' % (client))
|
||||
for c in self.config.comma_list(client, 'clients', err = False):
|
||||
self._load_client(c, depth + 1)
|
||||
if client in self.clients:
|
||||
raise error.general('repeated client: %s' % (client))
|
||||
host = self.config.get_item(client, 'host', err = False)
|
||||
if host is not None:
|
||||
ips = self.config.comma_list(client, 'ip', err = False)
|
||||
macs = self.config.comma_list(client, 'mac', err = False)
|
||||
if len(ips) != 0 and len(macs) != 0:
|
||||
raise error.general('client has ip and mac: %s' % (client))
|
||||
if len(ips) != 0:
|
||||
keys = ips
|
||||
elif len(macs) != 0:
|
||||
keys = macs
|
||||
else:
|
||||
raise error.general('not client ip or mac: %s' % (client))
|
||||
for key in keys:
|
||||
self.clients[key] = host
|
||||
|
||||
def _load(self, config):
|
||||
self.config.load(config)
|
||||
clients = self.config.comma_list('default', 'clients', err = False)
|
||||
if len(clients) == 0:
|
||||
raise error.general('\'clients\'" entry not found in config [defaults]')
|
||||
for client in clients:
|
||||
self._load_client(client)
|
||||
|
||||
def start(self):
|
||||
log.notice('Proxy: %s:%i' % (self.host, self.port))
|
||||
if self.host == 'all':
|
||||
host = ''
|
||||
else:
|
||||
host = self.host
|
||||
try:
|
||||
self.server = udp_server((host, self.port), udp_handler)
|
||||
except Exception as e:
|
||||
raise error.general('proxy create: %s' % (e))
|
||||
self.server.proxy = self
|
||||
self._lock()
|
||||
try:
|
||||
self.server_thread = threading.Thread(target = self.server.serve_forever)
|
||||
self.server_thread.daemon = True
|
||||
self.server_thread.start()
|
||||
finally:
|
||||
self._unlock()
|
||||
|
||||
def stop(self):
|
||||
self._lock()
|
||||
try:
|
||||
if self.server is not None:
|
||||
self.server.shutdown()
|
||||
self.server.server_close()
|
||||
self.server = None
|
||||
finally:
|
||||
self._unlock()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
def get_host(self, client):
|
||||
host = None
|
||||
self._lock()
|
||||
try:
|
||||
if client in self.clients:
|
||||
host = self.clients[client]
|
||||
else:
|
||||
mac = getmac.get_mac_address(ip = client)
|
||||
if mac in self.clients:
|
||||
host = self.clients[mac]
|
||||
finally:
|
||||
self._unlock()
|
||||
return host
|
||||
|
||||
def get_session_count(self):
|
||||
count = 0
|
||||
self._lock()
|
||||
try:
|
||||
self.session_counter += 1
|
||||
count = self.session_counter
|
||||
finally:
|
||||
self._unlock()
|
||||
return count
|
||||
|
||||
|
||||
def load_log(logfile):
|
||||
if logfile is None:
|
||||
log.default = log.log(streams = ['stdout'])
|
||||
else:
|
||||
log.default = log.log(streams = [logfile])
|
||||
|
||||
def run(args = sys.argv, command_path = None):
|
||||
ec = 0
|
||||
notice = None
|
||||
proxy = None
|
||||
try:
|
||||
description = 'Proxy TFTP sessions from the host running this proxy'
|
||||
description += 'to hosts and ports defined in the configuration file. '
|
||||
description += 'The tool lets you create a farm of hardware and to run '
|
||||
description += 'more than one TFTP test session on a host or multiple '
|
||||
description += 'hosts at once. This proxy service is not considered secure'
|
||||
description += 'and is for use in a secure environment.'
|
||||
|
||||
argsp = argparse.ArgumentParser(prog = 'rtems-tftp-proxy',
|
||||
description = description)
|
||||
argsp.add_argument('-l', '--log',
|
||||
help = 'log file.',
|
||||
type = str, default = None)
|
||||
argsp.add_argument('-v', '--trace',
|
||||
help = 'enable trace logging for debugging.',
|
||||
action = 'store_true', default = False)
|
||||
argsp.add_argument('-c', '--config',
|
||||
help = 'proxy configuation (default: %(default)s).',
|
||||
type = str, default = None)
|
||||
argsp.add_argument('-B', '--bind',
|
||||
help = 'address to bind the proxy too (default: %(default)s).',
|
||||
type = str, default = 'all')
|
||||
argsp.add_argument('-P', '--port',
|
||||
help = 'port to bind the proxy too(default: %(default)s).',
|
||||
type = int, default = '69')
|
||||
|
||||
argopts = argsp.parse_args(args[1:])
|
||||
|
||||
load_log(argopts.log)
|
||||
log.notice('RTEMS Tools - TFTP Proxy, %s' % (version.string()))
|
||||
log.output(log.info(args))
|
||||
log.tracing = argopts.trace
|
||||
|
||||
if argopts.config is None:
|
||||
raise error.general('no config file, see -h')
|
||||
|
||||
proxy = proxy_server(argopts.config, argopts.bind, argopts.port)
|
||||
|
||||
try:
|
||||
proxy.start()
|
||||
proxy.run()
|
||||
except:
|
||||
proxy.stop()
|
||||
raise
|
||||
|
||||
except error.general as gerr:
|
||||
notice = str(gerr)
|
||||
ec = 1
|
||||
except error.internal as ierr:
|
||||
notice = str(ierr)
|
||||
ec = 1
|
||||
except error.exit as eerr:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
notice = 'abort: user terminated'
|
||||
ec = 1
|
||||
except:
|
||||
raise
|
||||
notice = 'abort: unknown error'
|
||||
ec = 1
|
||||
if proxy is not None:
|
||||
del proxy
|
||||
if notice is not None:
|
||||
log.stderr(notice)
|
||||
sys.exit(ec)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
@ -75,11 +75,16 @@ def build(bld):
|
||||
#
|
||||
bld(features = 'py',
|
||||
source = ['tools/boot.py',
|
||||
'tools/cmd-boot-image.py'],
|
||||
'tools/cmd-boot-image.py',
|
||||
'tools/cmd-tftpproxy.py',
|
||||
'tools/tftpproxy.py',
|
||||
'tools/getmac/__init__.py',
|
||||
'tools/getmac/getmac.py'],
|
||||
install_from = '.',
|
||||
install_path = '${PREFIX}/share/rtems/misc')
|
||||
bld.install_files('${PREFIX}/bin',
|
||||
['rtems-boot-image'],
|
||||
['rtems-boot-image',
|
||||
'rtems-tftp-proxy'],
|
||||
chmod = 0o755)
|
||||
bld.install_files('${PREFIX}/share/rtems/tools/config',
|
||||
'tools/config/rtems-boot.ini')
|
||||
|
Loading…
x
Reference in New Issue
Block a user