Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/codespell_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ iff
implementors
inout
interaktive
joinin
JoinIn
merchantibility
microsof
mitre
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ coverage.xml
__pycache__/
doc/scapy/_build
doc/scapy/api
.idea
78 changes: 78 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache:
paths:
- .cache/pip
- venv/
- venv_pypy/

.default:
image: python:3.11
tags:
- docker-ipv6
before_script:
- pip install tox

health:
extends: .default
script:
- tox -e flake8
- tox -e spell
- tox -e twine

mypy:
extends: .default
script:
- tox -e mypy

py311:
image: dissecto/scapy-tests:latest
tags:
- docker-ipv6
script:
- ./.config/ci/test.sh 3.11 non_root

pypy3:
image: dissecto/scapy-tests-pypy:latest
tags:
- docker-ipv6
script:
- ./.config/ci/test.sh pypy3 non_root

.publish:
image: python:latest
needs:
- pypy3
- py311
- mypy
- health
tags:
- docker
script:
- pip install build twine
- python -m build
- TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --verbose --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/*

publish:
allow_failure: true
rules:
- if: $CI_COMMIT_BRANCH == "master"
when: always
- when: never
extends: .publish

publish_tags:
extends: .publish
only:
- tags
except:
- branches

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,4 @@ The documentation (everything unless marked otherwise in `doc/`, and except the

Want to contribute? Great! Please take a few minutes to
[read this](CONTRIBUTING.md)!

9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[build-system]
requires = [ "setuptools>=62.0.0" ]
requires = ["setuptools>=64.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"

[project]
name = "scapy"
dynamic = [ "version", "readme" ]
dynamic = [ "version"]
readme = "README.md"
authors = [
{ name="Philippe BIONDI" },
]
Expand Down Expand Up @@ -59,6 +60,7 @@ all = [
"matplotlib",
]
doc = [
"cryptography>=2.0",
"sphinx>=7.0.0",
"sphinx_rtd_theme>=1.3.0",
"tox>=3.0.0",
Expand All @@ -78,8 +80,7 @@ exclude = [
"doc*",
]

[tool.setuptools.dynamic]
version = { attr="scapy.VERSION" }
[tool.setuptools_scm]

# coverage

Expand Down
10 changes: 6 additions & 4 deletions scapy/contrib/automotive/bmw/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from scapy.packet import Packet, bind_layers
from scapy.fields import ByteField, ShortField, ByteEnumField, X3BytesField, \
StrField, StrFixedLenField, LEIntField, LEThreeBytesField, \
StrField, StrFixedLenField, LEThreeBytesField, \
PacketListField, IntField, IPField, ThreeBytesField, ShortEnumField, \
XStrFixedLenField
from scapy.contrib.automotive.uds import UDS, UDS_RDBI, UDS_DSC, UDS_IOCBI, \
Expand Down Expand Up @@ -321,14 +321,16 @@ class SVK(Packet):
3: "software entry incompatible to hardware entry",
4: "software entry incompatible with other software entry"}

@staticmethod
def get_length(p: Packet):
return len(p.original) - (8 * p.entries_count + 7)

fields_desc = [
ByteEnumField("prog_status1", 0, prog_status_enum),
ByteEnumField("prog_status2", 0, prog_status_enum),
ShortField("entries_count", 0),
SVK_DateField("prog_date", 0),
ByteField("pad1", 0),
LEIntField("prog_milage", 0),
StrFixedLenField("pad2", b'\x00\x00\x00\x00\x00', length=5),
StrFixedLenField("pad", b'\x00', length_from=get_length),
PacketListField("entries", [], SVK_Entry,
count_from=lambda x: x.entries_count)]

Expand Down
10 changes: 5 additions & 5 deletions scapy/contrib/automotive/gm/gmlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
if conf.contribs['GMLAN']['treat-response-pending-as-answer']:
pass
except KeyError:
log_automotive.info("Specify \"conf.contribs['GMLAN'] = "
"{'treat-response-pending-as-answer': True}\" to treat "
"a negative response 'RequestCorrectlyReceived-"
"ResponsePending' as answer of a request. \n"
"The default value is False.")
# log_automotive.info("Specify \"conf.contribs['GMLAN'] = "
# "{'treat-response-pending-as-answer': True}\" to treat "
# "a negative response 'RequestCorrectlyReceived-"
# "ResponsePending' as answer of a request. \n"
# "The default value is False.")
conf.contribs['GMLAN'] = {'treat-response-pending-as-answer': False}

conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = None
Expand Down
11 changes: 5 additions & 6 deletions scapy/contrib/automotive/obd/obd.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import struct

from scapy.contrib.automotive import log_automotive
from scapy.contrib.automotive.obd.iid.iids import *
from scapy.contrib.automotive.obd.mid.mids import *
from scapy.contrib.automotive.obd.pid.pids import *
Expand All @@ -24,11 +23,11 @@
if conf.contribs['OBD']['treat-response-pending-as-answer']:
pass
except KeyError:
log_automotive.info("Specify \"conf.contribs['OBD'] = "
"{'treat-response-pending-as-answer': True}\" to treat "
"a negative response 'requestCorrectlyReceived-"
"ResponsePending' as answer of a request. \n"
"The default value is False.")
# log_automotive.info("Specify \"conf.contribs['OBD'] = "
# "{'treat-response-pending-as-answer': True}\" to treat "
# "a negative response 'requestCorrectlyReceived-"
# "ResponsePending' as answer of a request. \n"
# "The default value is False.")
conf.contribs['OBD'] = {'treat-response-pending-as-answer': False}


Expand Down
10 changes: 10 additions & 0 deletions scapy/contrib/automotive/scanner/enumerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ def _get_initial_requests(self, **kwargs):

def __reduce__(self): # type: ignore
f, t, d = super(ServiceEnumerator, self).__reduce__() # type: ignore

try:
del d["_tester_present_sender"]
except KeyError:
pass

try:
for k, v in d["_request_iterators"].items():
d["_request_iterators"][k] = list(v)
Expand Down Expand Up @@ -287,6 +293,10 @@ def pre_execute(self, socket, state, global_configuration):
except KeyError:
self._tester_present_sender = None

def post_execute(self, socket, state, global_configuration):
# type: (_SocketUnion, EcuState, AutomotiveTestCaseExecutorConfiguration) -> None # noqa: E501
self._tester_present_sender = None

def execute(self, socket, state, **kwargs):
# type: (_SocketUnion, EcuState, Any) -> None
self.check_kwargs(kwargs)
Expand Down
48 changes: 29 additions & 19 deletions scapy/contrib/automotive/scanner/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,20 +156,20 @@ def reset_target(self):

def reconnect(self):
# type: () -> None
if self.reconnect_handler:
try:
if self.socket:
self.socket.close()
except Exception as e:
log_automotive.exception(
"Exception '%s' during socket.close", e)

log_automotive.info("Target reconnect")
socket = self.reconnect_handler()
if not isinstance(socket, SingleConversationSocket):
self.socket = SingleConversationSocket(socket)
else:
self.socket = socket
if not self.reconnect_handler:
return

try:
if self.socket:
self.socket.close()
except Exception as e:
log_automotive.exception(
"Exception '%s' during socket.close", e)

log_automotive.info("Target reconnect")
socket = self.reconnect_handler()
self.socket = socket if isinstance(socket, SingleConversationSocket) \
else SingleConversationSocket(socket)

if self.socket and self.socket.closed:
raise Scapy_Exception(
Expand Down Expand Up @@ -394,6 +394,20 @@ def enter_state(self, prev_state, next_state):
trans_func, trans_kwargs, clean_func = funcs
state_changed = trans_func(
self.socket, self.configuration, trans_kwargs)

if self.socket.closed:
for i in range(5):
try:
self.reconnect()
break
except Exception:
if i == 4:
raise
if self.configuration.stop_event:
self.configuration.stop_event.wait(1)
else:
time.sleep(1)

if state_changed:
self.target_state = next_state

Expand All @@ -410,15 +424,11 @@ def cleanup_state(self):
Executes all collected cleanup functions from a traversed path
:return: None
"""
if not self.socket:
log_automotive.warning("Socket is None! Leaving cleanup_state")
return

for f in self.cleanup_functions:
if not callable(f):
continue
try:
if not f(self.socket, self.configuration):
if not f(self.socket, self.configuration): # type: ignore
log_automotive.info(
"Cleanup function %s failed", repr(f))
except (OSError, ValueError, Scapy_Exception) as e:
Expand Down
11 changes: 5 additions & 6 deletions scapy/contrib/automotive/uds.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
PacketField
from scapy.packet import Packet, bind_layers, NoPayload, Raw
from scapy.config import conf
from scapy.error import log_loading
from scapy.utils import PeriodicSenderThread
from scapy.contrib.isotp import ISOTP

Expand All @@ -35,11 +34,11 @@
if conf.contribs['UDS']['treat-response-pending-as-answer']:
pass
except KeyError:
log_loading.info("Specify \"conf.contribs['UDS'] = "
"{'treat-response-pending-as-answer': True}\" to treat "
"a negative response 'requestCorrectlyReceived-"
"ResponsePending' as answer of a request. \n"
"The default value is False.")
# log_loading.info("Specify \"conf.contribs['UDS'] = "
# "{'treat-response-pending-as-answer': True}\" to treat "
# "a negative response 'requestCorrectlyReceived-"
# "ResponsePending' as answer of a request. \n"
# "The default value is False.")
conf.contribs['UDS'] = {'treat-response-pending-as-answer': False}


Expand Down
42 changes: 40 additions & 2 deletions scapy/contrib/automotive/uds_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ class UDS_DSCEnumerator(UDS_Enumerator, StateGeneratingServiceEnumerator):
_supported_kwargs = copy.copy(ServiceEnumerator._supported_kwargs)
_supported_kwargs.update({
'delay_state_change': (int, lambda x: x >= 0),
'overwrite_timeout': (bool, None)
'overwrite_timeout': (bool, None),
'close_socket_when_entering_session_2': (bool, None)
})
_supported_kwargs["scan_range"] = (
(list, tuple, range), lambda x: max(x) < 0x100 and min(x) >= 0)
Expand All @@ -112,7 +113,12 @@ class UDS_DSCEnumerator(UDS_Enumerator, StateGeneratingServiceEnumerator):
unit-test scenarios, this value should
be set to False, in order to use the
timeout specified by the 'timeout'
argument."""
argument.
:param bool close_socket_when_entering_session_2: False by default.
This enumerator will close the socket
if session 2 (ProgrammingSession)
was entered, if True. This will
force a reconnect by the executor."""

def _get_initial_requests(self, **kwargs):
# type: (Any) -> Iterable[Packet]
Expand Down Expand Up @@ -165,10 +171,21 @@ def get_new_edge(self,
config # type: AutomotiveTestCaseExecutorConfiguration
): # type: (...) -> Optional[_Edge]
edge = super(UDS_DSCEnumerator, self).get_new_edge(socket, config)

try:
close_socket = config[UDS_DSCEnumerator.__name__]["close_socket_when_entering_session_2"] # noqa: E501
except KeyError:
close_socket = False

if edge:
state, new_state = edge
# Force TesterPresent if session is changed
new_state.tp = 1 # type: ignore
try:
if close_socket and new_state.session == 2: # type: ignore
new_state.tp = 0 # type: ignore
except (AttributeError, KeyError):
pass
return state, new_state
return None

Expand All @@ -184,9 +201,30 @@ def enter_state_with_tp(sock, # type: _SocketUnion
delay = conf[UDS_DSCEnumerator.__name__]["delay_state_change"]
except KeyError:
delay = 5

try:
close_socket = conf[UDS_DSCEnumerator.__name__]["close_socket_when_entering_session_2"] # noqa: E501
except KeyError:
close_socket = False

conf.stop_event.wait(delay)
state_changed = UDS_DSCEnumerator.enter_state(
sock, conf, kwargs["req"])

try:
session = kwargs["req"].diagnosticSessionType
except AttributeError:
session = 0

if close_socket and session == 2:
if not hasattr(sock, "ip"):
log_automotive.warning("Likely closing a CAN based socket! "
"This might be a configuration issue.")
log_automotive.info(
"Entered Programming Session: Closing socket connection")
sock.close()
conf.stop_event.wait(delay)

if not state_changed:
UDS_TPEnumerator.cleanup(sock, conf)
return state_changed
Expand Down
Loading