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,20 @@ 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 ] = {}
599602 self ._loaded = []
603+ # Add to meta_path as we are an import provider
604+ if self not in sys .meta_path :
605+ sys .meta_path .append (self )
600606
601607 def find_spec (self , fullname , path , target = None ):
602608 if fullname in self .all_specs :
@@ -606,81 +612,85 @@ def invalidate_caches(self):
606612 pass
607613
608614 def _register_spec (self , spec ):
615+ # Register to known specs
609616 self .all_specs [spec .fullname ] = spec
617+
618+ # If default=True, inject it in the currently loaded modules
610619 if spec .default :
611620 loader = importlib .util .LazyLoader (spec .spec .loader )
612621 spec .spec .loader = loader
613622 module = importlib .util .module_from_spec (spec .spec )
614623 sys .modules [spec .fullname ] = module
615624 loader .exec_module (module )
616625
617- def load (self ):
626+ def load (self , extension : str ):
627+ """
628+ Load a scapy extension.
629+
630+ :param extension: the name of the extension, as installed.
631+ """
618632 try :
619633 import importlib .metadata
620634 except ImportError :
635+ raise ImportError ("Cannot import importlib.metadata ! Upgrade Python." )
636+
637+ # Get extension distribution
638+ distr = importlib .metadata .distribution (extension )
639+
640+ # Check the classifiers
641+ if distr .metadata .get ('License-Expression' , None ) not in self .GPLV2_LICENCES :
642+ log_loading .warning (
643+ "'%s' has no GPLv2 classifier therefore cannot be loaded." % extension
644+ )
621645 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- )
646+
647+ # Create the extension
648+ ext = ScapyExt ()
649+
650+ # Get the top-level declared "import packages"
651+ # HACK: not available nicely in importlib :/
652+ packages = distr .read_text ("top_level.txt" ).split ()
653+
654+ for package in packages :
655+ scapy_ext = importlib .import_module (package )
656+
657+ # We initialize the plugin by calling it's 'scapy_ext' function
658+ try :
659+ scapy_ext_func = scapy_ext .scapy_ext
660+ except AttributeError :
661+ log_loading .warning ("'%s' does not look like a Scapy plugin !" % extension )
662+ return
663+ try :
664+ scapy_ext_func (ext )
665+ except Exception as ex :
666+ log_loading .warning (
667+ "'%s' failed during initialization with %s" % (
668+ extension ,
669+ ex
677670 )
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 )
671+ )
672+ return
673+
674+ # Register all the specs provided by this extension
675+ for spec in ext .specs .values ():
676+ self ._register_spec (spec )
677+
678+ # Add to the extension list
679+ self .exts .append (ext )
680+
681+ # If there are bash autocompletions, add them
682+ if ext .bash_completions :
683+ from scapy .main import _add_bash_autocompletion
684+
685+ for name , script in ext .bash_completions .items ():
686+ _add_bash_autocompletion (name , script )
687+
688+ def loadall (self ):
689+ """
690+ Load all extensions registered in conf.
691+ """
692+ for extension in conf .load_extensions :
693+ self .load (extension )
684694
685695 def __repr__ (self ):
686696 from scapy .utils import pretty_list
@@ -1033,6 +1043,8 @@ class Conf(ConfClass):
10331043 #: netcache holds time-based caches for net operations
10341044 netcache : NetCache = NetCache ()
10351045 geoip_city = None
1046+ #: Scapy extensions that are loaded automatically on load
1047+ load_extensions : List [str ] = []
10361048 # can, tls, http and a few others are not loaded by default
10371049 load_layers : List [str ] = [
10381050 'bluetooth' ,
@@ -1170,11 +1182,6 @@ def __getattribute__(self, attr):
11701182
11711183conf = Conf () # type: Conf
11721184
1173- # Python 3.8 Only
1174- if sys .version_info >= (3 , 8 ):
1175- conf .exts .load ()
1176-
1177-
11781185def crypto_validator (func ):
11791186 # type: (DecoratorCallable) -> DecoratorCallable
11801187 """
0 commit comments