pydhcp 0.2.1rc1__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.
- pydhcp-0.2.1rc1/.gitignore +20 -0
- pydhcp-0.2.1rc1/CHANGELOG.md +71 -0
- pydhcp-0.2.1rc1/LICENSE +21 -0
- pydhcp-0.2.1rc1/PKG-INFO +116 -0
- pydhcp-0.2.1rc1/README.md +82 -0
- pydhcp-0.2.1rc1/benchmarks/README.md +25 -0
- pydhcp-0.2.1rc1/benchmarks/bench_options.py +58 -0
- pydhcp-0.2.1rc1/benchmarks/bench_parse.py +77 -0
- pydhcp-0.2.1rc1/docs/api.md +23 -0
- pydhcp-0.2.1rc1/docs/deployment.md +45 -0
- pydhcp-0.2.1rc1/docs/examples.md +50 -0
- pydhcp-0.2.1rc1/docs/faq.md +13 -0
- pydhcp-0.2.1rc1/docs/index.md +44 -0
- pydhcp-0.2.1rc1/docs/options.md +103 -0
- pydhcp-0.2.1rc1/docs/troubleshooting.md +41 -0
- pydhcp-0.2.1rc1/examples/dhcpd.py +48 -0
- pydhcp-0.2.1rc1/examples/listener.py +18 -0
- pydhcp-0.2.1rc1/mkdocs.yml +46 -0
- pydhcp-0.2.1rc1/pyproject.toml +53 -0
- pydhcp-0.2.1rc1/src/pydhcp/__init__.py +107 -0
- pydhcp-0.2.1rc1/src/pydhcp/_options.py +50 -0
- pydhcp-0.2.1rc1/src/pydhcp/_platform_utils.py +503 -0
- pydhcp-0.2.1rc1/src/pydhcp/_utils.py +24 -0
- pydhcp-0.2.1rc1/src/pydhcp/cli.py +108 -0
- pydhcp-0.2.1rc1/src/pydhcp/config.py +16 -0
- pydhcp-0.2.1rc1/src/pydhcp/constants.py +9 -0
- pydhcp-0.2.1rc1/src/pydhcp/enum/__init__.py +56 -0
- pydhcp-0.2.1rc1/src/pydhcp/enum/_optioncode_types.py +116 -0
- pydhcp-0.2.1rc1/src/pydhcp/enum/messagetype.py +89 -0
- pydhcp-0.2.1rc1/src/pydhcp/enum/optioncode.py +844 -0
- pydhcp-0.2.1rc1/src/pydhcp/lease.py +158 -0
- pydhcp-0.2.1rc1/src/pydhcp/listener.py +437 -0
- pydhcp-0.2.1rc1/src/pydhcp/log.py +2 -0
- pydhcp-0.2.1rc1/src/pydhcp/message.py +315 -0
- pydhcp-0.2.1rc1/src/pydhcp/metrics.py +17 -0
- pydhcp-0.2.1rc1/src/pydhcp/netutils.py +140 -0
- pydhcp-0.2.1rc1/src/pydhcp/options.py +217 -0
- pydhcp-0.2.1rc1/src/pydhcp/optiontype.py +1074 -0
- pydhcp-0.2.1rc1/src/pydhcp/py.typed +1 -0
- pydhcp-0.2.1rc1/src/pydhcp/server.py +297 -0
- pydhcp-0.2.1rc1/tests/integration/test_dora.py +290 -0
- pydhcp-0.2.1rc1/tests/test_async.py +69 -0
- pydhcp-0.2.1rc1/tests/test_async_concurrency.py +127 -0
- pydhcp-0.2.1rc1/tests/test_basic.py +40 -0
- pydhcp-0.2.1rc1/tests/test_cli.py +83 -0
- pydhcp-0.2.1rc1/tests/test_lease_backend.py +93 -0
- pydhcp-0.2.1rc1/tests/test_malformed_packets.py +72 -0
- pydhcp-0.2.1rc1/tests/test_message.py +77 -0
- pydhcp-0.2.1rc1/tests/test_option_types.py +374 -0
- pydhcp-0.2.1rc1/tests/test_options.py +369 -0
- pydhcp-0.2.1rc1/tests/test_permissions.py +32 -0
- pydhcp-0.2.1rc1/tests/test_platform_interfaces.py +29 -0
- pydhcp-0.2.1rc1/tests/test_rfc_compliance.py +113 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Build/dist output and generated docs site
|
|
2
|
+
dist/
|
|
3
|
+
build/
|
|
4
|
+
site/
|
|
5
|
+
|
|
6
|
+
# Agent/harness tooling
|
|
7
|
+
AGENTS.md
|
|
8
|
+
.agents/
|
|
9
|
+
CLAUDE.md
|
|
10
|
+
CLAUDE.local.md
|
|
11
|
+
.claude/
|
|
12
|
+
|
|
13
|
+
# Python-specific patterns
|
|
14
|
+
__pycache__/
|
|
15
|
+
*.py[cod]
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.venv/
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.1-rc.1] - 2026-07-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Expanded DHCP option-type registrations so common well-defined options decode to typed values.
|
|
14
|
+
- Added a "Common DHCP Options" docs page with typed examples and updated the custom-options example to prefer typed assignment.
|
|
15
|
+
- Added stricter option-type validation and `ClasslessRoute` truncation checks.
|
|
16
|
+
- Added wildcard listener `per_interface` support and exported `PktInfoUdpTransport`.
|
|
17
|
+
- Added typed codecs and registrations for policy filters, static routes, user classes, encapsulated vendor/relay sub-options, name service search, subnet selection, and RDNSS selection.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Made `DhcpMessage.encode()` idempotent and tolerant of reserved flag bits during decode.
|
|
21
|
+
- Corrected `DhcpMessage.dumps()` field labels and `secs` packing behavior.
|
|
22
|
+
|
|
23
|
+
## [0.2.0] - 2026-07-12
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Created `benchmarks/bench_parse.py` packet parsing and serialization performance benchmarks.
|
|
27
|
+
- Added `benchmarks/README.md` documenting performance baselines.
|
|
28
|
+
- Added `AsyncDhcpListener` and `AsyncDhcpServer` classes implementing asyncio-based event loop integration.
|
|
29
|
+
- Added integration tests for async server in `tests/test_async.py`.
|
|
30
|
+
- Configured MkDocs documentation with Material theme and `mkdocstrings` auto-generated API references.
|
|
31
|
+
- Added `Transport`, `UdpTransport`, and `RequestContext` classes for structured transport-layer abstraction and Interface tracking.
|
|
32
|
+
- Added comprehensive unit testing coverage for packet deserialization and type-safe dictionary operations in `test_message.py` and `test_options.py`.
|
|
33
|
+
- Added integration tests verifying standard client DORA sequences and RFC 2131 routing logic in `tests/integration/test_dora.py`.
|
|
34
|
+
- Added missing `ALL_VPNS` option (Tag 254) to `DhcpOptionCode` after comparing against the latest IANA registry.
|
|
35
|
+
- Populated default `ROUTER` and `DNS` lease options in `DhcpServer.acquire_lease` using the server's interface IP.
|
|
36
|
+
- Added pluggable lease backend API (`LeaseBackend`) and implementations (`InMemoryLeaseBackend`, `FileLeaseBackend`) for state preservation and persistence.
|
|
37
|
+
- Added unit and integration tests for lease backends in `tests/test_lease_backend.py` and `tests/integration/test_dora.py`.
|
|
38
|
+
- Added validation checks for hardware address length (hlen <= 16) during packet decoding.
|
|
39
|
+
- Added socket binding error diagnostics that offer actionable suggestions for permission and address-in-use errors.
|
|
40
|
+
- Added unit tests for binding error handling and malformed packet input in `tests/test_permissions.py` and `tests/test_malformed_packets.py`.
|
|
41
|
+
- Added command-line interface (CLI) with `interfaces`, `server`, `packet`, and `bench` subcommands.
|
|
42
|
+
- Added JSON-based configuration loading support in `config.py`.
|
|
43
|
+
- Added in-process metrics counters (`packets_received`, `packets_sent`, etc.) in `metrics.py` to support observability.
|
|
44
|
+
- Added option parsing and lease allocation performance benchmarks in `benchmarks/bench_options.py`.
|
|
45
|
+
- Added async DHCP server concurrency stress tests under `tests/test_async_concurrency.py`.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- Configured strict typechecking configuration in `pyproject.toml` and resolved all mypy type-checking errors across the library.
|
|
49
|
+
- Refactored `DhcpMessageType` and `OptionOverload` to implement the `DhcpOptionType` interface directly and avoid PEP 561 / type conflicts with `BaseFixedLengthInteger` and Enums.
|
|
50
|
+
- Refactored `DhcpServer.handle` to accept `RequestContext` instead of `SocketSession`, split processing into message-type specific handlers (`handle_discover`, etc.), and comply strictly with RFC 2131 routing paths.
|
|
51
|
+
- Enriched `NetworkInterface` and `host_ip_interfaces()` to yield fully detailed adapter metadata.
|
|
52
|
+
- Refactored `DhcpServer` and `AsyncDhcpServer` to accept and delegate lease lifecycle events (allocate, lookup, renew, release) to a configurable `lease_backend`.
|
|
53
|
+
- Refactored options parsing in `DhcpOptions.decode` to handle truncated option lengths gracefully by logging a warning and parsing remaining bytes instead of crashing.
|
|
54
|
+
- Added debug-level logging for packet arrival and lease allocation including client XIDs.
|
|
55
|
+
- Renamed misspelled `contants.py` to `constants.py` and updated all internal references.
|
|
56
|
+
- Added strict `__all__` public exports list to `src/pydhcp/__init__.py`.
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
- Fixed bug in `ClasslessRoute` destination descriptor parsing/serialization that caused incorrect length calculations for CIDR/8.
|
|
60
|
+
- Fixed forward reference type resolution issue for `DhcpOption` in `_options.py`.
|
|
61
|
+
- Fixed options encoding `OverflowError` when option size limit is infinite.
|
|
62
|
+
- Fixed `BaseDhcpOptionCode.__int__` returning constant zero, correcting enum integer conversion for option codes.
|
|
63
|
+
|
|
64
|
+
## [0.1.0] - 2026-07-11
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- Initial release.
|
|
69
|
+
|
|
70
|
+
[0.2.0]: https://github.com/jose-pr/pydhcp/compare/v0.1.0...v0.2.0
|
|
71
|
+
[0.1.0]: https://github.com/jose-pr/pydhcp/releases/tag/v0.1.0
|
pydhcp-0.2.1rc1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jose-pr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pydhcp-0.2.1rc1/PKG-INFO
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pydhcp
|
|
3
|
+
Version: 0.2.1rc1
|
|
4
|
+
Summary: A Python DHCP library and server implementation
|
|
5
|
+
Project-URL: Homepage, https://github.com/jose-pr/pydhcp/
|
|
6
|
+
Project-URL: Documentation, https://jose-pr.github.io/pydhcp/
|
|
7
|
+
Project-URL: Issues, https://github.com/jose-pr/pydhcp/issues
|
|
8
|
+
Author: jose-pr
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build; extra == 'dev'
|
|
23
|
+
Requires-Dist: hatchling; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
25
|
+
Requires-Dist: pip-audit; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
28
|
+
Requires-Dist: twine; extra == 'dev'
|
|
29
|
+
Provides-Extra: docs
|
|
30
|
+
Requires-Dist: mkdocs; extra == 'docs'
|
|
31
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
32
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# pydhcp
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/pydhcp/)
|
|
38
|
+
[](https://pypi.org/project/pydhcp/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
[](https://jose-pr.github.io/pydhcp/)
|
|
41
|
+
|
|
42
|
+
A Python DHCP library and server implementation.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- **DHCP Packet Parsing** — Full support for parsing and constructing DHCP packets.
|
|
47
|
+
- **DHCP Server** — Highly customizable and async-friendly DHCP server.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install pydhcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
### Synchronous Server
|
|
58
|
+
```python
|
|
59
|
+
from pydhcp.server import DhcpServer
|
|
60
|
+
|
|
61
|
+
server = DhcpServer()
|
|
62
|
+
server.listen()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Asynchronous Server
|
|
66
|
+
```python
|
|
67
|
+
import asyncio
|
|
68
|
+
from pydhcp.server import AsyncDhcpServer
|
|
69
|
+
|
|
70
|
+
async def main():
|
|
71
|
+
server = AsyncDhcpServer()
|
|
72
|
+
await server.start()
|
|
73
|
+
# Keep running or handle other async tasks
|
|
74
|
+
# To stop: await server.stop()
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Command Line Interface (CLI)
|
|
80
|
+
|
|
81
|
+
`pydhcp` includes a command line interface for listing network adapters, decoding packets, running benchmarks, and starting servers.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# List all network interfaces
|
|
85
|
+
pydhcp interfaces
|
|
86
|
+
|
|
87
|
+
# Decode a hex-encoded DHCP packet
|
|
88
|
+
pydhcp packet --decode "01010600..."
|
|
89
|
+
|
|
90
|
+
# Run performance benchmarks
|
|
91
|
+
pydhcp bench
|
|
92
|
+
|
|
93
|
+
# Start the DHCP server from JSON or INI config
|
|
94
|
+
pydhcp server --config config.json
|
|
95
|
+
|
|
96
|
+
# Increase logging while debugging
|
|
97
|
+
pydhcp server --listen 127.0.0.1:6767 --log-level debug
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
See development notes for environment setup, dependency install, and test commands.
|
|
103
|
+
|
|
104
|
+
### Releasing
|
|
105
|
+
|
|
106
|
+
This project follows [Semantic Versioning](https://semver.org/) and keeps a
|
|
107
|
+
[`CHANGELOG.md`](CHANGELOG.md). Pushing a tag matching `v*` triggers the release
|
|
108
|
+
workflow.
|
|
109
|
+
|
|
110
|
+
### Documentation site
|
|
111
|
+
|
|
112
|
+
MkDocs builds the API reference from `docs/`, published on every release. The docs also include a "Common DHCP Options" page with typed examples.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# pydhcp
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pydhcp/)
|
|
4
|
+
[](https://pypi.org/project/pydhcp/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://jose-pr.github.io/pydhcp/)
|
|
7
|
+
|
|
8
|
+
A Python DHCP library and server implementation.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **DHCP Packet Parsing** — Full support for parsing and constructing DHCP packets.
|
|
13
|
+
- **DHCP Server** — Highly customizable and async-friendly DHCP server.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install pydhcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
### Synchronous Server
|
|
24
|
+
```python
|
|
25
|
+
from pydhcp.server import DhcpServer
|
|
26
|
+
|
|
27
|
+
server = DhcpServer()
|
|
28
|
+
server.listen()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Asynchronous Server
|
|
32
|
+
```python
|
|
33
|
+
import asyncio
|
|
34
|
+
from pydhcp.server import AsyncDhcpServer
|
|
35
|
+
|
|
36
|
+
async def main():
|
|
37
|
+
server = AsyncDhcpServer()
|
|
38
|
+
await server.start()
|
|
39
|
+
# Keep running or handle other async tasks
|
|
40
|
+
# To stop: await server.stop()
|
|
41
|
+
|
|
42
|
+
asyncio.run(main())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Command Line Interface (CLI)
|
|
46
|
+
|
|
47
|
+
`pydhcp` includes a command line interface for listing network adapters, decoding packets, running benchmarks, and starting servers.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# List all network interfaces
|
|
51
|
+
pydhcp interfaces
|
|
52
|
+
|
|
53
|
+
# Decode a hex-encoded DHCP packet
|
|
54
|
+
pydhcp packet --decode "01010600..."
|
|
55
|
+
|
|
56
|
+
# Run performance benchmarks
|
|
57
|
+
pydhcp bench
|
|
58
|
+
|
|
59
|
+
# Start the DHCP server from JSON or INI config
|
|
60
|
+
pydhcp server --config config.json
|
|
61
|
+
|
|
62
|
+
# Increase logging while debugging
|
|
63
|
+
pydhcp server --listen 127.0.0.1:6767 --log-level debug
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
See development notes for environment setup, dependency install, and test commands.
|
|
69
|
+
|
|
70
|
+
### Releasing
|
|
71
|
+
|
|
72
|
+
This project follows [Semantic Versioning](https://semver.org/) and keeps a
|
|
73
|
+
[`CHANGELOG.md`](CHANGELOG.md). Pushing a tag matching `v*` triggers the release
|
|
74
|
+
workflow.
|
|
75
|
+
|
|
76
|
+
### Documentation site
|
|
77
|
+
|
|
78
|
+
MkDocs builds the API reference from `docs/`, published on every release. The docs also include a "Common DHCP Options" page with typed examples.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# DHCP Library Performance Benchmarks
|
|
2
|
+
|
|
3
|
+
This directory contains the benchmarks for measuring the serialization and deserialization performance of the `pydhcp` library.
|
|
4
|
+
|
|
5
|
+
## Benchmarks
|
|
6
|
+
|
|
7
|
+
### 1. Packet Parsing & Serialization (`benchmarks/bench_parse.py`)
|
|
8
|
+
Measures the operations per second (ops/sec) and total duration for 10,000 iterations of:
|
|
9
|
+
- **Decode**: Deserializing a typical `DHCPDISCOVER` packet payload into a `DhcpMessage` object.
|
|
10
|
+
- **Encode**: Serializing a constructed `DhcpMessage` object back into raw bytes.
|
|
11
|
+
|
|
12
|
+
#### Running the Benchmark
|
|
13
|
+
From the repository root directory, run:
|
|
14
|
+
```bash
|
|
15
|
+
python benchmarks/bench_parse.py
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Performance Baseline
|
|
19
|
+
|
|
20
|
+
The baseline measurements taken on a Windows development machine (Python 3.12) are as follows:
|
|
21
|
+
|
|
22
|
+
| Operation | Total Time (10k iterations) | Throughput (ops/sec) |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| **Decode** (Deserialization) | ~0.11s | ~92,600 ops/sec |
|
|
25
|
+
| **Encode** (Serialization) | ~0.08s | ~120,800 ops/sec |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import timeit
|
|
2
|
+
import sys
|
|
3
|
+
import pathlib
|
|
4
|
+
|
|
5
|
+
# Ensure src/ is in the import path
|
|
6
|
+
SRC_DIR = pathlib.Path(__file__).parent.parent / "src"
|
|
7
|
+
sys.path.insert(0, SRC_DIR.as_posix())
|
|
8
|
+
|
|
9
|
+
from pydhcp.options import DhcpOptions
|
|
10
|
+
from pydhcp.enum import DhcpOptionCode
|
|
11
|
+
from pydhcp.netutils import IPv4
|
|
12
|
+
from pydhcp.lease import InMemoryLeaseBackend
|
|
13
|
+
|
|
14
|
+
def build_options_payload(option_count: int) -> bytearray:
|
|
15
|
+
opts = DhcpOptions()
|
|
16
|
+
for i in range(1, option_count + 1):
|
|
17
|
+
opts[i] = bytearray([192, 168, 1, i])
|
|
18
|
+
return opts.encode()
|
|
19
|
+
|
|
20
|
+
def test_memory_usage():
|
|
21
|
+
backend = InMemoryLeaseBackend()
|
|
22
|
+
options = DhcpOptions()
|
|
23
|
+
for i in range(1000):
|
|
24
|
+
client_id = f"client-{i}"
|
|
25
|
+
ip = IPv4("192.168.1.1")
|
|
26
|
+
backend.allocate(client_id, ip, 3600, options)
|
|
27
|
+
|
|
28
|
+
def run_benchmarks(iterations: int = 10000):
|
|
29
|
+
print(f"--- Running DHCP Options & Memory Benchmarks ({iterations:,} iterations) ---")
|
|
30
|
+
|
|
31
|
+
p0 = memoryview(build_options_payload(0))
|
|
32
|
+
p5 = memoryview(build_options_payload(5))
|
|
33
|
+
p20 = memoryview(build_options_payload(20))
|
|
34
|
+
|
|
35
|
+
t0 = timeit.timeit(lambda: DhcpOptions().decode(p0), number=iterations)
|
|
36
|
+
t5 = timeit.timeit(lambda: DhcpOptions().decode(p5), number=iterations)
|
|
37
|
+
t20 = timeit.timeit(lambda: DhcpOptions().decode(p20), number=iterations)
|
|
38
|
+
|
|
39
|
+
print(f"Decode with 0 options: {t0:.4f}s ({iterations/t0:.1f} ops/sec)")
|
|
40
|
+
print(f"Decode with 5 options: {t5:.4f}s ({iterations/t5:.1f} ops/sec)")
|
|
41
|
+
print(f"Decode with 20 options: {t20:.4f}s ({iterations/t20:.1f} ops/sec)")
|
|
42
|
+
|
|
43
|
+
opts = DhcpOptions()
|
|
44
|
+
opts[DhcpOptionCode.SUBNET_MASK] = IPv4("255.255.255.0")
|
|
45
|
+
|
|
46
|
+
def round_trip():
|
|
47
|
+
encoded = opts.encode()
|
|
48
|
+
decoded = DhcpOptions()
|
|
49
|
+
decoded.decode(memoryview(encoded))
|
|
50
|
+
|
|
51
|
+
trt = timeit.timeit(round_trip, number=iterations)
|
|
52
|
+
print(f"Round-trip encode/decode: {trt:.4f}s ({iterations/trt:.1f} ops/sec)")
|
|
53
|
+
|
|
54
|
+
t_mem = timeit.timeit(test_memory_usage, number=100)
|
|
55
|
+
print(f"1000 lease allocations (x100 reps): {t_mem:.4f}s ({100/t_mem:.1f} reps/sec)")
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
run_benchmarks()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import timeit
|
|
2
|
+
import sys
|
|
3
|
+
import pathlib
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
|
|
6
|
+
# Ensure src/ is in the import path
|
|
7
|
+
SRC_DIR = pathlib.Path(__file__).parent.parent / "src"
|
|
8
|
+
sys.path.insert(0, SRC_DIR.as_posix())
|
|
9
|
+
|
|
10
|
+
from pydhcp.message import DhcpMessage
|
|
11
|
+
from pydhcp.enum import OpCode, HardwareAddressType, Flags, DhcpOptionCode, DhcpMessageType
|
|
12
|
+
from pydhcp.netutils import IPv4
|
|
13
|
+
from pydhcp.options import DhcpOptions
|
|
14
|
+
|
|
15
|
+
def build_benchmark_payload() -> bytes:
|
|
16
|
+
options = DhcpOptions()
|
|
17
|
+
options[DhcpOptionCode.DHCP_MESSAGE_TYPE] = bytearray([DhcpMessageType.DHCPDISCOVER.value])
|
|
18
|
+
options[DhcpOptionCode.CLIENT_IDENTIFIER] = bytearray([1, 0, 17, 34, 51, 68, 85])
|
|
19
|
+
options[DhcpOptionCode.PARAMETER_REQUEST_LIST] = bytearray([1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252])
|
|
20
|
+
|
|
21
|
+
msg = DhcpMessage(
|
|
22
|
+
op=OpCode.BOOTREQUEST,
|
|
23
|
+
htype=HardwareAddressType.ETHERNET,
|
|
24
|
+
hlen=6,
|
|
25
|
+
hops=0,
|
|
26
|
+
xid=0x3903F326,
|
|
27
|
+
secs=timedelta(seconds=0),
|
|
28
|
+
flags=Flags.UNICAST,
|
|
29
|
+
ciaddr=IPv4('0.0.0.0'),
|
|
30
|
+
yiaddr=IPv4('0.0.0.0'),
|
|
31
|
+
siaddr=IPv4('0.0.0.0'),
|
|
32
|
+
giaddr=IPv4('0.0.0.0'),
|
|
33
|
+
chaddr=b'\x00\x11\x22\x33\x44\x55',
|
|
34
|
+
sname='',
|
|
35
|
+
file='',
|
|
36
|
+
options=options
|
|
37
|
+
)
|
|
38
|
+
return bytes(msg.encode())
|
|
39
|
+
|
|
40
|
+
PAYLOAD_BYTES = build_benchmark_payload()
|
|
41
|
+
|
|
42
|
+
def run_benchmarks(iterations: int = 10000) -> dict[str, float]:
|
|
43
|
+
print(f"--- Running DHCP Packet Parsing Benchmarks ({iterations:,} iterations) ---")
|
|
44
|
+
|
|
45
|
+
# 1. Decode Benchmark
|
|
46
|
+
# We use memoryview(PAYLOAD_BYTES) as is typically done in the listener
|
|
47
|
+
payload_mv = memoryview(PAYLOAD_BYTES)
|
|
48
|
+
|
|
49
|
+
def test_decode() -> None:
|
|
50
|
+
DhcpMessage.decode(payload_mv)
|
|
51
|
+
|
|
52
|
+
decode_time = timeit.timeit(test_decode, number=iterations)
|
|
53
|
+
decode_ops_per_sec = iterations / decode_time
|
|
54
|
+
print(f"Decode: {decode_time:.4f} seconds ({decode_ops_per_sec:.1f} ops/sec)")
|
|
55
|
+
|
|
56
|
+
# 2. Encode Benchmark
|
|
57
|
+
msg = DhcpMessage.decode(payload_mv)
|
|
58
|
+
|
|
59
|
+
def test_encode() -> None:
|
|
60
|
+
msg.encode()
|
|
61
|
+
|
|
62
|
+
encode_time = timeit.timeit(test_encode, number=iterations)
|
|
63
|
+
encode_ops_per_sec = iterations / encode_time
|
|
64
|
+
print(f"Encode: {encode_time:.4f} seconds ({encode_ops_per_sec:.1f} ops/sec)")
|
|
65
|
+
|
|
66
|
+
# Return stats for documentation
|
|
67
|
+
return {
|
|
68
|
+
"iterations": iterations,
|
|
69
|
+
"decode_time": decode_time,
|
|
70
|
+
"decode_ops": decode_ops_per_sec,
|
|
71
|
+
"encode_time": encode_time,
|
|
72
|
+
"encode_ops": encode_ops_per_sec,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
run_benchmarks()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
This section provides references for the primary classes in the `pydhcp` package.
|
|
4
|
+
|
|
5
|
+
## DhcpMessage
|
|
6
|
+
|
|
7
|
+
::: pydhcp.message.DhcpMessage
|
|
8
|
+
|
|
9
|
+
## DhcpServer
|
|
10
|
+
|
|
11
|
+
::: pydhcp.server.DhcpServer
|
|
12
|
+
|
|
13
|
+
## AsyncDhcpServer
|
|
14
|
+
|
|
15
|
+
::: pydhcp.server.AsyncDhcpServer
|
|
16
|
+
|
|
17
|
+
## DhcpListener
|
|
18
|
+
|
|
19
|
+
::: pydhcp.listener.DhcpListener
|
|
20
|
+
|
|
21
|
+
## AsyncDhcpListener
|
|
22
|
+
|
|
23
|
+
::: pydhcp.listener.AsyncDhcpListener
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Deployment
|
|
2
|
+
|
|
3
|
+
`pydhcp` is a pure-Python library, so deployment is mostly about running it with the right permissions and network layout.
|
|
4
|
+
|
|
5
|
+
## Production checklist
|
|
6
|
+
|
|
7
|
+
1. Run the server under a dedicated service account.
|
|
8
|
+
2. Make sure the host can bind the DHCP ports you expect to use.
|
|
9
|
+
3. Confirm the selected interface has the address range you want to serve.
|
|
10
|
+
4. Keep lease persistence enabled if you need stable client assignment across restarts.
|
|
11
|
+
|
|
12
|
+
## systemd
|
|
13
|
+
|
|
14
|
+
For Linux services, run the server from a unit that starts the CLI entry point directly.
|
|
15
|
+
|
|
16
|
+
```ini
|
|
17
|
+
[Unit]
|
|
18
|
+
Description=pydhcp server
|
|
19
|
+
After=network-online.target
|
|
20
|
+
|
|
21
|
+
[Service]
|
|
22
|
+
ExecStart=/opt/pydhcp/.venv/bin/pydhcp server --help
|
|
23
|
+
Restart=on-failure
|
|
24
|
+
User=pydhcp
|
|
25
|
+
Group=pydhcp
|
|
26
|
+
|
|
27
|
+
[Install]
|
|
28
|
+
WantedBy=multi-user.target
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Replace `--help` with the server arguments you actually want in production. The CLI accepts JSON or INI configuration files through `--config`.
|
|
32
|
+
|
|
33
|
+
## Docker
|
|
34
|
+
|
|
35
|
+
The server can also run inside a container if the container is allowed to bind the needed UDP ports and see the host network.
|
|
36
|
+
|
|
37
|
+
- Prefer host networking for real DHCP service.
|
|
38
|
+
- Mount configuration and lease storage explicitly.
|
|
39
|
+
- Keep logs on stdout/stderr so orchestrators can collect them.
|
|
40
|
+
|
|
41
|
+
## Operational notes
|
|
42
|
+
|
|
43
|
+
- Use the CLI `interfaces` command to confirm interface detection before serving traffic.
|
|
44
|
+
- If you are debugging packet flow, turn on debug logging and look for the transaction ID in the output.
|
|
45
|
+
- Keep an eye on lease backend state after restarts if you are not using the in-memory backend.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
This page collects a few short patterns that are useful when you start wiring `pydhcp` into a real service.
|
|
4
|
+
|
|
5
|
+
## Custom lease backend
|
|
6
|
+
|
|
7
|
+
The server accepts a pluggable lease backend. That makes it easy to persist leases in memory for tests and swap in a file-backed store for simple deployments.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from pydhcp.lease import InMemoryLeaseBackend
|
|
11
|
+
from pydhcp.server import DhcpServer
|
|
12
|
+
|
|
13
|
+
server = DhcpServer(lease_backend=InMemoryLeaseBackend())
|
|
14
|
+
server.listen()
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Listening on all interfaces
|
|
18
|
+
|
|
19
|
+
If you want the server to bind every local IPv4 interface, pass `'*'` or `0.0.0.0`.
|
|
20
|
+
You can also force the portable per-interface path with `per_interface=True`.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from pydhcp.server import DhcpServer
|
|
24
|
+
|
|
25
|
+
server = DhcpServer(listen="*", per_interface=True)
|
|
26
|
+
server.listen()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Custom options
|
|
30
|
+
|
|
31
|
+
`DhcpOptions` behaves like an ordered mapping, so you can build option sets explicitly and preserve encode order.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pydhcp import DhcpOptions
|
|
35
|
+
from pydhcp.enum import DhcpOptionCode
|
|
36
|
+
|
|
37
|
+
options = DhcpOptions()
|
|
38
|
+
options[DhcpOptionCode.HOSTNAME] = "workstation-01"
|
|
39
|
+
options[DhcpOptionCode.DOMAIN_NAME] = "example.internal"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For more typed examples across the built-in option families, see [Common DHCP Options](options.md).
|
|
43
|
+
|
|
44
|
+
## Inspecting packets
|
|
45
|
+
|
|
46
|
+
The CLI can decode packets and print their fields, which is handy when you are debugging client behavior or validating captures.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pydhcp packet --help
|
|
50
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# FAQ
|
|
2
|
+
|
|
3
|
+
## Why does the server use a synthetic interface on loopback?
|
|
4
|
+
|
|
5
|
+
In sandboxed or minimal environments, interface enumeration may not return a matching `NetworkInterface` record for `127.0.0.1`. The server falls back to a synthetic interface so tests and local demos still work.
|
|
6
|
+
|
|
7
|
+
## Why are some clients sent broadcast replies?
|
|
8
|
+
|
|
9
|
+
If the server cannot send a direct unicast packet to the requested address, it falls back to broadcast delivery. That keeps local development usable even when the requested lease address is not reachable from the host network.
|
|
10
|
+
|
|
11
|
+
## Why are the docs split into several short pages?
|
|
12
|
+
|
|
13
|
+
The project has a few different operational concerns now: API reference, examples, deployment, and troubleshooting. Keeping them separate makes it easier to find the right piece without scrolling through a giant wall of text.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# pydhcp
|
|
2
|
+
|
|
3
|
+
A Python DHCP library and server implementation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **DHCP Packet Parsing & Construction**: Full control and type safety over DHCP message structures.
|
|
8
|
+
- **Synchronous & Asynchronous Sockets**: Standard threaded listening loops (`DhcpListener`/`DhcpServer`) and modern asyncio endpoints (`AsyncDhcpListener`/`AsyncDhcpServer`).
|
|
9
|
+
- **Flexible Options System**: Easy options manipulation using type-safe custom dictionaries.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install using pip:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install pydhcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Synchronous DHCP Server
|
|
22
|
+
```python
|
|
23
|
+
from pydhcp.server import DhcpServer
|
|
24
|
+
|
|
25
|
+
# Automatically binds to default DHCP server ports
|
|
26
|
+
server = DhcpServer()
|
|
27
|
+
server.listen()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Asynchronous DHCP Server
|
|
31
|
+
```python
|
|
32
|
+
import asyncio
|
|
33
|
+
from pydhcp.server import AsyncDhcpServer
|
|
34
|
+
|
|
35
|
+
async def main():
|
|
36
|
+
server = AsyncDhcpServer()
|
|
37
|
+
await server.start()
|
|
38
|
+
|
|
39
|
+
# Wait or run other async application logic
|
|
40
|
+
# To shut down cleanly:
|
|
41
|
+
# await server.stop()
|
|
42
|
+
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
```
|