SnowSignal 0.1.1__tar.gz → 0.1.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/.github/workflows/publish-to-test-pypi.yml +2 -2
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/.gitlab/.gitlab-ci.yml +21 -7
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/PKG-INFO +5 -4
- snowsignal-0.1.4.1/_version.py +34 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docker-compose.dev.yml +4 -5
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docker-compose.local-dev.yml +4 -4
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docker-compose.yml +2 -2
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docs/local_dev.md +3 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/pyproject.toml +3 -2
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/configure.py +7 -3
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/dockerfile +3 -1
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/netutils.py +1 -3
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/packet.py +30 -0
- snowsignal-0.1.4.1/snowsignal/pva_packet.py +394 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/snowsignal.py +6 -2
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/udp_relay_receive.py +43 -9
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/udp_relay_transmit.py +112 -11
- snowsignal-0.1.4.1/tests/dockerfile +5 -0
- snowsignal-0.1.4.1/tests/integration/test_snowsignal.py +228 -0
- snowsignal-0.1.4.1/tests/testenv.sh +2 -0
- snowsignal-0.1.4.1/tests/unit/__init__.py +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/tests/unit/test_netutils.py +12 -2
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/tests/unit/test_packet.py +50 -0
- snowsignal-0.1.4.1/tests/unit/test_pvapacket.py +254 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/tests/unit/test_udp_relay_receive.py +1 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/tests/unit/test_udp_relay_transmit.py +4 -3
- snowsignal-0.1.4.1/uv.lock +855 -0
- snowsignal-0.1.1/_version.py +0 -16
- snowsignal-0.1.1/tests/dockerfile +0 -5
- snowsignal-0.1.1/tests/unit/test_snowsignal.py +0 -102
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/.gitignore +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/LICENSE +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/README.md +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docs/local_dev_example.gif +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docs/pvacess_communication_example.png +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docs/socat_test_broadcast.md +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/docs/swarm_setup.md +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/__init__.py +0 -0
- {snowsignal-0.1.1 → snowsignal-0.1.4.1}/snowsignal/__main__.py +0 -0
- {snowsignal-0.1.1/tests/unit → snowsignal-0.1.4.1/tests/integration}/__init__.py +0 -0
@@ -28,7 +28,7 @@ jobs:
|
|
28
28
|
strategy:
|
29
29
|
matrix:
|
30
30
|
os: [ubuntu-latest] #, mac-latest]
|
31
|
-
python-version: ["3.11", "3.12"]
|
31
|
+
python-version: ["3.11", "3.12", "3.13"]
|
32
32
|
runs-on: ${{ matrix.os }}
|
33
33
|
continue-on-error: true
|
34
34
|
steps:
|
@@ -45,7 +45,7 @@ jobs:
|
|
45
45
|
- name: Run tests
|
46
46
|
run: |
|
47
47
|
PYTHON_PATH=$(which python)
|
48
|
-
sudo $PYTHON_PATH -m coverage run --source=. -m unittest discover tests/
|
48
|
+
sudo -E $PYTHON_PATH -m coverage run --source=. -m unittest discover tests/
|
49
49
|
- name: Gather coverage statistics
|
50
50
|
if: ${{ always() }}
|
51
51
|
run: |
|
@@ -1,7 +1,7 @@
|
|
1
1
|
stages:
|
2
2
|
- format-lint
|
3
3
|
- test
|
4
|
-
-
|
4
|
+
- publish
|
5
5
|
- build
|
6
6
|
- deploy
|
7
7
|
|
@@ -26,7 +26,7 @@ format:
|
|
26
26
|
image: "python:$VERSION"
|
27
27
|
parallel:
|
28
28
|
matrix:
|
29
|
-
- VERSION: ['3.11', '3.12']
|
29
|
+
- VERSION: ['3.11', '3.12', '3.13']
|
30
30
|
|
31
31
|
Run unittests:
|
32
32
|
<<: *test_job_template
|
@@ -35,6 +35,8 @@ Run unittests:
|
|
35
35
|
- coverage report -m
|
36
36
|
- coverage xml
|
37
37
|
coverage: '/TOTAL.*\s+(\d+\%)/'
|
38
|
+
variables:
|
39
|
+
FF_NETWORK_PER_BUILD: 1 # Integration tests need to send real UDP broadcasts so could potentially interfere with one another
|
38
40
|
artifacts:
|
39
41
|
when: always
|
40
42
|
reports:
|
@@ -44,27 +46,39 @@ Run unittests:
|
|
44
46
|
|
45
47
|
Publish:
|
46
48
|
<<: *before_script_template
|
47
|
-
stage:
|
49
|
+
stage: publish
|
48
50
|
when: on_success
|
49
51
|
image: python:latest
|
52
|
+
variables:
|
53
|
+
GIT_DEPTH: 0
|
50
54
|
script:
|
55
|
+
- export
|
51
56
|
- pip install .[dist]
|
52
57
|
- python -m build
|
53
|
-
-
|
58
|
+
- HATCH_INDEX_AUTH=${CI_JOB_TOKEN} HATCH_INDEX_USER=gitlab-ci-token
|
59
|
+
python -m hatch publish -r ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/*
|
54
60
|
|
55
61
|
Rebuild Image:
|
56
62
|
stage: build
|
57
63
|
image: docker
|
58
64
|
variables:
|
59
65
|
DOCKER_TAG: $CI_COMMIT_REF_NAME
|
66
|
+
PIPARGS:
|
67
|
+
rules:
|
68
|
+
- if: $CI_COMMIT_BRANCH == "dev"
|
69
|
+
variables:
|
70
|
+
PIPARGS: "--pre"
|
71
|
+
when: on_success
|
72
|
+
- if: $CI_COMMIT_BRANCH == "main"
|
73
|
+
when: on_success
|
74
|
+
- when: never
|
60
75
|
services:
|
61
76
|
- docker:dind
|
62
77
|
script:
|
63
78
|
- echo $NAME
|
64
|
-
- docker build -t snowsignal snowsignal
|
65
79
|
- docker login https://harbor.stfc.ac.uk -u $DOCKER_REG_NAME --password $DOCKER_REG_TOKEN
|
66
|
-
- echo Build Identifiers -
|
67
|
-
- docker build -t harbor.stfc.ac.uk/isis-accelerator-controls/snowsignal:$DOCKER_TAG snowsignal
|
80
|
+
- echo "Build Identifiers - name=${NAME} tag=${DOCKER_TAG} pipargs=${PIPARGS}"
|
81
|
+
- docker build --no-cache --build-arg PIPARGS=${PIPARGS} -t harbor.stfc.ac.uk/isis-accelerator-controls/snowsignal:$DOCKER_TAG snowsignal
|
68
82
|
- docker push harbor.stfc.ac.uk/isis-accelerator-controls/snowsignal:$DOCKER_TAG
|
69
83
|
|
70
84
|
Deploy Development Image:
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: SnowSignal
|
3
|
-
Version: 0.1.1
|
3
|
+
Version: 0.1.4.1
|
4
4
|
Summary: UDP Broadcast Relay
|
5
5
|
Project-URL: Repository, https://github.com/ISISNeutronMuon/SnowSignal
|
6
6
|
Author-email: Ivan Finch <ivan.finch@stfc.ac.uk>
|
@@ -16,14 +16,15 @@ Classifier: Programming Language :: Python
|
|
16
16
|
Classifier: Topic :: System :: Networking
|
17
17
|
Classifier: Typing :: Typed
|
18
18
|
Requires-Python: >=3.11
|
19
|
+
Requires-Dist: cachetools>=5.5
|
19
20
|
Requires-Dist: configargparse>=1.7
|
20
21
|
Requires-Dist: psutil>=5.9
|
21
22
|
Provides-Extra: dist
|
22
23
|
Requires-Dist: build>=1.2; extra == 'dist'
|
23
|
-
Requires-Dist:
|
24
|
+
Requires-Dist: hatch>=1.14; extra == 'dist'
|
24
25
|
Provides-Extra: test
|
25
26
|
Requires-Dist: coverage>=7.6; extra == 'test'
|
26
|
-
Requires-Dist: ruff>0.
|
27
|
+
Requires-Dist: ruff>0.9; extra == 'test'
|
27
28
|
Requires-Dist: scapy~=2.0; extra == 'test'
|
28
29
|
Description-Content-Type: text/markdown
|
29
30
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# file generated by setuptools-scm
|
2
|
+
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"__version__",
|
6
|
+
"__version_tuple__",
|
7
|
+
"version",
|
8
|
+
"version_tuple",
|
9
|
+
"__commit_id__",
|
10
|
+
"commit_id",
|
11
|
+
]
|
12
|
+
|
13
|
+
TYPE_CHECKING = False
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from typing import Tuple
|
16
|
+
from typing import Union
|
17
|
+
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
19
|
+
COMMIT_ID = Union[str, None]
|
20
|
+
else:
|
21
|
+
VERSION_TUPLE = object
|
22
|
+
COMMIT_ID = object
|
23
|
+
|
24
|
+
version: str
|
25
|
+
__version__: str
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
27
|
+
version_tuple: VERSION_TUPLE
|
28
|
+
commit_id: COMMIT_ID
|
29
|
+
__commit_id__: COMMIT_ID
|
30
|
+
|
31
|
+
__version__ = version = '0.1.4.1'
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 4, 1)
|
33
|
+
|
34
|
+
__commit_id__ = commit_id = None
|
@@ -1,22 +1,21 @@
|
|
1
|
-
version: '3.9'
|
2
|
-
|
3
1
|
services:
|
4
2
|
snowsignal:
|
5
3
|
image: harbor.stfc.ac.uk/isis-accelerator-controls/snowsignal:dev
|
6
4
|
environment:
|
7
5
|
SERVICENAME: '{{.Service.Name}}'
|
8
|
-
LOGLEVEL:
|
6
|
+
LOGLEVEL:
|
9
7
|
networks:
|
10
|
-
-
|
8
|
+
- controls
|
11
9
|
deploy:
|
12
10
|
mode: global
|
13
11
|
placement:
|
14
12
|
max_replicas_per_node: 1
|
13
|
+
restart: unless-stopped
|
15
14
|
|
16
15
|
probe:
|
17
16
|
image: harbor.stfc.ac.uk/isis-accelerator-controls/internal-pvagw-tests
|
18
17
|
networks:
|
19
|
-
-
|
18
|
+
- controls
|
20
19
|
deploy:
|
21
20
|
mode: global
|
22
21
|
placement:
|
@@ -10,8 +10,8 @@ services:
|
|
10
10
|
hostname: server1
|
11
11
|
container_name: server1
|
12
12
|
build:
|
13
|
-
context: ./
|
14
|
-
command: "
|
13
|
+
context: ./tests
|
14
|
+
command: "sh /snowsignal/tests/testenv.sh"
|
15
15
|
volumes:
|
16
16
|
- .:/snowsignal/
|
17
17
|
networks:
|
@@ -22,8 +22,8 @@ services:
|
|
22
22
|
hostname: server2
|
23
23
|
container_name: server2
|
24
24
|
build:
|
25
|
-
context: ./
|
26
|
-
command: "
|
25
|
+
context: ./tests
|
26
|
+
command: "sh /snowsignal/tests/testenv.sh"
|
27
27
|
volumes:
|
28
28
|
- .:/snowsignal/
|
29
29
|
networks:
|
@@ -1,5 +1,3 @@
|
|
1
|
-
version: '3.9'
|
2
|
-
|
3
1
|
services:
|
4
2
|
snowsignal:
|
5
3
|
image: harbor.stfc.ac.uk/isis-accelerator-controls/snowsignal:main
|
@@ -11,6 +9,8 @@ services:
|
|
11
9
|
mode: global
|
12
10
|
placement:
|
13
11
|
max_replicas_per_node: 1
|
12
|
+
restart: unless-stopped
|
13
|
+
|
14
14
|
|
15
15
|
networks:
|
16
16
|
controls:
|
@@ -25,6 +25,9 @@ socat - udp-datagram:255.255.255.255:5076,broadcast
|
|
25
25
|
```
|
26
26
|
will allow a user to type text and press enter to send it as the payload of a UDP broadcast message.
|
27
27
|
|
28
|
+
> [!caution]
|
29
|
+
> 'socat' (and `nc`) do not produce valid UDP checksums in their broadcasts. If SnowSignal rebroadcasts a packet from one of these sources it may appear to have an invalid UDP checksum.
|
30
|
+
|
28
31
|
`tcpdump` is used to show a raw dump of received network packets. This command line
|
29
32
|
```
|
30
33
|
tcpdump -e -i eth0 udp and broadcast -vv -X
|
@@ -2,6 +2,7 @@
|
|
2
2
|
name = "SnowSignal"
|
3
3
|
dynamic = ["version"]
|
4
4
|
dependencies = [
|
5
|
+
"cachetools>=5.5",
|
5
6
|
"configargparse>=1.7",
|
6
7
|
"psutil>=5.9",
|
7
8
|
]
|
@@ -33,12 +34,12 @@ Repository = "https://github.com/ISISNeutronMuon/SnowSignal"
|
|
33
34
|
[project.optional-dependencies]
|
34
35
|
test = [
|
35
36
|
"scapy~=2.0",
|
36
|
-
"ruff>0.
|
37
|
+
"ruff>0.9",
|
37
38
|
"coverage>=7.6"
|
38
39
|
]
|
39
40
|
dist = [
|
40
41
|
"build>=1.2",
|
41
|
-
"
|
42
|
+
"hatch>=1.14",
|
42
43
|
]
|
43
44
|
|
44
45
|
[build-system]
|
@@ -20,12 +20,13 @@ class ConfigArgs(NamedTuple):
|
|
20
20
|
mesh_port: int
|
21
21
|
other_relays: list[str]
|
22
22
|
log_level: str
|
23
|
+
decode_pvaccess: bool
|
23
24
|
|
24
25
|
|
25
26
|
def configure(argv: Sequence[str] | None = None) -> ConfigArgs:
|
26
27
|
"""Setup configuration for the SnowSignal service"""
|
27
28
|
|
28
|
-
p = configargparse.ArgParser()
|
29
|
+
p = configargparse.ArgParser(prog="snowsignal")
|
29
30
|
# Remember to add new arguments to the Args class above!
|
30
31
|
p.add_argument(
|
31
32
|
"-t",
|
@@ -66,6 +67,7 @@ def configure(argv: Sequence[str] | None = None) -> ConfigArgs:
|
|
66
67
|
default="info",
|
67
68
|
help="Logging level",
|
68
69
|
)
|
70
|
+
p.add_argument("--decode-pvaccess", action="store_true", help="Attempt to decode and log to INFO pvaccess messages")
|
69
71
|
# Remember to add new arguments to the Args class above!
|
70
72
|
|
71
73
|
# config = p.parse_args(argv)
|
@@ -82,10 +84,12 @@ def configure(argv: Sequence[str] | None = None) -> ConfigArgs:
|
|
82
84
|
loglevel = logging.INFO
|
83
85
|
case "debug":
|
84
86
|
loglevel = logging.DEBUG
|
87
|
+
case _:
|
88
|
+
loglevel = logging.WARNING
|
85
89
|
|
86
90
|
if loglevel < logging.INFO:
|
87
91
|
logging.basicConfig(
|
88
|
-
format="%(asctime)s - %(levelname)s -
|
92
|
+
format="%(asctime)s - %(levelname)s - %(name)s.%(funcName)s: %(message)s",
|
89
93
|
encoding="utf-8",
|
90
94
|
level=loglevel,
|
91
95
|
)
|
@@ -100,7 +104,7 @@ def configure(argv: Sequence[str] | None = None) -> ConfigArgs:
|
|
100
104
|
"Broadcast port (%i) and mesh port (%i) may not be the same", config.broadcast_port, config.mesh_port
|
101
105
|
)
|
102
106
|
raise ValueError(
|
103
|
-
f"Broadcast port ({config.broadcast_port}) and
|
107
|
+
f"Broadcast port ({config.broadcast_port}) and mesh port ({config.mesh_port}) may not be the same"
|
104
108
|
)
|
105
109
|
|
106
110
|
return config
|
@@ -2,8 +2,10 @@
|
|
2
2
|
# psutils requirement in requirements.txt
|
3
3
|
FROM python:3.12-slim
|
4
4
|
|
5
|
+
ARG PIPARGS=
|
6
|
+
|
5
7
|
# Token has read_api and read_repository permissions and so does not need to be kept secret
|
6
8
|
# However it will expire
|
7
|
-
RUN pip install SnowSignal --index-url https://gitlab.stfc.ac.uk/api/v4/projects/5671/packages/pypi/simple
|
9
|
+
RUN pip install SnowSignal ${PIPARGS} --no-cache-dir --index-url https://gitlab.stfc.ac.uk/api/v4/projects/5671/packages/pypi/simple
|
8
10
|
|
9
11
|
CMD ["python", "-m", "snowsignal"]
|
@@ -65,9 +65,7 @@ def get_from_iface(
|
|
65
65
|
if snicaddr.family == family:
|
66
66
|
return getattr(snicaddr, attribute)
|
67
67
|
|
68
|
-
raise ResourceNotFoundException(
|
69
|
-
f"Could not identify the {family}, " "{attribute} associated with interface {iface}"
|
70
|
-
)
|
68
|
+
raise ResourceNotFoundException(f"Could not identify the {family}, {attribute} associated with interface {iface}")
|
71
69
|
|
72
70
|
|
73
71
|
def get_localipv4_from_iface(iface: str) -> str:
|
@@ -48,6 +48,10 @@ class Packet:
|
|
48
48
|
ip_chksum: int | None = None
|
49
49
|
ip_src_addr: str | None = None
|
50
50
|
ip_dst_addr: str | None = None
|
51
|
+
ip_length: int | None = None
|
52
|
+
ipv4_identification: int | None = None
|
53
|
+
ipv4_more_fragments: bool = False
|
54
|
+
ipv4_fragmented_offset: int | None = None
|
51
55
|
|
52
56
|
udp_src_port: int | None = None
|
53
57
|
udp_dst_port: int | None = None
|
@@ -93,6 +97,14 @@ class Packet:
|
|
93
97
|
if self.ip_version != 4:
|
94
98
|
return
|
95
99
|
|
100
|
+
self.ip_length = iph[2]
|
101
|
+
self.ipv4_identification = iph[3]
|
102
|
+
|
103
|
+
# Decode fragment data
|
104
|
+
fragment_flag_and_offset = iph[4]
|
105
|
+
self.ipv4_more_fragments = bool(fragment_flag_and_offset & 0x2000)
|
106
|
+
self.ipv4_fragmented_offset = fragment_flag_and_offset & 0x1FFF
|
107
|
+
|
96
108
|
# Calculate the length (of the header?)
|
97
109
|
ihl = version_ihl & 0xF
|
98
110
|
self._iph_length = ihl * 4
|
@@ -172,3 +184,21 @@ class Packet:
|
|
172
184
|
def change_ethernet_source(self, newmac) -> None:
|
173
185
|
"""Change packet Ethernet source to a new MAC address"""
|
174
186
|
self.raw = self.raw[0:6] + newmac + self.raw[12:]
|
187
|
+
|
188
|
+
def is_ipv4_fragmented(self) -> bool:
|
189
|
+
"""Check if the IPv4 packet is fragmented"""
|
190
|
+
# Check for IP packet fragmentation, only IPv4 packets can be fragmented
|
191
|
+
# https://en.wikipedia.org/wiki/IPv4#Fragmentation_and_reassembly
|
192
|
+
# If a fragment then the More Fragments flag is set True for the first fragment and subsequent flags until the
|
193
|
+
# last segment in which the More Fragments flag is False. However, the Fragment Offset is only zero
|
194
|
+
# in the first fragment.
|
195
|
+
# We can identify a last fragment by the More Fragments flag being False and the Fragment Offset being non-zero.
|
196
|
+
# A non-fragmented IP packet will have the More Fragments flag set False and the Fragment Offset equal to zero.
|
197
|
+
logger.debug(
|
198
|
+
"ip_version = %i, ipv4_more_fragments = %s, ipv4_fragmented_offset = %i",
|
199
|
+
self.ip_version,
|
200
|
+
self.ipv4_more_fragments,
|
201
|
+
self.ipv4_fragmented_offset,
|
202
|
+
)
|
203
|
+
|
204
|
+
return self.ip_version == 4 and (self.ipv4_more_fragments or self.ipv4_fragmented_offset != 0)
|