0

that needs to be executed using the exec function. The script is quite large and contains many class definitions, but when I try to execute the script, the exec function returns an error:

 exec(script)
 File "<string>", line 73, in <module>
 File "<string>", line 79, in EthernetFrame
NameError: name 'EthernetFrameHeader' is not defined

Here is the script code:

import socket
import struct
from enum import Enum
from typing import Callable

class NetworkParseError(Exception):
    pass

class NetworkLevel(Enum):
    DATALINK = 'DataLink layer'
    NETWORK = 'Network layer'
    TRANSPORT = 'Transport layer'
    APPLICATION = 'Application layer'

class NetworkProtocol:
    def __init__(self, raw_data: bytes):
        self.raw_data = raw_data
        self.data_length = len(self.raw_data)

    def get_proto_info(self) -> str:
        raise NetworkParseError('Override function: %s in class: %s' % (self.get_proto_info.__name__,
                                                                           self.__class__.__name__))

    def __str__(self) -> str:
        return self.get_proto_info()

    @staticmethod
    def get_format_address(raw_addr: bytes, *, sep: str = '', function: Callable = str.upper) -> str:
        return f'{sep}'.join(map(function, raw_addr))

    def get_data(self, offset: int, total_length: int = None) -> bytes:
        if self.data_length:
            return self.raw_data[offset:] if total_length is None else self.raw_data[offset:total_length]
        else:
            raise NetworkParseError('No raw data in %s object' % self.__class__.__name__)

    def get_json_proto_header(self):
        raise NotImplementedError('Override function')

class EthernetFrameHeader:
    def __init__(self, src_mac: bytes, dst_mac: bytes, encapsulated_proto: int) -> None:
        self.__src_mac: bytes = src_mac
        self.__dst_mac: bytes = dst_mac
        self.__encapsulated_proto: int = encapsulated_proto

    @property
    def format_src_mac(self) -> str:
        return NetworkProtocol.get_format_address(self.__src_mac, sep=':', function="{:02x}".format).upper()

    @property
    def format_dst_mac(self) -> str:
        return NetworkProtocol.get_format_address(self.__dst_mac, sep=':', function="{:02x}".format).upper()

    @property
    def encapsulated_proto(self) -> int:
        return socket.ntohs(self.__encapsulated_proto)

    @property
    def source_mac(self) -> bytes:
        return self.__src_mac

    @property
    def destination_mac(self) -> bytes:
        return self.__dst_mac

    def __str__(self) -> str:
        result = f'{self.__level.value}\tEthernetProtocol:\n'
        result += f'Src MAC: {self.format_src_mac}\tDst MAC: {self.format_dst_mac}\t' \
                  f'Eth Proto: {self.encapsulated_proto}\n'
        return result


class EthernetFrame(NetworkProtocol):
    def __init__(self, raw_data: bytes):
        NetworkProtocol.__init__(self, raw_data)
        self.__header = self.__parse_data()
        self.__offset: int = 14

    def __parse_data(self) -> EthernetFrameHeader:
        if self.data_length:
            try:
                dst_mac, src_mac, proto = struct.unpack('!6s6sH', self.raw_data[:14])
                return EthernetFrameHeader(src_mac, dst_mac, proto)
            except struct.error:
                raise NetworkParseError("Incorrect Ethernet frame format")
        else:
            raise NetworkParseError(f"No {self.__class__.__name__} header")

    @property
    def header(self) -> EthernetFrameHeader:
        return self.__header

    @property
    def source_mac(self) -> bytes:
        return self.__header.source_mac

    @property
    def destination_mac(self) -> bytes:
        return self.__header.destination_mac

    @property
    def encapsulated_proto(self) -> int:
        return self.__header.encapsulated_proto

    def get_encapsulated_data(self) -> bytes:
        return NetworkProtocol.get_data(self, self.__offset)

    def get_proto_info(self) -> str:
        return str(self.__header)

    def get_json_proto_header(self):
        pass


sniffer = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))

raw_data = sniffer.recvfrom(65535)[0]

print(EthernetFrame(raw_data))

Example of its launch:

...
exec(script_code)

Where script_code contains the above script Maybe exec doesn't allow you to run code with more than one class?

Tried a lot of options, it does not work.

  • Scope with `exec` is a known issue; there's questions like [this](https://stackoverflow.com/questions/1463306/how-to-get-local-variables-updated-when-using-the-exec-call) one from all the way back in 2009 (14 years ago!!) which proposes some solutions. Have you tried any of them? – Random Davis Mar 16 '23 at 18:13

