77Implementation of the configuration object.
88"""
99
10-
1110import atexit
1211import copy
1312import functools
1413import os
14+ import pathlib
1515import re
1616import socket
1717import sys
@@ -538,7 +538,7 @@ def __repr__(self):
538538
539539
540540class ScapyExt :
541- __slots__ = ["specs" , "name" , "version" ]
541+ __slots__ = ["specs" , "name" , "version" , "bash_completions" ]
542542
543543 class MODE (Enum ):
544544 LAYERS = "layers"
@@ -554,6 +554,7 @@ class ScapyExtSpec:
554554
555555 def __init__ (self ):
556556 self .specs : Dict [str , 'ScapyExt.ScapyExtSpec' ] = {}
557+ self .bash_completions = {}
557558
558559 def config (self , name , version ):
559560 self .name = name
@@ -576,6 +577,9 @@ def register(self, name, mode, path, default=None):
576577 spec .default = bool (importlib .util .find_spec (spec .fullname ))
577578 self .specs [fullname ] = spec
578579
580+ def register_bashcompletion (self , script : pathlib .Path ):
581+ self .bash_completions [script .name ] = script
582+
579583 def __repr__ (self ):
580584 return "<ScapyExt %s %s (%s specs)>" % (
581585 self .name ,
@@ -585,18 +589,19 @@ def __repr__(self):
585589
586590
587591class ExtsManager (importlib .abc .MetaPathFinder ):
588- __slots__ = ["exts" , "_loaded" , " all_specs" ]
592+ __slots__ = ["exts" , "all_specs" ]
589593
590- SCAPY_PLUGIN_CLASSIFIER = 'Framework :: Scapy'
591- GPLV2_CLASSIFIERS = [
592- 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)' ,
593- 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)' ,
594+ GPLV2_LICENCES = [
595+ "GPL-2.0-only" ,
596+ "GPL-2.0-or-later" ,
594597 ]
595598
596599 def __init__ (self ):
597600 self .exts : List [ScapyExt ] = []
598601 self .all_specs : Dict [str , ScapyExt .ScapyExtSpec ] = {}
599- self ._loaded = []
602+ # Add to meta_path as we are an import provider
603+ if self not in sys .meta_path :
604+ sys .meta_path .append (self )
600605
601606 def find_spec (self , fullname , path , target = None ):
602607 if fullname in self .all_specs :
@@ -606,81 +611,87 @@ def invalidate_caches(self):
606611 pass
607612
608613 def _register_spec (self , spec ):
614+ # Register to known specs
609615 self .all_specs [spec .fullname ] = spec
616+
617+ # If default=True, inject it in the currently loaded modules
610618 if spec .default :
611619 loader = importlib .util .LazyLoader (spec .spec .loader )
612620 spec .spec .loader = loader
613621 module = importlib .util .module_from_spec (spec .spec )
614622 sys .modules [spec .fullname ] = module
615623 loader .exec_module (module )
616624
617- def load (self ):
625+ def load (self , extension : str ):
626+ """
627+ Load a scapy extension.
628+
629+ :param extension: the name of the extension, as installed.
630+ """
618631 try :
619632 import importlib .metadata
620633 except ImportError :
634+ raise ImportError ("Cannot import importlib.metadata ! Upgrade Python." )
635+
636+ # Get extension distribution
637+ distr = importlib .metadata .distribution (extension )
638+
639+ # Check the classifiers
640+ if distr .metadata .get ('License-Expression' , None ) not in self .GPLV2_LICENCES :
641+ log_loading .warning (
642+ "'%s' has no GPLv2 classifier therefore cannot be loaded." % extension
643+ )
621644 return
622- for distr in importlib .metadata .distributions ():
623- if any (
624- v == self .SCAPY_PLUGIN_CLASSIFIER
625- for k , v in distr .metadata .items () if k == 'Classifier'
626- ):
627- try :
628- # Python 3.13 raises an internal warning when calling this
629- with warnings .catch_warnings ():
630- warnings .filterwarnings ("ignore" , category = DeprecationWarning )
631- pkg = next (
632- k
633- for k , v in
634- importlib .metadata .packages_distributions ().items ()
635- if distr .name in v
636- )
637- except KeyError :
638- pkg = distr .name
639- if pkg in self ._loaded :
640- continue
641- if not any (
642- v in self .GPLV2_CLASSIFIERS
643- for k , v in distr .metadata .items () if k == 'Classifier'
644- ):
645- log_loading .warning (
646- "'%s' has no GPLv2 classifier therefore cannot be loaded." % pkg # noqa: E501
647- )
648- continue
649- self ._loaded .append (pkg )
650- ext = ScapyExt ()
651- try :
652- scapy_ext = importlib .import_module (pkg )
653- except Exception as ex :
654- log_loading .warning (
655- "'%s' failed during import with %s" % (
656- pkg ,
657- ex
658- )
659- )
660- continue
661- try :
662- scapy_ext_func = scapy_ext .scapy_ext
663- except AttributeError :
664- log_loading .info (
665- "'%s' included the Scapy Framework specifier "
666- "but did not include a scapy_ext" % pkg
667- )
668- continue
669- try :
670- scapy_ext_func (ext )
671- except Exception as ex :
672- log_loading .warning (
673- "'%s' failed during initialization with %s" % (
674- pkg ,
675- ex
676- )
645+
646+ # Create the extension
647+ ext = ScapyExt ()
648+
649+ # Get the top-level declared "import packages"
650+ # HACK: not available nicely in importlib :/
651+ packages = distr .read_text ("top_level.txt" ).split ()
652+
653+ for package in packages :
654+ scapy_ext = importlib .import_module (package )
655+
656+ # We initialize the plugin by calling it's 'scapy_ext' function
657+ try :
658+ scapy_ext_func = scapy_ext .scapy_ext
659+ except AttributeError :
660+ log_loading .warning (
661+ "'%s' does not look like a Scapy plugin !" % extension
662+ )
663+ return
664+ try :
665+ scapy_ext_func (ext )
666+ except Exception as ex :
667+ log_loading .warning (
668+ "'%s' failed during initialization with %s" % (
669+ extension ,
670+ ex
677671 )
678- continue
679- for spec in ext .specs .values ():
680- self ._register_spec (spec )
681- self .exts .append (ext )
682- if self not in sys .meta_path :
683- sys .meta_path .append (self )
672+ )
673+ return
674+
675+ # Register all the specs provided by this extension
676+ for spec in ext .specs .values ():
677+ self ._register_spec (spec )
678+
679+ # Add to the extension list
680+ self .exts .append (ext )
681+
682+ # If there are bash autocompletions, add them
683+ if ext .bash_completions :
684+ from scapy .main import _add_bash_autocompletion
685+
686+ for name , script in ext .bash_completions .items ():
687+ _add_bash_autocompletion (name , script )
688+
689+ def loadall (self ) -> None :
690+ """
691+ Load all extensions registered in conf.
692+ """
693+ for extension in conf .load_extensions :
694+ self .load (extension )
684695
685696 def __repr__ (self ):
686697 from scapy .utils import pretty_list
@@ -1033,6 +1044,8 @@ class Conf(ConfClass):
10331044 #: netcache holds time-based caches for net operations
10341045 netcache : NetCache = NetCache ()
10351046 geoip_city = None
1047+ #: Scapy extensions that are loaded automatically on load
1048+ load_extensions : List [str ] = []
10361049 # can, tls, http and a few others are not loaded by default
10371050 load_layers : List [str ] = [
10381051 'bluetooth' ,
@@ -1170,10 +1183,6 @@ def __getattribute__(self, attr):
11701183
11711184conf = Conf () # type: Conf
11721185
1173- # Python 3.8 Only
1174- if sys .version_info >= (3 , 8 ):
1175- conf .exts .load ()
1176-
11771186
11781187def crypto_validator (func ):
11791188 # type: (DecoratorCallable) -> DecoratorCallable
0 commit comments