1 Answers1

0

This is not an error with exec fuction, but rather your code. On line 79 you have:

def __parse_data(self) -> EthernetFrameHeader:

You can't return a class that function is in it, because it is not defined (yet). Take this example:

class Test:
    
    def __init__(self):
        pass
    
    def do_something(self) -> Test:  # Test is not defined
        # do something, maybe raise some error, etc.
        return self

This will return an error that 'Test is not defined'.
even if you change it to 'Test()' it will raise the same error.
But what you're actually returning is an object/instance of the class.
This code works:

class Test:
    
    def __init__(self):
        pass
    
    def do_something(self) -> object:  # We're returning object, not class that isn't defined
        # do something, maybe raise some error, etc.
        return self
    

So what you need to do is replace all function return annotations (or what they are called) with object. Here is your code:

import socket
import struct
from enum import Enum
from typing import Callable

class NetworkParseError(Exception):
    pass

class NetworkLevel(Enum):
    DATALINK = 'DataLink layer'
    NETWORK = 'Network layer'
    TRANSPORT = 'Transport layer'
    APPLICATION = 'Application layer'

class NetworkProtocol:
    def __init__(self, raw_data: bytes):
        self.raw_data = raw_data
        self.data_length = len(self.raw_data)

    def get_proto_info(self) -> str:
        raise NetworkParseError('Override function: %s in class: %s' % (self.get_proto_info.__name__,
                                                                           self.__class__.__name__))

    def __str__(self) -> str:
        return self.get_proto_info()

    @staticmethod
    def get_format_address(raw_addr: bytes, *, sep: str = '', function: Callable = str.upper) -> str:
        return f'{sep}'.join(map(function, raw_addr))

    def get_data(self, offset: int, total_length: int = None) -> bytes:
        if self.data_length:
            return self.raw_data[offset:] if total_length is None else self.raw_data[offset:total_length]
        else:
            raise NetworkParseError('No raw data in %s object' % self.__class__.__name__)

    def get_json_proto_header(self):
        raise NotImplementedError('Override function')

class EthernetFrameHeader:
    def __init__(self, src_mac: bytes, dst_mac: bytes, encapsulated_proto: int) -> None:
        self.__src_mac: bytes = src_mac
        self.__dst_mac: bytes = dst_mac
        self.__encapsulated_proto: int = encapsulated_proto

    @property
    def format_src_mac(self) -> str:
        return NetworkProtocol.get_format_address(self.__src_mac, sep=':', function="{:02x}".format).upper()

    @property
    def format_dst_mac(self) -> str:
        return NetworkProtocol.get_format_address(self.__dst_mac, sep=':', function="{:02x}".format).upper()

    @property
    def encapsulated_proto(self) -> int:
        return socket.ntohs(self.__encapsulated_proto)

    @property
    def source_mac(self) -> bytes:
        return self.__src_mac

    @property
    def destination_mac(self) -> bytes:
        return self.__dst_mac

    def __str__(self) -> str:
        result = f'{self.__level.value}\tEthernetProtocol:\n'
        result += f'Src MAC: {self.format_src_mac}\tDst MAC: {self.format_dst_mac}\t' \
                  f'Eth Proto: {self.encapsulated_proto}\n'
        return result


class EthernetFrame(NetworkProtocol):
    def __init__(self, raw_data: bytes):
        NetworkProtocol.__init__(self, raw_data)
        self.__header = self.__parse_data()
        self.__offset: int = 14

    def __parse_data(self) -> object:
        if self.data_length:
            try:
                dst_mac, src_mac, proto = struct.unpack('!6s6sH', self.raw_data[:14])
                return EthernetFrameHeader(src_mac, dst_mac, proto)
            except struct.error:
                raise NetworkParseError("Incorrect Ethernet frame format")
        else:
            raise NetworkParseError(f"No {self.__class__.__name__} header")

    @property
    def header(self) -> object:
        return self.__header

    @property
    def source_mac(self) -> bytes:
        return self.__header.source_mac

    @property
    def destination_mac(self) -> bytes:
        return self.__header.destination_mac

    @property
    def encapsulated_proto(self) -> int:
        return self.__header.encapsulated_proto

    def get_encapsulated_data(self) -> bytes:
        return NetworkProtocol.get_data(self, self.__offset)

    def get_proto_info(self) -> str:
        return str(self.__header)

    def get_json_proto_header(self):
        pass


sniffer = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))

raw_data = sniffer.recvfrom(65535)[0]

print(EthernetFrame(raw_data))
NotMEE12
  • 74
  • 7