Compare commits
252 Commits
Author | SHA1 | Date |
---|---|---|
bors[bot] | 4d1cbdb217 | |
Emil Fresk | 089443674b | |
bors[bot] | c45632058a | |
Emil Fresk | 8a8d2cc6ef | |
bors[bot] | 1ce3b796ed | |
Thibaut Vandervelden | a9925b5957 | |
bors[bot] | e8659d7cca | |
Thibaut Vandervelden | 76f1b81182 | |
bors[bot] | 9fdf0fab04 | |
Dario Nieuwenhuis | eda4af4e68 | |
Dario Nieuwenhuis | a6b3b64c78 | |
Dario Nieuwenhuis | 8c43fdd443 | |
Dario Nieuwenhuis | f8cc1eacbe | |
Dario Nieuwenhuis | c08dd8dcf6 | |
Dario Nieuwenhuis | b1d6104a74 | |
bors[bot] | 214a13bca6 | |
bors[bot] | 9d0372454a | |
bors[bot] | 2c304b4e8b | |
Dario Nieuwenhuis | 565da5ab33 | |
bors[bot] | 494d797602 | |
bors[bot] | 9bc46a00fd | |
Dean Li | 1ce91fc206 | |
Dean Li | 40a4a7dd2e | |
Alexandra Sandulescu | d14d238422 | |
luojia65 | c111bee3b6 | |
luojia65 | a714def8d0 | |
bors[bot] | 5185a105c3 | |
Thibaut Vandervelden | e9ec0aefa8 | |
Thibaut Vandervelden | 6a08f958b2 | |
Dario Nieuwenhuis | 68c6a85a71 | |
bors[bot] | 221eaf90f1 | |
Dario Nieuwenhuis | 9990249336 | |
Dario Nieuwenhuis | 0d664c14b4 | |
Dario Nieuwenhuis | 8922895a8b | |
Thibaut Vandervelden | 19d9cd46ca | |
Thibaut Vandervelden | be14fbb2a6 | |
Thibaut Vandervelden | ce9145dd62 | |
Thibaut Vandervelden | 91d73273d8 | |
Thibaut Vandervelden | ecc45dbeef | |
Thibaut Vandervelden | 4b9d39706c | |
Thibaut Vandervelden | bff2330309 | |
Thibaut Vandervelden | 061fcf880f | |
Thibaut Vandervelden | 6e63433ab3 | |
Thibaut Vandervelden | 80ba5a2e2d | |
Thibaut Vandervelden | b068a856e5 | |
Thibaut Vandervelden | 1154390307 | |
Dario Nieuwenhuis | 3b8a0781e5 | |
Dario Nieuwenhuis | 36a0e9b2f3 | |
Dario Nieuwenhuis | 7c35a061a4 | |
Dario Nieuwenhuis | c9712939fb | |
Dario Nieuwenhuis | a0b9fb6ebc | |
Dario Nieuwenhuis | 3269ce6124 | |
Dario Nieuwenhuis | b4764e4973 | |
Thibaut Vandervelden | fb2d0029d8 | |
Dario Nieuwenhuis | d66944c4ac | |
Dario Nieuwenhuis | 831ac323cc | |
Dario Nieuwenhuis | daef41bb1c | |
bors[bot] | a1a48b5f36 | |
mkb2091 | b3f93117d2 | |
bors[bot] | 3e9ccf53e1 | |
Dario Nieuwenhuis | 3baa13372c | |
bors[bot] | 7a89d23cf3 | |
Dario Nieuwenhuis | 55c22b9865 | |
bors[bot] | 03492be337 | |
Marc-André Lureau | aa10b5250a | |
bors[bot] | d0928cf068 | |
Dario Nieuwenhuis | 48debf7db8 | |
Dario Nieuwenhuis | bcf6211fbe | |
Dario Nieuwenhuis | a43a6772c9 | |
Dario Nieuwenhuis | 6768d89165 | |
Dario Nieuwenhuis | 94541ae827 | |
Dario Nieuwenhuis | 3b7100c501 | |
Dario Nieuwenhuis | af4db615f5 | |
Dario Nieuwenhuis | d34f4f783b | |
Dario Nieuwenhuis | 6d37633353 | |
Dario Nieuwenhuis | fc5559069c | |
bors[bot] | c0a46e2d14 | |
Dario Nieuwenhuis | 61293b2cb2 | |
Dario Nieuwenhuis | 17e0ddcee3 | |
bors[bot] | 1ba5283cf7 | |
Dario Nieuwenhuis | d98f56f8bd | |
bors[bot] | bf5f0abc31 | |
Dario Nieuwenhuis | b4d7819769 | |
Dario Nieuwenhuis | 975ae59eab | |
Dario Nieuwenhuis | 271ec5d26b | |
Dario Nieuwenhuis | f98a89ba61 | |
Dario Nieuwenhuis | 0d467df5f3 | |
Dario Nieuwenhuis | 3af5386bc5 | |
Dario Nieuwenhuis | 1f25a986c7 | |
Dario Nieuwenhuis | 3a2cdc8680 | |
Dario Nieuwenhuis | c7ae2e4f9b | |
Dario Nieuwenhuis | aea021cfa9 | |
bors[bot] | 6fee12dcee | |
qiujiangkun | acfa7f0b19 | |
bors[bot] | 120a8452cd | |
bors[bot] | 9539e7987a | |
Emil Gardström | 28c6dc6faf | |
bors[bot] | 4acde9ff15 | |
Dario Nieuwenhuis | 3458bffc0c | |
Dario Nieuwenhuis | 49b1a61495 | |
Dario Nieuwenhuis | f044edf805 | |
Dario Nieuwenhuis | b50532cabd | |
Dario Nieuwenhuis | 07c6fb835d | |
Dario Nieuwenhuis | 16abd60e9b | |
Dario Nieuwenhuis | 947a69b8b2 | |
Dario Nieuwenhuis | e19151b9d0 | |
Dario Nieuwenhuis | 7f30c7268b | |
bors[bot] | 4692119e05 | |
Dario Nieuwenhuis | 6d61f5ab6c | |
Dario Nieuwenhuis | 8058a6289f | |
Dario Nieuwenhuis | b674f0d0ba | |
Dario Nieuwenhuis | 28e350f300 | |
bors[bot] | b5874723ff | |
Dario Nieuwenhuis | df53707dad | |
Dario Nieuwenhuis | 7e4180b503 | |
bors[bot] | f058a94b85 | |
bors[bot] | 4638c8d0f3 | |
Dario Nieuwenhuis | 9317cb0ede | |
Thibaut Vandervelden | 82a62327ba | |
Dario Nieuwenhuis | 3ea597ce78 | |
Dario Nieuwenhuis | 4187fc3a5c | |
Dario Nieuwenhuis | ddfabb42f0 | |
david-sawatzke | 1f8110687d | |
Dario Nieuwenhuis | 27665865f6 | |
Thibaut Vandervelden | cebd9d38f4 | |
Dario Nieuwenhuis | be85f807dd | |
Thibaut Vandervelden | e2e2a6be79 | |
Dario Nieuwenhuis | 19fa24577e | |
bdbai | 96dedcc4c6 | |
Dario Nieuwenhuis | c6026c7c1a | |
Thibaut Vandervelden | 8ce629257a | |
Dario Nieuwenhuis | 5c36964d13 | |
Thibaut Vandervelden | 8adbd1b875 | |
Thibaut Vandervelden | 6a27136047 | |
Dario Nieuwenhuis | 5b08ac41be | |
Dario Nieuwenhuis | a94f1b231c | |
Dario Nieuwenhuis | 34c24c7c8c | |
qiujiangkun | a43fb222e9 | |
Dario Nieuwenhuis | 58321c21a5 | |
Dario Nieuwenhuis | 2d49b64182 | |
Dario Nieuwenhuis | b91b356bb4 | |
Dario Nieuwenhuis | 68ec69d411 | |
Dario Nieuwenhuis | 5e58dd1db2 | |
Anton Romanov | 7a83e7d69b | |
Dario Nieuwenhuis | 3782d6425e | |
Dario Nieuwenhuis | ec416ceac4 | |
qiujiangkun | 2a574fbca6 | |
Dario Nieuwenhuis | 1ac34f34eb | |
Dario Nieuwenhuis | 98fe17890a | |
Dario Nieuwenhuis | a803adb514 | |
Dario Nieuwenhuis | 9101e039d5 | |
Dario Nieuwenhuis | 4045dee3f9 | |
Dario Nieuwenhuis | 736a17cae3 | |
Dario Nieuwenhuis | 60a299bcc2 | |
Dario Nieuwenhuis | 9965f09e02 | |
Dario Nieuwenhuis | 32311b23dc | |
Dario Nieuwenhuis | 07449fd995 | |
Gerd Zellweger | e4d487d5fb | |
Gerd Zellweger | bae279c3a8 | |
Gerd Zellweger | 4210fe5e60 | |
Dario Nieuwenhuis | 1f5afa7895 | |
Ryan Summers | 2aff8bd2ce | |
Ryan Summers | 180cc7edaf | |
Dario Nieuwenhuis | 195b500920 | |
Ryan Summers | 9f7bf5dc68 | |
Dario Nieuwenhuis | 6d3df4500f | |
Dario Nieuwenhuis | 0754a7703c | |
Dario Nieuwenhuis | 158fdad8bf | |
Dario Nieuwenhuis | caad8929d5 | |
Dario Nieuwenhuis | f1b0a04599 | |
Dario Nieuwenhuis | 8bed6cd0b4 | |
Dario Nieuwenhuis | de950ef823 | |
Dario Nieuwenhuis | 3783958aa6 | |
Ryan Summers | 06a52a7de9 | |
Dario Nieuwenhuis | 2aa36d4f81 | |
Ryan Summers | a6dbd18574 | |
Ryan Summers | 57604a4d4c | |
Ryan Summers | 421870ee75 | |
Ryan Summers | 2fb5880628 | |
Dario Nieuwenhuis | 2c6567bc4b | |
qiujiangkun | 0be1a76bc3 | |
Ryan Summers | 84866f8f69 | |
Ryan Summers | fa77ddd836 | |
Ryan Summers | e27e3254f4 | |
Ryan Summers | 0bb9fc90f0 | |
Ryan Summers | 44add57e8e | |
Ryan Summers | f790f59088 | |
Ryan Summers | 8474a1b1e4 | |
Ryan Summers | 95829934db | |
Ryan Summers | 8d4e255090 | |
Ryan Summers | 108543a2f6 | |
Dario Nieuwenhuis | 03429a8b4e | |
Dario Nieuwenhuis | ca22d95e2b | |
Dario Nieuwenhuis | 2edd35be13 | |
Dario Nieuwenhuis | 0b001ec0dd | |
Dario Nieuwenhuis | 1d38a0b89a | |
Dario Nieuwenhuis | b9477f25bc | |
Dario Nieuwenhuis | 832c9bace1 | |
Dario Nieuwenhuis | ea8d2ae7f4 | |
Anton Romanov | 7b6b70d058 | |
Dario Nieuwenhuis | 78407dca17 | |
Dario Nieuwenhuis | a5b9461275 | |
Dario Nieuwenhuis | 74b124f4c9 | |
Dario Nieuwenhuis | 383d2426cb | |
Dario Nieuwenhuis | 360dbc79d4 | |
Dario Nieuwenhuis | 8f1e547b67 | |
Dario Nieuwenhuis | a81f2fef56 | |
Dario Nieuwenhuis | 07c3a402a3 | |
Dario Nieuwenhuis | 743f9de039 | |
Dario Nieuwenhuis | 284f5bc834 | |
Dario Nieuwenhuis | a916888ab8 | |
Dario Nieuwenhuis | 53b62cfbad | |
Dario Nieuwenhuis | b1f2bcb678 | |
Dario Nieuwenhuis | cb075bfc16 | |
Dario Nieuwenhuis | 0d53163c55 | |
Dario Nieuwenhuis | ef58fc67a4 | |
Dario Nieuwenhuis | ab47db24e0 | |
Dario Nieuwenhuis | f2231c1cb5 | |
Dario Nieuwenhuis | 68d60a202b | |
Dario Nieuwenhuis | 613fea062e | |
Dario Nieuwenhuis | 1d19ff8d91 | |
Dario Nieuwenhuis | 562f12ffed | |
Dario Nieuwenhuis | 3de0a7e6ac | |
Dario Nieuwenhuis | c043897327 | |
Dario Nieuwenhuis | 0dd91bd309 | |
Dario Nieuwenhuis | 77c46220a6 | |
Dario Nieuwenhuis | e57ec1e5c8 | |
Dario Nieuwenhuis | d64c8593f0 | |
Dario Nieuwenhuis | 5947c5947e | |
Dario Nieuwenhuis | 6e8c2a8455 | |
Dario Nieuwenhuis | 9e3b373e36 | |
Dario Nieuwenhuis | b6220a04c8 | |
Dario Nieuwenhuis | af4a1e6436 | |
Dario Nieuwenhuis | 9ac2cac075 | |
Dario Nieuwenhuis | bbecbf80c1 | |
Dario Nieuwenhuis | b1ea7dd6af | |
Dario Nieuwenhuis | 201d58f232 | |
Dario Nieuwenhuis | 610fb306ba | |
Dario Nieuwenhuis | 22f23d2b6d | |
Dario Nieuwenhuis | 1a1741660a | |
Dario Nieuwenhuis | 711900dbab | |
Dario Nieuwenhuis | f6259f8113 | |
Dario Nieuwenhuis | 20bf6c8e26 | |
Dario Nieuwenhuis | 79dfc7e97e | |
Dario Nieuwenhuis | 8821fed6c0 | |
Dario Nieuwenhuis | a576389340 | |
Dario Nieuwenhuis | 067eee1681 | |
Dario Nieuwenhuis | 8e86318a13 | |
Dario Nieuwenhuis | 21c6e87671 | |
Dario Nieuwenhuis | e40b1442ce | |
Dario Nieuwenhuis | 4390452dcd | |
Martijn Groeneveldt | 13ed4cfd96 |
|
@ -0,0 +1,6 @@
|
|||
status = [
|
||||
"tests",
|
||||
"fuzz",
|
||||
"clippy",
|
||||
"fmt",
|
||||
]
|
|
@ -1,6 +1,6 @@
|
|||
on:
|
||||
push:
|
||||
branches: [ staging, trying, master ]
|
||||
branches: [ staging, trying ]
|
||||
pull_request_target:
|
||||
|
||||
name: Clippy check
|
||||
|
@ -17,7 +17,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.49.0
|
||||
toolchain: 1.53.0
|
||||
override: true
|
||||
components: clippy
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
on:
|
||||
push:
|
||||
branches: [ staging, trying, master ]
|
||||
branches: [ staging, trying ]
|
||||
pull_request:
|
||||
|
||||
name: Fuzz
|
||||
|
@ -16,6 +16,9 @@ jobs:
|
|||
toolchain: nightly
|
||||
override: true
|
||||
- name: Install cargo-fuzz
|
||||
run: cargo install cargo-fuzz
|
||||
# Fix for cargo-fuzz on latest nightly: https://github.com/rust-fuzz/cargo-fuzz/issues/276
|
||||
# Switch back to installing from crates.io when it's released.
|
||||
#run: cargo install cargo-fuzz
|
||||
run: cargo install --git https://github.com/rust-fuzz/cargo-fuzz --rev b4df3e58f767b5cad8d1aa6753961003f56f3609
|
||||
- name: Fuzz
|
||||
run: cargo fuzz run packet_parser -- -max_len=1536 -max_total_time=30
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
name: Matrix bot
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, closed]
|
||||
|
||||
jobs:
|
||||
new-pr:
|
||||
if: github.event.action == 'opened' && github.repository == 'smoltcp-rs/smoltcp'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: send message
|
||||
uses: s3krit/matrix-message-action@v0.0.3
|
||||
with:
|
||||
room_id: ${{ secrets.MATRIX_ROOM_ID }}
|
||||
access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }}
|
||||
message: "New PR: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})"
|
||||
server: "matrix.org"
|
||||
|
||||
merged-pr:
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.repository == 'smoltcp-rs/smoltcp'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: send message
|
||||
uses: s3krit/matrix-message-action@v0.0.3
|
||||
with:
|
||||
room_id: ${{ secrets.MATRIX_ROOM_ID }}
|
||||
access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }}
|
||||
message: "PR merged: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})"
|
||||
server: "matrix.org"
|
||||
|
||||
abandoned-pr:
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == false && github.repository == 'smoltcp-rs/smoltcp'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: send message
|
||||
uses: s3krit/matrix-message-action@v0.0.3
|
||||
with:
|
||||
room_id: ${{ secrets.MATRIX_ROOM_ID }}
|
||||
access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }}
|
||||
message: "PR closed without merging: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})"
|
||||
server: "matrix.org"
|
|
@ -0,0 +1,18 @@
|
|||
on:
|
||||
push:
|
||||
branches: [ staging, trying ]
|
||||
pull_request:
|
||||
|
||||
name: Rustfmt check
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
- name: Check fmt
|
||||
run: cargo fmt -- --check
|
|
@ -1,21 +1,27 @@
|
|||
on:
|
||||
push:
|
||||
branches: [ staging, trying, master ]
|
||||
branches: [ staging, trying ]
|
||||
pull_request:
|
||||
|
||||
name: Test
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [test, check]
|
||||
steps:
|
||||
- name: Done
|
||||
run: exit 0
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' }}
|
||||
strategy:
|
||||
matrix:
|
||||
# Test on stable, MSRV 1.40, and nightly.
|
||||
# Test on stable, MSRV 1.46, and nightly.
|
||||
# Failure is permitted on nightly.
|
||||
rust:
|
||||
- stable
|
||||
- 1.40.0
|
||||
- 1.56.0
|
||||
- nightly
|
||||
|
||||
features:
|
||||
|
@ -26,25 +32,23 @@ jobs:
|
|||
- std proto-ipv4
|
||||
|
||||
# Test features chosen to be as orthogonal as possible.
|
||||
- std ethernet phy-raw_socket proto-ipv6 socket-udp
|
||||
- std ethernet phy-tap_interface proto-ipv6 socket-udp
|
||||
- std ethernet proto-ipv4 proto-igmp socket-raw
|
||||
- std ethernet proto-ipv4 socket-udp socket-tcp
|
||||
- std ethernet proto-ipv4 proto-dhcpv4 socket-udp
|
||||
- std ethernet proto-ipv6 socket-udp
|
||||
- std ethernet proto-ipv6 socket-tcp
|
||||
- std ethernet proto-ipv4 socket-icmp socket-tcp
|
||||
- std ethernet proto-ipv6 socket-icmp socket-tcp
|
||||
- std medium-ethernet phy-raw_socket proto-ipv6 socket-udp
|
||||
- std medium-ethernet phy-tuntap_interface proto-ipv6 socket-udp
|
||||
- std medium-ethernet proto-ipv4 proto-igmp socket-raw
|
||||
- std medium-ethernet proto-ipv4 socket-udp socket-tcp
|
||||
- std medium-ethernet proto-ipv4 proto-dhcpv4 socket-udp
|
||||
- std medium-ethernet proto-ipv6 socket-udp
|
||||
- std medium-ethernet proto-ipv6 socket-tcp
|
||||
- std medium-ethernet proto-ipv4 socket-icmp socket-tcp
|
||||
- std medium-ethernet proto-ipv6 socket-icmp socket-tcp
|
||||
|
||||
# Test features chosen to be as aggressive as possible.
|
||||
- std ethernet proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp async
|
||||
- std medium-ethernet proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp async
|
||||
|
||||
include:
|
||||
# Test alloc feature which requires nightly.
|
||||
- rust: nightly
|
||||
features: alloc ethernet proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp
|
||||
- rust: nightly
|
||||
features: alloc proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp
|
||||
features: alloc rand-custom-impl medium-ethernet proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
|
@ -60,16 +64,17 @@ jobs:
|
|||
continue-on-error: ${{ matrix.rust == 'nightly' }}
|
||||
strategy:
|
||||
matrix:
|
||||
# Test on stable, MSRV 1.40, and nightly.
|
||||
# Test on stable, MSRV 1.46, and nightly.
|
||||
# Failure is permitted on nightly.
|
||||
rust:
|
||||
- stable
|
||||
- 1.40.0
|
||||
- 1.56.0
|
||||
- nightly
|
||||
|
||||
features:
|
||||
# These feature sets cannot run tests, so we only check they build.
|
||||
- ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
|
||||
- rand-custom-impl medium-ip medium-ethernet medium-ieee802154 proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
|
||||
- rand-custom-impl defmt medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -6,12 +6,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### New features
|
||||
* dhcp: Updated DHCP client to respect lease times provided by the server.
|
||||
- Version bumped to 0.8
|
||||
- Minimum Supported Rust Version (MSRV) **bumped** from 1.40 to 1.46
|
||||
- Update `managed` from 0.7 to 0.8 ([442](https://github.com/smoltcp-rs/smoltcp/pull/442))
|
||||
- udp: Add `close()` method to unbind socket.
|
||||
|
||||
## [0.7.5] - 2021-06-28
|
||||
|
||||
- dhcpv4: emit DNS servers in repr (#505)
|
||||
|
||||
## [0.7.4] - 2021-06-11
|
||||
|
||||
- tcp: fix "subtract sequence numbers with underflow" on remote window shrink. (#490)
|
||||
- tcp: fix substract with overflow when receiving a SYNACK with unincremented ACK number. (#491)
|
||||
- tcp: use nonzero initial sequence number to workaround misbehaving servers. (#492)
|
||||
|
||||
## [0.7.3] - 2021-05-29
|
||||
|
||||
- Fix "unused attribute" error in recent nightlies.
|
||||
|
||||
## [0.7.2] - 2021-05-29
|
||||
|
||||
- iface: check for ipv4 subnet broadcast addrs everywhere (#462)
|
||||
- dhcp: always send parameter_request_list. (#456)
|
||||
- dhcp: Clear expiration time on reset. (#456)
|
||||
- phy: fix FaultInjector returning a too big buffer when simulating a drop on tx (#463)
|
||||
- tcp rtte: fix "attempt to multiply with overflow". (#476)
|
||||
- tcp: LastAck should only change to Closed on ack of fin. (#477)
|
||||
- wire/dhcpv4: account for lease time, router and subnet options in DhcpRepr::buffer_len (#478)
|
||||
|
||||
## [0.7.1] - 2021-03-27
|
||||
|
||||
- ndisc: Fix NeighborSolicit incorrectly asking for src addr instead of dst addr ([419](https://github.com/smoltcp-rs/smoltcp/pull/419))
|
||||
- dhcpv4: respect lease time from the server instead of renewing every 60 seconds. ([437](https://github.com/smoltcp-rs/smoltcp/pull/437))
|
||||
- Fix build errors due to invalid combinations of features ([416](https://github.com/smoltcp-rs/smoltcp/pull/416), [447](https://github.com/smoltcp-rs/smoltcp/pull/447))
|
||||
- wire/ipv4: make some functions const ([420](https://github.com/smoltcp-rs/smoltcp/pull/420))
|
||||
- phy: fix BPF on OpenBSD ([421](https://github.com/smoltcp-rs/smoltcp/pull/421), [427](https://github.com/smoltcp-rs/smoltcp/pull/427))
|
||||
- phy: enable RawSocket, TapInterface on Android ([435](https://github.com/smoltcp-rs/smoltcp/pull/435))
|
||||
- phy: fix phy_wait for waits longer than 1 second ([449](https://github.com/smoltcp-rs/smoltcp/pull/449))
|
||||
|
||||
## [0.7.0] - 2021-01-20
|
||||
|
||||
Minimum Supported Rust Version (MSRV) **bumped** from 1.36 to 1.40
|
||||
- Minimum Supported Rust Version (MSRV) **bumped** from 1.36 to 1.40
|
||||
|
||||
### New features
|
||||
- tcp: Allow distinguishing between graceful (FIN) and ungraceful (RST) close. On graceful close, `recv()` now returns `Error::Finished`. On ungraceful close, `Error::Illegal` is returned, as before. ([351](https://github.com/smoltcp-rs/smoltcp/pull/351))
|
||||
|
@ -48,4 +84,9 @@ only processed when directed to the 255.255.255.255 address. ([377](https://gith
|
|||
- Simplify lifetime parameters of sockets, SocketSet, EthernetInterface ([410](https://github.com/smoltcp-rs/smoltcp/pull/410), [413](https://github.com/smoltcp-rs/smoltcp/pull/413))
|
||||
|
||||
[Unreleased]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.0...HEAD
|
||||
[0.7.5]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.4...v0.7.5
|
||||
[0.7.4]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.3...v0.7.4
|
||||
[0.7.3]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.2...v0.7.3
|
||||
[0.7.2]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.1...v0.7.2
|
||||
[0.7.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.0...v0.7.1
|
||||
[0.7.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.6.0...v0.7.0
|
||||
|
|
52
Cargo.toml
52
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "smoltcp"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
edition = "2018"
|
||||
authors = ["whitequark <whitequark@whitequark.org>"]
|
||||
description = "A TCP/IP stack designed for bare-metal, real-time systems without a heap."
|
||||
|
@ -16,11 +16,13 @@ license = "0BSD"
|
|||
autoexamples = false
|
||||
|
||||
[dependencies]
|
||||
managed = { version = "0.7", default-features = false, features = ["map"] }
|
||||
managed = { version = "0.8", default-features = false, features = ["map"] }
|
||||
byteorder = { version = "1.0", default-features = false }
|
||||
log = { version = "0.4.4", default-features = false, optional = true }
|
||||
libc = { version = "0.2.18", optional = true }
|
||||
bitflags = { version = "1.0", default-features = false }
|
||||
defmt = { version = "0.3", optional = true }
|
||||
rand_core = { version = "0.6.3", optional = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
|
@ -29,28 +31,38 @@ rand = "0.3"
|
|||
url = "1.0"
|
||||
|
||||
[features]
|
||||
std = ["managed/std"]
|
||||
std = ["managed/std", "rand_core/std"]
|
||||
alloc = ["managed/alloc"]
|
||||
verbose = []
|
||||
ethernet = []
|
||||
rand-custom-impl = []
|
||||
"medium-ethernet" = ["socket"]
|
||||
"medium-ip" = ["socket"]
|
||||
"medium-ieee802154" = ["socket", "proto-sixlowpan"]
|
||||
|
||||
"phy-raw_socket" = ["std", "libc"]
|
||||
"phy-tap_interface" = ["std", "libc"]
|
||||
"phy-tuntap_interface" = ["std", "libc", "medium-ethernet"]
|
||||
|
||||
"proto-ipv4" = []
|
||||
"proto-igmp" = ["proto-ipv4"]
|
||||
"proto-dhcpv4" = ["proto-ipv4", "socket-raw"]
|
||||
"proto-dhcpv4" = ["proto-ipv4"]
|
||||
"proto-ipv6" = []
|
||||
"proto-sixlowpan" = ["proto-ipv6"]
|
||||
|
||||
"socket" = []
|
||||
"socket-raw" = ["socket"]
|
||||
"socket-udp" = ["socket"]
|
||||
"socket-tcp" = ["socket"]
|
||||
"socket-icmp" = ["socket"]
|
||||
"socket-dhcpv4" = ["socket", "medium-ethernet", "proto-dhcpv4"]
|
||||
|
||||
"async" = []
|
||||
|
||||
default = [
|
||||
"std", "log", # needed for `cargo test --no-default-features --features default` :/
|
||||
"ethernet",
|
||||
"phy-raw_socket", "phy-tap_interface",
|
||||
"proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6",
|
||||
"socket-raw", "socket-icmp", "socket-udp", "socket-tcp",
|
||||
"medium-ethernet", "medium-ip", "medium-ieee802154",
|
||||
"phy-raw_socket", "phy-tuntap_interface",
|
||||
"proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-sixlowpan",
|
||||
"socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4",
|
||||
"async"
|
||||
]
|
||||
|
||||
|
@ -68,35 +80,39 @@ required-features = ["std", "phy-raw_socket", "proto-ipv4"]
|
|||
|
||||
[[example]]
|
||||
name = "httpclient"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-ipv6", "socket-tcp"]
|
||||
required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-ipv6", "socket-tcp"]
|
||||
|
||||
[[example]]
|
||||
name = "ping"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-ipv6", "socket-icmp"]
|
||||
required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-ipv6", "socket-icmp"]
|
||||
|
||||
[[example]]
|
||||
name = "server"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp", "socket-udp"]
|
||||
required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-tcp", "socket-udp"]
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp", "socket-udp"]
|
||||
required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-tcp", "socket-udp"]
|
||||
|
||||
[[example]]
|
||||
name = "loopback"
|
||||
required-features = ["log", "proto-ipv4", "socket-tcp"]
|
||||
required-features = ["log", "medium-ethernet", "proto-ipv4", "socket-tcp"]
|
||||
|
||||
[[example]]
|
||||
name = "multicast"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-igmp", "socket-udp"]
|
||||
required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-igmp", "socket-udp"]
|
||||
|
||||
[[example]]
|
||||
name = "benchmark"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-raw", "socket-udp"]
|
||||
required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-raw", "socket-udp"]
|
||||
|
||||
[[example]]
|
||||
name = "dhcp_client"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-dhcpv4", "socket-raw"]
|
||||
required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-dhcpv4", "socket-raw"]
|
||||
|
||||
[[example]]
|
||||
name = "sixlowpan"
|
||||
required-features = ["std", "medium-ieee802154", "phy-raw_socket", "proto-sixlowpan", "socket-udp"]
|
||||
|
||||
[profile.release]
|
||||
debug = 2
|
||||
|
|
74
README.md
74
README.md
|
@ -1,12 +1,17 @@
|
|||
# smoltcp
|
||||
|
||||
[![docs.rs](https://docs.rs/smoltcp/badge.svg)](https://docs.rs/smoltcp)
|
||||
[![crates.io](https://img.shields.io/crates/v/smoltcp.svg)](https://crates.io/crates/smoltcp)
|
||||
[![crates.io](https://img.shields.io/crates/d/smoltcp.svg)](https://crates.io/crates/smoltcp)
|
||||
[![crates.io](https://img.shields.io/matrix/smoltcp:matrix.org)](https://matrix.to/#/#smoltcp:matrix.org)
|
||||
|
||||
_smoltcp_ is a standalone, event-driven TCP/IP stack that is designed for bare-metal,
|
||||
real-time systems. Its design goals are simplicity and robustness. Its design anti-goals
|
||||
include complicated compile-time computations, such as macro or type tricks, even
|
||||
at cost of performance degradation.
|
||||
|
||||
_smoltcp_ does not need heap allocation *at all*, is [extensively documented][docs],
|
||||
and compiles on stable Rust 1.40 and later.
|
||||
and compiles on stable Rust 1.53 and later.
|
||||
|
||||
_smoltcp_ achieves [~Gbps of throughput](#examplesbenchmarkrs) when tested against
|
||||
the Linux TCP stack in loopback mode.
|
||||
|
@ -125,7 +130,7 @@ To use the _smoltcp_ library in your project, add the following to `Cargo.toml`:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
smoltcp = "0.5"
|
||||
smoltcp = "0.7.5"
|
||||
```
|
||||
|
||||
The default configuration assumes a hosted environment, for ease of evaluation.
|
||||
|
@ -133,7 +138,7 @@ You probably want to disable default features and configure them one by one:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
smoltcp = { version = "0.5", default-features = false, features = ["log"] }
|
||||
smoltcp = { version = "0.7.5", default-features = false, features = ["log"] }
|
||||
```
|
||||
|
||||
### Feature `std`
|
||||
|
@ -170,9 +175,9 @@ or `BufWriter` is used, which are of course not available on heap-less systems.
|
|||
|
||||
This feature is disabled by default.
|
||||
|
||||
### Features `phy-raw_socket` and `phy-tap_interface`
|
||||
### Features `phy-raw_socket` and `phy-tuntap_interface`
|
||||
|
||||
Enable `smoltcp::phy::RawSocket` and `smoltcp::phy::TapInterface`, respectively.
|
||||
Enable `smoltcp::phy::RawSocket` and `smoltcp::phy::TunTapInterface`, respectively.
|
||||
|
||||
These features are enabled by default.
|
||||
|
||||
|
@ -217,6 +222,45 @@ sudo iptables -t nat -A POSTROUTING -s 192.168.69.0/24 -j MASQUERADE
|
|||
sudo sysctl net.ipv4.ip_forward=1
|
||||
sudo ip6tables -t nat -A POSTROUTING -s fdaa::/64 -j MASQUERADE
|
||||
sudo sysctl -w net.ipv6.conf.all.forwarding=1
|
||||
|
||||
# Some distros have a default policy of DROP. This allows the traffic.
|
||||
sudo iptables -A FORWARD -i tap0 -s 192.168.69.0/24 -j ACCEPT
|
||||
sudo iptables -A FORWARD -o tap0 -d 192.168.69.0/24 -j ACCEPT
|
||||
```
|
||||
|
||||
### Bridged connection
|
||||
|
||||
Instead of the routed connection above, you may also set up a bridged (switched)
|
||||
connection. This will make smoltcp speak directly to your LAN, with real ARP, etc.
|
||||
It is needed to run the DHCP example.
|
||||
|
||||
NOTE: In this case, the examples' IP configuration must match your LAN's!
|
||||
|
||||
NOTE: this ONLY works with actual wired Ethernet connections. It
|
||||
will NOT work on a WiFi connection.
|
||||
|
||||
```sh
|
||||
# Replace with your wired Ethernet interface name
|
||||
ETH=enp0s20f0u1u1
|
||||
|
||||
sudo modprobe bridge
|
||||
sudo modprobe br_netfilter
|
||||
|
||||
sudo sysctl -w net.bridge.bridge-nf-call-arptables=0
|
||||
sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0
|
||||
sudo sysctl -w net.bridge.bridge-nf-call-iptables=0
|
||||
|
||||
sudo ip tuntap add name tap0 mode tap user $USER
|
||||
sudo brctl addbr br0
|
||||
sudo brctl addif br0 tap0
|
||||
sudo brctl addif br0 $ETH
|
||||
sudo ip link set tap0 up
|
||||
sudo ip link set $ETH up
|
||||
sudo ip link set br0 up
|
||||
|
||||
# This connects your host system to the internet, so you can use it
|
||||
# at the same time you run the examples.
|
||||
sudo dhcpcd br0
|
||||
```
|
||||
|
||||
### Fault injection
|
||||
|
@ -270,19 +314,19 @@ The host is assigned the hardware address `02-00-00-00-00-02`, IPv4 address `192
|
|||
Read its [source code](/examples/httpclient.rs), then run it as:
|
||||
|
||||
```sh
|
||||
cargo run --example httpclient -- tap0 ADDRESS URL
|
||||
cargo run --example httpclient -- --tap tap0 ADDRESS URL
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```sh
|
||||
cargo run --example httpclient -- tap0 93.184.216.34 http://example.org/
|
||||
cargo run --example httpclient -- --tap tap0 93.184.216.34 http://example.org/
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```sh
|
||||
cargo run --example httpclient -- tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/
|
||||
cargo run --example httpclient -- --tap tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/
|
||||
```
|
||||
|
||||
It connects to the given address (not a hostname) and URL, and prints any returned response data.
|
||||
|
@ -297,7 +341,7 @@ The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `
|
|||
Read its [source code](/examples/ping.rs), then run it as:
|
||||
|
||||
```sh
|
||||
cargo run --example ping -- tap0 ADDRESS
|
||||
cargo run --example ping -- --tap tap0 ADDRESS
|
||||
```
|
||||
|
||||
It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and
|
||||
|
@ -319,7 +363,7 @@ The host is assigned the hardware address `02-00-00-00-00-01` and IPv4 address `
|
|||
Read its [source code](/examples/server.rs), then run it as:
|
||||
|
||||
```sh
|
||||
cargo run --example server -- tap0
|
||||
cargo run --example server -- --tap tap0
|
||||
```
|
||||
|
||||
It responds to:
|
||||
|
@ -349,7 +393,7 @@ The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `
|
|||
Read its [source code](/examples/client.rs), then run it as:
|
||||
|
||||
```sh
|
||||
cargo run --example client -- tap0 ADDRESS PORT
|
||||
cargo run --example client -- --tap tap0 ADDRESS PORT
|
||||
```
|
||||
|
||||
It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen:1234`),
|
||||
|
@ -362,7 +406,7 @@ _examples/benchmark.rs_ implements a simple throughput benchmark.
|
|||
Read its [source code](/examples/benchmark.rs), then run it as:
|
||||
|
||||
```sh
|
||||
cargo run --release --example benchmark -- tap0 [reader|writer]
|
||||
cargo run --release --example benchmark -- --tap tap0 [reader|writer]
|
||||
```
|
||||
|
||||
It establishes a connection to itself from a different thread and reads or writes a large amount
|
||||
|
@ -372,9 +416,9 @@ A typical result (achieved on a Intel Core i7-7500U CPU and a Linux 4.9.65 x86_6
|
|||
on a Dell XPS 13 9360 laptop) is as follows:
|
||||
|
||||
```
|
||||
$ cargo run -q --release --example benchmark tap0 reader
|
||||
$ cargo run -q --release --example benchmark -- --tap tap0 reader
|
||||
throughput: 2.556 Gbps
|
||||
$ cargo run -q --release --example benchmark tap0 writer
|
||||
$ cargo run -q --release --example benchmark -- --tap tap0 writer
|
||||
throughput: 5.301 Gbps
|
||||
```
|
||||
|
||||
|
@ -391,7 +435,7 @@ Although it does not require `std`, this example still requires the `alloc` feat
|
|||
Read its [source code](/examples/loopback.rs), then run it without `std`:
|
||||
|
||||
```sh
|
||||
cargo run --example loopback --no-default-features --features="log proto-ipv4 socket-tcp alloc"
|
||||
cargo run --example loopback --no-default-features --features="log proto-ipv4 socket-tcp alloc"
|
||||
```
|
||||
|
||||
... or with `std` (in this case the features don't have to be explicitly listed):
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
#![feature(test)]
|
||||
|
||||
mod wire {
|
||||
use test;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
use smoltcp::wire::{Ipv6Address, Ipv6Repr, Ipv6Packet};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use smoltcp::wire::{Ipv4Address, Ipv4Repr, Ipv4Packet};
|
||||
use smoltcp::phy::{ChecksumCapabilities};
|
||||
use smoltcp::phy::ChecksumCapabilities;
|
||||
use smoltcp::wire::{IpAddress, IpProtocol};
|
||||
use smoltcp::wire::{TcpRepr, TcpPacket, TcpSeqNumber, TcpControl};
|
||||
use smoltcp::wire::{UdpRepr, UdpPacket};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use smoltcp::wire::{Ipv4Address, Ipv4Packet, Ipv4Repr};
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
use smoltcp::wire::{Ipv6Address, Ipv6Packet, Ipv6Repr};
|
||||
use smoltcp::wire::{TcpControl, TcpPacket, TcpRepr, TcpSeqNumber};
|
||||
use smoltcp::wire::{UdpPacket, UdpRepr};
|
||||
|
||||
extern crate test;
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
const SRC_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1]));
|
||||
const SRC_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address([
|
||||
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
]));
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
const DST_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 2]));
|
||||
const DST_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address([
|
||||
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
||||
]));
|
||||
|
||||
#[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
|
||||
const SRC_ADDR: IpAddress = IpAddress::Ipv4(Ipv4Address([192, 168, 1, 1]));
|
||||
|
@ -26,42 +29,53 @@ mod wire {
|
|||
#[bench]
|
||||
#[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))]
|
||||
fn bench_emit_tcp(b: &mut test::Bencher) {
|
||||
static PAYLOAD_BYTES: [u8; 400] =
|
||||
[0x2a; 400];
|
||||
static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400];
|
||||
let repr = TcpRepr {
|
||||
src_port: 48896,
|
||||
dst_port: 80,
|
||||
seq_number: TcpSeqNumber(0x01234567),
|
||||
ack_number: None,
|
||||
window_len: 0x0123,
|
||||
control: TcpControl::Syn,
|
||||
max_seg_size: None,
|
||||
src_port: 48896,
|
||||
dst_port: 80,
|
||||
control: TcpControl::Syn,
|
||||
seq_number: TcpSeqNumber(0x01234567),
|
||||
ack_number: None,
|
||||
window_len: 0x0123,
|
||||
window_scale: None,
|
||||
payload: &PAYLOAD_BYTES
|
||||
max_seg_size: None,
|
||||
sack_permitted: false,
|
||||
sack_ranges: [None, None, None],
|
||||
payload: &PAYLOAD_BYTES,
|
||||
};
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
|
||||
b.iter(|| {
|
||||
let mut packet = TcpPacket::new(&mut bytes);
|
||||
repr.emit(&mut packet, &SRC_ADDR, &DST_ADDR, &ChecksumCapabilities::default());
|
||||
let mut packet = TcpPacket::new_unchecked(&mut bytes);
|
||||
repr.emit(
|
||||
&mut packet,
|
||||
&SRC_ADDR,
|
||||
&DST_ADDR,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))]
|
||||
fn bench_emit_udp(b: &mut test::Bencher) {
|
||||
static PAYLOAD_BYTES: [u8; 400] =
|
||||
[0x2a; 400];
|
||||
static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400];
|
||||
let repr = UdpRepr {
|
||||
src_port: 48896,
|
||||
dst_port: 80,
|
||||
payload: &PAYLOAD_BYTES
|
||||
};
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()];
|
||||
|
||||
b.iter(|| {
|
||||
let mut packet = UdpPacket::new(&mut bytes);
|
||||
repr.emit(&mut packet, &SRC_ADDR, &DST_ADDR, &ChecksumCapabilities::default());
|
||||
let mut packet = UdpPacket::new_unchecked(&mut bytes);
|
||||
repr.emit(
|
||||
&mut packet,
|
||||
&SRC_ADDR,
|
||||
&DST_ADDR,
|
||||
PAYLOAD_BYTES.len(),
|
||||
|buf| buf.copy_from_slice(&PAYLOAD_BYTES),
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -69,16 +83,16 @@ mod wire {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
fn bench_emit_ipv4(b: &mut test::Bencher) {
|
||||
let repr = Ipv4Repr {
|
||||
src_addr: Ipv4Address([192, 168, 1, 1]),
|
||||
dst_addr: Ipv4Address([192, 168, 1, 2]),
|
||||
protocol: IpProtocol::Tcp,
|
||||
src_addr: Ipv4Address([192, 168, 1, 1]),
|
||||
dst_addr: Ipv4Address([192, 168, 1, 2]),
|
||||
protocol: IpProtocol::Tcp,
|
||||
payload_len: 100,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
};
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
|
||||
b.iter(|| {
|
||||
let mut packet = Ipv4Packet::new(&mut bytes);
|
||||
let mut packet = Ipv4Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut packet, &ChecksumCapabilities::default());
|
||||
});
|
||||
}
|
||||
|
@ -87,18 +101,16 @@ mod wire {
|
|||
#[cfg(feature = "proto-ipv6")]
|
||||
fn bench_emit_ipv6(b: &mut test::Bencher) {
|
||||
let repr = Ipv6Repr {
|
||||
src_addr: Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1]),
|
||||
dst_addr: Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 2]),
|
||||
src_addr: Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
|
||||
dst_addr: Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]),
|
||||
next_header: IpProtocol::Tcp,
|
||||
payload_len: 100,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
};
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
|
||||
b.iter(|| {
|
||||
let mut packet = Ipv6Packet::new(&mut bytes);
|
||||
let mut packet = Ipv6Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut packet);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,28 +2,33 @@
|
|||
|
||||
mod utils;
|
||||
|
||||
use log::debug;
|
||||
use std::cmp;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{Ordering, AtomicBool};
|
||||
use std::thread;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use log::debug;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
|
||||
use smoltcp::socket::SocketSet;
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache};
|
||||
use smoltcp::phy::{wait as phy_wait, Device, Medium};
|
||||
use smoltcp::socket::{TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
|
||||
|
||||
const AMOUNT: usize = 1_000_000_000;
|
||||
|
||||
enum Client { Reader, Writer }
|
||||
enum Client {
|
||||
Reader,
|
||||
Writer,
|
||||
}
|
||||
|
||||
fn client(kind: Client) {
|
||||
let port = match kind { Client::Reader => 1234, Client::Writer => 1235 };
|
||||
let port = match kind {
|
||||
Client::Reader => 1234,
|
||||
Client::Writer => 1235,
|
||||
};
|
||||
let mut stream = TcpStream::connect(("192.168.69.1", port)).unwrap();
|
||||
let mut buffer = vec![0; 1_000_000];
|
||||
|
||||
|
@ -42,7 +47,7 @@ fn client(kind: Client) {
|
|||
// print!("(P:{})", result);
|
||||
processed += result
|
||||
}
|
||||
Err(err) => panic!("cannot process: {}", err)
|
||||
Err(err) => panic!("cannot process: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,18 +67,18 @@ fn main() {
|
|||
utils::setup_logging("info");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_tuntap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
free.push("MODE");
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let device = utils::parse_tuntap_options(&mut matches);
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
let mode = match matches.free[0].as_ref() {
|
||||
"reader" => Client::Reader,
|
||||
"writer" => Client::Writer,
|
||||
_ => panic!("invalid mode")
|
||||
_ => panic!("invalid mode"),
|
||||
};
|
||||
|
||||
thread::spawn(move || client(mode));
|
||||
|
@ -90,68 +95,69 @@ fn main() {
|
|||
|
||||
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)];
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.finalize();
|
||||
let medium = device.capabilities().medium;
|
||||
let mut builder = InterfaceBuilder::new(device, vec![]).ip_addrs(ip_addrs);
|
||||
if medium == Medium::Ethernet {
|
||||
builder = builder
|
||||
.hardware_addr(ethernet_addr.into())
|
||||
.neighbor_cache(neighbor_cache);
|
||||
}
|
||||
let mut iface = builder.finalize();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let tcp1_handle = sockets.add(tcp1_socket);
|
||||
let tcp2_handle = sockets.add(tcp2_socket);
|
||||
let tcp1_handle = iface.add_socket(tcp1_socket);
|
||||
let tcp2_handle = iface.add_socket(tcp2_socket);
|
||||
let default_timeout = Some(Duration::from_millis(1000));
|
||||
|
||||
let mut processed = 0;
|
||||
while !CLIENT_DONE.load(Ordering::SeqCst) {
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll(&mut sockets, timestamp) {
|
||||
Ok(_) => {},
|
||||
match iface.poll(timestamp) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}",e);
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// tcp:1234: emit data
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp1_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(1234).unwrap();
|
||||
}
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp1_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(1234).unwrap();
|
||||
}
|
||||
|
||||
if socket.can_send() {
|
||||
if processed < AMOUNT {
|
||||
let length = socket.send(|buffer| {
|
||||
if socket.can_send() {
|
||||
if processed < AMOUNT {
|
||||
let length = socket
|
||||
.send(|buffer| {
|
||||
let length = cmp::min(buffer.len(), AMOUNT - processed);
|
||||
(length, length)
|
||||
}).unwrap();
|
||||
processed += length;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
processed += length;
|
||||
}
|
||||
}
|
||||
|
||||
// tcp:1235: sink data
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp2_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(1235).unwrap();
|
||||
}
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp2_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(1235).unwrap();
|
||||
}
|
||||
|
||||
if socket.can_recv() {
|
||||
if processed < AMOUNT {
|
||||
let length = socket.recv(|buffer| {
|
||||
if socket.can_recv() {
|
||||
if processed < AMOUNT {
|
||||
let length = socket
|
||||
.recv(|buffer| {
|
||||
let length = cmp::min(buffer.len(), AMOUNT - processed);
|
||||
(length, length)
|
||||
}).unwrap();
|
||||
processed += length;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
processed += length;
|
||||
}
|
||||
}
|
||||
|
||||
match iface.poll_at(&sockets, timestamp) {
|
||||
match iface.poll_at(timestamp) {
|
||||
Some(poll_at) if timestamp < poll_at => {
|
||||
phy_wait(fd, Some(poll_at - timestamp)).expect("wait error");
|
||||
},
|
||||
}
|
||||
Some(_) => (),
|
||||
None => {
|
||||
phy_wait(fd, default_timeout).expect("wait error");
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
mod utils;
|
||||
|
||||
use std::str::{self, FromStr};
|
||||
use log::debug;
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use log::debug;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::wire::{EthernetAddress, Ipv4Address, IpAddress, IpCidr};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
|
||||
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache, Routes};
|
||||
use smoltcp::phy::{wait as phy_wait, Device, Medium};
|
||||
use smoltcp::socket::{TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
|
||||
|
||||
fn main() {
|
||||
utils::setup_logging("");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_tuntap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
free.push("ADDRESS");
|
||||
free.push("PORT");
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let device = utils::parse_tuntap_options(&mut matches);
|
||||
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
|
||||
let port = u16::from_str(&matches.free[1]).expect("invalid port format");
|
||||
|
||||
|
@ -39,64 +40,70 @@ fn main() {
|
|||
let mut routes_storage = [None; 1];
|
||||
let mut routes = Routes::new(&mut routes_storage[..]);
|
||||
routes.add_default_ipv4_route(default_v4_gw).unwrap();
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes)
|
||||
.finalize();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let tcp_handle = sockets.add(tcp_socket);
|
||||
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp_handle);
|
||||
socket.connect((address, port), 49500).unwrap();
|
||||
let medium = device.capabilities().medium;
|
||||
let mut builder = InterfaceBuilder::new(device, vec![])
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes);
|
||||
if medium == Medium::Ethernet {
|
||||
builder = builder
|
||||
.hardware_addr(ethernet_addr.into())
|
||||
.neighbor_cache(neighbor_cache);
|
||||
}
|
||||
let mut iface = builder.finalize();
|
||||
|
||||
let tcp_handle = iface.add_socket(tcp_socket);
|
||||
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp_handle);
|
||||
socket.connect((address, port), 49500).unwrap();
|
||||
|
||||
let mut tcp_active = false;
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll(&mut sockets, timestamp) {
|
||||
Ok(_) => {},
|
||||
match iface.poll(timestamp) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp_handle);
|
||||
if socket.is_active() && !tcp_active {
|
||||
debug!("connected");
|
||||
} else if !socket.is_active() && tcp_active {
|
||||
debug!("disconnected");
|
||||
break
|
||||
}
|
||||
tcp_active = socket.is_active();
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp_handle);
|
||||
if socket.is_active() && !tcp_active {
|
||||
debug!("connected");
|
||||
} else if !socket.is_active() && tcp_active {
|
||||
debug!("disconnected");
|
||||
break;
|
||||
}
|
||||
tcp_active = socket.is_active();
|
||||
|
||||
if socket.may_recv() {
|
||||
let data = socket.recv(|data| {
|
||||
if socket.may_recv() {
|
||||
let data = socket
|
||||
.recv(|data| {
|
||||
let mut data = data.to_owned();
|
||||
if !data.is_empty() {
|
||||
debug!("recv data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)"));
|
||||
debug!(
|
||||
"recv data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
|
||||
);
|
||||
data = data.split(|&b| b == b'\n').collect::<Vec<_>>().concat();
|
||||
data.reverse();
|
||||
data.extend(b"\n");
|
||||
}
|
||||
(data.len(), data)
|
||||
}).unwrap();
|
||||
if socket.can_send() && !data.is_empty() {
|
||||
debug!("send data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)"));
|
||||
socket.send_slice(&data[..]).unwrap();
|
||||
}
|
||||
} else if socket.may_send() {
|
||||
debug!("close");
|
||||
socket.close();
|
||||
})
|
||||
.unwrap();
|
||||
if socket.can_send() && !data.is_empty() {
|
||||
debug!(
|
||||
"send data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
|
||||
);
|
||||
socket.send_slice(&data[..]).unwrap();
|
||||
}
|
||||
} else if socket.may_send() {
|
||||
debug!("close");
|
||||
socket.close();
|
||||
}
|
||||
|
||||
phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error");
|
||||
phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,100 +1,105 @@
|
|||
#![allow(clippy::option_map_unit_fn)]
|
||||
mod utils;
|
||||
|
||||
use log::*;
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::wire::{EthernetAddress, Ipv4Address, IpCidr, Ipv4Cidr};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
|
||||
use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
|
||||
|
||||
use smoltcp::iface::{Interface, InterfaceBuilder, NeighborCache, Routes};
|
||||
use smoltcp::socket::{Dhcpv4Event, Dhcpv4Socket};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::dhcp::Dhcpv4Client;
|
||||
use smoltcp::wire::{EthernetAddress, IpCidr, Ipv4Address, Ipv4Cidr};
|
||||
use smoltcp::{
|
||||
phy::{wait as phy_wait, Device, Medium},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "log")]
|
||||
utils::setup_logging("");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_tuntap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let device = utils::parse_tuntap_options(&mut matches);
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
|
||||
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
let ip_addrs = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)];
|
||||
let mut routes_storage = [None; 1];
|
||||
let routes = Routes::new(&mut routes_storage[..]);
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes)
|
||||
.finalize();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let dhcp_rx_buffer = RawSocketBuffer::new(
|
||||
[RawPacketMetadata::EMPTY; 1],
|
||||
vec![0; 900]
|
||||
);
|
||||
let dhcp_tx_buffer = RawSocketBuffer::new(
|
||||
[RawPacketMetadata::EMPTY; 1],
|
||||
vec![0; 600]
|
||||
);
|
||||
let mut dhcp = Dhcpv4Client::new(&mut sockets, dhcp_rx_buffer, dhcp_tx_buffer, Instant::now());
|
||||
let mut prev_cidr = Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0);
|
||||
let medium = device.capabilities().medium;
|
||||
let mut builder = InterfaceBuilder::new(device, vec![])
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes);
|
||||
if medium == Medium::Ethernet {
|
||||
builder = builder
|
||||
.hardware_addr(ethernet_addr.into())
|
||||
.neighbor_cache(neighbor_cache);
|
||||
}
|
||||
let mut iface = builder.finalize();
|
||||
|
||||
let mut dhcp_socket = Dhcpv4Socket::new();
|
||||
|
||||
// Set a ridiculously short max lease time to show DHCP renews work properly.
|
||||
// This will cause the DHCP client to start renewing after 5 seconds, and give up the
|
||||
// lease after 10 seconds if renew hasn't succeeded.
|
||||
// IMPORTANT: This should be removed in production.
|
||||
dhcp_socket.set_max_lease_duration(Some(Duration::from_secs(10)));
|
||||
|
||||
let dhcp_handle = iface.add_socket(dhcp_socket);
|
||||
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
iface.poll(&mut sockets, timestamp)
|
||||
.map(|_| ())
|
||||
.unwrap_or_else(|e| println!("Poll: {:?}", e));
|
||||
let config = dhcp.poll(&mut iface, &mut sockets, timestamp)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("DHCP: {:?}", e);
|
||||
None
|
||||
});
|
||||
config.map(|config| {
|
||||
println!("DHCP config: {:?}", config);
|
||||
if let Some(cidr) = config.address {
|
||||
if cidr != prev_cidr {
|
||||
iface.update_ip_addrs(|addrs| {
|
||||
addrs.iter_mut().next()
|
||||
.map(|addr| {
|
||||
*addr = IpCidr::Ipv4(cidr);
|
||||
});
|
||||
});
|
||||
prev_cidr = cidr;
|
||||
println!("Assigned a new IPv4 address: {}", cidr);
|
||||
if let Err(e) = iface.poll(timestamp) {
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
|
||||
let event = iface.get_socket::<Dhcpv4Socket>(dhcp_handle).poll();
|
||||
match event {
|
||||
None => {}
|
||||
Some(Dhcpv4Event::Configured(config)) => {
|
||||
debug!("DHCP config acquired!");
|
||||
|
||||
debug!("IP address: {}", config.address);
|
||||
set_ipv4_addr(&mut iface, config.address);
|
||||
|
||||
if let Some(router) = config.router {
|
||||
debug!("Default gateway: {}", router);
|
||||
iface.routes_mut().add_default_ipv4_route(router).unwrap();
|
||||
} else {
|
||||
debug!("Default gateway: None");
|
||||
iface.routes_mut().remove_default_ipv4_route();
|
||||
}
|
||||
|
||||
for (i, s) in config.dns_servers.iter().enumerate() {
|
||||
if let Some(s) = s {
|
||||
debug!("DNS server {}: {}", i, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.router.map(|router| iface.routes_mut()
|
||||
.add_default_ipv4_route(router)
|
||||
.unwrap()
|
||||
);
|
||||
iface.routes_mut()
|
||||
.update(|routes_map| {
|
||||
routes_map.get(&IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0))
|
||||
.map(|default_route| {
|
||||
println!("Default gateway: {}", default_route.via_router);
|
||||
});
|
||||
});
|
||||
|
||||
if config.dns_servers.iter().any(|s| s.is_some()) {
|
||||
println!("DNS servers:");
|
||||
for dns_server in config.dns_servers.iter().filter_map(|s| *s) {
|
||||
println!("- {}", dns_server);
|
||||
}
|
||||
Some(Dhcpv4Event::Deconfigured) => {
|
||||
debug!("DHCP lost config!");
|
||||
set_ipv4_addr(&mut iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
|
||||
iface.routes_mut().remove_default_ipv4_route();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut timeout = dhcp.next_poll(timestamp);
|
||||
iface.poll_delay(&sockets, timestamp)
|
||||
.map(|sockets_timeout| timeout = sockets_timeout);
|
||||
phy_wait(fd, Some(timeout))
|
||||
.unwrap_or_else(|e| println!("Wait: {:?}", e));
|
||||
phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error");
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ipv4_addr<DeviceT>(iface: &mut Interface<'_, DeviceT>, cidr: Ipv4Cidr)
|
||||
where
|
||||
DeviceT: for<'d> Device<'d>,
|
||||
{
|
||||
iface.update_ip_addrs(|addrs| {
|
||||
let dest = addrs.iter_mut().next().unwrap();
|
||||
*dest = IpCidr::Ipv4(cidr);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
mod utils;
|
||||
|
||||
use std::str::{self, FromStr};
|
||||
use log::debug;
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::str::{self, FromStr};
|
||||
use url::Url;
|
||||
use log::debug;
|
||||
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::wire::{EthernetAddress, Ipv4Address, Ipv6Address, IpAddress, IpCidr};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
|
||||
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache, Routes};
|
||||
use smoltcp::phy::{wait as phy_wait, Device, Medium};
|
||||
use smoltcp::socket::{TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address};
|
||||
|
||||
fn main() {
|
||||
utils::setup_logging("");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_tuntap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
free.push("ADDRESS");
|
||||
free.push("URL");
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let device = utils::parse_tuntap_options(&mut matches);
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
|
||||
let url = Url::parse(&matches.free[1]).expect("invalid url format");
|
||||
|
||||
|
||||
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||
|
||||
let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
|
||||
|
@ -36,72 +35,86 @@ fn main() {
|
|||
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
||||
|
||||
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
|
||||
let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
|
||||
IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64),
|
||||
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)];
|
||||
let ip_addrs = [
|
||||
IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
|
||||
IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64),
|
||||
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64),
|
||||
];
|
||||
let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
|
||||
let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100);
|
||||
let mut routes_storage = [None; 2];
|
||||
let mut routes = Routes::new(&mut routes_storage[..]);
|
||||
routes.add_default_ipv4_route(default_v4_gw).unwrap();
|
||||
routes.add_default_ipv6_route(default_v6_gw).unwrap();
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes)
|
||||
.finalize();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let tcp_handle = sockets.add(tcp_socket);
|
||||
let medium = device.capabilities().medium;
|
||||
let mut builder = InterfaceBuilder::new(device, vec![])
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes);
|
||||
if medium == Medium::Ethernet {
|
||||
builder = builder
|
||||
.hardware_addr(ethernet_addr.into())
|
||||
.neighbor_cache(neighbor_cache);
|
||||
}
|
||||
let mut iface = builder.finalize();
|
||||
|
||||
enum State { Connect, Request, Response }
|
||||
let tcp_handle = iface.add_socket(tcp_socket);
|
||||
|
||||
enum State {
|
||||
Connect,
|
||||
Request,
|
||||
Response,
|
||||
}
|
||||
let mut state = State::Connect;
|
||||
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll(&mut sockets, timestamp) {
|
||||
Ok(_) => {},
|
||||
match iface.poll(timestamp) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}",e);
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp_handle);
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp_handle);
|
||||
|
||||
state = match state {
|
||||
State::Connect if !socket.is_active() => {
|
||||
debug!("connecting");
|
||||
let local_port = 49152 + rand::random::<u16>() % 16384;
|
||||
socket.connect((address, url.port().unwrap_or(80)), local_port).unwrap();
|
||||
State::Request
|
||||
}
|
||||
State::Request if socket.may_send() => {
|
||||
debug!("sending request");
|
||||
let http_get = "GET ".to_owned() + url.path() + " HTTP/1.1\r\n";
|
||||
socket.send_slice(http_get.as_ref()).expect("cannot send");
|
||||
let http_host = "Host: ".to_owned() + url.host_str().unwrap() + "\r\n";
|
||||
socket.send_slice(http_host.as_ref()).expect("cannot send");
|
||||
socket.send_slice(b"Connection: close\r\n").expect("cannot send");
|
||||
socket.send_slice(b"\r\n").expect("cannot send");
|
||||
State::Response
|
||||
}
|
||||
State::Response if socket.can_recv() => {
|
||||
socket.recv(|data| {
|
||||
state = match state {
|
||||
State::Connect if !socket.is_active() => {
|
||||
debug!("connecting");
|
||||
let local_port = 49152 + rand::random::<u16>() % 16384;
|
||||
socket
|
||||
.connect((address, url.port().unwrap_or(80)), local_port)
|
||||
.unwrap();
|
||||
State::Request
|
||||
}
|
||||
State::Request if socket.may_send() => {
|
||||
debug!("sending request");
|
||||
let http_get = "GET ".to_owned() + url.path() + " HTTP/1.1\r\n";
|
||||
socket.send_slice(http_get.as_ref()).expect("cannot send");
|
||||
let http_host = "Host: ".to_owned() + url.host_str().unwrap() + "\r\n";
|
||||
socket.send_slice(http_host.as_ref()).expect("cannot send");
|
||||
socket
|
||||
.send_slice(b"Connection: close\r\n")
|
||||
.expect("cannot send");
|
||||
socket.send_slice(b"\r\n").expect("cannot send");
|
||||
State::Response
|
||||
}
|
||||
State::Response if socket.can_recv() => {
|
||||
socket
|
||||
.recv(|data| {
|
||||
println!("{}", str::from_utf8(data).unwrap_or("(invalid utf8)"));
|
||||
(data.len(), ())
|
||||
}).unwrap();
|
||||
State::Response
|
||||
}
|
||||
State::Response if !socket.may_recv() => {
|
||||
debug!("received complete response");
|
||||
break
|
||||
}
|
||||
_ => state
|
||||
})
|
||||
.unwrap();
|
||||
State::Response
|
||||
}
|
||||
}
|
||||
State::Response if !socket.may_recv() => {
|
||||
debug!("received complete response");
|
||||
break;
|
||||
}
|
||||
_ => state,
|
||||
};
|
||||
|
||||
phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error");
|
||||
phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,20 +7,21 @@
|
|||
mod utils;
|
||||
|
||||
use core::str;
|
||||
use log::{info, debug, error};
|
||||
use log::{debug, error, info};
|
||||
|
||||
use smoltcp::phy::Loopback;
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
|
||||
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache};
|
||||
use smoltcp::phy::{Loopback, Medium};
|
||||
use smoltcp::socket::{TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod mock {
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use core::cell::Cell;
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Clock(Cell<Instant>);
|
||||
|
||||
impl Clock {
|
||||
|
@ -36,16 +37,25 @@ mod mock {
|
|||
self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
struct Rand;
|
||||
smoltcp::rand_custom_impl!(Rand);
|
||||
impl smoltcp::Rand for Rand {
|
||||
fn rand_bytes(buf: &mut [u8]) {
|
||||
buf.fill(0x42);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod mock {
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{Ordering, AtomicUsize};
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
|
||||
// should be AtomicU64 but that's unstable
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Clock(Arc<AtomicUsize>);
|
||||
|
||||
impl Clock {
|
||||
|
@ -54,7 +64,8 @@ mod mock {
|
|||
}
|
||||
|
||||
pub fn advance(&self, duration: Duration) {
|
||||
self.0.fetch_add(duration.total_millis() as usize, Ordering::SeqCst);
|
||||
self.0
|
||||
.fetch_add(duration.total_millis() as usize, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn elapsed(&self) -> Instant {
|
||||
|
@ -65,7 +76,7 @@ mod mock {
|
|||
|
||||
fn main() {
|
||||
let clock = mock::Clock::new();
|
||||
let device = Loopback::new();
|
||||
let device = Loopback::new(Medium::Ethernet);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
let device = {
|
||||
|
@ -76,18 +87,19 @@ fn main() {
|
|||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
utils::parse_middleware_options(&mut matches, device, /*loopback=*/true)
|
||||
utils::parse_middleware_options(&mut matches, device, /*loopback=*/ true)
|
||||
};
|
||||
|
||||
let mut neighbor_cache_entries = [None; 8];
|
||||
let mut neighbor_cache = NeighborCache::new(&mut neighbor_cache_entries[..]);
|
||||
|
||||
let mut ip_addrs = [IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)];
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(EthernetAddress::default())
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.finalize();
|
||||
let mut sockets: [_; 2] = Default::default();
|
||||
let mut iface = InterfaceBuilder::new(device, &mut sockets[..])
|
||||
.hardware_addr(EthernetAddress::default().into())
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.finalize();
|
||||
|
||||
let server_socket = {
|
||||
// It is not strictly necessary to use a `static mut` and unsafe code here, but
|
||||
|
@ -109,66 +121,65 @@ fn main() {
|
|||
TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer)
|
||||
};
|
||||
|
||||
let mut socket_set_entries: [_; 2] = Default::default();
|
||||
let mut socket_set = SocketSet::new(&mut socket_set_entries[..]);
|
||||
let server_handle = socket_set.add(server_socket);
|
||||
let client_handle = socket_set.add(client_socket);
|
||||
let server_handle = iface.add_socket(server_socket);
|
||||
let client_handle = iface.add_socket(client_socket);
|
||||
|
||||
let mut did_listen = false;
|
||||
let mut did_listen = false;
|
||||
let mut did_connect = false;
|
||||
let mut done = false;
|
||||
while !done && clock.elapsed() < Instant::from_millis(10_000) {
|
||||
match iface.poll(&mut socket_set, clock.elapsed()) {
|
||||
Ok(_) => {},
|
||||
match iface.poll(clock.elapsed()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut socket = socket_set.get::<TcpSocket>(server_handle);
|
||||
if !socket.is_active() && !socket.is_listening() {
|
||||
if !did_listen {
|
||||
debug!("listening");
|
||||
socket.listen(1234).unwrap();
|
||||
did_listen = true;
|
||||
}
|
||||
}
|
||||
|
||||
if socket.can_recv() {
|
||||
debug!("got {:?}", socket.recv(|buffer| {
|
||||
(buffer.len(), str::from_utf8(buffer).unwrap())
|
||||
}));
|
||||
socket.close();
|
||||
done = true;
|
||||
let mut socket = iface.get_socket::<TcpSocket>(server_handle);
|
||||
if !socket.is_active() && !socket.is_listening() {
|
||||
if !did_listen {
|
||||
debug!("listening");
|
||||
socket.listen(1234).unwrap();
|
||||
did_listen = true;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut socket = socket_set.get::<TcpSocket>(client_handle);
|
||||
if !socket.is_open() {
|
||||
if !did_connect {
|
||||
debug!("connecting");
|
||||
socket.connect((IpAddress::v4(127, 0, 0, 1), 1234),
|
||||
(IpAddress::Unspecified, 65000)).unwrap();
|
||||
did_connect = true;
|
||||
}
|
||||
}
|
||||
if socket.can_recv() {
|
||||
debug!(
|
||||
"got {:?}",
|
||||
socket.recv(|buffer| { (buffer.len(), str::from_utf8(buffer).unwrap()) })
|
||||
);
|
||||
socket.close();
|
||||
done = true;
|
||||
}
|
||||
|
||||
if socket.can_send() {
|
||||
debug!("sending");
|
||||
socket.send_slice(b"0123456789abcdef").unwrap();
|
||||
socket.close();
|
||||
let mut socket = iface.get_socket::<TcpSocket>(client_handle);
|
||||
if !socket.is_open() {
|
||||
if !did_connect {
|
||||
debug!("connecting");
|
||||
socket
|
||||
.connect(
|
||||
(IpAddress::v4(127, 0, 0, 1), 1234),
|
||||
(IpAddress::Unspecified, 65000),
|
||||
)
|
||||
.unwrap();
|
||||
did_connect = true;
|
||||
}
|
||||
}
|
||||
|
||||
match iface.poll_delay(&socket_set, clock.elapsed()) {
|
||||
Some(Duration { millis: 0 }) => debug!("resuming"),
|
||||
if socket.can_send() {
|
||||
debug!("sending");
|
||||
socket.send_slice(b"0123456789abcdef").unwrap();
|
||||
socket.close();
|
||||
}
|
||||
|
||||
match iface.poll_delay(clock.elapsed()) {
|
||||
Some(Duration::ZERO) => debug!("resuming"),
|
||||
Some(delay) => {
|
||||
debug!("sleeping for {} ms", delay);
|
||||
clock.advance(delay)
|
||||
},
|
||||
None => clock.advance(Duration::from_millis(1))
|
||||
}
|
||||
None => clock.advance(Duration::from_millis(1)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
mod utils;
|
||||
|
||||
use log::debug;
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use log::debug;
|
||||
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache};
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::wire::{EthernetAddress, IpVersion, IpProtocol, IpAddress, IpCidr, Ipv4Address,
|
||||
Ipv4Packet, IgmpPacket, IgmpRepr};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
|
||||
use smoltcp::socket::{SocketSet,
|
||||
RawSocket, RawSocketBuffer, RawPacketMetadata,
|
||||
UdpSocket, UdpSocketBuffer, UdpPacketMetadata};
|
||||
use smoltcp::socket::{
|
||||
RawPacketMetadata, RawSocket, RawSocketBuffer, UdpPacketMetadata, UdpSocket, UdpSocketBuffer,
|
||||
};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{
|
||||
EthernetAddress, IgmpPacket, IgmpRepr, IpAddress, IpCidr, IpProtocol, IpVersion, Ipv4Address,
|
||||
Ipv4Packet,
|
||||
};
|
||||
|
||||
const MDNS_PORT: u16 = 5353;
|
||||
const MDNS_GROUP: [u8; 4] = [224, 0, 0, 251];
|
||||
|
@ -20,16 +22,13 @@ fn main() {
|
|||
utils::setup_logging("warn");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_tuntap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let device = utils::parse_tuntap_options(&mut matches);
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches,
|
||||
device,
|
||||
/*loopback=*/
|
||||
false);
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||
|
||||
let local_addr = Ipv4Address::new(192, 168, 69, 2);
|
||||
|
@ -37,72 +36,75 @@ fn main() {
|
|||
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
|
||||
let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24);
|
||||
let mut ipv4_multicast_storage = [None; 1];
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs([ip_addr])
|
||||
.ipv4_multicast_groups(&mut ipv4_multicast_storage[..])
|
||||
.finalize();
|
||||
let mut iface = InterfaceBuilder::new(device, vec![])
|
||||
.hardware_addr(ethernet_addr.into())
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs([ip_addr])
|
||||
.ipv4_multicast_groups(&mut ipv4_multicast_storage[..])
|
||||
.finalize();
|
||||
|
||||
let now = Instant::now();
|
||||
// Join a multicast group to receive mDNS traffic
|
||||
iface.join_multicast_group(Ipv4Address::from_bytes(&MDNS_GROUP), now).unwrap();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
iface
|
||||
.join_multicast_group(Ipv4Address::from_bytes(&MDNS_GROUP), now)
|
||||
.unwrap();
|
||||
|
||||
// Must fit at least one IGMP packet
|
||||
let raw_rx_buffer = RawSocketBuffer::new(vec![RawPacketMetadata::EMPTY; 2], vec![0; 512]);
|
||||
// Will not send IGMP
|
||||
let raw_tx_buffer = RawSocketBuffer::new(vec![], vec![]);
|
||||
let raw_socket = RawSocket::new(
|
||||
IpVersion::Ipv4, IpProtocol::Igmp,
|
||||
raw_rx_buffer, raw_tx_buffer
|
||||
IpVersion::Ipv4,
|
||||
IpProtocol::Igmp,
|
||||
raw_rx_buffer,
|
||||
raw_tx_buffer,
|
||||
);
|
||||
let raw_handle = sockets.add(raw_socket);
|
||||
let raw_handle = iface.add_socket(raw_socket);
|
||||
|
||||
// Must fit mDNS payload of at least one packet
|
||||
let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY; 4], vec![0; 1024]);
|
||||
// Will not send mDNS
|
||||
let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 0]);
|
||||
let udp_socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer);
|
||||
let udp_handle = sockets.add(udp_socket);
|
||||
let udp_handle = iface.add_socket(udp_socket);
|
||||
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll(&mut sockets, timestamp) {
|
||||
Ok(_) => {},
|
||||
match iface.poll(timestamp) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}",e);
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut socket = sockets.get::<RawSocket>(raw_handle);
|
||||
let socket = iface.get_socket::<RawSocket>(raw_handle);
|
||||
|
||||
if socket.can_recv() {
|
||||
// For display purposes only - normally we wouldn't process incoming IGMP packets
|
||||
// in the application layer
|
||||
socket.recv()
|
||||
.and_then(Ipv4Packet::new_checked)
|
||||
.and_then(|ipv4_packet| IgmpPacket::new_checked(ipv4_packet.payload()))
|
||||
.and_then(|igmp_packet| IgmpRepr::parse(&igmp_packet))
|
||||
.map(|igmp_repr| println!("IGMP packet: {:?}", igmp_repr))
|
||||
.unwrap_or_else(|e| println!("Recv IGMP error: {:?}", e));
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut socket = sockets.get::<UdpSocket>(udp_handle);
|
||||
if !socket.is_open() {
|
||||
socket.bind(MDNS_PORT).unwrap()
|
||||
}
|
||||
|
||||
if socket.can_recv() {
|
||||
socket.recv()
|
||||
.map(|(data, sender)| println!("mDNS traffic: {} UDP bytes from {}", data.len(), sender))
|
||||
.unwrap_or_else(|e| println!("Recv UDP error: {:?}", e));
|
||||
}
|
||||
if socket.can_recv() {
|
||||
// For display purposes only - normally we wouldn't process incoming IGMP packets
|
||||
// in the application layer
|
||||
socket
|
||||
.recv()
|
||||
.and_then(Ipv4Packet::new_checked)
|
||||
.and_then(|ipv4_packet| IgmpPacket::new_checked(ipv4_packet.payload()))
|
||||
.and_then(|igmp_packet| IgmpRepr::parse(&igmp_packet))
|
||||
.map(|igmp_repr| println!("IGMP packet: {:?}", igmp_repr))
|
||||
.unwrap_or_else(|e| println!("Recv IGMP error: {:?}", e));
|
||||
}
|
||||
|
||||
phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error");
|
||||
let socket = iface.get_socket::<UdpSocket>(udp_handle);
|
||||
if !socket.is_open() {
|
||||
socket.bind(MDNS_PORT).unwrap()
|
||||
}
|
||||
|
||||
if socket.can_recv() {
|
||||
socket
|
||||
.recv()
|
||||
.map(|(data, sender)| {
|
||||
println!("mDNS traffic: {} UDP bytes from {}", data.len(), sender)
|
||||
})
|
||||
.unwrap_or_else(|e| println!("Recv UDP error: {:?}", e));
|
||||
}
|
||||
|
||||
phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error");
|
||||
}
|
||||
}
|
||||
|
|
294
examples/ping.rs
294
examples/ping.rs
|
@ -1,21 +1,25 @@
|
|||
mod utils;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::collections::BTreeMap;
|
||||
use std::cmp;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::collections::HashMap;
|
||||
use log::debug;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use log::debug;
|
||||
use std::cmp;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::str::FromStr;
|
||||
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use smoltcp::phy::Device;
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache, Routes};
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr,
|
||||
Ipv6Address, Icmpv6Repr, Icmpv6Packet,
|
||||
Ipv4Address, Icmpv4Repr, Icmpv4Packet};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
|
||||
use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketMetadata, IcmpEndpoint};
|
||||
use smoltcp::phy::Device;
|
||||
use smoltcp::socket::{IcmpEndpoint, IcmpPacketMetadata, IcmpSocket, IcmpSocketBuffer};
|
||||
use smoltcp::wire::{
|
||||
EthernetAddress, Icmpv4Packet, Icmpv4Repr, Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr,
|
||||
Ipv4Address, Ipv6Address,
|
||||
};
|
||||
use smoltcp::{
|
||||
phy::Medium,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
macro_rules! send_icmp_ping {
|
||||
( $repr_type:ident, $packet_type:ident, $ident:expr, $seq_no:expr,
|
||||
|
@ -26,13 +30,11 @@ macro_rules! send_icmp_ping {
|
|||
data: &$echo_payload,
|
||||
};
|
||||
|
||||
let icmp_payload = $socket
|
||||
.send(icmp_repr.buffer_len(), $remote_addr)
|
||||
.unwrap();
|
||||
let icmp_payload = $socket.send(icmp_repr.buffer_len(), $remote_addr).unwrap();
|
||||
|
||||
let icmp_packet = $packet_type::new_unchecked(icmp_payload);
|
||||
(icmp_repr, icmp_packet)
|
||||
}}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! get_icmp_pong {
|
||||
|
@ -41,42 +43,65 @@ macro_rules! get_icmp_pong {
|
|||
if let $repr_type::EchoReply { seq_no, data, .. } = $repr {
|
||||
if let Some(_) = $waiting_queue.get(&seq_no) {
|
||||
let packet_timestamp_ms = NetworkEndian::read_i64(data);
|
||||
println!("{} bytes from {}: icmp_seq={}, time={}ms",
|
||||
data.len(), $remote_addr, seq_no,
|
||||
$timestamp.total_millis() - packet_timestamp_ms);
|
||||
println!(
|
||||
"{} bytes from {}: icmp_seq={}, time={}ms",
|
||||
data.len(),
|
||||
$remote_addr,
|
||||
seq_no,
|
||||
$timestamp.total_millis() - packet_timestamp_ms
|
||||
);
|
||||
$waiting_queue.remove(&seq_no);
|
||||
$received += 1;
|
||||
}
|
||||
}
|
||||
}}
|
||||
}};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
utils::setup_logging("warn");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_tuntap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
opts.optopt("c", "count", "Amount of echo request packets to send (default: 4)", "COUNT");
|
||||
opts.optopt("i", "interval",
|
||||
"Interval between successive packets sent (seconds) (default: 1)", "INTERVAL");
|
||||
opts.optopt("", "timeout",
|
||||
"Maximum wait duration for an echo response packet (seconds) (default: 5)",
|
||||
"TIMEOUT");
|
||||
opts.optopt(
|
||||
"c",
|
||||
"count",
|
||||
"Amount of echo request packets to send (default: 4)",
|
||||
"COUNT",
|
||||
);
|
||||
opts.optopt(
|
||||
"i",
|
||||
"interval",
|
||||
"Interval between successive packets sent (seconds) (default: 1)",
|
||||
"INTERVAL",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"timeout",
|
||||
"Maximum wait duration for an echo response packet (seconds) (default: 5)",
|
||||
"TIMEOUT",
|
||||
);
|
||||
free.push("ADDRESS");
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let device = utils::parse_tuntap_options(&mut matches);
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
let device_caps = device.capabilities();
|
||||
let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
|
||||
let count = matches.opt_str("count").map(|s| usize::from_str(&s).unwrap()).unwrap_or(4);
|
||||
let interval = matches.opt_str("interval")
|
||||
let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
|
||||
let count = matches
|
||||
.opt_str("count")
|
||||
.map(|s| usize::from_str(&s).unwrap())
|
||||
.unwrap_or(4);
|
||||
let interval = matches
|
||||
.opt_str("interval")
|
||||
.map(|s| Duration::from_secs(u64::from_str(&s).unwrap()))
|
||||
.unwrap_or_else(|| Duration::from_secs(1));
|
||||
let timeout = Duration::from_secs(
|
||||
matches.opt_str("timeout").map(|s| u64::from_str(&s).unwrap()).unwrap_or(5)
|
||||
let timeout = Duration::from_secs(
|
||||
matches
|
||||
.opt_str("timeout")
|
||||
.map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(5),
|
||||
);
|
||||
|
||||
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||
|
@ -89,24 +114,30 @@ fn main() {
|
|||
|
||||
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
|
||||
let src_ipv6 = IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1);
|
||||
let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
|
||||
IpCidr::new(src_ipv6, 64),
|
||||
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)];
|
||||
let ip_addrs = [
|
||||
IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
|
||||
IpCidr::new(src_ipv6, 64),
|
||||
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64),
|
||||
];
|
||||
let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
|
||||
let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100);
|
||||
let mut routes_storage = [None; 2];
|
||||
let mut routes = Routes::new(&mut routes_storage[..]);
|
||||
routes.add_default_ipv4_route(default_v4_gw).unwrap();
|
||||
routes.add_default_ipv6_route(default_v6_gw).unwrap();
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.finalize();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let icmp_handle = sockets.add(icmp_socket);
|
||||
let medium = device.capabilities().medium;
|
||||
let mut builder = InterfaceBuilder::new(device, vec![])
|
||||
.ip_addrs(ip_addrs)
|
||||
.routes(routes);
|
||||
if medium == Medium::Ethernet {
|
||||
builder = builder
|
||||
.hardware_addr(ethernet_addr.into())
|
||||
.neighbor_cache(neighbor_cache);
|
||||
}
|
||||
let mut iface = builder.finalize();
|
||||
|
||||
let icmp_handle = iface.add_socket(icmp_socket);
|
||||
|
||||
let mut send_at = Instant::from_millis(0);
|
||||
let mut seq_no = 0;
|
||||
|
@ -117,89 +148,120 @@ fn main() {
|
|||
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll(&mut sockets, timestamp) {
|
||||
Ok(_) => {},
|
||||
match iface.poll(timestamp) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let timestamp = Instant::now();
|
||||
let mut socket = sockets.get::<IcmpSocket>(icmp_handle);
|
||||
if !socket.is_open() {
|
||||
socket.bind(IcmpEndpoint::Ident(ident)).unwrap();
|
||||
send_at = timestamp;
|
||||
let timestamp = Instant::now();
|
||||
let socket = iface.get_socket::<IcmpSocket>(icmp_handle);
|
||||
if !socket.is_open() {
|
||||
socket.bind(IcmpEndpoint::Ident(ident)).unwrap();
|
||||
send_at = timestamp;
|
||||
}
|
||||
|
||||
if socket.can_send() && seq_no < count as u16 && send_at <= timestamp {
|
||||
NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis());
|
||||
|
||||
match remote_addr {
|
||||
IpAddress::Ipv4(_) => {
|
||||
let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
|
||||
Icmpv4Repr,
|
||||
Icmpv4Packet,
|
||||
ident,
|
||||
seq_no,
|
||||
echo_payload,
|
||||
socket,
|
||||
remote_addr
|
||||
);
|
||||
icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
|
||||
}
|
||||
IpAddress::Ipv6(_) => {
|
||||
let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
|
||||
Icmpv6Repr,
|
||||
Icmpv6Packet,
|
||||
ident,
|
||||
seq_no,
|
||||
echo_payload,
|
||||
socket,
|
||||
remote_addr
|
||||
);
|
||||
icmp_repr.emit(
|
||||
&src_ipv6,
|
||||
&remote_addr,
|
||||
&mut icmp_packet,
|
||||
&device_caps.checksum,
|
||||
);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
if socket.can_send() && seq_no < count as u16 &&
|
||||
send_at <= timestamp {
|
||||
NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis());
|
||||
waiting_queue.insert(seq_no, timestamp);
|
||||
seq_no += 1;
|
||||
send_at += interval;
|
||||
}
|
||||
|
||||
match remote_addr {
|
||||
IpAddress::Ipv4(_) => {
|
||||
let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
|
||||
Icmpv4Repr, Icmpv4Packet, ident, seq_no,
|
||||
echo_payload, socket, remote_addr);
|
||||
icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
|
||||
},
|
||||
IpAddress::Ipv6(_) => {
|
||||
let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
|
||||
Icmpv6Repr, Icmpv6Packet, ident, seq_no,
|
||||
echo_payload, socket, remote_addr);
|
||||
icmp_repr.emit(&src_ipv6, &remote_addr,
|
||||
&mut icmp_packet, &device_caps.checksum);
|
||||
},
|
||||
_ => unimplemented!()
|
||||
if socket.can_recv() {
|
||||
let (payload, _) = socket.recv().unwrap();
|
||||
|
||||
match remote_addr {
|
||||
IpAddress::Ipv4(_) => {
|
||||
let icmp_packet = Icmpv4Packet::new_checked(&payload).unwrap();
|
||||
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap();
|
||||
get_icmp_pong!(
|
||||
Icmpv4Repr,
|
||||
icmp_repr,
|
||||
payload,
|
||||
waiting_queue,
|
||||
remote_addr,
|
||||
timestamp,
|
||||
received
|
||||
);
|
||||
}
|
||||
|
||||
waiting_queue.insert(seq_no, timestamp);
|
||||
seq_no += 1;
|
||||
send_at += interval;
|
||||
}
|
||||
|
||||
if socket.can_recv() {
|
||||
let (payload, _) = socket.recv().unwrap();
|
||||
|
||||
match remote_addr {
|
||||
IpAddress::Ipv4(_) => {
|
||||
let icmp_packet = Icmpv4Packet::new_checked(&payload).unwrap();
|
||||
let icmp_repr =
|
||||
Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap();
|
||||
get_icmp_pong!(Icmpv4Repr, icmp_repr, payload,
|
||||
waiting_queue, remote_addr, timestamp, received);
|
||||
}
|
||||
IpAddress::Ipv6(_) => {
|
||||
let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap();
|
||||
let icmp_repr = Icmpv6Repr::parse(&remote_addr, &src_ipv6,
|
||||
&icmp_packet, &device_caps.checksum).unwrap();
|
||||
get_icmp_pong!(Icmpv6Repr, icmp_repr, payload,
|
||||
waiting_queue, remote_addr, timestamp, received);
|
||||
},
|
||||
_ => unimplemented!()
|
||||
IpAddress::Ipv6(_) => {
|
||||
let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap();
|
||||
let icmp_repr = Icmpv6Repr::parse(
|
||||
&remote_addr,
|
||||
&src_ipv6,
|
||||
&icmp_packet,
|
||||
&device_caps.checksum,
|
||||
)
|
||||
.unwrap();
|
||||
get_icmp_pong!(
|
||||
Icmpv6Repr,
|
||||
icmp_repr,
|
||||
payload,
|
||||
waiting_queue,
|
||||
remote_addr,
|
||||
timestamp,
|
||||
received
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
waiting_queue.retain(|seq, from| {
|
||||
if timestamp - *from < timeout {
|
||||
true
|
||||
} else {
|
||||
println!("From {} icmp_seq={} timeout", remote_addr, seq);
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if seq_no == count as u16 && waiting_queue.is_empty() {
|
||||
break
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
waiting_queue.retain(|seq, from| {
|
||||
if timestamp - *from < timeout {
|
||||
true
|
||||
} else {
|
||||
println!("From {} icmp_seq={} timeout", remote_addr, seq);
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if seq_no == count as u16 && waiting_queue.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll_at(&sockets, timestamp) {
|
||||
match iface.poll_at(timestamp) {
|
||||
Some(poll_at) if timestamp < poll_at => {
|
||||
let resume_at = cmp::min(poll_at, send_at);
|
||||
phy_wait(fd, Some(resume_at - timestamp)).expect("wait error");
|
||||
},
|
||||
}
|
||||
Some(_) => (),
|
||||
None => {
|
||||
phy_wait(fd, Some(send_at - timestamp)).expect("wait error");
|
||||
|
@ -208,6 +270,10 @@ fn main() {
|
|||
}
|
||||
|
||||
println!("--- {} ping statistics ---", remote_addr);
|
||||
println!("{} packets transmitted, {} received, {:.0}% packet loss",
|
||||
seq_no, received, 100.0 * (seq_no - received) as f64 / seq_no as f64);
|
||||
println!(
|
||||
"{} packets transmitted, {} received, {:.0}% packet loss",
|
||||
seq_no,
|
||||
received,
|
||||
100.0 * (seq_no - received) as f64 / seq_no as f64
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
mod utils;
|
||||
|
||||
use std::str;
|
||||
use log::debug;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Write;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use log::debug;
|
||||
use std::str;
|
||||
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
|
||||
use smoltcp::socket::SocketSet;
|
||||
use smoltcp::socket::{UdpSocket, UdpSocketBuffer, UdpPacketMetadata};
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache};
|
||||
use smoltcp::phy::{wait as phy_wait, Device, Medium};
|
||||
use smoltcp::socket::{TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer};
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr};
|
||||
|
||||
fn main() {
|
||||
utils::setup_logging("");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_tuntap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let device = utils::parse_tuntap_options(&mut matches);
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
|
||||
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||
|
||||
|
@ -52,137 +51,145 @@ fn main() {
|
|||
let ip_addrs = [
|
||||
IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
|
||||
IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64),
|
||||
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)
|
||||
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64),
|
||||
];
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.finalize();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let udp_handle = sockets.add(udp_socket);
|
||||
let tcp1_handle = sockets.add(tcp1_socket);
|
||||
let tcp2_handle = sockets.add(tcp2_socket);
|
||||
let tcp3_handle = sockets.add(tcp3_socket);
|
||||
let tcp4_handle = sockets.add(tcp4_socket);
|
||||
let medium = device.capabilities().medium;
|
||||
let mut builder = InterfaceBuilder::new(device, vec![]).ip_addrs(ip_addrs);
|
||||
if medium == Medium::Ethernet {
|
||||
builder = builder
|
||||
.hardware_addr(ethernet_addr.into())
|
||||
.neighbor_cache(neighbor_cache);
|
||||
}
|
||||
let mut iface = builder.finalize();
|
||||
|
||||
let udp_handle = iface.add_socket(udp_socket);
|
||||
let tcp1_handle = iface.add_socket(tcp1_socket);
|
||||
let tcp2_handle = iface.add_socket(tcp2_socket);
|
||||
let tcp3_handle = iface.add_socket(tcp3_socket);
|
||||
let tcp4_handle = iface.add_socket(tcp4_socket);
|
||||
|
||||
let mut tcp_6970_active = false;
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll(&mut sockets, timestamp) {
|
||||
Ok(_) => {},
|
||||
match iface.poll(timestamp) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// udp:6969: respond "hello"
|
||||
{
|
||||
let mut socket = sockets.get::<UdpSocket>(udp_handle);
|
||||
if !socket.is_open() {
|
||||
socket.bind(6969).unwrap()
|
||||
}
|
||||
let socket = iface.get_socket::<UdpSocket>(udp_handle);
|
||||
if !socket.is_open() {
|
||||
socket.bind(6969).unwrap()
|
||||
}
|
||||
|
||||
let client = match socket.recv() {
|
||||
Ok((data, endpoint)) => {
|
||||
debug!("udp:6969 recv data: {:?} from {}",
|
||||
str::from_utf8(data).unwrap(), endpoint);
|
||||
Some(endpoint)
|
||||
}
|
||||
Err(_) => None
|
||||
};
|
||||
if let Some(endpoint) = client {
|
||||
let data = b"hello\n";
|
||||
debug!("udp:6969 send data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap());
|
||||
socket.send_slice(data, endpoint).unwrap();
|
||||
let client = match socket.recv() {
|
||||
Ok((data, endpoint)) => {
|
||||
debug!(
|
||||
"udp:6969 recv data: {:?} from {}",
|
||||
str::from_utf8(data).unwrap(),
|
||||
endpoint
|
||||
);
|
||||
Some(endpoint)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
if let Some(endpoint) = client {
|
||||
let data = b"hello\n";
|
||||
debug!(
|
||||
"udp:6969 send data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap()
|
||||
);
|
||||
socket.send_slice(data, endpoint).unwrap();
|
||||
}
|
||||
|
||||
// tcp:6969: respond "hello"
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp1_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6969).unwrap();
|
||||
}
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp1_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6969).unwrap();
|
||||
}
|
||||
|
||||
if socket.can_send() {
|
||||
debug!("tcp:6969 send greeting");
|
||||
writeln!(socket, "hello").unwrap();
|
||||
debug!("tcp:6969 close");
|
||||
socket.close();
|
||||
}
|
||||
if socket.can_send() {
|
||||
debug!("tcp:6969 send greeting");
|
||||
writeln!(socket, "hello").unwrap();
|
||||
debug!("tcp:6969 close");
|
||||
socket.close();
|
||||
}
|
||||
|
||||
// tcp:6970: echo with reverse
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp2_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6970).unwrap()
|
||||
}
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp2_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6970).unwrap()
|
||||
}
|
||||
|
||||
if socket.is_active() && !tcp_6970_active {
|
||||
debug!("tcp:6970 connected");
|
||||
} else if !socket.is_active() && tcp_6970_active {
|
||||
debug!("tcp:6970 disconnected");
|
||||
}
|
||||
tcp_6970_active = socket.is_active();
|
||||
if socket.is_active() && !tcp_6970_active {
|
||||
debug!("tcp:6970 connected");
|
||||
} else if !socket.is_active() && tcp_6970_active {
|
||||
debug!("tcp:6970 disconnected");
|
||||
}
|
||||
tcp_6970_active = socket.is_active();
|
||||
|
||||
if socket.may_recv() {
|
||||
let data = socket.recv(|buffer| {
|
||||
if socket.may_recv() {
|
||||
let data = socket
|
||||
.recv(|buffer| {
|
||||
let recvd_len = buffer.len();
|
||||
let mut data = buffer.to_owned();
|
||||
if !data.is_empty() {
|
||||
debug!("tcp:6970 recv data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)"));
|
||||
debug!(
|
||||
"tcp:6970 recv data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
|
||||
);
|
||||
data = data.split(|&b| b == b'\n').collect::<Vec<_>>().concat();
|
||||
data.reverse();
|
||||
data.extend(b"\n");
|
||||
}
|
||||
(recvd_len, data)
|
||||
}).unwrap();
|
||||
if socket.can_send() && !data.is_empty() {
|
||||
debug!("tcp:6970 send data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)"));
|
||||
socket.send_slice(&data[..]).unwrap();
|
||||
}
|
||||
} else if socket.may_send() {
|
||||
debug!("tcp:6970 close");
|
||||
socket.close();
|
||||
})
|
||||
.unwrap();
|
||||
if socket.can_send() && !data.is_empty() {
|
||||
debug!(
|
||||
"tcp:6970 send data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")
|
||||
);
|
||||
socket.send_slice(&data[..]).unwrap();
|
||||
}
|
||||
} else if socket.may_send() {
|
||||
debug!("tcp:6970 close");
|
||||
socket.close();
|
||||
}
|
||||
|
||||
// tcp:6971: sinkhole
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp3_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6971).unwrap();
|
||||
socket.set_keep_alive(Some(Duration::from_millis(1000)));
|
||||
socket.set_timeout(Some(Duration::from_millis(2000)));
|
||||
}
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp3_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6971).unwrap();
|
||||
socket.set_keep_alive(Some(Duration::from_millis(1000)));
|
||||
socket.set_timeout(Some(Duration::from_millis(2000)));
|
||||
}
|
||||
|
||||
if socket.may_recv() {
|
||||
socket.recv(|buffer| {
|
||||
if socket.may_recv() {
|
||||
socket
|
||||
.recv(|buffer| {
|
||||
if !buffer.is_empty() {
|
||||
debug!("tcp:6971 recv {:?} octets", buffer.len());
|
||||
}
|
||||
(buffer.len(), ())
|
||||
}).unwrap();
|
||||
} else if socket.may_send() {
|
||||
socket.close();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
} else if socket.may_send() {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
// tcp:6972: fountain
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp4_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6972).unwrap()
|
||||
}
|
||||
let socket = iface.get_socket::<TcpSocket>(tcp4_handle);
|
||||
if !socket.is_open() {
|
||||
socket.listen(6972).unwrap()
|
||||
}
|
||||
|
||||
if socket.may_send() {
|
||||
socket.send(|data| {
|
||||
if socket.may_send() {
|
||||
socket
|
||||
.send(|data| {
|
||||
if !data.is_empty() {
|
||||
debug!("tcp:6972 send {:?} octets", data.len());
|
||||
for (i, b) in data.iter_mut().enumerate() {
|
||||
|
@ -190,10 +197,10 @@ fn main() {
|
|||
}
|
||||
}
|
||||
(data.len(), ())
|
||||
}).unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error");
|
||||
phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
//! 6lowpan exmaple
|
||||
//!
|
||||
//! This example is designed to run using the Linux ieee802154/6lowpan support,
|
||||
//! using mac802154_hwsim.
|
||||
//!
|
||||
//! mac802154_hwsim allows you to create multiple "virtual" radios and specify
|
||||
//! which is in range with which. This is very useful for testing without
|
||||
//! needing real hardware. By default it creates two interfaces `wpan0` and
|
||||
//! `wpan1` that are in range with each other. You can customize this with
|
||||
//! the `wpan-hwsim` tool.
|
||||
//!
|
||||
//! We'll configure Linux to speak 6lowpan on `wpan0`, and leave `wpan1`
|
||||
//! unconfigured so smoltcp can use it with a raw socket.
|
||||
//!
|
||||
//! # Setup
|
||||
//!
|
||||
//! modprobe mac802154_hwsim
|
||||
//!
|
||||
//! ip link set wpan0 down
|
||||
//! ip link set wpan1 down
|
||||
//! iwpan dev wpan0 set pan_id 0xbeef
|
||||
//! iwpan dev wpan1 set pan_id 0xbeef
|
||||
//! ip link add link wpan0 name lowpan0 type lowpan
|
||||
//! ip link set wpan0 up
|
||||
//! ip link set wpan1 up
|
||||
//! ip link set lowpan0 up
|
||||
//!
|
||||
//! # Running
|
||||
//!
|
||||
//! Run it with `sudo ./target/debug/examples/sixlowpan`.
|
||||
//!
|
||||
//! You can set wireshark to sniff on interface `wpan0` to see the packets.
|
||||
//!
|
||||
//! Ping it with `ping fe80::180b:4242:4242:4242%lowpan0`.
|
||||
//!
|
||||
//! Speak UDP with `nc -uv fe80::180b:4242:4242:4242%lowpan0 6969`.
|
||||
//!
|
||||
//! # Teardown
|
||||
//!
|
||||
//! rmmod mac802154_hwsim
|
||||
//!
|
||||
|
||||
mod utils;
|
||||
|
||||
use log::debug;
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::str;
|
||||
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache};
|
||||
use smoltcp::phy::{wait as phy_wait, Medium, RawSocket};
|
||||
use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{Ieee802154Pan, IpAddress, IpCidr};
|
||||
|
||||
fn main() {
|
||||
utils::setup_logging("");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
|
||||
let device = RawSocket::new("wpan1", Medium::Ieee802154).unwrap();
|
||||
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false);
|
||||
|
||||
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||
|
||||
let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 64]);
|
||||
let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 128]);
|
||||
let udp_socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer);
|
||||
|
||||
let ieee802154_addr = smoltcp::wire::Ieee802154Address::Extended([
|
||||
0x1a, 0x0b, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
|
||||
]);
|
||||
let ip_addrs = [IpCidr::new(
|
||||
IpAddress::v6(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242),
|
||||
64,
|
||||
)];
|
||||
|
||||
let mut builder = InterfaceBuilder::new(device, vec![])
|
||||
.ip_addrs(ip_addrs)
|
||||
.pan_id(Ieee802154Pan(0xbeef));
|
||||
builder = builder
|
||||
.hardware_addr(ieee802154_addr.into())
|
||||
.neighbor_cache(neighbor_cache);
|
||||
let mut iface = builder.finalize();
|
||||
|
||||
let udp_handle = iface.add_socket(udp_socket);
|
||||
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
match iface.poll(timestamp) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
debug!("poll error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// udp:6969: respond "hello"
|
||||
let socket = iface.get_socket::<UdpSocket>(udp_handle);
|
||||
if !socket.is_open() {
|
||||
socket.bind(6969).unwrap()
|
||||
}
|
||||
|
||||
let client = match socket.recv() {
|
||||
Ok((data, endpoint)) => {
|
||||
debug!(
|
||||
"udp:6969 recv data: {:?} from {}",
|
||||
str::from_utf8(data).unwrap(),
|
||||
endpoint
|
||||
);
|
||||
Some(endpoint)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
if let Some(endpoint) = client {
|
||||
let data = b"hello\n";
|
||||
debug!(
|
||||
"udp:6969 send data: {:?}",
|
||||
str::from_utf8(data.as_ref()).unwrap()
|
||||
);
|
||||
socket.send_slice(data, endpoint).unwrap();
|
||||
}
|
||||
|
||||
phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error");
|
||||
}
|
||||
}
|
|
@ -1,19 +1,24 @@
|
|||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::phy::{Device, RawSocket, RxToken};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{EthernetFrame, PrettyPrinter};
|
||||
use std::env;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use smoltcp::phy::wait as phy_wait;
|
||||
use smoltcp::phy::{Device, RxToken, RawSocket};
|
||||
use smoltcp::wire::{PrettyPrinter, EthernetFrame};
|
||||
use smoltcp::time::Instant;
|
||||
|
||||
fn main() {
|
||||
let ifname = env::args().nth(1).unwrap();
|
||||
let mut socket = RawSocket::new(ifname.as_ref()).unwrap();
|
||||
let mut socket = RawSocket::new(ifname.as_ref(), smoltcp::phy::Medium::Ethernet).unwrap();
|
||||
loop {
|
||||
phy_wait(socket.as_raw_fd(), None).unwrap();
|
||||
let (rx_token, _) = socket.receive().unwrap();
|
||||
rx_token.consume(Instant::now(), |buffer| {
|
||||
println!("{}", PrettyPrinter::<EthernetFrame<&[u8]>>::new("", &buffer));
|
||||
Ok(())
|
||||
}).unwrap();
|
||||
rx_token
|
||||
.consume(Instant::now(), |buffer| {
|
||||
println!(
|
||||
"{}",
|
||||
PrettyPrinter::<EthernetFrame<&[u8]>>::new("", &buffer)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,56 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::str::{self, FromStr};
|
||||
use std::rc::Rc;
|
||||
use std::io::{self, Write};
|
||||
use std::fs::File;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::env;
|
||||
use std::process;
|
||||
#[cfg(feature = "log")]
|
||||
use log::{Level, LevelFilter, trace};
|
||||
#[cfg(feature = "log")]
|
||||
use env_logger::Builder;
|
||||
use getopts::{Options, Matches};
|
||||
use getopts::{Matches, Options};
|
||||
#[cfg(feature = "log")]
|
||||
use log::{trace, Level, LevelFilter};
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::process;
|
||||
use std::str::{self, FromStr};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use smoltcp::phy::{Device, EthernetTracer, FaultInjector};
|
||||
#[cfg(feature = "phy-tap_interface")]
|
||||
use smoltcp::phy::TapInterface;
|
||||
use smoltcp::phy::{PcapWriter, PcapSink, PcapMode, PcapLinkType};
|
||||
use smoltcp::phy::RawSocket;
|
||||
#[cfg(feature = "phy-tuntap_interface")]
|
||||
use smoltcp::phy::TunTapInterface;
|
||||
use smoltcp::phy::{Device, FaultInjector, Medium, Tracer};
|
||||
use smoltcp::phy::{PcapMode, PcapWriter};
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
pub fn setup_logging_with_clock<F>(filter: &str, since_startup: F)
|
||||
where F: Fn() -> Instant + Send + Sync + 'static {
|
||||
where
|
||||
F: Fn() -> Instant + Send + Sync + 'static,
|
||||
{
|
||||
Builder::new()
|
||||
.format(move |buf, record| {
|
||||
let elapsed = since_startup();
|
||||
let timestamp = format!("[{}]", elapsed);
|
||||
if record.target().starts_with("smoltcp::") {
|
||||
writeln!(buf, "\x1b[0m{} ({}): {}\x1b[0m", timestamp,
|
||||
record.target().replace("smoltcp::", ""), record.args())
|
||||
writeln!(
|
||||
buf,
|
||||
"\x1b[0m{} ({}): {}\x1b[0m",
|
||||
timestamp,
|
||||
record.target().replace("smoltcp::", ""),
|
||||
record.args()
|
||||
)
|
||||
} else if record.level() == Level::Trace {
|
||||
let message = format!("{}", record.args());
|
||||
writeln!(buf, "\x1b[37m{} {}\x1b[0m", timestamp,
|
||||
message.replace("\n", "\n "))
|
||||
writeln!(
|
||||
buf,
|
||||
"\x1b[37m{} {}\x1b[0m",
|
||||
timestamp,
|
||||
message.replace("\n", "\n ")
|
||||
)
|
||||
} else {
|
||||
writeln!(buf, "\x1b[32m{} ({}): {}\x1b[0m", timestamp,
|
||||
record.target(), record.args())
|
||||
writeln!(
|
||||
buf,
|
||||
"\x1b[32m{} ({}): {}\x1b[0m",
|
||||
timestamp,
|
||||
record.target(),
|
||||
record.args()
|
||||
)
|
||||
}
|
||||
})
|
||||
.filter(None, LevelFilter::Trace)
|
||||
|
@ -48,9 +61,7 @@ pub fn setup_logging_with_clock<F>(filter: &str, since_startup: F)
|
|||
|
||||
#[cfg(feature = "log")]
|
||||
pub fn setup_logging(filter: &str) {
|
||||
setup_logging_with_clock(filter, move || {
|
||||
Instant::now()
|
||||
})
|
||||
setup_logging_with_clock(filter, Instant::now)
|
||||
}
|
||||
|
||||
pub fn create_options() -> (Options, Vec<&'static str>) {
|
||||
|
@ -67,59 +78,113 @@ pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches {
|
|||
}
|
||||
Ok(matches) => {
|
||||
if matches.opt_present("h") || matches.free.len() != free.len() {
|
||||
let brief = format!("Usage: {} [OPTION]... {}",
|
||||
env::args().next().unwrap(), free.join(" "));
|
||||
let brief = format!(
|
||||
"Usage: {} [OPTION]... {}",
|
||||
env::args().next().unwrap(),
|
||||
free.join(" ")
|
||||
);
|
||||
print!("{}", options.usage(&brief));
|
||||
process::exit(if matches.free.len() != free.len() { 1 } else { 0 })
|
||||
process::exit(if matches.free.len() != free.len() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
})
|
||||
}
|
||||
matches
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_tap_options(_opts: &mut Options, free: &mut Vec<&str>) {
|
||||
free.push("INTERFACE");
|
||||
pub fn add_tuntap_options(opts: &mut Options, _free: &mut Vec<&str>) {
|
||||
opts.optopt("", "tun", "TUN interface to use", "tun0");
|
||||
opts.optopt("", "tap", "TAP interface to use", "tap0");
|
||||
}
|
||||
|
||||
#[cfg(feature = "phy-tap_interface")]
|
||||
pub fn parse_tap_options(matches: &mut Matches) -> TapInterface {
|
||||
let interface = matches.free.remove(0);
|
||||
TapInterface::new(&interface).unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_raw_socket_options(matches: &mut Matches) -> RawSocket {
|
||||
let interface = matches.free.remove(0);
|
||||
RawSocket::new(&interface).unwrap()
|
||||
#[cfg(feature = "phy-tuntap_interface")]
|
||||
pub fn parse_tuntap_options(matches: &mut Matches) -> TunTapInterface {
|
||||
let tun = matches.opt_str("tun");
|
||||
let tap = matches.opt_str("tap");
|
||||
match (tun, tap) {
|
||||
(Some(tun), None) => TunTapInterface::new(&tun, Medium::Ip).unwrap(),
|
||||
(None, Some(tap)) => TunTapInterface::new(&tap, Medium::Ethernet).unwrap(),
|
||||
_ => panic!("You must specify exactly one of --tun or --tap"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_middleware_options(opts: &mut Options, _free: &mut Vec<&str>) {
|
||||
opts.optopt("", "pcap", "Write a packet capture file", "FILE");
|
||||
opts.optopt("", "drop-chance", "Chance of dropping a packet (%)", "CHANCE");
|
||||
opts.optopt("", "corrupt-chance", "Chance of corrupting a packet (%)", "CHANCE");
|
||||
opts.optopt("", "size-limit", "Drop packets larger than given size (octets)", "SIZE");
|
||||
opts.optopt("", "tx-rate-limit", "Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)", "RATE");
|
||||
opts.optopt("", "rx-rate-limit", "Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)", "RATE");
|
||||
opts.optopt("", "shaping-interval", "Sets the interval for rate limiting (ms)", "RATE");
|
||||
opts.optopt(
|
||||
"",
|
||||
"drop-chance",
|
||||
"Chance of dropping a packet (%)",
|
||||
"CHANCE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"corrupt-chance",
|
||||
"Chance of corrupting a packet (%)",
|
||||
"CHANCE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"size-limit",
|
||||
"Drop packets larger than given size (octets)",
|
||||
"SIZE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"tx-rate-limit",
|
||||
"Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)",
|
||||
"RATE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"rx-rate-limit",
|
||||
"Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)",
|
||||
"RATE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"shaping-interval",
|
||||
"Sets the interval for rate limiting (ms)",
|
||||
"RATE",
|
||||
);
|
||||
}
|
||||
|
||||
pub fn parse_middleware_options<D>(matches: &mut Matches, device: D, loopback: bool)
|
||||
-> FaultInjector<EthernetTracer<PcapWriter<D, Rc<dyn PcapSink>>>>
|
||||
where D: for<'a> Device<'a>
|
||||
pub fn parse_middleware_options<D>(
|
||||
matches: &mut Matches,
|
||||
device: D,
|
||||
loopback: bool,
|
||||
) -> FaultInjector<Tracer<PcapWriter<D, Box<dyn io::Write>>>>
|
||||
where
|
||||
D: for<'a> Device<'a>,
|
||||
{
|
||||
let drop_chance = matches.opt_str("drop-chance").map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let corrupt_chance = matches.opt_str("corrupt-chance").map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let size_limit = matches.opt_str("size-limit").map(|s| usize::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let tx_rate_limit = matches.opt_str("tx-rate-limit").map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let rx_rate_limit = matches.opt_str("rx-rate-limit").map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let shaping_interval = matches.opt_str("shaping-interval").map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let drop_chance = matches
|
||||
.opt_str("drop-chance")
|
||||
.map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let corrupt_chance = matches
|
||||
.opt_str("corrupt-chance")
|
||||
.map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let size_limit = matches
|
||||
.opt_str("size-limit")
|
||||
.map(|s| usize::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let tx_rate_limit = matches
|
||||
.opt_str("tx-rate-limit")
|
||||
.map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let rx_rate_limit = matches
|
||||
.opt_str("rx-rate-limit")
|
||||
.map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let shaping_interval = matches
|
||||
.opt_str("shaping-interval")
|
||||
.map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
|
||||
let pcap_writer: Box<dyn io::Write>;
|
||||
if let Some(pcap_filename) = matches.opt_str("pcap") {
|
||||
|
@ -128,15 +193,26 @@ pub fn parse_middleware_options<D>(matches: &mut Matches, device: D, loopback: b
|
|||
pcap_writer = Box::new(io::sink())
|
||||
}
|
||||
|
||||
let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos();
|
||||
let seed = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.subsec_nanos();
|
||||
|
||||
let device = PcapWriter::new(device, Rc::new(RefCell::new(pcap_writer)) as Rc<dyn PcapSink>,
|
||||
if loopback { PcapMode::TxOnly } else { PcapMode::Both },
|
||||
PcapLinkType::Ethernet);
|
||||
let device = EthernetTracer::new(device, |_timestamp, _printer| {
|
||||
let device = PcapWriter::new(
|
||||
device,
|
||||
pcap_writer,
|
||||
if loopback {
|
||||
PcapMode::TxOnly
|
||||
} else {
|
||||
PcapMode::Both
|
||||
},
|
||||
);
|
||||
|
||||
let device = Tracer::new(device, |_timestamp, _printer| {
|
||||
#[cfg(feature = "log")]
|
||||
trace!("{}", _printer);
|
||||
});
|
||||
|
||||
let mut device = FaultInjector::new(device, seed);
|
||||
device.set_drop_chance(drop_chance);
|
||||
device.set_corrupt_chance(corrupt_chance);
|
||||
|
|
|
@ -3,21 +3,16 @@ name = "smoltcp-fuzz"
|
|||
version = "0.0.1"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
arbitrary = { version = "1", features = ["derive"] }
|
||||
getopts = "0.2"
|
||||
|
||||
[dependencies.smoltcp]
|
||||
path = ".."
|
||||
|
||||
[dependencies.libfuzzer-sys]
|
||||
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # needed to prevent weird linker error about sancov guards
|
||||
smoltcp = { path = "..", features = [ "medium-ethernet" ] }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
|
@ -26,7 +21,35 @@ members = ["."]
|
|||
[[bin]]
|
||||
name = "packet_parser"
|
||||
path = "fuzz_targets/packet_parser.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "tcp_headers"
|
||||
path = "fuzz_targets/tcp_headers.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "dhcp_header"
|
||||
path = "fuzz_targets/dhcp_header.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "ieee802154_header"
|
||||
path = "fuzz_targets/ieee802154_header.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "sixlowpan_udp_header"
|
||||
path = "fuzz_targets/sixlowpan_udp_header.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "sixlowpan_iphc_header"
|
||||
path = "fuzz_targets/sixlowpan_iphc_header.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use smoltcp::wire::{DhcpPacket, DhcpRepr};
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let _ = match DhcpPacket::new_checked(data) {
|
||||
Ok(ref packet) => match DhcpRepr::parse(packet) {
|
||||
Ok(dhcp_repr) => {
|
||||
let mut dhcp_payload = vec![0; dhcp_repr.buffer_len()];
|
||||
match DhcpPacket::new_checked(&mut dhcp_payload[..]) {
|
||||
Ok(mut dhcp_packet) => Some(dhcp_repr.emit(&mut dhcp_packet)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
};
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use smoltcp::wire::{Ieee802154Frame, Ieee802154Repr};
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(ref frame) = Ieee802154Frame::new_checked(data) {
|
||||
if let Ok(repr) = Ieee802154Repr::parse(frame) {
|
||||
// The buffer len returns only the lenght required for emitting the header
|
||||
// and does not take into account the length of the payload.
|
||||
let mut buffer = vec![0; repr.buffer_len()];
|
||||
|
||||
// NOTE: unchecked because the checked version checks if the addressing mode field
|
||||
// is valid or not. The addressing mode field is required for calculating the length of
|
||||
// the header, which is used in `check_len`.
|
||||
let mut frame = Ieee802154Frame::new_unchecked(&mut buffer[..]);
|
||||
repr.emit(&mut frame);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,8 +1,10 @@
|
|||
#![no_main]
|
||||
#[macro_use] extern crate libfuzzer_sys;
|
||||
extern crate smoltcp;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use smoltcp::wire::*;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
use smoltcp::wire::*;
|
||||
format!("{}", PrettyPrinter::<EthernetFrame<&'static [u8]>>::new("", &data));
|
||||
format!(
|
||||
"{}",
|
||||
PrettyPrinter::<EthernetFrame<&'static [u8]>>::new("", &data)
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use smoltcp::wire::{Ieee802154Address, SixlowpanIphcPacket, SixlowpanIphcRepr};
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, arbitrary::Arbitrary)]
|
||||
pub enum AddressFuzzer {
|
||||
Absent,
|
||||
Short([u8; 2]),
|
||||
Extended([u8; 8]),
|
||||
}
|
||||
|
||||
impl From<AddressFuzzer> for Ieee802154Address {
|
||||
fn from(val: AddressFuzzer) -> Self {
|
||||
match val {
|
||||
AddressFuzzer::Absent => Ieee802154Address::Absent,
|
||||
AddressFuzzer::Short(b) => Ieee802154Address::Short(b),
|
||||
AddressFuzzer::Extended(b) => Ieee802154Address::Extended(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, arbitrary::Arbitrary)]
|
||||
struct SixlowpanIphcPacketFuzzer<'a> {
|
||||
data: &'a [u8],
|
||||
ll_src_addr: Option<AddressFuzzer>,
|
||||
ll_dst_addr: Option<AddressFuzzer>,
|
||||
}
|
||||
|
||||
fuzz_target!(|fuzz: SixlowpanIphcPacketFuzzer| {
|
||||
if let Ok(ref frame) = SixlowpanIphcPacket::new_checked(fuzz.data) {
|
||||
if let Ok(repr) = SixlowpanIphcRepr::parse(
|
||||
frame,
|
||||
fuzz.ll_src_addr.map(Into::into),
|
||||
fuzz.ll_dst_addr.map(Into::into),
|
||||
) {
|
||||
let mut buffer = vec![0; repr.buffer_len()];
|
||||
|
||||
let mut frame = SixlowpanIphcPacket::new_unchecked(&mut buffer[..]);
|
||||
repr.emit(&mut frame);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use smoltcp::wire::{Ipv6Address, SixlowpanUdpPacket, SixlowpanUdpRepr};
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, arbitrary::Arbitrary)]
|
||||
pub struct AddressFuzzer(pub [u8; 16]);
|
||||
|
||||
impl From<AddressFuzzer> for Ipv6Address {
|
||||
fn from(val: AddressFuzzer) -> Self {
|
||||
Ipv6Address(val.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, arbitrary::Arbitrary)]
|
||||
struct SixlowpanUdpPacketFuzzer<'a> {
|
||||
data: &'a [u8],
|
||||
src_addr: AddressFuzzer,
|
||||
dst_addr: AddressFuzzer,
|
||||
checksum: Option<u16>,
|
||||
}
|
||||
|
||||
fuzz_target!(|fuzz: SixlowpanUdpPacketFuzzer| {
|
||||
if let Ok(ref frame) = SixlowpanUdpPacket::new_checked(fuzz.data) {
|
||||
if let Ok(repr) = SixlowpanUdpRepr::parse(
|
||||
frame,
|
||||
&fuzz.src_addr.into(),
|
||||
&fuzz.dst_addr.into(),
|
||||
fuzz.checksum,
|
||||
) {
|
||||
let payload = frame.payload();
|
||||
let mut buffer = vec![0; repr.header_len() + payload.len()];
|
||||
|
||||
let mut frame = SixlowpanUdpPacket::new_unchecked(&mut buffer[..]);
|
||||
repr.emit(
|
||||
&mut frame,
|
||||
&fuzz.src_addr.into(),
|
||||
&fuzz.dst_addr.into(),
|
||||
payload.len(),
|
||||
|b| b.copy_from_slice(payload),
|
||||
);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,29 +1,24 @@
|
|||
#![no_main]
|
||||
#[macro_use] extern crate libfuzzer_sys;
|
||||
extern crate smoltcp;
|
||||
|
||||
use std as core;
|
||||
extern crate getopts;
|
||||
|
||||
use core::cmp;
|
||||
use smoltcp::phy::Loopback;
|
||||
use smoltcp::wire::{EthernetAddress, EthernetFrame, EthernetProtocol};
|
||||
use smoltcp::wire::{IpAddress, IpCidr, Ipv4Packet, Ipv6Packet, TcpPacket};
|
||||
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use smoltcp::iface::{InterfaceBuilder, NeighborCache};
|
||||
use smoltcp::phy::{Loopback, Medium};
|
||||
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use smoltcp::wire::{EthernetAddress, EthernetFrame, EthernetProtocol};
|
||||
use smoltcp::wire::{IpAddress, IpCidr, Ipv4Packet, Ipv6Packet, TcpPacket};
|
||||
use std::cmp;
|
||||
|
||||
mod utils {
|
||||
include!("../utils.rs");
|
||||
}
|
||||
#[path = "../utils.rs"]
|
||||
mod utils;
|
||||
|
||||
mod mock {
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{Ordering, AtomicUsize};
|
||||
use smoltcp::time::{Duration, Instant};
|
||||
|
||||
// should be AtomicU64 but that's unstable
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Clock(Arc<AtomicUsize>);
|
||||
|
||||
impl Clock {
|
||||
|
@ -32,7 +27,8 @@ mod mock {
|
|||
}
|
||||
|
||||
pub fn advance(&self, duration: Duration) {
|
||||
self.0.fetch_add(duration.total_millis() as usize, Ordering::SeqCst);
|
||||
self.0
|
||||
.fetch_add(duration.total_millis() as usize, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn elapsed(&self) -> Instant {
|
||||
|
@ -51,7 +47,10 @@ impl TcpHeaderFuzzer {
|
|||
//
|
||||
// Otherwise, it replaces the entire rest of the TCP header with the fuzzer's output.
|
||||
pub fn new(data: &[u8]) -> TcpHeaderFuzzer {
|
||||
let copy_len = cmp::min(data.len(), 56 /* max TCP header length without port numbers*/);
|
||||
let copy_len = cmp::min(
|
||||
data.len(),
|
||||
56, /* max TCP header length without port numbers*/
|
||||
);
|
||||
|
||||
let mut fuzzer = TcpHeaderFuzzer([0; 56], copy_len);
|
||||
fuzzer.0[..copy_len].copy_from_slice(&data[..copy_len]);
|
||||
|
@ -67,13 +66,16 @@ impl smoltcp::phy::Fuzzer for TcpHeaderFuzzer {
|
|||
|
||||
let tcp_packet_offset = {
|
||||
let eth_frame = EthernetFrame::new_unchecked(&frame_data);
|
||||
EthernetFrame::<&mut [u8]>::header_len() + match eth_frame.ethertype() {
|
||||
EthernetProtocol::Ipv4 =>
|
||||
Ipv4Packet::new_unchecked(eth_frame.payload()).header_len() as usize,
|
||||
EthernetProtocol::Ipv6 =>
|
||||
Ipv6Packet::new_unchecked(eth_frame.payload()).header_len() as usize,
|
||||
_ => return
|
||||
}
|
||||
EthernetFrame::<&mut [u8]>::header_len()
|
||||
+ match eth_frame.ethertype() {
|
||||
EthernetProtocol::Ipv4 => {
|
||||
Ipv4Packet::new_unchecked(eth_frame.payload()).header_len() as usize
|
||||
}
|
||||
EthernetProtocol::Ipv6 => {
|
||||
Ipv6Packet::new_unchecked(eth_frame.payload()).header_len() as usize
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
};
|
||||
|
||||
let tcp_is_syn = {
|
||||
|
@ -94,7 +96,7 @@ impl smoltcp::phy::Fuzzer for TcpHeaderFuzzer {
|
|||
(tcp_packet[12] as usize >> 4) * 4
|
||||
};
|
||||
|
||||
let tcp_packet = &mut frame_data[tcp_packet_offset+4..];
|
||||
let tcp_packet = &mut frame_data[tcp_packet_offset + 4..];
|
||||
|
||||
let replacement_data = &self.0[..self.1];
|
||||
let copy_len = cmp::min(replacement_data.len(), tcp_header_len);
|
||||
|
@ -113,28 +115,28 @@ fuzz_target!(|data: &[u8]| {
|
|||
let clock = mock::Clock::new();
|
||||
|
||||
let device = {
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_middleware_options(&mut matches, Loopback::new(),
|
||||
/*loopback=*/true);
|
||||
let device = utils::parse_middleware_options(
|
||||
&mut matches,
|
||||
Loopback::new(Medium::Ethernet),
|
||||
/*loopback=*/ true,
|
||||
);
|
||||
|
||||
smoltcp::phy::FuzzInjector::new(device,
|
||||
EmptyFuzzer(),
|
||||
TcpHeaderFuzzer::new(data))
|
||||
smoltcp::phy::FuzzInjector::new(device, EmptyFuzzer(), TcpHeaderFuzzer::new(data))
|
||||
};
|
||||
|
||||
let mut neighbor_cache_entries = [None; 8];
|
||||
let neighbor_cache = NeighborCache::new(&mut neighbor_cache_entries[..]);
|
||||
|
||||
let ip_addrs = [IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)];
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(EthernetAddress::default())
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.finalize();
|
||||
let mut iface = InterfaceBuilder::new(device)
|
||||
.ethernet_addr(EthernetAddress::default())
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.finalize();
|
||||
|
||||
let server_socket = {
|
||||
// It is not strictly necessary to use a `static mut` and unsafe code here, but
|
||||
|
@ -161,7 +163,7 @@ fuzz_target!(|data: &[u8]| {
|
|||
let server_handle = socket_set.add(server_socket);
|
||||
let client_handle = socket_set.add(client_socket);
|
||||
|
||||
let mut did_listen = false;
|
||||
let mut did_listen = false;
|
||||
let mut did_connect = false;
|
||||
let mut done = false;
|
||||
while !done && clock.elapsed() < Instant::from_millis(4_000) {
|
||||
|
@ -186,24 +188,28 @@ fuzz_target!(|data: &[u8]| {
|
|||
let mut socket = socket_set.get::<TcpSocket>(client_handle);
|
||||
if !socket.is_open() {
|
||||
if !did_connect {
|
||||
socket.connect((IpAddress::v4(127, 0, 0, 1), 1234),
|
||||
(IpAddress::Unspecified, 65000)).unwrap();
|
||||
socket
|
||||
.connect(
|
||||
(IpAddress::v4(127, 0, 0, 1), 1234),
|
||||
(IpAddress::Unspecified, 65000),
|
||||
)
|
||||
.unwrap();
|
||||
did_connect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if socket.can_send() {
|
||||
socket.send_slice(b"0123456789abcdef0123456789abcdef0123456789abcdef").unwrap();
|
||||
socket
|
||||
.send_slice(b"0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
.unwrap();
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
match iface.poll_delay(&socket_set, clock.elapsed()) {
|
||||
Some(Duration { millis: 0 }) => {},
|
||||
Some(delay) => {
|
||||
clock.advance(delay)
|
||||
},
|
||||
None => clock.advance(Duration::from_millis(1))
|
||||
Some(Duration::ZERO) => {}
|
||||
Some(delay) => clock.advance(delay),
|
||||
None => clock.advance(Duration::from_millis(1)),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
144
fuzz/utils.rs
144
fuzz/utils.rs
|
@ -1,18 +1,17 @@
|
|||
// TODO: this is literally a copy of examples/utils.rs, but without an allow dead code attribute.
|
||||
// The include logic does not allow having attributes in included files.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::str::{self, FromStr};
|
||||
use std::rc::Rc;
|
||||
use std::io;
|
||||
use std::fs::File;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use getopts::{Matches, Options};
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::process;
|
||||
use getopts::{Options, Matches};
|
||||
use std::str::{self, FromStr};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use smoltcp::phy::{Device, EthernetTracer, FaultInjector};
|
||||
use smoltcp::phy::{PcapWriter, PcapSink, PcapMode, PcapLinkType};
|
||||
use smoltcp::phy::{Device, FaultInjector, Tracer};
|
||||
use smoltcp::phy::{PcapMode, PcapWriter};
|
||||
use smoltcp::time::Duration;
|
||||
|
||||
pub fn create_options() -> (Options, Vec<&'static str>) {
|
||||
|
@ -29,10 +28,17 @@ pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches {
|
|||
}
|
||||
Ok(matches) => {
|
||||
if matches.opt_present("h") || matches.free.len() != free.len() {
|
||||
let brief = format!("Usage: {} [OPTION]... {}",
|
||||
env::args().nth(0).unwrap(), free.join(" "));
|
||||
let brief = format!(
|
||||
"Usage: {} [OPTION]... {}",
|
||||
env::args().nth(0).unwrap(),
|
||||
free.join(" ")
|
||||
);
|
||||
print!("{}", options.usage(&brief));
|
||||
process::exit(if matches.free.len() != free.len() { 1 } else { 0 })
|
||||
process::exit(if matches.free.len() != free.len() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
})
|
||||
}
|
||||
matches
|
||||
}
|
||||
|
@ -41,46 +47,102 @@ pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches {
|
|||
|
||||
pub fn add_middleware_options(opts: &mut Options, _free: &mut Vec<&str>) {
|
||||
opts.optopt("", "pcap", "Write a packet capture file", "FILE");
|
||||
opts.optopt("", "drop-chance", "Chance of dropping a packet (%)", "CHANCE");
|
||||
opts.optopt("", "corrupt-chance", "Chance of corrupting a packet (%)", "CHANCE");
|
||||
opts.optopt("", "size-limit", "Drop packets larger than given size (octets)", "SIZE");
|
||||
opts.optopt("", "tx-rate-limit", "Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)", "RATE");
|
||||
opts.optopt("", "rx-rate-limit", "Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)", "RATE");
|
||||
opts.optopt("", "shaping-interval", "Sets the interval for rate limiting (ms)", "RATE");
|
||||
opts.optopt(
|
||||
"",
|
||||
"drop-chance",
|
||||
"Chance of dropping a packet (%)",
|
||||
"CHANCE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"corrupt-chance",
|
||||
"Chance of corrupting a packet (%)",
|
||||
"CHANCE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"size-limit",
|
||||
"Drop packets larger than given size (octets)",
|
||||
"SIZE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"tx-rate-limit",
|
||||
"Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)",
|
||||
"RATE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"rx-rate-limit",
|
||||
"Drop packets after transmit rate exceeds given limit \
|
||||
(packets per interval)",
|
||||
"RATE",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"shaping-interval",
|
||||
"Sets the interval for rate limiting (ms)",
|
||||
"RATE",
|
||||
);
|
||||
}
|
||||
|
||||
pub fn parse_middleware_options<D>(matches: &mut Matches, device: D, loopback: bool)
|
||||
-> FaultInjector<EthernetTracer<PcapWriter<D, Rc<PcapSink>>>>
|
||||
where D: for<'a> Device<'a>
|
||||
pub fn parse_middleware_options<D>(
|
||||
matches: &mut Matches,
|
||||
device: D,
|
||||
loopback: bool,
|
||||
) -> FaultInjector<Tracer<PcapWriter<D, Box<dyn Write>>>>
|
||||
where
|
||||
D: for<'a> Device<'a>,
|
||||
{
|
||||
let drop_chance = matches.opt_str("drop-chance").map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let corrupt_chance = matches.opt_str("corrupt-chance").map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let size_limit = matches.opt_str("size-limit").map(|s| usize::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let tx_rate_limit = matches.opt_str("tx-rate-limit").map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let rx_rate_limit = matches.opt_str("rx-rate-limit").map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let shaping_interval = matches.opt_str("shaping-interval").map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let drop_chance = matches
|
||||
.opt_str("drop-chance")
|
||||
.map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let corrupt_chance = matches
|
||||
.opt_str("corrupt-chance")
|
||||
.map(|s| u8::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let size_limit = matches
|
||||
.opt_str("size-limit")
|
||||
.map(|s| usize::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let tx_rate_limit = matches
|
||||
.opt_str("tx-rate-limit")
|
||||
.map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let rx_rate_limit = matches
|
||||
.opt_str("rx-rate-limit")
|
||||
.map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
let shaping_interval = matches
|
||||
.opt_str("shaping-interval")
|
||||
.map(|s| u64::from_str(&s).unwrap())
|
||||
.unwrap_or(0);
|
||||
|
||||
let pcap_writer: Box<io::Write>;
|
||||
let pcap_writer: Box<dyn io::Write>;
|
||||
if let Some(pcap_filename) = matches.opt_str("pcap") {
|
||||
pcap_writer = Box::new(File::create(pcap_filename).expect("cannot open file"))
|
||||
} else {
|
||||
pcap_writer = Box::new(io::sink())
|
||||
}
|
||||
|
||||
let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos();
|
||||
let seed = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.subsec_nanos();
|
||||
|
||||
let device = PcapWriter::new(device, Rc::new(RefCell::new(pcap_writer)) as Rc<PcapSink>,
|
||||
if loopback { PcapMode::TxOnly } else { PcapMode::Both },
|
||||
PcapLinkType::Ethernet);
|
||||
let device = EthernetTracer::new(device, |_timestamp, _printer| {
|
||||
let device = PcapWriter::new(
|
||||
device,
|
||||
pcap_writer,
|
||||
if loopback {
|
||||
PcapMode::TxOnly
|
||||
} else {
|
||||
PcapMode::Both
|
||||
},
|
||||
);
|
||||
|
||||
let device = Tracer::new(device, |_timestamp, _printer| {
|
||||
#[cfg(feature = "log")]
|
||||
trace!("{}", _printer);
|
||||
});
|
||||
|
|
|
@ -1,424 +0,0 @@
|
|||
use crate::{Result, Error};
|
||||
use crate::wire::{IpVersion, IpProtocol, IpEndpoint, IpAddress,
|
||||
Ipv4Cidr, Ipv4Address, Ipv4Packet, Ipv4Repr,
|
||||
UdpPacket, UdpRepr,
|
||||
DhcpPacket, DhcpRepr, DhcpMessageType};
|
||||
use crate::wire::dhcpv4::field as dhcpv4_field;
|
||||
use crate::socket::{SocketSet, SocketHandle, RawSocket, RawSocketBuffer};
|
||||
use crate::phy::{Device, ChecksumCapabilities};
|
||||
use crate::iface::EthernetInterface as Interface;
|
||||
use crate::time::{Instant, Duration};
|
||||
use super::{UDP_SERVER_PORT, UDP_CLIENT_PORT};
|
||||
|
||||
const DISCOVER_TIMEOUT: u64 = 10;
|
||||
const REQUEST_TIMEOUT: u64 = 1;
|
||||
const REQUEST_RETRIES: u16 = 15;
|
||||
const DEFAULT_RENEW_INTERVAL: u32 = 60;
|
||||
const PARAMETER_REQUEST_LIST: &[u8] = &[
|
||||
dhcpv4_field::OPT_SUBNET_MASK,
|
||||
dhcpv4_field::OPT_ROUTER,
|
||||
dhcpv4_field::OPT_DOMAIN_NAME_SERVER,
|
||||
];
|
||||
|
||||
/// IPv4 configuration data returned by `client.poll()`
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub address: Option<Ipv4Cidr>,
|
||||
pub router: Option<Ipv4Address>,
|
||||
pub dns_servers: [Option<Ipv4Address>; 3],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RequestState {
|
||||
retry: u16,
|
||||
endpoint_ip: Ipv4Address,
|
||||
server_identifier: Ipv4Address,
|
||||
requested_ip: Ipv4Address,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RenewState {
|
||||
endpoint_ip: Ipv4Address,
|
||||
server_identifier: Ipv4Address,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ClientState {
|
||||
/// Discovering the DHCP server
|
||||
Discovering,
|
||||
/// Requesting an address
|
||||
Requesting(RequestState),
|
||||
/// Having an address, refresh it periodically
|
||||
Renew(RenewState),
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
state: ClientState,
|
||||
raw_handle: SocketHandle,
|
||||
/// When to send next request
|
||||
next_egress: Instant,
|
||||
/// When any existing DHCP address will expire.
|
||||
lease_expiration: Option<Instant>,
|
||||
transaction_id: u32,
|
||||
}
|
||||
|
||||
/// DHCP client with a RawSocket.
|
||||
///
|
||||
/// To provide memory for the dynamic IP address, configure your
|
||||
/// `Interface` with one of `ip_addrs` and the `ipv4_gateway` being
|
||||
/// `Ipv4Address::UNSPECIFIED`. You must also assign this `0.0.0.0/0`
|
||||
/// while the client's state is `Discovering`. Hence, the `poll()`
|
||||
/// method returns a corresponding `Config` struct in this case.
|
||||
///
|
||||
/// You must call `dhcp_client.poll()` after `iface.poll()` to send
|
||||
/// and receive DHCP packets.
|
||||
impl Client {
|
||||
/// # Usage
|
||||
/// ```rust
|
||||
/// use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
|
||||
/// use smoltcp::dhcp::Dhcpv4Client;
|
||||
/// use smoltcp::time::Instant;
|
||||
///
|
||||
/// let mut sockets = SocketSet::new(vec![]);
|
||||
/// let dhcp_rx_buffer = RawSocketBuffer::new(
|
||||
/// [RawPacketMetadata::EMPTY; 1],
|
||||
/// vec![0; 600]
|
||||
/// );
|
||||
/// let dhcp_tx_buffer = RawSocketBuffer::new(
|
||||
/// [RawPacketMetadata::EMPTY; 1],
|
||||
/// vec![0; 600]
|
||||
/// );
|
||||
/// let mut dhcp = Dhcpv4Client::new(
|
||||
/// &mut sockets,
|
||||
/// dhcp_rx_buffer, dhcp_tx_buffer,
|
||||
/// Instant::now()
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new<'a>(sockets: &mut SocketSet<'a>, rx_buffer: RawSocketBuffer<'a>, tx_buffer: RawSocketBuffer<'a>, now: Instant) -> Self
|
||||
{
|
||||
let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Udp, rx_buffer, tx_buffer);
|
||||
let raw_handle = sockets.add(raw_socket);
|
||||
|
||||
Client {
|
||||
state: ClientState::Discovering,
|
||||
raw_handle,
|
||||
next_egress: now,
|
||||
transaction_id: 1,
|
||||
lease_expiration: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// When to send next packet
|
||||
///
|
||||
/// Useful for suspending execution after polling.
|
||||
pub fn next_poll(&self, now: Instant) -> Duration {
|
||||
self.next_egress - now
|
||||
}
|
||||
|
||||
/// Process incoming packets on the contained RawSocket, and send
|
||||
/// DHCP requests when timeouts are ready.
|
||||
///
|
||||
/// Applying the obtained network configuration is left to the
|
||||
/// user.
|
||||
///
|
||||
/// A Config can be returned from any valid DHCP reply. The client
|
||||
/// performs no bookkeeping on configuration or their changes.
|
||||
pub fn poll<DeviceT>(&mut self,
|
||||
iface: &mut Interface<DeviceT>, sockets: &mut SocketSet,
|
||||
now: Instant
|
||||
) -> Result<Option<Config>>
|
||||
where
|
||||
DeviceT: for<'d> Device<'d>,
|
||||
{
|
||||
let checksum_caps = iface.device().capabilities().checksum;
|
||||
let mut raw_socket = sockets.get::<RawSocket>(self.raw_handle);
|
||||
|
||||
// Process incoming
|
||||
let config = {
|
||||
match raw_socket.recv()
|
||||
.and_then(|packet| parse_udp(packet, &checksum_caps)) {
|
||||
Ok((IpEndpoint {
|
||||
addr: IpAddress::Ipv4(src_ip),
|
||||
port: UDP_SERVER_PORT,
|
||||
}, IpEndpoint {
|
||||
addr: _,
|
||||
port: UDP_CLIENT_PORT,
|
||||
}, payload)) =>
|
||||
self.ingress(iface, now, payload, &src_ip),
|
||||
Ok(_) =>
|
||||
return Err(Error::Unrecognized),
|
||||
Err(Error::Exhausted) =>
|
||||
None,
|
||||
Err(e) =>
|
||||
return Err(e),
|
||||
}
|
||||
};
|
||||
|
||||
if config.is_some() {
|
||||
// Return a new config immediately so that addresses can
|
||||
// be configured that are required by egress().
|
||||
Ok(config)
|
||||
} else {
|
||||
// Send requests
|
||||
if raw_socket.can_send() && now >= self.next_egress {
|
||||
self.egress(iface, &mut *raw_socket, &checksum_caps, now)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ingress<DeviceT>(&mut self,
|
||||
iface: &mut Interface<DeviceT>, now: Instant,
|
||||
data: &[u8], src_ip: &Ipv4Address
|
||||
) -> Option<Config>
|
||||
where
|
||||
DeviceT: for<'d> Device<'d>,
|
||||
{
|
||||
let dhcp_packet = match DhcpPacket::new_checked(data) {
|
||||
Ok(dhcp_packet) => dhcp_packet,
|
||||
Err(e) => {
|
||||
net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) {
|
||||
Ok(dhcp_repr) => dhcp_repr,
|
||||
Err(e) => {
|
||||
net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let mac = iface.ethernet_addr();
|
||||
if dhcp_repr.client_hardware_address != mac { return None }
|
||||
if dhcp_repr.transaction_id != self.transaction_id { return None }
|
||||
let server_identifier = match dhcp_repr.server_identifier {
|
||||
Some(server_identifier) => server_identifier,
|
||||
None => return None,
|
||||
};
|
||||
net_debug!("DHCP recv {:?} from {} ({})", dhcp_repr.message_type, src_ip, server_identifier);
|
||||
|
||||
// once we receive the ack, we can pass the config to the user
|
||||
let config = if dhcp_repr.message_type == DhcpMessageType::Ack {
|
||||
let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_RENEW_INTERVAL * 2);
|
||||
self.lease_expiration = Some(now + Duration::from_secs(lease_duration.into()));
|
||||
|
||||
// RFC 2131 indicates clients should renew a lease halfway through its expiration.
|
||||
self.next_egress = now + Duration::from_secs((lease_duration / 2).into());
|
||||
|
||||
let address = dhcp_repr.subnet_mask
|
||||
.and_then(|mask| IpAddress::Ipv4(mask).to_prefix_len())
|
||||
.map(|prefix_len| Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len));
|
||||
let router = dhcp_repr.router;
|
||||
let dns_servers = dhcp_repr.dns_servers
|
||||
.unwrap_or([None; 3]);
|
||||
Some(Config { address, router, dns_servers })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match self.state {
|
||||
ClientState::Discovering
|
||||
if dhcp_repr.message_type == DhcpMessageType::Offer =>
|
||||
{
|
||||
self.next_egress = now;
|
||||
let r_state = RequestState {
|
||||
retry: 0,
|
||||
endpoint_ip: *src_ip,
|
||||
server_identifier,
|
||||
requested_ip: dhcp_repr.your_ip // use the offered ip
|
||||
};
|
||||
Some(ClientState::Requesting(r_state))
|
||||
}
|
||||
ClientState::Requesting(ref r_state)
|
||||
if dhcp_repr.message_type == DhcpMessageType::Ack &&
|
||||
server_identifier == r_state.server_identifier =>
|
||||
{
|
||||
let p_state = RenewState {
|
||||
endpoint_ip: *src_ip,
|
||||
server_identifier,
|
||||
};
|
||||
Some(ClientState::Renew(p_state))
|
||||
}
|
||||
_ => None
|
||||
}.map(|new_state| self.state = new_state);
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn egress<DeviceT: for<'d> Device<'d>>(&mut self, iface: &mut Interface<DeviceT>, raw_socket: &mut RawSocket, checksum_caps: &ChecksumCapabilities, now: Instant) -> Result<Option<Config>> {
|
||||
// Reset after maximum amount of retries
|
||||
let retries_exceeded = match self.state {
|
||||
ClientState::Requesting(ref mut r_state) if r_state.retry >= REQUEST_RETRIES => {
|
||||
net_debug!("DHCP request retries exceeded, restarting discovery");
|
||||
true
|
||||
}
|
||||
_ => false
|
||||
};
|
||||
|
||||
let lease_expired = self.lease_expiration.map_or(false, |expiration| now >= expiration);
|
||||
|
||||
if lease_expired || retries_exceeded {
|
||||
self.reset(now);
|
||||
// Return a config now so that user code assigns the
|
||||
// 0.0.0.0/0 address, which will be used sending a DHCP
|
||||
// discovery packet in the next call to egress().
|
||||
return Ok(Some(Config {
|
||||
address: Some(Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)),
|
||||
router: None,
|
||||
dns_servers: [None; 3],
|
||||
}));
|
||||
}
|
||||
|
||||
// Prepare sending next packet
|
||||
self.transaction_id += 1;
|
||||
let mac = iface.ethernet_addr();
|
||||
|
||||
let mut dhcp_repr = DhcpRepr {
|
||||
message_type: DhcpMessageType::Discover,
|
||||
transaction_id: self.transaction_id,
|
||||
client_hardware_address: mac,
|
||||
client_ip: Ipv4Address::UNSPECIFIED,
|
||||
your_ip: Ipv4Address::UNSPECIFIED,
|
||||
server_ip: Ipv4Address::UNSPECIFIED,
|
||||
router: None,
|
||||
subnet_mask: None,
|
||||
relay_agent_ip: Ipv4Address::UNSPECIFIED,
|
||||
broadcast: true,
|
||||
requested_ip: None,
|
||||
client_identifier: Some(mac),
|
||||
server_identifier: None,
|
||||
parameter_request_list: None,
|
||||
max_size: Some(raw_socket.payload_recv_capacity() as u16),
|
||||
lease_duration: None,
|
||||
dns_servers: None,
|
||||
};
|
||||
let mut send_packet = |iface, endpoint, dhcp_repr| {
|
||||
send_packet(iface, raw_socket, &endpoint, &dhcp_repr, checksum_caps)
|
||||
.map(|()| None)
|
||||
};
|
||||
|
||||
|
||||
match self.state {
|
||||
ClientState::Discovering => {
|
||||
self.next_egress = now + Duration::from_secs(DISCOVER_TIMEOUT);
|
||||
let endpoint = IpEndpoint {
|
||||
addr: Ipv4Address::BROADCAST.into(),
|
||||
port: UDP_SERVER_PORT,
|
||||
};
|
||||
net_trace!("DHCP send discover to {}: {:?}", endpoint, dhcp_repr);
|
||||
send_packet(iface, endpoint, dhcp_repr)
|
||||
}
|
||||
ClientState::Requesting(ref mut r_state) => {
|
||||
r_state.retry += 1;
|
||||
self.next_egress = now + Duration::from_secs(REQUEST_TIMEOUT);
|
||||
|
||||
let endpoint = IpEndpoint {
|
||||
addr: Ipv4Address::BROADCAST.into(),
|
||||
port: UDP_SERVER_PORT,
|
||||
};
|
||||
dhcp_repr.message_type = DhcpMessageType::Request;
|
||||
dhcp_repr.broadcast = false;
|
||||
dhcp_repr.requested_ip = Some(r_state.requested_ip);
|
||||
dhcp_repr.server_identifier = Some(r_state.server_identifier);
|
||||
dhcp_repr.parameter_request_list = Some(PARAMETER_REQUEST_LIST);
|
||||
net_trace!("DHCP send request to {} = {:?}", endpoint, dhcp_repr);
|
||||
send_packet(iface, endpoint, dhcp_repr)
|
||||
}
|
||||
ClientState::Renew(ref mut p_state) => {
|
||||
self.next_egress = now + Duration::from_secs(DEFAULT_RENEW_INTERVAL.into());
|
||||
|
||||
let endpoint = IpEndpoint {
|
||||
addr: p_state.endpoint_ip.into(),
|
||||
port: UDP_SERVER_PORT,
|
||||
};
|
||||
let client_ip = iface.ipv4_addr().unwrap_or(Ipv4Address::UNSPECIFIED);
|
||||
dhcp_repr.message_type = DhcpMessageType::Request;
|
||||
dhcp_repr.client_ip = client_ip;
|
||||
dhcp_repr.broadcast = false;
|
||||
net_trace!("DHCP send renew to {}: {:?}", endpoint, dhcp_repr);
|
||||
send_packet(iface, endpoint, dhcp_repr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset state and restart discovery phase.
|
||||
///
|
||||
/// Use this to speed up acquisition of an address in a new
|
||||
/// network if a link was down and it is now back up.
|
||||
///
|
||||
/// You *must* configure a `0.0.0.0` address on your interface
|
||||
/// before the next call to `poll()`!
|
||||
pub fn reset(&mut self, now: Instant) {
|
||||
net_trace!("DHCP reset");
|
||||
self.state = ClientState::Discovering;
|
||||
self.next_egress = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn send_packet<DeviceT: for<'d> Device<'d>>(iface: &mut Interface<DeviceT>, raw_socket: &mut RawSocket, endpoint: &IpEndpoint, dhcp_repr: &DhcpRepr, checksum_caps: &ChecksumCapabilities) -> Result<()> {
|
||||
let mut dhcp_payload_buf = [0; 320];
|
||||
assert!(dhcp_repr.buffer_len() <= dhcp_payload_buf.len());
|
||||
let dhcp_payload = &mut dhcp_payload_buf[0..dhcp_repr.buffer_len()];
|
||||
{
|
||||
let mut dhcp_packet = DhcpPacket::new_checked(&mut dhcp_payload[..])?;
|
||||
dhcp_repr.emit(&mut dhcp_packet)?;
|
||||
}
|
||||
|
||||
let udp_repr = UdpRepr {
|
||||
src_port: UDP_CLIENT_PORT,
|
||||
dst_port: endpoint.port,
|
||||
payload: dhcp_payload,
|
||||
};
|
||||
|
||||
let src_addr = iface.ipv4_addr().unwrap();
|
||||
let dst_addr = match endpoint.addr {
|
||||
IpAddress::Ipv4(addr) => addr,
|
||||
_ => return Err(Error::Illegal),
|
||||
};
|
||||
let ipv4_repr = Ipv4Repr {
|
||||
src_addr,
|
||||
dst_addr,
|
||||
protocol: IpProtocol::Udp,
|
||||
payload_len: udp_repr.buffer_len(),
|
||||
hop_limit: 64,
|
||||
};
|
||||
|
||||
let mut packet = raw_socket.send(
|
||||
ipv4_repr.buffer_len() + udp_repr.buffer_len()
|
||||
)?;
|
||||
{
|
||||
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut packet);
|
||||
ipv4_repr.emit(&mut ipv4_packet, &checksum_caps);
|
||||
}
|
||||
{
|
||||
let mut udp_packet = UdpPacket::new_unchecked(
|
||||
&mut packet[ipv4_repr.buffer_len()..]
|
||||
);
|
||||
udp_repr.emit(&mut udp_packet,
|
||||
&src_addr.into(), &dst_addr.into(),
|
||||
checksum_caps);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_udp<'a>(data: &'a [u8], checksum_caps: &ChecksumCapabilities) -> Result<(IpEndpoint, IpEndpoint, &'a [u8])> {
|
||||
let ipv4_packet = Ipv4Packet::new_checked(data)?;
|
||||
let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &checksum_caps)?;
|
||||
let udp_packet = UdpPacket::new_checked(ipv4_packet.payload())?;
|
||||
let udp_repr = UdpRepr::parse(
|
||||
&udp_packet,
|
||||
&ipv4_repr.src_addr.into(), &ipv4_repr.dst_addr.into(),
|
||||
checksum_caps
|
||||
)?;
|
||||
let src = IpEndpoint {
|
||||
addr: ipv4_repr.src_addr.into(),
|
||||
port: udp_repr.src_port,
|
||||
};
|
||||
let dst = IpEndpoint {
|
||||
addr: ipv4_repr.dst_addr.into(),
|
||||
port: udp_repr.dst_port,
|
||||
};
|
||||
let data = udp_repr.payload;
|
||||
Ok((src, dst, data))
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
pub const UDP_SERVER_PORT: u16 = 67;
|
||||
pub const UDP_CLIENT_PORT: u16 = 68;
|
||||
|
||||
mod clientv4;
|
||||
pub use self::clientv4::{Client as Dhcpv4Client, Config as Dhcpv4Config};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -4,19 +4,27 @@ The `iface` module deals with the *network interfaces*. It filters incoming fram
|
|||
provides lookup and caching of hardware addresses, and handles management packets.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(any(
|
||||
feature = "medium-ethernet",
|
||||
feature = "medium-ip",
|
||||
feature = "medium-ieee802154"
|
||||
))]
|
||||
mod interface;
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
mod neighbor;
|
||||
mod route;
|
||||
#[cfg(feature = "ethernet")]
|
||||
mod ethernet;
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
pub use self::neighbor::Neighbor as Neighbor;
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
pub(crate) use self::neighbor::Answer as NeighborAnswer;
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
pub use self::neighbor::Cache as NeighborCache;
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
pub use self::neighbor::Neighbor;
|
||||
pub use self::route::{Route, Routes};
|
||||
#[cfg(feature = "ethernet")]
|
||||
pub use self::ethernet::{Interface as EthernetInterface,
|
||||
InterfaceBuilder as EthernetInterfaceBuilder};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "medium-ethernet",
|
||||
feature = "medium-ip",
|
||||
feature = "medium-ieee802154"
|
||||
))]
|
||||
pub use self::interface::{Interface, InterfaceBuilder};
|
||||
|
|
|
@ -3,29 +3,31 @@
|
|||
|
||||
use managed::ManagedMap;
|
||||
|
||||
use crate::wire::{EthernetAddress, IpAddress};
|
||||
use crate::time::{Duration, Instant};
|
||||
use crate::wire::{HardwareAddress, IpAddress};
|
||||
|
||||
/// A cached neighbor.
|
||||
///
|
||||
/// A neighbor mapping translates from a protocol address to a hardware address,
|
||||
/// and contains the timestamp past which the mapping should be discarded.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Neighbor {
|
||||
hardware_addr: EthernetAddress,
|
||||
expires_at: Instant,
|
||||
hardware_addr: HardwareAddress,
|
||||
expires_at: Instant,
|
||||
}
|
||||
|
||||
/// An answer to a neighbor cache lookup.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub(crate) enum Answer {
|
||||
/// The neighbor address is in the cache and not expired.
|
||||
Found(EthernetAddress),
|
||||
Found(HardwareAddress),
|
||||
/// The neighbor address is not in the cache, or has expired.
|
||||
NotFound,
|
||||
/// The neighbor address is not in the cache, or has expired,
|
||||
/// and a lookup has been made recently.
|
||||
RateLimited
|
||||
RateLimited,
|
||||
}
|
||||
|
||||
impl Answer {
|
||||
|
@ -59,20 +61,21 @@ impl Answer {
|
|||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Cache<'a> {
|
||||
storage: ManagedMap<'a, IpAddress, Neighbor>,
|
||||
storage: ManagedMap<'a, IpAddress, Neighbor>,
|
||||
silent_until: Instant,
|
||||
gc_threshold: usize
|
||||
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
gc_threshold: usize,
|
||||
}
|
||||
|
||||
impl<'a> Cache<'a> {
|
||||
/// Minimum delay between discovery requests, in milliseconds.
|
||||
pub(crate) const SILENT_TIME: Duration = Duration { millis: 1_000 };
|
||||
pub(crate) const SILENT_TIME: Duration = Duration::from_millis(1_000);
|
||||
|
||||
/// Neighbor entry lifetime, in milliseconds.
|
||||
pub(crate) const ENTRY_LIFETIME: Duration = Duration { millis: 60_000 };
|
||||
pub(crate) const ENTRY_LIFETIME: Duration = Duration::from_millis(60_000);
|
||||
|
||||
/// Default number of entries in the cache before GC kicks in
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
pub(crate) const GC_THRESHOLD: usize = 1024;
|
||||
|
||||
/// Create a cache. The backing storage is cleared upon creation.
|
||||
|
@ -80,21 +83,41 @@ impl<'a> Cache<'a> {
|
|||
/// # Panics
|
||||
/// This function panics if `storage.len() == 0`.
|
||||
pub fn new<T>(storage: T) -> Cache<'a>
|
||||
where T: Into<ManagedMap<'a, IpAddress, Neighbor>> {
|
||||
|
||||
Cache::new_with_limit(storage, Cache::GC_THRESHOLD)
|
||||
}
|
||||
|
||||
pub fn new_with_limit<T>(storage: T, gc_threshold: usize) -> Cache<'a>
|
||||
where T: Into<ManagedMap<'a, IpAddress, Neighbor>> {
|
||||
where
|
||||
T: Into<ManagedMap<'a, IpAddress, Neighbor>>,
|
||||
{
|
||||
let mut storage = storage.into();
|
||||
storage.clear();
|
||||
|
||||
Cache { storage, gc_threshold, silent_until: Instant::from_millis(0) }
|
||||
Cache {
|
||||
storage,
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
gc_threshold: Self::GC_THRESHOLD,
|
||||
silent_until: Instant::from_millis(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill(&mut self, protocol_addr: IpAddress, hardware_addr: EthernetAddress,
|
||||
timestamp: Instant) {
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
pub fn new_with_limit<T>(storage: T, gc_threshold: usize) -> Cache<'a>
|
||||
where
|
||||
T: Into<ManagedMap<'a, IpAddress, Neighbor>>,
|
||||
{
|
||||
let mut storage = storage.into();
|
||||
storage.clear();
|
||||
|
||||
Cache {
|
||||
storage,
|
||||
gc_threshold,
|
||||
silent_until: Instant::from_millis(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill(
|
||||
&mut self,
|
||||
protocol_addr: IpAddress,
|
||||
hardware_addr: HardwareAddress,
|
||||
timestamp: Instant,
|
||||
) {
|
||||
debug_assert!(protocol_addr.is_unicast());
|
||||
debug_assert!(hardware_addr.is_unicast());
|
||||
|
||||
|
@ -102,11 +125,12 @@ impl<'a> Cache<'a> {
|
|||
let current_storage_size = self.storage.len();
|
||||
|
||||
match self.storage {
|
||||
ManagedMap::Borrowed(_) => (),
|
||||
ManagedMap::Borrowed(_) => (),
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
ManagedMap::Owned(ref mut map) => {
|
||||
if current_storage_size >= self.gc_threshold {
|
||||
let new_btree_map = map.iter_mut()
|
||||
let new_btree_map = map
|
||||
.iter_mut()
|
||||
.map(|(key, value)| (*key, *value))
|
||||
.filter(|(_, v)| timestamp < v.expires_at)
|
||||
.collect();
|
||||
|
@ -116,13 +140,18 @@ impl<'a> Cache<'a> {
|
|||
}
|
||||
};
|
||||
let neighbor = Neighbor {
|
||||
expires_at: timestamp + Self::ENTRY_LIFETIME, hardware_addr
|
||||
expires_at: timestamp + Self::ENTRY_LIFETIME,
|
||||
hardware_addr,
|
||||
};
|
||||
match self.storage.insert(protocol_addr, neighbor) {
|
||||
Ok(Some(old_neighbor)) => {
|
||||
if old_neighbor.hardware_addr != hardware_addr {
|
||||
net_trace!("replaced {} => {} (was {})",
|
||||
protocol_addr, hardware_addr, old_neighbor.hardware_addr);
|
||||
net_trace!(
|
||||
"replaced {} => {} (was {})",
|
||||
protocol_addr,
|
||||
hardware_addr,
|
||||
old_neighbor.hardware_addr
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
|
@ -145,34 +174,37 @@ impl<'a> Cache<'a> {
|
|||
}
|
||||
// Owned maps can extend themselves.
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
ManagedMap::Owned(_) => unreachable!()
|
||||
ManagedMap::Owned(_) => unreachable!(),
|
||||
};
|
||||
|
||||
let _old_neighbor =
|
||||
self.storage.remove(&old_protocol_addr).unwrap();
|
||||
let _old_neighbor = self.storage.remove(&old_protocol_addr).unwrap();
|
||||
match self.storage.insert(protocol_addr, neighbor) {
|
||||
Ok(None) => {
|
||||
net_trace!("filled {} => {} (evicted {} => {})",
|
||||
protocol_addr, hardware_addr,
|
||||
old_protocol_addr, _old_neighbor.hardware_addr);
|
||||
net_trace!(
|
||||
"filled {} => {} (evicted {} => {})",
|
||||
protocol_addr,
|
||||
hardware_addr,
|
||||
old_protocol_addr,
|
||||
_old_neighbor.hardware_addr
|
||||
);
|
||||
}
|
||||
// We've covered everything else above.
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, protocol_addr: &IpAddress, timestamp: Instant) -> Answer {
|
||||
if protocol_addr.is_broadcast() {
|
||||
return Answer::Found(EthernetAddress::BROADCAST);
|
||||
}
|
||||
assert!(protocol_addr.is_unicast());
|
||||
|
||||
if let Some(&Neighbor { expires_at, hardware_addr }) =
|
||||
self.storage.get(protocol_addr) {
|
||||
if let Some(&Neighbor {
|
||||
expires_at,
|
||||
hardware_addr,
|
||||
}) = self.storage.get(protocol_addr)
|
||||
{
|
||||
if timestamp < expires_at {
|
||||
return Answer::Found(hardware_addr)
|
||||
return Answer::Found(hardware_addr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,36 +218,56 @@ impl<'a> Cache<'a> {
|
|||
pub(crate) fn limit_rate(&mut self, timestamp: Instant) {
|
||||
self.silent_until = timestamp + Self::SILENT_TIME;
|
||||
}
|
||||
|
||||
pub(crate) fn flush(&mut self) {
|
||||
self.storage.clear()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::collections::BTreeMap;
|
||||
use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::wire::EthernetAddress;
|
||||
|
||||
const HADDR_A: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 1]);
|
||||
const HADDR_B: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 2]);
|
||||
const HADDR_C: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 3]);
|
||||
const HADDR_D: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 4]);
|
||||
const HADDR_A: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 1]));
|
||||
const HADDR_B: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 2]));
|
||||
const HADDR_C: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 3]));
|
||||
const HADDR_D: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 4]));
|
||||
|
||||
#[test]
|
||||
fn test_fill() {
|
||||
let mut cache_storage = [Default::default(); 3];
|
||||
let mut cache = Cache::new(&mut cache_storage[..]);
|
||||
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)).found(), false);
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0)).found(), false);
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
|
||||
.found());
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
|
||||
.found());
|
||||
|
||||
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Answer::Found(HADDR_A));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0)).found(), false);
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2).found(),
|
||||
false);
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
|
||||
Answer::Found(HADDR_A)
|
||||
);
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
|
||||
.found());
|
||||
assert!(!cache
|
||||
.lookup(
|
||||
&MOCK_IP_ADDR_1,
|
||||
Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
|
||||
)
|
||||
.found(),);
|
||||
|
||||
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0)).found(), false);
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
|
||||
.found());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -224,9 +276,16 @@ mod test {
|
|||
let mut cache = Cache::new(&mut cache_storage[..]);
|
||||
|
||||
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Answer::Found(HADDR_A));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2).found(),
|
||||
false);
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
|
||||
Answer::Found(HADDR_A)
|
||||
);
|
||||
assert!(!cache
|
||||
.lookup(
|
||||
&MOCK_IP_ADDR_1,
|
||||
Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
|
||||
)
|
||||
.found(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -235,9 +294,15 @@ mod test {
|
|||
let mut cache = Cache::new(&mut cache_storage[..]);
|
||||
|
||||
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Answer::Found(HADDR_A));
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
|
||||
Answer::Found(HADDR_A)
|
||||
);
|
||||
cache.fill(MOCK_IP_ADDR_1, HADDR_B, Instant::from_millis(0));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Answer::Found(HADDR_B));
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
|
||||
Answer::Found(HADDR_B)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -247,10 +312,20 @@ mod test {
|
|||
cache.fill(MOCK_IP_ADDR_2, HADDR_B, Instant::from_millis(50));
|
||||
// Adding third item after the expiration of the previous
|
||||
// two should garbage collect
|
||||
cache.fill(MOCK_IP_ADDR_3, HADDR_C, Instant::from_millis(50) + Cache::ENTRY_LIFETIME * 2);
|
||||
cache.fill(
|
||||
MOCK_IP_ADDR_3,
|
||||
HADDR_C,
|
||||
Instant::from_millis(50) + Cache::ENTRY_LIFETIME * 2,
|
||||
);
|
||||
|
||||
assert_eq!(cache.storage.len(), 1);
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_3, Instant::from_millis(50) + Cache::ENTRY_LIFETIME * 2), Answer::Found(HADDR_C));
|
||||
assert_eq!(
|
||||
cache.lookup(
|
||||
&MOCK_IP_ADDR_3,
|
||||
Instant::from_millis(50) + Cache::ENTRY_LIFETIME * 2
|
||||
),
|
||||
Answer::Found(HADDR_C)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -261,12 +336,22 @@ mod test {
|
|||
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(100));
|
||||
cache.fill(MOCK_IP_ADDR_2, HADDR_B, Instant::from_millis(50));
|
||||
cache.fill(MOCK_IP_ADDR_3, HADDR_C, Instant::from_millis(200));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000)), Answer::Found(HADDR_B));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000)).found(), false);
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000)),
|
||||
Answer::Found(HADDR_B)
|
||||
);
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000))
|
||||
.found());
|
||||
|
||||
cache.fill(MOCK_IP_ADDR_4, HADDR_D, Instant::from_millis(300));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000)).found(), false);
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000)), Answer::Found(HADDR_D));
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000))
|
||||
.found());
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000)),
|
||||
Answer::Found(HADDR_D)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -274,10 +359,42 @@ mod test {
|
|||
let mut cache_storage = [Default::default(); 3];
|
||||
let mut cache = Cache::new(&mut cache_storage[..]);
|
||||
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Answer::NotFound);
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
|
||||
Answer::NotFound
|
||||
);
|
||||
|
||||
cache.limit_rate(Instant::from_millis(0));
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(100)), Answer::RateLimited);
|
||||
assert_eq!(cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(2000)), Answer::NotFound);
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(100)),
|
||||
Answer::RateLimited
|
||||
);
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(2000)),
|
||||
Answer::NotFound
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flush() {
|
||||
let mut cache_storage = [Default::default(); 3];
|
||||
let mut cache = Cache::new(&mut cache_storage[..]);
|
||||
|
||||
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
|
||||
assert_eq!(
|
||||
cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
|
||||
Answer::Found(HADDR_A)
|
||||
);
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
|
||||
.found());
|
||||
|
||||
cache.flush();
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
|
||||
.found());
|
||||
assert!(!cache
|
||||
.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
|
||||
.found());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use managed::ManagedMap;
|
||||
use crate::time::Instant;
|
||||
use core::ops::Bound;
|
||||
use managed::ManagedMap;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::wire::{IpCidr, IpAddress};
|
||||
use crate::wire::{IpAddress, IpCidr};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::{Ipv4Address, Ipv4Cidr};
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
use crate::wire::{Ipv6Address, Ipv6Cidr};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// A prefix of addresses that should be routed via a router
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Route {
|
||||
pub via_router: IpAddress,
|
||||
/// `None` means "forever".
|
||||
|
@ -69,7 +70,9 @@ impl<'a> Routes<'a> {
|
|||
/// Creates a routing tables. The backing storage is **not** cleared
|
||||
/// upon creation.
|
||||
pub fn new<T>(storage: T) -> Routes<'a>
|
||||
where T: Into<ManagedMap<'a, IpCidr, Route>> {
|
||||
where
|
||||
T: Into<ManagedMap<'a, IpCidr, Route>>,
|
||||
{
|
||||
let storage = storage.into();
|
||||
Routes { storage }
|
||||
}
|
||||
|
@ -88,7 +91,7 @@ impl<'a> Routes<'a> {
|
|||
let route = Route::new_ipv4_gateway(gateway);
|
||||
match self.storage.insert(cidr, route) {
|
||||
Ok(route) => Ok(route),
|
||||
Err((_cidr, _route)) => Err(Error::Exhausted)
|
||||
Err((_cidr, _route)) => Err(Error::Exhausted),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,12 +104,29 @@ impl<'a> Routes<'a> {
|
|||
let route = Route::new_ipv6_gateway(gateway);
|
||||
match self.storage.insert(cidr, route) {
|
||||
Ok(route) => Ok(route),
|
||||
Err((_cidr, _route)) => Err(Error::Exhausted)
|
||||
Err((_cidr, _route)) => Err(Error::Exhausted),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) ->
|
||||
Option<IpAddress> {
|
||||
/// Remove the default ipv4 gateway
|
||||
///
|
||||
/// On success, returns the previous default route, if any.
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub fn remove_default_ipv4_route(&mut self) -> Option<Route> {
|
||||
let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0);
|
||||
self.storage.remove(&cidr)
|
||||
}
|
||||
|
||||
/// Remove the default ipv6 gateway
|
||||
///
|
||||
/// On success, returns the previous default route, if any.
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub fn remove_default_ipv6_route(&mut self) -> Option<Route> {
|
||||
let cidr = IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), 0);
|
||||
self.storage.remove(&cidr)
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) -> Option<IpAddress> {
|
||||
assert!(addr.is_unicast());
|
||||
|
||||
let cidr = match addr {
|
||||
|
@ -114,10 +134,14 @@ impl<'a> Routes<'a> {
|
|||
IpAddress::Ipv4(addr) => IpCidr::Ipv4(Ipv4Cidr::new(*addr, 32)),
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
IpAddress::Ipv6(addr) => IpCidr::Ipv6(Ipv6Cidr::new(*addr, 128)),
|
||||
_ => unimplemented!()
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
for (prefix, route) in self.storage.range((Bound::Unbounded::<IpCidr>, Bound::Included(cidr))).rev() {
|
||||
for (prefix, route) in self
|
||||
.storage
|
||||
.range((Bound::Unbounded::<IpCidr>, Bound::Included(cidr)))
|
||||
.rev()
|
||||
{
|
||||
// TODO: do something with route.preferred_until
|
||||
if let Some(expires_at) = route.expires_at {
|
||||
if timestamp > expires_at {
|
||||
|
@ -140,24 +164,28 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv6")]
|
||||
mod mock {
|
||||
use super::super::*;
|
||||
pub const ADDR_1A: Ipv6Address = Ipv6Address(
|
||||
[0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
pub const ADDR_1B: Ipv6Address = Ipv6Address(
|
||||
[0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 13]);
|
||||
pub const ADDR_1C: Ipv6Address = Ipv6Address(
|
||||
[0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 42]);
|
||||
pub const ADDR_1A: Ipv6Address =
|
||||
Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
pub const ADDR_1B: Ipv6Address =
|
||||
Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 13]);
|
||||
pub const ADDR_1C: Ipv6Address =
|
||||
Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 42]);
|
||||
pub fn cidr_1() -> Ipv6Cidr {
|
||||
Ipv6Cidr::new(Ipv6Address(
|
||||
[0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]), 64)
|
||||
Ipv6Cidr::new(
|
||||
Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
64,
|
||||
)
|
||||
}
|
||||
|
||||
pub const ADDR_2A: Ipv6Address = Ipv6Address(
|
||||
[0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
pub const ADDR_2B: Ipv6Address = Ipv6Address(
|
||||
[0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 21]);
|
||||
pub const ADDR_2A: Ipv6Address =
|
||||
Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
pub const ADDR_2B: Ipv6Address =
|
||||
Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 21]);
|
||||
pub fn cidr_2() -> Ipv6Cidr {
|
||||
Ipv6Cidr::new(Ipv6Address(
|
||||
[0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 0]), 64)
|
||||
Ipv6Cidr::new(
|
||||
Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
64,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,25 +213,56 @@ mod test {
|
|||
let mut routes_storage = [None, None, None];
|
||||
let mut routes = Routes::new(&mut routes_storage[..]);
|
||||
|
||||
assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), None);
|
||||
assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), None);
|
||||
assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), None);
|
||||
assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), None);
|
||||
assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), None);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
|
||||
None
|
||||
);
|
||||
|
||||
let route = Route {
|
||||
via_router: ADDR_1A.into(),
|
||||
preferred_until: None, expires_at: None,
|
||||
preferred_until: None,
|
||||
expires_at: None,
|
||||
};
|
||||
routes.update(|storage| {
|
||||
storage.insert(cidr_1().into(), route).unwrap();
|
||||
});
|
||||
|
||||
assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), None);
|
||||
assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), None);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
|
||||
None
|
||||
);
|
||||
|
||||
let route2 = Route {
|
||||
via_router: ADDR_2A.into(),
|
||||
|
@ -214,16 +273,46 @@ mod test {
|
|||
storage.insert(cidr_2().into(), route2).unwrap();
|
||||
});
|
||||
|
||||
assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), Some(ADDR_2A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), Some(ADDR_2A.into()));
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_2A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)),
|
||||
Some(ADDR_2A.into())
|
||||
);
|
||||
|
||||
assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)), Some(ADDR_1A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)), Some(ADDR_2A.into()));
|
||||
assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)), Some(ADDR_2A.into()));
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)),
|
||||
Some(ADDR_1A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)),
|
||||
Some(ADDR_2A.into())
|
||||
);
|
||||
assert_eq!(
|
||||
routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)),
|
||||
Some(ADDR_2A.into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
99
src/lib.rs
99
src/lib.rs
|
@ -1,6 +1,12 @@
|
|||
#![cfg_attr(not(any(test, feature = "std")), no_std)]
|
||||
#![deny(unsafe_code)]
|
||||
#![cfg_attr(all(any(feature = "proto-ipv4", feature = "proto-ipv6"), feature = "ethernet"), deny(unused))]
|
||||
#![cfg_attr(
|
||||
all(
|
||||
any(feature = "proto-ipv4", feature = "proto-ipv6"),
|
||||
feature = "medium-ethernet"
|
||||
),
|
||||
deny(unused)
|
||||
)]
|
||||
|
||||
//! The _smoltcp_ library is built in a layered structure, with the layers corresponding
|
||||
//! to the levels of API abstraction. Only the highest layers would be used by a typical
|
||||
|
@ -64,18 +70,13 @@
|
|||
//! feature ever defined, to ensure that, when the representation layer is unable to make sense
|
||||
//! of a packet, it is still logged correctly and in full.
|
||||
//!
|
||||
//! ## Packet and representation layer support
|
||||
//! | Protocol | Packet | Representation |
|
||||
//! |----------|--------|----------------|
|
||||
//! | Ethernet | Yes | Yes |
|
||||
//! | ARP | Yes | Yes |
|
||||
//! | IPv4 | Yes | Yes |
|
||||
//! | ICMPv4 | Yes | Yes |
|
||||
//! | IGMPv1/2 | Yes | Yes |
|
||||
//! | IPv6 | Yes | Yes |
|
||||
//! | ICMPv6 | Yes | Yes |
|
||||
//! | TCP | Yes | Yes |
|
||||
//! | UDP | Yes | Yes |
|
||||
//! # Minimum Supported Rust Version (MSRV)
|
||||
//!
|
||||
//! This crate is guaranteed to compile on stable Rust 1.46 and up with any valid set of features.
|
||||
//! It *might* compile on older versions but that may change in any new patch release.
|
||||
//!
|
||||
//! The exception is when using the `defmt` feature, in which case `defmt`'s MSRV applies, which
|
||||
//! is higher than 1.46.
|
||||
//!
|
||||
//! [wire]: wire/index.html
|
||||
//! [osi]: https://en.wikipedia.org/wiki/OSI_model
|
||||
|
@ -93,28 +94,62 @@ compile_error!("at least one socket needs to be enabled"); */
|
|||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::unit_arg)]
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "proto-ipv4",
|
||||
feature = "proto-ipv6",
|
||||
feature = "proto-sixlowpan"
|
||||
)))]
|
||||
compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6, proto-sixlowpan");
|
||||
|
||||
#[cfg(all(
|
||||
feature = "socket",
|
||||
not(any(
|
||||
feature = "socket-raw",
|
||||
feature = "socket-udp",
|
||||
feature = "socket-tcp",
|
||||
feature = "socket-icmp",
|
||||
))
|
||||
))]
|
||||
compile_error!("If you enable the socket feature, you must enable at least one of the following features: socket-raw, socket-udp, socket-tcp, socket-icmp");
|
||||
|
||||
#[cfg(all(
|
||||
feature = "socket",
|
||||
not(any(
|
||||
feature = "medium-ethernet",
|
||||
feature = "medium-ip",
|
||||
feature = "medium-ieee802154",
|
||||
))
|
||||
))]
|
||||
compile_error!("If you enable the socket feature, you must enable at least one of the following features: medium-ip, medium-ethernet, medium-ieee802154");
|
||||
|
||||
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||
compile_error!("You must enable at most one of the following features: defmt, log");
|
||||
|
||||
use core::fmt;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod parsers;
|
||||
|
||||
pub mod storage;
|
||||
pub mod phy;
|
||||
pub mod wire;
|
||||
mod rand;
|
||||
#[cfg(feature = "rand-custom-impl")]
|
||||
pub use crate::rand::Rand;
|
||||
|
||||
pub mod iface;
|
||||
pub mod phy;
|
||||
#[cfg(feature = "socket")]
|
||||
pub mod socket;
|
||||
pub mod storage;
|
||||
pub mod time;
|
||||
#[cfg(feature = "proto-dhcpv4")]
|
||||
pub mod dhcp;
|
||||
pub mod wire;
|
||||
|
||||
/// The error type for the networking stack.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
/// An operation cannot proceed because a buffer is empty or full.
|
||||
Exhausted,
|
||||
|
@ -147,24 +182,32 @@ pub enum Error {
|
|||
/// An incoming packet was recognized but contradicted internal state.
|
||||
/// E.g. a TCP packet addressed to a socket that doesn't exist.
|
||||
Dropped,
|
||||
|
||||
/// An incoming packet was recognized but some parts are not supported by smoltcp.
|
||||
/// E.g. some bit configuration in a packet header is not supported, but is defined in an RFC.
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
/// The result type for the networking stack.
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Exhausted => write!(f, "buffer space exhausted"),
|
||||
Error::Illegal => write!(f, "illegal operation"),
|
||||
Error::Exhausted => write!(f, "buffer space exhausted"),
|
||||
Error::Illegal => write!(f, "illegal operation"),
|
||||
Error::Unaddressable => write!(f, "unaddressable destination"),
|
||||
Error::Finished => write!(f, "operation finished"),
|
||||
Error::Truncated => write!(f, "truncated packet"),
|
||||
Error::Checksum => write!(f, "checksum error"),
|
||||
Error::Unrecognized => write!(f, "unrecognized packet"),
|
||||
Error::Fragmented => write!(f, "fragmented packet"),
|
||||
Error::Malformed => write!(f, "malformed packet"),
|
||||
Error::Dropped => write!(f, "dropped by socket"),
|
||||
Error::Finished => write!(f, "operation finished"),
|
||||
Error::Truncated => write!(f, "truncated packet"),
|
||||
Error::Checksum => write!(f, "checksum error"),
|
||||
Error::Unrecognized => write!(f, "unrecognized packet"),
|
||||
Error::Fragmented => write!(f, "fragmented packet"),
|
||||
Error::Malformed => write!(f, "malformed packet"),
|
||||
Error::Dropped => write!(f, "dropped by socket"),
|
||||
Error::NotSupported => write!(f, "not supported by smoltcp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "log")]
|
||||
macro_rules! net_log {
|
||||
(trace, $($arg:expr),*) => { log::trace!($($arg),*); };
|
||||
(debug, $($arg:expr),*) => { log::debug!($($arg),*); };
|
||||
(trace, $($arg:expr),*) => { log::trace!($($arg),*) };
|
||||
(debug, $($arg:expr),*) => { log::debug!($($arg),*) };
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "log"))]
|
||||
#[macro_use]
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "log")]
|
||||
macro_rules! net_log {
|
||||
($level:ident, $($arg:expr),*) => { $( let _ = $arg; )* }
|
||||
(trace, $($arg:expr),*) => { println!($($arg),*) };
|
||||
(debug, $($arg:expr),*) => { println!($($arg),*) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
macro_rules! net_log {
|
||||
(trace, $($arg:expr),*) => { defmt::trace!($($arg),*) };
|
||||
(debug, $($arg:expr),*) => { defmt::debug!($($arg),*) };
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "log", feature = "defmt")))]
|
||||
macro_rules! net_log {
|
||||
($level:ident, $($arg:expr),*) => {{ $( let _ = $arg; )* }}
|
||||
}
|
||||
|
||||
macro_rules! net_trace {
|
||||
|
@ -25,11 +37,12 @@ macro_rules! enum_with_unknown {
|
|||
pub enum $name:ident($ty:ty) {
|
||||
$(
|
||||
$( #[$variant_attr:meta] )*
|
||||
$variant:ident = $value:expr $(,)*
|
||||
),+
|
||||
$variant:ident = $value:expr
|
||||
),+ $(,)?
|
||||
}
|
||||
) => {
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
$( #[$enum_attr] )*
|
||||
pub enum $name {
|
||||
$(
|
||||
|
|
356
src/parsers.rs
356
src/parsers.rs
|
@ -1,9 +1,12 @@
|
|||
#![cfg_attr(not(all(feature = "proto-ipv6", feature = "proto-ipv4")), allow(dead_code))]
|
||||
#![cfg_attr(
|
||||
not(all(feature = "proto-ipv6", feature = "proto-ipv4")),
|
||||
allow(dead_code)
|
||||
)]
|
||||
|
||||
use core::str::FromStr;
|
||||
use core::result;
|
||||
use core::str::FromStr;
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
use crate::wire::EthernetAddress;
|
||||
use crate::wire::{IpAddress, IpCidr, IpEndpoint};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
|
@ -15,14 +18,14 @@ type Result<T> = result::Result<T, ()>;
|
|||
|
||||
struct Parser<'a> {
|
||||
data: &'a [u8],
|
||||
pos: usize
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
fn new(data: &'a str) -> Parser<'a> {
|
||||
Parser {
|
||||
data: data.as_bytes(),
|
||||
pos: 0
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,12 +43,14 @@ impl<'a> Parser<'a> {
|
|||
self.pos += 1;
|
||||
Ok(chr)
|
||||
}
|
||||
None => Err(())
|
||||
None => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_do<F, T>(&mut self, f: F) -> Option<T>
|
||||
where F: FnOnce(&mut Parser<'a>) -> Result<T> {
|
||||
where
|
||||
F: FnOnce(&mut Parser<'a>) -> Result<T>,
|
||||
{
|
||||
let pos = self.pos;
|
||||
match f(self) {
|
||||
Ok(res) => Some(res),
|
||||
|
@ -65,7 +70,9 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
fn until_eof<F, T>(&mut self, f: F) -> Result<T>
|
||||
where F: FnOnce(&mut Parser<'a>) -> Result<T> {
|
||||
where
|
||||
F: FnOnce(&mut Parser<'a>) -> Result<T>,
|
||||
{
|
||||
let res = f(self)?;
|
||||
self.accept_eof()?;
|
||||
Ok(res)
|
||||
|
@ -99,8 +106,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn accept_number(&mut self, max_digits: usize, max_value: u32,
|
||||
hex: bool) -> Result<u32> {
|
||||
fn accept_number(&mut self, max_digits: usize, max_value: u32, hex: bool) -> Result<u32> {
|
||||
let mut value = self.accept_digit(hex)? as u32;
|
||||
for _ in 1..max_digits {
|
||||
match self.try_do(|p| p.accept_digit(hex)) {
|
||||
|
@ -108,7 +114,7 @@ impl<'a> Parser<'a> {
|
|||
value *= if hex { 16 } else { 10 };
|
||||
value += digit as u32;
|
||||
}
|
||||
None => break
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
if value < max_value {
|
||||
|
@ -118,7 +124,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
fn accept_mac_joined_with(&mut self, separator: u8) -> Result<EthernetAddress> {
|
||||
let mut octets = [0u8; 6];
|
||||
for (n, octet) in octets.iter_mut().enumerate() {
|
||||
|
@ -130,13 +136,13 @@ impl<'a> Parser<'a> {
|
|||
Ok(EthernetAddress(octets))
|
||||
}
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
fn accept_mac(&mut self) -> Result<EthernetAddress> {
|
||||
if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b'-')) {
|
||||
return Ok(mac)
|
||||
return Ok(mac);
|
||||
}
|
||||
if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b':')) {
|
||||
return Ok(mac)
|
||||
return Ok(mac);
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
|
@ -154,9 +160,13 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
fn accept_ipv6_part(&mut self, (head, tail): (&mut [u16; 8], &mut [u16; 6]),
|
||||
(head_idx, tail_idx): (&mut usize, &mut usize),
|
||||
mut use_tail: bool, is_cidr: bool) -> Result<()> {
|
||||
fn accept_ipv6_part(
|
||||
&mut self,
|
||||
(head, tail): (&mut [u16; 8], &mut [u16; 6]),
|
||||
(head_idx, tail_idx): (&mut usize, &mut usize),
|
||||
mut use_tail: bool,
|
||||
is_cidr: bool,
|
||||
) -> Result<()> {
|
||||
let double_colon = match self.try_do(|p| p.accept_str(b"::")) {
|
||||
Some(_) if !use_tail && *head_idx < 7 => {
|
||||
// Found a double colon. Start filling out the
|
||||
|
@ -164,7 +174,7 @@ impl<'a> Parser<'a> {
|
|||
// this is the last character we can parse.
|
||||
use_tail = true;
|
||||
true
|
||||
},
|
||||
}
|
||||
Some(_) => {
|
||||
// This is a bad address. Only one double colon is
|
||||
// allowed and an address is only 128 bits.
|
||||
|
@ -193,21 +203,20 @@ impl<'a> Parser<'a> {
|
|||
});
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
Some(part) if *tail_idx < 6 => {
|
||||
// Valid u16 to be added to the address
|
||||
tail[*tail_idx] = part as u16;
|
||||
*tail_idx += 1;
|
||||
|
||||
if *tail_idx == 1 && tail[0] == 0xffff
|
||||
&& head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] {
|
||||
if *tail_idx == 1 && tail[0] == 0xffff && head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] {
|
||||
self.try_do(|p| {
|
||||
p.accept_char(b':')?;
|
||||
p.accept_ipv4_mapped_ipv6_part(tail, tail_idx)
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
Some(_) => {
|
||||
// Tail or head section is too long
|
||||
Err(())
|
||||
|
@ -259,7 +268,12 @@ impl<'a> Parser<'a> {
|
|||
let (mut addr, mut tail) = ([0u16; 8], [0u16; 6]);
|
||||
let (mut head_idx, mut tail_idx) = (0, 0);
|
||||
|
||||
self.accept_ipv6_part((&mut addr, &mut tail), (&mut head_idx, &mut tail_idx), false, is_cidr)?;
|
||||
self.accept_ipv6_part(
|
||||
(&mut addr, &mut tail),
|
||||
(&mut head_idx, &mut tail_idx),
|
||||
false,
|
||||
is_cidr,
|
||||
)?;
|
||||
|
||||
// We need to copy the tail portion (the portion following the "::") to the
|
||||
// end of the address.
|
||||
|
@ -290,14 +304,14 @@ impl<'a> Parser<'a> {
|
|||
#[allow(clippy::single_match)]
|
||||
match self.try_do(|p| p.accept_ipv4()) {
|
||||
Some(ipv4) => return Ok(IpAddress::Ipv4(ipv4)),
|
||||
None => ()
|
||||
None => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
#[allow(clippy::single_match)]
|
||||
match self.try_do(|p| p.accept_ipv6(false)) {
|
||||
Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)),
|
||||
None => ()
|
||||
None => (),
|
||||
}
|
||||
|
||||
Err(())
|
||||
|
@ -314,7 +328,10 @@ impl<'a> Parser<'a> {
|
|||
self.accept_number(5, 65535, false)?
|
||||
};
|
||||
|
||||
Ok(IpEndpoint { addr: IpAddress::Ipv4(ip), port: port as u16 })
|
||||
Ok(IpEndpoint {
|
||||
addr: IpAddress::Ipv4(ip),
|
||||
port: port as u16,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
|
@ -326,10 +343,16 @@ impl<'a> Parser<'a> {
|
|||
self.accept_char(b':')?;
|
||||
let port = self.accept_number(5, 65535, false)?;
|
||||
|
||||
Ok(IpEndpoint { addr: IpAddress::Ipv6(ip), port: port as u16 })
|
||||
Ok(IpEndpoint {
|
||||
addr: IpAddress::Ipv6(ip),
|
||||
port: port as u16,
|
||||
})
|
||||
} else {
|
||||
let ip = self.accept_ipv6(false)?;
|
||||
Ok(IpEndpoint { addr: IpAddress::Ipv6(ip), port: 0 })
|
||||
Ok(IpEndpoint {
|
||||
addr: IpAddress::Ipv6(ip),
|
||||
port: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,21 +361,21 @@ impl<'a> Parser<'a> {
|
|||
#[allow(clippy::single_match)]
|
||||
match self.try_do(|p| p.accept_ipv4_endpoint()) {
|
||||
Some(ipv4) => return Ok(ipv4),
|
||||
None => ()
|
||||
None => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
#[allow(clippy::single_match)]
|
||||
match self.try_do(|p| p.accept_ipv6_endpoint()) {
|
||||
Some(ipv6) => return Ok(ipv6),
|
||||
None => ()
|
||||
None => (),
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
impl FromStr for EthernetAddress {
|
||||
type Err = ();
|
||||
|
||||
|
@ -431,14 +454,14 @@ impl FromStr for IpCidr {
|
|||
#[allow(clippy::single_match)]
|
||||
match Ipv4Cidr::from_str(s) {
|
||||
Ok(cidr) => return Ok(IpCidr::Ipv4(cidr)),
|
||||
Err(_) => ()
|
||||
Err(_) => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
#[allow(clippy::single_match)]
|
||||
match Ipv6Cidr::from_str(s) {
|
||||
Ok(cidr) => return Ok(IpCidr::Ipv6(cidr)),
|
||||
Err(_) => ()
|
||||
Err(_) => (),
|
||||
}
|
||||
|
||||
Err(())
|
||||
|
@ -449,7 +472,7 @@ impl FromStr for IpEndpoint {
|
|||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<IpEndpoint> {
|
||||
Parser::new(s).until_eof(|p| Ok(p.accept_ip_endpoint()?))
|
||||
Parser::new(s).until_eof(|p| p.accept_ip_endpoint())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,29 +488,40 @@ mod test {
|
|||
|
||||
if let Ok(cidr) = cidr {
|
||||
assert_eq!($from_str(&format!("{}", cidr)), Ok(cidr));
|
||||
assert_eq!(IpCidr::from_str(&format!("{}", cidr)),
|
||||
Ok($variant(cidr)));
|
||||
assert_eq!(IpCidr::from_str(&format!("{}", cidr)), Ok($variant(cidr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "proto-ipv4", feature = "ethernet"))]
|
||||
#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
|
||||
fn test_mac() {
|
||||
assert_eq!(EthernetAddress::from_str(""), Err(()));
|
||||
assert_eq!(EthernetAddress::from_str("02:00:00:00:00:00"),
|
||||
Ok(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x00])));
|
||||
assert_eq!(EthernetAddress::from_str("01:23:45:67:89:ab"),
|
||||
Ok(EthernetAddress([0x01, 0x23, 0x45, 0x67, 0x89, 0xab])));
|
||||
assert_eq!(EthernetAddress::from_str("cd:ef:10:00:00:00"),
|
||||
Ok(EthernetAddress([0xcd, 0xef, 0x10, 0x00, 0x00, 0x00])));
|
||||
assert_eq!(EthernetAddress::from_str("00:00:00:ab:cd:ef"),
|
||||
Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef])));
|
||||
assert_eq!(EthernetAddress::from_str("00-00-00-ab-cd-ef"),
|
||||
Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef])));
|
||||
assert_eq!(EthernetAddress::from_str("AB-CD-EF-00-00-00"),
|
||||
Ok(EthernetAddress([0xab, 0xcd, 0xef, 0x00, 0x00, 0x00])));
|
||||
assert_eq!(
|
||||
EthernetAddress::from_str("02:00:00:00:00:00"),
|
||||
Ok(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x00]))
|
||||
);
|
||||
assert_eq!(
|
||||
EthernetAddress::from_str("01:23:45:67:89:ab"),
|
||||
Ok(EthernetAddress([0x01, 0x23, 0x45, 0x67, 0x89, 0xab]))
|
||||
);
|
||||
assert_eq!(
|
||||
EthernetAddress::from_str("cd:ef:10:00:00:00"),
|
||||
Ok(EthernetAddress([0xcd, 0xef, 0x10, 0x00, 0x00, 0x00]))
|
||||
);
|
||||
assert_eq!(
|
||||
EthernetAddress::from_str("00:00:00:ab:cd:ef"),
|
||||
Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
|
||||
);
|
||||
assert_eq!(
|
||||
EthernetAddress::from_str("00-00-00-ab-cd-ef"),
|
||||
Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef]))
|
||||
);
|
||||
assert_eq!(
|
||||
EthernetAddress::from_str("AB-CD-EF-00-00-00"),
|
||||
Ok(EthernetAddress([0xab, 0xcd, 0xef, 0x00, 0x00, 0x00]))
|
||||
);
|
||||
assert_eq!(EthernetAddress::from_str("100:00:00:00:00:00"), Err(()));
|
||||
assert_eq!(EthernetAddress::from_str("002:00:00:00:00:00"), Err(()));
|
||||
assert_eq!(EthernetAddress::from_str("02:00:00:00:00:000"), Err(()));
|
||||
|
@ -498,10 +532,14 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_ipv4() {
|
||||
assert_eq!(Ipv4Address::from_str(""), Err(()));
|
||||
assert_eq!(Ipv4Address::from_str("1.2.3.4"),
|
||||
Ok(Ipv4Address([1, 2, 3, 4])));
|
||||
assert_eq!(Ipv4Address::from_str("001.2.3.4"),
|
||||
Ok(Ipv4Address([1, 2, 3, 4])));
|
||||
assert_eq!(
|
||||
Ipv4Address::from_str("1.2.3.4"),
|
||||
Ok(Ipv4Address([1, 2, 3, 4]))
|
||||
);
|
||||
assert_eq!(
|
||||
Ipv4Address::from_str("001.2.3.4"),
|
||||
Ok(Ipv4Address([1, 2, 3, 4]))
|
||||
);
|
||||
assert_eq!(Ipv4Address::from_str("0001.2.3.4"), Err(()));
|
||||
assert_eq!(Ipv4Address::from_str("999.2.3.4"), Err(()));
|
||||
assert_eq!(Ipv4Address::from_str("1.2.3.4.5"), Err(()));
|
||||
|
@ -515,73 +553,87 @@ mod test {
|
|||
fn test_ipv6() {
|
||||
// Obviously not valid
|
||||
assert_eq!(Ipv6Address::from_str(""), Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("fe80:0:0:0:0:0:0:1"),
|
||||
Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)));
|
||||
assert_eq!(Ipv6Address::from_str("::1"),
|
||||
Ok(Ipv6Address::LOOPBACK));
|
||||
assert_eq!(Ipv6Address::from_str("::"),
|
||||
Ok(Ipv6Address::UNSPECIFIED));
|
||||
assert_eq!(Ipv6Address::from_str("fe80::1"),
|
||||
Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)));
|
||||
assert_eq!(Ipv6Address::from_str("1234:5678::"),
|
||||
Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0, 0)));
|
||||
assert_eq!(Ipv6Address::from_str("1234:5678::8765:4321"),
|
||||
Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0x8765, 0x4321)));
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("fe80:0:0:0:0:0:0:1"),
|
||||
Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))
|
||||
);
|
||||
assert_eq!(Ipv6Address::from_str("::1"), Ok(Ipv6Address::LOOPBACK));
|
||||
assert_eq!(Ipv6Address::from_str("::"), Ok(Ipv6Address::UNSPECIFIED));
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("fe80::1"),
|
||||
Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))
|
||||
);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("1234:5678::"),
|
||||
Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0, 0))
|
||||
);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("1234:5678::8765:4321"),
|
||||
Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0x8765, 0x4321))
|
||||
);
|
||||
// Two double colons in address
|
||||
assert_eq!(Ipv6Address::from_str("1234:5678::1::1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("4444:333:22:1::4"),
|
||||
Ok(Ipv6Address::new(0x4444, 0x0333, 0x0022, 0x0001, 0, 0, 0, 4)));
|
||||
assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1::"),
|
||||
Ok(Ipv6Address::new(1, 1, 1, 1, 1, 1, 0, 0)));
|
||||
assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1"),
|
||||
Ok(Ipv6Address::new(0, 0, 1, 1, 1, 1, 1, 1)));
|
||||
assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("1234:5678::1::1"), Err(()));
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("4444:333:22:1::4"),
|
||||
Ok(Ipv6Address::new(0x4444, 0x0333, 0x0022, 0x0001, 0, 0, 0, 4))
|
||||
);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("1:1:1:1:1:1::"),
|
||||
Ok(Ipv6Address::new(1, 1, 1, 1, 1, 1, 0, 0))
|
||||
);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("::1:1:1:1:1:1"),
|
||||
Ok(Ipv6Address::new(0, 0, 1, 1, 1, 1, 1, 1))
|
||||
);
|
||||
assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(()));
|
||||
// Double colon appears too late indicating an address that is too long
|
||||
assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1::"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1::"), Err(()));
|
||||
// Section after double colon is too long for a valid address
|
||||
assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), Err(()));
|
||||
// Obviously too long
|
||||
assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1:1:1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1:1:1"), Err(()));
|
||||
// Address is too short
|
||||
assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1"), Err(()));
|
||||
// Long number
|
||||
assert_eq!(Ipv6Address::from_str("::000001"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("::000001"), Err(()));
|
||||
// IPv4-Mapped address
|
||||
assert_eq!(Ipv6Address::from_str("::ffff:192.168.1.1"),
|
||||
Ok(Ipv6Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1])));
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:192.168.1.1"),
|
||||
Ok(Ipv6Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1])));
|
||||
assert_eq!(Ipv6Address::from_str("0::ffff:192.168.1.1"),
|
||||
Ok(Ipv6Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1])));
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("::ffff:192.168.1.1"),
|
||||
Ok(Ipv6Address([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("0:0:0:0:0:ffff:192.168.1.1"),
|
||||
Ok(Ipv6Address([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_str("0::ffff:192.168.1.1"),
|
||||
Ok(Ipv6Address([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1
|
||||
]))
|
||||
);
|
||||
// Only ffff is allowed in position 6 when IPv4 mapped
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:eeee:192.168.1.1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:eeee:192.168.1.1"), Err(()));
|
||||
// Positions 1-5 must be 0 when IPv4 mapped
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:1:ffff:192.168.1.1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("1::ffff:192.168.1.1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:1:ffff:192.168.1.1"), Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("1::ffff:192.168.1.1"), Err(()));
|
||||
// Out of range ipv4 octet
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:256.168.1.1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:256.168.1.1"), Err(()));
|
||||
// Invalid hex in ipv4 octet
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:c0.168.1.1"),
|
||||
Err(()));
|
||||
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:c0.168.1.1"), Err(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_ip_ipv4() {
|
||||
assert_eq!(IpAddress::from_str(""), Err(()));
|
||||
assert_eq!(IpAddress::from_str("1.2.3.4"),
|
||||
Ok(IpAddress::Ipv4(Ipv4Address([1, 2, 3, 4]))));
|
||||
assert_eq!(
|
||||
IpAddress::from_str("1.2.3.4"),
|
||||
Ok(IpAddress::Ipv4(Ipv4Address([1, 2, 3, 4])))
|
||||
);
|
||||
assert_eq!(IpAddress::from_str("x"), Err(()));
|
||||
}
|
||||
|
||||
|
@ -589,8 +641,12 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv6")]
|
||||
fn test_ip_ipv6() {
|
||||
assert_eq!(IpAddress::from_str(""), Err(()));
|
||||
assert_eq!(IpAddress::from_str("fe80::1"),
|
||||
Ok(IpAddress::Ipv6(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))));
|
||||
assert_eq!(
|
||||
IpAddress::from_str("fe80::1"),
|
||||
Ok(IpAddress::Ipv6(Ipv6Address::new(
|
||||
0xfe80, 0, 0, 0, 0, 0, 0, 1
|
||||
)))
|
||||
);
|
||||
assert_eq!(IpAddress::from_str("x"), Err(()));
|
||||
}
|
||||
|
||||
|
@ -598,14 +654,22 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_cidr_ipv4() {
|
||||
let tests = [
|
||||
("127.0.0.1/8",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([127, 0, 0, 1]), 8u8))),
|
||||
("192.168.1.1/24",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 1]), 24u8))),
|
||||
("8.8.8.8/32",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 32u8))),
|
||||
("8.8.8.8/0",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 0u8))),
|
||||
(
|
||||
"127.0.0.1/8",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([127, 0, 0, 1]), 8u8)),
|
||||
),
|
||||
(
|
||||
"192.168.1.1/24",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 1]), 24u8)),
|
||||
),
|
||||
(
|
||||
"8.8.8.8/32",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 32u8)),
|
||||
),
|
||||
(
|
||||
"8.8.8.8/0",
|
||||
Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 0u8)),
|
||||
),
|
||||
("", Err(())),
|
||||
("1", Err(())),
|
||||
("127.0.0.1", Err(())),
|
||||
|
@ -622,22 +686,32 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv6")]
|
||||
fn test_cidr_ipv6() {
|
||||
let tests = [
|
||||
("fe80::1/64",
|
||||
Ok(Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64u8))),
|
||||
("fe80::/64",
|
||||
Ok(Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 64u8))),
|
||||
("::1/128",
|
||||
Ok(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128u8))),
|
||||
("::/128",
|
||||
Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))),
|
||||
("fe80:0:0:0:0:0:0:1/64",
|
||||
Ok(Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64u8))),
|
||||
("fe80:0:0:0:0:0:0:1|64",
|
||||
Err(())),
|
||||
("fe80::|64",
|
||||
Err(())),
|
||||
("fe80::1::/64",
|
||||
Err(()))
|
||||
(
|
||||
"fe80::1/64",
|
||||
Ok(Ipv6Cidr::new(
|
||||
Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
|
||||
64u8,
|
||||
)),
|
||||
),
|
||||
(
|
||||
"fe80::/64",
|
||||
Ok(Ipv6Cidr::new(
|
||||
Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
|
||||
64u8,
|
||||
)),
|
||||
),
|
||||
("::1/128", Ok(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128u8))),
|
||||
("::/128", Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))),
|
||||
(
|
||||
"fe80:0:0:0:0:0:0:1/64",
|
||||
Ok(Ipv6Cidr::new(
|
||||
Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
|
||||
64u8,
|
||||
)),
|
||||
),
|
||||
("fe80:0:0:0:0:0:0:1|64", Err(())),
|
||||
("fe80::|64", Err(())),
|
||||
("fe80::1::/64", Err(())),
|
||||
];
|
||||
check_cidr_test_array!(tests, Ipv6Cidr::from_str, IpCidr::Ipv6);
|
||||
}
|
||||
|
@ -649,11 +723,17 @@ mod test {
|
|||
assert_eq!(IpEndpoint::from_str("x"), Err(()));
|
||||
assert_eq!(
|
||||
IpEndpoint::from_str("127.0.0.1"),
|
||||
Ok(IpEndpoint { addr: IpAddress::v4(127, 0, 0, 1), port: 0 })
|
||||
Ok(IpEndpoint {
|
||||
addr: IpAddress::v4(127, 0, 0, 1),
|
||||
port: 0
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
IpEndpoint::from_str("127.0.0.1:12345"),
|
||||
Ok(IpEndpoint { addr: IpAddress::v4(127, 0, 0, 1), port: 12345 })
|
||||
Ok(IpEndpoint {
|
||||
addr: IpAddress::v4(127, 0, 0, 1),
|
||||
port: 12345
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -664,11 +744,17 @@ mod test {
|
|||
assert_eq!(IpEndpoint::from_str("x"), Err(()));
|
||||
assert_eq!(
|
||||
IpEndpoint::from_str("fe80::1"),
|
||||
Ok(IpEndpoint { addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), port: 0 })
|
||||
Ok(IpEndpoint {
|
||||
addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
|
||||
port: 0
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
IpEndpoint::from_str("[fe80::1]:12345"),
|
||||
Ok(IpEndpoint { addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), port: 12345 })
|
||||
Ok(IpEndpoint {
|
||||
addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
|
||||
port: 12345
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use core::cell::RefCell;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::phy::{self, DeviceCapabilities, Device};
|
||||
use crate::phy::{self, Device, DeviceCapabilities};
|
||||
use crate::time::{Duration, Instant};
|
||||
use crate::{Error, Result};
|
||||
|
||||
// We use our own RNG to stay compatible with #![no_std].
|
||||
// The use of the RNG below has a slight bias, but it doesn't matter.
|
||||
|
@ -19,22 +19,23 @@ fn xorshift32(state: &mut u32) -> u32 {
|
|||
const MTU: usize = 1536;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
struct Config {
|
||||
corrupt_pct: u8,
|
||||
drop_pct: u8,
|
||||
reorder_pct: u8,
|
||||
max_size: usize,
|
||||
drop_pct: u8,
|
||||
max_size: usize,
|
||||
max_tx_rate: u64,
|
||||
max_rx_rate: u64,
|
||||
interval: Duration,
|
||||
interval: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
struct State {
|
||||
rng_seed: u32,
|
||||
rng_seed: u32,
|
||||
refilled_at: Instant,
|
||||
tx_bucket: u64,
|
||||
rx_bucket: u64,
|
||||
tx_bucket: u64,
|
||||
rx_bucket: u64,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
@ -46,7 +47,7 @@ impl State {
|
|||
let buffer = buffer.as_mut();
|
||||
// We introduce a single bitflip, as the most likely, and the hardest to detect, error.
|
||||
let index = (xorshift32(&mut self.rng_seed) as usize) % buffer.len();
|
||||
let bit = 1 << (xorshift32(&mut self.rng_seed) % 8) as u8;
|
||||
let bit = 1 << (xorshift32(&mut self.rng_seed) % 8) as u8;
|
||||
buffer[index] ^= bit;
|
||||
}
|
||||
|
||||
|
@ -59,7 +60,9 @@ impl State {
|
|||
}
|
||||
|
||||
fn maybe_transmit(&mut self, config: &Config, timestamp: Instant) -> bool {
|
||||
if config.max_tx_rate == 0 { return true }
|
||||
if config.max_tx_rate == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.refill(config, timestamp);
|
||||
if self.tx_bucket > 0 {
|
||||
|
@ -71,7 +74,9 @@ impl State {
|
|||
}
|
||||
|
||||
fn maybe_receive(&mut self, config: &Config, timestamp: Instant) -> bool {
|
||||
if config.max_rx_rate == 0 { return true }
|
||||
if config.max_rx_rate == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.refill(config, timestamp);
|
||||
if self.rx_bucket > 0 {
|
||||
|
@ -90,22 +95,22 @@ impl State {
|
|||
/// or hardware limitations (such as a limited number or size of usable network buffers).
|
||||
#[derive(Debug)]
|
||||
pub struct FaultInjector<D: for<'a> Device<'a>> {
|
||||
inner: D,
|
||||
state: RefCell<State>,
|
||||
config: Config,
|
||||
inner: D,
|
||||
state: RefCell<State>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl<D: for<'a> Device<'a>> FaultInjector<D> {
|
||||
/// Create a fault injector device, using the given random number generator seed.
|
||||
pub fn new(inner: D, seed: u32) -> FaultInjector<D> {
|
||||
let state = State {
|
||||
rng_seed: seed,
|
||||
rng_seed: seed,
|
||||
refilled_at: Instant::from_millis(0),
|
||||
tx_bucket: 0,
|
||||
rx_bucket: 0,
|
||||
tx_bucket: 0,
|
||||
rx_bucket: 0,
|
||||
};
|
||||
FaultInjector {
|
||||
inner: inner,
|
||||
inner,
|
||||
state: RefCell::new(state),
|
||||
config: Config::default(),
|
||||
}
|
||||
|
@ -151,7 +156,9 @@ impl<D: for<'a> Device<'a>> FaultInjector<D> {
|
|||
/// # Panics
|
||||
/// This function panics if the probability is not between 0% and 100%.
|
||||
pub fn set_corrupt_chance(&mut self, pct: u8) {
|
||||
if pct > 100 { panic!("percentage out of range") }
|
||||
if pct > 100 {
|
||||
panic!("percentage out of range")
|
||||
}
|
||||
self.config.corrupt_pct = pct
|
||||
}
|
||||
|
||||
|
@ -160,7 +167,9 @@ impl<D: for<'a> Device<'a>> FaultInjector<D> {
|
|||
/// # Panics
|
||||
/// This function panics if the probability is not between 0% and 100%.
|
||||
pub fn set_drop_chance(&mut self, pct: u8) {
|
||||
if pct > 100 { panic!("percentage out of range") }
|
||||
if pct > 100 {
|
||||
panic!("percentage out of range")
|
||||
}
|
||||
self.config.drop_pct = pct
|
||||
}
|
||||
|
||||
|
@ -187,7 +196,8 @@ impl<D: for<'a> Device<'a>> FaultInjector<D> {
|
|||
}
|
||||
|
||||
impl<'a, D> Device<'a> for FaultInjector<D>
|
||||
where D: for<'b> Device<'b>,
|
||||
where
|
||||
D: for<'b> Device<'b>,
|
||||
{
|
||||
type RxToken = RxToken<'a, <D as Device<'a>>::RxToken>;
|
||||
type TxToken = TxToken<'a, <D as Device<'a>>::TxToken>;
|
||||
|
@ -201,67 +211,85 @@ impl<'a, D> Device<'a> for FaultInjector<D>
|
|||
}
|
||||
|
||||
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||
let &mut Self { ref mut inner, ref state, config } = self;
|
||||
let &mut Self {
|
||||
ref mut inner,
|
||||
ref state,
|
||||
config,
|
||||
} = self;
|
||||
inner.receive().map(|(rx_token, tx_token)| {
|
||||
let rx = RxToken {
|
||||
state: &state,
|
||||
config: config,
|
||||
token: rx_token,
|
||||
state,
|
||||
config,
|
||||
token: rx_token,
|
||||
corrupt: [0; MTU],
|
||||
};
|
||||
let tx = TxToken {
|
||||
state: &state,
|
||||
config: config,
|
||||
token: tx_token,
|
||||
junk: [0; MTU],
|
||||
state,
|
||||
config,
|
||||
token: tx_token,
|
||||
junk: [0; MTU],
|
||||
};
|
||||
(rx, tx)
|
||||
})
|
||||
}
|
||||
|
||||
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
||||
let &mut Self { ref mut inner, ref state, config } = self;
|
||||
let &mut Self {
|
||||
ref mut inner,
|
||||
ref state,
|
||||
config,
|
||||
} = self;
|
||||
inner.transmit().map(|token| TxToken {
|
||||
state: &state,
|
||||
config: config,
|
||||
token: token,
|
||||
junk: [0; MTU],
|
||||
state,
|
||||
config,
|
||||
token,
|
||||
junk: [0; MTU],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct RxToken<'a, Rx: phy::RxToken> {
|
||||
state: &'a RefCell<State>,
|
||||
config: Config,
|
||||
token: Rx,
|
||||
state: &'a RefCell<State>,
|
||||
config: Config,
|
||||
token: Rx,
|
||||
corrupt: [u8; MTU],
|
||||
}
|
||||
|
||||
impl<'a, Rx: phy::RxToken> phy::RxToken for RxToken<'a, Rx> {
|
||||
fn consume<R, F>(self, timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
if self.state.borrow_mut().maybe(self.config.drop_pct) {
|
||||
net_trace!("rx: randomly dropping a packet");
|
||||
return Err(Error::Exhausted)
|
||||
return Err(Error::Exhausted);
|
||||
}
|
||||
if !self.state.borrow_mut().maybe_receive(&self.config, timestamp) {
|
||||
if !self
|
||||
.state
|
||||
.borrow_mut()
|
||||
.maybe_receive(&self.config, timestamp)
|
||||
{
|
||||
net_trace!("rx: dropping a packet because of rate limiting");
|
||||
return Err(Error::Exhausted)
|
||||
return Err(Error::Exhausted);
|
||||
}
|
||||
let Self { token, config, state, mut corrupt } = self;
|
||||
let Self {
|
||||
token,
|
||||
config,
|
||||
state,
|
||||
mut corrupt,
|
||||
} = self;
|
||||
token.consume(timestamp, |buffer| {
|
||||
if config.max_size > 0 && buffer.as_ref().len() > config.max_size {
|
||||
net_trace!("rx: dropping a packet that is too large");
|
||||
return Err(Error::Exhausted)
|
||||
return Err(Error::Exhausted);
|
||||
}
|
||||
if state.borrow_mut().maybe(config.corrupt_pct) {
|
||||
net_trace!("rx: randomly corrupting a packet");
|
||||
let mut corrupt = &mut corrupt[..buffer.len()];
|
||||
corrupt.copy_from_slice(buffer);
|
||||
state.borrow_mut().corrupt(&mut corrupt);
|
||||
f(&mut corrupt)
|
||||
f(corrupt)
|
||||
} else {
|
||||
f(buffer)
|
||||
}
|
||||
|
@ -271,15 +299,16 @@ impl<'a, Rx: phy::RxToken> phy::RxToken for RxToken<'a, Rx> {
|
|||
|
||||
#[doc(hidden)]
|
||||
pub struct TxToken<'a, Tx: phy::TxToken> {
|
||||
state: &'a RefCell<State>,
|
||||
state: &'a RefCell<State>,
|
||||
config: Config,
|
||||
token: Tx,
|
||||
junk: [u8; MTU],
|
||||
token: Tx,
|
||||
junk: [u8; MTU],
|
||||
}
|
||||
|
||||
impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> {
|
||||
fn consume<R, F>(mut self, timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let drop = if self.state.borrow_mut().maybe(self.config.drop_pct) {
|
||||
net_trace!("tx: randomly dropping a packet");
|
||||
|
@ -287,7 +316,11 @@ impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> {
|
|||
} else if self.config.max_size > 0 && len > self.config.max_size {
|
||||
net_trace!("tx: dropping a packet that is too large");
|
||||
true
|
||||
} else if !self.state.borrow_mut().maybe_transmit(&self.config, timestamp) {
|
||||
} else if !self
|
||||
.state
|
||||
.borrow_mut()
|
||||
.maybe_transmit(&self.config, timestamp)
|
||||
{
|
||||
net_trace!("tx: dropping a packet because of rate limiting");
|
||||
true
|
||||
} else {
|
||||
|
@ -295,10 +328,15 @@ impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> {
|
|||
};
|
||||
|
||||
if drop {
|
||||
return f(&mut self.junk);
|
||||
return f(&mut self.junk[..len]);
|
||||
}
|
||||
|
||||
let Self { token, state, config, .. } = self;
|
||||
let Self {
|
||||
token,
|
||||
state,
|
||||
config,
|
||||
..
|
||||
} = self;
|
||||
token.consume(timestamp, len, |mut buf| {
|
||||
if state.borrow_mut().maybe(config.corrupt_pct) {
|
||||
net_trace!("tx: corrupting a packet");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::Result;
|
||||
use crate::phy::{self, DeviceCapabilities, Device};
|
||||
use crate::phy::{self, Device, DeviceCapabilities};
|
||||
use crate::time::Instant;
|
||||
use crate::Result;
|
||||
|
||||
// This could be fixed once associated consts are stable.
|
||||
const MTU: usize = 1536;
|
||||
|
@ -18,8 +18,9 @@ pub trait Fuzzer {
|
|||
/// smoltcp, and is not for production use.
|
||||
#[allow(unused)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct FuzzInjector<D: for<'a> Device<'a>, FTx: Fuzzer, FRx: Fuzzer> {
|
||||
inner: D,
|
||||
inner: D,
|
||||
fuzz_tx: FTx,
|
||||
fuzz_rx: FRx,
|
||||
}
|
||||
|
@ -28,7 +29,11 @@ pub struct FuzzInjector<D: for<'a> Device<'a>, FTx: Fuzzer, FRx: Fuzzer> {
|
|||
impl<D: for<'a> Device<'a>, FTx: Fuzzer, FRx: Fuzzer> FuzzInjector<D, FTx, FRx> {
|
||||
/// Create a fuzz injector device.
|
||||
pub fn new(inner: D, fuzz_tx: FTx, fuzz_rx: FRx) -> FuzzInjector<D, FTx, FRx> {
|
||||
FuzzInjector { inner, fuzz_tx, fuzz_rx }
|
||||
FuzzInjector {
|
||||
inner,
|
||||
fuzz_tx,
|
||||
fuzz_rx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the underlying device, consuming the fuzz injector.
|
||||
|
@ -38,9 +43,10 @@ impl<D: for<'a> Device<'a>, FTx: Fuzzer, FRx: Fuzzer> FuzzInjector<D, FTx, FRx>
|
|||
}
|
||||
|
||||
impl<'a, D, FTx, FRx> Device<'a> for FuzzInjector<D, FTx, FRx>
|
||||
where D: for<'b> Device<'b>,
|
||||
FTx: Fuzzer + 'a,
|
||||
FRx: Fuzzer + 'a
|
||||
where
|
||||
D: for<'b> Device<'b>,
|
||||
FTx: Fuzzer + 'a,
|
||||
FRx: Fuzzer + 'a,
|
||||
{
|
||||
type RxToken = RxToken<'a, <D as Device<'a>>::RxToken, FRx>;
|
||||
type TxToken = TxToken<'a, <D as Device<'a>>::TxToken, FTx>;
|
||||
|
@ -54,38 +60,47 @@ impl<'a, D, FTx, FRx> Device<'a> for FuzzInjector<D, FTx, FRx>
|
|||
}
|
||||
|
||||
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||
let &mut Self { ref mut inner, ref fuzz_rx, ref fuzz_tx } = self;
|
||||
let &mut Self {
|
||||
ref mut inner,
|
||||
ref fuzz_rx,
|
||||
ref fuzz_tx,
|
||||
} = self;
|
||||
inner.receive().map(|(rx_token, tx_token)| {
|
||||
let rx = RxToken {
|
||||
fuzzer: fuzz_rx,
|
||||
token: rx_token,
|
||||
token: rx_token,
|
||||
};
|
||||
let tx = TxToken {
|
||||
fuzzer: fuzz_tx,
|
||||
token: tx_token,
|
||||
token: tx_token,
|
||||
};
|
||||
(rx, tx)
|
||||
})
|
||||
}
|
||||
|
||||
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
||||
let &mut Self { ref mut inner, fuzz_rx: _, ref fuzz_tx } = self;
|
||||
let &mut Self {
|
||||
ref mut inner,
|
||||
fuzz_rx: _,
|
||||
ref fuzz_tx,
|
||||
} = self;
|
||||
inner.transmit().map(|token| TxToken {
|
||||
fuzzer: fuzz_tx,
|
||||
token: token,
|
||||
token: token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct RxToken<'a, Rx: phy::RxToken, F: Fuzzer + 'a>{
|
||||
pub struct RxToken<'a, Rx: phy::RxToken, F: Fuzzer + 'a> {
|
||||
fuzzer: &'a F,
|
||||
token: Rx,
|
||||
token: Rx,
|
||||
}
|
||||
|
||||
impl<'a, Rx: phy::RxToken, FRx: Fuzzer> phy::RxToken for RxToken<'a, Rx, FRx> {
|
||||
fn consume<R, F>(self, timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let Self { fuzzer, token } = self;
|
||||
token.consume(timestamp, |buffer| {
|
||||
|
@ -98,17 +113,19 @@ impl<'a, Rx: phy::RxToken, FRx: Fuzzer> phy::RxToken for RxToken<'a, Rx, FRx> {
|
|||
#[doc(hidden)]
|
||||
pub struct TxToken<'a, Tx: phy::TxToken, F: Fuzzer + 'a> {
|
||||
fuzzer: &'a F,
|
||||
token: Tx,
|
||||
token: Tx,
|
||||
}
|
||||
|
||||
impl<'a, Tx: phy::TxToken, FTx: Fuzzer> phy::TxToken for TxToken<'a, Tx, FTx> {
|
||||
fn consume<R, F>(self, timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let Self { fuzzer, token } = self;
|
||||
token.consume(timestamp, len, |mut buf| {
|
||||
fuzzer.fuzz_packet(&mut buf);
|
||||
f(buf)
|
||||
token.consume(timestamp, len, |buf| {
|
||||
let result = f(buf);
|
||||
fuzzer.fuzz_packet(buf);
|
||||
result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
#[cfg(feature = "std")]
|
||||
use std::vec::Vec;
|
||||
#[cfg(feature = "std")]
|
||||
use std::collections::VecDeque;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
#[cfg(all(feature = "alloc", not(feature = "rust-1_28")))]
|
||||
#[cfg(not(feature = "rust-1_28"))]
|
||||
use alloc::collections::VecDeque;
|
||||
#[cfg(all(feature = "alloc", feature = "rust-1_28"))]
|
||||
use alloc::vec::Vec;
|
||||
#[cfg(feature = "rust-1_28")]
|
||||
use alloc::VecDeque;
|
||||
|
||||
use crate::Result;
|
||||
use crate::phy::{self, Device, DeviceCapabilities};
|
||||
use crate::phy::{self, Device, DeviceCapabilities, Medium};
|
||||
use crate::time::Instant;
|
||||
use crate::Result;
|
||||
|
||||
/// A loopback device.
|
||||
#[derive(Debug)]
|
||||
pub struct Loopback {
|
||||
queue: VecDeque<Vec<u8>>,
|
||||
medium: Medium,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
|
@ -25,9 +21,10 @@ impl Loopback {
|
|||
///
|
||||
/// Every packet transmitted through this device will be received through it
|
||||
/// in FIFO order.
|
||||
pub fn new() -> Loopback {
|
||||
pub fn new(medium: Medium) -> Loopback {
|
||||
Loopback {
|
||||
queue: VecDeque::new(),
|
||||
medium,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +36,7 @@ impl<'a> Device<'a> for Loopback {
|
|||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
DeviceCapabilities {
|
||||
max_transmission_unit: 65535,
|
||||
medium: self.medium,
|
||||
..DeviceCapabilities::default()
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +44,9 @@ impl<'a> Device<'a> for Loopback {
|
|||
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||
self.queue.pop_front().map(move |buffer| {
|
||||
let rx = RxToken { buffer };
|
||||
let tx = TxToken { queue: &mut self.queue };
|
||||
let tx = TxToken {
|
||||
queue: &mut self.queue,
|
||||
};
|
||||
(rx, tx)
|
||||
})
|
||||
}
|
||||
|
@ -65,7 +65,8 @@ pub struct RxToken {
|
|||
|
||||
impl phy::RxToken for RxToken {
|
||||
fn consume<R, F>(mut self, _timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
f(&mut self.buffer)
|
||||
}
|
||||
|
@ -78,7 +79,8 @@ pub struct TxToken<'a> {
|
|||
|
||||
impl<'a> phy::TxToken for TxToken<'a> {
|
||||
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let mut buffer = Vec::new();
|
||||
buffer.resize(len, 0);
|
||||
|
|
135
src/phy/mod.rs
135
src/phy/mod.rs
|
@ -8,9 +8,12 @@ and implementations of it:
|
|||
* _middleware_ [Tracer](struct.Tracer.html) and
|
||||
[FaultInjector](struct.FaultInjector.html), to facilitate debugging;
|
||||
* _adapters_ [RawSocket](struct.RawSocket.html) and
|
||||
[TapInterface](struct.TapInterface.html), to transmit and receive frames
|
||||
[TunTapInterface](struct.TunTapInterface.html), to transmit and receive frames
|
||||
on the host OS.
|
||||
|
||||
*/
|
||||
#![cfg_attr(
|
||||
feature = "medium-ethernet",
|
||||
doc = r##"
|
||||
# Examples
|
||||
|
||||
An implementation of the [Device](trait.Device.html) trait for a simple hardware
|
||||
|
@ -18,7 +21,7 @@ Ethernet controller could look as follows:
|
|||
|
||||
```rust
|
||||
use smoltcp::Result;
|
||||
use smoltcp::phy::{self, DeviceCapabilities, Device};
|
||||
use smoltcp::phy::{self, DeviceCapabilities, Device, Medium};
|
||||
use smoltcp::time::Instant;
|
||||
|
||||
struct StmPhy {
|
||||
|
@ -52,6 +55,7 @@ impl<'a> phy::Device<'a> for StmPhy {
|
|||
let mut caps = DeviceCapabilities::default();
|
||||
caps.max_transmission_unit = 1536;
|
||||
caps.max_burst_size = Some(1);
|
||||
caps.medium = Medium::Ethernet;
|
||||
caps
|
||||
}
|
||||
}
|
||||
|
@ -82,45 +86,55 @@ impl<'a> phy::TxToken for StmPhyTxToken<'a> {
|
|||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
"##
|
||||
)]
|
||||
|
||||
use crate::Result;
|
||||
use crate::time::Instant;
|
||||
use crate::Result;
|
||||
|
||||
#[cfg(all(any(feature = "phy-raw_socket", feature = "phy-tap_interface"), unix))]
|
||||
#[cfg(all(
|
||||
any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"),
|
||||
unix
|
||||
))]
|
||||
mod sys;
|
||||
|
||||
mod tracer;
|
||||
mod fault_injector;
|
||||
mod fuzz_injector;
|
||||
mod pcap_writer;
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
mod loopback;
|
||||
mod pcap_writer;
|
||||
#[cfg(all(feature = "phy-raw_socket", unix))]
|
||||
mod raw_socket;
|
||||
#[cfg(all(feature = "phy-tap_interface", any(target_os = "linux", target_os = "android")))]
|
||||
mod tap_interface;
|
||||
mod tracer;
|
||||
#[cfg(all(
|
||||
feature = "phy-tuntap_interface",
|
||||
any(target_os = "linux", target_os = "android")
|
||||
))]
|
||||
mod tuntap_interface;
|
||||
|
||||
#[cfg(all(any(feature = "phy-raw_socket", feature = "phy-tap_interface"), unix))]
|
||||
#[cfg(all(
|
||||
any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"),
|
||||
unix
|
||||
))]
|
||||
pub use self::sys::wait;
|
||||
|
||||
pub use self::tracer::Tracer;
|
||||
pub use self::fault_injector::FaultInjector;
|
||||
pub use self::fuzz_injector::{Fuzzer, FuzzInjector};
|
||||
pub use self::pcap_writer::{PcapLinkType, PcapMode, PcapSink, PcapWriter};
|
||||
pub use self::fuzz_injector::{FuzzInjector, Fuzzer};
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
pub use self::loopback::Loopback;
|
||||
pub use self::pcap_writer::{PcapLinkType, PcapMode, PcapSink, PcapWriter};
|
||||
#[cfg(all(feature = "phy-raw_socket", unix))]
|
||||
pub use self::raw_socket::RawSocket;
|
||||
#[cfg(all(feature = "phy-tap_interface", any(target_os = "linux", target_os = "android")))]
|
||||
pub use self::tap_interface::TapInterface;
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
/// A tracer device for Ethernet frames.
|
||||
pub type EthernetTracer<T> = Tracer<T, super::wire::EthernetFrame<&'static [u8]>>;
|
||||
pub use self::tracer::Tracer;
|
||||
#[cfg(all(
|
||||
feature = "phy-tuntap_interface",
|
||||
any(target_os = "linux", target_os = "android")
|
||||
))]
|
||||
pub use self::tuntap_interface::TunTapInterface;
|
||||
|
||||
/// A description of checksum behavior for a particular protocol.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Checksum {
|
||||
/// Verify checksum when receiving and compute checksum when sending.
|
||||
Both,
|
||||
|
@ -143,7 +157,7 @@ impl Checksum {
|
|||
pub fn rx(&self) -> bool {
|
||||
match *self {
|
||||
Checksum::Both | Checksum::Rx => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,13 +165,14 @@ impl Checksum {
|
|||
pub fn tx(&self) -> bool {
|
||||
match *self {
|
||||
Checksum::Both | Checksum::Tx => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A description of checksum behavior for every supported protocol.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct ChecksumCapabilities {
|
||||
pub ipv4: Checksum,
|
||||
|
@ -190,8 +205,16 @@ impl ChecksumCapabilities {
|
|||
/// Higher-level protocols may achieve higher throughput or lower latency if they consider
|
||||
/// the bandwidth or packet size limitations.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct DeviceCapabilities {
|
||||
/// Medium of the device.
|
||||
///
|
||||
/// This indicates what kind of packet the sent/received bytes are, and determines
|
||||
/// some behaviors of Interface. For example, ARP/NDISC address resolution is only done
|
||||
/// for Ethernet mediums.
|
||||
pub medium: Medium,
|
||||
|
||||
/// Maximum transmission unit.
|
||||
///
|
||||
/// The network device is unable to send or receive frames larger than the value returned
|
||||
|
@ -222,6 +245,68 @@ pub struct DeviceCapabilities {
|
|||
pub checksum: ChecksumCapabilities,
|
||||
}
|
||||
|
||||
impl DeviceCapabilities {
|
||||
pub fn ip_mtu(&self) -> usize {
|
||||
match self.medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => {
|
||||
self.max_transmission_unit - crate::wire::EthernetFrame::<&[u8]>::header_len()
|
||||
}
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => self.max_transmission_unit,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => self.max_transmission_unit, // TODO(thvdveld): what is the MTU for Medium::IEEE802
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of medium of a device.
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Medium {
|
||||
/// Ethernet medium. Devices of this type send and receive Ethernet frames,
|
||||
/// and interfaces using it must do neighbor discovery via ARP or NDISC.
|
||||
///
|
||||
/// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode.
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Ethernet,
|
||||
|
||||
/// IP medium. Devices of this type send and receive IP frames, without an
|
||||
/// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done.
|
||||
///
|
||||
/// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Ip,
|
||||
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Ieee802154,
|
||||
}
|
||||
|
||||
impl Default for Medium {
|
||||
fn default() -> Medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
return Medium::Ethernet;
|
||||
#[cfg(all(
|
||||
feature = "medium-ip",
|
||||
not(feature = "medium-ethernet"),
|
||||
not(feature = "medium-ieee802154")
|
||||
))]
|
||||
return Medium::Ip;
|
||||
#[cfg(all(
|
||||
feature = "medium-ieee802154",
|
||||
not(feature = "medium-ip"),
|
||||
not(feature = "medium-ethernet")
|
||||
))]
|
||||
return Medium::Ieee802154;
|
||||
#[cfg(all(
|
||||
not(feature = "medium-ip"),
|
||||
not(feature = "medium-ethernet"),
|
||||
not(feature = "medium-ieee802154")
|
||||
))]
|
||||
panic!("No medium enabled");
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface for sending and receiving raw network frames.
|
||||
///
|
||||
/// The interface is based on _tokens_, which are types that allow to receive/transmit a
|
||||
|
@ -256,7 +341,8 @@ pub trait RxToken {
|
|||
/// The timestamp must be a number of milliseconds, monotonically increasing since an
|
||||
/// arbitrary moment in time, such as system startup.
|
||||
fn consume<R, F>(self, timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>;
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>;
|
||||
}
|
||||
|
||||
/// A token to transmit a single network packet.
|
||||
|
@ -271,5 +357,6 @@ pub trait TxToken {
|
|||
/// The timestamp must be a number of milliseconds, monotonically increasing since an
|
||||
/// arbitrary moment in time, such as system startup.
|
||||
fn consume<R, F>(self, timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>;
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#[cfg(feature = "std")]
|
||||
use std::cell::RefCell;
|
||||
use byteorder::{ByteOrder, NativeEndian};
|
||||
use core::cell::RefCell;
|
||||
use phy::Medium;
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::Write;
|
||||
use byteorder::{ByteOrder, NativeEndian};
|
||||
|
||||
use crate::Result;
|
||||
use crate::phy::{self, DeviceCapabilities, Device};
|
||||
use crate::phy::{self, Device, DeviceCapabilities};
|
||||
use crate::time::Instant;
|
||||
use crate::Result;
|
||||
|
||||
enum_with_unknown! {
|
||||
/// Captured packet header type.
|
||||
|
@ -14,35 +14,41 @@ enum_with_unknown! {
|
|||
/// Ethernet frames
|
||||
Ethernet = 1,
|
||||
/// IPv4 or IPv6 packets (depending on the version field)
|
||||
Ip = 101
|
||||
Ip = 101,
|
||||
/// IEEE 802.15.4 packets with FCS included.
|
||||
Ieee802154WithFcs = 195,
|
||||
}
|
||||
}
|
||||
|
||||
/// Packet capture mode.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PcapMode {
|
||||
/// Capture both received and transmitted packets.
|
||||
Both,
|
||||
/// Capture only received packets.
|
||||
RxOnly,
|
||||
/// Capture only transmitted packets.
|
||||
TxOnly
|
||||
TxOnly,
|
||||
}
|
||||
|
||||
/// A packet capture sink.
|
||||
pub trait PcapSink {
|
||||
/// Write data into the sink.
|
||||
fn write(&self, data: &[u8]);
|
||||
fn write(&mut self, data: &[u8]);
|
||||
|
||||
/// Flush data written into the sync.
|
||||
fn flush(&mut self) {}
|
||||
|
||||
/// Write an `u16` into the sink, in native byte order.
|
||||
fn write_u16(&self, value: u16) {
|
||||
fn write_u16(&mut self, value: u16) {
|
||||
let mut bytes = [0u8; 2];
|
||||
NativeEndian::write_u16(&mut bytes, value);
|
||||
self.write(&bytes[..])
|
||||
}
|
||||
|
||||
/// Write an `u32` into the sink, in native byte order.
|
||||
fn write_u32(&self, value: u32) {
|
||||
fn write_u32(&mut self, value: u32) {
|
||||
let mut bytes = [0u8; 4];
|
||||
NativeEndian::write_u32(&mut bytes, value);
|
||||
self.write(&bytes[..])
|
||||
|
@ -51,13 +57,13 @@ pub trait PcapSink {
|
|||
/// Write the libpcap global header into the sink.
|
||||
///
|
||||
/// This method may be overridden e.g. if special synchronization is necessary.
|
||||
fn global_header(&self, link_type: PcapLinkType) {
|
||||
self.write_u32(0xa1b2c3d4); // magic number
|
||||
self.write_u16(2); // major version
|
||||
self.write_u16(4); // minor version
|
||||
self.write_u32(0); // timezone (= UTC)
|
||||
self.write_u32(0); // accuracy (not used)
|
||||
self.write_u32(65535); // maximum packet length
|
||||
fn global_header(&mut self, link_type: PcapLinkType) {
|
||||
self.write_u32(0xa1b2c3d4); // magic number
|
||||
self.write_u16(2); // major version
|
||||
self.write_u16(4); // minor version
|
||||
self.write_u32(0); // timezone (= UTC)
|
||||
self.write_u32(0); // accuracy (not used)
|
||||
self.write_u32(65535); // maximum packet length
|
||||
self.write_u32(link_type.into()); // link-layer header type
|
||||
}
|
||||
|
||||
|
@ -67,40 +73,33 @@ pub trait PcapSink {
|
|||
///
|
||||
/// # Panics
|
||||
/// This function panics if `length` is greater than 65535.
|
||||
fn packet_header(&self, timestamp: Instant, length: usize) {
|
||||
fn packet_header(&mut self, timestamp: Instant, length: usize) {
|
||||
assert!(length <= 65535);
|
||||
|
||||
self.write_u32(timestamp.secs() as u32); // timestamp seconds
|
||||
self.write_u32(timestamp.millis() as u32); // timestamp microseconds
|
||||
self.write_u32(length as u32); // captured length
|
||||
self.write_u32(length as u32); // original length
|
||||
self.write_u32(timestamp.secs() as u32); // timestamp seconds
|
||||
self.write_u32(timestamp.micros() as u32); // timestamp microseconds
|
||||
self.write_u32(length as u32); // captured length
|
||||
self.write_u32(length as u32); // original length
|
||||
}
|
||||
|
||||
/// Write the libpcap packet header followed by packet data into the sink.
|
||||
///
|
||||
/// See also the note for [global_header](#method.global_header).
|
||||
fn packet(&self, timestamp: Instant, packet: &[u8]) {
|
||||
fn packet(&mut self, timestamp: Instant, packet: &[u8]) {
|
||||
self.packet_header(timestamp, packet.len());
|
||||
self.write(packet)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<dyn PcapSink>> PcapSink for T {
|
||||
fn write(&self, data: &[u8]) {
|
||||
self.as_ref().write(data)
|
||||
self.write(packet);
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Write> PcapSink for RefCell<T> {
|
||||
fn write(&self, data: &[u8]) {
|
||||
self.borrow_mut().write_all(data).expect("cannot write")
|
||||
impl<T: Write> PcapSink for T {
|
||||
fn write(&mut self, data: &[u8]) {
|
||||
T::write_all(self, data).expect("cannot write")
|
||||
}
|
||||
|
||||
fn packet(&self, timestamp: Instant, packet: &[u8]) {
|
||||
self.packet_header(timestamp, packet.len());
|
||||
PcapSink::write(self, packet);
|
||||
self.borrow_mut().flush().expect("cannot flush")
|
||||
fn flush(&mut self) {
|
||||
T::flush(self).expect("cannot flush")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,63 +117,106 @@ impl<T: Write> PcapSink for RefCell<T> {
|
|||
/// [sink]: trait.PcapSink.html
|
||||
#[derive(Debug)]
|
||||
pub struct PcapWriter<D, S>
|
||||
where D: for<'a> Device<'a>,
|
||||
S: PcapSink + Clone,
|
||||
where
|
||||
D: for<'a> Device<'a>,
|
||||
S: PcapSink,
|
||||
{
|
||||
lower: D,
|
||||
sink: S,
|
||||
mode: PcapMode,
|
||||
sink: RefCell<S>,
|
||||
mode: PcapMode,
|
||||
}
|
||||
|
||||
impl<D: for<'a> Device<'a>, S: PcapSink + Clone> PcapWriter<D, S> {
|
||||
impl<D: for<'a> Device<'a>, S: PcapSink> PcapWriter<D, S> {
|
||||
/// Creates a packet capture writer.
|
||||
pub fn new(lower: D, sink: S, mode: PcapMode, link_type: PcapLinkType) -> PcapWriter<D, S> {
|
||||
pub fn new(lower: D, mut sink: S, mode: PcapMode) -> PcapWriter<D, S> {
|
||||
let medium = lower.capabilities().medium;
|
||||
let link_type = match medium {
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => PcapLinkType::Ip,
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => PcapLinkType::Ethernet,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => PcapLinkType::Ieee802154WithFcs,
|
||||
};
|
||||
sink.global_header(link_type);
|
||||
PcapWriter { lower, sink, mode }
|
||||
PcapWriter {
|
||||
lower,
|
||||
sink: RefCell::new(sink),
|
||||
mode,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the underlying device.
|
||||
///
|
||||
/// Even if the device offers reading through a standard reference, it is inadvisable to
|
||||
/// directly read from the device as doing so will circumvent the packet capture.
|
||||
pub fn get_ref(&self) -> &D {
|
||||
&self.lower
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the underlying device.
|
||||
///
|
||||
/// It is inadvisable to directly read from the device as doing so will circumvent the packet capture.
|
||||
pub fn get_mut(&mut self) -> &mut D {
|
||||
&mut self.lower
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, D, S> Device<'a> for PcapWriter<D, S>
|
||||
where D: for<'b> Device<'b>,
|
||||
S: PcapSink + Clone + 'a,
|
||||
where
|
||||
D: for<'b> Device<'b>,
|
||||
S: PcapSink + 'a,
|
||||
{
|
||||
type RxToken = RxToken<<D as Device<'a>>::RxToken, S>;
|
||||
type TxToken = TxToken<<D as Device<'a>>::TxToken, S>;
|
||||
type RxToken = RxToken<'a, <D as Device<'a>>::RxToken, S>;
|
||||
type TxToken = TxToken<'a, <D as Device<'a>>::TxToken, S>;
|
||||
|
||||
fn capabilities(&self) -> DeviceCapabilities { self.lower.capabilities() }
|
||||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
self.lower.capabilities()
|
||||
}
|
||||
|
||||
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||
let &mut Self { ref mut lower, ref sink, mode, .. } = self;
|
||||
lower.receive().map(|(rx_token, tx_token)| {
|
||||
let rx = RxToken { token: rx_token, sink: sink.clone(), mode };
|
||||
let tx = TxToken { token: tx_token, sink: sink.clone(), mode };
|
||||
let sink = &self.sink;
|
||||
let mode = self.mode;
|
||||
self.lower.receive().map(move |(rx_token, tx_token)| {
|
||||
let rx = RxToken {
|
||||
token: rx_token,
|
||||
sink,
|
||||
mode,
|
||||
};
|
||||
let tx = TxToken {
|
||||
token: tx_token,
|
||||
sink,
|
||||
mode,
|
||||
};
|
||||
(rx, tx)
|
||||
})
|
||||
}
|
||||
|
||||
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
||||
let &mut Self { ref mut lower, ref sink, mode } = self;
|
||||
lower.transmit().map(|token| {
|
||||
TxToken { token, sink: sink.clone(), mode }
|
||||
})
|
||||
let sink = &self.sink;
|
||||
let mode = self.mode;
|
||||
self.lower
|
||||
.transmit()
|
||||
.map(move |token| TxToken { token, sink, mode })
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct RxToken<Rx: phy::RxToken, S: PcapSink> {
|
||||
pub struct RxToken<'a, Rx: phy::RxToken, S: PcapSink> {
|
||||
token: Rx,
|
||||
sink: S,
|
||||
mode: PcapMode,
|
||||
sink: &'a RefCell<S>,
|
||||
mode: PcapMode,
|
||||
}
|
||||
|
||||
impl<Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<Rx, S> {
|
||||
impl<'a, Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<'a, Rx, S> {
|
||||
fn consume<R, F: FnOnce(&mut [u8]) -> Result<R>>(self, timestamp: Instant, f: F) -> Result<R> {
|
||||
let Self { token, sink, mode } = self;
|
||||
token.consume(timestamp, |buffer| {
|
||||
match mode {
|
||||
PcapMode::Both | PcapMode::RxOnly =>
|
||||
sink.packet(timestamp, buffer.as_ref()),
|
||||
PcapMode::TxOnly => ()
|
||||
PcapMode::Both | PcapMode::RxOnly => {
|
||||
sink.borrow_mut().packet(timestamp, buffer.as_ref())
|
||||
}
|
||||
PcapMode::TxOnly => (),
|
||||
}
|
||||
f(buffer)
|
||||
})
|
||||
|
@ -182,23 +224,23 @@ impl<Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<Rx, S> {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct TxToken<Tx: phy::TxToken, S: PcapSink> {
|
||||
pub struct TxToken<'a, Tx: phy::TxToken, S: PcapSink> {
|
||||
token: Tx,
|
||||
sink: S,
|
||||
mode: PcapMode
|
||||
sink: &'a RefCell<S>,
|
||||
mode: PcapMode,
|
||||
}
|
||||
|
||||
impl<Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<Tx, S> {
|
||||
impl<'a, Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<'a, Tx, S> {
|
||||
fn consume<R, F>(self, timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let Self { token, sink, mode } = self;
|
||||
token.consume(timestamp, len, |buffer| {
|
||||
let result = f(buffer);
|
||||
match mode {
|
||||
PcapMode::Both | PcapMode::TxOnly =>
|
||||
sink.packet(timestamp, &buffer),
|
||||
PcapMode::RxOnly => ()
|
||||
PcapMode::Both | PcapMode::TxOnly => sink.borrow_mut().packet(timestamp, buffer),
|
||||
PcapMode::RxOnly => (),
|
||||
};
|
||||
result
|
||||
})
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
use std::cell::RefCell;
|
||||
use std::vec::Vec;
|
||||
use std::rc::Rc;
|
||||
use std::io;
|
||||
use std::os::unix::io::{RawFd, AsRawFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::Result;
|
||||
use crate::phy::{self, sys, DeviceCapabilities, Device};
|
||||
use crate::phy::{self, sys, Device, DeviceCapabilities, Medium};
|
||||
use crate::time::Instant;
|
||||
use crate::Result;
|
||||
|
||||
/// A socket that captures or transmits the complete frame.
|
||||
#[derive(Debug)]
|
||||
pub struct RawSocket {
|
||||
lower: Rc<RefCell<sys::RawSocketDesc>>,
|
||||
mtu: usize
|
||||
medium: Medium,
|
||||
lower: Rc<RefCell<sys::RawSocketDesc>>,
|
||||
mtu: usize,
|
||||
}
|
||||
|
||||
impl AsRawFd for RawSocket {
|
||||
|
@ -26,13 +27,23 @@ impl RawSocket {
|
|||
///
|
||||
/// This requires superuser privileges or a corresponding capability bit
|
||||
/// set on the executable.
|
||||
pub fn new(name: &str) -> io::Result<RawSocket> {
|
||||
let mut lower = sys::RawSocketDesc::new(name)?;
|
||||
pub fn new(name: &str, medium: Medium) -> io::Result<RawSocket> {
|
||||
let mut lower = sys::RawSocketDesc::new(name, medium)?;
|
||||
lower.bind_interface()?;
|
||||
let mtu = lower.interface_mtu()?;
|
||||
|
||||
let mut mtu = lower.interface_mtu()?;
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if medium == Medium::Ethernet {
|
||||
// SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
|
||||
// smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
|
||||
mtu += crate::wire::EthernetFrame::<&[u8]>::header_len()
|
||||
}
|
||||
|
||||
Ok(RawSocket {
|
||||
medium,
|
||||
lower: Rc::new(RefCell::new(lower)),
|
||||
mtu: mtu
|
||||
mtu: mtu,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +55,7 @@ impl<'a> Device<'a> for RawSocket {
|
|||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
DeviceCapabilities {
|
||||
max_transmission_unit: self.mtu,
|
||||
medium: self.medium,
|
||||
..DeviceCapabilities::default()
|
||||
}
|
||||
}
|
||||
|
@ -55,13 +67,13 @@ impl<'a> Device<'a> for RawSocket {
|
|||
Ok(size) => {
|
||||
buffer.resize(size, 0);
|
||||
let rx = RxToken { buffer };
|
||||
let tx = TxToken { lower: self.lower.clone() };
|
||||
let tx = TxToken {
|
||||
lower: self.lower.clone(),
|
||||
};
|
||||
Some((rx, tx))
|
||||
}
|
||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {
|
||||
None
|
||||
}
|
||||
Err(err) => panic!("{}", err)
|
||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => None,
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,12 +86,13 @@ impl<'a> Device<'a> for RawSocket {
|
|||
|
||||
#[doc(hidden)]
|
||||
pub struct RxToken {
|
||||
buffer: Vec<u8>
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl phy::RxToken for RxToken {
|
||||
fn consume<R, F>(mut self, _timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
f(&mut self.buffer[..])
|
||||
}
|
||||
|
@ -87,12 +100,13 @@ impl phy::RxToken for RxToken {
|
|||
|
||||
#[doc(hidden)]
|
||||
pub struct TxToken {
|
||||
lower: Rc<RefCell<sys::RawSocketDesc>>,
|
||||
lower: Rc<RefCell<sys::RawSocketDesc>>,
|
||||
}
|
||||
|
||||
impl phy::TxToken for TxToken {
|
||||
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let mut lower = self.lower.borrow_mut();
|
||||
let mut buffer = vec![0; len];
|
||||
|
|
|
@ -4,8 +4,9 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||
|
||||
use libc;
|
||||
|
||||
use crate::wire::ETHERNET_HEADER_LEN;
|
||||
use super::{ifreq, ifreq_for};
|
||||
use crate::phy::Medium;
|
||||
use crate::wire::ETHERNET_HEADER_LEN;
|
||||
|
||||
/// set interface
|
||||
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
|
||||
|
@ -67,7 +68,7 @@ fn open_device() -> io::Result<libc::c_int> {
|
|||
}
|
||||
|
||||
impl BpfDevice {
|
||||
pub fn new(name: &str) -> io::Result<BpfDevice> {
|
||||
pub fn new(name: &str, _medium: Medium) -> io::Result<BpfDevice> {
|
||||
Ok(BpfDevice {
|
||||
fd: open_device()?,
|
||||
ifreq: ifreq_for(name),
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
#[cfg(any(feature = "phy-raw_socket",
|
||||
feature = "phy-tap_interface"))]
|
||||
pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
|
||||
#[cfg(any(feature = "phy-raw_socket"))]
|
||||
pub const SIOCGIFINDEX: libc::c_ulong = 0x8933;
|
||||
#[cfg(any(feature = "phy-raw_socket"))]
|
||||
pub const ETH_P_ALL: libc::c_short = 0x0003;
|
||||
#![allow(unused)]
|
||||
|
||||
#[cfg(feature = "phy-tap_interface")]
|
||||
pub const TUNSETIFF: libc::c_ulong = 0x400454CA;
|
||||
#[cfg(feature = "phy-tap_interface")]
|
||||
pub const IFF_TAP: libc::c_int = 0x0002;
|
||||
#[cfg(feature = "phy-tap_interface")]
|
||||
pub const IFF_NO_PI: libc::c_int = 0x1000;
|
||||
pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
|
||||
pub const SIOCGIFINDEX: libc::c_ulong = 0x8933;
|
||||
pub const ETH_P_ALL: libc::c_short = 0x0003;
|
||||
pub const ETH_P_IEEE802154: libc::c_short = 0x00F6;
|
||||
|
||||
pub const TUNSETIFF: libc::c_ulong = 0x400454CA;
|
||||
pub const IFF_TUN: libc::c_int = 0x0001;
|
||||
pub const IFF_TAP: libc::c_int = 0x0002;
|
||||
pub const IFF_NO_PI: libc::c_int = 0x1000;
|
||||
|
|
|
@ -1,26 +1,46 @@
|
|||
#![allow(unsafe_code)]
|
||||
|
||||
use std::{mem, ptr, io};
|
||||
use std::os::unix::io::RawFd;
|
||||
use crate::time::Duration;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::{io, mem, ptr};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[path = "linux.rs"]
|
||||
mod imp;
|
||||
|
||||
#[cfg(all(feature = "phy-raw_socket", any(target_os = "linux", target_os = "android")))]
|
||||
pub mod raw_socket;
|
||||
#[cfg(all(feature = "phy-raw_socket", not(any(target_os = "linux", target_os = "android")), unix))]
|
||||
#[cfg(all(
|
||||
feature = "phy-raw_socket",
|
||||
not(any(target_os = "linux", target_os = "android")),
|
||||
unix
|
||||
))]
|
||||
pub mod bpf;
|
||||
#[cfg(all(feature = "phy-tap_interface", any(target_os = "linux", target_os = "android")))]
|
||||
pub mod tap_interface;
|
||||
#[cfg(all(
|
||||
feature = "phy-raw_socket",
|
||||
any(target_os = "linux", target_os = "android")
|
||||
))]
|
||||
pub mod raw_socket;
|
||||
#[cfg(all(
|
||||
feature = "phy-tuntap_interface",
|
||||
any(target_os = "linux", target_os = "android")
|
||||
))]
|
||||
pub mod tuntap_interface;
|
||||
|
||||
#[cfg(all(feature = "phy-raw_socket", any(target_os = "linux", target_os = "android")))]
|
||||
pub use self::raw_socket::RawSocketDesc;
|
||||
#[cfg(all(feature = "phy-raw_socket", not(any(target_os = "linux", target_os = "android")), unix))]
|
||||
#[cfg(all(
|
||||
feature = "phy-raw_socket",
|
||||
not(any(target_os = "linux", target_os = "android")),
|
||||
unix
|
||||
))]
|
||||
pub use self::bpf::BpfDevice as RawSocketDesc;
|
||||
#[cfg(all(feature = "phy-tap_interface", any(target_os = "linux", target_os = "android")))]
|
||||
pub use self::tap_interface::TapInterfaceDesc;
|
||||
#[cfg(all(
|
||||
feature = "phy-raw_socket",
|
||||
any(target_os = "linux", target_os = "android")
|
||||
))]
|
||||
pub use self::raw_socket::RawSocketDesc;
|
||||
#[cfg(all(
|
||||
feature = "phy-tuntap_interface",
|
||||
any(target_os = "linux", target_os = "android")
|
||||
))]
|
||||
pub use self::tuntap_interface::TunTapInterfaceDesc;
|
||||
|
||||
/// Wait until given file descriptor becomes readable, but no longer than given timeout.
|
||||
pub fn wait(fd: RawFd, duration: Option<Duration>) -> io::Result<()> {
|
||||
|
@ -44,34 +64,51 @@ pub fn wait(fd: RawFd, duration: Option<Duration>) -> io::Result<()> {
|
|||
exceptfds.assume_init()
|
||||
};
|
||||
|
||||
let mut timeout = libc::timeval { tv_sec: 0, tv_usec: 0 };
|
||||
let timeout_ptr =
|
||||
if let Some(duration) = duration {
|
||||
timeout.tv_usec = (duration.total_millis() * 1_000) as libc::suseconds_t;
|
||||
&mut timeout as *mut _
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
let mut timeout = libc::timeval {
|
||||
tv_sec: 0,
|
||||
tv_usec: 0,
|
||||
};
|
||||
let timeout_ptr = if let Some(duration) = duration {
|
||||
timeout.tv_sec = duration.secs() as libc::time_t;
|
||||
timeout.tv_usec = (duration.millis() * 1_000) as libc::suseconds_t;
|
||||
&mut timeout as *mut _
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
};
|
||||
|
||||
let res = libc::select(fd + 1, &mut readfds, &mut writefds, &mut exceptfds, timeout_ptr);
|
||||
if res == -1 { return Err(io::Error::last_os_error()) }
|
||||
let res = libc::select(
|
||||
fd + 1,
|
||||
&mut readfds,
|
||||
&mut writefds,
|
||||
&mut exceptfds,
|
||||
timeout_ptr,
|
||||
);
|
||||
if res == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "phy-tap_interface", feature = "phy-raw_socket"), unix))]
|
||||
#[cfg(all(
|
||||
any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"),
|
||||
unix
|
||||
))]
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct ifreq {
|
||||
ifr_name: [libc::c_char; libc::IF_NAMESIZE],
|
||||
ifr_data: libc::c_int /* ifr_ifindex or ifr_mtu */
|
||||
ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "phy-tap_interface", feature = "phy-raw_socket"), unix))]
|
||||
#[cfg(all(
|
||||
any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"),
|
||||
unix
|
||||
))]
|
||||
fn ifreq_for(name: &str) -> ifreq {
|
||||
let mut ifreq = ifreq {
|
||||
ifr_name: [0; libc::IF_NAMESIZE],
|
||||
ifr_data: 0
|
||||
ifr_data: 0,
|
||||
};
|
||||
for (i, byte) in name.as_bytes().iter().enumerate() {
|
||||
ifreq.ifr_name[i] = *byte as libc::c_char
|
||||
|
@ -79,13 +116,20 @@ fn ifreq_for(name: &str) -> ifreq {
|
|||
ifreq
|
||||
}
|
||||
|
||||
#[cfg(all(any(target_os = "linux", target_os = "android"),
|
||||
any(feature = "phy-tap_interface", feature = "phy-raw_socket")))]
|
||||
fn ifreq_ioctl(lower: libc::c_int, ifreq: &mut ifreq,
|
||||
cmd: libc::c_ulong) -> io::Result<libc::c_int> {
|
||||
#[cfg(all(
|
||||
any(target_os = "linux", target_os = "android"),
|
||||
any(feature = "phy-tuntap_interface", feature = "phy-raw_socket")
|
||||
))]
|
||||
fn ifreq_ioctl(
|
||||
lower: libc::c_int,
|
||||
ifreq: &mut ifreq,
|
||||
cmd: libc::c_ulong,
|
||||
) -> io::Result<libc::c_int> {
|
||||
unsafe {
|
||||
let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq);
|
||||
if res == -1 { return Err(io::Error::last_os_error()) }
|
||||
if res == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ifreq.ifr_data)
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::{mem, io};
|
||||
use std::os::unix::io::{RawFd, AsRawFd};
|
||||
use super::*;
|
||||
use crate::phy::Medium;
|
||||
use crate::wire::EthernetFrame;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::{io, mem};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RawSocketDesc {
|
||||
protocol: libc::c_short,
|
||||
lower: libc::c_int,
|
||||
ifreq: ifreq
|
||||
ifreq: ifreq,
|
||||
}
|
||||
|
||||
impl AsRawFd for RawSocketDesc {
|
||||
|
@ -16,43 +18,63 @@ impl AsRawFd for RawSocketDesc {
|
|||
}
|
||||
|
||||
impl RawSocketDesc {
|
||||
pub fn new(name: &str) -> io::Result<RawSocketDesc> {
|
||||
pub fn new(name: &str, medium: Medium) -> io::Result<RawSocketDesc> {
|
||||
let protocol = match medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => imp::ETH_P_ALL,
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => imp::ETH_P_ALL,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => imp::ETH_P_IEEE802154,
|
||||
};
|
||||
|
||||
let lower = unsafe {
|
||||
let lower = libc::socket(libc::AF_PACKET, libc::SOCK_RAW | libc::SOCK_NONBLOCK,
|
||||
imp::ETH_P_ALL.to_be() as i32);
|
||||
if lower == -1 { return Err(io::Error::last_os_error()) }
|
||||
let lower = libc::socket(
|
||||
libc::AF_PACKET,
|
||||
libc::SOCK_RAW | libc::SOCK_NONBLOCK,
|
||||
protocol.to_be() as i32,
|
||||
);
|
||||
if lower == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
lower
|
||||
};
|
||||
|
||||
Ok(RawSocketDesc {
|
||||
lower: lower,
|
||||
ifreq: ifreq_for(name)
|
||||
protocol,
|
||||
lower,
|
||||
ifreq: ifreq_for(name),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn interface_mtu(&mut self) -> io::Result<usize> {
|
||||
// SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
|
||||
// smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
|
||||
let ip_mtu = ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize)?;
|
||||
let ip_mtu =
|
||||
ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize)?;
|
||||
Ok(ip_mtu + EthernetFrame::<&[u8]>::header_len())
|
||||
}
|
||||
|
||||
pub fn bind_interface(&mut self) -> io::Result<()> {
|
||||
let sockaddr = libc::sockaddr_ll {
|
||||
sll_family: libc::AF_PACKET as u16,
|
||||
sll_protocol: imp::ETH_P_ALL.to_be() as u16,
|
||||
sll_ifindex: ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFINDEX)?,
|
||||
sll_hatype: 1,
|
||||
sll_pkttype: 0,
|
||||
sll_halen: 6,
|
||||
sll_addr: [0; 8]
|
||||
sll_family: libc::AF_PACKET as u16,
|
||||
sll_protocol: self.protocol.to_be() as u16,
|
||||
sll_ifindex: ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFINDEX)?,
|
||||
sll_hatype: 1,
|
||||
sll_pkttype: 0,
|
||||
sll_halen: 6,
|
||||
sll_addr: [0; 8],
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let res = libc::bind(self.lower,
|
||||
&sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr,
|
||||
mem::size_of::<libc::sockaddr_ll>() as u32);
|
||||
if res == -1 { return Err(io::Error::last_os_error()) }
|
||||
let res = libc::bind(
|
||||
self.lower,
|
||||
&sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr,
|
||||
mem::size_of::<libc::sockaddr_ll>() as u32,
|
||||
);
|
||||
if res == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -60,18 +82,30 @@ impl RawSocketDesc {
|
|||
|
||||
pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let len = libc::recv(self.lower, buffer.as_mut_ptr() as *mut libc::c_void,
|
||||
buffer.len(), 0);
|
||||
if len == -1 { return Err(io::Error::last_os_error()) }
|
||||
let len = libc::recv(
|
||||
self.lower,
|
||||
buffer.as_mut_ptr() as *mut libc::c_void,
|
||||
buffer.len(),
|
||||
0,
|
||||
);
|
||||
if len == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let len = libc::send(self.lower, buffer.as_ptr() as *const libc::c_void,
|
||||
buffer.len(), 0);
|
||||
if len == -1 { Err(io::Error::last_os_error()).unwrap() }
|
||||
let len = libc::send(
|
||||
self.lower,
|
||||
buffer.as_ptr() as *const libc::c_void,
|
||||
buffer.len(),
|
||||
0,
|
||||
);
|
||||
if len == -1 {
|
||||
Err(io::Error::last_os_error()).unwrap()
|
||||
}
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +113,8 @@ impl RawSocketDesc {
|
|||
|
||||
impl Drop for RawSocketDesc {
|
||||
fn drop(&mut self) {
|
||||
unsafe { libc::close(self.lower); }
|
||||
unsafe {
|
||||
libc::close(self.lower);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
use std::io;
|
||||
use std::os::unix::io::{RawFd, AsRawFd};
|
||||
use super::*;
|
||||
use crate::wire::EthernetFrame;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TapInterfaceDesc {
|
||||
lower: libc::c_int,
|
||||
ifreq: ifreq
|
||||
}
|
||||
|
||||
impl AsRawFd for TapInterfaceDesc {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.lower
|
||||
}
|
||||
}
|
||||
|
||||
impl TapInterfaceDesc {
|
||||
pub fn new(name: &str) -> io::Result<TapInterfaceDesc> {
|
||||
let lower = unsafe {
|
||||
let lower = libc::open("/dev/net/tun\0".as_ptr() as *const libc::c_char,
|
||||
libc::O_RDWR | libc::O_NONBLOCK);
|
||||
if lower == -1 { return Err(io::Error::last_os_error()) }
|
||||
lower
|
||||
};
|
||||
|
||||
Ok(TapInterfaceDesc {
|
||||
lower: lower,
|
||||
ifreq: ifreq_for(name)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attach_interface(&mut self) -> io::Result<()> {
|
||||
self.ifreq.ifr_data = imp::IFF_TAP | imp::IFF_NO_PI;
|
||||
ifreq_ioctl(self.lower, &mut self.ifreq, imp::TUNSETIFF).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn interface_mtu(&mut self) -> io::Result<usize> {
|
||||
let lower = unsafe {
|
||||
let lower = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
|
||||
if lower == -1 { return Err(io::Error::last_os_error()) }
|
||||
lower
|
||||
};
|
||||
|
||||
let ip_mtu = ifreq_ioctl(lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize);
|
||||
|
||||
unsafe { libc::close(lower); }
|
||||
|
||||
// SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
|
||||
// smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
|
||||
Ok(ip_mtu? + EthernetFrame::<&[u8]>::header_len())
|
||||
}
|
||||
|
||||
pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let len = libc::read(self.lower, buffer.as_mut_ptr() as *mut libc::c_void,
|
||||
buffer.len());
|
||||
if len == -1 { return Err(io::Error::last_os_error()) }
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let len = libc::write(self.lower, buffer.as_ptr() as *const libc::c_void,
|
||||
buffer.len());
|
||||
if len == -1 { Err(io::Error::last_os_error()).unwrap() }
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TapInterfaceDesc {
|
||||
fn drop(&mut self) {
|
||||
unsafe { libc::close(self.lower); }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
use super::*;
|
||||
use crate::{phy::Medium, wire::EthernetFrame};
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TunTapInterfaceDesc {
|
||||
lower: libc::c_int,
|
||||
ifreq: ifreq,
|
||||
medium: Medium,
|
||||
}
|
||||
|
||||
impl AsRawFd for TunTapInterfaceDesc {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.lower
|
||||
}
|
||||
}
|
||||
|
||||
impl TunTapInterfaceDesc {
|
||||
pub fn new(name: &str, medium: Medium) -> io::Result<TunTapInterfaceDesc> {
|
||||
let lower = unsafe {
|
||||
let lower = libc::open(
|
||||
"/dev/net/tun\0".as_ptr() as *const libc::c_char,
|
||||
libc::O_RDWR | libc::O_NONBLOCK,
|
||||
);
|
||||
if lower == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
lower
|
||||
};
|
||||
|
||||
Ok(TunTapInterfaceDesc {
|
||||
lower,
|
||||
ifreq: ifreq_for(name),
|
||||
medium,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attach_interface(&mut self) -> io::Result<()> {
|
||||
let mode = match self.medium {
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => imp::IFF_TUN,
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => imp::IFF_TAP,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => todo!(),
|
||||
};
|
||||
self.ifreq.ifr_data = mode | imp::IFF_NO_PI;
|
||||
ifreq_ioctl(self.lower, &mut self.ifreq, imp::TUNSETIFF).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn interface_mtu(&mut self) -> io::Result<usize> {
|
||||
let lower = unsafe {
|
||||
let lower = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
|
||||
if lower == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
lower
|
||||
};
|
||||
|
||||
let ip_mtu = ifreq_ioctl(lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize);
|
||||
|
||||
unsafe {
|
||||
libc::close(lower);
|
||||
}
|
||||
|
||||
// Propagate error after close, to ensure we always close.
|
||||
let ip_mtu = ip_mtu?;
|
||||
|
||||
// SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
|
||||
// smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
|
||||
let mtu = match self.medium {
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => ip_mtu,
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => ip_mtu + EthernetFrame::<&[u8]>::header_len(),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => todo!(),
|
||||
};
|
||||
|
||||
Ok(mtu)
|
||||
}
|
||||
|
||||
pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let len = libc::read(
|
||||
self.lower,
|
||||
buffer.as_mut_ptr() as *mut libc::c_void,
|
||||
buffer.len(),
|
||||
);
|
||||
if len == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let len = libc::write(
|
||||
self.lower,
|
||||
buffer.as_ptr() as *const libc::c_void,
|
||||
buffer.len(),
|
||||
);
|
||||
if len == -1 {
|
||||
Err(io::Error::last_os_error()).unwrap()
|
||||
}
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TunTapInterfaceDesc {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::close(self.lower);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +1,25 @@
|
|||
use crate::Result;
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyPrinter};
|
||||
use crate::phy::{self, DeviceCapabilities, Device};
|
||||
use core::fmt;
|
||||
|
||||
use crate::phy::{self, Device, DeviceCapabilities, Medium};
|
||||
use crate::time::Instant;
|
||||
use crate::{
|
||||
wire::pretty_print::{PrettyIndent, PrettyPrint},
|
||||
Result,
|
||||
};
|
||||
|
||||
/// A tracer device.
|
||||
///
|
||||
/// A tracer is a device that pretty prints all packets traversing it
|
||||
/// using the provided writer function, and then passes them to another
|
||||
/// device.
|
||||
pub struct Tracer<D: for<'a> Device<'a>, P: PrettyPrint> {
|
||||
inner: D,
|
||||
writer: fn(Instant, PrettyPrinter<P>),
|
||||
pub struct Tracer<D: for<'a> Device<'a>> {
|
||||
inner: D,
|
||||
writer: fn(Instant, Packet),
|
||||
}
|
||||
|
||||
impl<D: for<'a> Device<'a>, P: PrettyPrint> Tracer<D, P> {
|
||||
impl<D: for<'a> Device<'a>> Tracer<D> {
|
||||
/// Create a tracer device.
|
||||
pub fn new(inner: D, writer: fn(timestamp: Instant, printer: PrettyPrinter<P>)) -> Tracer<D, P> {
|
||||
pub fn new(inner: D, writer: fn(timestamp: Instant, packet: Packet)) -> Tracer<D> {
|
||||
Tracer { inner, writer }
|
||||
}
|
||||
|
||||
|
@ -40,65 +44,154 @@ impl<D: for<'a> Device<'a>, P: PrettyPrint> Tracer<D, P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, D, P> Device<'a> for Tracer<D, P>
|
||||
where D: for<'b> Device<'b>,
|
||||
P: PrettyPrint + 'a,
|
||||
impl<'a, D> Device<'a> for Tracer<D>
|
||||
where
|
||||
D: for<'b> Device<'b>,
|
||||
{
|
||||
type RxToken = RxToken<<D as Device<'a>>::RxToken, P>;
|
||||
type TxToken = TxToken<<D as Device<'a>>::TxToken, P>;
|
||||
type RxToken = RxToken<<D as Device<'a>>::RxToken>;
|
||||
type TxToken = TxToken<<D as Device<'a>>::TxToken>;
|
||||
|
||||
fn capabilities(&self) -> DeviceCapabilities { self.inner.capabilities() }
|
||||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
self.inner.capabilities()
|
||||
}
|
||||
|
||||
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||
let &mut Self { ref mut inner, writer, .. } = self;
|
||||
let &mut Self {
|
||||
ref mut inner,
|
||||
writer,
|
||||
..
|
||||
} = self;
|
||||
let medium = inner.capabilities().medium;
|
||||
inner.receive().map(|(rx_token, tx_token)| {
|
||||
let rx = RxToken { token: rx_token, writer };
|
||||
let tx = TxToken { token: tx_token, writer };
|
||||
let rx = RxToken {
|
||||
token: rx_token,
|
||||
writer,
|
||||
medium,
|
||||
};
|
||||
let tx = TxToken {
|
||||
token: tx_token,
|
||||
writer,
|
||||
medium,
|
||||
};
|
||||
(rx, tx)
|
||||
})
|
||||
}
|
||||
|
||||
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
||||
let &mut Self { ref mut inner, writer } = self;
|
||||
inner.transmit().map(|tx_token| {
|
||||
TxToken { token: tx_token, writer }
|
||||
let &mut Self {
|
||||
ref mut inner,
|
||||
writer,
|
||||
} = self;
|
||||
let medium = inner.capabilities().medium;
|
||||
inner.transmit().map(|tx_token| TxToken {
|
||||
token: tx_token,
|
||||
medium,
|
||||
writer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct RxToken<Rx: phy::RxToken, P: PrettyPrint> {
|
||||
token: Rx,
|
||||
writer: fn(Instant, PrettyPrinter<P>)
|
||||
pub struct RxToken<Rx: phy::RxToken> {
|
||||
token: Rx,
|
||||
writer: fn(Instant, Packet),
|
||||
medium: Medium,
|
||||
}
|
||||
|
||||
impl<Rx: phy::RxToken, P: PrettyPrint> phy::RxToken for RxToken<Rx, P> {
|
||||
impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
|
||||
fn consume<R, F>(self, timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let Self { token, writer } = self;
|
||||
let Self {
|
||||
token,
|
||||
writer,
|
||||
medium,
|
||||
} = self;
|
||||
token.consume(timestamp, |buffer| {
|
||||
writer(timestamp, PrettyPrinter::<P>::new("<- ", &buffer));
|
||||
writer(
|
||||
timestamp,
|
||||
Packet {
|
||||
buffer,
|
||||
medium,
|
||||
prefix: "<- ",
|
||||
},
|
||||
);
|
||||
f(buffer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct TxToken<Tx: phy::TxToken, P: PrettyPrint> {
|
||||
token: Tx,
|
||||
writer: fn(Instant, PrettyPrinter<P>)
|
||||
pub struct TxToken<Tx: phy::TxToken> {
|
||||
token: Tx,
|
||||
writer: fn(Instant, Packet),
|
||||
medium: Medium,
|
||||
}
|
||||
|
||||
impl<Tx: phy::TxToken, P: PrettyPrint> phy::TxToken for TxToken<Tx, P> {
|
||||
impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
|
||||
fn consume<R, F>(self, timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let Self { token, writer } = self;
|
||||
let Self {
|
||||
token,
|
||||
writer,
|
||||
medium,
|
||||
} = self;
|
||||
token.consume(timestamp, len, |buffer| {
|
||||
let result = f(buffer);
|
||||
writer(timestamp, PrettyPrinter::<P>::new("-> ", &buffer));
|
||||
writer(
|
||||
timestamp,
|
||||
Packet {
|
||||
buffer,
|
||||
medium,
|
||||
prefix: "-> ",
|
||||
},
|
||||
);
|
||||
result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Packet<'a> {
|
||||
buffer: &'a [u8],
|
||||
medium: Medium,
|
||||
prefix: &'static str,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Packet<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut indent = PrettyIndent::new(self.prefix);
|
||||
match self.medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print(
|
||||
&self.buffer,
|
||||
f,
|
||||
&mut indent,
|
||||
),
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => match crate::wire::IpVersion::of_packet(self.buffer) {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
Ok(crate::wire::IpVersion::Ipv4) => {
|
||||
crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print(
|
||||
&self.buffer,
|
||||
f,
|
||||
&mut indent,
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
Ok(crate::wire::IpVersion::Ipv6) => {
|
||||
crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print(
|
||||
&self.buffer,
|
||||
f,
|
||||
&mut indent,
|
||||
)
|
||||
}
|
||||
_ => f.write_str("unrecognized IP version"),
|
||||
},
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => Ok(()), // XXX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,53 @@
|
|||
use std::cell::RefCell;
|
||||
use std::vec::Vec;
|
||||
use std::rc::Rc;
|
||||
use std::io;
|
||||
use std::os::unix::io::{RawFd, AsRawFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::Result;
|
||||
use crate::phy::{self, sys, DeviceCapabilities, Device};
|
||||
use crate::phy::{self, sys, Device, DeviceCapabilities, Medium};
|
||||
use crate::time::Instant;
|
||||
use crate::Result;
|
||||
|
||||
/// A virtual Ethernet interface.
|
||||
/// A virtual TUN (IP) or TAP (Ethernet) interface.
|
||||
#[derive(Debug)]
|
||||
pub struct TapInterface {
|
||||
lower: Rc<RefCell<sys::TapInterfaceDesc>>,
|
||||
mtu: usize
|
||||
pub struct TunTapInterface {
|
||||
lower: Rc<RefCell<sys::TunTapInterfaceDesc>>,
|
||||
mtu: usize,
|
||||
medium: Medium,
|
||||
}
|
||||
|
||||
impl AsRawFd for TapInterface {
|
||||
impl AsRawFd for TunTapInterface {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.lower.borrow().as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl TapInterface {
|
||||
/// Attaches to a TAP interface called `name`, or creates it if it does not exist.
|
||||
impl TunTapInterface {
|
||||
/// Attaches to a TUN/TAP interface called `name`, or creates it if it does not exist.
|
||||
///
|
||||
/// If `name` is a persistent interface configured with UID of the current user,
|
||||
/// no special privileges are needed. Otherwise, this requires superuser privileges
|
||||
/// or a corresponding capability set on the executable.
|
||||
pub fn new(name: &str) -> io::Result<TapInterface> {
|
||||
let mut lower = sys::TapInterfaceDesc::new(name)?;
|
||||
pub fn new(name: &str, medium: Medium) -> io::Result<TunTapInterface> {
|
||||
let mut lower = sys::TunTapInterfaceDesc::new(name, medium)?;
|
||||
lower.attach_interface()?;
|
||||
let mtu = lower.interface_mtu()?;
|
||||
Ok(TapInterface {
|
||||
Ok(TunTapInterface {
|
||||
lower: Rc::new(RefCell::new(lower)),
|
||||
mtu: mtu
|
||||
mtu,
|
||||
medium,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Device<'a> for TapInterface {
|
||||
impl<'a> Device<'a> for TunTapInterface {
|
||||
type RxToken = RxToken;
|
||||
type TxToken = TxToken;
|
||||
|
||||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
DeviceCapabilities {
|
||||
max_transmission_unit: self.mtu,
|
||||
medium: self.medium,
|
||||
..DeviceCapabilities::default()
|
||||
}
|
||||
}
|
||||
|
@ -56,13 +59,13 @@ impl<'a> Device<'a> for TapInterface {
|
|||
Ok(size) => {
|
||||
buffer.resize(size, 0);
|
||||
let rx = RxToken { buffer };
|
||||
let tx = TxToken { lower: self.lower.clone() };
|
||||
let tx = TxToken {
|
||||
lower: self.lower.clone(),
|
||||
};
|
||||
Some((rx, tx))
|
||||
}
|
||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {
|
||||
None
|
||||
}
|
||||
Err(err) => panic!("{}", err)
|
||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => None,
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,12 +78,13 @@ impl<'a> Device<'a> for TapInterface {
|
|||
|
||||
#[doc(hidden)]
|
||||
pub struct RxToken {
|
||||
buffer: Vec<u8>
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl phy::RxToken for RxToken {
|
||||
fn consume<R, F>(mut self, _timestamp: Instant, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
f(&mut self.buffer[..])
|
||||
}
|
||||
|
@ -88,12 +92,13 @@ impl phy::RxToken for RxToken {
|
|||
|
||||
#[doc(hidden)]
|
||||
pub struct TxToken {
|
||||
lower: Rc<RefCell<sys::TapInterfaceDesc>>,
|
||||
lower: Rc<RefCell<sys::TunTapInterfaceDesc>>,
|
||||
}
|
||||
|
||||
impl phy::TxToken for TxToken {
|
||||
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<R>,
|
||||
{
|
||||
let mut lower = self.lower.borrow_mut();
|
||||
let mut buffer = vec![0; len];
|
|
@ -0,0 +1,67 @@
|
|||
#![allow(unsafe_code)]
|
||||
#![allow(unused)]
|
||||
|
||||
#[cfg(not(any(test, feature = "std", feature = "rand-custom-impl")))]
|
||||
compile_error!("None of the Cargo features `std` or `rand-custom-impl` is enabled. smoltcp needs a `rand` implementation to work. If your target supports `std`, enable the `std` feature to use the OS's RNG. Otherwise, you must enable the `rand-custom-impl` Cargo feature, and supply your own custom implementation using the `smoltcp::rand_custom_impl!()` macro");
|
||||
|
||||
pub fn rand_u32() -> u32 {
|
||||
let mut val = [0; 4];
|
||||
rand_bytes(&mut val);
|
||||
u32::from_ne_bytes(val)
|
||||
}
|
||||
|
||||
/// Fill `buf` with random bytes.
|
||||
pub fn rand_bytes(buf: &mut [u8]) {
|
||||
extern "Rust" {
|
||||
fn _smoltcp_rand(buf: &mut [u8]);
|
||||
}
|
||||
|
||||
unsafe { _smoltcp_rand(buf) }
|
||||
}
|
||||
|
||||
/// Methods required for a custom rand implementation.
|
||||
///
|
||||
/// This trait is not intended to be used directly, just to supply a custom rand implementation to smoltcp.
|
||||
#[cfg(feature = "rand-custom-impl")]
|
||||
pub trait Rand {
|
||||
/// Fill `buf` with random bytes.
|
||||
fn rand_bytes(buf: &mut [u8]);
|
||||
}
|
||||
|
||||
/// Set the custom rand implementation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// struct Rand;
|
||||
/// smoltcp::rand_custom_impl!(Rand);
|
||||
/// impl smoltcp::Rand for Rand {
|
||||
/// fn rand_bytes(buf: &mut [u8]) {
|
||||
/// // TODO
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
#[macro_export]
|
||||
#[cfg(feature = "rand-custom-impl")]
|
||||
macro_rules! rand_custom_impl {
|
||||
($t: ty) => {
|
||||
#[no_mangle]
|
||||
fn _smoltcp_rand(buf: &mut [u8]) {
|
||||
<$t as $crate::Rand>::rand_bytes(buf)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(feature = "rand-custom-impl"), not(test)))]
|
||||
#[no_mangle]
|
||||
fn _smoltcp_rand(buf: &mut [u8]) {
|
||||
use rand_core::RngCore;
|
||||
|
||||
rand_core::OsRng.fill_bytes(buf)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[no_mangle]
|
||||
fn _smoltcp_rand(buf: &mut [u8]) {
|
||||
panic!("Rand should not be used when testing");
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,13 @@
|
|||
use crate::wire::IpAddress;
|
||||
use crate::socket::{SocketHandle, PollAt};
|
||||
use crate::socket::{PollAt, SocketHandle};
|
||||
use crate::time::{Duration, Instant};
|
||||
use crate::wire::IpAddress;
|
||||
|
||||
/// Neighbor dependency.
|
||||
///
|
||||
/// This enum tracks whether the socket should be polled based on the neighbor it is
|
||||
/// going to send packets to.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
enum NeighborState {
|
||||
/// Socket can be polled immediately.
|
||||
Active,
|
||||
|
@ -15,7 +16,7 @@ enum NeighborState {
|
|||
Waiting {
|
||||
neighbor: IpAddress,
|
||||
silent_until: Instant,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for NeighborState {
|
||||
|
@ -29,12 +30,13 @@ impl Default for NeighborState {
|
|||
/// This includes things that only external (to the socket, that is) code
|
||||
/// is interested in, but which are more conveniently stored inside the socket itself.
|
||||
#[derive(Debug, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Meta {
|
||||
/// Handle of this socket within its enclosing `SocketSet`.
|
||||
/// Mainly useful for debug output.
|
||||
pub(crate) handle: SocketHandle,
|
||||
/// See [NeighborState](struct.NeighborState.html).
|
||||
neighbor_state: NeighborState,
|
||||
neighbor_state: NeighborState,
|
||||
}
|
||||
|
||||
impl Meta {
|
||||
|
@ -42,36 +44,43 @@ impl Meta {
|
|||
/// in milliseconds.
|
||||
///
|
||||
/// See also `iface::NeighborCache::SILENT_TIME`.
|
||||
pub(crate) const DISCOVERY_SILENT_TIME: Duration = Duration { millis: 3_000 };
|
||||
pub(crate) const DISCOVERY_SILENT_TIME: Duration = Duration::from_millis(3_000);
|
||||
|
||||
pub(crate) fn poll_at<F>(&self, socket_poll_at: PollAt, has_neighbor: F) -> PollAt
|
||||
where F: Fn(IpAddress) -> bool
|
||||
pub(crate) fn poll_at<F>(&self, socket_poll_at: PollAt, has_neighbor: F) -> PollAt
|
||||
where
|
||||
F: Fn(IpAddress) -> bool,
|
||||
{
|
||||
match self.neighbor_state {
|
||||
NeighborState::Active =>
|
||||
socket_poll_at,
|
||||
NeighborState::Waiting { neighbor, .. }
|
||||
if has_neighbor(neighbor) =>
|
||||
socket_poll_at,
|
||||
NeighborState::Waiting { silent_until, .. } =>
|
||||
PollAt::Time(silent_until)
|
||||
NeighborState::Active => socket_poll_at,
|
||||
NeighborState::Waiting { neighbor, .. } if has_neighbor(neighbor) => socket_poll_at,
|
||||
NeighborState::Waiting { silent_until, .. } => PollAt::Time(silent_until),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn egress_permitted<F>(&mut self, timestamp: Instant, has_neighbor: F) -> bool
|
||||
where F: Fn(IpAddress) -> bool
|
||||
where
|
||||
F: Fn(IpAddress) -> bool,
|
||||
{
|
||||
match self.neighbor_state {
|
||||
NeighborState::Active =>
|
||||
true,
|
||||
NeighborState::Waiting { neighbor, silent_until } => {
|
||||
NeighborState::Active => true,
|
||||
NeighborState::Waiting {
|
||||
neighbor,
|
||||
silent_until,
|
||||
} => {
|
||||
if has_neighbor(neighbor) {
|
||||
net_trace!("{}: neighbor {} discovered, unsilencing",
|
||||
self.handle, neighbor);
|
||||
net_trace!(
|
||||
"{}: neighbor {} discovered, unsilencing",
|
||||
self.handle,
|
||||
neighbor
|
||||
);
|
||||
self.neighbor_state = NeighborState::Active;
|
||||
true
|
||||
} else if timestamp >= silent_until {
|
||||
net_trace!("{}: neighbor {} silence timer expired, rediscovering", self.handle, neighbor);
|
||||
net_trace!(
|
||||
"{}: neighbor {} silence timer expired, rediscovering",
|
||||
self.handle,
|
||||
neighbor
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -81,10 +90,15 @@ impl Meta {
|
|||
}
|
||||
|
||||
pub(crate) fn neighbor_missing(&mut self, timestamp: Instant, neighbor: IpAddress) {
|
||||
net_trace!("{}: neighbor {} missing, silencing until t+{}",
|
||||
self.handle, neighbor, Self::DISCOVERY_SILENT_TIME);
|
||||
net_trace!(
|
||||
"{}: neighbor {} missing, silencing until t+{}",
|
||||
self.handle,
|
||||
neighbor,
|
||||
Self::DISCOVERY_SILENT_TIME
|
||||
);
|
||||
self.neighbor_state = NeighborState::Waiting {
|
||||
neighbor, silent_until: timestamp + Self::DISCOVERY_SILENT_TIME
|
||||
neighbor,
|
||||
silent_until: timestamp + Self::DISCOVERY_SILENT_TIME,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,19 +11,24 @@ The interface implemented by this module uses explicit buffering: you decide on
|
|||
size for a buffer, allocate it, and let the networking stack use it.
|
||||
*/
|
||||
|
||||
use crate::phy::DeviceCapabilities;
|
||||
use crate::time::Instant;
|
||||
|
||||
#[cfg(feature = "socket-dhcpv4")]
|
||||
mod dhcpv4;
|
||||
#[cfg(all(
|
||||
feature = "socket-icmp",
|
||||
any(feature = "proto-ipv4", feature = "proto-ipv6")
|
||||
))]
|
||||
mod icmp;
|
||||
mod meta;
|
||||
#[cfg(feature = "socket-raw")]
|
||||
mod raw;
|
||||
#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
|
||||
mod icmp;
|
||||
#[cfg(feature = "socket-udp")]
|
||||
mod udp;
|
||||
mod set;
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
mod tcp;
|
||||
mod set;
|
||||
mod ref_;
|
||||
#[cfg(feature = "socket-udp")]
|
||||
mod udp;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
mod waker;
|
||||
|
@ -33,34 +38,29 @@ pub(crate) use self::meta::Meta as SocketMeta;
|
|||
pub(crate) use self::waker::WakerRegistration;
|
||||
|
||||
#[cfg(feature = "socket-raw")]
|
||||
pub use self::raw::{RawPacketMetadata,
|
||||
RawSocketBuffer,
|
||||
RawSocket};
|
||||
pub use self::raw::{RawPacketMetadata, RawSocket, RawSocketBuffer};
|
||||
|
||||
#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
|
||||
pub use self::icmp::{IcmpPacketMetadata,
|
||||
IcmpSocketBuffer,
|
||||
Endpoint as IcmpEndpoint,
|
||||
IcmpSocket};
|
||||
#[cfg(all(
|
||||
feature = "socket-icmp",
|
||||
any(feature = "proto-ipv4", feature = "proto-ipv6")
|
||||
))]
|
||||
pub use self::icmp::{Endpoint as IcmpEndpoint, IcmpPacketMetadata, IcmpSocket, IcmpSocketBuffer};
|
||||
|
||||
#[cfg(feature = "socket-udp")]
|
||||
pub use self::udp::{UdpPacketMetadata,
|
||||
UdpSocketBuffer,
|
||||
UdpSocket};
|
||||
pub use self::udp::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer};
|
||||
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
pub use self::tcp::{SocketBuffer as TcpSocketBuffer,
|
||||
State as TcpState,
|
||||
TcpSocket};
|
||||
pub use self::tcp::{SocketBuffer as TcpSocketBuffer, State as TcpState, TcpSocket};
|
||||
|
||||
pub use self::set::{Set as SocketSet, Item as SocketSetItem, Handle as SocketHandle};
|
||||
#[cfg(feature = "socket-dhcpv4")]
|
||||
pub use self::dhcpv4::{Config as Dhcpv4Config, Dhcpv4Socket, Event as Dhcpv4Event};
|
||||
|
||||
pub use self::set::{Handle as SocketHandle, Item as SocketSetItem, Set as SocketSet};
|
||||
pub use self::set::{Iter as SocketSetIter, IterMut as SocketSetIterMut};
|
||||
|
||||
pub use self::ref_::Ref as SocketRef;
|
||||
pub(crate) use self::ref_::Session as SocketSession;
|
||||
|
||||
/// Gives an indication on the next time the socket should be polled.
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub(crate) enum PollAt {
|
||||
/// The socket needs to be polled immidiately.
|
||||
Now,
|
||||
|
@ -84,20 +84,25 @@ pub(crate) enum PollAt {
|
|||
pub enum Socket<'a> {
|
||||
#[cfg(feature = "socket-raw")]
|
||||
Raw(RawSocket<'a>),
|
||||
#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
|
||||
#[cfg(all(
|
||||
feature = "socket-icmp",
|
||||
any(feature = "proto-ipv4", feature = "proto-ipv6")
|
||||
))]
|
||||
Icmp(IcmpSocket<'a>),
|
||||
#[cfg(feature = "socket-udp")]
|
||||
Udp(UdpSocket<'a>),
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
Tcp(TcpSocket<'a>),
|
||||
#[cfg(feature = "socket-dhcpv4")]
|
||||
Dhcpv4(Dhcpv4Socket),
|
||||
}
|
||||
|
||||
macro_rules! dispatch_socket {
|
||||
($self_:expr, |$socket:ident| $code:expr) => {
|
||||
dispatch_socket!(@inner $self_, |$socket| $code);
|
||||
dispatch_socket!(@inner $self_, |$socket| $code)
|
||||
};
|
||||
(mut $self_:expr, |$socket:ident| $code:expr) => {
|
||||
dispatch_socket!(@inner mut $self_, |$socket| $code);
|
||||
dispatch_socket!(@inner mut $self_, |$socket| $code)
|
||||
};
|
||||
(@inner $( $mut_:ident )* $self_:expr, |$socket:ident| $code:expr) => {
|
||||
match $self_ {
|
||||
|
@ -109,6 +114,8 @@ macro_rules! dispatch_socket {
|
|||
&$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code,
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
&$( $mut_ )* Socket::Tcp(ref $( $mut_ )* $socket) => $code,
|
||||
#[cfg(feature = "socket-dhcpv4")]
|
||||
&$( $mut_ )* Socket::Dhcpv4(ref $( $mut_ )* $socket) => $code,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -128,43 +135,91 @@ impl<'a> Socket<'a> {
|
|||
dispatch_socket!(mut self, |socket| &mut socket.meta)
|
||||
}
|
||||
|
||||
pub(crate) fn poll_at(&self) -> PollAt {
|
||||
dispatch_socket!(self, |socket| socket.poll_at())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SocketSession for Socket<'a> {
|
||||
fn finish(&mut self) {
|
||||
dispatch_socket!(mut self, |socket| socket.finish())
|
||||
pub(crate) fn poll_at(&self, cx: &Context) -> PollAt {
|
||||
dispatch_socket!(self, |socket| socket.poll_at(cx))
|
||||
}
|
||||
}
|
||||
|
||||
/// A conversion trait for network sockets.
|
||||
pub trait AnySocket<'a>: SocketSession + Sized {
|
||||
fn downcast<'c>(socket_ref: SocketRef<'c, Socket<'a>>) ->
|
||||
Option<SocketRef<'c, Self>>;
|
||||
pub trait AnySocket<'a>: Sized {
|
||||
fn downcast<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self>;
|
||||
}
|
||||
|
||||
macro_rules! from_socket {
|
||||
($socket:ty, $variant:ident) => {
|
||||
impl<'a> AnySocket<'a> for $socket {
|
||||
fn downcast<'c>(ref_: SocketRef<'c, Socket<'a>>) ->
|
||||
Option<SocketRef<'c, Self>> {
|
||||
if let Socket::$variant(ref mut socket) = SocketRef::into_inner(ref_) {
|
||||
Some(SocketRef::new(socket))
|
||||
} else {
|
||||
None
|
||||
fn downcast<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self> {
|
||||
#[allow(unreachable_patterns)]
|
||||
match socket {
|
||||
Socket::$variant(socket) => Some(socket),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "socket-raw")]
|
||||
from_socket!(RawSocket<'a>, Raw);
|
||||
#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
|
||||
#[cfg(all(
|
||||
feature = "socket-icmp",
|
||||
any(feature = "proto-ipv4", feature = "proto-ipv6")
|
||||
))]
|
||||
from_socket!(IcmpSocket<'a>, Icmp);
|
||||
#[cfg(feature = "socket-udp")]
|
||||
from_socket!(UdpSocket<'a>, Udp);
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
from_socket!(TcpSocket<'a>, Tcp);
|
||||
#[cfg(feature = "socket-dhcpv4")]
|
||||
from_socket!(Dhcpv4Socket, Dhcpv4);
|
||||
|
||||
/// Data passed to sockets when processing.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Context {
|
||||
pub now: Instant,
|
||||
#[cfg(all(
|
||||
any(feature = "medium-ethernet", feature = "medium-ieee802154"),
|
||||
feature = "socket-dhcpv4"
|
||||
))]
|
||||
pub hardware_addr: Option<crate::wire::HardwareAddress>,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
pub pan_id: Option<crate::wire::Ieee802154Pan>,
|
||||
pub caps: DeviceCapabilities,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Context {
|
||||
pub(crate) const DUMMY: Context = Context {
|
||||
caps: DeviceCapabilities {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
medium: crate::phy::Medium::Ethernet,
|
||||
#[cfg(not(feature = "medium-ethernet"))]
|
||||
medium: crate::phy::Medium::Ip,
|
||||
checksum: crate::phy::ChecksumCapabilities {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
icmpv4: crate::phy::Checksum::Both,
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
icmpv6: crate::phy::Checksum::Both,
|
||||
ipv4: crate::phy::Checksum::Both,
|
||||
tcp: crate::phy::Checksum::Both,
|
||||
udp: crate::phy::Checksum::Both,
|
||||
},
|
||||
max_burst_size: None,
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
max_transmission_unit: 1514,
|
||||
#[cfg(not(feature = "medium-ethernet"))]
|
||||
max_transmission_unit: 1500,
|
||||
},
|
||||
#[cfg(all(
|
||||
any(feature = "medium-ethernet", feature = "medium-ieee802154"),
|
||||
feature = "socket-dhcpv4"
|
||||
))]
|
||||
hardware_addr: Some(crate::wire::HardwareAddress::Ethernet(
|
||||
crate::wire::EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]),
|
||||
)),
|
||||
now: Instant::from_millis_const(0),
|
||||
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
pan_id: Some(crate::wire::Ieee802154Pan(0xabcd)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,18 +2,18 @@ use core::cmp::min;
|
|||
#[cfg(feature = "async")]
|
||||
use core::task::Waker;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::socket::{Socket, SocketMeta, SocketHandle, PollAt};
|
||||
use crate::storage::{PacketBuffer, PacketMetadata};
|
||||
#[cfg(feature = "async")]
|
||||
use crate::socket::WakerRegistration;
|
||||
use crate::socket::{Context, PollAt, Socket, SocketHandle, SocketMeta};
|
||||
use crate::storage::{PacketBuffer, PacketMetadata};
|
||||
use crate::{Error, Result};
|
||||
|
||||
use crate::wire::{IpVersion, IpRepr, IpProtocol};
|
||||
use crate::wire::{IpProtocol, IpRepr, IpVersion};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::{Ipv4Repr, Ipv4Packet};
|
||||
use crate::wire::{Ipv4Packet, Ipv4Repr};
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
use crate::wire::{Ipv6Repr, Ipv6Packet};
|
||||
use crate::wire::{Ipv6Packet, Ipv6Repr};
|
||||
|
||||
/// A UDP packet metadata.
|
||||
pub type RawPacketMetadata = PacketMetadata<()>;
|
||||
|
@ -28,10 +28,10 @@ pub type RawSocketBuffer<'a> = PacketBuffer<'a, ()>;
|
|||
#[derive(Debug)]
|
||||
pub struct RawSocket<'a> {
|
||||
pub(crate) meta: SocketMeta,
|
||||
ip_version: IpVersion,
|
||||
ip_version: IpVersion,
|
||||
ip_protocol: IpProtocol,
|
||||
rx_buffer: RawSocketBuffer<'a>,
|
||||
tx_buffer: RawSocketBuffer<'a>,
|
||||
rx_buffer: RawSocketBuffer<'a>,
|
||||
tx_buffer: RawSocketBuffer<'a>,
|
||||
#[cfg(feature = "async")]
|
||||
rx_waker: WakerRegistration,
|
||||
#[cfg(feature = "async")]
|
||||
|
@ -41,9 +41,12 @@ pub struct RawSocket<'a> {
|
|||
impl<'a> RawSocket<'a> {
|
||||
/// Create a raw IP socket bound to the given IP version and datagram protocol,
|
||||
/// with the given buffers.
|
||||
pub fn new(ip_version: IpVersion, ip_protocol: IpProtocol,
|
||||
rx_buffer: RawSocketBuffer<'a>,
|
||||
tx_buffer: RawSocketBuffer<'a>) -> RawSocket<'a> {
|
||||
pub fn new(
|
||||
ip_version: IpVersion,
|
||||
ip_protocol: IpProtocol,
|
||||
rx_buffer: RawSocketBuffer<'a>,
|
||||
tx_buffer: RawSocketBuffer<'a>,
|
||||
) -> RawSocket<'a> {
|
||||
RawSocket {
|
||||
meta: SocketMeta::default(),
|
||||
ip_version,
|
||||
|
@ -61,12 +64,12 @@ impl<'a> RawSocket<'a> {
|
|||
///
|
||||
/// The waker is woken on state changes that might affect the return value
|
||||
/// of `recv` method calls, such as receiving data, or the socket closing.
|
||||
///
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// - Only one waker can be registered at a time. If another waker was previously registered,
|
||||
/// it is overwritten and will no longer be woken.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
|
||||
/// necessarily changed.
|
||||
#[cfg(feature = "async")]
|
||||
|
@ -79,12 +82,12 @@ impl<'a> RawSocket<'a> {
|
|||
/// The waker is woken on state changes that might affect the return value
|
||||
/// of `send` method calls, such as space becoming available in the transmit
|
||||
/// buffer, or the socket closing.
|
||||
///
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// - Only one waker can be registered at a time. If another waker was previously registered,
|
||||
/// it is overwritten and will no longer be woken.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
|
||||
/// necessarily changed.
|
||||
#[cfg(feature = "async")]
|
||||
|
@ -160,9 +163,13 @@ impl<'a> RawSocket<'a> {
|
|||
pub fn send(&mut self, size: usize) -> Result<&mut [u8]> {
|
||||
let packet_buf = self.tx_buffer.enqueue(size, ())?;
|
||||
|
||||
net_trace!("{}:{}:{}: buffer to send {} octets",
|
||||
self.meta.handle, self.ip_version, self.ip_protocol,
|
||||
packet_buf.len());
|
||||
net_trace!(
|
||||
"{}:{}:{}: buffer to send {} octets",
|
||||
self.meta.handle,
|
||||
self.ip_version,
|
||||
self.ip_protocol,
|
||||
packet_buf.len()
|
||||
);
|
||||
Ok(packet_buf)
|
||||
}
|
||||
|
||||
|
@ -183,9 +190,13 @@ impl<'a> RawSocket<'a> {
|
|||
pub fn recv(&mut self) -> Result<&[u8]> {
|
||||
let ((), packet_buf) = self.rx_buffer.dequeue()?;
|
||||
|
||||
net_trace!("{}:{}:{}: receive {} buffered octets",
|
||||
self.meta.handle, self.ip_version, self.ip_protocol,
|
||||
packet_buf.len());
|
||||
net_trace!(
|
||||
"{}:{}:{}: receive {} buffered octets",
|
||||
self.meta.handle,
|
||||
self.ip_version,
|
||||
self.ip_protocol,
|
||||
packet_buf.len()
|
||||
);
|
||||
Ok(packet_buf)
|
||||
}
|
||||
|
||||
|
@ -200,25 +211,32 @@ impl<'a> RawSocket<'a> {
|
|||
}
|
||||
|
||||
pub(crate) fn accepts(&self, ip_repr: &IpRepr) -> bool {
|
||||
if ip_repr.version() != self.ip_version { return false }
|
||||
if ip_repr.protocol() != self.ip_protocol { return false }
|
||||
if ip_repr.version() != self.ip_version {
|
||||
return false;
|
||||
}
|
||||
if ip_repr.protocol() != self.ip_protocol {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn process(&mut self, ip_repr: &IpRepr, payload: &[u8],
|
||||
checksum_caps: &ChecksumCapabilities) -> Result<()> {
|
||||
pub(crate) fn process(&mut self, cx: &Context, ip_repr: &IpRepr, payload: &[u8]) -> Result<()> {
|
||||
debug_assert!(self.accepts(ip_repr));
|
||||
|
||||
let header_len = ip_repr.buffer_len();
|
||||
let total_len = header_len + payload.len();
|
||||
let total_len = header_len + payload.len();
|
||||
let packet_buf = self.rx_buffer.enqueue(total_len, ())?;
|
||||
ip_repr.emit(&mut packet_buf[..header_len], &checksum_caps);
|
||||
ip_repr.emit(&mut packet_buf[..header_len], &cx.caps.checksum);
|
||||
packet_buf[header_len..].copy_from_slice(payload);
|
||||
|
||||
net_trace!("{}:{}:{}: receiving {} octets",
|
||||
self.meta.handle, self.ip_version, self.ip_protocol,
|
||||
packet_buf.len());
|
||||
net_trace!(
|
||||
"{}:{}:{}: receiving {} octets",
|
||||
self.meta.handle,
|
||||
self.ip_version,
|
||||
self.ip_protocol,
|
||||
packet_buf.len()
|
||||
);
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
self.rx_waker.wake();
|
||||
|
@ -226,16 +244,22 @@ impl<'a> RawSocket<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch<F>(&mut self, checksum_caps: &ChecksumCapabilities, emit: F) ->
|
||||
Result<()>
|
||||
where F: FnOnce((IpRepr, &[u8])) -> Result<()> {
|
||||
fn prepare<'a>(protocol: IpProtocol, buffer: &'a mut [u8],
|
||||
_checksum_caps: &ChecksumCapabilities) -> Result<(IpRepr, &'a [u8])> {
|
||||
pub(crate) fn dispatch<F>(&mut self, cx: &Context, emit: F) -> Result<()>
|
||||
where
|
||||
F: FnOnce((IpRepr, &[u8])) -> Result<()>,
|
||||
{
|
||||
fn prepare<'a>(
|
||||
protocol: IpProtocol,
|
||||
buffer: &'a mut [u8],
|
||||
_checksum_caps: &ChecksumCapabilities,
|
||||
) -> Result<(IpRepr, &'a [u8])> {
|
||||
match IpVersion::of_packet(buffer)? {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
IpVersion::Ipv4 => {
|
||||
let mut packet = Ipv4Packet::new_checked(buffer)?;
|
||||
if packet.protocol() != protocol { return Err(Error::Unaddressable) }
|
||||
if packet.protocol() != protocol {
|
||||
return Err(Error::Unaddressable);
|
||||
}
|
||||
if _checksum_caps.ipv4.tx() {
|
||||
packet.fill_checksum();
|
||||
} else {
|
||||
|
@ -251,7 +275,9 @@ impl<'a> RawSocket<'a> {
|
|||
#[cfg(feature = "proto-ipv6")]
|
||||
IpVersion::Ipv6 => {
|
||||
let packet = Ipv6Packet::new_checked(buffer)?;
|
||||
if packet.next_header() != protocol { return Err(Error::Unaddressable) }
|
||||
if packet.next_header() != protocol {
|
||||
return Err(Error::Unaddressable);
|
||||
}
|
||||
let packet = Ipv6Packet::new_unchecked(&*packet.into_inner());
|
||||
let ipv6_repr = Ipv6Repr::parse(&packet)?;
|
||||
Ok((IpRepr::Ipv6(ipv6_repr), packet.payload()))
|
||||
|
@ -260,21 +286,29 @@ impl<'a> RawSocket<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
let handle = self.meta.handle;
|
||||
let handle = self.meta.handle;
|
||||
let ip_protocol = self.ip_protocol;
|
||||
let ip_version = self.ip_version;
|
||||
let ip_version = self.ip_version;
|
||||
self.tx_buffer.dequeue_with(|&mut (), packet_buf| {
|
||||
match prepare(ip_protocol, packet_buf, &checksum_caps) {
|
||||
match prepare(ip_protocol, packet_buf, &cx.caps.checksum) {
|
||||
Ok((ip_repr, raw_packet)) => {
|
||||
net_trace!("{}:{}:{}: sending {} octets",
|
||||
handle, ip_version, ip_protocol,
|
||||
ip_repr.buffer_len() + raw_packet.len());
|
||||
net_trace!(
|
||||
"{}:{}:{}: sending {} octets",
|
||||
handle,
|
||||
ip_version,
|
||||
ip_protocol,
|
||||
ip_repr.buffer_len() + raw_packet.len()
|
||||
);
|
||||
emit((ip_repr, raw_packet))
|
||||
}
|
||||
Err(error) => {
|
||||
net_debug!("{}:{}:{}: dropping outgoing packet ({})",
|
||||
handle, ip_version, ip_protocol,
|
||||
error);
|
||||
net_debug!(
|
||||
"{}:{}:{}: dropping outgoing packet ({})",
|
||||
handle,
|
||||
ip_version,
|
||||
ip_protocol,
|
||||
error
|
||||
);
|
||||
// Return Ok(()) so the packet is dequeued.
|
||||
Ok(())
|
||||
}
|
||||
|
@ -287,7 +321,7 @@ impl<'a> RawSocket<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn poll_at(&self) -> PollAt {
|
||||
pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt {
|
||||
if self.tx_buffer.is_empty() {
|
||||
PollAt::Ingress
|
||||
} else {
|
||||
|
@ -296,34 +330,42 @@ impl<'a> RawSocket<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Socket<'a>> for RawSocket<'a> {
|
||||
fn into(self) -> Socket<'a> {
|
||||
Socket::Raw(self)
|
||||
impl<'a> From<RawSocket<'a>> for Socket<'a> {
|
||||
fn from(val: RawSocket<'a>) -> Self {
|
||||
Socket::Raw(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::wire::IpRepr;
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::{Ipv4Address, Ipv4Repr};
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
use crate::wire::{Ipv6Address, Ipv6Repr};
|
||||
use super::*;
|
||||
|
||||
fn buffer(packets: usize) -> RawSocketBuffer<'static> {
|
||||
RawSocketBuffer::new(vec![RawPacketMetadata::EMPTY; packets], vec![0; 48 * packets])
|
||||
RawSocketBuffer::new(
|
||||
vec![RawPacketMetadata::EMPTY; packets],
|
||||
vec![0; 48 * packets],
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
mod ipv4_locals {
|
||||
use super::*;
|
||||
|
||||
pub fn socket(rx_buffer: RawSocketBuffer<'static>,
|
||||
tx_buffer: RawSocketBuffer<'static>)
|
||||
-> RawSocket<'static> {
|
||||
RawSocket::new(IpVersion::Ipv4, IpProtocol::Unknown(IP_PROTO),
|
||||
rx_buffer, tx_buffer)
|
||||
pub fn socket(
|
||||
rx_buffer: RawSocketBuffer<'static>,
|
||||
tx_buffer: RawSocketBuffer<'static>,
|
||||
) -> RawSocket<'static> {
|
||||
RawSocket::new(
|
||||
IpVersion::Ipv4,
|
||||
IpProtocol::Unknown(IP_PROTO),
|
||||
rx_buffer,
|
||||
tx_buffer,
|
||||
)
|
||||
}
|
||||
|
||||
pub const IP_PROTO: u8 = 63;
|
||||
|
@ -332,60 +374,54 @@ mod test {
|
|||
dst_addr: Ipv4Address([10, 0, 0, 2]),
|
||||
protocol: IpProtocol::Unknown(IP_PROTO),
|
||||
payload_len: 4,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
});
|
||||
pub const PACKET_BYTES: [u8; 24] = [
|
||||
0x45, 0x00, 0x00, 0x18,
|
||||
0x00, 0x00, 0x40, 0x00,
|
||||
0x40, 0x3f, 0x00, 0x00,
|
||||
0x0a, 0x00, 0x00, 0x01,
|
||||
0x0a, 0x00, 0x00, 0x02,
|
||||
0xaa, 0x00, 0x00, 0xff
|
||||
];
|
||||
pub const PACKET_PAYLOAD: [u8; 4] = [
|
||||
0xaa, 0x00, 0x00, 0xff
|
||||
0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x3f, 0x00, 0x00, 0x0a, 0x00,
|
||||
0x00, 0x01, 0x0a, 0x00, 0x00, 0x02, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
pub const PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod ipv6_locals {
|
||||
use super::*;
|
||||
|
||||
pub fn socket(rx_buffer: RawSocketBuffer<'static>,
|
||||
tx_buffer: RawSocketBuffer<'static>)
|
||||
-> RawSocket<'static> {
|
||||
RawSocket::new(IpVersion::Ipv6, IpProtocol::Unknown(IP_PROTO),
|
||||
rx_buffer, tx_buffer)
|
||||
pub fn socket(
|
||||
rx_buffer: RawSocketBuffer<'static>,
|
||||
tx_buffer: RawSocketBuffer<'static>,
|
||||
) -> RawSocket<'static> {
|
||||
RawSocket::new(
|
||||
IpVersion::Ipv6,
|
||||
IpProtocol::Unknown(IP_PROTO),
|
||||
rx_buffer,
|
||||
tx_buffer,
|
||||
)
|
||||
}
|
||||
|
||||
pub const IP_PROTO: u8 = 63;
|
||||
pub const HEADER_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr {
|
||||
src_addr: Ipv6Address([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]),
|
||||
dst_addr: Ipv6Address([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]),
|
||||
src_addr: Ipv6Address([
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01,
|
||||
]),
|
||||
dst_addr: Ipv6Address([
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02,
|
||||
]),
|
||||
next_header: IpProtocol::Unknown(IP_PROTO),
|
||||
payload_len: 4,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
});
|
||||
|
||||
pub const PACKET_BYTES: [u8; 44] = [
|
||||
0x60, 0x00, 0x00, 0x00,
|
||||
0x00, 0x04, 0x3f, 0x40,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0xaa, 0x00, 0x00, 0xff
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3f, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0x00,
|
||||
0x00, 0xff,
|
||||
];
|
||||
|
||||
pub const PACKET_PAYLOAD: [u8; 4] = [
|
||||
0xaa, 0x00, 0x00, 0xff
|
||||
];
|
||||
pub const PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
|
||||
}
|
||||
|
||||
macro_rules! reusable_ip_specific_tests {
|
||||
|
@ -401,29 +437,36 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_send_dispatch() {
|
||||
let checksum_caps = &ChecksumCapabilities::default();
|
||||
let mut socket = $socket(buffer(0), buffer(1));
|
||||
|
||||
assert!(socket.can_send());
|
||||
assert_eq!(socket.dispatch(&checksum_caps, |_| unreachable!()),
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
socket.dispatch(&Context::DUMMY, |_| unreachable!()),
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
|
||||
assert_eq!(socket.send_slice(&$packet[..]), Ok(()));
|
||||
assert_eq!(socket.send_slice(b""), Err(Error::Exhausted));
|
||||
assert!(!socket.can_send());
|
||||
|
||||
assert_eq!(socket.dispatch(&checksum_caps, |(ip_repr, ip_payload)| {
|
||||
assert_eq!(ip_repr, $hdr);
|
||||
assert_eq!(ip_payload, &$payload);
|
||||
assert_eq!(
|
||||
socket.dispatch(&Context::DUMMY, |(ip_repr, ip_payload)| {
|
||||
assert_eq!(ip_repr, $hdr);
|
||||
assert_eq!(ip_payload, &$payload);
|
||||
Err(Error::Unaddressable)
|
||||
}),
|
||||
Err(Error::Unaddressable)
|
||||
}), Err(Error::Unaddressable));
|
||||
);
|
||||
assert!(!socket.can_send());
|
||||
|
||||
assert_eq!(socket.dispatch(&checksum_caps, |(ip_repr, ip_payload)| {
|
||||
assert_eq!(ip_repr, $hdr);
|
||||
assert_eq!(ip_payload, &$payload);
|
||||
assert_eq!(
|
||||
socket.dispatch(&Context::DUMMY, |(ip_repr, ip_payload)| {
|
||||
assert_eq!(ip_repr, $hdr);
|
||||
assert_eq!(ip_payload, &$payload);
|
||||
Ok(())
|
||||
}),
|
||||
Ok(())
|
||||
}), Ok(()));
|
||||
);
|
||||
assert!(socket.can_send());
|
||||
}
|
||||
|
||||
|
@ -432,8 +475,7 @@ mod test {
|
|||
let mut socket = $socket(buffer(1), buffer(0));
|
||||
|
||||
assert!(socket.accepts(&$hdr));
|
||||
assert_eq!(socket.process(&$hdr, &$payload,
|
||||
&ChecksumCapabilities::default()), Ok(()));
|
||||
assert_eq!(socket.process(&Context::DUMMY, &$hdr, &$payload), Ok(()));
|
||||
|
||||
let mut slice = [0; 4];
|
||||
assert_eq!(socket.recv_slice(&mut slice[..]), Ok(4));
|
||||
|
@ -448,26 +490,36 @@ mod test {
|
|||
buffer[..$packet.len()].copy_from_slice(&$packet[..]);
|
||||
|
||||
assert!(socket.accepts(&$hdr));
|
||||
assert_eq!(socket.process(&$hdr, &buffer, &ChecksumCapabilities::default()),
|
||||
Err(Error::Truncated));
|
||||
assert_eq!(
|
||||
socket.process(&Context::DUMMY, &$hdr, &buffer),
|
||||
Err(Error::Truncated)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
reusable_ip_specific_tests!(ipv4, ipv4_locals::socket, ipv4_locals::HEADER_REPR,
|
||||
ipv4_locals::PACKET_BYTES, ipv4_locals::PACKET_PAYLOAD);
|
||||
reusable_ip_specific_tests!(
|
||||
ipv4,
|
||||
ipv4_locals::socket,
|
||||
ipv4_locals::HEADER_REPR,
|
||||
ipv4_locals::PACKET_BYTES,
|
||||
ipv4_locals::PACKET_PAYLOAD
|
||||
);
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
reusable_ip_specific_tests!(ipv6, ipv6_locals::socket, ipv6_locals::HEADER_REPR,
|
||||
ipv6_locals::PACKET_BYTES, ipv6_locals::PACKET_PAYLOAD);
|
||||
|
||||
reusable_ip_specific_tests!(
|
||||
ipv6,
|
||||
ipv6_locals::socket,
|
||||
ipv6_locals::HEADER_REPR,
|
||||
ipv6_locals::PACKET_BYTES,
|
||||
ipv6_locals::PACKET_PAYLOAD
|
||||
);
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_send_illegal() {
|
||||
let checksum_caps = &ChecksumCapabilities::default();
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
{
|
||||
let mut socket = ipv4_locals::socket(buffer(0), buffer(2));
|
||||
|
@ -476,15 +528,13 @@ mod test {
|
|||
Ipv4Packet::new_unchecked(&mut wrong_version).set_version(6);
|
||||
|
||||
assert_eq!(socket.send_slice(&wrong_version[..]), Ok(()));
|
||||
assert_eq!(socket.dispatch(&checksum_caps, |_| unreachable!()),
|
||||
Ok(()));
|
||||
assert_eq!(socket.dispatch(&Context::DUMMY, |_| unreachable!()), Ok(()));
|
||||
|
||||
let mut wrong_protocol = ipv4_locals::PACKET_BYTES;
|
||||
Ipv4Packet::new_unchecked(&mut wrong_protocol).set_protocol(IpProtocol::Tcp);
|
||||
|
||||
assert_eq!(socket.send_slice(&wrong_protocol[..]), Ok(()));
|
||||
assert_eq!(socket.dispatch(&checksum_caps, |_| unreachable!()),
|
||||
Ok(()));
|
||||
assert_eq!(socket.dispatch(&Context::DUMMY, |_| unreachable!()), Ok(()));
|
||||
}
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
{
|
||||
|
@ -494,15 +544,13 @@ mod test {
|
|||
Ipv6Packet::new_unchecked(&mut wrong_version[..]).set_version(4);
|
||||
|
||||
assert_eq!(socket.send_slice(&wrong_version[..]), Ok(()));
|
||||
assert_eq!(socket.dispatch(&checksum_caps, |_| unreachable!()),
|
||||
Ok(()));
|
||||
assert_eq!(socket.dispatch(&Context::DUMMY, |_| unreachable!()), Ok(()));
|
||||
|
||||
let mut wrong_protocol = ipv6_locals::PACKET_BYTES;
|
||||
Ipv6Packet::new_unchecked(&mut wrong_protocol[..]).set_next_header(IpProtocol::Tcp);
|
||||
|
||||
assert_eq!(socket.send_slice(&wrong_protocol[..]), Ok(()));
|
||||
assert_eq!(socket.dispatch(&checksum_caps, |_| unreachable!()),
|
||||
Ok(()));
|
||||
assert_eq!(socket.dispatch(&Context::DUMMY, |_| unreachable!()), Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,15 +566,25 @@ mod test {
|
|||
|
||||
assert_eq!(socket.recv(), Err(Error::Exhausted));
|
||||
assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
|
||||
assert_eq!(socket.process(&ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD,
|
||||
&ChecksumCapabilities::default()),
|
||||
Ok(()));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&ipv4_locals::HEADER_REPR,
|
||||
&ipv4_locals::PACKET_PAYLOAD
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
assert!(socket.can_recv());
|
||||
|
||||
assert!(socket.accepts(&ipv4_locals::HEADER_REPR));
|
||||
assert_eq!(socket.process(&ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD,
|
||||
&ChecksumCapabilities::default()),
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&ipv4_locals::HEADER_REPR,
|
||||
&ipv4_locals::PACKET_PAYLOAD
|
||||
),
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
assert_eq!(socket.recv(), Ok(&cksumd_packet[..]));
|
||||
assert!(!socket.can_recv());
|
||||
}
|
||||
|
@ -537,15 +595,25 @@ mod test {
|
|||
|
||||
assert_eq!(socket.recv(), Err(Error::Exhausted));
|
||||
assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
|
||||
assert_eq!(socket.process(&ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD,
|
||||
&ChecksumCapabilities::default()),
|
||||
Ok(()));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&ipv6_locals::HEADER_REPR,
|
||||
&ipv6_locals::PACKET_PAYLOAD
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
assert!(socket.can_recv());
|
||||
|
||||
assert!(socket.accepts(&ipv6_locals::HEADER_REPR));
|
||||
assert_eq!(socket.process(&ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD,
|
||||
&ChecksumCapabilities::default()),
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&ipv6_locals::HEADER_REPR,
|
||||
&ipv6_locals::PACKET_PAYLOAD
|
||||
),
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
assert_eq!(socket.recv(), Ok(&ipv6_locals::PACKET_BYTES[..]));
|
||||
assert!(!socket.can_recv());
|
||||
}
|
||||
|
@ -555,16 +623,24 @@ mod test {
|
|||
fn test_doesnt_accept_wrong_proto() {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
{
|
||||
let socket = RawSocket::new(IpVersion::Ipv4,
|
||||
IpProtocol::Unknown(ipv4_locals::IP_PROTO+1), buffer(1), buffer(1));
|
||||
let socket = RawSocket::new(
|
||||
IpVersion::Ipv4,
|
||||
IpProtocol::Unknown(ipv4_locals::IP_PROTO + 1),
|
||||
buffer(1),
|
||||
buffer(1),
|
||||
);
|
||||
assert!(!socket.accepts(&ipv4_locals::HEADER_REPR));
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
assert!(!socket.accepts(&ipv6_locals::HEADER_REPR));
|
||||
}
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
{
|
||||
let socket = RawSocket::new(IpVersion::Ipv6,
|
||||
IpProtocol::Unknown(ipv6_locals::IP_PROTO+1), buffer(1), buffer(1));
|
||||
let socket = RawSocket::new(
|
||||
IpVersion::Ipv6,
|
||||
IpProtocol::Unknown(ipv6_locals::IP_PROTO + 1),
|
||||
buffer(1),
|
||||
buffer(1),
|
||||
);
|
||||
assert!(!socket.accepts(&ipv6_locals::HEADER_REPR));
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
assert!(!socket.accepts(&ipv4_locals::HEADER_REPR));
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
#[cfg(feature = "socket-raw")]
|
||||
use crate::socket::RawSocket;
|
||||
#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
|
||||
use crate::socket::IcmpSocket;
|
||||
#[cfg(feature = "socket-udp")]
|
||||
use crate::socket::UdpSocket;
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
use crate::socket::TcpSocket;
|
||||
|
||||
/// A trait for tracking a socket usage session.
|
||||
///
|
||||
/// Allows implementation of custom drop logic that runs only if the socket was changed
|
||||
/// in specific ways. For example, drop logic for UDP would check if the local endpoint
|
||||
/// has changed, and if yes, notify the socket set.
|
||||
#[doc(hidden)]
|
||||
pub trait Session {
|
||||
fn finish(&mut self) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "socket-raw")]
|
||||
impl<'a> Session for RawSocket<'a> {}
|
||||
#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
|
||||
impl<'a> Session for IcmpSocket<'a> {}
|
||||
#[cfg(feature = "socket-udp")]
|
||||
impl<'a> Session for UdpSocket<'a> {}
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
impl<'a> Session for TcpSocket<'a> {}
|
||||
|
||||
/// A smart pointer to a socket.
|
||||
///
|
||||
/// Allows the network stack to efficiently determine if the socket state was changed in any way.
|
||||
pub struct Ref<'a, T: Session + 'a> {
|
||||
/// Reference to the socket.
|
||||
///
|
||||
/// This is almost always `Some` except when dropped in `into_inner` which removes the socket
|
||||
/// reference. This properly tracks the initialization state without any additional bytes as
|
||||
/// the `None` variant occupies the `0` pattern which is invalid for the reference.
|
||||
socket: Option<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Session + 'a> Ref<'a, T> {
|
||||
/// Wrap a pointer to a socket to make a smart pointer.
|
||||
///
|
||||
/// Calling this function is only necessary if your code is using [into_inner].
|
||||
///
|
||||
/// [into_inner]: #method.into_inner
|
||||
pub fn new(socket: &'a mut T) -> Self {
|
||||
Ref { socket: Some(socket) }
|
||||
}
|
||||
|
||||
/// Unwrap a smart pointer to a socket.
|
||||
///
|
||||
/// The finalization code is not run. Prompt operation of the network stack depends
|
||||
/// on wrapping the returned pointer back and dropping it.
|
||||
///
|
||||
/// Calling this function is only necessary to achieve composability if you *must*
|
||||
/// map a `&mut SocketRef<'a, XSocket>` to a `&'a mut XSocket` (note the lifetimes);
|
||||
/// be sure to call [new] afterwards.
|
||||
///
|
||||
/// [new]: #method.new
|
||||
pub fn into_inner(mut ref_: Self) -> &'a mut T {
|
||||
ref_.socket.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Session> Deref for Ref<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// Deref is only used while the socket is still in place (into inner has not been called).
|
||||
self.socket.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Session> DerefMut for Ref<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.socket.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Session> Drop for Ref<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(socket) = self.socket.take() {
|
||||
Session::finish(socket);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
use core::{fmt, slice};
|
||||
use managed::ManagedSlice;
|
||||
|
||||
use crate::socket::{Socket, SocketRef, AnySocket};
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
use crate::socket::TcpState;
|
||||
use crate::socket::{AnySocket, Socket};
|
||||
|
||||
/// An item of a socket set.
|
||||
///
|
||||
|
@ -12,11 +10,11 @@ use crate::socket::TcpState;
|
|||
#[derive(Debug)]
|
||||
pub struct Item<'a> {
|
||||
socket: Socket<'a>,
|
||||
refs: usize
|
||||
}
|
||||
|
||||
/// A handle, identifying a socket in a set.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Handle(usize);
|
||||
|
||||
impl fmt::Display for Handle {
|
||||
|
@ -30,30 +28,32 @@ impl fmt::Display for Handle {
|
|||
/// The lifetime `'a` is used when storing a `Socket<'a>`.
|
||||
#[derive(Debug)]
|
||||
pub struct Set<'a> {
|
||||
sockets: ManagedSlice<'a, Option<Item<'a>>>
|
||||
sockets: ManagedSlice<'a, Option<Item<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> Set<'a> {
|
||||
/// Create a socket set using the provided storage.
|
||||
pub fn new<SocketsT>(sockets: SocketsT) -> Set<'a>
|
||||
where SocketsT: Into<ManagedSlice<'a, Option<Item<'a>>>> {
|
||||
where
|
||||
SocketsT: Into<ManagedSlice<'a, Option<Item<'a>>>>,
|
||||
{
|
||||
let sockets = sockets.into();
|
||||
Set { sockets }
|
||||
}
|
||||
|
||||
/// Add a socket to the set with the reference count 1, and return its handle.
|
||||
/// Add a socket to the set, and return its handle.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function panics if the storage is fixed-size (not a `Vec`) and is full.
|
||||
pub fn add<T>(&mut self, socket: T) -> Handle
|
||||
where T: Into<Socket<'a>>
|
||||
where
|
||||
T: Into<Socket<'a>>,
|
||||
{
|
||||
fn put<'a>(index: usize, slot: &mut Option<Item<'a>>,
|
||||
mut socket: Socket<'a>) -> Handle {
|
||||
fn put<'a>(index: usize, slot: &mut Option<Item<'a>>, mut socket: Socket<'a>) -> Handle {
|
||||
net_trace!("[{}]: adding", index);
|
||||
let handle = Handle(index);
|
||||
socket.meta_mut().handle = handle;
|
||||
*slot = Some(Item { socket, refs: 1 });
|
||||
*slot = Some(Item { socket });
|
||||
handle
|
||||
}
|
||||
|
||||
|
@ -61,14 +61,12 @@ impl<'a> Set<'a> {
|
|||
|
||||
for (index, slot) in self.sockets.iter_mut().enumerate() {
|
||||
if slot.is_none() {
|
||||
return put(index, slot, socket)
|
||||
return put(index, slot, socket);
|
||||
}
|
||||
}
|
||||
|
||||
match self.sockets {
|
||||
ManagedSlice::Borrowed(_) => {
|
||||
panic!("adding a socket to a full SocketSet")
|
||||
}
|
||||
ManagedSlice::Borrowed(_) => panic!("adding a socket to a full SocketSet"),
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
ManagedSlice::Owned(ref mut sockets) => {
|
||||
sockets.push(None);
|
||||
|
@ -83,13 +81,12 @@ impl<'a> Set<'a> {
|
|||
/// # Panics
|
||||
/// This function may panic if the handle does not belong to this socket set
|
||||
/// or the socket has the wrong type.
|
||||
pub fn get<T: AnySocket<'a>>(&mut self, handle: Handle) -> SocketRef<T> {
|
||||
pub fn get<T: AnySocket<'a>>(&mut self, handle: Handle) -> &mut T {
|
||||
match self.sockets[handle.0].as_mut() {
|
||||
Some(item) => {
|
||||
T::downcast(SocketRef::new(&mut item.socket))
|
||||
.expect("handle refers to a socket of a wrong type")
|
||||
T::downcast(&mut item.socket).expect("handle refers to a socket of a wrong type")
|
||||
}
|
||||
None => panic!("handle does not refer to a valid socket")
|
||||
None => panic!("handle does not refer to a valid socket"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,77 +98,22 @@ impl<'a> Set<'a> {
|
|||
net_trace!("[{}]: removing", handle.0);
|
||||
match self.sockets[handle.0].take() {
|
||||
Some(item) => item.socket,
|
||||
None => panic!("handle does not refer to a valid socket")
|
||||
}
|
||||
}
|
||||
|
||||
/// Increase reference count by 1.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function may panic if the handle does not belong to this socket set.
|
||||
pub fn retain(&mut self, handle: Handle) {
|
||||
self.sockets[handle.0]
|
||||
.as_mut()
|
||||
.expect("handle does not refer to a valid socket")
|
||||
.refs += 1
|
||||
}
|
||||
|
||||
/// Decrease reference count by 1.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function may panic if the handle does not belong to this socket set,
|
||||
/// or if the reference count is already zero.
|
||||
pub fn release(&mut self, handle: Handle) {
|
||||
let refs = &mut self.sockets[handle.0]
|
||||
.as_mut()
|
||||
.expect("handle does not refer to a valid socket")
|
||||
.refs;
|
||||
if *refs == 0 { panic!("decreasing reference count past zero") }
|
||||
*refs -= 1
|
||||
}
|
||||
|
||||
/// Prune the sockets in this set.
|
||||
///
|
||||
/// Pruning affects sockets with reference count 0. Open sockets are closed.
|
||||
/// Closed sockets are removed and dropped.
|
||||
pub fn prune(&mut self) {
|
||||
for (index, item) in self.sockets.iter_mut().enumerate() {
|
||||
let mut may_remove = false;
|
||||
if let Some(Item { refs: 0, ref mut socket }) = *item {
|
||||
match *socket {
|
||||
#[cfg(feature = "socket-raw")]
|
||||
Socket::Raw(_) =>
|
||||
may_remove = true,
|
||||
#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
|
||||
Socket::Icmp(_) =>
|
||||
may_remove = true,
|
||||
#[cfg(feature = "socket-udp")]
|
||||
Socket::Udp(_) =>
|
||||
may_remove = true,
|
||||
#[cfg(feature = "socket-tcp")]
|
||||
Socket::Tcp(ref mut socket) =>
|
||||
if socket.state() == TcpState::Closed {
|
||||
may_remove = true
|
||||
} else {
|
||||
socket.close()
|
||||
},
|
||||
}
|
||||
}
|
||||
if may_remove {
|
||||
net_trace!("[{}]: pruning", index);
|
||||
*item = None
|
||||
}
|
||||
None => panic!("handle does not refer to a valid socket"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate every socket in this set.
|
||||
pub fn iter<'d>(&'d self) -> Iter<'d, 'a> {
|
||||
Iter { lower: self.sockets.iter() }
|
||||
Iter {
|
||||
lower: self.sockets.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate every socket in this set, as SocketRef.
|
||||
/// Iterate every socket in this set.
|
||||
pub fn iter_mut<'d>(&'d mut self) -> IterMut<'d, 'a> {
|
||||
IterMut { lower: self.sockets.iter_mut() }
|
||||
IterMut {
|
||||
lower: self.sockets.iter_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,16 +122,16 @@ impl<'a> Set<'a> {
|
|||
/// This struct is created by the [iter](struct.SocketSet.html#method.iter)
|
||||
/// on [socket sets](struct.SocketSet.html).
|
||||
pub struct Iter<'a, 'b: 'a> {
|
||||
lower: slice::Iter<'a, Option<Item<'b>>>
|
||||
lower: slice::Iter<'a, Option<Item<'b>>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> Iterator for Iter<'a, 'b> {
|
||||
type Item = &'a Socket<'b>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(item_opt) = self.lower.next() {
|
||||
for item_opt in &mut self.lower {
|
||||
if let Some(item) = item_opt.as_ref() {
|
||||
return Some(&item.socket)
|
||||
return Some(&item.socket);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
@ -205,12 +147,12 @@ pub struct IterMut<'a, 'b: 'a> {
|
|||
}
|
||||
|
||||
impl<'a, 'b: 'a> Iterator for IterMut<'a, 'b> {
|
||||
type Item = SocketRef<'a, Socket<'b>>;
|
||||
type Item = &'a mut Socket<'b>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(item_opt) = self.lower.next() {
|
||||
for item_opt in &mut self.lower {
|
||||
if let Some(item) = item_opt.as_mut() {
|
||||
return Some(SocketRef::new(&mut item.socket))
|
||||
return Some(&mut item.socket);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
5323
src/socket/tcp.rs
5323
src/socket/tcp.rs
File diff suppressed because it is too large
Load Diff
|
@ -2,12 +2,12 @@ use core::cmp::min;
|
|||
#[cfg(feature = "async")]
|
||||
use core::task::Waker;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::socket::{Socket, SocketMeta, SocketHandle, PollAt};
|
||||
use crate::storage::{PacketBuffer, PacketMetadata};
|
||||
use crate::wire::{IpProtocol, IpRepr, IpEndpoint, UdpRepr};
|
||||
#[cfg(feature = "async")]
|
||||
use crate::socket::WakerRegistration;
|
||||
use crate::socket::{Context, PollAt, Socket, SocketHandle, SocketMeta};
|
||||
use crate::storage::{PacketBuffer, PacketMetadata};
|
||||
use crate::wire::{IpEndpoint, IpProtocol, IpRepr, UdpRepr};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// A UDP packet metadata.
|
||||
pub type UdpPacketMetadata = PacketMetadata<IpEndpoint>;
|
||||
|
@ -22,7 +22,7 @@ pub type UdpSocketBuffer<'a> = PacketBuffer<'a, IpEndpoint>;
|
|||
#[derive(Debug)]
|
||||
pub struct UdpSocket<'a> {
|
||||
pub(crate) meta: SocketMeta,
|
||||
endpoint: IpEndpoint,
|
||||
endpoint: IpEndpoint,
|
||||
rx_buffer: UdpSocketBuffer<'a>,
|
||||
tx_buffer: UdpSocketBuffer<'a>,
|
||||
/// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
|
||||
|
@ -35,11 +35,10 @@ pub struct UdpSocket<'a> {
|
|||
|
||||
impl<'a> UdpSocket<'a> {
|
||||
/// Create an UDP socket with the given buffers.
|
||||
pub fn new(rx_buffer: UdpSocketBuffer<'a>,
|
||||
tx_buffer: UdpSocketBuffer<'a>) -> UdpSocket<'a> {
|
||||
pub fn new(rx_buffer: UdpSocketBuffer<'a>, tx_buffer: UdpSocketBuffer<'a>) -> UdpSocket<'a> {
|
||||
UdpSocket {
|
||||
meta: SocketMeta::default(),
|
||||
endpoint: IpEndpoint::default(),
|
||||
meta: SocketMeta::default(),
|
||||
endpoint: IpEndpoint::default(),
|
||||
rx_buffer: rx_buffer,
|
||||
tx_buffer: tx_buffer,
|
||||
hop_limit: None,
|
||||
|
@ -54,12 +53,12 @@ impl<'a> UdpSocket<'a> {
|
|||
///
|
||||
/// The waker is woken on state changes that might affect the return value
|
||||
/// of `recv` method calls, such as receiving data, or the socket closing.
|
||||
///
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// - Only one waker can be registered at a time. If another waker was previously registered,
|
||||
/// it is overwritten and will no longer be woken.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
|
||||
/// necessarily changed.
|
||||
#[cfg(feature = "async")]
|
||||
|
@ -72,12 +71,12 @@ impl<'a> UdpSocket<'a> {
|
|||
/// The waker is woken on state changes that might affect the return value
|
||||
/// of `send` method calls, such as space becoming available in the transmit
|
||||
/// buffer, or the socket closing.
|
||||
///
|
||||
///
|
||||
/// Notes:
|
||||
///
|
||||
/// - Only one waker can be registered at a time. If another waker was previously registered,
|
||||
/// it is overwritten and will no longer be woken.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
|
||||
/// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
|
||||
/// necessarily changed.
|
||||
#[cfg(feature = "async")]
|
||||
|
@ -131,9 +130,13 @@ impl<'a> UdpSocket<'a> {
|
|||
/// if the port in the given endpoint is zero.
|
||||
pub fn bind<T: Into<IpEndpoint>>(&mut self, endpoint: T) -> Result<()> {
|
||||
let endpoint = endpoint.into();
|
||||
if endpoint.port == 0 { return Err(Error::Unaddressable) }
|
||||
if endpoint.port == 0 {
|
||||
return Err(Error::Unaddressable);
|
||||
}
|
||||
|
||||
if self.is_open() { return Err(Error::Illegal) }
|
||||
if self.is_open() {
|
||||
return Err(Error::Illegal);
|
||||
}
|
||||
|
||||
self.endpoint = endpoint;
|
||||
|
||||
|
@ -146,6 +149,22 @@ impl<'a> UdpSocket<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Close the socket.
|
||||
pub fn close(&mut self) {
|
||||
// Clear the bound endpoint of the socket.
|
||||
self.endpoint = IpEndpoint::default();
|
||||
|
||||
// Reset the RX and TX buffers of the socket.
|
||||
self.tx_buffer.reset();
|
||||
self.rx_buffer.reset();
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
{
|
||||
self.rx_waker.wake();
|
||||
self.tx_waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the socket is open.
|
||||
#[inline]
|
||||
pub fn is_open(&self) -> bool {
|
||||
|
@ -196,13 +215,22 @@ impl<'a> UdpSocket<'a> {
|
|||
/// and `Err(Error::Truncated)` if there is not enough transmit buffer capacity
|
||||
/// to ever send this packet.
|
||||
pub fn send(&mut self, size: usize, endpoint: IpEndpoint) -> Result<&mut [u8]> {
|
||||
if self.endpoint.port == 0 { return Err(Error::Unaddressable) }
|
||||
if !endpoint.is_specified() { return Err(Error::Unaddressable) }
|
||||
if self.endpoint.port == 0 {
|
||||
return Err(Error::Unaddressable);
|
||||
}
|
||||
if !endpoint.is_specified() {
|
||||
return Err(Error::Unaddressable);
|
||||
}
|
||||
|
||||
let payload_buf = self.tx_buffer.enqueue(size, endpoint)?;
|
||||
|
||||
net_trace!("{}:{}:{}: buffer to send {} octets",
|
||||
self.meta.handle, self.endpoint, endpoint, size);
|
||||
net_trace!(
|
||||
"{}:{}:{}: buffer to send {} octets",
|
||||
self.meta.handle,
|
||||
self.endpoint,
|
||||
endpoint,
|
||||
size
|
||||
);
|
||||
Ok(payload_buf)
|
||||
}
|
||||
|
||||
|
@ -221,9 +249,13 @@ impl<'a> UdpSocket<'a> {
|
|||
pub fn recv(&mut self) -> Result<(&[u8], IpEndpoint)> {
|
||||
let (endpoint, payload_buf) = self.rx_buffer.dequeue()?;
|
||||
|
||||
net_trace!("{}:{}:{}: receive {} buffered octets",
|
||||
self.meta.handle, self.endpoint,
|
||||
endpoint, payload_buf.len());
|
||||
net_trace!(
|
||||
"{}:{}:{}: receive {} buffered octets",
|
||||
self.meta.handle,
|
||||
self.endpoint,
|
||||
endpoint,
|
||||
payload_buf.len()
|
||||
);
|
||||
Ok((payload_buf, endpoint))
|
||||
}
|
||||
|
||||
|
@ -247,10 +279,14 @@ impl<'a> UdpSocket<'a> {
|
|||
let handle = self.meta.handle;
|
||||
let endpoint = self.endpoint;
|
||||
self.rx_buffer.peek().map(|(remote_endpoint, payload_buf)| {
|
||||
net_trace!("{}:{}:{}: peek {} buffered octets",
|
||||
handle, endpoint,
|
||||
remote_endpoint, payload_buf.len());
|
||||
(payload_buf, remote_endpoint)
|
||||
net_trace!(
|
||||
"{}:{}:{}: peek {} buffered octets",
|
||||
handle,
|
||||
endpoint,
|
||||
remote_endpoint,
|
||||
payload_buf.len()
|
||||
);
|
||||
(payload_buf, remote_endpoint)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -268,26 +304,46 @@ impl<'a> UdpSocket<'a> {
|
|||
}
|
||||
|
||||
pub(crate) fn accepts(&self, ip_repr: &IpRepr, repr: &UdpRepr) -> bool {
|
||||
if self.endpoint.port != repr.dst_port { return false }
|
||||
if !self.endpoint.addr.is_unspecified() &&
|
||||
self.endpoint.addr != ip_repr.dst_addr() &&
|
||||
!ip_repr.dst_addr().is_broadcast() &&
|
||||
!ip_repr.dst_addr().is_multicast() { return false }
|
||||
if self.endpoint.port != repr.dst_port {
|
||||
return false;
|
||||
}
|
||||
if !self.endpoint.addr.is_unspecified()
|
||||
&& self.endpoint.addr != ip_repr.dst_addr()
|
||||
&& !ip_repr.dst_addr().is_broadcast()
|
||||
&& !ip_repr.dst_addr().is_multicast()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn process(&mut self, ip_repr: &IpRepr, repr: &UdpRepr) -> Result<()> {
|
||||
pub(crate) fn process(
|
||||
&mut self,
|
||||
_cx: &Context,
|
||||
ip_repr: &IpRepr,
|
||||
repr: &UdpRepr,
|
||||
payload: &[u8],
|
||||
) -> Result<()> {
|
||||
debug_assert!(self.accepts(ip_repr, repr));
|
||||
|
||||
let size = repr.payload.len();
|
||||
let size = payload.len();
|
||||
|
||||
let endpoint = IpEndpoint { addr: ip_repr.src_addr(), port: repr.src_port };
|
||||
self.rx_buffer.enqueue(size, endpoint)?.copy_from_slice(repr.payload);
|
||||
let endpoint = IpEndpoint {
|
||||
addr: ip_repr.src_addr(),
|
||||
port: repr.src_port,
|
||||
};
|
||||
self.rx_buffer
|
||||
.enqueue(size, endpoint)?
|
||||
.copy_from_slice(payload);
|
||||
|
||||
net_trace!("{}:{}:{}: receiving {} octets",
|
||||
self.meta.handle, self.endpoint,
|
||||
endpoint, size);
|
||||
net_trace!(
|
||||
"{}:{}:{}: receiving {} octets",
|
||||
self.meta.handle,
|
||||
self.endpoint,
|
||||
endpoint,
|
||||
size
|
||||
);
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
self.rx_waker.wake();
|
||||
|
@ -295,31 +351,37 @@ impl<'a> UdpSocket<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch<F>(&mut self, emit: F) -> Result<()>
|
||||
where F: FnOnce((IpRepr, UdpRepr)) -> Result<()> {
|
||||
let handle = self.handle();
|
||||
let endpoint = self.endpoint;
|
||||
pub(crate) fn dispatch<F>(&mut self, _cx: &Context, emit: F) -> Result<()>
|
||||
where
|
||||
F: FnOnce((IpRepr, UdpRepr, &[u8])) -> Result<()>,
|
||||
{
|
||||
let handle = self.handle();
|
||||
let endpoint = self.endpoint;
|
||||
let hop_limit = self.hop_limit.unwrap_or(64);
|
||||
|
||||
self.tx_buffer.dequeue_with(|remote_endpoint, payload_buf| {
|
||||
net_trace!("{}:{}:{}: sending {} octets",
|
||||
handle, endpoint,
|
||||
endpoint, payload_buf.len());
|
||||
self.tx_buffer
|
||||
.dequeue_with(|remote_endpoint, payload_buf| {
|
||||
net_trace!(
|
||||
"{}:{}:{}: sending {} octets",
|
||||
handle,
|
||||
endpoint,
|
||||
endpoint,
|
||||
payload_buf.len()
|
||||
);
|
||||
|
||||
let repr = UdpRepr {
|
||||
src_port: endpoint.port,
|
||||
dst_port: remote_endpoint.port,
|
||||
payload: payload_buf,
|
||||
};
|
||||
let ip_repr = IpRepr::Unspecified {
|
||||
src_addr: endpoint.addr,
|
||||
dst_addr: remote_endpoint.addr,
|
||||
protocol: IpProtocol::Udp,
|
||||
payload_len: repr.buffer_len(),
|
||||
hop_limit: hop_limit,
|
||||
};
|
||||
emit((ip_repr, repr))
|
||||
})?;
|
||||
let repr = UdpRepr {
|
||||
src_port: endpoint.port,
|
||||
dst_port: remote_endpoint.port,
|
||||
};
|
||||
let ip_repr = IpRepr::Unspecified {
|
||||
src_addr: endpoint.addr,
|
||||
dst_addr: remote_endpoint.addr,
|
||||
protocol: IpProtocol::Udp,
|
||||
payload_len: repr.header_len() + payload_buf.len(),
|
||||
hop_limit: hop_limit,
|
||||
};
|
||||
emit((ip_repr, repr, payload_buf))
|
||||
})?;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
self.tx_waker.wake();
|
||||
|
@ -327,7 +389,7 @@ impl<'a> UdpSocket<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn poll_at(&self) -> PollAt {
|
||||
pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt {
|
||||
if self.tx_buffer.is_empty() {
|
||||
PollAt::Ingress
|
||||
} else {
|
||||
|
@ -336,37 +398,47 @@ impl<'a> UdpSocket<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Socket<'a>> for UdpSocket<'a> {
|
||||
fn into(self) -> Socket<'a> {
|
||||
Socket::Udp(self)
|
||||
impl<'a> From<UdpSocket<'a>> for Socket<'a> {
|
||||
fn from(val: UdpSocket<'a>) -> Self {
|
||||
Socket::Udp(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::wire::{IpAddress, IpRepr, UdpRepr};
|
||||
use super::*;
|
||||
use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::Ipv4Repr;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
use crate::wire::Ipv6Repr;
|
||||
use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3};
|
||||
use super::*;
|
||||
use crate::wire::{IpAddress, IpRepr, UdpRepr};
|
||||
|
||||
fn buffer(packets: usize) -> UdpSocketBuffer<'static> {
|
||||
UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY; packets], vec![0; 16 * packets])
|
||||
UdpSocketBuffer::new(
|
||||
vec![UdpPacketMetadata::EMPTY; packets],
|
||||
vec![0; 16 * packets],
|
||||
)
|
||||
}
|
||||
|
||||
fn socket(rx_buffer: UdpSocketBuffer<'static>,
|
||||
tx_buffer: UdpSocketBuffer<'static>)
|
||||
-> UdpSocket<'static> {
|
||||
fn socket(
|
||||
rx_buffer: UdpSocketBuffer<'static>,
|
||||
tx_buffer: UdpSocketBuffer<'static>,
|
||||
) -> UdpSocket<'static> {
|
||||
UdpSocket::new(rx_buffer, tx_buffer)
|
||||
}
|
||||
|
||||
const LOCAL_PORT: u16 = 53;
|
||||
const REMOTE_PORT: u16 = 49500;
|
||||
const LOCAL_PORT: u16 = 53;
|
||||
const REMOTE_PORT: u16 = 49500;
|
||||
|
||||
pub const LOCAL_END: IpEndpoint = IpEndpoint { addr: MOCK_IP_ADDR_1, port: LOCAL_PORT };
|
||||
pub const REMOTE_END: IpEndpoint = IpEndpoint { addr: MOCK_IP_ADDR_2, port: REMOTE_PORT };
|
||||
pub const LOCAL_END: IpEndpoint = IpEndpoint {
|
||||
addr: MOCK_IP_ADDR_1,
|
||||
port: LOCAL_PORT,
|
||||
};
|
||||
pub const REMOTE_END: IpEndpoint = IpEndpoint {
|
||||
addr: MOCK_IP_ADDR_2,
|
||||
port: REMOTE_PORT,
|
||||
};
|
||||
|
||||
pub const LOCAL_IP_REPR: IpRepr = IpRepr::Unspecified {
|
||||
src_addr: MOCK_IP_ADDR_1,
|
||||
|
@ -379,15 +451,15 @@ mod test {
|
|||
const LOCAL_UDP_REPR: UdpRepr = UdpRepr {
|
||||
src_port: LOCAL_PORT,
|
||||
dst_port: REMOTE_PORT,
|
||||
payload: b"abcdef"
|
||||
};
|
||||
|
||||
const REMOTE_UDP_REPR: UdpRepr = UdpRepr {
|
||||
src_port: REMOTE_PORT,
|
||||
dst_port: LOCAL_PORT,
|
||||
payload: b"abcdef"
|
||||
};
|
||||
|
||||
const PAYLOAD: &[u8] = b"abcdef";
|
||||
|
||||
fn remote_ip_repr() -> IpRepr {
|
||||
match (MOCK_IP_ADDR_2, MOCK_IP_ADDR_1) {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
|
@ -396,7 +468,7 @@ mod test {
|
|||
dst_addr: dst,
|
||||
protocol: IpProtocol::Udp,
|
||||
payload_len: 8 + 6,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
}),
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
(IpAddress::Ipv6(src), IpAddress::Ipv6(dst)) => IpRepr::Ipv6(Ipv6Repr {
|
||||
|
@ -404,9 +476,9 @@ mod test {
|
|||
dst_addr: dst,
|
||||
next_header: IpProtocol::Udp,
|
||||
payload_len: 8 + 6,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
}),
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,14 +505,31 @@ mod test {
|
|||
#[test]
|
||||
fn test_send_unaddressable() {
|
||||
let mut socket = socket(buffer(0), buffer(1));
|
||||
assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Err(Error::Unaddressable));
|
||||
assert_eq!(
|
||||
socket.send_slice(b"abcdef", REMOTE_END),
|
||||
Err(Error::Unaddressable)
|
||||
);
|
||||
assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
|
||||
assert_eq!(socket.send_slice(b"abcdef",
|
||||
IpEndpoint { addr: IpAddress::Unspecified, ..REMOTE_END }),
|
||||
Err(Error::Unaddressable));
|
||||
assert_eq!(socket.send_slice(b"abcdef",
|
||||
IpEndpoint { port: 0, ..REMOTE_END }),
|
||||
Err(Error::Unaddressable));
|
||||
assert_eq!(
|
||||
socket.send_slice(
|
||||
b"abcdef",
|
||||
IpEndpoint {
|
||||
addr: IpAddress::Unspecified,
|
||||
..REMOTE_END
|
||||
}
|
||||
),
|
||||
Err(Error::Unaddressable)
|
||||
);
|
||||
assert_eq!(
|
||||
socket.send_slice(
|
||||
b"abcdef",
|
||||
IpEndpoint {
|
||||
port: 0,
|
||||
..REMOTE_END
|
||||
}
|
||||
),
|
||||
Err(Error::Unaddressable)
|
||||
);
|
||||
assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Ok(()));
|
||||
}
|
||||
|
||||
|
@ -450,25 +539,38 @@ mod test {
|
|||
assert_eq!(socket.bind(LOCAL_END), Ok(()));
|
||||
|
||||
assert!(socket.can_send());
|
||||
assert_eq!(socket.dispatch(|_| unreachable!()),
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
socket.dispatch(&Context::DUMMY, |_| unreachable!()),
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
|
||||
assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Ok(()));
|
||||
assert_eq!(socket.send_slice(b"123456", REMOTE_END), Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
socket.send_slice(b"123456", REMOTE_END),
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
assert!(!socket.can_send());
|
||||
|
||||
assert_eq!(socket.dispatch(|(ip_repr, udp_repr)| {
|
||||
assert_eq!(ip_repr, LOCAL_IP_REPR);
|
||||
assert_eq!(udp_repr, LOCAL_UDP_REPR);
|
||||
assert_eq!(
|
||||
socket.dispatch(&Context::DUMMY, |(ip_repr, udp_repr, payload)| {
|
||||
assert_eq!(ip_repr, LOCAL_IP_REPR);
|
||||
assert_eq!(udp_repr, LOCAL_UDP_REPR);
|
||||
assert_eq!(payload, PAYLOAD);
|
||||
Err(Error::Unaddressable)
|
||||
}),
|
||||
Err(Error::Unaddressable)
|
||||
}), Err(Error::Unaddressable));
|
||||
);
|
||||
assert!(!socket.can_send());
|
||||
|
||||
assert_eq!(socket.dispatch(|(ip_repr, udp_repr)| {
|
||||
assert_eq!(ip_repr, LOCAL_IP_REPR);
|
||||
assert_eq!(udp_repr, LOCAL_UDP_REPR);
|
||||
assert_eq!(
|
||||
socket.dispatch(&Context::DUMMY, |(ip_repr, udp_repr, payload)| {
|
||||
assert_eq!(ip_repr, LOCAL_IP_REPR);
|
||||
assert_eq!(udp_repr, LOCAL_UDP_REPR);
|
||||
assert_eq!(payload, PAYLOAD);
|
||||
Ok(())
|
||||
}),
|
||||
Ok(())
|
||||
}), Ok(()));
|
||||
);
|
||||
assert!(socket.can_send());
|
||||
}
|
||||
|
||||
|
@ -481,13 +583,27 @@ mod test {
|
|||
assert_eq!(socket.recv(), Err(Error::Exhausted));
|
||||
|
||||
assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR));
|
||||
assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
|
||||
Ok(()));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&remote_ip_repr(),
|
||||
&REMOTE_UDP_REPR,
|
||||
PAYLOAD
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
assert!(socket.can_recv());
|
||||
|
||||
assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR));
|
||||
assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&remote_ip_repr(),
|
||||
&REMOTE_UDP_REPR,
|
||||
PAYLOAD
|
||||
),
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END)));
|
||||
assert!(!socket.can_recv());
|
||||
}
|
||||
|
@ -499,8 +615,15 @@ mod test {
|
|||
|
||||
assert_eq!(socket.peek(), Err(Error::Exhausted));
|
||||
|
||||
assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
|
||||
Ok(()));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&remote_ip_repr(),
|
||||
&REMOTE_UDP_REPR,
|
||||
PAYLOAD
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(socket.peek(), Ok((&b"abcdef"[..], &REMOTE_END)));
|
||||
assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END)));
|
||||
assert_eq!(socket.peek(), Err(Error::Exhausted));
|
||||
|
@ -512,8 +635,15 @@ mod test {
|
|||
assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
|
||||
|
||||
assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR));
|
||||
assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
|
||||
Ok(()));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&remote_ip_repr(),
|
||||
&REMOTE_UDP_REPR,
|
||||
PAYLOAD
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let mut slice = [0; 4];
|
||||
assert_eq!(socket.recv_slice(&mut slice[..]), Ok((4, REMOTE_END)));
|
||||
|
@ -525,8 +655,15 @@ mod test {
|
|||
let mut socket = socket(buffer(1), buffer(0));
|
||||
assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
|
||||
|
||||
assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
|
||||
Ok(()));
|
||||
assert_eq!(
|
||||
socket.process(
|
||||
&Context::DUMMY,
|
||||
&remote_ip_repr(),
|
||||
&REMOTE_UDP_REPR,
|
||||
PAYLOAD
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let mut slice = [0; 4];
|
||||
assert_eq!(socket.peek_slice(&mut slice[..]), Ok((4, &REMOTE_END)));
|
||||
|
@ -543,16 +680,22 @@ mod test {
|
|||
|
||||
s.set_hop_limit(Some(0x2a));
|
||||
assert_eq!(s.send_slice(b"abcdef", REMOTE_END), Ok(()));
|
||||
assert_eq!(s.dispatch(|(ip_repr, _)| {
|
||||
assert_eq!(ip_repr, IpRepr::Unspecified{
|
||||
src_addr: MOCK_IP_ADDR_1,
|
||||
dst_addr: MOCK_IP_ADDR_2,
|
||||
protocol: IpProtocol::Udp,
|
||||
payload_len: 8 + 6,
|
||||
hop_limit: 0x2a,
|
||||
});
|
||||
assert_eq!(
|
||||
s.dispatch(&Context::DUMMY, |(ip_repr, _, _)| {
|
||||
assert_eq!(
|
||||
ip_repr,
|
||||
IpRepr::Unspecified {
|
||||
src_addr: MOCK_IP_ADDR_1,
|
||||
dst_addr: MOCK_IP_ADDR_2,
|
||||
protocol: IpProtocol::Udp,
|
||||
payload_len: 8 + 6,
|
||||
hop_limit: 0x2a,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}),
|
||||
Ok(())
|
||||
}), Ok(()));
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -576,7 +719,7 @@ mod test {
|
|||
dst_addr: dst,
|
||||
protocol: IpProtocol::Udp,
|
||||
payload_len: 8 + 6,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
}),
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
(IpAddress::Ipv6(src), IpAddress::Ipv6(dst)) => IpRepr::Ipv6(Ipv6Repr {
|
||||
|
@ -584,9 +727,9 @@ mod test {
|
|||
dst_addr: dst,
|
||||
next_header: IpProtocol::Udp,
|
||||
payload_len: 8 + 6,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
}),
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -606,8 +749,11 @@ mod test {
|
|||
assert_eq!(socket.bind(LOCAL_END), Ok(()));
|
||||
|
||||
let too_large = b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefx";
|
||||
assert_eq!(socket.send_slice(too_large, REMOTE_END), Err(Error::Truncated));
|
||||
assert_eq!(socket.send_slice(&too_large[..16*4], REMOTE_END), Ok(()));
|
||||
assert_eq!(
|
||||
socket.send_slice(too_large, REMOTE_END),
|
||||
Err(Error::Truncated)
|
||||
);
|
||||
assert_eq!(socket.send_slice(&too_large[..16 * 4], REMOTE_END), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -619,9 +765,22 @@ mod test {
|
|||
let repr = UdpRepr {
|
||||
src_port: REMOTE_PORT,
|
||||
dst_port: LOCAL_PORT,
|
||||
payload: &[]
|
||||
};
|
||||
assert_eq!(socket.process(&remote_ip_repr(), &repr), Ok(()));
|
||||
assert_eq!(
|
||||
socket.process(&Context::DUMMY, &remote_ip_repr(), &repr, &[]),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(socket.recv(), Ok((&[][..], REMOTE_END)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closing() {
|
||||
let recv_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY; 1], vec![]);
|
||||
let mut socket = socket(recv_buffer, buffer(0));
|
||||
assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
|
||||
|
||||
assert!(socket.is_open());
|
||||
socket.close();
|
||||
assert!(!socket.is_open());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,4 +30,4 @@ impl WakerRegistration {
|
|||
pub fn wake(&mut self) {
|
||||
self.waker.take().map(|w| w.wake());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,31 +5,47 @@ pub struct TooManyHolesError;
|
|||
|
||||
/// A contiguous chunk of absent data, followed by a contiguous chunk of present data.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
struct Contig {
|
||||
hole_size: usize,
|
||||
data_size: usize
|
||||
data_size: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for Contig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.has_hole() { write!(f, "({})", self.hole_size)?; }
|
||||
if self.has_hole() && self.has_data() { write!(f, " ")?; }
|
||||
if self.has_data() { write!(f, "{}", self.data_size)?; }
|
||||
if self.has_hole() {
|
||||
write!(f, "({})", self.hole_size)?;
|
||||
}
|
||||
if self.has_hole() && self.has_data() {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
if self.has_data() {
|
||||
write!(f, "{}", self.data_size)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Contig {
|
||||
fn empty() -> Contig {
|
||||
Contig { hole_size: 0, data_size: 0 }
|
||||
Contig {
|
||||
hole_size: 0,
|
||||
data_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn hole(size: usize) -> Contig {
|
||||
Contig { hole_size: size, data_size: 0 }
|
||||
Contig {
|
||||
hole_size: size,
|
||||
data_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn hole_and_data(hole_size: usize, data_size: usize) -> Contig {
|
||||
Contig { hole_size, data_size }
|
||||
Contig {
|
||||
hole_size,
|
||||
data_size,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_hole(&self) -> bool {
|
||||
|
@ -65,10 +81,10 @@ impl Contig {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::boxed::Box;
|
||||
#[cfg(all(feature = "alloc", not(feature = "std")))]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "std")]
|
||||
use std::boxed::Box;
|
||||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
const CONTIG_COUNT: usize = 32;
|
||||
|
||||
|
@ -80,6 +96,7 @@ const CONTIG_COUNT: usize = 4;
|
|||
/// Currently, up to a hardcoded limit of 4 or 32 holes can be tracked in the buffer.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq, Clone))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Assembler {
|
||||
#[cfg(not(any(feature = "std", feature = "alloc")))]
|
||||
contigs: [Contig; CONTIG_COUNT],
|
||||
|
@ -91,7 +108,9 @@ impl fmt::Display for Assembler {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "[ ")?;
|
||||
for contig in self.contigs.iter() {
|
||||
if contig.is_empty() { break }
|
||||
if contig.is_empty() {
|
||||
break;
|
||||
}
|
||||
write!(f, "{} ", contig)?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
|
@ -113,10 +132,7 @@ impl Assembler {
|
|||
/// FIXME(whitequark): remove this once I'm certain enough that the assembler works well.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn total_size(&self) -> usize {
|
||||
self.contigs
|
||||
.iter()
|
||||
.map(|contig| contig.total_size())
|
||||
.sum()
|
||||
self.contigs.iter().map(|contig| contig.total_size()).sum()
|
||||
}
|
||||
|
||||
fn front(&self) -> Contig {
|
||||
|
@ -141,7 +157,7 @@ impl Assembler {
|
|||
self.contigs[i] = self.contigs[i + 1];
|
||||
if !self.contigs[i].has_data() {
|
||||
self.contigs[i + 1] = Contig::empty();
|
||||
return &mut self.contigs[i]
|
||||
return &mut self.contigs[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,7 +170,9 @@ impl Assembler {
|
|||
fn add_contig_at(&mut self, at: usize) -> Result<&mut Contig, TooManyHolesError> {
|
||||
debug_assert!(!self.contigs[at].is_empty());
|
||||
|
||||
if !self.back().is_empty() { return Err(TooManyHolesError) }
|
||||
if !self.back().is_empty() {
|
||||
return Err(TooManyHolesError);
|
||||
}
|
||||
|
||||
for i in (at + 1..self.contigs.len()).rev() {
|
||||
self.contigs[i] = self.contigs[i - 1];
|
||||
|
@ -199,11 +217,11 @@ impl Assembler {
|
|||
// The range being added covers a part of the hole but not of the data
|
||||
// in this contig, add a new contig containing the range.
|
||||
{
|
||||
let inserted = self.add_contig_at(index)?;
|
||||
*inserted = Contig::hole_and_data(offset, size);
|
||||
let inserted = self.add_contig_at(index)?;
|
||||
*inserted = Contig::hole_and_data(offset, size);
|
||||
}
|
||||
// Previous contigs[index] got moved to contigs[index+1]
|
||||
self.contigs[index+1].shrink_hole_by(offset + size);
|
||||
self.contigs[index + 1].shrink_hole_by(offset + size);
|
||||
index += 2;
|
||||
} else {
|
||||
unreachable!()
|
||||
|
@ -213,7 +231,7 @@ impl Assembler {
|
|||
if offset >= contig.total_size() {
|
||||
offset = offset.saturating_sub(contig.total_size());
|
||||
} else {
|
||||
size = (offset + size).saturating_sub(contig.total_size());
|
||||
size = (offset + size).saturating_sub(contig.total_size());
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +274,7 @@ pub struct AssemblerIter<'a> {
|
|||
offset: usize,
|
||||
index: usize,
|
||||
left: usize,
|
||||
right: usize
|
||||
right: usize,
|
||||
}
|
||||
|
||||
impl<'a> AssemblerIter<'a> {
|
||||
|
@ -266,7 +284,7 @@ impl<'a> AssemblerIter<'a> {
|
|||
offset: offset,
|
||||
index: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
right: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,8 +313,8 @@ impl<'a> Iterator for AssemblerIter<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::vec::Vec;
|
||||
use super::*;
|
||||
use std::vec::Vec;
|
||||
|
||||
impl From<Vec<(usize, usize)>> for Assembler {
|
||||
fn from(vec: Vec<(usize, usize)>) -> Assembler {
|
||||
|
@ -305,7 +323,10 @@ mod test {
|
|||
#[cfg(any(feature = "std", feature = "alloc"))]
|
||||
let mut contigs = Box::new([Contig::empty(); CONTIG_COUNT]);
|
||||
for (i, &(hole_size, data_size)) in vec.iter().enumerate() {
|
||||
contigs[i] = Contig { hole_size, data_size };
|
||||
contigs[i] = Contig {
|
||||
hole_size,
|
||||
data_size,
|
||||
};
|
||||
}
|
||||
Assembler { contigs }
|
||||
}
|
||||
|
@ -410,9 +431,9 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_rejected_add_keeps_state() {
|
||||
let mut assr = Assembler::new(CONTIG_COUNT*20);
|
||||
for c in 1..=CONTIG_COUNT-1 {
|
||||
assert_eq!(assr.add(c*10, 3), Ok(()));
|
||||
let mut assr = Assembler::new(CONTIG_COUNT * 20);
|
||||
for c in 1..=CONTIG_COUNT - 1 {
|
||||
assert_eq!(assr.add(c * 10, 3), Ok(()));
|
||||
}
|
||||
// Maximum of allowed holes is reached
|
||||
let assr_before = assr.clone();
|
||||
|
@ -438,7 +459,6 @@ mod test {
|
|||
let mut assr = contigs![(0, 4), (4, 4)];
|
||||
assert_eq!(assr.remove_front(), Some(4));
|
||||
assert_eq!(assr, contigs![(4, 4), (4, 0)]);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -6,12 +6,12 @@ or `alloc` crates being available, and heap-allocated memory.
|
|||
*/
|
||||
|
||||
mod assembler;
|
||||
mod ring_buffer;
|
||||
mod packet_buffer;
|
||||
mod ring_buffer;
|
||||
|
||||
pub use self::assembler::Assembler;
|
||||
pub use self::ring_buffer::RingBuffer;
|
||||
pub use self::packet_buffer::{PacketBuffer, PacketMetadata};
|
||||
pub use self::ring_buffer::RingBuffer;
|
||||
|
||||
/// A trait for setting a value to a known state.
|
||||
///
|
||||
|
|
|
@ -1,30 +1,34 @@
|
|||
use managed::ManagedSlice;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::storage::RingBuffer;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Size and header of a packet.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PacketMetadata<H> {
|
||||
size: usize,
|
||||
header: Option<H>
|
||||
size: usize,
|
||||
header: Option<H>,
|
||||
}
|
||||
|
||||
impl<H> PacketMetadata<H> {
|
||||
/// Empty packet description.
|
||||
pub const EMPTY: PacketMetadata<H> = PacketMetadata { size: 0, header: None };
|
||||
pub const EMPTY: PacketMetadata<H> = PacketMetadata {
|
||||
size: 0,
|
||||
header: None,
|
||||
};
|
||||
|
||||
fn padding(size: usize) -> PacketMetadata<H> {
|
||||
PacketMetadata {
|
||||
size: size,
|
||||
header: None
|
||||
size: size,
|
||||
header: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn packet(size: usize, header: H) -> PacketMetadata<H> {
|
||||
PacketMetadata {
|
||||
size: size,
|
||||
header: Some(header)
|
||||
size: size,
|
||||
header: Some(header),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,9 +39,9 @@ impl<H> PacketMetadata<H> {
|
|||
|
||||
/// An UDP packet ring buffer.
|
||||
#[derive(Debug)]
|
||||
pub struct PacketBuffer<'a, H: 'a> {
|
||||
pub struct PacketBuffer<'a, H: 'a> {
|
||||
metadata_ring: RingBuffer<'a, PacketMetadata<H>>,
|
||||
payload_ring: RingBuffer<'a, u8>,
|
||||
payload_ring: RingBuffer<'a, u8>,
|
||||
}
|
||||
|
||||
impl<'a, H> PacketBuffer<'a, H> {
|
||||
|
@ -46,12 +50,13 @@ impl<'a, H> PacketBuffer<'a, H> {
|
|||
/// Metadata storage limits the maximum _number_ of packets in the buffer and payload
|
||||
/// storage limits the maximum _total size_ of packets.
|
||||
pub fn new<MS, PS>(metadata_storage: MS, payload_storage: PS) -> PacketBuffer<'a, H>
|
||||
where MS: Into<ManagedSlice<'a, PacketMetadata<H>>>,
|
||||
PS: Into<ManagedSlice<'a, u8>>,
|
||||
where
|
||||
MS: Into<ManagedSlice<'a, PacketMetadata<H>>>,
|
||||
PS: Into<ManagedSlice<'a, u8>>,
|
||||
{
|
||||
PacketBuffer {
|
||||
metadata_ring: RingBuffer::new(metadata_storage),
|
||||
payload_ring: RingBuffer::new(payload_storage),
|
||||
payload_ring: RingBuffer::new(payload_storage),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,30 +79,32 @@ impl<'a, H> PacketBuffer<'a, H> {
|
|||
/// does not have enough spare payload space.
|
||||
pub fn enqueue(&mut self, size: usize, header: H) -> Result<&mut [u8]> {
|
||||
if self.payload_ring.capacity() < size {
|
||||
return Err(Error::Truncated)
|
||||
return Err(Error::Truncated);
|
||||
}
|
||||
|
||||
if self.metadata_ring.is_full() {
|
||||
return Err(Error::Exhausted)
|
||||
return Err(Error::Exhausted);
|
||||
}
|
||||
|
||||
let window = self.payload_ring.window();
|
||||
let contig_window = self.payload_ring.contiguous_window();
|
||||
|
||||
if window < size {
|
||||
return Err(Error::Exhausted)
|
||||
return Err(Error::Exhausted);
|
||||
} else if contig_window < size {
|
||||
if window - contig_window < size {
|
||||
// The buffer length is larger than the current contiguous window
|
||||
// and is larger than the contiguous window will be after adding
|
||||
// the padding necessary to circle around to the beginning of the
|
||||
// ring buffer.
|
||||
return Err(Error::Exhausted)
|
||||
return Err(Error::Exhausted);
|
||||
} else {
|
||||
// Add padding to the end of the ring buffer so that the
|
||||
// contiguous window is at the beginning of the ring buffer.
|
||||
*self.metadata_ring.enqueue_one()? = PacketMetadata::padding(contig_window);
|
||||
self.payload_ring.enqueue_many(contig_window);
|
||||
// note(discard): function does not write to the result
|
||||
// enqueued padding buffer location
|
||||
let _buf_enqueued = self.payload_ring.enqueue_many(contig_window);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,11 +116,15 @@ impl<'a, H> PacketBuffer<'a, H> {
|
|||
}
|
||||
|
||||
fn dequeue_padding(&mut self) {
|
||||
let Self { ref mut metadata_ring, ref mut payload_ring } = *self;
|
||||
let Self {
|
||||
ref mut metadata_ring,
|
||||
ref mut payload_ring,
|
||||
} = *self;
|
||||
|
||||
let _ = metadata_ring.dequeue_one_with(|metadata| {
|
||||
if metadata.is_padding() {
|
||||
payload_ring.dequeue_many(metadata.size);
|
||||
// note(discard): function does not use value of dequeued padding bytes
|
||||
let _buf_dequeued = payload_ring.dequeue_many(metadata.size);
|
||||
Ok(()) // dequeue metadata
|
||||
} else {
|
||||
Err(Error::Exhausted) // don't dequeue metadata
|
||||
|
@ -124,22 +135,32 @@ impl<'a, H> PacketBuffer<'a, H> {
|
|||
/// Call `f` with a single packet from the buffer, and dequeue the packet if `f`
|
||||
/// returns successfully, or return `Err(Error::Exhausted)` if the buffer is empty.
|
||||
pub fn dequeue_with<'c, R, F>(&'c mut self, f: F) -> Result<R>
|
||||
where F: FnOnce(&mut H, &'c mut [u8]) -> Result<R> {
|
||||
where
|
||||
F: FnOnce(&mut H, &'c mut [u8]) -> Result<R>,
|
||||
{
|
||||
self.dequeue_padding();
|
||||
|
||||
let Self { ref mut metadata_ring, ref mut payload_ring } = *self;
|
||||
let Self {
|
||||
ref mut metadata_ring,
|
||||
ref mut payload_ring,
|
||||
} = *self;
|
||||
|
||||
metadata_ring.dequeue_one_with(move |metadata| {
|
||||
let PacketMetadata { ref mut header, size } = *metadata;
|
||||
let PacketMetadata {
|
||||
ref mut header,
|
||||
size,
|
||||
} = *metadata;
|
||||
|
||||
payload_ring.dequeue_many_with(|payload_buf| {
|
||||
debug_assert!(payload_buf.len() >= size);
|
||||
payload_ring
|
||||
.dequeue_many_with(|payload_buf| {
|
||||
debug_assert!(payload_buf.len() >= size);
|
||||
|
||||
match f(header.as_mut().unwrap(), &mut payload_buf[..size]) {
|
||||
Ok(val) => (size, Ok(val)),
|
||||
Err(err) => (0, Err(err)),
|
||||
}
|
||||
}).1
|
||||
match f(header.as_mut().unwrap(), &mut payload_buf[..size]) {
|
||||
Ok(val) => (size, Ok(val)),
|
||||
Err(err) => (0, Err(err)),
|
||||
}
|
||||
})
|
||||
.1
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -148,7 +169,10 @@ impl<'a, H> PacketBuffer<'a, H> {
|
|||
pub fn dequeue(&mut self) -> Result<(H, &mut [u8])> {
|
||||
self.dequeue_padding();
|
||||
|
||||
let PacketMetadata { ref mut header, size } = *self.metadata_ring.dequeue_one()?;
|
||||
let PacketMetadata {
|
||||
ref mut header,
|
||||
size,
|
||||
} = *self.metadata_ring.dequeue_one()?;
|
||||
|
||||
let payload_buf = self.payload_ring.dequeue_many(size);
|
||||
debug_assert!(payload_buf.len() == size);
|
||||
|
@ -163,7 +187,10 @@ impl<'a, H> PacketBuffer<'a, H> {
|
|||
self.dequeue_padding();
|
||||
|
||||
if let Some(metadata) = self.metadata_ring.get_allocated(0, 1).first() {
|
||||
Ok((metadata.header.as_ref().unwrap(), self.payload_ring.get_allocated(0, metadata.size)))
|
||||
Ok((
|
||||
metadata.header.as_ref().unwrap(),
|
||||
self.payload_ring.get_allocated(0, metadata.size),
|
||||
))
|
||||
} else {
|
||||
Err(Error::Exhausted)
|
||||
}
|
||||
|
@ -178,6 +205,13 @@ impl<'a, H> PacketBuffer<'a, H> {
|
|||
pub fn payload_capacity(&self) -> usize {
|
||||
self.payload_ring.capacity()
|
||||
}
|
||||
|
||||
/// Reset the packet buffer and clear any staged.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.payload_ring.clear();
|
||||
self.metadata_ring.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -185,8 +219,7 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
fn buffer() -> PacketBuffer<'static, ()> {
|
||||
PacketBuffer::new(vec![PacketMetadata::EMPTY; 4],
|
||||
vec![0u8; 16])
|
||||
PacketBuffer::new(vec![PacketMetadata::EMPTY; 4], vec![0u8; 16])
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -229,7 +262,10 @@ mod test {
|
|||
let mut buffer = buffer();
|
||||
assert!(buffer.enqueue(12, ()).is_ok());
|
||||
assert!(buffer.dequeue().is_ok());
|
||||
buffer.enqueue(12, ()).unwrap().copy_from_slice(b"abcdefghijkl");
|
||||
buffer
|
||||
.enqueue(12, ())
|
||||
.unwrap()
|
||||
.copy_from_slice(b"abcdefghijkl");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -242,30 +278,34 @@ mod test {
|
|||
assert_eq!(buffer.metadata_ring.len(), 3);
|
||||
assert!(buffer.dequeue().is_ok());
|
||||
|
||||
assert!(buffer.dequeue_with(|_, _| Err(Error::Unaddressable) as Result<()>).is_err());
|
||||
assert!(buffer
|
||||
.dequeue_with(|_, _| Err(Error::Unaddressable) as Result<()>)
|
||||
.is_err());
|
||||
assert_eq!(buffer.metadata_ring.len(), 1);
|
||||
|
||||
assert!(buffer.dequeue_with(|&mut (), payload| {
|
||||
assert_eq!(payload, &b"abcd"[..]);
|
||||
Ok(())
|
||||
}).is_ok());
|
||||
assert!(buffer
|
||||
.dequeue_with(|&mut (), payload| {
|
||||
assert_eq!(payload, &b"abcd"[..]);
|
||||
Ok(())
|
||||
})
|
||||
.is_ok());
|
||||
assert_eq!(buffer.metadata_ring.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_metadata_full_empty() {
|
||||
let mut buffer = buffer();
|
||||
assert_eq!(buffer.is_empty(), true);
|
||||
assert_eq!(buffer.is_full(), false);
|
||||
assert!(buffer.is_empty());
|
||||
assert!(!buffer.is_full());
|
||||
assert!(buffer.enqueue(1, ()).is_ok());
|
||||
assert_eq!(buffer.is_empty(), false);
|
||||
assert!(!buffer.is_empty());
|
||||
assert!(buffer.enqueue(1, ()).is_ok());
|
||||
assert!(buffer.enqueue(1, ()).is_ok());
|
||||
assert_eq!(buffer.is_full(), false);
|
||||
assert_eq!(buffer.is_empty(), false);
|
||||
assert!(!buffer.is_full());
|
||||
assert!(!buffer.is_empty());
|
||||
assert!(buffer.enqueue(1, ()).is_ok());
|
||||
assert_eq!(buffer.is_full(), true);
|
||||
assert_eq!(buffer.is_empty(), false);
|
||||
assert!(buffer.is_full());
|
||||
assert!(!buffer.is_empty());
|
||||
assert_eq!(buffer.metadata_ring.len(), 4);
|
||||
assert_eq!(buffer.enqueue(1, ()), Err(Error::Exhausted));
|
||||
}
|
||||
|
@ -303,4 +343,18 @@ mod test {
|
|||
assert!(buffer.dequeue().is_ok());
|
||||
assert!(buffer.enqueue(5, ()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mut buffer = buffer();
|
||||
|
||||
// Ensure enqueuing data in teh buffer fills it somewhat.
|
||||
assert!(buffer.is_empty());
|
||||
assert!(buffer.enqueue(6, ()).is_ok());
|
||||
|
||||
// Ensure that resetting the buffer causes it to be empty.
|
||||
assert!(!buffer.is_empty());
|
||||
buffer.reset();
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
// Uncomment the #[must_use]s here once [RFC 1940] hits stable.
|
||||
// Some of the functions in ring buffer is marked as #[must_use]. It notes that
|
||||
// these functions may have side effects, and it's implemented by [RFC 1940].
|
||||
// [RFC 1940]: https://github.com/rust-lang/rust/issues/43302
|
||||
|
||||
use core::cmp;
|
||||
use managed::ManagedSlice;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::storage::Resettable;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// A ring buffer.
|
||||
///
|
||||
|
@ -25,7 +26,7 @@ use crate::storage::Resettable;
|
|||
pub struct RingBuffer<'a, T: 'a> {
|
||||
storage: ManagedSlice<'a, T>,
|
||||
read_at: usize,
|
||||
length: usize,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> RingBuffer<'a, T> {
|
||||
|
@ -33,19 +34,20 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
///
|
||||
/// During creation, every element in `storage` is reset.
|
||||
pub fn new<S>(storage: S) -> RingBuffer<'a, T>
|
||||
where S: Into<ManagedSlice<'a, T>>,
|
||||
where
|
||||
S: Into<ManagedSlice<'a, T>>,
|
||||
{
|
||||
RingBuffer {
|
||||
storage: storage.into(),
|
||||
read_at: 0,
|
||||
length: 0,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the ring buffer.
|
||||
pub fn clear(&mut self) {
|
||||
self.read_at = 0;
|
||||
self.length = 0;
|
||||
self.length = 0;
|
||||
}
|
||||
|
||||
/// Return the maximum number of elements in the ring buffer.
|
||||
|
@ -55,7 +57,9 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
|
||||
/// Clear the ring buffer, and reset every element.
|
||||
pub fn reset(&mut self)
|
||||
where T: Resettable {
|
||||
where
|
||||
T: Resettable,
|
||||
{
|
||||
self.clear();
|
||||
for elem in self.storage.iter_mut() {
|
||||
elem.reset();
|
||||
|
@ -112,8 +116,12 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
/// Call `f` with a single buffer element, and enqueue the element if `f`
|
||||
/// returns successfully, or return `Err(Error::Exhausted)` if the buffer is full.
|
||||
pub fn enqueue_one_with<'b, R, F>(&'b mut self, f: F) -> Result<R>
|
||||
where F: FnOnce(&'b mut T) -> Result<R> {
|
||||
if self.is_full() { return Err(Error::Exhausted) }
|
||||
where
|
||||
F: FnOnce(&'b mut T) -> Result<R>,
|
||||
{
|
||||
if self.is_full() {
|
||||
return Err(Error::Exhausted);
|
||||
}
|
||||
|
||||
let index = self.get_idx_unchecked(self.length);
|
||||
match f(&mut self.storage[index]) {
|
||||
|
@ -121,7 +129,7 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
self.length += 1;
|
||||
Ok(result)
|
||||
}
|
||||
Err(error) => Err(error)
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,8 +144,12 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
/// Call `f` with a single buffer element, and dequeue the element if `f`
|
||||
/// returns successfully, or return `Err(Error::Exhausted)` if the buffer is empty.
|
||||
pub fn dequeue_one_with<'b, R, F>(&'b mut self, f: F) -> Result<R>
|
||||
where F: FnOnce(&'b mut T) -> Result<R> {
|
||||
if self.is_empty() { return Err(Error::Exhausted) }
|
||||
where
|
||||
F: FnOnce(&'b mut T) -> Result<R>,
|
||||
{
|
||||
if self.is_empty() {
|
||||
return Err(Error::Exhausted);
|
||||
}
|
||||
|
||||
let next_at = self.get_idx_unchecked(1);
|
||||
match f(&mut self.storage[self.read_at]) {
|
||||
|
@ -146,7 +158,7 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
self.read_at = next_at;
|
||||
Ok(result)
|
||||
}
|
||||
Err(error) => Err(error)
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +181,9 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
/// This function panics if the amount of elements returned by `f` is larger
|
||||
/// than the size of the slice passed into it.
|
||||
pub fn enqueue_many_with<'b, R, F>(&'b mut self, f: F) -> (usize, R)
|
||||
where F: FnOnce(&'b mut [T]) -> (usize, R) {
|
||||
where
|
||||
F: FnOnce(&'b mut [T]) -> (usize, R),
|
||||
{
|
||||
if self.length == 0 {
|
||||
// Ring is currently empty. Reset `read_at` to optimize
|
||||
// for contiguous space.
|
||||
|
@ -189,19 +203,22 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
///
|
||||
/// This function may return a slice smaller than the given size
|
||||
/// if the free space in the buffer is not contiguous.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn enqueue_many(&mut self, size: usize) -> &mut [T] {
|
||||
self.enqueue_many_with(|buf| {
|
||||
let size = cmp::min(size, buf.len());
|
||||
(size, &mut buf[..size])
|
||||
}).1
|
||||
})
|
||||
.1
|
||||
}
|
||||
|
||||
/// Enqueue as many elements from the given slice into the buffer as possible,
|
||||
/// and return the amount of elements that could fit.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn enqueue_slice(&mut self, data: &[T]) -> usize
|
||||
where T: Copy {
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
let (size_1, data) = self.enqueue_many_with(|buf| {
|
||||
let size = cmp::min(buf.len(), data.len());
|
||||
buf[..size].copy_from_slice(&data[..size]);
|
||||
|
@ -222,7 +239,9 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
/// This function panics if the amount of elements returned by `f` is larger
|
||||
/// than the size of the slice passed into it.
|
||||
pub fn dequeue_many_with<'b, R, F>(&'b mut self, f: F) -> (usize, R)
|
||||
where F: FnOnce(&'b mut [T]) -> (usize, R) {
|
||||
where
|
||||
F: FnOnce(&'b mut [T]) -> (usize, R),
|
||||
{
|
||||
let capacity = self.capacity();
|
||||
let max_size = cmp::min(self.len(), capacity - self.read_at);
|
||||
let (size, result) = f(&mut self.storage[self.read_at..self.read_at + max_size]);
|
||||
|
@ -241,19 +260,22 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
///
|
||||
/// This function may return a slice smaller than the given size
|
||||
/// if the allocated space in the buffer is not contiguous.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn dequeue_many(&mut self, size: usize) -> &mut [T] {
|
||||
self.dequeue_many_with(|buf| {
|
||||
let size = cmp::min(size, buf.len());
|
||||
(size, &mut buf[..size])
|
||||
}).1
|
||||
})
|
||||
.1
|
||||
}
|
||||
|
||||
/// Dequeue as many elements from the buffer into the given slice as possible,
|
||||
/// and return the amount of elements that could fit.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn dequeue_slice(&mut self, data: &mut [T]) -> usize
|
||||
where T: Copy {
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
let (size_1, data) = self.dequeue_many_with(|buf| {
|
||||
let size = cmp::min(buf.len(), data.len());
|
||||
data[..size].copy_from_slice(&buf[..size]);
|
||||
|
@ -273,17 +295,23 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
impl<'a, T: 'a> RingBuffer<'a, T> {
|
||||
/// Return the largest contiguous slice of unallocated buffer elements starting
|
||||
/// at the given offset past the last allocated element, and up to the given size.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn get_unallocated(&mut self, offset: usize, mut size: usize) -> &mut [T] {
|
||||
let start_at = self.get_idx(self.length + offset);
|
||||
// We can't access past the end of unallocated data.
|
||||
if offset > self.window() { return &mut [] }
|
||||
if offset > self.window() {
|
||||
return &mut [];
|
||||
}
|
||||
// We can't enqueue more than there is free space.
|
||||
let clamped_window = self.window() - offset;
|
||||
if size > clamped_window { size = clamped_window }
|
||||
if size > clamped_window {
|
||||
size = clamped_window
|
||||
}
|
||||
// We can't contiguously enqueue past the end of the storage.
|
||||
let until_end = self.capacity() - start_at;
|
||||
if size > until_end { size = until_end }
|
||||
if size > until_end {
|
||||
size = until_end
|
||||
}
|
||||
|
||||
&mut self.storage[start_at..start_at + size]
|
||||
}
|
||||
|
@ -291,9 +319,11 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
/// Write as many elements from the given slice into unallocated buffer elements
|
||||
/// starting at the given offset past the last allocated element, and return
|
||||
/// the amount written.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn write_unallocated(&mut self, offset: usize, data: &[T]) -> usize
|
||||
where T: Copy {
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
let (size_1, offset, data) = {
|
||||
let slice = self.get_unallocated(offset, data.len());
|
||||
let slice_len = slice.len();
|
||||
|
@ -320,17 +350,23 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
|
||||
/// Return the largest contiguous slice of allocated buffer elements starting
|
||||
/// at the given offset past the first allocated element, and up to the given size.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn get_allocated(&self, offset: usize, mut size: usize) -> &[T] {
|
||||
let start_at = self.get_idx(offset);
|
||||
// We can't read past the end of the allocated data.
|
||||
if offset > self.length { return &mut [] }
|
||||
if offset > self.length {
|
||||
return &mut [];
|
||||
}
|
||||
// We can't read more than we have allocated.
|
||||
let clamped_length = self.length - offset;
|
||||
if size > clamped_length { size = clamped_length }
|
||||
if size > clamped_length {
|
||||
size = clamped_length
|
||||
}
|
||||
// We can't contiguously dequeue past the end of the storage.
|
||||
let until_end = self.capacity() - start_at;
|
||||
if size > until_end { size = until_end }
|
||||
if size > until_end {
|
||||
size = until_end
|
||||
}
|
||||
|
||||
&self.storage[start_at..start_at + size]
|
||||
}
|
||||
|
@ -338,9 +374,11 @@ impl<'a, T: 'a> RingBuffer<'a, T> {
|
|||
/// Read as many elements from allocated buffer elements into the given slice
|
||||
/// starting at the given offset past the first allocated element, and return
|
||||
/// the amount read.
|
||||
// #[must_use]
|
||||
#[must_use]
|
||||
pub fn read_allocated(&mut self, offset: usize, data: &mut [T]) -> usize
|
||||
where T: Copy {
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
let (size_1, offset, data) = {
|
||||
let slice = self.get_allocated(offset, data.len());
|
||||
data[..slice.len()].copy_from_slice(slice);
|
||||
|
@ -402,8 +440,10 @@ mod test {
|
|||
#[test]
|
||||
fn test_buffer_enqueue_dequeue_one_with() {
|
||||
let mut ring = RingBuffer::new(vec![0; 5]);
|
||||
assert_eq!(ring.dequeue_one_with(|_| unreachable!()) as Result<()>,
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
ring.dequeue_one_with(|_| unreachable!()) as Result<()>,
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
|
||||
ring.enqueue_one_with(Ok).unwrap();
|
||||
assert!(!ring.is_empty());
|
||||
|
@ -414,15 +454,19 @@ mod test {
|
|||
assert!(!ring.is_empty());
|
||||
}
|
||||
assert!(ring.is_full());
|
||||
assert_eq!(ring.enqueue_one_with(|_| unreachable!()) as Result<()>,
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
ring.enqueue_one_with(|_| unreachable!()) as Result<()>,
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
|
||||
for i in 0..5 {
|
||||
assert_eq!(ring.dequeue_one_with(|e| Ok(*e)).unwrap(), i);
|
||||
assert!(!ring.is_full());
|
||||
}
|
||||
assert_eq!(ring.dequeue_one_with(|_| unreachable!()) as Result<()>,
|
||||
Err(Error::Exhausted));
|
||||
assert_eq!(
|
||||
ring.dequeue_one_with(|_| unreachable!()) as Result<()>,
|
||||
Err(Error::Exhausted)
|
||||
);
|
||||
assert!(ring.is_empty());
|
||||
}
|
||||
|
||||
|
@ -454,11 +498,14 @@ mod test {
|
|||
fn test_buffer_enqueue_many_with() {
|
||||
let mut ring = RingBuffer::new(vec![b'.'; 12]);
|
||||
|
||||
assert_eq!(ring.enqueue_many_with(|buf| {
|
||||
assert_eq!(buf.len(), 12);
|
||||
buf[0..2].copy_from_slice(b"ab");
|
||||
assert_eq!(
|
||||
ring.enqueue_many_with(|buf| {
|
||||
assert_eq!(buf.len(), 12);
|
||||
buf[0..2].copy_from_slice(b"ab");
|
||||
(2, true)
|
||||
}),
|
||||
(2, true)
|
||||
}), (2, true));
|
||||
);
|
||||
assert_eq!(ring.len(), 2);
|
||||
assert_eq!(&ring.storage[..], b"ab..........");
|
||||
|
||||
|
@ -545,12 +592,15 @@ mod test {
|
|||
|
||||
assert_eq!(ring.enqueue_slice(b"abcdefghijkl"), 12);
|
||||
|
||||
assert_eq!(ring.dequeue_many_with(|buf| {
|
||||
assert_eq!(buf.len(), 12);
|
||||
assert_eq!(buf, b"abcdefghijkl");
|
||||
buf[..4].copy_from_slice(b"....");
|
||||
assert_eq!(
|
||||
ring.dequeue_many_with(|buf| {
|
||||
assert_eq!(buf.len(), 12);
|
||||
assert_eq!(buf, b"abcdefghijkl");
|
||||
buf[..4].copy_from_slice(b"....");
|
||||
(4, true)
|
||||
}),
|
||||
(4, true)
|
||||
}), (4, true));
|
||||
);
|
||||
assert_eq!(ring.len(), 8);
|
||||
assert_eq!(&ring.storage[..], b"....efghijkl");
|
||||
|
||||
|
@ -637,7 +687,8 @@ mod test {
|
|||
}
|
||||
assert_eq!(&ring.storage[..], b"abcd........");
|
||||
|
||||
ring.enqueue_many(4);
|
||||
let buf_enqueued = ring.enqueue_many(4);
|
||||
assert_eq!(buf_enqueued.len(), 4);
|
||||
assert_eq!(ring.len(), 4);
|
||||
|
||||
{
|
||||
|
@ -679,17 +730,20 @@ mod test {
|
|||
let mut ring = RingBuffer::new(vec![b'.'; 12]);
|
||||
|
||||
assert_eq!(ring.get_allocated(16, 4), b"");
|
||||
assert_eq!(ring.get_allocated(0, 4), b"");
|
||||
assert_eq!(ring.get_allocated(0, 4), b"");
|
||||
|
||||
ring.enqueue_slice(b"abcd");
|
||||
let len_enqueued = ring.enqueue_slice(b"abcd");
|
||||
assert_eq!(ring.get_allocated(0, 8), b"abcd");
|
||||
assert_eq!(len_enqueued, 4);
|
||||
|
||||
ring.enqueue_slice(b"efghijkl");
|
||||
let len_enqueued = ring.enqueue_slice(b"efghijkl");
|
||||
ring.dequeue_many(4).copy_from_slice(b"....");
|
||||
assert_eq!(ring.get_allocated(4, 8), b"ijkl");
|
||||
assert_eq!(len_enqueued, 8);
|
||||
|
||||
ring.enqueue_slice(b"abcd");
|
||||
let len_enqueued = ring.enqueue_slice(b"abcd");
|
||||
assert_eq!(ring.get_allocated(4, 8), b"ijkl");
|
||||
assert_eq!(len_enqueued, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -711,7 +765,6 @@ mod test {
|
|||
let mut data = [0; 6];
|
||||
assert_eq!(ring.read_allocated(6, &mut data[..]), 3);
|
||||
assert_eq!(&data[..], b"mno\x00\x00\x00");
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -734,10 +787,11 @@ mod test {
|
|||
#[test]
|
||||
fn test_buffer_write_wholly() {
|
||||
let mut ring = RingBuffer::new(vec![b'.'; 8]);
|
||||
ring.enqueue_many(2).copy_from_slice(b"xx");
|
||||
ring.enqueue_many(2).copy_from_slice(b"xx");
|
||||
ring.enqueue_many(2).copy_from_slice(b"ab");
|
||||
ring.enqueue_many(2).copy_from_slice(b"cd");
|
||||
assert_eq!(ring.len(), 4);
|
||||
ring.dequeue_many(4);
|
||||
let buf_dequeued = ring.dequeue_many(4);
|
||||
assert_eq!(buf_dequeued, b"abcd");
|
||||
assert_eq!(ring.len(), 0);
|
||||
|
||||
let large = ring.enqueue_many(8);
|
||||
|
|
206
src/time.rs
206
src/time.rs
|
@ -10,7 +10,7 @@ absolute and relative time.
|
|||
[Duration]: struct.Duration.html
|
||||
*/
|
||||
|
||||
use core::{ops, fmt};
|
||||
use core::{fmt, ops};
|
||||
|
||||
/// A representation of an absolute time value.
|
||||
///
|
||||
|
@ -22,19 +22,42 @@ use core::{ops, fmt};
|
|||
/// * A value less than `0` indicates a time before the starting
|
||||
/// point.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Instant {
|
||||
pub millis: i64,
|
||||
micros: i64,
|
||||
}
|
||||
|
||||
impl Instant {
|
||||
/// Create a new `Instant` from a number of microseconds.
|
||||
pub fn from_micros<T: Into<i64>>(micros: T) -> Instant {
|
||||
Instant {
|
||||
micros: micros.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_micros_const(micros: i64) -> Instant {
|
||||
Instant { micros }
|
||||
}
|
||||
|
||||
/// Create a new `Instant` from a number of milliseconds.
|
||||
pub fn from_millis<T: Into<i64>>(millis: T) -> Instant {
|
||||
Instant { millis: millis.into() }
|
||||
Instant {
|
||||
micros: millis.into() * 1000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Instant` from a number of milliseconds.
|
||||
pub const fn from_millis_const(millis: i64) -> Instant {
|
||||
Instant {
|
||||
micros: millis * 1000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Instant` from a number of seconds.
|
||||
pub fn from_secs<T: Into<i64>>(secs: T) -> Instant {
|
||||
Instant { millis: secs.into() * 1000 }
|
||||
Instant {
|
||||
micros: secs.into() * 1000000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Instant` from the current [std::time::SystemTime].
|
||||
|
@ -51,19 +74,30 @@ impl Instant {
|
|||
/// The fractional number of milliseconds that have passed
|
||||
/// since the beginning of time.
|
||||
pub const fn millis(&self) -> i64 {
|
||||
self.millis % 1000
|
||||
self.micros % 1000000 / 1000
|
||||
}
|
||||
|
||||
/// The fractional number of microseconds that have passed
|
||||
/// since the beginning of time.
|
||||
pub const fn micros(&self) -> i64 {
|
||||
self.micros % 1000000
|
||||
}
|
||||
|
||||
/// The number of whole seconds that have passed since the
|
||||
/// beginning of time.
|
||||
pub const fn secs(&self) -> i64 {
|
||||
self.millis / 1000
|
||||
self.micros / 1000000
|
||||
}
|
||||
|
||||
/// The total number of milliseconds that have passed since
|
||||
/// the biginning of time.
|
||||
/// the beginning of time.
|
||||
pub const fn total_millis(&self) -> i64 {
|
||||
self.millis
|
||||
self.micros / 1000
|
||||
}
|
||||
/// The total number of milliseconds that have passed since
|
||||
/// the beginning of time.
|
||||
pub const fn total_micros(&self) -> i64 {
|
||||
self.micros
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,23 +105,24 @@ impl Instant {
|
|||
impl From<::std::time::Instant> for Instant {
|
||||
fn from(other: ::std::time::Instant) -> Instant {
|
||||
let elapsed = other.elapsed();
|
||||
Instant::from_millis((elapsed.as_secs() * 1_000) as i64 + elapsed.subsec_millis() as i64)
|
||||
Instant::from_micros((elapsed.as_secs() * 1_000000) as i64 + elapsed.subsec_micros() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl From<::std::time::SystemTime> for Instant {
|
||||
fn from(other: ::std::time::SystemTime) -> Instant {
|
||||
let n = other.duration_since(::std::time::UNIX_EPOCH)
|
||||
let n = other
|
||||
.duration_since(::std::time::UNIX_EPOCH)
|
||||
.expect("start time must not be before the unix epoch");
|
||||
Self::from_millis(n.as_secs() as i64 * 1000 + n.subsec_millis() as i64)
|
||||
Self::from_micros(n.as_secs() as i64 * 1000000 + n.subsec_micros() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Into<::std::time::SystemTime> for Instant {
|
||||
fn into(self) -> ::std::time::SystemTime {
|
||||
::std::time::UNIX_EPOCH + ::std::time::Duration::from_millis(self.millis as u64)
|
||||
impl From<Instant> for ::std::time::SystemTime {
|
||||
fn from(val: Instant) -> Self {
|
||||
::std::time::UNIX_EPOCH + ::std::time::Duration::from_micros(val.micros as u64)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,13 +136,13 @@ impl ops::Add<Duration> for Instant {
|
|||
type Output = Instant;
|
||||
|
||||
fn add(self, rhs: Duration) -> Instant {
|
||||
Instant::from_millis(self.millis + rhs.total_millis() as i64)
|
||||
Instant::from_micros(self.micros + rhs.total_micros() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::AddAssign<Duration> for Instant {
|
||||
fn add_assign(&mut self, rhs: Duration) {
|
||||
self.millis += rhs.total_millis() as i64;
|
||||
self.micros += rhs.total_micros() as i64;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,13 +150,13 @@ impl ops::Sub<Duration> for Instant {
|
|||
type Output = Instant;
|
||||
|
||||
fn sub(self, rhs: Duration) -> Instant {
|
||||
Instant::from_millis(self.millis - rhs.total_millis() as i64)
|
||||
Instant::from_micros(self.micros - rhs.total_micros() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::SubAssign<Duration> for Instant {
|
||||
fn sub_assign(&mut self, rhs: Duration) {
|
||||
self.millis -= rhs.total_millis() as i64;
|
||||
self.micros -= rhs.total_micros() as i64;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,40 +164,61 @@ impl ops::Sub<Instant> for Instant {
|
|||
type Output = Duration;
|
||||
|
||||
fn sub(self, rhs: Instant) -> Duration {
|
||||
Duration::from_millis((self.millis - rhs.millis).abs() as u64)
|
||||
Duration::from_micros((self.micros - rhs.micros).abs() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
/// A relative amount of time.
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Duration {
|
||||
pub millis: u64,
|
||||
micros: u64,
|
||||
}
|
||||
|
||||
impl Duration {
|
||||
pub const ZERO: Duration = Duration::from_micros(0);
|
||||
/// Create a new `Duration` from a number of microeconds.
|
||||
pub const fn from_micros(micros: u64) -> Duration {
|
||||
Duration { micros }
|
||||
}
|
||||
|
||||
/// Create a new `Duration` from a number of milliseconds.
|
||||
pub const fn from_millis(millis: u64) -> Duration {
|
||||
Duration { millis }
|
||||
Duration {
|
||||
micros: millis * 1000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Instant` from a number of seconds.
|
||||
pub const fn from_secs(secs: u64) -> Duration {
|
||||
Duration { millis: secs * 1000 }
|
||||
Duration {
|
||||
micros: secs * 1000000,
|
||||
}
|
||||
}
|
||||
|
||||
/// The fractional number of milliseconds in this `Duration`.
|
||||
pub const fn millis(&self) -> u64 {
|
||||
self.millis % 1000
|
||||
self.micros / 1000 % 1000
|
||||
}
|
||||
|
||||
/// The fractional number of milliseconds in this `Duration`.
|
||||
pub const fn micros(&self) -> u64 {
|
||||
self.micros % 1000000
|
||||
}
|
||||
|
||||
/// The number of whole seconds in this `Duration`.
|
||||
pub const fn secs(&self) -> u64 {
|
||||
self.millis / 1000
|
||||
self.micros / 1000000
|
||||
}
|
||||
|
||||
/// The total number of milliseconds in this `Duration`.
|
||||
pub const fn total_millis(&self) -> u64 {
|
||||
self.millis
|
||||
self.micros / 1000
|
||||
}
|
||||
|
||||
/// The total number of microseconds in this `Duration`.
|
||||
pub const fn total_micros(&self) -> u64 {
|
||||
self.micros
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,13 +232,13 @@ impl ops::Add<Duration> for Duration {
|
|||
type Output = Duration;
|
||||
|
||||
fn add(self, rhs: Duration) -> Duration {
|
||||
Duration::from_millis(self.millis + rhs.total_millis())
|
||||
Duration::from_micros(self.micros + rhs.total_micros())
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::AddAssign<Duration> for Duration {
|
||||
fn add_assign(&mut self, rhs: Duration) {
|
||||
self.millis += rhs.total_millis();
|
||||
self.micros += rhs.total_micros();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,15 +246,20 @@ impl ops::Sub<Duration> for Duration {
|
|||
type Output = Duration;
|
||||
|
||||
fn sub(self, rhs: Duration) -> Duration {
|
||||
Duration::from_millis(
|
||||
self.millis.checked_sub(rhs.total_millis()).expect("overflow when subtracting durations"))
|
||||
Duration::from_micros(
|
||||
self.micros
|
||||
.checked_sub(rhs.total_micros())
|
||||
.expect("overflow when subtracting durations"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::SubAssign<Duration> for Duration {
|
||||
fn sub_assign(&mut self, rhs: Duration) {
|
||||
self.millis = self.millis.checked_sub(
|
||||
rhs.total_millis()).expect("overflow when subtracting durations");
|
||||
self.micros = self
|
||||
.micros
|
||||
.checked_sub(rhs.total_micros())
|
||||
.expect("overflow when subtracting durations");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,13 +267,13 @@ impl ops::Mul<u32> for Duration {
|
|||
type Output = Duration;
|
||||
|
||||
fn mul(self, rhs: u32) -> Duration {
|
||||
Duration::from_millis(self.millis * rhs as u64)
|
||||
Duration::from_micros(self.micros * rhs as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::MulAssign<u32> for Duration {
|
||||
fn mul_assign(&mut self, rhs: u32) {
|
||||
self.millis *= rhs as u64;
|
||||
self.micros *= rhs as u64;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,29 +281,53 @@ impl ops::Div<u32> for Duration {
|
|||
type Output = Duration;
|
||||
|
||||
fn div(self, rhs: u32) -> Duration {
|
||||
Duration::from_millis(self.millis / rhs as u64)
|
||||
Duration::from_micros(self.micros / rhs as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DivAssign<u32> for Duration {
|
||||
fn div_assign(&mut self, rhs: u32) {
|
||||
self.millis /= rhs as u64;
|
||||
self.micros /= rhs as u64;
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Shl<u32> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn shl(self, rhs: u32) -> Duration {
|
||||
Duration::from_micros(self.micros << rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::ShlAssign<u32> for Duration {
|
||||
fn shl_assign(&mut self, rhs: u32) {
|
||||
self.micros <<= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Shr<u32> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn shr(self, rhs: u32) -> Duration {
|
||||
Duration::from_micros(self.micros >> rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::ShrAssign<u32> for Duration {
|
||||
fn shr_assign(&mut self, rhs: u32) {
|
||||
self.micros >>= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::core::time::Duration> for Duration {
|
||||
fn from(other: ::core::time::Duration) -> Duration {
|
||||
Duration::from_millis(
|
||||
other.as_secs() * 1000 + other.subsec_millis() as u64
|
||||
)
|
||||
Duration::from_micros(other.as_secs() * 1000000 + other.subsec_micros() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<::core::time::Duration> for Duration {
|
||||
fn into(self) -> ::core::time::Duration {
|
||||
::core::time::Duration::from_millis(
|
||||
self.total_millis()
|
||||
)
|
||||
impl From<Duration> for ::core::time::Duration {
|
||||
fn from(val: Duration) -> Self {
|
||||
::core::time::Duration::from_micros(val.total_micros())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,9 +338,15 @@ mod test {
|
|||
#[test]
|
||||
fn test_instant_ops() {
|
||||
// std::ops::Add
|
||||
assert_eq!(Instant::from_millis(4) + Duration::from_millis(6), Instant::from_millis(10));
|
||||
assert_eq!(
|
||||
Instant::from_millis(4) + Duration::from_millis(6),
|
||||
Instant::from_millis(10)
|
||||
);
|
||||
// std::ops::Sub
|
||||
assert_eq!(Instant::from_millis(7) - Duration::from_millis(5), Instant::from_millis(2));
|
||||
assert_eq!(
|
||||
Instant::from_millis(7) - Duration::from_millis(5),
|
||||
Instant::from_millis(2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -276,23 +367,34 @@ mod test {
|
|||
#[cfg(feature = "std")]
|
||||
fn test_instant_conversions() {
|
||||
let mut epoc: ::std::time::SystemTime = Instant::from_millis(0).into();
|
||||
assert_eq!(Instant::from(::std::time::UNIX_EPOCH),
|
||||
Instant::from_millis(0));
|
||||
assert_eq!(
|
||||
Instant::from(::std::time::UNIX_EPOCH),
|
||||
Instant::from_millis(0)
|
||||
);
|
||||
assert_eq!(epoc, ::std::time::UNIX_EPOCH);
|
||||
epoc = Instant::from_millis(2085955200i64 * 1000).into();
|
||||
assert_eq!(epoc, ::std::time::UNIX_EPOCH + ::std::time::Duration::from_secs(2085955200));
|
||||
assert_eq!(
|
||||
epoc,
|
||||
::std::time::UNIX_EPOCH + ::std::time::Duration::from_secs(2085955200)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duration_ops() {
|
||||
// std::ops::Add
|
||||
assert_eq!(Duration::from_millis(40) + Duration::from_millis(2), Duration::from_millis(42));
|
||||
assert_eq!(
|
||||
Duration::from_millis(40) + Duration::from_millis(2),
|
||||
Duration::from_millis(42)
|
||||
);
|
||||
// std::ops::Sub
|
||||
assert_eq!(Duration::from_millis(555) - Duration::from_millis(42), Duration::from_millis(513));
|
||||
assert_eq!(
|
||||
Duration::from_millis(555) - Duration::from_millis(42),
|
||||
Duration::from_millis(513)
|
||||
);
|
||||
// std::ops::Mul
|
||||
assert_eq!(Duration::from_millis(13) * 22, Duration::from_millis(286));
|
||||
// std::ops::Div
|
||||
assert_eq!(Duration::from_millis(53) / 4, Duration::from_millis(13));
|
||||
assert_eq!(Duration::from_millis(53) / 4, Duration::from_micros(13250));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -305,7 +407,7 @@ mod test {
|
|||
duration *= 4;
|
||||
assert_eq!(duration, Duration::from_millis(20936));
|
||||
duration /= 5;
|
||||
assert_eq!(duration, Duration::from_millis(4187));
|
||||
assert_eq!(duration, Duration::from_micros(4187200));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
148
src/wire/arp.rs
148
src/wire/arp.rs
|
@ -1,5 +1,5 @@
|
|||
use core::fmt;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::fmt;
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
|
@ -22,8 +22,9 @@ enum_with_unknown! {
|
|||
|
||||
/// A read/write wrapper around an Address Resolution Protocol packet buffer.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
mod field {
|
||||
|
@ -33,9 +34,9 @@ mod field {
|
|||
|
||||
pub const HTYPE: Field = 0..2;
|
||||
pub const PTYPE: Field = 2..4;
|
||||
pub const HLEN: usize = 4;
|
||||
pub const PLEN: usize = 5;
|
||||
pub const OPER: Field = 6..8;
|
||||
pub const HLEN: usize = 4;
|
||||
pub const PLEN: usize = 5;
|
||||
pub const OPER: Field = 6..8;
|
||||
|
||||
#[inline]
|
||||
pub fn SHA(hardware_len: u8, _protocol_len: u8) -> Field {
|
||||
|
@ -253,6 +254,7 @@ use crate::wire::{EthernetAddress, Ipv4Address};
|
|||
|
||||
/// A high-level representation of an Address Resolution Protocol packet.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Repr {
|
||||
/// An Ethernet and IPv4 Address Resolution Protocol packet.
|
||||
|
@ -261,7 +263,7 @@ pub enum Repr {
|
|||
source_hardware_addr: EthernetAddress,
|
||||
source_protocol_addr: Ipv4Address,
|
||||
target_hardware_addr: EthernetAddress,
|
||||
target_protocol_addr: Ipv4Address
|
||||
target_protocol_addr: Ipv4Address,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -269,22 +271,20 @@ impl Repr {
|
|||
/// Parse an Address Resolution Protocol packet and return a high-level representation,
|
||||
/// or return `Err(Error::Unrecognized)` if the packet is not recognized.
|
||||
pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr> {
|
||||
match (packet.hardware_type(), packet.protocol_type(),
|
||||
packet.hardware_len(), packet.protocol_len()) {
|
||||
(Hardware::Ethernet, Protocol::Ipv4, 6, 4) => {
|
||||
Ok(Repr::EthernetIpv4 {
|
||||
operation: packet.operation(),
|
||||
source_hardware_addr:
|
||||
EthernetAddress::from_bytes(packet.source_hardware_addr()),
|
||||
source_protocol_addr:
|
||||
Ipv4Address::from_bytes(packet.source_protocol_addr()),
|
||||
target_hardware_addr:
|
||||
EthernetAddress::from_bytes(packet.target_hardware_addr()),
|
||||
target_protocol_addr:
|
||||
Ipv4Address::from_bytes(packet.target_protocol_addr())
|
||||
})
|
||||
},
|
||||
_ => Err(Error::Unrecognized)
|
||||
match (
|
||||
packet.hardware_type(),
|
||||
packet.protocol_type(),
|
||||
packet.hardware_len(),
|
||||
packet.protocol_len(),
|
||||
) {
|
||||
(Hardware::Ethernet, Protocol::Ipv4, 6, 4) => Ok(Repr::EthernetIpv4 {
|
||||
operation: packet.operation(),
|
||||
source_hardware_addr: EthernetAddress::from_bytes(packet.source_hardware_addr()),
|
||||
source_protocol_addr: Ipv4Address::from_bytes(packet.source_protocol_addr()),
|
||||
target_hardware_addr: EthernetAddress::from_bytes(packet.target_hardware_addr()),
|
||||
target_protocol_addr: Ipv4Address::from_bytes(packet.target_protocol_addr()),
|
||||
}),
|
||||
_ => Err(Error::Unrecognized),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,8 +300,10 @@ impl Repr {
|
|||
match *self {
|
||||
Repr::EthernetIpv4 {
|
||||
operation,
|
||||
source_hardware_addr, source_protocol_addr,
|
||||
target_hardware_addr, target_protocol_addr
|
||||
source_hardware_addr,
|
||||
source_protocol_addr,
|
||||
target_hardware_addr,
|
||||
target_protocol_addr,
|
||||
} => {
|
||||
packet.set_hardware_type(Hardware::Ethernet);
|
||||
packet.set_protocol_type(Protocol::Ipv4);
|
||||
|
@ -312,7 +314,7 @@ impl Repr {
|
|||
packet.set_source_protocol_addr(source_protocol_addr.as_bytes());
|
||||
packet.set_target_hardware_addr(target_hardware_addr.as_bytes());
|
||||
packet.set_target_protocol_addr(target_protocol_addr.as_bytes());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -323,13 +325,23 @@ impl<T: AsRef<[u8]>> fmt::Display for Packet<T> {
|
|||
Ok(repr) => write!(f, "{}", repr),
|
||||
_ => {
|
||||
write!(f, "ARP (unrecognized)")?;
|
||||
write!(f, " htype={:?} ptype={:?} hlen={:?} plen={:?} op={:?}",
|
||||
self.hardware_type(), self.protocol_type(),
|
||||
self.hardware_len(), self.protocol_len(),
|
||||
self.operation())?;
|
||||
write!(f, " sha={:?} spa={:?} tha={:?} tpa={:?}",
|
||||
self.source_hardware_addr(), self.source_protocol_addr(),
|
||||
self.target_hardware_addr(), self.target_protocol_addr())?;
|
||||
write!(
|
||||
f,
|
||||
" htype={:?} ptype={:?} hlen={:?} plen={:?} op={:?}",
|
||||
self.hardware_type(),
|
||||
self.protocol_type(),
|
||||
self.hardware_len(),
|
||||
self.protocol_len(),
|
||||
self.operation()
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
" sha={:?} spa={:?} tha={:?} tpa={:?}",
|
||||
self.source_hardware_addr(),
|
||||
self.source_protocol_addr(),
|
||||
self.target_hardware_addr(),
|
||||
self.target_protocol_addr()
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -341,26 +353,36 @@ impl fmt::Display for Repr {
|
|||
match *self {
|
||||
Repr::EthernetIpv4 {
|
||||
operation,
|
||||
source_hardware_addr, source_protocol_addr,
|
||||
target_hardware_addr, target_protocol_addr
|
||||
source_hardware_addr,
|
||||
source_protocol_addr,
|
||||
target_hardware_addr,
|
||||
target_protocol_addr,
|
||||
} => {
|
||||
write!(f, "ARP type=Ethernet+IPv4 src={}/{} tgt={}/{} op={:?}",
|
||||
source_hardware_addr, source_protocol_addr,
|
||||
target_hardware_addr, target_protocol_addr,
|
||||
operation)
|
||||
},
|
||||
write!(
|
||||
f,
|
||||
"ARP type=Ethernet+IPv4 src={}/{} tgt={}/{} op={:?}",
|
||||
source_hardware_addr,
|
||||
source_protocol_addr,
|
||||
target_hardware_addr,
|
||||
target_protocol_addr,
|
||||
operation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
match Packet::new_checked(buffer) {
|
||||
Err(err) => write!(f, "{}({})", indent, err),
|
||||
Ok(packet) => write!(f, "{}{}", indent, packet)
|
||||
Ok(packet) => write!(f, "{}{}", indent, packet),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -369,16 +391,10 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
static PACKET_BYTES: [u8; 28] =
|
||||
[0x00, 0x01,
|
||||
0x08, 0x00,
|
||||
0x06,
|
||||
0x04,
|
||||
0x00, 0x01,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x21, 0x22, 0x23, 0x24,
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
||||
0x41, 0x42, 0x43, 0x44];
|
||||
static PACKET_BYTES: [u8; 28] = [
|
||||
0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x21,
|
||||
0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x41, 0x42, 0x43, 0x44,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_deconstruct() {
|
||||
|
@ -388,9 +404,15 @@ mod test {
|
|||
assert_eq!(packet.hardware_len(), 6);
|
||||
assert_eq!(packet.protocol_len(), 4);
|
||||
assert_eq!(packet.operation(), Operation::Request);
|
||||
assert_eq!(packet.source_hardware_addr(), &[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]);
|
||||
assert_eq!(
|
||||
packet.source_hardware_addr(),
|
||||
&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]
|
||||
);
|
||||
assert_eq!(packet.source_protocol_addr(), &[0x21, 0x22, 0x23, 0x24]);
|
||||
assert_eq!(packet.target_hardware_addr(), &[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]);
|
||||
assert_eq!(
|
||||
packet.target_hardware_addr(),
|
||||
&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]
|
||||
);
|
||||
assert_eq!(packet.target_protocol_addr(), &[0x41, 0x42, 0x43, 0x44]);
|
||||
}
|
||||
|
||||
|
@ -413,14 +435,14 @@ mod test {
|
|||
fn packet_repr() -> Repr {
|
||||
Repr::EthernetIpv4 {
|
||||
operation: Operation::Request,
|
||||
source_hardware_addr:
|
||||
EthernetAddress::from_bytes(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]),
|
||||
source_protocol_addr:
|
||||
Ipv4Address::from_bytes(&[0x21, 0x22, 0x23, 0x24]),
|
||||
target_hardware_addr:
|
||||
EthernetAddress::from_bytes(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]),
|
||||
target_protocol_addr:
|
||||
Ipv4Address::from_bytes(&[0x41, 0x42, 0x43, 0x44])
|
||||
source_hardware_addr: EthernetAddress::from_bytes(&[
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
]),
|
||||
source_protocol_addr: Ipv4Address::from_bytes(&[0x21, 0x22, 0x23, 0x24]),
|
||||
target_hardware_addr: EthernetAddress::from_bytes(&[
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
||||
]),
|
||||
target_protocol_addr: Ipv4Address::from_bytes(&[0x41, 0x42, 0x43, 0x44]),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
// See https://tools.ietf.org/html/rfc2131 for the DHCP specification.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::wire::{EthernetAddress, Ipv4Address};
|
||||
use crate::wire::arp::Hardware;
|
||||
use crate::wire::{EthernetAddress, Ipv4Address};
|
||||
use crate::{Error, Result};
|
||||
|
||||
pub const SERVER_PORT: u16 = 67;
|
||||
pub const CLIENT_PORT: u16 = 68;
|
||||
pub const MAX_DNS_SERVER_COUNT: usize = 3;
|
||||
|
||||
const DHCP_MAGIC_NUMBER: u32 = 0x63825363;
|
||||
|
||||
|
@ -30,11 +35,20 @@ enum_with_unknown! {
|
|||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct Flags: u16 {
|
||||
const BROADCAST = 0b1000_0000_0000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageType {
|
||||
fn opcode(&self) -> OpCode {
|
||||
match *self {
|
||||
MessageType::Discover | MessageType::Inform | MessageType::Request |
|
||||
MessageType::Decline | MessageType::Release => OpCode::Request,
|
||||
MessageType::Discover
|
||||
| MessageType::Inform
|
||||
| MessageType::Request
|
||||
| MessageType::Decline
|
||||
| MessageType::Release => OpCode::Request,
|
||||
MessageType::Offer | MessageType::Ack | MessageType::Nak => OpCode::Reply,
|
||||
MessageType::Unknown(_) => OpCode::Unknown(0),
|
||||
}
|
||||
|
@ -43,6 +57,7 @@ impl MessageType {
|
|||
|
||||
/// A representation of a single DHCP option.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum DhcpOption<'a> {
|
||||
EndOfList,
|
||||
Pad,
|
||||
|
@ -54,7 +69,7 @@ pub enum DhcpOption<'a> {
|
|||
Router(Ipv4Address),
|
||||
SubnetMask(Ipv4Address),
|
||||
MaximumDhcpMessageSize(u16),
|
||||
Other { kind: u8, data: &'a [u8] }
|
||||
Other { kind: u8, data: &'a [u8] },
|
||||
}
|
||||
|
||||
impl<'a> DhcpOption<'a> {
|
||||
|
@ -76,12 +91,10 @@ impl<'a> DhcpOption<'a> {
|
|||
skip_len = length + 2;
|
||||
let data = buffer.get(2..skip_len).ok_or(Error::Truncated)?;
|
||||
match (kind, length) {
|
||||
(field::OPT_END, _) |
|
||||
(field::OPT_PAD, _) =>
|
||||
unreachable!(),
|
||||
(field::OPT_END, _) | (field::OPT_PAD, _) => unreachable!(),
|
||||
(field::OPT_DHCP_MESSAGE_TYPE, 1) => {
|
||||
option = DhcpOption::MessageType(MessageType::from(data[0]));
|
||||
},
|
||||
}
|
||||
(field::OPT_REQUESTED_IP, 4) => {
|
||||
option = DhcpOption::RequestedIp(Ipv4Address::from_bytes(data));
|
||||
}
|
||||
|
@ -90,7 +103,8 @@ impl<'a> DhcpOption<'a> {
|
|||
if hardware_type != Hardware::Ethernet {
|
||||
return Err(Error::Unrecognized);
|
||||
}
|
||||
option = DhcpOption::ClientIdentifier(EthernetAddress::from_bytes(&data[1..]));
|
||||
option =
|
||||
DhcpOption::ClientIdentifier(EthernetAddress::from_bytes(&data[1..]));
|
||||
}
|
||||
(field::OPT_SERVER_IDENTIFIER, 4) => {
|
||||
option = DhcpOption::ServerIdentifier(Ipv4Address::from_bytes(data));
|
||||
|
@ -102,13 +116,20 @@ impl<'a> DhcpOption<'a> {
|
|||
option = DhcpOption::SubnetMask(Ipv4Address::from_bytes(data));
|
||||
}
|
||||
(field::OPT_MAX_DHCP_MESSAGE_SIZE, 2) => {
|
||||
option = DhcpOption::MaximumDhcpMessageSize(u16::from_be_bytes([data[0], data[1]]));
|
||||
option = DhcpOption::MaximumDhcpMessageSize(u16::from_be_bytes([
|
||||
data[0], data[1],
|
||||
]));
|
||||
}
|
||||
(field::OPT_IP_LEASE_TIME, 4) => {
|
||||
option = DhcpOption::IpLeaseTime(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
|
||||
option = DhcpOption::IpLeaseTime(u32::from_be_bytes([
|
||||
data[0], data[1], data[2], data[3],
|
||||
]))
|
||||
}
|
||||
(_, _) => {
|
||||
option = DhcpOption::Other { kind: kind, data: data };
|
||||
option = DhcpOption::Other {
|
||||
kind: kind,
|
||||
data: data,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,20 +142,14 @@ impl<'a> DhcpOption<'a> {
|
|||
&DhcpOption::EndOfList => 1,
|
||||
&DhcpOption::Pad => 1,
|
||||
&DhcpOption::MessageType(_) => 3,
|
||||
&DhcpOption::ClientIdentifier(eth_addr) => {
|
||||
3 + eth_addr.as_bytes().len()
|
||||
}
|
||||
&DhcpOption::RequestedIp(ip) |
|
||||
&DhcpOption::ServerIdentifier(ip) |
|
||||
&DhcpOption::Router(ip) |
|
||||
&DhcpOption::SubnetMask(ip) => {
|
||||
2 + ip.as_bytes().len()
|
||||
},
|
||||
&DhcpOption::MaximumDhcpMessageSize(_) => {
|
||||
4
|
||||
}
|
||||
&DhcpOption::ClientIdentifier(eth_addr) => 3 + eth_addr.as_bytes().len(),
|
||||
&DhcpOption::RequestedIp(ip)
|
||||
| &DhcpOption::ServerIdentifier(ip)
|
||||
| &DhcpOption::Router(ip)
|
||||
| &DhcpOption::SubnetMask(ip) => 2 + ip.as_bytes().len(),
|
||||
&DhcpOption::MaximumDhcpMessageSize(_) => 4,
|
||||
&DhcpOption::IpLeaseTime(_) => 6,
|
||||
&DhcpOption::Other { data, .. } => 2 + data.len()
|
||||
&DhcpOption::Other { data, .. } => 2 + data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,19 +178,19 @@ impl<'a> DhcpOption<'a> {
|
|||
buffer[2] = u16::from(Hardware::Ethernet) as u8;
|
||||
buffer[3..9].copy_from_slice(eth_addr.as_bytes());
|
||||
}
|
||||
DhcpOption::RequestedIp(ip) => {
|
||||
DhcpOption::RequestedIp(ip) => {
|
||||
buffer[0] = field::OPT_REQUESTED_IP;
|
||||
buffer[2..6].copy_from_slice(ip.as_bytes());
|
||||
}
|
||||
DhcpOption::ServerIdentifier(ip) => {
|
||||
DhcpOption::ServerIdentifier(ip) => {
|
||||
buffer[0] = field::OPT_SERVER_IDENTIFIER;
|
||||
buffer[2..6].copy_from_slice(ip.as_bytes());
|
||||
}
|
||||
DhcpOption::Router(ip) => {
|
||||
DhcpOption::Router(ip) => {
|
||||
buffer[0] = field::OPT_ROUTER;
|
||||
buffer[2..6].copy_from_slice(ip.as_bytes());
|
||||
}
|
||||
DhcpOption::SubnetMask(mask) => {
|
||||
DhcpOption::SubnetMask(mask) => {
|
||||
buffer[0] = field::OPT_SUBNET_MASK;
|
||||
buffer[2..6].copy_from_slice(mask.as_bytes());
|
||||
}
|
||||
|
@ -187,7 +202,10 @@ impl<'a> DhcpOption<'a> {
|
|||
buffer[0] = field::OPT_IP_LEASE_TIME;
|
||||
buffer[2..6].copy_from_slice(&lease_time.to_be_bytes()[..]);
|
||||
}
|
||||
DhcpOption::Other { kind, data: provided } => {
|
||||
DhcpOption::Other {
|
||||
kind,
|
||||
data: provided,
|
||||
} => {
|
||||
buffer[0] = kind;
|
||||
buffer[2..skip_length].copy_from_slice(provided);
|
||||
}
|
||||
|
@ -200,8 +218,9 @@ impl<'a> DhcpOption<'a> {
|
|||
|
||||
/// A read/write wrapper around a Dynamic Host Configuration Protocol packet buffer.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
pub(crate) mod field {
|
||||
|
@ -440,10 +459,9 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
Ipv4Address::from_bytes(field)
|
||||
}
|
||||
|
||||
/// Returns true if the broadcast flag is set.
|
||||
pub fn broadcast_flag(&self) -> bool {
|
||||
pub fn flags(&self) -> Flags {
|
||||
let field = &self.buffer.as_ref()[field::FLAGS];
|
||||
NetworkEndian::read_u16(field) & 0b1 == 0b1
|
||||
Flags::from_bits_truncate(NetworkEndian::read_u16(field))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -563,10 +581,10 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
field.copy_from_slice(value.as_bytes());
|
||||
}
|
||||
|
||||
/// Sets the broadcast flag to the specified value.
|
||||
pub fn set_broadcast_flag(&mut self, value: bool) {
|
||||
/// Sets the flags to the specified value.
|
||||
pub fn set_flags(&mut self, val: Flags) {
|
||||
let field = &mut self.buffer.as_mut()[field::FLAGS];
|
||||
NetworkEndian::write_u16(field, if value { 1 } else { 0 });
|
||||
NetworkEndian::write_u16(field, val.bits());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,6 +642,7 @@ impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
|
|||
///
|
||||
/// The `options` field has a variable length.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr<'a> {
|
||||
/// This field is also known as `op` in the RFC. It indicates the type of DHCP message this
|
||||
/// packet represents.
|
||||
|
@ -680,11 +699,11 @@ pub struct Repr<'a> {
|
|||
/// the client is interested in.
|
||||
pub parameter_request_list: Option<&'a [u8]>,
|
||||
/// DNS servers
|
||||
pub dns_servers: Option<[Option<Ipv4Address>; 3]>,
|
||||
pub dns_servers: Option<[Option<Ipv4Address>; MAX_DNS_SERVER_COUNT]>,
|
||||
/// The maximum size dhcp packet the interface can receive
|
||||
pub max_size: Option<u16>,
|
||||
/// The DHCP IP lease duration, specified in seconds.
|
||||
pub lease_duration: Option<u32>
|
||||
pub lease_duration: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
|
@ -693,19 +712,43 @@ impl<'a> Repr<'a> {
|
|||
let mut len = field::OPTIONS.start;
|
||||
// message type and end-of-options options
|
||||
len += 3 + 1;
|
||||
if self.requested_ip.is_some() { len += 6; }
|
||||
if self.client_identifier.is_some() { len += 9; }
|
||||
if self.server_identifier.is_some() { len += 6; }
|
||||
if self.max_size.is_some() { len += 4; }
|
||||
if let Some(list) = self.parameter_request_list { len += list.len() + 2; }
|
||||
if self.requested_ip.is_some() {
|
||||
len += 6;
|
||||
}
|
||||
if self.client_identifier.is_some() {
|
||||
len += 9;
|
||||
}
|
||||
if self.server_identifier.is_some() {
|
||||
len += 6;
|
||||
}
|
||||
if self.max_size.is_some() {
|
||||
len += 4;
|
||||
}
|
||||
if self.router.is_some() {
|
||||
len += 6;
|
||||
}
|
||||
if self.subnet_mask.is_some() {
|
||||
len += 6;
|
||||
}
|
||||
if self.lease_duration.is_some() {
|
||||
len += 6;
|
||||
}
|
||||
if let Some(dns_servers) = self.dns_servers {
|
||||
len += 2;
|
||||
len += dns_servers.iter().flatten().count() * core::mem::size_of::<u32>();
|
||||
}
|
||||
if let Some(list) = self.parameter_request_list {
|
||||
len += list.len() + 2;
|
||||
}
|
||||
|
||||
len
|
||||
}
|
||||
|
||||
/// Parse a DHCP packet and return a high-level representation.
|
||||
pub fn parse<T>(packet: &Packet<&'a T>) -> Result<Self>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
let transaction_id = packet.transaction_id();
|
||||
let client_hardware_address = packet.client_hardware_address();
|
||||
let client_ip = packet.client_ip();
|
||||
|
@ -743,12 +786,12 @@ impl<'a> Repr<'a> {
|
|||
let (next_options, option) = DhcpOption::parse(options)?;
|
||||
match option {
|
||||
DhcpOption::EndOfList => break,
|
||||
DhcpOption::Pad => {},
|
||||
DhcpOption::Pad => {}
|
||||
DhcpOption::MessageType(value) => {
|
||||
if value.opcode() == packet.opcode() {
|
||||
message_type = Ok(value);
|
||||
}
|
||||
},
|
||||
}
|
||||
DhcpOption::RequestedIp(ip) => {
|
||||
requested_ip = Some(ip);
|
||||
}
|
||||
|
@ -763,34 +806,56 @@ impl<'a> Repr<'a> {
|
|||
}
|
||||
DhcpOption::SubnetMask(mask) => {
|
||||
subnet_mask = Some(mask);
|
||||
},
|
||||
}
|
||||
DhcpOption::MaximumDhcpMessageSize(size) => {
|
||||
max_size = Some(size);
|
||||
}
|
||||
DhcpOption::IpLeaseTime(duration) => {
|
||||
lease_duration = Some(duration);
|
||||
}
|
||||
DhcpOption::Other {kind: field::OPT_PARAMETER_REQUEST_LIST, data} => {
|
||||
DhcpOption::Other {
|
||||
kind: field::OPT_PARAMETER_REQUEST_LIST,
|
||||
data,
|
||||
} => {
|
||||
parameter_request_list = Some(data);
|
||||
}
|
||||
DhcpOption::Other {kind: field::OPT_DOMAIN_NAME_SERVER, data} => {
|
||||
let mut servers = [None; 3];
|
||||
for (server, chunk) in servers.iter_mut().zip(data.chunks(4)) {
|
||||
DhcpOption::Other {
|
||||
kind: field::OPT_DOMAIN_NAME_SERVER,
|
||||
data,
|
||||
} => {
|
||||
let mut servers = [None; MAX_DNS_SERVER_COUNT];
|
||||
let chunk_size = 4;
|
||||
for (server, chunk) in servers.iter_mut().zip(data.chunks(chunk_size)) {
|
||||
if chunk.len() != chunk_size {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
*server = Some(Ipv4Address::from_bytes(chunk));
|
||||
}
|
||||
dns_servers = Some(servers);
|
||||
}
|
||||
DhcpOption::Other {..} => {}
|
||||
DhcpOption::Other { .. } => {}
|
||||
}
|
||||
options = next_options;
|
||||
}
|
||||
|
||||
let broadcast = packet.broadcast_flag();
|
||||
let broadcast = packet.flags().contains(Flags::BROADCAST);
|
||||
|
||||
Ok(Repr {
|
||||
transaction_id, client_hardware_address, client_ip, your_ip, server_ip, relay_agent_ip,
|
||||
broadcast, requested_ip, server_identifier, router,
|
||||
subnet_mask, client_identifier, parameter_request_list, dns_servers, max_size,
|
||||
transaction_id,
|
||||
client_hardware_address,
|
||||
client_ip,
|
||||
your_ip,
|
||||
server_ip,
|
||||
relay_agent_ip,
|
||||
broadcast,
|
||||
requested_ip,
|
||||
server_identifier,
|
||||
router,
|
||||
subnet_mask,
|
||||
client_identifier,
|
||||
parameter_request_list,
|
||||
dns_servers,
|
||||
max_size,
|
||||
lease_duration,
|
||||
message_type: message_type?,
|
||||
})
|
||||
|
@ -799,7 +864,9 @@ impl<'a> Repr<'a> {
|
|||
/// Emit a high-level representation into a Dynamic Host
|
||||
/// Configuration Protocol packet.
|
||||
pub fn emit<T>(&self, packet: &mut Packet<&mut T>) -> Result<()>
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||
where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
packet.set_sname_and_boot_file_to_zero();
|
||||
packet.set_opcode(self.message_type.opcode());
|
||||
packet.set_hardware_type(Hardware::Ethernet);
|
||||
|
@ -813,35 +880,62 @@ impl<'a> Repr<'a> {
|
|||
packet.set_your_ip(self.your_ip);
|
||||
packet.set_server_ip(self.server_ip);
|
||||
packet.set_relay_agent_ip(self.relay_agent_ip);
|
||||
packet.set_broadcast_flag(self.broadcast);
|
||||
|
||||
let mut flags = Flags::empty();
|
||||
if self.broadcast {
|
||||
flags |= Flags::BROADCAST;
|
||||
}
|
||||
packet.set_flags(flags);
|
||||
|
||||
{
|
||||
let mut options = packet.options_mut()?;
|
||||
let tmp = options; options = DhcpOption::MessageType(self.message_type).emit(tmp);
|
||||
options = DhcpOption::MessageType(self.message_type).emit(options);
|
||||
if let Some(eth_addr) = self.client_identifier {
|
||||
let tmp = options; options = DhcpOption::ClientIdentifier(eth_addr).emit(tmp);
|
||||
options = DhcpOption::ClientIdentifier(eth_addr).emit(options);
|
||||
}
|
||||
if let Some(ip) = self.server_identifier {
|
||||
let tmp = options; options = DhcpOption::ServerIdentifier(ip).emit(tmp);
|
||||
options = DhcpOption::ServerIdentifier(ip).emit(options);
|
||||
}
|
||||
if let Some(ip) = self.router {
|
||||
let tmp = options; options = DhcpOption::Router(ip).emit(tmp);
|
||||
options = DhcpOption::Router(ip).emit(options);
|
||||
}
|
||||
if let Some(ip) = self.subnet_mask {
|
||||
let tmp = options; options = DhcpOption::SubnetMask(ip).emit(tmp);
|
||||
options = DhcpOption::SubnetMask(ip).emit(options);
|
||||
}
|
||||
if let Some(ip) = self.requested_ip {
|
||||
let tmp = options; options = DhcpOption::RequestedIp(ip).emit(tmp);
|
||||
options = DhcpOption::RequestedIp(ip).emit(options);
|
||||
}
|
||||
if let Some(size) = self.max_size {
|
||||
let tmp = options; options = DhcpOption::MaximumDhcpMessageSize(size).emit(tmp);
|
||||
options = DhcpOption::MaximumDhcpMessageSize(size).emit(options);
|
||||
}
|
||||
if let Some(duration) = self.lease_duration {
|
||||
let tmp = options; options = DhcpOption::IpLeaseTime(duration).emit(tmp);
|
||||
options = DhcpOption::IpLeaseTime(duration).emit(options);
|
||||
}
|
||||
if let Some(dns_servers) = self.dns_servers {
|
||||
const IP_SIZE: usize = core::mem::size_of::<u32>();
|
||||
let mut servers = [0; MAX_DNS_SERVER_COUNT * IP_SIZE];
|
||||
|
||||
let data_len = dns_servers
|
||||
.iter()
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.inspect(|(i, ip)| {
|
||||
servers[(i * IP_SIZE)..((i + 1) * IP_SIZE)].copy_from_slice(ip.as_bytes());
|
||||
})
|
||||
.count()
|
||||
* IP_SIZE;
|
||||
let option = DhcpOption::Other {
|
||||
kind: field::OPT_DOMAIN_NAME_SERVER,
|
||||
data: &servers[..data_len],
|
||||
};
|
||||
options = option.emit(options);
|
||||
}
|
||||
if let Some(list) = self.parameter_request_list {
|
||||
let option = DhcpOption::Other{ kind: field::OPT_PARAMETER_REQUEST_LIST, data: list };
|
||||
let tmp = options; options = option.emit(tmp);
|
||||
options = DhcpOption::Other {
|
||||
kind: field::OPT_PARAMETER_REQUEST_LIST,
|
||||
data: list,
|
||||
}
|
||||
.emit(options);
|
||||
}
|
||||
DhcpOption::EndOfList.emit(options);
|
||||
}
|
||||
|
@ -852,75 +946,80 @@ impl<'a> Repr<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::wire::Ipv4Address;
|
||||
use super::*;
|
||||
use crate::wire::Ipv4Address;
|
||||
|
||||
const MAGIC_COOKIE: u32 = 0x63825363;
|
||||
|
||||
static DISCOVER_BYTES: &[u8] = &[
|
||||
0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x82, 0x01,
|
||||
0xfc, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
||||
0x35, 0x01, 0x01, 0x3d, 0x07, 0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42, 0x32, 0x04, 0x00, 0x00,
|
||||
0x00, 0x00, 0x39, 0x2, 0x5, 0xdc, 0x37, 0x04, 0x01, 0x03, 0x06, 0x2a, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b,
|
||||
0x82, 0x01, 0xfc, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
||||
0x35, 0x01, 0x01, 0x3d, 0x07, 0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42, 0x32, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x39, 0x2, 0x5, 0xdc, 0x37, 0x04, 0x01, 0x03, 0x06, 0x2a, 0xff, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
static ACK_DNS_SERVER_BYTES: &[u8] = &[
|
||||
0x02, 0x01, 0x06, 0x00, 0xcc, 0x34, 0x75, 0xab, 0x00, 0x00, 0x80, 0x00, 0x0a, 0xff, 0x06, 0x91,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x06, 0xfe, 0x34, 0x17, 0xeb, 0xc9,
|
||||
0xaa, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
||||
0x35, 0x01, 0x05, 0x36, 0x04, 0xa3, 0x01, 0x4a, 0x16, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x2b,
|
||||
0x05, 0xdc, 0x03, 0x4e, 0x41, 0x50, 0x0f, 0x15, 0x6e, 0x61, 0x74, 0x2e, 0x70, 0x68, 0x79, 0x73,
|
||||
0x69, 0x63, 0x73, 0x2e, 0x6f, 0x78, 0x2e, 0x61, 0x63, 0x2e, 0x75, 0x6b, 0x00, 0x03, 0x04, 0x0a,
|
||||
0xff, 0x06, 0xfe, 0x06, 0x10, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0xa3, 0x01, 0x4a,
|
||||
0x03, 0xa3, 0x01, 0x4a, 0x04, 0x2c, 0x10, 0xa3, 0x01, 0x4a, 0x03, 0xa3, 0x01, 0x4a, 0x04, 0xa3,
|
||||
0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0x2e, 0x01, 0x08, 0xff
|
||||
0x02, 0x01, 0x06, 0x00, 0xcc, 0x34, 0x75, 0xab, 0x00, 0x00, 0x80, 0x00, 0x0a, 0xff, 0x06,
|
||||
0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x06, 0xfe, 0x34, 0x17,
|
||||
0xeb, 0xc9, 0xaa, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
||||
0x35, 0x01, 0x05, 0x36, 0x04, 0xa3, 0x01, 0x4a, 0x16, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00,
|
||||
0x2b, 0x05, 0xdc, 0x03, 0x4e, 0x41, 0x50, 0x0f, 0x15, 0x6e, 0x61, 0x74, 0x2e, 0x70, 0x68,
|
||||
0x79, 0x73, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x78, 0x2e, 0x61, 0x63, 0x2e, 0x75, 0x6b, 0x00,
|
||||
0x03, 0x04, 0x0a, 0xff, 0x06, 0xfe, 0x06, 0x10, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a,
|
||||
0x07, 0xa3, 0x01, 0x4a, 0x03, 0xa3, 0x01, 0x4a, 0x04, 0x2c, 0x10, 0xa3, 0x01, 0x4a, 0x03,
|
||||
0xa3, 0x01, 0x4a, 0x04, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0x2e, 0x01, 0x08,
|
||||
0xff,
|
||||
];
|
||||
|
||||
static ACK_LEASE_TIME_BYTES: &[u8] = &[
|
||||
0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0a, 0x22, 0x10, 0x0b, 0x0a, 0x22, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x04, 0x91, 0x62, 0xd2,
|
||||
0xa8, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
||||
0x35, 0x01, 0x05, 0x36, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0x33, 0x04, 0x00, 0x00, 0x02, 0x56, 0x01,
|
||||
0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0a, 0x22, 0x10, 0x0b, 0x0a, 0x22, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x04, 0x91,
|
||||
0x62, 0xd2, 0xa8, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
|
||||
0x35, 0x01, 0x05, 0x36, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0x33, 0x04, 0x00, 0x00, 0x02, 0x56,
|
||||
0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
const IP_NULL: Ipv4Address = Ipv4Address([0, 0, 0, 0]);
|
||||
|
@ -962,9 +1061,13 @@ mod test {
|
|||
assert_eq!(options.len(), 6 + 1 + 7);
|
||||
|
||||
let (options, client_id) = DhcpOption::parse(options).unwrap();
|
||||
assert_eq!(client_id, DhcpOption::Other {
|
||||
kind: field::OPT_PARAMETER_REQUEST_LIST, data: &[1, 3, 6, 42]
|
||||
});
|
||||
assert_eq!(
|
||||
client_id,
|
||||
DhcpOption::Other {
|
||||
kind: field::OPT_PARAMETER_REQUEST_LIST,
|
||||
data: &[1, 3, 6, 42]
|
||||
}
|
||||
);
|
||||
assert_eq!(options.len(), 1 + 7);
|
||||
|
||||
let (options, client_id) = DhcpOption::parse(options).unwrap();
|
||||
|
@ -984,7 +1087,7 @@ mod test {
|
|||
packet.set_hops(0);
|
||||
packet.set_transaction_id(0x3d1d);
|
||||
packet.set_secs(0);
|
||||
packet.set_broadcast_flag(false);
|
||||
packet.set_flags(Flags::empty());
|
||||
packet.set_client_ip(IP_NULL);
|
||||
packet.set_your_ip(IP_NULL);
|
||||
packet.set_server_ip(IP_NULL);
|
||||
|
@ -993,14 +1096,15 @@ mod test {
|
|||
|
||||
{
|
||||
let mut options = packet.options_mut().unwrap();
|
||||
let tmp = options; options = DhcpOption::MessageType(MessageType::Discover).emit(tmp);
|
||||
let tmp = options; options = DhcpOption::ClientIdentifier(CLIENT_MAC).emit(tmp);
|
||||
let tmp = options; options = DhcpOption::RequestedIp(IP_NULL).emit(tmp);
|
||||
let tmp = options; options = DhcpOption::MaximumDhcpMessageSize(DHCP_SIZE).emit(tmp);
|
||||
options = DhcpOption::MessageType(MessageType::Discover).emit(options);
|
||||
options = DhcpOption::ClientIdentifier(CLIENT_MAC).emit(options);
|
||||
options = DhcpOption::RequestedIp(IP_NULL).emit(options);
|
||||
options = DhcpOption::MaximumDhcpMessageSize(DHCP_SIZE).emit(options);
|
||||
let option = DhcpOption::Other {
|
||||
kind: field::OPT_PARAMETER_REQUEST_LIST, data: &[1, 3, 6, 42],
|
||||
kind: field::OPT_PARAMETER_REQUEST_LIST,
|
||||
data: &[1, 3, 6, 42],
|
||||
};
|
||||
let tmp = options; options = option.emit(tmp);
|
||||
options = option.emit(options);
|
||||
DhcpOption::EndOfList.emit(options);
|
||||
}
|
||||
|
||||
|
@ -1012,6 +1116,28 @@ mod test {
|
|||
assert_eq!(packet, DISCOVER_BYTES);
|
||||
}
|
||||
|
||||
fn offer_repr() -> Repr<'static> {
|
||||
Repr {
|
||||
message_type: MessageType::Offer,
|
||||
transaction_id: 0x3d1d,
|
||||
client_hardware_address: CLIENT_MAC,
|
||||
client_ip: IP_NULL,
|
||||
your_ip: IP_NULL,
|
||||
server_ip: IP_NULL,
|
||||
router: Some(IP_NULL),
|
||||
subnet_mask: Some(IP_NULL),
|
||||
relay_agent_ip: IP_NULL,
|
||||
broadcast: false,
|
||||
requested_ip: None,
|
||||
client_identifier: Some(CLIENT_MAC),
|
||||
server_identifier: None,
|
||||
parameter_request_list: None,
|
||||
dns_servers: None,
|
||||
max_size: None,
|
||||
lease_duration: Some(0xffff_ffff), // Infinite lease
|
||||
}
|
||||
}
|
||||
|
||||
fn discover_repr() -> Repr<'static> {
|
||||
Repr {
|
||||
message_type: MessageType::Discover,
|
||||
|
@ -1055,6 +1181,42 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_offer() {
|
||||
let repr = offer_repr();
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut packet).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_offer_dns() {
|
||||
let repr = {
|
||||
let mut repr = offer_repr();
|
||||
repr.dns_servers = Some([
|
||||
Some(Ipv4Address([163, 1, 74, 6])),
|
||||
Some(Ipv4Address([163, 1, 74, 7])),
|
||||
Some(Ipv4Address([163, 1, 74, 3])),
|
||||
]);
|
||||
repr
|
||||
};
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut packet).unwrap();
|
||||
|
||||
let packet = Packet::new_unchecked(&bytes);
|
||||
let repr_parsed = Repr::parse(&packet).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
repr_parsed.dns_servers,
|
||||
Some([
|
||||
Some(Ipv4Address([163, 1, 74, 6])),
|
||||
Some(Ipv4Address([163, 1, 74, 7])),
|
||||
Some(Ipv4Address([163, 1, 74, 3]))
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_dhcp_option() {
|
||||
static DATA: &[u8] = &[1, 3, 6];
|
||||
|
@ -1067,7 +1229,10 @@ mod test {
|
|||
let rest = dhcp_option.emit(&mut bytes);
|
||||
assert_eq!(rest.len(), 0);
|
||||
}
|
||||
assert_eq!(&bytes[0..2], &[field::OPT_PARAMETER_REQUEST_LIST, DATA.len() as u8]);
|
||||
assert_eq!(
|
||||
&bytes[0..2],
|
||||
&[field::OPT_PARAMETER_REQUEST_LIST, DATA.len() as u8]
|
||||
);
|
||||
assert_eq!(&bytes[2..], DATA);
|
||||
}
|
||||
|
||||
|
@ -1079,10 +1244,14 @@ mod test {
|
|||
// The packet described by ACK_BYTES advertises 4 DNS servers
|
||||
// Here we ensure that we correctly parse the first 3 into our fixed
|
||||
// length-3 array (see issue #305)
|
||||
assert_eq!(repr.dns_servers, Some([
|
||||
Some(Ipv4Address([163, 1, 74, 6])),
|
||||
Some(Ipv4Address([163, 1, 74, 7])),
|
||||
Some(Ipv4Address([163, 1, 74, 3]))]));
|
||||
assert_eq!(
|
||||
repr.dns_servers,
|
||||
Some([
|
||||
Some(Ipv4Address([163, 1, 74, 6])),
|
||||
Some(Ipv4Address([163, 1, 74, 7])),
|
||||
Some(Ipv4Address([163, 1, 74, 3]))
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use core::fmt;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::fmt;
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
|
@ -17,14 +17,15 @@ impl fmt::Display for EtherType {
|
|||
match *self {
|
||||
EtherType::Ipv4 => write!(f, "IPv4"),
|
||||
EtherType::Ipv6 => write!(f, "IPv6"),
|
||||
EtherType::Arp => write!(f, "ARP"),
|
||||
EtherType::Unknown(id) => write!(f, "0x{:04x}", id)
|
||||
EtherType::Arp => write!(f, "ARP"),
|
||||
EtherType::Unknown(id) => write!(f, "0x{:04x}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A six-octet Ethernet II address.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Address(pub [u8; 6]);
|
||||
|
||||
impl Address {
|
||||
|
@ -48,8 +49,7 @@ impl Address {
|
|||
|
||||
/// Query whether the address is an unicast address.
|
||||
pub fn is_unicast(&self) -> bool {
|
||||
!(self.is_broadcast() ||
|
||||
self.is_multicast())
|
||||
!(self.is_broadcast() || self.is_multicast())
|
||||
}
|
||||
|
||||
/// Query whether this address is the broadcast address.
|
||||
|
@ -71,24 +71,28 @@ impl Address {
|
|||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let bytes = self.0;
|
||||
write!(f, "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
|
||||
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5])
|
||||
write!(
|
||||
f,
|
||||
"{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
|
||||
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A read/write wrapper around an Ethernet II frame buffer.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Frame<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
mod field {
|
||||
use crate::wire::field::*;
|
||||
|
||||
pub const DESTINATION: Field = 0..6;
|
||||
pub const SOURCE: Field = 6..12;
|
||||
pub const ETHERTYPE: Field = 12..14;
|
||||
pub const PAYLOAD: Rest = 14..;
|
||||
pub const DESTINATION: Field = 0..6;
|
||||
pub const SOURCE: Field = 6..12;
|
||||
pub const ETHERTYPE: Field = 12..14;
|
||||
pub const PAYLOAD: Rest = 14..;
|
||||
}
|
||||
|
||||
/// The Ethernet header length
|
||||
|
@ -207,19 +211,27 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Frame<T> {
|
|||
|
||||
impl<T: AsRef<[u8]>> fmt::Display for Frame<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "EthernetII src={} dst={} type={}",
|
||||
self.src_addr(), self.dst_addr(), self.ethertype())
|
||||
write!(
|
||||
f,
|
||||
"EthernetII src={} dst={} type={}",
|
||||
self.src_addr(),
|
||||
self.dst_addr(),
|
||||
self.ethertype()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Frame<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
let frame = match Frame::new_checked(buffer) {
|
||||
Err(err) => return write!(f, "{}({})", indent, err),
|
||||
Ok(frame) => frame
|
||||
Err(err) => return write!(f, "{}({})", indent, err),
|
||||
Ok(frame) => frame,
|
||||
};
|
||||
write!(f, "{}{}", indent, frame)?;
|
||||
|
||||
|
@ -239,17 +251,18 @@ impl<T: AsRef<[u8]>> PrettyPrint for Frame<T> {
|
|||
indent.increase(f)?;
|
||||
super::Ipv6Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent)
|
||||
}
|
||||
_ => Ok(())
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A high-level representation of an Internet Protocol version 4 packet header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr {
|
||||
pub src_addr: Address,
|
||||
pub dst_addr: Address,
|
||||
pub ethertype: EtherType,
|
||||
pub src_addr: Address,
|
||||
pub dst_addr: Address,
|
||||
pub ethertype: EtherType,
|
||||
}
|
||||
|
||||
impl Repr {
|
||||
|
@ -297,32 +310,32 @@ mod test_ipv4 {
|
|||
// Tests that are valid only with "proto-ipv4"
|
||||
use super::*;
|
||||
|
||||
static FRAME_BYTES: [u8; 64] =
|
||||
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x08, 0x00,
|
||||
0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xff];
|
||||
static FRAME_BYTES: [u8; 64] = [
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08, 0x00, 0xaa,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
static PAYLOAD_BYTES: [u8; 50] =
|
||||
[0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xff];
|
||||
static PAYLOAD_BYTES: [u8; 50] = [
|
||||
0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_deconstruct() {
|
||||
let frame = Frame::new_unchecked(&FRAME_BYTES[..]);
|
||||
assert_eq!(frame.dst_addr(), Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]));
|
||||
assert_eq!(frame.src_addr(), Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16]));
|
||||
assert_eq!(
|
||||
frame.dst_addr(),
|
||||
Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
|
||||
);
|
||||
assert_eq!(
|
||||
frame.src_addr(),
|
||||
Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16])
|
||||
);
|
||||
assert_eq!(frame.ethertype(), EtherType::Ipv4);
|
||||
assert_eq!(frame.payload(), &PAYLOAD_BYTES[..]);
|
||||
}
|
||||
|
@ -345,28 +358,30 @@ mod test_ipv6 {
|
|||
// Tests that are valid only with "proto-ipv6"
|
||||
use super::*;
|
||||
|
||||
static FRAME_BYTES: [u8; 54] =
|
||||
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x86, 0xdd,
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01];
|
||||
static FRAME_BYTES: [u8; 54] = [
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x86, 0xdd, 0x60,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
];
|
||||
|
||||
static PAYLOAD_BYTES: [u8; 40] =
|
||||
[0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01];
|
||||
static PAYLOAD_BYTES: [u8; 40] = [
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_deconstruct() {
|
||||
let frame = Frame::new_unchecked(&FRAME_BYTES[..]);
|
||||
assert_eq!(frame.dst_addr(), Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]));
|
||||
assert_eq!(frame.src_addr(), Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16]));
|
||||
assert_eq!(
|
||||
frame.dst_addr(),
|
||||
Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
|
||||
);
|
||||
assert_eq!(
|
||||
frame.src_addr(),
|
||||
Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16])
|
||||
);
|
||||
assert_eq!(frame.ethertype(), EtherType::Ipv6);
|
||||
assert_eq!(frame.payload(), &PAYLOAD_BYTES[..]);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::wire::icmpv4;
|
|||
use crate::wire::icmpv6;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Repr<'a> {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
Ipv4(icmpv4::Repr<'a>),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use core::{cmp, fmt};
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::{cmp, fmt};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::ip::checksum;
|
||||
use crate::wire::{Ipv4Packet, Ipv4Repr};
|
||||
use crate::{Error, Result};
|
||||
|
||||
enum_with_unknown! {
|
||||
/// Internet protocol control message type.
|
||||
|
@ -35,17 +35,17 @@ enum_with_unknown! {
|
|||
impl fmt::Display for Message {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Message::EchoReply => write!(f, "echo reply"),
|
||||
Message::EchoReply => write!(f, "echo reply"),
|
||||
Message::DstUnreachable => write!(f, "destination unreachable"),
|
||||
Message::Redirect => write!(f, "message redirect"),
|
||||
Message::EchoRequest => write!(f, "echo request"),
|
||||
Message::RouterAdvert => write!(f, "router advertisement"),
|
||||
Message::RouterSolicit => write!(f, "router solicitation"),
|
||||
Message::TimeExceeded => write!(f, "time exceeded"),
|
||||
Message::ParamProblem => write!(f, "parameter problem"),
|
||||
Message::Timestamp => write!(f, "timestamp"),
|
||||
Message::Redirect => write!(f, "message redirect"),
|
||||
Message::EchoRequest => write!(f, "echo request"),
|
||||
Message::RouterAdvert => write!(f, "router advertisement"),
|
||||
Message::RouterSolicit => write!(f, "router solicitation"),
|
||||
Message::TimeExceeded => write!(f, "time exceeded"),
|
||||
Message::ParamProblem => write!(f, "parameter problem"),
|
||||
Message::Timestamp => write!(f, "timestamp"),
|
||||
Message::TimestampReply => write!(f, "timestamp reply"),
|
||||
Message::Unknown(id) => write!(f, "{}", id)
|
||||
Message::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,40 +91,25 @@ enum_with_unknown! {
|
|||
impl fmt::Display for DstUnreachable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
DstUnreachable::NetUnreachable =>
|
||||
write!(f, "destination network unreachable"),
|
||||
DstUnreachable::HostUnreachable =>
|
||||
write!(f, "destination host unreachable"),
|
||||
DstUnreachable::ProtoUnreachable =>
|
||||
write!(f, "destination protocol unreachable"),
|
||||
DstUnreachable::PortUnreachable =>
|
||||
write!(f, "destination port unreachable"),
|
||||
DstUnreachable::FragRequired =>
|
||||
write!(f, "fragmentation required, and DF flag set"),
|
||||
DstUnreachable::SrcRouteFailed =>
|
||||
write!(f, "source route failed"),
|
||||
DstUnreachable::DstNetUnknown =>
|
||||
write!(f, "destination network unknown"),
|
||||
DstUnreachable::DstHostUnknown =>
|
||||
write!(f, "destination host unknown"),
|
||||
DstUnreachable::SrcHostIsolated =>
|
||||
write!(f, "source host isolated"),
|
||||
DstUnreachable::NetProhibited =>
|
||||
write!(f, "network administratively prohibited"),
|
||||
DstUnreachable::HostProhibited =>
|
||||
write!(f, "host administratively prohibited"),
|
||||
DstUnreachable::NetUnreachToS =>
|
||||
write!(f, "network unreachable for ToS"),
|
||||
DstUnreachable::HostUnreachToS =>
|
||||
write!(f, "host unreachable for ToS"),
|
||||
DstUnreachable::CommProhibited =>
|
||||
write!(f, "communication administratively prohibited"),
|
||||
DstUnreachable::HostPrecedViol =>
|
||||
write!(f, "host precedence violation"),
|
||||
DstUnreachable::PrecedCutoff =>
|
||||
write!(f, "precedence cutoff in effect"),
|
||||
DstUnreachable::Unknown(id) =>
|
||||
write!(f, "{}", id)
|
||||
DstUnreachable::NetUnreachable => write!(f, "destination network unreachable"),
|
||||
DstUnreachable::HostUnreachable => write!(f, "destination host unreachable"),
|
||||
DstUnreachable::ProtoUnreachable => write!(f, "destination protocol unreachable"),
|
||||
DstUnreachable::PortUnreachable => write!(f, "destination port unreachable"),
|
||||
DstUnreachable::FragRequired => write!(f, "fragmentation required, and DF flag set"),
|
||||
DstUnreachable::SrcRouteFailed => write!(f, "source route failed"),
|
||||
DstUnreachable::DstNetUnknown => write!(f, "destination network unknown"),
|
||||
DstUnreachable::DstHostUnknown => write!(f, "destination host unknown"),
|
||||
DstUnreachable::SrcHostIsolated => write!(f, "source host isolated"),
|
||||
DstUnreachable::NetProhibited => write!(f, "network administratively prohibited"),
|
||||
DstUnreachable::HostProhibited => write!(f, "host administratively prohibited"),
|
||||
DstUnreachable::NetUnreachToS => write!(f, "network unreachable for ToS"),
|
||||
DstUnreachable::HostUnreachToS => write!(f, "host unreachable for ToS"),
|
||||
DstUnreachable::CommProhibited => {
|
||||
write!(f, "communication administratively prohibited")
|
||||
}
|
||||
DstUnreachable::HostPrecedViol => write!(f, "host precedence violation"),
|
||||
DstUnreachable::PrecedCutoff => write!(f, "precedence cutoff in effect"),
|
||||
DstUnreachable::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,18 +152,19 @@ enum_with_unknown! {
|
|||
|
||||
/// A read/write wrapper around an Internet Control Message Protocol version 4 packet buffer.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
mod field {
|
||||
use crate::wire::field::*;
|
||||
|
||||
pub const TYPE: usize = 0;
|
||||
pub const CODE: usize = 1;
|
||||
pub const CHECKSUM: Field = 2..4;
|
||||
pub const TYPE: usize = 0;
|
||||
pub const CODE: usize = 1;
|
||||
pub const CHECKSUM: Field = 2..4;
|
||||
|
||||
pub const UNUSED: Field = 4..8;
|
||||
pub const UNUSED: Field = 4..8;
|
||||
|
||||
pub const ECHO_IDENT: Field = 4..6;
|
||||
pub const ECHO_SEQNO: Field = 6..8;
|
||||
|
@ -267,10 +253,10 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
/// The result depends on the value of the message type field.
|
||||
pub fn header_len(&self) -> usize {
|
||||
match self.msg_type() {
|
||||
Message::EchoRequest => field::ECHO_SEQNO.end,
|
||||
Message::EchoReply => field::ECHO_SEQNO.end,
|
||||
Message::EchoRequest => field::ECHO_SEQNO.end,
|
||||
Message::EchoReply => field::ECHO_SEQNO.end,
|
||||
Message::DstUnreachable => field::UNUSED.end,
|
||||
_ => field::UNUSED.end // make a conservative assumption
|
||||
_ => field::UNUSED.end, // make a conservative assumption
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,7 +265,9 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
/// # Fuzzing
|
||||
/// This function always returns `true` when fuzzing.
|
||||
pub fn verify_checksum(&self) -> bool {
|
||||
if cfg!(fuzzing) { return true }
|
||||
if cfg!(fuzzing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let data = self.buffer.as_ref();
|
||||
checksum::data(data) == !0
|
||||
|
@ -366,50 +354,53 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
|
|||
|
||||
/// A high-level representation of an Internet Control Message Protocol version 4 packet header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Repr<'a> {
|
||||
EchoRequest {
|
||||
ident: u16,
|
||||
ident: u16,
|
||||
seq_no: u16,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
EchoReply {
|
||||
ident: u16,
|
||||
ident: u16,
|
||||
seq_no: u16,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
DstUnreachable {
|
||||
reason: DstUnreachable,
|
||||
header: Ipv4Repr,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse an Internet Control Message Protocol version 4 packet and return
|
||||
/// a high-level representation.
|
||||
pub fn parse<T>(packet: &Packet<&'a T>, checksum_caps: &ChecksumCapabilities)
|
||||
-> Result<Repr<'a>>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(
|
||||
packet: &Packet<&'a T>,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) -> Result<Repr<'a>>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
// Valid checksum is expected.
|
||||
if checksum_caps.icmpv4.rx() && !packet.verify_checksum() { return Err(Error::Checksum) }
|
||||
if checksum_caps.icmpv4.rx() && !packet.verify_checksum() {
|
||||
return Err(Error::Checksum);
|
||||
}
|
||||
|
||||
match (packet.msg_type(), packet.msg_code()) {
|
||||
(Message::EchoRequest, 0) => {
|
||||
Ok(Repr::EchoRequest {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.data()
|
||||
})
|
||||
},
|
||||
(Message::EchoRequest, 0) => Ok(Repr::EchoRequest {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.data(),
|
||||
}),
|
||||
|
||||
(Message::EchoReply, 0) => {
|
||||
Ok(Repr::EchoReply {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.data()
|
||||
})
|
||||
},
|
||||
(Message::EchoReply, 0) => Ok(Repr::EchoReply {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.data(),
|
||||
}),
|
||||
|
||||
(Message::DstUnreachable, code) => {
|
||||
let ip_packet = Ipv4Packet::new_checked(packet.data())?;
|
||||
|
@ -417,7 +408,9 @@ impl<'a> Repr<'a> {
|
|||
let payload = &packet.data()[ip_packet.header_len() as usize..];
|
||||
// RFC 792 requires exactly eight bytes to be returned.
|
||||
// We allow more, since there isn't a reason not to, but require at least eight.
|
||||
if payload.len() < 8 { return Err(Error::Truncated) }
|
||||
if payload.len() < 8 {
|
||||
return Err(Error::Truncated);
|
||||
}
|
||||
|
||||
Ok(Repr::DstUnreachable {
|
||||
reason: DstUnreachable::from(code),
|
||||
|
@ -426,22 +419,21 @@ impl<'a> Repr<'a> {
|
|||
dst_addr: ip_packet.dst_addr(),
|
||||
protocol: ip_packet.protocol(),
|
||||
payload_len: payload.len(),
|
||||
hop_limit: ip_packet.hop_limit()
|
||||
hop_limit: ip_packet.hop_limit(),
|
||||
},
|
||||
data: payload
|
||||
data: payload,
|
||||
})
|
||||
}
|
||||
_ => Err(Error::Unrecognized)
|
||||
_ => Err(Error::Unrecognized),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the length of a packet that will be emitted from this high-level representation.
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
match self {
|
||||
&Repr::EchoRequest { data, .. } |
|
||||
&Repr::EchoReply { data, .. } => {
|
||||
&Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
|
||||
field::ECHO_SEQNO.end + data.len()
|
||||
},
|
||||
}
|
||||
&Repr::DstUnreachable { header, data, .. } => {
|
||||
field::UNUSED.end + header.buffer_len() + data.len()
|
||||
}
|
||||
|
@ -451,35 +443,49 @@ impl<'a> Repr<'a> {
|
|||
/// Emit a high-level representation into an Internet Control Message Protocol version 4
|
||||
/// packet.
|
||||
pub fn emit<T>(&self, packet: &mut Packet<&mut T>, checksum_caps: &ChecksumCapabilities)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||
where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
packet.set_msg_code(0);
|
||||
match *self {
|
||||
Repr::EchoRequest { ident, seq_no, data } => {
|
||||
Repr::EchoRequest {
|
||||
ident,
|
||||
seq_no,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::EchoRequest);
|
||||
packet.set_msg_code(0);
|
||||
packet.set_echo_ident(ident);
|
||||
packet.set_echo_seq_no(seq_no);
|
||||
let data_len = cmp::min(packet.data_mut().len(), data.len());
|
||||
packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
|
||||
},
|
||||
}
|
||||
|
||||
Repr::EchoReply { ident, seq_no, data } => {
|
||||
Repr::EchoReply {
|
||||
ident,
|
||||
seq_no,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::EchoReply);
|
||||
packet.set_msg_code(0);
|
||||
packet.set_echo_ident(ident);
|
||||
packet.set_echo_seq_no(seq_no);
|
||||
let data_len = cmp::min(packet.data_mut().len(), data.len());
|
||||
packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
|
||||
},
|
||||
}
|
||||
|
||||
Repr::DstUnreachable { reason, header, data } => {
|
||||
Repr::DstUnreachable {
|
||||
reason,
|
||||
header,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::DstUnreachable);
|
||||
packet.set_msg_code(reason.into());
|
||||
|
||||
let mut ip_packet = Ipv4Packet::new_unchecked(packet.data_mut());
|
||||
header.emit(&mut ip_packet, checksum_caps);
|
||||
let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
|
||||
payload.copy_from_slice(&data[..])
|
||||
payload.copy_from_slice(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -501,9 +507,10 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
|||
write!(f, "ICMPv4 ({})", err)?;
|
||||
write!(f, " type={:?}", self.msg_type())?;
|
||||
match self.msg_type() {
|
||||
Message::DstUnreachable =>
|
||||
write!(f, " code={:?}", DstUnreachable::from(self.msg_code())),
|
||||
_ => write!(f, " code={}", self.msg_code())
|
||||
Message::DstUnreachable => {
|
||||
write!(f, " code={:?}", DstUnreachable::from(self.msg_code()))
|
||||
}
|
||||
_ => write!(f, " code={}", self.msg_code()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -513,27 +520,46 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
|||
impl<'a> fmt::Display for Repr<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Repr::EchoRequest { ident, seq_no, data } =>
|
||||
write!(f, "ICMPv4 echo request id={} seq={} len={}",
|
||||
ident, seq_no, data.len()),
|
||||
Repr::EchoReply { ident, seq_no, data } =>
|
||||
write!(f, "ICMPv4 echo reply id={} seq={} len={}",
|
||||
ident, seq_no, data.len()),
|
||||
Repr::DstUnreachable { reason, .. } =>
|
||||
write!(f, "ICMPv4 destination unreachable ({})",
|
||||
reason),
|
||||
Repr::EchoRequest {
|
||||
ident,
|
||||
seq_no,
|
||||
data,
|
||||
} => write!(
|
||||
f,
|
||||
"ICMPv4 echo request id={} seq={} len={}",
|
||||
ident,
|
||||
seq_no,
|
||||
data.len()
|
||||
),
|
||||
Repr::EchoReply {
|
||||
ident,
|
||||
seq_no,
|
||||
data,
|
||||
} => write!(
|
||||
f,
|
||||
"ICMPv4 echo reply id={} seq={} len={}",
|
||||
ident,
|
||||
seq_no,
|
||||
data.len()
|
||||
),
|
||||
Repr::DstUnreachable { reason, .. } => {
|
||||
write!(f, "ICMPv4 destination unreachable ({})", reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
let packet = match Packet::new_checked(buffer) {
|
||||
Err(err) => return write!(f, "{}({})", indent, err),
|
||||
Ok(packet) => packet
|
||||
Err(err) => return write!(f, "{}({})", indent, err),
|
||||
Ok(packet) => packet,
|
||||
};
|
||||
write!(f, "{}{}", indent, packet)?;
|
||||
|
||||
|
@ -542,7 +568,7 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
|||
indent.increase(f)?;
|
||||
super::Ipv4Packet::<&[u8]>::pretty_print(&packet.data(), f, indent)
|
||||
}
|
||||
_ => Ok(())
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -551,13 +577,11 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
static ECHO_PACKET_BYTES: [u8; 12] =
|
||||
[0x08, 0x00, 0x8e, 0xfe,
|
||||
0x12, 0x34, 0xab, 0xcd,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static ECHO_PACKET_BYTES: [u8; 12] = [
|
||||
0x08, 0x00, 0x8e, 0xfe, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
static ECHO_DATA_BYTES: [u8; 4] =
|
||||
[0xaa, 0x00, 0x00, 0xff];
|
||||
static ECHO_DATA_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
|
||||
|
||||
#[test]
|
||||
fn test_echo_deconstruct() {
|
||||
|
@ -568,7 +592,7 @@ mod test {
|
|||
assert_eq!(packet.echo_ident(), 0x1234);
|
||||
assert_eq!(packet.echo_seq_no(), 0xabcd);
|
||||
assert_eq!(packet.data(), &ECHO_DATA_BYTES[..]);
|
||||
assert_eq!(packet.verify_checksum(), true);
|
||||
assert!(packet.verify_checksum());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -588,7 +612,7 @@ mod test {
|
|||
Repr::EchoRequest {
|
||||
ident: 0x1234,
|
||||
seq_no: 0xabcd,
|
||||
data: &ECHO_DATA_BYTES
|
||||
data: &ECHO_DATA_BYTES,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -610,8 +634,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_check_len() {
|
||||
let bytes = [0x0b, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00];
|
||||
let bytes = [0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
assert_eq!(Packet::new_checked(&[]), Err(Error::Truncated));
|
||||
assert_eq!(Packet::new_checked(&bytes[..4]), Err(Error::Truncated));
|
||||
assert!(Packet::new_checked(&bytes[..]).is_ok());
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use core::{cmp, fmt};
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::{cmp, fmt};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::ip::checksum;
|
||||
use crate::wire::{IpAddress, IpProtocol, Ipv6Packet, Ipv6Repr};
|
||||
use crate::wire::MldRepr;
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
use crate::wire::NdiscRepr;
|
||||
use crate::wire::{IpAddress, IpProtocol, Ipv6Packet, Ipv6Repr};
|
||||
use crate::{Error, Result};
|
||||
|
||||
enum_with_unknown! {
|
||||
/// Internet protocol control message type.
|
||||
|
@ -57,8 +57,11 @@ impl Message {
|
|||
/// [NDISC]: https://tools.ietf.org/html/rfc4861
|
||||
pub fn is_ndisc(&self) -> bool {
|
||||
match *self {
|
||||
Message::RouterSolicit | Message::RouterAdvert | Message::NeighborSolicit |
|
||||
Message::NeighborAdvert | Message::Redirect => true,
|
||||
Message::RouterSolicit
|
||||
| Message::RouterAdvert
|
||||
| Message::NeighborSolicit
|
||||
| Message::NeighborAdvert
|
||||
| Message::Redirect => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -78,20 +81,20 @@ impl Message {
|
|||
impl fmt::Display for Message {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Message::DstUnreachable => write!(f, "destination unreachable"),
|
||||
Message::PktTooBig => write!(f, "packet too big"),
|
||||
Message::TimeExceeded => write!(f, "time exceeded"),
|
||||
Message::ParamProblem => write!(f, "parameter problem"),
|
||||
Message::EchoReply => write!(f, "echo reply"),
|
||||
Message::EchoRequest => write!(f, "echo request"),
|
||||
Message::RouterSolicit => write!(f, "router solicitation"),
|
||||
Message::RouterAdvert => write!(f, "router advertisement"),
|
||||
Message::DstUnreachable => write!(f, "destination unreachable"),
|
||||
Message::PktTooBig => write!(f, "packet too big"),
|
||||
Message::TimeExceeded => write!(f, "time exceeded"),
|
||||
Message::ParamProblem => write!(f, "parameter problem"),
|
||||
Message::EchoReply => write!(f, "echo reply"),
|
||||
Message::EchoRequest => write!(f, "echo request"),
|
||||
Message::RouterSolicit => write!(f, "router solicitation"),
|
||||
Message::RouterAdvert => write!(f, "router advertisement"),
|
||||
Message::NeighborSolicit => write!(f, "neighbor solicitation"),
|
||||
Message::NeighborAdvert => write!(f, "neighbor advert"),
|
||||
Message::Redirect => write!(f, "redirect"),
|
||||
Message::MldQuery => write!(f, "multicast listener query"),
|
||||
Message::MldReport => write!(f, "multicast listener report"),
|
||||
Message::Unknown(id) => write!(f, "{}", id)
|
||||
Message::NeighborAdvert => write!(f, "neighbor advert"),
|
||||
Message::Redirect => write!(f, "redirect"),
|
||||
Message::MldQuery => write!(f, "multicast listener query"),
|
||||
Message::MldReport => write!(f, "multicast listener report"),
|
||||
Message::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,22 +122,19 @@ enum_with_unknown! {
|
|||
impl fmt::Display for DstUnreachable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
DstUnreachable::NoRoute =>
|
||||
write!(f, "no route to destination"),
|
||||
DstUnreachable::AdminProhibit =>
|
||||
write!(f, "communication with destination administratively prohibited"),
|
||||
DstUnreachable::BeyondScope =>
|
||||
write!(f, "beyond scope of source address"),
|
||||
DstUnreachable::AddrUnreachable =>
|
||||
write!(f, "address unreachable"),
|
||||
DstUnreachable::PortUnreachable =>
|
||||
write!(f, "port unreachable"),
|
||||
DstUnreachable::FailedPolicy =>
|
||||
write!(f, "source address failed ingress/egress policy"),
|
||||
DstUnreachable::RejectRoute =>
|
||||
write!(f, "reject route to destination"),
|
||||
DstUnreachable::Unknown(id) =>
|
||||
write!(f, "{}", id)
|
||||
DstUnreachable::NoRoute => write!(f, "no route to destination"),
|
||||
DstUnreachable::AdminProhibit => write!(
|
||||
f,
|
||||
"communication with destination administratively prohibited"
|
||||
),
|
||||
DstUnreachable::BeyondScope => write!(f, "beyond scope of source address"),
|
||||
DstUnreachable::AddrUnreachable => write!(f, "address unreachable"),
|
||||
DstUnreachable::PortUnreachable => write!(f, "port unreachable"),
|
||||
DstUnreachable::FailedPolicy => {
|
||||
write!(f, "source address failed ingress/egress policy")
|
||||
}
|
||||
DstUnreachable::RejectRoute => write!(f, "reject route to destination"),
|
||||
DstUnreachable::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,14 +154,10 @@ enum_with_unknown! {
|
|||
impl fmt::Display for ParamProblem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ParamProblem::ErroneousHdrField =>
|
||||
write!(f, "erroneous header field."),
|
||||
ParamProblem::UnrecognizedNxtHdr =>
|
||||
write!(f, "unrecognized next header type."),
|
||||
ParamProblem::UnrecognizedOption =>
|
||||
write!(f, "unrecognized IPv6 option."),
|
||||
ParamProblem::Unknown(id) =>
|
||||
write!(f, "{}", id)
|
||||
ParamProblem::ErroneousHdrField => write!(f, "erroneous header field."),
|
||||
ParamProblem::UnrecognizedNxtHdr => write!(f, "unrecognized next header type."),
|
||||
ParamProblem::UnrecognizedOption => write!(f, "unrecognized IPv6 option."),
|
||||
ParamProblem::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,20 +175,18 @@ enum_with_unknown! {
|
|||
impl fmt::Display for TimeExceeded {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
TimeExceeded::HopLimitExceeded =>
|
||||
write!(f, "hop limit exceeded in transit"),
|
||||
TimeExceeded::FragReassemExceeded =>
|
||||
write!(f, "fragment reassembly time exceeded"),
|
||||
TimeExceeded::Unknown(id) =>
|
||||
write!(f, "{}", id)
|
||||
TimeExceeded::HopLimitExceeded => write!(f, "hop limit exceeded in transit"),
|
||||
TimeExceeded::FragReassemExceeded => write!(f, "fragment reassembly time exceeded"),
|
||||
TimeExceeded::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A read/write wrapper around an Internet Control Message Protocol version 6 packet buffer.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
pub(super) buffer: T
|
||||
pub(super) buffer: T,
|
||||
}
|
||||
|
||||
// Ranges and constants describing key boundaries in the ICMPv6 header.
|
||||
|
@ -200,54 +194,54 @@ pub(super) mod field {
|
|||
use crate::wire::field::*;
|
||||
|
||||
// ICMPv6: See https://tools.ietf.org/html/rfc4443
|
||||
pub const TYPE: usize = 0;
|
||||
pub const CODE: usize = 1;
|
||||
pub const CHECKSUM: Field = 2..4;
|
||||
pub const TYPE: usize = 0;
|
||||
pub const CODE: usize = 1;
|
||||
pub const CHECKSUM: Field = 2..4;
|
||||
|
||||
pub const UNUSED: Field = 4..8;
|
||||
pub const MTU: Field = 4..8;
|
||||
pub const POINTER: Field = 4..8;
|
||||
pub const ECHO_IDENT: Field = 4..6;
|
||||
pub const ECHO_SEQNO: Field = 6..8;
|
||||
pub const UNUSED: Field = 4..8;
|
||||
pub const MTU: Field = 4..8;
|
||||
pub const POINTER: Field = 4..8;
|
||||
pub const ECHO_IDENT: Field = 4..6;
|
||||
pub const ECHO_SEQNO: Field = 6..8;
|
||||
|
||||
pub const HEADER_END: usize = 8;
|
||||
pub const HEADER_END: usize = 8;
|
||||
|
||||
// NDISC: See https://tools.ietf.org/html/rfc4861
|
||||
// Router Advertisement message offsets
|
||||
pub const CUR_HOP_LIMIT: usize = 4;
|
||||
pub const ROUTER_FLAGS: usize = 5;
|
||||
pub const ROUTER_LT: Field = 6..8;
|
||||
pub const REACHABLE_TM: Field = 8..12;
|
||||
pub const RETRANS_TM: Field = 12..16;
|
||||
pub const CUR_HOP_LIMIT: usize = 4;
|
||||
pub const ROUTER_FLAGS: usize = 5;
|
||||
pub const ROUTER_LT: Field = 6..8;
|
||||
pub const REACHABLE_TM: Field = 8..12;
|
||||
pub const RETRANS_TM: Field = 12..16;
|
||||
|
||||
// Neighbor Solicitation message offsets
|
||||
pub const TARGET_ADDR: Field = 8..24;
|
||||
pub const TARGET_ADDR: Field = 8..24;
|
||||
|
||||
// Neighbor Advertisement message offsets
|
||||
pub const NEIGH_FLAGS: usize = 4;
|
||||
pub const NEIGH_FLAGS: usize = 4;
|
||||
|
||||
// Redirected Header message offsets
|
||||
pub const DEST_ADDR: Field = 24..40;
|
||||
pub const DEST_ADDR: Field = 24..40;
|
||||
|
||||
// MLD:
|
||||
// - https://tools.ietf.org/html/rfc3810
|
||||
// - https://tools.ietf.org/html/rfc3810
|
||||
// Multicast Listener Query message
|
||||
pub const MAX_RESP_CODE: Field = 4..6;
|
||||
pub const QUERY_RESV: Field = 6..8;
|
||||
pub const QUERY_MCAST_ADDR: Field = 8..24;
|
||||
pub const SQRV: usize = 24;
|
||||
pub const QQIC: usize = 25;
|
||||
pub const QUERY_NUM_SRCS: Field = 26..28;
|
||||
pub const MAX_RESP_CODE: Field = 4..6;
|
||||
pub const QUERY_RESV: Field = 6..8;
|
||||
pub const QUERY_MCAST_ADDR: Field = 8..24;
|
||||
pub const SQRV: usize = 24;
|
||||
pub const QQIC: usize = 25;
|
||||
pub const QUERY_NUM_SRCS: Field = 26..28;
|
||||
|
||||
// Multicast Listener Report Message
|
||||
pub const RECORD_RESV: Field = 4..6;
|
||||
pub const NR_MCAST_RCRDS: Field = 6..8;
|
||||
pub const RECORD_RESV: Field = 4..6;
|
||||
pub const NR_MCAST_RCRDS: Field = 6..8;
|
||||
|
||||
// Multicast Address Record Offsets
|
||||
pub const RECORD_TYPE: usize = 0;
|
||||
pub const AUX_DATA_LEN: usize = 1;
|
||||
pub const RECORD_NUM_SRCS: Field = 2..4;
|
||||
pub const RECORD_TYPE: usize = 0;
|
||||
pub const AUX_DATA_LEN: usize = 1;
|
||||
pub const RECORD_NUM_SRCS: Field = 2..4;
|
||||
pub const RECORD_MCAST_ADDR: Field = 4..20;
|
||||
}
|
||||
|
||||
|
@ -332,29 +326,28 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
NetworkEndian::read_u32(&data[field::POINTER])
|
||||
}
|
||||
|
||||
|
||||
/// Return the header length. The result depends on the value of
|
||||
/// the message type field.
|
||||
pub fn header_len(&self) -> usize {
|
||||
match self.msg_type() {
|
||||
Message::DstUnreachable => field::UNUSED.end,
|
||||
Message::PktTooBig => field::MTU.end,
|
||||
Message::TimeExceeded => field::UNUSED.end,
|
||||
Message::ParamProblem => field::POINTER.end,
|
||||
Message::EchoRequest => field::ECHO_SEQNO.end,
|
||||
Message::EchoReply => field::ECHO_SEQNO.end,
|
||||
Message::RouterSolicit => field::UNUSED.end,
|
||||
Message::RouterAdvert => field::RETRANS_TM.end,
|
||||
Message::DstUnreachable => field::UNUSED.end,
|
||||
Message::PktTooBig => field::MTU.end,
|
||||
Message::TimeExceeded => field::UNUSED.end,
|
||||
Message::ParamProblem => field::POINTER.end,
|
||||
Message::EchoRequest => field::ECHO_SEQNO.end,
|
||||
Message::EchoReply => field::ECHO_SEQNO.end,
|
||||
Message::RouterSolicit => field::UNUSED.end,
|
||||
Message::RouterAdvert => field::RETRANS_TM.end,
|
||||
Message::NeighborSolicit => field::TARGET_ADDR.end,
|
||||
Message::NeighborAdvert => field::TARGET_ADDR.end,
|
||||
Message::Redirect => field::DEST_ADDR.end,
|
||||
Message::MldQuery => field::QUERY_NUM_SRCS.end,
|
||||
Message::MldReport => field::NR_MCAST_RCRDS.end,
|
||||
Message::NeighborAdvert => field::TARGET_ADDR.end,
|
||||
Message::Redirect => field::DEST_ADDR.end,
|
||||
Message::MldQuery => field::QUERY_NUM_SRCS.end,
|
||||
Message::MldReport => field::NR_MCAST_RCRDS.end,
|
||||
// For packets that are not included in RFC 4443, do not
|
||||
// include the last 32 bits of the ICMPv6 header in
|
||||
// `header_bytes`. This must be done so that these bytes
|
||||
// can be accessed in the `payload`.
|
||||
_ => field::CHECKSUM.end
|
||||
_ => field::CHECKSUM.end,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,13 +356,14 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
/// # Fuzzing
|
||||
/// This function always returns `true` when fuzzing.
|
||||
pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
|
||||
if cfg!(fuzzing) { return true }
|
||||
if cfg!(fuzzing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let data = self.buffer.as_ref();
|
||||
checksum::combine(&[
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6,
|
||||
data.len() as u32),
|
||||
checksum::data(data)
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32),
|
||||
checksum::data(data),
|
||||
]) == !0
|
||||
}
|
||||
}
|
||||
|
@ -408,16 +402,18 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
#[inline]
|
||||
pub fn clear_reserved(&mut self) {
|
||||
match self.msg_type() {
|
||||
Message::RouterSolicit | Message::NeighborSolicit |
|
||||
Message::NeighborAdvert | Message::Redirect => {
|
||||
Message::RouterSolicit
|
||||
| Message::NeighborSolicit
|
||||
| Message::NeighborAdvert
|
||||
| Message::Redirect => {
|
||||
let data = self.buffer.as_mut();
|
||||
NetworkEndian::write_u32(&mut data[field::UNUSED], 0);
|
||||
},
|
||||
}
|
||||
Message::MldQuery => {
|
||||
let data = self.buffer.as_mut();
|
||||
NetworkEndian::write_u16(&mut data[field::QUERY_RESV], 0);
|
||||
data[field::SQRV] &= 0xf;
|
||||
},
|
||||
}
|
||||
Message::MldReport => {
|
||||
let data = self.buffer.as_mut();
|
||||
NetworkEndian::write_u16(&mut data[field::RECORD_RESV], 0);
|
||||
|
@ -478,9 +474,8 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
let checksum = {
|
||||
let data = self.buffer.as_ref();
|
||||
!checksum::combine(&[
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6,
|
||||
data.len() as u32),
|
||||
checksum::data(data)
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32),
|
||||
checksum::data(data),
|
||||
])
|
||||
};
|
||||
self.set_checksum(checksum)
|
||||
|
@ -503,40 +498,41 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
|
|||
|
||||
/// A high-level representation of an Internet Control Message Protocol version 6 packet header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Repr<'a> {
|
||||
DstUnreachable {
|
||||
reason: DstUnreachable,
|
||||
header: Ipv6Repr,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
PktTooBig {
|
||||
mtu: u32,
|
||||
header: Ipv6Repr,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
TimeExceeded {
|
||||
reason: TimeExceeded,
|
||||
header: Ipv6Repr,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
ParamProblem {
|
||||
reason: ParamProblem,
|
||||
reason: ParamProblem,
|
||||
pointer: u32,
|
||||
header: Ipv6Repr,
|
||||
data: &'a [u8]
|
||||
header: Ipv6Repr,
|
||||
data: &'a [u8],
|
||||
},
|
||||
EchoRequest {
|
||||
ident: u16,
|
||||
ident: u16,
|
||||
seq_no: u16,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
EchoReply {
|
||||
ident: u16,
|
||||
ident: u16,
|
||||
seq_no: u16,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
#[cfg(feature = "ethernet")]
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
Ndisc(NdiscRepr<'a>),
|
||||
Mld(MldRepr<'a>),
|
||||
}
|
||||
|
@ -544,29 +540,37 @@ pub enum Repr<'a> {
|
|||
impl<'a> Repr<'a> {
|
||||
/// Parse an Internet Control Message Protocol version 6 packet and return
|
||||
/// a high-level representation.
|
||||
pub fn parse<T>(src_addr: &IpAddress, dst_addr: &IpAddress,
|
||||
packet: &Packet<&'a T>, checksum_caps: &ChecksumCapabilities)
|
||||
-> Result<Repr<'a>>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
fn create_packet_from_payload<'a, T>(packet: &Packet<&'a T>)
|
||||
-> Result<(&'a [u8], Ipv6Repr)>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(
|
||||
src_addr: &IpAddress,
|
||||
dst_addr: &IpAddress,
|
||||
packet: &Packet<&'a T>,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) -> Result<Repr<'a>>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
fn create_packet_from_payload<'a, T>(packet: &Packet<&'a T>) -> Result<(&'a [u8], Ipv6Repr)>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
let ip_packet = Ipv6Packet::new_checked(packet.payload())?;
|
||||
|
||||
let payload = &packet.payload()[ip_packet.header_len() as usize..];
|
||||
if payload.len() < 8 { return Err(Error::Truncated) }
|
||||
if payload.len() < 8 {
|
||||
return Err(Error::Truncated);
|
||||
}
|
||||
let repr = Ipv6Repr {
|
||||
src_addr: ip_packet.src_addr(),
|
||||
dst_addr: ip_packet.dst_addr(),
|
||||
next_header: ip_packet.next_header(),
|
||||
payload_len: payload.len(),
|
||||
hop_limit: ip_packet.hop_limit()
|
||||
hop_limit: ip_packet.hop_limit(),
|
||||
};
|
||||
Ok((payload, repr))
|
||||
}
|
||||
// Valid checksum is expected.
|
||||
if checksum_caps.icmpv6.rx() && !packet.verify_checksum(src_addr, dst_addr) {
|
||||
return Err(Error::Checksum)
|
||||
return Err(Error::Checksum);
|
||||
}
|
||||
|
||||
match (packet.msg_type(), packet.msg_code()) {
|
||||
|
@ -575,149 +579,161 @@ impl<'a> Repr<'a> {
|
|||
Ok(Repr::DstUnreachable {
|
||||
reason: DstUnreachable::from(code),
|
||||
header: repr,
|
||||
data: payload
|
||||
data: payload,
|
||||
})
|
||||
},
|
||||
}
|
||||
(Message::PktTooBig, 0) => {
|
||||
let (payload, repr) = create_packet_from_payload(packet)?;
|
||||
Ok(Repr::PktTooBig {
|
||||
mtu: packet.pkt_too_big_mtu(),
|
||||
header: repr,
|
||||
data: payload
|
||||
data: payload,
|
||||
})
|
||||
},
|
||||
}
|
||||
(Message::TimeExceeded, code) => {
|
||||
let (payload, repr) = create_packet_from_payload(packet)?;
|
||||
Ok(Repr::TimeExceeded {
|
||||
reason: TimeExceeded::from(code),
|
||||
header: repr,
|
||||
data: payload
|
||||
data: payload,
|
||||
})
|
||||
},
|
||||
}
|
||||
(Message::ParamProblem, code) => {
|
||||
let (payload, repr) = create_packet_from_payload(packet)?;
|
||||
Ok(Repr::ParamProblem {
|
||||
reason: ParamProblem::from(code),
|
||||
pointer: packet.param_problem_ptr(),
|
||||
header: repr,
|
||||
data: payload
|
||||
data: payload,
|
||||
})
|
||||
},
|
||||
(Message::EchoRequest, 0) => {
|
||||
Ok(Repr::EchoRequest {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.payload()
|
||||
})
|
||||
},
|
||||
(Message::EchoReply, 0) => {
|
||||
Ok(Repr::EchoReply {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.payload()
|
||||
})
|
||||
},
|
||||
#[cfg(feature = "ethernet")]
|
||||
(msg_type, 0) if msg_type.is_ndisc() => {
|
||||
NdiscRepr::parse(packet).map(Repr::Ndisc)
|
||||
},
|
||||
(msg_type, 0) if msg_type.is_mld() => {
|
||||
MldRepr::parse(packet).map(Repr::Mld)
|
||||
},
|
||||
_ => Err(Error::Unrecognized)
|
||||
}
|
||||
(Message::EchoRequest, 0) => Ok(Repr::EchoRequest {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.payload(),
|
||||
}),
|
||||
(Message::EchoReply, 0) => Ok(Repr::EchoReply {
|
||||
ident: packet.echo_ident(),
|
||||
seq_no: packet.echo_seq_no(),
|
||||
data: packet.payload(),
|
||||
}),
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
(msg_type, 0) if msg_type.is_ndisc() => NdiscRepr::parse(packet).map(Repr::Ndisc),
|
||||
(msg_type, 0) if msg_type.is_mld() => MldRepr::parse(packet).map(Repr::Mld),
|
||||
_ => Err(Error::Unrecognized),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the length of a packet that will be emitted from this high-level representation.
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
match self {
|
||||
&Repr::DstUnreachable { header, data, .. } | &Repr::PktTooBig { header, data, .. } |
|
||||
&Repr::TimeExceeded { header, data, .. } | &Repr::ParamProblem { header, data, .. } => {
|
||||
&Repr::DstUnreachable { header, data, .. }
|
||||
| &Repr::PktTooBig { header, data, .. }
|
||||
| &Repr::TimeExceeded { header, data, .. }
|
||||
| &Repr::ParamProblem { header, data, .. } => {
|
||||
field::UNUSED.end + header.buffer_len() + data.len()
|
||||
}
|
||||
&Repr::EchoRequest { data, .. } |
|
||||
&Repr::EchoReply { data, .. } => {
|
||||
&Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
|
||||
field::ECHO_SEQNO.end + data.len()
|
||||
},
|
||||
#[cfg(feature = "ethernet")]
|
||||
&Repr::Ndisc(ndisc) => {
|
||||
ndisc.buffer_len()
|
||||
},
|
||||
&Repr::Mld(mld) => {
|
||||
mld.buffer_len()
|
||||
},
|
||||
}
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
&Repr::Ndisc(ndisc) => ndisc.buffer_len(),
|
||||
&Repr::Mld(mld) => mld.buffer_len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a high-level representation into an Internet Control Message Protocol version 6
|
||||
/// packet.
|
||||
pub fn emit<T>(&self, src_addr: &IpAddress, dst_addr: &IpAddress,
|
||||
packet: &mut Packet<&mut T>, checksum_caps: &ChecksumCapabilities)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||
pub fn emit<T>(
|
||||
&self,
|
||||
src_addr: &IpAddress,
|
||||
dst_addr: &IpAddress,
|
||||
packet: &mut Packet<&mut T>,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
fn emit_contained_packet(buffer: &mut [u8], header: Ipv6Repr, data: &[u8]) {
|
||||
let mut ip_packet = Ipv6Packet::new_unchecked(buffer);
|
||||
header.emit(&mut ip_packet);
|
||||
let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
|
||||
payload.copy_from_slice(&data[..]);
|
||||
payload.copy_from_slice(data);
|
||||
}
|
||||
|
||||
match *self {
|
||||
Repr::DstUnreachable { reason, header, data } => {
|
||||
Repr::DstUnreachable {
|
||||
reason,
|
||||
header,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::DstUnreachable);
|
||||
packet.set_msg_code(reason.into());
|
||||
|
||||
emit_contained_packet(packet.payload_mut(), header, &data);
|
||||
},
|
||||
emit_contained_packet(packet.payload_mut(), header, data);
|
||||
}
|
||||
|
||||
Repr::PktTooBig { mtu, header, data } => {
|
||||
packet.set_msg_type(Message::PktTooBig);
|
||||
packet.set_msg_code(0);
|
||||
packet.set_pkt_too_big_mtu(mtu);
|
||||
|
||||
emit_contained_packet(packet.payload_mut(), header, &data);
|
||||
},
|
||||
emit_contained_packet(packet.payload_mut(), header, data);
|
||||
}
|
||||
|
||||
Repr::TimeExceeded { reason, header, data } => {
|
||||
Repr::TimeExceeded {
|
||||
reason,
|
||||
header,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::TimeExceeded);
|
||||
packet.set_msg_code(reason.into());
|
||||
|
||||
emit_contained_packet(packet.payload_mut(), header, &data);
|
||||
},
|
||||
emit_contained_packet(packet.payload_mut(), header, data);
|
||||
}
|
||||
|
||||
Repr::ParamProblem { reason, pointer, header, data } => {
|
||||
Repr::ParamProblem {
|
||||
reason,
|
||||
pointer,
|
||||
header,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::ParamProblem);
|
||||
packet.set_msg_code(reason.into());
|
||||
packet.set_param_problem_ptr(pointer);
|
||||
|
||||
emit_contained_packet(packet.payload_mut(), header, &data);
|
||||
},
|
||||
emit_contained_packet(packet.payload_mut(), header, data);
|
||||
}
|
||||
|
||||
Repr::EchoRequest { ident, seq_no, data } => {
|
||||
Repr::EchoRequest {
|
||||
ident,
|
||||
seq_no,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::EchoRequest);
|
||||
packet.set_msg_code(0);
|
||||
packet.set_echo_ident(ident);
|
||||
packet.set_echo_seq_no(seq_no);
|
||||
let data_len = cmp::min(packet.payload_mut().len(), data.len());
|
||||
packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
|
||||
},
|
||||
}
|
||||
|
||||
Repr::EchoReply { ident, seq_no, data } => {
|
||||
Repr::EchoReply {
|
||||
ident,
|
||||
seq_no,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::EchoReply);
|
||||
packet.set_msg_code(0);
|
||||
packet.set_echo_ident(ident);
|
||||
packet.set_echo_seq_no(seq_no);
|
||||
let data_len = cmp::min(packet.payload_mut().len(), data.len());
|
||||
packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
Repr::Ndisc(ndisc) => {
|
||||
ndisc.emit(packet)
|
||||
},
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
Repr::Ndisc(ndisc) => ndisc.emit(packet),
|
||||
|
||||
Repr::Mld(mld) => {
|
||||
mld.emit(packet)
|
||||
},
|
||||
Repr::Mld(mld) => mld.emit(packet),
|
||||
}
|
||||
|
||||
if checksum_caps.icmpv6.tx() {
|
||||
|
@ -731,60 +747,39 @@ impl<'a> Repr<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::wire::{Ipv6Address, Ipv6Repr, IpProtocol};
|
||||
use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2};
|
||||
use super::*;
|
||||
use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2};
|
||||
use crate::wire::{IpProtocol, Ipv6Address, Ipv6Repr};
|
||||
|
||||
static ECHO_PACKET_BYTES: [u8; 12] =
|
||||
[0x80, 0x00, 0x19, 0xb3,
|
||||
0x12, 0x34, 0xab, 0xcd,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static ECHO_PACKET_BYTES: [u8; 12] = [
|
||||
0x80, 0x00, 0x19, 0xb3, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
static ECHO_PACKET_PAYLOAD: [u8; 4] =
|
||||
[0xaa, 0x00, 0x00, 0xff];
|
||||
static ECHO_PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
|
||||
|
||||
static PKT_TOO_BIG_BYTES: [u8; 60] =
|
||||
[0x02, 0x00, 0x0f, 0xc9,
|
||||
0x00, 0x00, 0x05, 0xdc,
|
||||
0x60, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x11, 0x40,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0xbf, 0x00, 0x00, 0x35,
|
||||
0x00, 0x0c, 0x12, 0x4d,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static PKT_TOO_BIG_BYTES: [u8; 60] = [
|
||||
0x02, 0x00, 0x0f, 0xc9, 0x00, 0x00, 0x05, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11,
|
||||
0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
static PKT_TOO_BIG_IP_PAYLOAD: [u8; 52] =
|
||||
[0x60, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x11, 0x40,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0xbf, 0x00, 0x00, 0x35,
|
||||
0x00, 0x0c, 0x12, 0x4d,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static PKT_TOO_BIG_IP_PAYLOAD: [u8; 52] = [
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00,
|
||||
0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
static PKT_TOO_BIG_UDP_PAYLOAD: [u8; 12] =
|
||||
[0xbf, 0x00, 0x00, 0x35,
|
||||
0x00, 0x0c, 0x12, 0x4d,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static PKT_TOO_BIG_UDP_PAYLOAD: [u8; 12] = [
|
||||
0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
fn echo_packet_repr() -> Repr<'static> {
|
||||
Repr::EchoRequest {
|
||||
ident: 0x1234,
|
||||
seq_no: 0xabcd,
|
||||
data: &ECHO_PACKET_PAYLOAD
|
||||
data: &ECHO_PACKET_PAYLOAD,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -792,17 +787,17 @@ mod test {
|
|||
Repr::PktTooBig {
|
||||
mtu: 1500,
|
||||
header: Ipv6Repr {
|
||||
src_addr: Ipv6Address([0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01]),
|
||||
dst_addr: Ipv6Address([0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02]),
|
||||
src_addr: Ipv6Address([
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01,
|
||||
]),
|
||||
dst_addr: Ipv6Address([
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02,
|
||||
]),
|
||||
next_header: IpProtocol::Udp,
|
||||
payload_len: 12,
|
||||
hop_limit: 0x40
|
||||
hop_limit: 0x40,
|
||||
},
|
||||
data: &PKT_TOO_BIG_UDP_PAYLOAD,
|
||||
}
|
||||
|
@ -817,7 +812,7 @@ mod test {
|
|||
assert_eq!(packet.echo_ident(), 0x1234);
|
||||
assert_eq!(packet.echo_seq_no(), 0xabcd);
|
||||
assert_eq!(packet.payload(), &ECHO_PACKET_PAYLOAD[..]);
|
||||
assert_eq!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2), true);
|
||||
assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
|
||||
assert!(!packet.msg_type().is_error());
|
||||
}
|
||||
|
||||
|
@ -829,7 +824,9 @@ mod test {
|
|||
packet.set_msg_code(0);
|
||||
packet.set_echo_ident(0x1234);
|
||||
packet.set_echo_seq_no(0xabcd);
|
||||
packet.payload_mut().copy_from_slice(&ECHO_PACKET_PAYLOAD[..]);
|
||||
packet
|
||||
.payload_mut()
|
||||
.copy_from_slice(&ECHO_PACKET_PAYLOAD[..]);
|
||||
packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
|
||||
assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
|
||||
}
|
||||
|
@ -837,8 +834,13 @@ mod test {
|
|||
#[test]
|
||||
fn test_echo_repr_parse() {
|
||||
let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
|
||||
let repr = Repr::parse(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2,
|
||||
&packet, &ChecksumCapabilities::default()).unwrap();
|
||||
let repr = Repr::parse(
|
||||
&MOCK_IP_ADDR_1,
|
||||
&MOCK_IP_ADDR_2,
|
||||
&packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(repr, echo_packet_repr());
|
||||
}
|
||||
|
||||
|
@ -847,8 +849,12 @@ mod test {
|
|||
let repr = echo_packet_repr();
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2,
|
||||
&mut packet, &ChecksumCapabilities::default());
|
||||
repr.emit(
|
||||
&MOCK_IP_ADDR_1,
|
||||
&MOCK_IP_ADDR_2,
|
||||
&mut packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
|
||||
}
|
||||
|
||||
|
@ -860,7 +866,7 @@ mod test {
|
|||
assert_eq!(packet.checksum(), 0x0fc9);
|
||||
assert_eq!(packet.pkt_too_big_mtu(), 1500);
|
||||
assert_eq!(packet.payload(), &PKT_TOO_BIG_IP_PAYLOAD[..]);
|
||||
assert_eq!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2), true);
|
||||
assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
|
||||
assert!(packet.msg_type().is_error());
|
||||
}
|
||||
|
||||
|
@ -871,7 +877,9 @@ mod test {
|
|||
packet.set_msg_type(Message::PktTooBig);
|
||||
packet.set_msg_code(0);
|
||||
packet.set_pkt_too_big_mtu(1500);
|
||||
packet.payload_mut().copy_from_slice(&PKT_TOO_BIG_IP_PAYLOAD[..]);
|
||||
packet
|
||||
.payload_mut()
|
||||
.copy_from_slice(&PKT_TOO_BIG_IP_PAYLOAD[..]);
|
||||
packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
|
||||
assert_eq!(&packet.into_inner()[..], &PKT_TOO_BIG_BYTES[..]);
|
||||
}
|
||||
|
@ -879,8 +887,13 @@ mod test {
|
|||
#[test]
|
||||
fn test_too_big_repr_parse() {
|
||||
let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]);
|
||||
let repr = Repr::parse(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2,
|
||||
&packet, &ChecksumCapabilities::default()).unwrap();
|
||||
let repr = Repr::parse(
|
||||
&MOCK_IP_ADDR_1,
|
||||
&MOCK_IP_ADDR_2,
|
||||
&packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(repr, too_big_packet_repr());
|
||||
}
|
||||
|
||||
|
@ -889,8 +902,12 @@ mod test {
|
|||
let repr = too_big_packet_repr();
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2,
|
||||
&mut packet, &ChecksumCapabilities::default());
|
||||
repr.emit(
|
||||
&MOCK_IP_ADDR_1,
|
||||
&MOCK_IP_ADDR_2,
|
||||
&mut packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &PKT_TOO_BIG_BYTES[..]);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,9 @@
|
|||
use core::fmt;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::fmt;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::wire::ip::checksum;
|
||||
use crate::time::Duration;
|
||||
use crate::wire::ip::checksum;
|
||||
use crate::{Error, Result};
|
||||
|
||||
use crate::wire::Ipv4Address;
|
||||
|
||||
|
@ -23,6 +23,7 @@ enum_with_unknown! {
|
|||
|
||||
/// A read/write wrapper around an Internet Group Management Protocol v1/v2 packet buffer.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T,
|
||||
}
|
||||
|
@ -171,6 +172,7 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
|
||||
/// A high-level representation of an Internet Group Management Protocol v1/v2 header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Repr {
|
||||
MembershipQuery {
|
||||
max_resp_time: Duration,
|
||||
|
@ -188,6 +190,7 @@ pub enum Repr {
|
|||
|
||||
/// Type of IGMP membership report version
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum IgmpVersion {
|
||||
/// IGMPv1
|
||||
Version1,
|
||||
|
@ -199,7 +202,8 @@ impl Repr {
|
|||
/// Parse an Internet Group Management Protocol v1/v2 packet and return
|
||||
/// a high-level representation.
|
||||
pub fn parse<T>(packet: &Packet<&T>) -> Result<Repr>
|
||||
where T: AsRef<[u8]> + ?Sized
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
// Check if the address is 0.0.0.0 or multicast
|
||||
let addr = packet.group_addr();
|
||||
|
@ -223,13 +227,13 @@ impl Repr {
|
|||
version,
|
||||
})
|
||||
}
|
||||
Message::MembershipReportV2 => {
|
||||
Ok(Repr::MembershipReport {
|
||||
group_addr: packet.group_addr(),
|
||||
version: IgmpVersion::Version2,
|
||||
})
|
||||
}
|
||||
Message::LeaveGroup => Ok(Repr::LeaveGroup { group_addr: packet.group_addr() }),
|
||||
Message::MembershipReportV2 => Ok(Repr::MembershipReport {
|
||||
group_addr: packet.group_addr(),
|
||||
version: IgmpVersion::Version2,
|
||||
}),
|
||||
Message::LeaveGroup => Ok(Repr::LeaveGroup {
|
||||
group_addr: packet.group_addr(),
|
||||
}),
|
||||
Message::MembershipReportV1 => {
|
||||
// for backwards compatibility with IGMPv1
|
||||
Ok(Repr::MembershipReport {
|
||||
|
@ -249,20 +253,21 @@ impl Repr {
|
|||
|
||||
/// Emit a high-level representation into an Internet Group Management Protocol v2 packet.
|
||||
pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized
|
||||
where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
match *self {
|
||||
Repr::MembershipQuery {
|
||||
max_resp_time,
|
||||
group_addr,
|
||||
version
|
||||
version,
|
||||
} => {
|
||||
packet.set_msg_type(Message::MembershipQuery);
|
||||
match version {
|
||||
IgmpVersion::Version1 =>
|
||||
packet.set_max_resp_code(0),
|
||||
IgmpVersion::Version2 =>
|
||||
packet.set_max_resp_code(duration_to_max_resp_code(max_resp_time)),
|
||||
IgmpVersion::Version1 => packet.set_max_resp_code(0),
|
||||
IgmpVersion::Version2 => {
|
||||
packet.set_max_resp_code(duration_to_max_resp_code(max_resp_time))
|
||||
}
|
||||
}
|
||||
packet.set_group_address(group_addr);
|
||||
}
|
||||
|
@ -332,22 +337,19 @@ impl<'a> fmt::Display for Repr {
|
|||
max_resp_time,
|
||||
group_addr,
|
||||
version,
|
||||
} => {
|
||||
write!(f,
|
||||
"IGMP membership query max_resp_time={} group_addr={} version={:?}",
|
||||
max_resp_time,
|
||||
group_addr,
|
||||
version)
|
||||
}
|
||||
} => write!(
|
||||
f,
|
||||
"IGMP membership query max_resp_time={} group_addr={} version={:?}",
|
||||
max_resp_time, group_addr, version
|
||||
),
|
||||
Repr::MembershipReport {
|
||||
group_addr,
|
||||
version,
|
||||
} => {
|
||||
write!(f,
|
||||
"IGMP membership report group_addr={} version={:?}",
|
||||
group_addr,
|
||||
version)
|
||||
}
|
||||
} => write!(
|
||||
f,
|
||||
"IGMP membership report group_addr={} version={:?}",
|
||||
group_addr, version
|
||||
),
|
||||
Repr::LeaveGroup { group_addr } => {
|
||||
write!(f, "IGMP leave group group_addr={})", group_addr)
|
||||
}
|
||||
|
@ -358,10 +360,11 @@ impl<'a> fmt::Display for Repr {
|
|||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent)
|
||||
-> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
match Packet::new_checked(buffer) {
|
||||
Err(err) => writeln!(f, "{}({})", indent, err),
|
||||
Ok(packet) => writeln!(f, "{}{}", indent, packet),
|
||||
|
@ -373,7 +376,6 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
|
||||
static LEAVE_PACKET_BYTES: [u8; 8] = [0x17, 0x00, 0x02, 0x69, 0xe0, 0x00, 0x06, 0x96];
|
||||
static REPORT_PACKET_BYTES: [u8; 8] = [0x16, 0x00, 0x08, 0xda, 0xe1, 0x00, 0x00, 0x25];
|
||||
|
||||
|
@ -383,9 +385,11 @@ mod test {
|
|||
assert_eq!(packet.msg_type(), Message::LeaveGroup);
|
||||
assert_eq!(packet.max_resp_code(), 0);
|
||||
assert_eq!(packet.checksum(), 0x269);
|
||||
assert_eq!(packet.group_addr(),
|
||||
Ipv4Address::from_bytes(&[224, 0, 6, 150]));
|
||||
assert_eq!(packet.verify_checksum(), true);
|
||||
assert_eq!(
|
||||
packet.group_addr(),
|
||||
Ipv4Address::from_bytes(&[224, 0, 6, 150])
|
||||
);
|
||||
assert!(packet.verify_checksum());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -394,9 +398,11 @@ mod test {
|
|||
assert_eq!(packet.msg_type(), Message::MembershipReportV2);
|
||||
assert_eq!(packet.max_resp_code(), 0);
|
||||
assert_eq!(packet.checksum(), 0x08da);
|
||||
assert_eq!(packet.group_addr(),
|
||||
Ipv4Address::from_bytes(&[225, 0, 0, 37]));
|
||||
assert_eq!(packet.verify_checksum(), true);
|
||||
assert_eq!(
|
||||
packet.group_addr(),
|
||||
Ipv4Address::from_bytes(&[225, 0, 0, 37])
|
||||
);
|
||||
assert!(packet.verify_checksum());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -434,9 +440,7 @@ mod test {
|
|||
#[test]
|
||||
fn duration_to_max_resp_time_max() {
|
||||
for duration in 31744..65536 {
|
||||
let time = duration_to_max_resp_code(
|
||||
Duration::from_millis(duration * 100)
|
||||
);
|
||||
let time = duration_to_max_resp_code(Duration::from_millis(duration * 100));
|
||||
assert_eq!(time, 0xFF);
|
||||
}
|
||||
}
|
||||
|
|
641
src/wire/ip.rs
641
src/wire/ip.rs
File diff suppressed because it is too large
Load Diff
507
src/wire/ipv4.rs
507
src/wire/ipv4.rs
|
@ -1,9 +1,9 @@
|
|||
use core::fmt;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::fmt;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::ip::{checksum, pretty_print_ip_payload};
|
||||
use crate::{Error, Result};
|
||||
|
||||
pub use super::IpProtocol as Protocol;
|
||||
|
||||
|
@ -27,10 +27,10 @@ pub struct Address(pub [u8; 4]);
|
|||
|
||||
impl Address {
|
||||
/// An unspecified address.
|
||||
pub const UNSPECIFIED: Address = Address([0x00; 4]);
|
||||
pub const UNSPECIFIED: Address = Address([0x00; 4]);
|
||||
|
||||
/// The broadcast address.
|
||||
pub const BROADCAST: Address = Address([0xff; 4]);
|
||||
pub const BROADCAST: Address = Address([0xff; 4]);
|
||||
|
||||
/// All multicast-capable nodes
|
||||
pub const MULTICAST_ALL_SYSTEMS: Address = Address([224, 0, 0, 1]);
|
||||
|
@ -60,9 +60,7 @@ impl Address {
|
|||
|
||||
/// Query whether the address is an unicast address.
|
||||
pub fn is_unicast(&self) -> bool {
|
||||
!(self.is_broadcast() ||
|
||||
self.is_multicast() ||
|
||||
self.is_unspecified())
|
||||
!(self.is_broadcast() || self.is_multicast() || self.is_unspecified())
|
||||
}
|
||||
|
||||
/// Query whether the address is the broadcast address.
|
||||
|
@ -112,11 +110,25 @@ impl fmt::Display for Address {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Address {
|
||||
fn format(&self, f: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
f,
|
||||
"{=u8}.{=u8}.{=u8}.{=u8}",
|
||||
self.0[0],
|
||||
self.0[1],
|
||||
self.0[2],
|
||||
self.0[3]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A specification of an IPv4 CIDR block, containing an address and a variable-length
|
||||
/// subnet masking prefix length.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
|
||||
pub struct Cidr {
|
||||
address: Address,
|
||||
address: Address,
|
||||
prefix_len: u8,
|
||||
}
|
||||
|
||||
|
@ -130,14 +142,20 @@ impl Cidr {
|
|||
// Replace with const panic (or assert) when stabilized
|
||||
// see: https://github.com/rust-lang/rust/issues/51999
|
||||
["Prefix length should be <= 32"][(prefix_len > 32) as usize];
|
||||
Cidr { address, prefix_len }
|
||||
Cidr {
|
||||
address,
|
||||
prefix_len,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an IPv4 CIDR block from the given address and network mask.
|
||||
pub fn from_netmask(addr: Address, netmask: Address) -> Result<Cidr> {
|
||||
let netmask = NetworkEndian::read_u32(&netmask.0[..]);
|
||||
if netmask.leading_zeros() == 0 && netmask.trailing_zeros() == netmask.count_zeros() {
|
||||
Ok(Cidr { address: addr, prefix_len: netmask.count_ones() as u8 })
|
||||
Ok(Cidr {
|
||||
address: addr,
|
||||
prefix_len: netmask.count_ones() as u8,
|
||||
})
|
||||
} else {
|
||||
Err(Error::Illegal)
|
||||
}
|
||||
|
@ -163,8 +181,8 @@ impl Cidr {
|
|||
let data = [
|
||||
((number >> 24) & 0xff) as u8,
|
||||
((number >> 16) & 0xff) as u8,
|
||||
((number >> 8) & 0xff) as u8,
|
||||
((number >> 0) & 0xff) as u8,
|
||||
((number >> 8) & 0xff) as u8,
|
||||
((number >> 0) & 0xff) as u8,
|
||||
];
|
||||
|
||||
Address(data)
|
||||
|
@ -183,8 +201,8 @@ impl Cidr {
|
|||
let data = [
|
||||
((number >> 24) & 0xff) as u8,
|
||||
((number >> 16) & 0xff) as u8,
|
||||
((number >> 8) & 0xff) as u8,
|
||||
((number >> 0) & 0xff) as u8,
|
||||
((number >> 8) & 0xff) as u8,
|
||||
((number >> 0) & 0xff) as u8,
|
||||
];
|
||||
|
||||
Some(Address(data))
|
||||
|
@ -199,14 +217,19 @@ impl Cidr {
|
|||
self.address.0[2] & mask[2],
|
||||
self.address.0[3] & mask[3],
|
||||
];
|
||||
Cidr { address: Address(network), prefix_len: self.prefix_len }
|
||||
Cidr {
|
||||
address: Address(network),
|
||||
prefix_len: self.prefix_len,
|
||||
}
|
||||
}
|
||||
|
||||
/// Query whether the subnetwork described by this IPv4 CIDR block contains
|
||||
/// the given address.
|
||||
pub fn contains_addr(&self, addr: &Address) -> bool {
|
||||
// right shift by 32 is not legal
|
||||
if self.prefix_len == 0 { return true }
|
||||
if self.prefix_len == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let shift = 32 - self.prefix_len;
|
||||
let self_prefix = NetworkEndian::read_u32(self.address.as_bytes()) >> shift;
|
||||
|
@ -227,27 +250,37 @@ impl fmt::Display for Cidr {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Cidr {
|
||||
fn format(&self, f: defmt::Formatter) {
|
||||
defmt::write!(f, "{:?}/{=u8}", self.address, self.prefix_len);
|
||||
}
|
||||
}
|
||||
|
||||
/// A read/write wrapper around an Internet Protocol version 4 packet buffer.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
mod field {
|
||||
use crate::wire::field::*;
|
||||
|
||||
pub const VER_IHL: usize = 0;
|
||||
pub const VER_IHL: usize = 0;
|
||||
pub const DSCP_ECN: usize = 1;
|
||||
pub const LENGTH: Field = 2..4;
|
||||
pub const IDENT: Field = 4..6;
|
||||
pub const FLG_OFF: Field = 6..8;
|
||||
pub const TTL: usize = 8;
|
||||
pub const LENGTH: Field = 2..4;
|
||||
pub const IDENT: Field = 4..6;
|
||||
pub const FLG_OFF: Field = 6..8;
|
||||
pub const TTL: usize = 8;
|
||||
pub const PROTOCOL: usize = 9;
|
||||
pub const CHECKSUM: Field = 10..12;
|
||||
pub const SRC_ADDR: Field = 12..16;
|
||||
pub const DST_ADDR: Field = 16..20;
|
||||
}
|
||||
|
||||
pub const HEADER_LEN: usize = field::DST_ADDR.end;
|
||||
|
||||
impl<T: AsRef<[u8]>> Packet<T> {
|
||||
/// Imbue a raw octet buffer with IPv4 packet structure.
|
||||
pub fn new_unchecked(buffer: T) -> Packet<T> {
|
||||
|
@ -396,7 +429,9 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
/// # Fuzzing
|
||||
/// This function always returns `true` when fuzzing.
|
||||
pub fn verify_checksum(&self) -> bool {
|
||||
if cfg!(fuzzing) { return true }
|
||||
if cfg!(fuzzing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let data = self.buffer.as_ref();
|
||||
checksum::data(&data[..self.header_len() as usize]) == !0
|
||||
|
@ -552,37 +587,48 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
|
|||
|
||||
/// A high-level representation of an Internet Protocol version 4 packet header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr {
|
||||
pub src_addr: Address,
|
||||
pub dst_addr: Address,
|
||||
pub protocol: Protocol,
|
||||
pub src_addr: Address,
|
||||
pub dst_addr: Address,
|
||||
pub protocol: Protocol,
|
||||
pub payload_len: usize,
|
||||
pub hop_limit: u8
|
||||
pub hop_limit: u8,
|
||||
}
|
||||
|
||||
impl Repr {
|
||||
/// Parse an Internet Protocol version 4 packet and return a high-level representation.
|
||||
pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&T>,
|
||||
checksum_caps: &ChecksumCapabilities) -> Result<Repr> {
|
||||
pub fn parse<T: AsRef<[u8]> + ?Sized>(
|
||||
packet: &Packet<&T>,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) -> Result<Repr> {
|
||||
// Version 4 is expected.
|
||||
if packet.version() != 4 { return Err(Error::Malformed) }
|
||||
if packet.version() != 4 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
// Valid checksum is expected.
|
||||
if checksum_caps.ipv4.rx() && !packet.verify_checksum() { return Err(Error::Checksum) }
|
||||
if checksum_caps.ipv4.rx() && !packet.verify_checksum() {
|
||||
return Err(Error::Checksum);
|
||||
}
|
||||
// We do not support fragmentation.
|
||||
if packet.more_frags() || packet.frag_offset() != 0 { return Err(Error::Fragmented) }
|
||||
if packet.more_frags() || packet.frag_offset() != 0 {
|
||||
return Err(Error::Fragmented);
|
||||
}
|
||||
// Since the packet is not fragmented, it must include the entire payload.
|
||||
let payload_len = packet.total_len() as usize - packet.header_len() as usize;
|
||||
if packet.payload().len() < payload_len { return Err(Error::Truncated) }
|
||||
if packet.payload().len() < payload_len {
|
||||
return Err(Error::Truncated);
|
||||
}
|
||||
|
||||
// All DSCP values are acceptable, since they are of no concern to receiving endpoint.
|
||||
// All ECN values are acceptable, since ECN requires opt-in from both endpoints.
|
||||
// All TTL values are acceptable, since we do not perform routing.
|
||||
Ok(Repr {
|
||||
src_addr: packet.src_addr(),
|
||||
dst_addr: packet.dst_addr(),
|
||||
protocol: packet.protocol(),
|
||||
src_addr: packet.src_addr(),
|
||||
dst_addr: packet.dst_addr(),
|
||||
protocol: packet.protocol(),
|
||||
payload_len: payload_len,
|
||||
hop_limit: packet.hop_limit()
|
||||
hop_limit: packet.hop_limit(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -593,7 +639,11 @@ impl Repr {
|
|||
}
|
||||
|
||||
/// Emit a high-level representation into an Internet Protocol version 4 packet.
|
||||
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>, checksum_caps: &ChecksumCapabilities) {
|
||||
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
|
||||
&self,
|
||||
packet: &mut Packet<T>,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) {
|
||||
packet.set_version(4);
|
||||
packet.set_header_len(field::DST_ADDR.end as u8);
|
||||
packet.set_dscp(0);
|
||||
|
@ -626,8 +676,14 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
|||
Ok(repr) => write!(f, "{}", repr),
|
||||
Err(err) => {
|
||||
write!(f, "IPv4 ({})", err)?;
|
||||
write!(f, " src={} dst={} proto={} hop_limit={}",
|
||||
self.src_addr(), self.dst_addr(), self.protocol(), self.hop_limit())?;
|
||||
write!(
|
||||
f,
|
||||
" src={} dst={} proto={} hop_limit={}",
|
||||
self.src_addr(),
|
||||
self.dst_addr(),
|
||||
self.protocol(),
|
||||
self.hop_limit()
|
||||
)?;
|
||||
if self.version() != 4 {
|
||||
write!(f, " ver={}", self.version())?;
|
||||
}
|
||||
|
@ -661,32 +717,36 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
|||
|
||||
impl fmt::Display for Repr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "IPv4 src={} dst={} proto={}",
|
||||
self.src_addr, self.dst_addr, self.protocol)
|
||||
write!(
|
||||
f,
|
||||
"IPv4 src={} dst={} proto={}",
|
||||
self.src_addr, self.dst_addr, self.protocol
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
use crate::wire::ip::checksum::format_checksum;
|
||||
|
||||
let checksum_caps = ChecksumCapabilities::ignored();
|
||||
|
||||
let (ip_repr, payload) = match Packet::new_checked(buffer) {
|
||||
Err(err) => return write!(f, "{}({})", indent, err),
|
||||
Ok(ip_packet) => {
|
||||
match Repr::parse(&ip_packet, &checksum_caps) {
|
||||
Err(_) => return Ok(()),
|
||||
Ok(ip_repr) => {
|
||||
write!(f, "{}{}", indent, ip_repr)?;
|
||||
format_checksum(f, ip_packet.verify_checksum())?;
|
||||
(ip_repr, ip_packet.payload())
|
||||
}
|
||||
Ok(ip_packet) => match Repr::parse(&ip_packet, &checksum_caps) {
|
||||
Err(_) => return Ok(()),
|
||||
Ok(ip_repr) => {
|
||||
write!(f, "{}{}", indent, ip_repr)?;
|
||||
format_checksum(f, ip_packet.verify_checksum())?;
|
||||
(ip_repr, ip_packet.payload())
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
pretty_print_ip_payload(f, indent, ip_repr, payload)
|
||||
|
@ -697,20 +757,12 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
static PACKET_BYTES: [u8; 30] =
|
||||
[0x45, 0x00, 0x00, 0x1e,
|
||||
0x01, 0x02, 0x62, 0x03,
|
||||
0x1a, 0x01, 0xd5, 0x6e,
|
||||
0x11, 0x12, 0x13, 0x14,
|
||||
0x21, 0x22, 0x23, 0x24,
|
||||
0xaa, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xff];
|
||||
static PACKET_BYTES: [u8; 30] = [
|
||||
0x45, 0x00, 0x00, 0x1e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xd5, 0x6e, 0x11, 0x12, 0x13,
|
||||
0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
static PAYLOAD_BYTES: [u8; 10] =
|
||||
[0xaa, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xff];
|
||||
static PAYLOAD_BYTES: [u8; 10] = [0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff];
|
||||
|
||||
#[test]
|
||||
fn test_deconstruct() {
|
||||
|
@ -721,15 +773,15 @@ mod test {
|
|||
assert_eq!(packet.ecn(), 0);
|
||||
assert_eq!(packet.total_len(), 30);
|
||||
assert_eq!(packet.ident(), 0x102);
|
||||
assert_eq!(packet.more_frags(), true);
|
||||
assert_eq!(packet.dont_frag(), true);
|
||||
assert!(packet.more_frags());
|
||||
assert!(packet.dont_frag());
|
||||
assert_eq!(packet.frag_offset(), 0x203 * 8);
|
||||
assert_eq!(packet.hop_limit(), 0x1a);
|
||||
assert_eq!(packet.protocol(), Protocol::Icmp);
|
||||
assert_eq!(packet.checksum(), 0xd56e);
|
||||
assert_eq!(packet.src_addr(), Address([0x11, 0x12, 0x13, 0x14]));
|
||||
assert_eq!(packet.dst_addr(), Address([0x21, 0x22, 0x23, 0x24]));
|
||||
assert_eq!(packet.verify_checksum(), true);
|
||||
assert!(packet.verify_checksum());
|
||||
assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]);
|
||||
}
|
||||
|
||||
|
@ -762,10 +814,14 @@ mod test {
|
|||
bytes.extend(&PACKET_BYTES[..]);
|
||||
bytes.push(0);
|
||||
|
||||
assert_eq!(Packet::new_unchecked(&bytes).payload().len(),
|
||||
PAYLOAD_BYTES.len());
|
||||
assert_eq!(Packet::new_unchecked(&mut bytes).payload_mut().len(),
|
||||
PAYLOAD_BYTES.len());
|
||||
assert_eq!(
|
||||
Packet::new_unchecked(&bytes).payload().len(),
|
||||
PAYLOAD_BYTES.len()
|
||||
);
|
||||
assert_eq!(
|
||||
Packet::new_unchecked(&mut bytes).payload_mut().len(),
|
||||
PAYLOAD_BYTES.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -774,28 +830,23 @@ mod test {
|
|||
bytes.extend(&PACKET_BYTES[..]);
|
||||
Packet::new_unchecked(&mut bytes).set_total_len(128);
|
||||
|
||||
assert_eq!(Packet::new_checked(&bytes).unwrap_err(),
|
||||
Error::Truncated);
|
||||
assert_eq!(Packet::new_checked(&bytes).unwrap_err(), Error::Truncated);
|
||||
}
|
||||
|
||||
static REPR_PACKET_BYTES: [u8; 24] =
|
||||
[0x45, 0x00, 0x00, 0x18,
|
||||
0x00, 0x00, 0x40, 0x00,
|
||||
0x40, 0x01, 0xd2, 0x79,
|
||||
0x11, 0x12, 0x13, 0x14,
|
||||
0x21, 0x22, 0x23, 0x24,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static REPR_PACKET_BYTES: [u8; 24] = [
|
||||
0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x01, 0xd2, 0x79, 0x11, 0x12, 0x13,
|
||||
0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
static REPR_PAYLOAD_BYTES: [u8; 4] =
|
||||
[0xaa, 0x00, 0x00, 0xff];
|
||||
static REPR_PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
|
||||
|
||||
fn packet_repr() -> Repr {
|
||||
Repr {
|
||||
src_addr: Address([0x11, 0x12, 0x13, 0x14]),
|
||||
dst_addr: Address([0x21, 0x22, 0x23, 0x24]),
|
||||
protocol: Protocol::Icmp,
|
||||
src_addr: Address([0x11, 0x12, 0x13, 0x14]),
|
||||
dst_addr: Address([0x21, 0x22, 0x23, 0x24]),
|
||||
protocol: Protocol::Icmp,
|
||||
payload_len: 4,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -814,7 +865,10 @@ mod test {
|
|||
packet.set_version(6);
|
||||
packet.fill_checksum();
|
||||
let packet = Packet::new_unchecked(&*packet.into_inner());
|
||||
assert_eq!(Repr::parse(&packet, &ChecksumCapabilities::default()), Err(Error::Malformed));
|
||||
assert_eq!(
|
||||
Repr::parse(&packet, &ChecksumCapabilities::default()),
|
||||
Err(Error::Malformed)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -857,28 +911,34 @@ mod test {
|
|||
let cidr = Cidr::new(Address::new(192, 168, 1, 10), 24);
|
||||
|
||||
let inside_subnet = [
|
||||
[192, 168, 1, 0], [192, 168, 1, 1],
|
||||
[192, 168, 1, 2], [192, 168, 1, 10],
|
||||
[192, 168, 1, 127], [192, 168, 1, 255],
|
||||
[192, 168, 1, 0],
|
||||
[192, 168, 1, 1],
|
||||
[192, 168, 1, 2],
|
||||
[192, 168, 1, 10],
|
||||
[192, 168, 1, 127],
|
||||
[192, 168, 1, 255],
|
||||
];
|
||||
|
||||
let outside_subnet = [
|
||||
[192, 168, 0, 0], [127, 0, 0, 1],
|
||||
[192, 168, 2, 0], [192, 168, 0, 255],
|
||||
[ 0, 0, 0, 0], [255, 255, 255, 255],
|
||||
[192, 168, 0, 0],
|
||||
[127, 0, 0, 1],
|
||||
[192, 168, 2, 0],
|
||||
[192, 168, 0, 255],
|
||||
[0, 0, 0, 0],
|
||||
[255, 255, 255, 255],
|
||||
];
|
||||
|
||||
let subnets = [
|
||||
([192, 168, 1, 0], 32),
|
||||
([192, 168, 1, 255], 24),
|
||||
([192, 168, 1, 10], 30),
|
||||
([192, 168, 1, 0], 32),
|
||||
([192, 168, 1, 255], 24),
|
||||
([192, 168, 1, 10], 30),
|
||||
];
|
||||
|
||||
let not_subnets = [
|
||||
([192, 168, 1, 10], 23),
|
||||
([127, 0, 0, 1], 8),
|
||||
([192, 168, 1, 0], 0),
|
||||
([192, 168, 0, 255], 32),
|
||||
([192, 168, 1, 10], 23),
|
||||
([127, 0, 0, 1], 8),
|
||||
([192, 168, 1, 0], 0),
|
||||
([192, 168, 0, 255], 32),
|
||||
];
|
||||
|
||||
for addr in inside_subnet.iter().map(|a| Address::from_bytes(a)) {
|
||||
|
@ -889,13 +949,17 @@ mod test {
|
|||
assert!(!cidr.contains_addr(&addr));
|
||||
}
|
||||
|
||||
for subnet in subnets.iter().map(
|
||||
|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p)) {
|
||||
for subnet in subnets
|
||||
.iter()
|
||||
.map(|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p))
|
||||
{
|
||||
assert!(cidr.contains_subnet(&subnet));
|
||||
}
|
||||
|
||||
for subnet in not_subnets.iter().map(
|
||||
|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p)) {
|
||||
for subnet in not_subnets
|
||||
.iter()
|
||||
.map(|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p))
|
||||
{
|
||||
assert!(!cidr.contains_subnet(&subnet));
|
||||
}
|
||||
|
||||
|
@ -905,94 +969,169 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_cidr_from_netmask() {
|
||||
assert_eq!(Cidr::from_netmask(Address([0, 0, 0, 0]), Address([1, 0, 2, 0])).is_err(),
|
||||
true);
|
||||
assert_eq!(Cidr::from_netmask(Address([0, 0, 0, 0]), Address([0, 0, 0, 0])).is_err(),
|
||||
true);
|
||||
assert_eq!(Cidr::from_netmask(Address([0, 0, 0, 1]), Address([255, 255, 255, 0])).unwrap(),
|
||||
Cidr::new(Address([0, 0, 0, 1]), 24));
|
||||
assert_eq!(Cidr::from_netmask(Address([192, 168, 0, 1]), Address([255, 255, 0, 0])).unwrap(),
|
||||
Cidr::new(Address([192, 168, 0, 1]), 16));
|
||||
assert_eq!(Cidr::from_netmask(Address([172, 16, 0, 1]), Address([255, 240, 0, 0])).unwrap(),
|
||||
Cidr::new(Address([172, 16, 0, 1]), 12));
|
||||
assert_eq!(Cidr::from_netmask(Address([255, 255, 255, 1]), Address([255, 255, 255, 0])).unwrap(),
|
||||
Cidr::new(Address([255, 255, 255, 1]), 24));
|
||||
assert_eq!(Cidr::from_netmask(Address([255, 255, 255, 255]), Address([255, 255, 255, 255])).unwrap(),
|
||||
Cidr::new(Address([255, 255, 255, 255]), 32));
|
||||
assert!(Cidr::from_netmask(Address([0, 0, 0, 0]), Address([1, 0, 2, 0])).is_err());
|
||||
assert!(Cidr::from_netmask(Address([0, 0, 0, 0]), Address([0, 0, 0, 0])).is_err());
|
||||
assert_eq!(
|
||||
Cidr::from_netmask(Address([0, 0, 0, 1]), Address([255, 255, 255, 0])).unwrap(),
|
||||
Cidr::new(Address([0, 0, 0, 1]), 24)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::from_netmask(Address([192, 168, 0, 1]), Address([255, 255, 0, 0])).unwrap(),
|
||||
Cidr::new(Address([192, 168, 0, 1]), 16)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::from_netmask(Address([172, 16, 0, 1]), Address([255, 240, 0, 0])).unwrap(),
|
||||
Cidr::new(Address([172, 16, 0, 1]), 12)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::from_netmask(Address([255, 255, 255, 1]), Address([255, 255, 255, 0])).unwrap(),
|
||||
Cidr::new(Address([255, 255, 255, 1]), 24)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::from_netmask(Address([255, 255, 255, 255]), Address([255, 255, 255, 255]))
|
||||
.unwrap(),
|
||||
Cidr::new(Address([255, 255, 255, 255]), 32)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cidr_netmask() {
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 0).netmask(),
|
||||
Address([0, 0, 0, 0]));
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 1]), 24).netmask(),
|
||||
Address([255, 255, 255, 0]));
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 32).netmask(),
|
||||
Address([255, 255, 255, 255]));
|
||||
assert_eq!(Cidr::new(Address([127, 0, 0, 0]), 8).netmask(),
|
||||
Address([255, 0, 0, 0]));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 0, 0]), 16).netmask(),
|
||||
Address([255, 255, 0, 0]));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 1, 1]), 16).netmask(),
|
||||
Address([255, 255, 0, 0]));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 1, 1]), 17).netmask(),
|
||||
Address([255, 255, 128, 0]));
|
||||
assert_eq!(Cidr::new(Address([172, 16, 0, 0]), 12).netmask(),
|
||||
Address([255, 240, 0, 0]));
|
||||
assert_eq!(Cidr::new(Address([255, 255, 255, 1]), 24).netmask(),
|
||||
Address([255, 255, 255, 0]));
|
||||
assert_eq!(Cidr::new(Address([255, 255, 255, 255]), 32).netmask(),
|
||||
Address([255, 255, 255, 255]));
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 0]), 0).netmask(),
|
||||
Address([0, 0, 0, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 1]), 24).netmask(),
|
||||
Address([255, 255, 255, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 0]), 32).netmask(),
|
||||
Address([255, 255, 255, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([127, 0, 0, 0]), 8).netmask(),
|
||||
Address([255, 0, 0, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 0, 0]), 16).netmask(),
|
||||
Address([255, 255, 0, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 1, 1]), 16).netmask(),
|
||||
Address([255, 255, 0, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 1, 1]), 17).netmask(),
|
||||
Address([255, 255, 128, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([172, 16, 0, 0]), 12).netmask(),
|
||||
Address([255, 240, 0, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([255, 255, 255, 1]), 24).netmask(),
|
||||
Address([255, 255, 255, 0])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([255, 255, 255, 255]), 32).netmask(),
|
||||
Address([255, 255, 255, 255])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cidr_broadcast() {
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 0).broadcast().unwrap(),
|
||||
Address([255, 255, 255, 255]));
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 1]), 24).broadcast().unwrap(),
|
||||
Address([0, 0, 0, 255]));
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 32).broadcast(),
|
||||
None);
|
||||
assert_eq!(Cidr::new(Address([127, 0, 0, 0]), 8).broadcast().unwrap(),
|
||||
Address([127, 255, 255, 255]));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 0, 0]), 16).broadcast().unwrap(),
|
||||
Address([192, 168, 255, 255]));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 1, 1]), 16).broadcast().unwrap(),
|
||||
Address([192, 168, 255, 255]));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 1, 1]), 17).broadcast().unwrap(),
|
||||
Address([192, 168, 127, 255]));
|
||||
assert_eq!(Cidr::new(Address([172, 16, 0, 1]), 12).broadcast().unwrap(),
|
||||
Address([172, 31, 255, 255]));
|
||||
assert_eq!(Cidr::new(Address([255, 255, 255, 1]), 24).broadcast().unwrap(),
|
||||
Address([255, 255, 255, 255]));
|
||||
assert_eq!(Cidr::new(Address([255, 255, 255, 254]), 31).broadcast(),
|
||||
None);
|
||||
assert_eq!(Cidr::new(Address([255, 255, 255, 255]), 32).broadcast(),
|
||||
None);
|
||||
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 0]), 0).broadcast().unwrap(),
|
||||
Address([255, 255, 255, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 1]), 24).broadcast().unwrap(),
|
||||
Address([0, 0, 0, 255])
|
||||
);
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 32).broadcast(), None);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([127, 0, 0, 0]), 8).broadcast().unwrap(),
|
||||
Address([127, 255, 255, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 0, 0]), 16)
|
||||
.broadcast()
|
||||
.unwrap(),
|
||||
Address([192, 168, 255, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 1, 1]), 16)
|
||||
.broadcast()
|
||||
.unwrap(),
|
||||
Address([192, 168, 255, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 1, 1]), 17)
|
||||
.broadcast()
|
||||
.unwrap(),
|
||||
Address([192, 168, 127, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([172, 16, 0, 1]), 12).broadcast().unwrap(),
|
||||
Address([172, 31, 255, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([255, 255, 255, 1]), 24)
|
||||
.broadcast()
|
||||
.unwrap(),
|
||||
Address([255, 255, 255, 255])
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([255, 255, 255, 254]), 31).broadcast(),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([255, 255, 255, 255]), 32).broadcast(),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cidr_network() {
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 0).network(),
|
||||
Cidr::new(Address([0, 0, 0, 0]), 0));
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 1]), 24).network(),
|
||||
Cidr::new(Address([0, 0, 0, 0]), 24));
|
||||
assert_eq!(Cidr::new(Address([0, 0, 0, 0]), 32).network(),
|
||||
Cidr::new(Address([0, 0, 0, 0]), 32));
|
||||
assert_eq!(Cidr::new(Address([127, 0, 0, 0]), 8).network(),
|
||||
Cidr::new(Address([127, 0, 0, 0]), 8));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 0, 0]), 16).network(),
|
||||
Cidr::new(Address([192, 168, 0, 0]), 16));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 1, 1]), 16).network(),
|
||||
Cidr::new(Address([192, 168, 0, 0]), 16));
|
||||
assert_eq!(Cidr::new(Address([192, 168, 1, 1]), 17).network(),
|
||||
Cidr::new(Address([192, 168, 0, 0]), 17));
|
||||
assert_eq!(Cidr::new(Address([172, 16, 0, 1]), 12).network(),
|
||||
Cidr::new(Address([172, 16, 0, 0]), 12));
|
||||
assert_eq!(Cidr::new(Address([255, 255, 255, 1]), 24).network(),
|
||||
Cidr::new(Address([255, 255, 255, 0]), 24));
|
||||
assert_eq!(Cidr::new(Address([255, 255, 255, 255]), 32).network(),
|
||||
Cidr::new(Address([255, 255, 255, 255]), 32));
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 0]), 0).network(),
|
||||
Cidr::new(Address([0, 0, 0, 0]), 0)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 1]), 24).network(),
|
||||
Cidr::new(Address([0, 0, 0, 0]), 24)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([0, 0, 0, 0]), 32).network(),
|
||||
Cidr::new(Address([0, 0, 0, 0]), 32)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([127, 0, 0, 0]), 8).network(),
|
||||
Cidr::new(Address([127, 0, 0, 0]), 8)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 0, 0]), 16).network(),
|
||||
Cidr::new(Address([192, 168, 0, 0]), 16)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 1, 1]), 16).network(),
|
||||
Cidr::new(Address([192, 168, 0, 0]), 16)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([192, 168, 1, 1]), 17).network(),
|
||||
Cidr::new(Address([192, 168, 0, 0]), 17)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([172, 16, 0, 1]), 12).network(),
|
||||
Cidr::new(Address([172, 16, 0, 0]), 12)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([255, 255, 255, 1]), 24).network(),
|
||||
Cidr::new(Address([255, 255, 255, 0]), 24)
|
||||
);
|
||||
assert_eq!(
|
||||
Cidr::new(Address([255, 255, 255, 255]), 32).network(),
|
||||
Cidr::new(Address([255, 255, 255, 255]), 32)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
495
src/wire/ipv6.rs
495
src/wire/ipv6.rs
|
@ -1,12 +1,12 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
use core::fmt;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::fmt;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::wire::ip::pretty_print_ip_payload;
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::ipv4;
|
||||
use crate::{Error, Result};
|
||||
|
||||
pub use super::IpProtocol as Protocol;
|
||||
|
||||
|
@ -17,6 +17,7 @@ pub const MIN_MTU: usize = 1280;
|
|||
|
||||
/// A sixteen-octet IPv6 address.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Address(pub [u8; 16]);
|
||||
|
||||
impl Address {
|
||||
|
@ -28,28 +29,30 @@ impl Address {
|
|||
/// The link-local [all routers multicast address].
|
||||
///
|
||||
/// [all routers multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1
|
||||
pub const LINK_LOCAL_ALL_NODES: Address =
|
||||
Address([0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
pub const LINK_LOCAL_ALL_NODES: Address = Address([
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01,
|
||||
]);
|
||||
|
||||
/// The link-local [all nodes multicast address].
|
||||
///
|
||||
/// [all nodes multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1
|
||||
pub const LINK_LOCAL_ALL_ROUTERS: Address =
|
||||
Address([0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]);
|
||||
pub const LINK_LOCAL_ALL_ROUTERS: Address = Address([
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02,
|
||||
]);
|
||||
|
||||
/// The [loopback address].
|
||||
///
|
||||
/// [loopback address]: https://tools.ietf.org/html/rfc4291#section-2.5.3
|
||||
pub const LOOPBACK: Address =
|
||||
Address([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
pub const LOOPBACK: Address = Address([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01,
|
||||
]);
|
||||
|
||||
/// Construct an IPv6 address from parts.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(a0: u16, a1: u16, a2: u16, a3: u16,
|
||||
a4: u16, a5: u16, a6: u16, a7: u16) -> Address {
|
||||
pub fn new(a0: u16, a1: u16, a2: u16, a3: u16, a4: u16, a5: u16, a6: u16, a7: u16) -> Address {
|
||||
let mut addr = [0u8; 16];
|
||||
NetworkEndian::write_u16(&mut addr[0..2], a0);
|
||||
NetworkEndian::write_u16(&mut addr[2..4], a1);
|
||||
|
@ -126,8 +129,7 @@ impl Address {
|
|||
///
|
||||
/// [link-local]: https://tools.ietf.org/html/rfc4291#section-2.5.6
|
||||
pub fn is_link_local(&self) -> bool {
|
||||
self.0[0..8] == [0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00]
|
||||
self.0[0..8] == [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
}
|
||||
|
||||
/// Query whether the IPv6 address is the [loopback address].
|
||||
|
@ -148,7 +150,9 @@ impl Address {
|
|||
/// Convert an IPv4 mapped IPv6 address to an IPv4 address.
|
||||
pub fn as_ipv4(&self) -> Option<ipv4::Address> {
|
||||
if self.is_ipv4_mapped() {
|
||||
Some(ipv4::Address::new(self.0[12], self.0[13], self.0[14], self.0[15]))
|
||||
Some(ipv4::Address::new(
|
||||
self.0[12], self.0[13], self.0[14], self.0[15],
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -164,7 +168,7 @@ impl Address {
|
|||
let idx = (mask as usize) / 8;
|
||||
let modulus = (mask as usize) % 8;
|
||||
let (first, second) = self.0.split_at(idx);
|
||||
bytes[0..idx].copy_from_slice(&first);
|
||||
bytes[0..idx].copy_from_slice(first);
|
||||
if idx < 16 {
|
||||
let part = second[0];
|
||||
bytes[idx] = part & (!(0xff >> modulus) as u8);
|
||||
|
@ -179,10 +183,10 @@ impl Address {
|
|||
/// unicast.
|
||||
pub fn solicited_node(&self) -> Address {
|
||||
assert!(self.is_unicast());
|
||||
let mut bytes = [0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
bytes[14..].copy_from_slice(&self.0[14..]);
|
||||
Address(bytes)
|
||||
Address([
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
|
||||
self.0[13], self.0[14], self.0[15],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +207,11 @@ impl From<Address> for ::std::net::Ipv6Addr {
|
|||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.is_ipv4_mapped() {
|
||||
return write!(f, "::ffff:{}.{}.{}.{}", self.0[12], self.0[13], self.0[14], self.0[15])
|
||||
return write!(
|
||||
f,
|
||||
"::ffff:{}.{}.{}.{}",
|
||||
self.0[12], self.0[13], self.0[14], self.0[15]
|
||||
);
|
||||
}
|
||||
|
||||
// The string representation of an IPv6 address should
|
||||
|
@ -216,7 +224,7 @@ impl fmt::Display for Address {
|
|||
Head,
|
||||
HeadBody,
|
||||
Tail,
|
||||
TailBody
|
||||
TailBody,
|
||||
}
|
||||
let mut words = [0u16; 8];
|
||||
self.write_parts(&mut words);
|
||||
|
@ -228,7 +236,7 @@ impl fmt::Display for Address {
|
|||
(0, &State::Head) | (0, &State::HeadBody) => {
|
||||
write!(f, "::")?;
|
||||
State::Tail
|
||||
},
|
||||
}
|
||||
// Continue iterating without writing any characters until
|
||||
// we hit anothing non-zero value.
|
||||
(0, &State::Tail) => State::Tail,
|
||||
|
@ -237,11 +245,11 @@ impl fmt::Display for Address {
|
|||
(_, &State::Head) => {
|
||||
write!(f, "{:x}", word)?;
|
||||
State::HeadBody
|
||||
},
|
||||
}
|
||||
(_, &State::Tail) => {
|
||||
write!(f, "{:x}", word)?;
|
||||
State::TailBody
|
||||
},
|
||||
}
|
||||
// Write the u16 with a leading colon when parsing a value
|
||||
// that isn't the first in a section
|
||||
(_, &State::HeadBody) | (_, &State::TailBody) => {
|
||||
|
@ -259,16 +267,18 @@ impl fmt::Display for Address {
|
|||
impl From<ipv4::Address> for Address {
|
||||
fn from(address: ipv4::Address) -> Self {
|
||||
let octets = address.0;
|
||||
Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff,
|
||||
octets[0], octets[1], octets[2], octets[3]])
|
||||
Address([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, octets[0], octets[1], octets[2], octets[3],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// A specification of an IPv6 CIDR block, containing an address and a variable-length
|
||||
/// subnet masking prefix length.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Cidr {
|
||||
address: Address,
|
||||
address: Address,
|
||||
prefix_len: u8,
|
||||
}
|
||||
|
||||
|
@ -276,12 +286,13 @@ impl Cidr {
|
|||
/// The [solicited node prefix].
|
||||
///
|
||||
/// [solicited node prefix]: https://tools.ietf.org/html/rfc4291#section-2.7.1
|
||||
pub const SOLICITED_NODE_PREFIX: Cidr =
|
||||
Cidr {
|
||||
address: Address([0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00]),
|
||||
prefix_len: 104
|
||||
};
|
||||
pub const SOLICITED_NODE_PREFIX: Cidr = Cidr {
|
||||
address: Address([
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00,
|
||||
0x00, 0x00,
|
||||
]),
|
||||
prefix_len: 104,
|
||||
};
|
||||
|
||||
/// Create an IPv6 CIDR block from the given address and prefix length.
|
||||
///
|
||||
|
@ -289,7 +300,10 @@ impl Cidr {
|
|||
/// This function panics if the prefix length is larger than 128.
|
||||
pub fn new(address: Address, prefix_len: u8) -> Cidr {
|
||||
assert!(prefix_len <= 128);
|
||||
Cidr { address, prefix_len }
|
||||
Cidr {
|
||||
address,
|
||||
prefix_len,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the address of this IPv6 CIDR block.
|
||||
|
@ -306,7 +320,9 @@ impl Cidr {
|
|||
/// the given address.
|
||||
pub fn contains_addr(&self, addr: &Address) -> bool {
|
||||
// right shift by 128 is not legal
|
||||
if self.prefix_len == 0 { return true }
|
||||
if self.prefix_len == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let shift = 128 - self.prefix_len;
|
||||
self.address.mask(shift) == addr.mask(shift)
|
||||
|
@ -328,8 +344,9 @@ impl fmt::Display for Cidr {
|
|||
|
||||
/// A read/write wrapper around an Internet Protocol version 6 packet buffer.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
// Ranges and constants describing the IPv6 header
|
||||
|
@ -364,19 +381,22 @@ mod field {
|
|||
pub const VER_TC_FLOW: Field = 0..4;
|
||||
// 16-bit value representing the length of the payload.
|
||||
// Note: Options are included in this length.
|
||||
pub const LENGTH: Field = 4..6;
|
||||
pub const LENGTH: Field = 4..6;
|
||||
// 8-bit value identifying the type of header following this
|
||||
// one. Note: The same numbers are used in IPv4.
|
||||
pub const NXT_HDR: usize = 6;
|
||||
pub const NXT_HDR: usize = 6;
|
||||
// 8-bit value decremented by each node that forwards this
|
||||
// packet. The packet is discarded when the value is 0.
|
||||
pub const HOP_LIMIT: usize = 7;
|
||||
pub const HOP_LIMIT: usize = 7;
|
||||
// IPv6 address of the source node.
|
||||
pub const SRC_ADDR: Field = 8..24;
|
||||
pub const SRC_ADDR: Field = 8..24;
|
||||
// IPv6 address of the destination node.
|
||||
pub const DST_ADDR: Field = 24..40;
|
||||
pub const DST_ADDR: Field = 24..40;
|
||||
}
|
||||
|
||||
/// Length of an IPv6 header.
|
||||
pub const HEADER_LEN: usize = field::DST_ADDR.end;
|
||||
|
||||
impl<T: AsRef<[u8]>> Packet<T> {
|
||||
/// Create a raw octet buffer with an IPv6 packet structure.
|
||||
#[inline]
|
||||
|
@ -593,17 +613,18 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
|
|||
|
||||
/// A high-level representation of an Internet Protocol version 6 packet header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr {
|
||||
/// IPv6 address of the source node.
|
||||
pub src_addr: Address,
|
||||
pub src_addr: Address,
|
||||
/// IPv6 address of the destination node.
|
||||
pub dst_addr: Address,
|
||||
pub dst_addr: Address,
|
||||
/// Protocol contained in the next header.
|
||||
pub next_header: Protocol,
|
||||
/// Length of the payload including the extension headers.
|
||||
pub payload_len: usize,
|
||||
/// The 8-bit hop limit field.
|
||||
pub hop_limit: u8
|
||||
pub hop_limit: u8,
|
||||
}
|
||||
|
||||
impl Repr {
|
||||
|
@ -611,13 +632,15 @@ impl Repr {
|
|||
pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&T>) -> Result<Repr> {
|
||||
// Ensure basic accessors will work
|
||||
packet.check_len()?;
|
||||
if packet.version() != 6 { return Err(Error::Malformed); }
|
||||
if packet.version() != 6 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
Ok(Repr {
|
||||
src_addr: packet.src_addr(),
|
||||
dst_addr: packet.dst_addr(),
|
||||
src_addr: packet.src_addr(),
|
||||
dst_addr: packet.dst_addr(),
|
||||
next_header: packet.next_header(),
|
||||
payload_len: packet.payload_len() as usize,
|
||||
hop_limit: packet.hop_limit()
|
||||
hop_limit: packet.hop_limit(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -644,29 +667,33 @@ impl Repr {
|
|||
|
||||
impl fmt::Display for Repr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "IPv6 src={} dst={} nxt_hdr={} hop_limit={}",
|
||||
self.src_addr, self.dst_addr, self.next_header, self.hop_limit)
|
||||
write!(
|
||||
f,
|
||||
"IPv6 src={} dst={} nxt_hdr={} hop_limit={}",
|
||||
self.src_addr, self.dst_addr, self.next_header, self.hop_limit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
// TODO: This is very similar to the implementation for IPv4. Make
|
||||
// a way to have less copy and pasted code here.
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
let (ip_repr, payload) = match Packet::new_checked(buffer) {
|
||||
Err(err) => return write!(f, "{}({})", indent, err),
|
||||
Ok(ip_packet) => {
|
||||
match Repr::parse(&ip_packet) {
|
||||
Err(_) => return Ok(()),
|
||||
Ok(ip_repr) => {
|
||||
write!(f, "{}{}", indent, ip_repr)?;
|
||||
(ip_repr, ip_packet.payload())
|
||||
}
|
||||
Ok(ip_packet) => match Repr::parse(&ip_packet) {
|
||||
Err(_) => return Ok(()),
|
||||
Ok(ip_repr) => {
|
||||
write!(f, "{}{}", indent, ip_repr)?;
|
||||
(ip_repr, ip_packet.payload())
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
pretty_print_ip_payload(f, indent, ip_repr, payload)
|
||||
|
@ -675,18 +702,18 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Error;
|
||||
use super::{Address, Cidr};
|
||||
use super::{Packet, Protocol, Repr};
|
||||
use crate::wire::pretty_print::{PrettyPrinter};
|
||||
use crate::wire::pretty_print::PrettyPrinter;
|
||||
use crate::Error;
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::ipv4::Address as Ipv4Address;
|
||||
|
||||
static LINK_LOCAL_ADDR: Address = Address([0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01]);
|
||||
static LINK_LOCAL_ADDR: Address = Address([
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01,
|
||||
]);
|
||||
#[test]
|
||||
fn test_basic_multicast() {
|
||||
assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_unspecified());
|
||||
|
@ -717,48 +744,62 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_address_format() {
|
||||
assert_eq!("ff02::1",
|
||||
format!("{}", Address::LINK_LOCAL_ALL_NODES));
|
||||
assert_eq!("fe80::1",
|
||||
format!("{}", LINK_LOCAL_ADDR));
|
||||
assert_eq!("fe80::7f00:0:1",
|
||||
format!("{}", Address::new(0xfe80, 0, 0, 0, 0, 0x7f00, 0x0000, 0x0001)));
|
||||
assert_eq!("::",
|
||||
format!("{}", Address::UNSPECIFIED));
|
||||
assert_eq!("::1",
|
||||
format!("{}", Address::LOOPBACK));
|
||||
assert_eq!("ff02::1", format!("{}", Address::LINK_LOCAL_ALL_NODES));
|
||||
assert_eq!("fe80::1", format!("{}", LINK_LOCAL_ADDR));
|
||||
assert_eq!(
|
||||
"fe80::7f00:0:1",
|
||||
format!(
|
||||
"{}",
|
||||
Address::new(0xfe80, 0, 0, 0, 0, 0x7f00, 0x0000, 0x0001)
|
||||
)
|
||||
);
|
||||
assert_eq!("::", format!("{}", Address::UNSPECIFIED));
|
||||
assert_eq!("::1", format!("{}", Address::LOOPBACK));
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
assert_eq!("::ffff:192.168.1.1",
|
||||
format!("{}", Address::from(Ipv4Address::new(192, 168, 1, 1))));
|
||||
assert_eq!(
|
||||
"::ffff:192.168.1.1",
|
||||
format!("{}", Address::from(Ipv4Address::new(192, 168, 1, 1)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
assert_eq!(Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
|
||||
Address::LINK_LOCAL_ALL_NODES);
|
||||
assert_eq!(Address::new(0xff02, 0, 0, 0, 0, 0, 0, 2),
|
||||
Address::LINK_LOCAL_ALL_ROUTERS);
|
||||
assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 1),
|
||||
Address::LOOPBACK);
|
||||
assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 0),
|
||||
Address::UNSPECIFIED);
|
||||
assert_eq!(Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
|
||||
LINK_LOCAL_ADDR);
|
||||
assert_eq!(
|
||||
Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
|
||||
Address::LINK_LOCAL_ALL_NODES
|
||||
);
|
||||
assert_eq!(
|
||||
Address::new(0xff02, 0, 0, 0, 0, 0, 0, 2),
|
||||
Address::LINK_LOCAL_ALL_ROUTERS
|
||||
);
|
||||
assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 1), Address::LOOPBACK);
|
||||
assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 0), Address::UNSPECIFIED);
|
||||
assert_eq!(Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), LINK_LOCAL_ADDR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_parts() {
|
||||
assert_eq!(Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 1]),
|
||||
Address::LINK_LOCAL_ALL_NODES);
|
||||
assert_eq!(Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 2]),
|
||||
Address::LINK_LOCAL_ALL_ROUTERS);
|
||||
assert_eq!(Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 1]),
|
||||
Address::LOOPBACK);
|
||||
assert_eq!(Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
Address::UNSPECIFIED);
|
||||
assert_eq!(Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 1]),
|
||||
LINK_LOCAL_ADDR);
|
||||
assert_eq!(
|
||||
Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 1]),
|
||||
Address::LINK_LOCAL_ALL_NODES
|
||||
);
|
||||
assert_eq!(
|
||||
Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 2]),
|
||||
Address::LINK_LOCAL_ALL_ROUTERS
|
||||
);
|
||||
assert_eq!(
|
||||
Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 1]),
|
||||
Address::LOOPBACK
|
||||
);
|
||||
assert_eq!(
|
||||
Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
Address::UNSPECIFIED
|
||||
);
|
||||
assert_eq!(
|
||||
Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 1]),
|
||||
LINK_LOCAL_ADDR
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -781,18 +822,33 @@ mod test {
|
|||
#[test]
|
||||
fn test_mask() {
|
||||
let addr = Address::new(0x0123, 0x4567, 0x89ab, 0, 0, 0, 0, 1);
|
||||
assert_eq!(addr.mask(11), [0x01, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(addr.mask(15), [0x01, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(addr.mask(26), [0x01, 0x23, 0x45, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(addr.mask(128), [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
assert_eq!(addr.mask(127), [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(
|
||||
addr.mask(11),
|
||||
[0x01, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(
|
||||
addr.mask(15),
|
||||
[0x01, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(
|
||||
addr.mask(26),
|
||||
[0x01, 0x23, 0x45, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(
|
||||
addr.mask(128),
|
||||
[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
|
||||
);
|
||||
assert_eq!(
|
||||
addr.mask(127),
|
||||
[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
#[test]
|
||||
fn test_is_ipv4_mapped() {
|
||||
assert_eq!(false, Address::UNSPECIFIED.is_ipv4_mapped());
|
||||
assert_eq!(true, Address::from(Ipv4Address::new(192, 168, 1, 1)).is_ipv4_mapped());
|
||||
assert!(!Address::UNSPECIFIED.is_ipv4_mapped());
|
||||
assert!(Address::from(Ipv4Address::new(192, 168, 1, 1)).is_ipv4_mapped());
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
|
@ -807,10 +863,14 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
#[test]
|
||||
fn test_from_ipv4_address() {
|
||||
assert_eq!(Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1]),
|
||||
Address::from(Ipv4Address::new(192, 168, 1, 1)));
|
||||
assert_eq!(Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 222, 1, 41, 90]),
|
||||
Address::from(Ipv4Address::new(222, 1, 41, 90)));
|
||||
assert_eq!(
|
||||
Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1]),
|
||||
Address::from(Ipv4Address::new(192, 168, 1, 1))
|
||||
);
|
||||
assert_eq!(
|
||||
Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 222, 1, 41, 90]),
|
||||
Address::from(Ipv4Address::new(222, 1, 41, 90))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -818,52 +878,96 @@ mod test {
|
|||
let cidr = Cidr::new(LINK_LOCAL_ADDR, 64);
|
||||
|
||||
let inside_subnet = [
|
||||
[0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02],
|
||||
[0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
|
||||
[0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
[0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff]
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02,
|
||||
],
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
|
||||
0x77, 0x88,
|
||||
],
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
],
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xff,
|
||||
],
|
||||
];
|
||||
|
||||
let outside_subnet = [
|
||||
[0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
|
||||
[0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
|
||||
[0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01,
|
||||
],
|
||||
[
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01,
|
||||
],
|
||||
[
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01,
|
||||
],
|
||||
[
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02,
|
||||
],
|
||||
];
|
||||
|
||||
let subnets = [
|
||||
([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
||||
65),
|
||||
([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
|
||||
128),
|
||||
([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78],
|
||||
96)
|
||||
(
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff,
|
||||
],
|
||||
65,
|
||||
),
|
||||
(
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01,
|
||||
],
|
||||
128,
|
||||
),
|
||||
(
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x34, 0x56, 0x78,
|
||||
],
|
||||
96,
|
||||
),
|
||||
];
|
||||
|
||||
let not_subnets = [
|
||||
([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
||||
63),
|
||||
([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
||||
64),
|
||||
([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
||||
65),
|
||||
([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
|
||||
128)
|
||||
(
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff,
|
||||
],
|
||||
63,
|
||||
),
|
||||
(
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff,
|
||||
],
|
||||
64,
|
||||
),
|
||||
(
|
||||
[
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff,
|
||||
],
|
||||
65,
|
||||
),
|
||||
(
|
||||
[
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01,
|
||||
],
|
||||
128,
|
||||
),
|
||||
];
|
||||
|
||||
for addr in inside_subnet.iter().map(|a| Address::from_bytes(a)) {
|
||||
|
@ -874,13 +978,11 @@ mod test {
|
|||
assert!(!cidr.contains_addr(&addr));
|
||||
}
|
||||
|
||||
for subnet in subnets.iter().map(
|
||||
|&(a, p)| Cidr::new(Address(a), p)) {
|
||||
for subnet in subnets.iter().map(|&(a, p)| Cidr::new(Address(a), p)) {
|
||||
assert!(cidr.contains_subnet(&subnet));
|
||||
}
|
||||
|
||||
for subnet in not_subnets.iter().map(
|
||||
|&(a, p)| Cidr::new(Address(a), p)) {
|
||||
for subnet in not_subnets.iter().map(|&(a, p)| Cidr::new(Address(a), p)) {
|
||||
assert!(!cidr.contains_subnet(&subnet));
|
||||
}
|
||||
|
||||
|
@ -900,33 +1002,26 @@ mod test {
|
|||
let _ = Address::from_parts(&[0u16; 7]);
|
||||
}
|
||||
|
||||
static REPR_PACKET_BYTES: [u8; 52] = [0x60, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x11, 0x40,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x01, 0x00, 0x02,
|
||||
0x00, 0x0c, 0x02, 0x4e,
|
||||
0xff, 0xff, 0xff, 0xff];
|
||||
static REPR_PAYLOAD_BYTES: [u8; 12] = [0x00, 0x01, 0x00, 0x02,
|
||||
0x00, 0x0c, 0x02, 0x4e,
|
||||
0xff, 0xff, 0xff, 0xff];
|
||||
static REPR_PACKET_BYTES: [u8; 52] = [
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
|
||||
0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff,
|
||||
];
|
||||
static REPR_PAYLOAD_BYTES: [u8; 12] = [
|
||||
0x00, 0x01, 0x00, 0x02, 0x00, 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff,
|
||||
];
|
||||
|
||||
fn packet_repr() -> Repr {
|
||||
Repr {
|
||||
src_addr: Address([0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01]),
|
||||
dst_addr: Address::LINK_LOCAL_ALL_NODES,
|
||||
src_addr: Address([
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01,
|
||||
]),
|
||||
dst_addr: Address::LINK_LOCAL_ALL_NODES,
|
||||
next_header: Protocol::Udp,
|
||||
payload_len: 12,
|
||||
hop_limit: 64
|
||||
hop_limit: 64,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -941,10 +1036,13 @@ mod test {
|
|||
assert_eq!(packet.payload_len() as usize, REPR_PAYLOAD_BYTES.len());
|
||||
assert_eq!(packet.next_header(), Protocol::Udp);
|
||||
assert_eq!(packet.hop_limit(), 0x40);
|
||||
assert_eq!(packet.src_addr(), Address([0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01]));
|
||||
assert_eq!(
|
||||
packet.src_addr(),
|
||||
Address([
|
||||
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01
|
||||
])
|
||||
);
|
||||
assert_eq!(packet.dst_addr(), Address::LINK_LOCAL_ALL_NODES);
|
||||
assert_eq!(packet.payload(), &REPR_PAYLOAD_BYTES[..]);
|
||||
}
|
||||
|
@ -969,15 +1067,14 @@ mod test {
|
|||
packet.set_hop_limit(0xfe);
|
||||
packet.set_src_addr(Address::LINK_LOCAL_ALL_ROUTERS);
|
||||
packet.set_dst_addr(Address::LINK_LOCAL_ALL_NODES);
|
||||
packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES[..]);
|
||||
packet
|
||||
.payload_mut()
|
||||
.copy_from_slice(&REPR_PAYLOAD_BYTES[..]);
|
||||
let mut expected_bytes = [
|
||||
0x69, 0x95, 0x43, 0x21, 0x00, 0x0c, 0x11, 0xfe,
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
0x69, 0x95, 0x43, 0x21, 0x00, 0x0c, 0x11, 0xfe, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let start = expected_bytes.len() - REPR_PAYLOAD_BYTES.len();
|
||||
expected_bytes[start..].copy_from_slice(&REPR_PAYLOAD_BYTES[..]);
|
||||
|
@ -991,10 +1088,14 @@ mod test {
|
|||
bytes.extend(&REPR_PACKET_BYTES[..]);
|
||||
bytes.push(0);
|
||||
|
||||
assert_eq!(Packet::new_unchecked(&bytes).payload().len(),
|
||||
REPR_PAYLOAD_BYTES.len());
|
||||
assert_eq!(Packet::new_unchecked(&mut bytes).payload_mut().len(),
|
||||
REPR_PAYLOAD_BYTES.len());
|
||||
assert_eq!(
|
||||
Packet::new_unchecked(&bytes).payload().len(),
|
||||
REPR_PAYLOAD_BYTES.len()
|
||||
);
|
||||
assert_eq!(
|
||||
Packet::new_unchecked(&mut bytes).payload_mut().len(),
|
||||
REPR_PAYLOAD_BYTES.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1003,8 +1104,7 @@ mod test {
|
|||
bytes.extend(&REPR_PACKET_BYTES[..]);
|
||||
Packet::new_unchecked(&mut bytes).set_payload_len(0x80);
|
||||
|
||||
assert_eq!(Packet::new_checked(&bytes).unwrap_err(),
|
||||
Error::Truncated);
|
||||
assert_eq!(Packet::new_checked(&bytes).unwrap_err(), Error::Truncated);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1056,7 +1156,12 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_pretty_print() {
|
||||
assert_eq!(format!("{}", PrettyPrinter::<Packet<&'static [u8]>>::new("\n", &&REPR_PACKET_BYTES[..])),
|
||||
"\nIPv6 src=fe80::1 dst=ff02::1 nxt_hdr=UDP hop_limit=64\n \\ UDP src=1 dst=2 len=4");
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
PrettyPrinter::<Packet<&'static [u8]>>::new("\n", &&REPR_PACKET_BYTES[..])
|
||||
),
|
||||
"\nIPv6 src=fe80::1 dst=ff02::1 nxt_hdr=UDP hop_limit=64\n \\ UDP src=1 dst=2 len=4"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use core::fmt;
|
||||
use crate::{Error, Result};
|
||||
use core::fmt;
|
||||
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
|
||||
|
@ -7,8 +7,9 @@ pub use super::IpProtocol as Protocol;
|
|||
|
||||
/// A read/write wrapper around an IPv6 Fragment Header.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Header<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
// Format of the Fragment Header
|
||||
|
@ -24,13 +25,13 @@ mod field {
|
|||
use crate::wire::field::*;
|
||||
|
||||
// 8-bit identifier of the header immediately following this header.
|
||||
pub const NXT_HDR: usize = 0;
|
||||
pub const NXT_HDR: usize = 0;
|
||||
// 8-bit reserved field.
|
||||
pub const RESERVED: usize = 1;
|
||||
pub const RESERVED: usize = 1;
|
||||
// 16-bit field containing the fragment offset, reserved and more fragments values.
|
||||
pub const FR_OF_M: Field = 2..4;
|
||||
pub const FR_OF_M: Field = 2..4;
|
||||
// 32-bit field identifying the fragmented packet
|
||||
pub const IDENT: Field = 4..8;
|
||||
pub const IDENT: Field = 4..8;
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> Header<T> {
|
||||
|
@ -158,6 +159,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Header<&'a T> {
|
|||
|
||||
/// A high-level representation of an IPv6 Fragment header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr {
|
||||
/// The type of header immediately following the Fragment header.
|
||||
pub next_header: Protocol,
|
||||
|
@ -168,17 +170,19 @@ pub struct Repr {
|
|||
pub more_frags: bool,
|
||||
/// The identification for every packet that is fragmented.
|
||||
pub ident: u32,
|
||||
|
||||
}
|
||||
|
||||
impl Repr {
|
||||
/// Parse an IPv6 Fragment Header and return a high-level representation.
|
||||
pub fn parse<T>(header: &Header<&T>) -> Result<Repr> where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(header: &Header<&T>) -> Result<Repr>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
Ok(Repr {
|
||||
next_header: header.next_header(),
|
||||
frag_offset: header.frag_offset(),
|
||||
more_frags: header.more_frags(),
|
||||
ident: header.ident()
|
||||
ident: header.ident(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -200,8 +204,11 @@ impl Repr {
|
|||
|
||||
impl<'a> fmt::Display for Repr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "IPv6 Fragment next_hdr={} offset={} more={} ident={}",
|
||||
self.next_header, self.frag_offset, self.more_frags, self.ident)
|
||||
write!(
|
||||
f,
|
||||
"IPv6 Fragment next_hdr={} offset={} more={} ident={}",
|
||||
self.next_header, self.frag_offset, self.more_frags, self.ident
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,21 +217,23 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
// A Fragment Header with more fragments remaining
|
||||
static BYTES_HEADER_MORE_FRAG: [u8; 8] = [0x6, 0x0, 0x0, 0x1,
|
||||
0x0, 0x0, 0x30, 0x39];
|
||||
static BYTES_HEADER_MORE_FRAG: [u8; 8] = [0x6, 0x0, 0x0, 0x1, 0x0, 0x0, 0x30, 0x39];
|
||||
|
||||
// A Fragment Header with no more fragments remaining
|
||||
static BYTES_HEADER_LAST_FRAG: [u8; 8] = [0x6, 0x0, 0xa, 0x0,
|
||||
0x0, 0x1, 0x9, 0x32];
|
||||
static BYTES_HEADER_LAST_FRAG: [u8; 8] = [0x6, 0x0, 0xa, 0x0, 0x0, 0x1, 0x9, 0x32];
|
||||
|
||||
#[test]
|
||||
fn test_check_len() {
|
||||
// less than 8 bytes
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Header::new_unchecked(&BYTES_HEADER_MORE_FRAG[..7]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new_unchecked(&BYTES_HEADER_MORE_FRAG[..7]).check_len()
|
||||
);
|
||||
// valid
|
||||
assert_eq!(Ok(()),
|
||||
Header::new_unchecked(&BYTES_HEADER_MORE_FRAG).check_len());
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
Header::new_unchecked(&BYTES_HEADER_MORE_FRAG).check_len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -232,13 +241,13 @@ mod test {
|
|||
let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG);
|
||||
assert_eq!(header.next_header(), Protocol::Tcp);
|
||||
assert_eq!(header.frag_offset(), 0);
|
||||
assert_eq!(header.more_frags(), true);
|
||||
assert!(header.more_frags());
|
||||
assert_eq!(header.ident(), 12345);
|
||||
|
||||
let header = Header::new_unchecked(&BYTES_HEADER_LAST_FRAG);
|
||||
assert_eq!(header.next_header(), Protocol::Tcp);
|
||||
assert_eq!(header.frag_offset(), 320);
|
||||
assert_eq!(header.more_frags(), false);
|
||||
assert!(!header.more_frags());
|
||||
assert_eq!(header.ident(), 67890);
|
||||
}
|
||||
|
||||
|
@ -246,24 +255,48 @@ mod test {
|
|||
fn test_repr_parse_valid() {
|
||||
let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG);
|
||||
let repr = Repr::parse(&header).unwrap();
|
||||
assert_eq!(repr,
|
||||
Repr{ next_header: Protocol::Tcp, frag_offset: 0, more_frags: true, ident: 12345 });
|
||||
assert_eq!(
|
||||
repr,
|
||||
Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
frag_offset: 0,
|
||||
more_frags: true,
|
||||
ident: 12345
|
||||
}
|
||||
);
|
||||
|
||||
let header = Header::new_unchecked(&BYTES_HEADER_LAST_FRAG);
|
||||
let repr = Repr::parse(&header).unwrap();
|
||||
assert_eq!(repr,
|
||||
Repr{ next_header: Protocol::Tcp, frag_offset: 320, more_frags: false, ident: 67890 });
|
||||
assert_eq!(
|
||||
repr,
|
||||
Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
frag_offset: 320,
|
||||
more_frags: false,
|
||||
ident: 67890
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repr_emit() {
|
||||
let repr = Repr{ next_header: Protocol::Tcp, frag_offset: 0, more_frags: true, ident: 12345 };
|
||||
let repr = Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
frag_offset: 0,
|
||||
more_frags: true,
|
||||
ident: 12345,
|
||||
};
|
||||
let mut bytes = [0u8; 8];
|
||||
let mut header = Header::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut header);
|
||||
assert_eq!(header.into_inner(), &BYTES_HEADER_MORE_FRAG[0..8]);
|
||||
|
||||
let repr = Repr{ next_header: Protocol::Tcp, frag_offset: 320, more_frags: false, ident: 67890 };
|
||||
let repr = Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
frag_offset: 320,
|
||||
more_frags: false,
|
||||
ident: 67890,
|
||||
};
|
||||
let mut bytes = [0u8; 8];
|
||||
let mut header = Header::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut header);
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use core::fmt;
|
||||
use crate::{Error, Result};
|
||||
use core::fmt;
|
||||
|
||||
use crate::wire::ipv6option::Ipv6OptionsIterator;
|
||||
pub use super::IpProtocol as Protocol;
|
||||
use crate::wire::ipv6option::Ipv6OptionsIterator;
|
||||
|
||||
/// A read/write wrapper around an IPv6 Hop-by-Hop Options Header.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Header<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
// Format of the Hop-by-Hop Options Header
|
||||
|
@ -30,13 +31,13 @@ mod field {
|
|||
use crate::wire::field::*;
|
||||
|
||||
// Minimum size of the header.
|
||||
pub const MIN_HEADER_SIZE: usize = 8;
|
||||
pub const MIN_HEADER_SIZE: usize = 8;
|
||||
|
||||
// 8-bit identifier of the header immediately following this header.
|
||||
pub const NXT_HDR: usize = 0;
|
||||
pub const NXT_HDR: usize = 0;
|
||||
// 8-bit unsigned integer. Length of the OPTIONS field in 8-octet units,
|
||||
// not including the first 8 octets.
|
||||
pub const LENGTH: usize = 1;
|
||||
pub const LENGTH: usize = 1;
|
||||
// Variable-length field. Option-Type-specific data.
|
||||
//
|
||||
// Length of the header is in 8-octet units, not including the first 8 octets. The first two
|
||||
|
@ -110,7 +111,7 @@ impl<T: AsRef<[u8]>> Header<T> {
|
|||
impl<'a, T: AsRef<[u8]> + ?Sized> Header<&'a T> {
|
||||
/// Return the option data.
|
||||
#[inline]
|
||||
pub fn options(&self) -> &'a[u8] {
|
||||
pub fn options(&self) -> &'a [u8] {
|
||||
let data = self.buffer.as_ref();
|
||||
&data[field::OPTIONS(data[field::LENGTH])]
|
||||
}
|
||||
|
@ -157,22 +158,26 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Header<&'a T> {
|
|||
|
||||
/// A high-level representation of an IPv6 Hop-by-Hop Options header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr<'a> {
|
||||
/// The type of header immediately following the Hop-by-Hop Options header.
|
||||
pub next_header: Protocol,
|
||||
/// Length of the Hop-by-Hop Options header in 8-octet units, not including the first 8 octets.
|
||||
pub length: u8,
|
||||
pub length: u8,
|
||||
/// The options contained in the Hop-by-Hop Options header.
|
||||
pub options: &'a [u8]
|
||||
pub options: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse an IPv6 Hop-by-Hop Options Header and return a high-level representation.
|
||||
pub fn parse<T>(header: &Header<&'a T>) -> Result<Repr<'a>> where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(header: &Header<&'a T>) -> Result<Repr<'a>>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
Ok(Repr {
|
||||
next_header: header.next_header(),
|
||||
length: header.header_len(),
|
||||
options: header.options()
|
||||
options: header.options(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -197,7 +202,11 @@ impl<'a> Repr<'a> {
|
|||
|
||||
impl<'a> fmt::Display for Repr<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "IPv6 Hop-by-Hop Options next_hdr={} length={} ", self.next_header, self.length)
|
||||
write!(
|
||||
f,
|
||||
"IPv6 Hop-by-Hop Options next_hdr={} length={} ",
|
||||
self.next_header, self.length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,36 +215,43 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
// A Hop-by-Hop Option header with a PadN option of option data length 4.
|
||||
static REPR_PACKET_PAD4: [u8; 8] = [0x6, 0x0, 0x1, 0x4,
|
||||
0x0, 0x0, 0x0, 0x0];
|
||||
static REPR_PACKET_PAD4: [u8; 8] = [0x6, 0x0, 0x1, 0x4, 0x0, 0x0, 0x0, 0x0];
|
||||
|
||||
// A Hop-by-Hop Option header with a PadN option of option data length 12.
|
||||
static REPR_PACKET_PAD12: [u8; 16] = [0x06, 0x1, 0x1, 0x12,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0];
|
||||
static REPR_PACKET_PAD12: [u8; 16] = [
|
||||
0x06, 0x1, 0x1, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_check_len() {
|
||||
// zero byte buffer
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len()
|
||||
);
|
||||
// no length field
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD4[..1]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD4[..1]).check_len()
|
||||
);
|
||||
// less than 8 bytes
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD4[..7]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD4[..7]).check_len()
|
||||
);
|
||||
// valid
|
||||
assert_eq!(Ok(()),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD4).check_len());
|
||||
assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len());
|
||||
// valid
|
||||
assert_eq!(Ok(()),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD12).check_len());
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
Header::new_unchecked(&REPR_PACKET_PAD12).check_len()
|
||||
);
|
||||
// length field value greater than number of bytes
|
||||
let header: [u8; 8] = [0x06, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Header::new_unchecked(&header).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new_unchecked(&header).check_len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -257,19 +273,27 @@ mod test {
|
|||
bytes.extend(&REPR_PACKET_PAD4[..]);
|
||||
bytes.push(0);
|
||||
|
||||
assert_eq!(Header::new_unchecked(&bytes).options().len(),
|
||||
REPR_PACKET_PAD4[2..].len());
|
||||
assert_eq!(Header::new_unchecked(&mut bytes).options_mut().len(),
|
||||
REPR_PACKET_PAD4[2..].len());
|
||||
assert_eq!(
|
||||
Header::new_unchecked(&bytes).options().len(),
|
||||
REPR_PACKET_PAD4[2..].len()
|
||||
);
|
||||
assert_eq!(
|
||||
Header::new_unchecked(&mut bytes).options_mut().len(),
|
||||
REPR_PACKET_PAD4[2..].len()
|
||||
);
|
||||
|
||||
let mut bytes = vec![];
|
||||
bytes.extend(&REPR_PACKET_PAD12[..]);
|
||||
bytes.push(0);
|
||||
|
||||
assert_eq!(Header::new_unchecked(&bytes).options().len(),
|
||||
REPR_PACKET_PAD12[2..].len());
|
||||
assert_eq!(Header::new_unchecked(&mut bytes).options_mut().len(),
|
||||
REPR_PACKET_PAD12[2..].len());
|
||||
assert_eq!(
|
||||
Header::new_unchecked(&bytes).options().len(),
|
||||
REPR_PACKET_PAD12[2..].len()
|
||||
);
|
||||
assert_eq!(
|
||||
Header::new_unchecked(&mut bytes).options_mut().len(),
|
||||
REPR_PACKET_PAD12[2..].len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -293,26 +317,44 @@ mod test {
|
|||
fn test_repr_parse_valid() {
|
||||
let header = Header::new_unchecked(&REPR_PACKET_PAD4);
|
||||
let repr = Repr::parse(&header).unwrap();
|
||||
assert_eq!(repr, Repr {
|
||||
next_header: Protocol::Tcp, length: 0, options: &REPR_PACKET_PAD4[2..]
|
||||
});
|
||||
assert_eq!(
|
||||
repr,
|
||||
Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
length: 0,
|
||||
options: &REPR_PACKET_PAD4[2..]
|
||||
}
|
||||
);
|
||||
|
||||
let header = Header::new_unchecked(&REPR_PACKET_PAD12);
|
||||
let repr = Repr::parse(&header).unwrap();
|
||||
assert_eq!(repr, Repr {
|
||||
next_header: Protocol::Tcp, length: 1, options: &REPR_PACKET_PAD12[2..]
|
||||
});
|
||||
assert_eq!(
|
||||
repr,
|
||||
Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
length: 1,
|
||||
options: &REPR_PACKET_PAD12[2..]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repr_emit() {
|
||||
let repr = Repr{ next_header: Protocol::Tcp, length: 0, options: &REPR_PACKET_PAD4[2..] };
|
||||
let repr = Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
length: 0,
|
||||
options: &REPR_PACKET_PAD4[2..],
|
||||
};
|
||||
let mut bytes = [0u8; 8];
|
||||
let mut header = Header::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut header);
|
||||
assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..]);
|
||||
|
||||
let repr = Repr{ next_header: Protocol::Tcp, length: 1, options: &REPR_PACKET_PAD12[2..] };
|
||||
let repr = Repr {
|
||||
next_header: Protocol::Tcp,
|
||||
length: 1,
|
||||
options: &REPR_PACKET_PAD12[2..],
|
||||
};
|
||||
let mut bytes = [0u8; 16];
|
||||
let mut header = Header::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut header);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use core::fmt;
|
||||
use crate::{Error, Result};
|
||||
use core::fmt;
|
||||
|
||||
enum_with_unknown! {
|
||||
/// IPv6 Extension Header Option Type
|
||||
|
@ -14,9 +14,9 @@ enum_with_unknown! {
|
|||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Type::Pad1 => write!(f, "Pad1"),
|
||||
Type::PadN => write!(f, "PadN"),
|
||||
Type::Unknown(id) => write!(f, "{}", id)
|
||||
Type::Pad1 => write!(f, "Pad1"),
|
||||
Type::PadN => write!(f, "PadN"),
|
||||
Type::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,11 +40,11 @@ enum_with_unknown! {
|
|||
impl fmt::Display for FailureType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
FailureType::Skip => write!(f, "skip"),
|
||||
FailureType::Discard => write!(f, "discard"),
|
||||
FailureType::DiscardSendAll => write!(f, "discard and send error"),
|
||||
FailureType::Skip => write!(f, "skip"),
|
||||
FailureType::Discard => write!(f, "discard"),
|
||||
FailureType::DiscardSendAll => write!(f, "discard and send error"),
|
||||
FailureType::DiscardSendUnicast => write!(f, "discard and send error if unicast"),
|
||||
FailureType::Unknown(id) => write!(f, "Unknown({})", id),
|
||||
FailureType::Unknown(id) => write!(f, "Unknown({})", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,9 @@ impl From<Type> for FailureType {
|
|||
|
||||
/// A read/write wrapper around an IPv6 Extension Header Option.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Ipv6Option<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
// Format of Option
|
||||
|
@ -76,9 +77,9 @@ mod field {
|
|||
use crate::wire::field::*;
|
||||
|
||||
// 8-bit identifier of the type of option.
|
||||
pub const TYPE: usize = 0;
|
||||
pub const TYPE: usize = 0;
|
||||
// 8-bit unsigned integer. Length of the DATA field of this option, in octets.
|
||||
pub const LENGTH: usize = 1;
|
||||
pub const LENGTH: usize = 1;
|
||||
// Variable-length field. Option-Type-specific data.
|
||||
pub fn DATA(length: u8) -> Field {
|
||||
2..length as usize + 2
|
||||
|
@ -214,32 +215,32 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Ipv6Option<&'a T> {
|
|||
|
||||
/// A high-level representation of an IPv6 Extension Header Option.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Repr<'a> {
|
||||
Pad1,
|
||||
PadN(u8),
|
||||
Unknown {
|
||||
type_: Type,
|
||||
type_: Type,
|
||||
length: u8,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse an IPv6 Extension Header Option and return a high-level representation.
|
||||
pub fn parse<T>(opt: &Ipv6Option<&'a T>) -> Result<Repr<'a>> where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(opt: &Ipv6Option<&'a T>) -> Result<Repr<'a>>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
match opt.option_type() {
|
||||
Type::Pad1 =>
|
||||
Ok(Repr::Pad1),
|
||||
Type::PadN =>
|
||||
Ok(Repr::PadN(opt.data_len())),
|
||||
unknown_type @ Type::Unknown(_) => {
|
||||
Ok(Repr::Unknown {
|
||||
type_: unknown_type,
|
||||
length: opt.data_len(),
|
||||
data: opt.data(),
|
||||
})
|
||||
}
|
||||
Type::Pad1 => Ok(Repr::Pad1),
|
||||
Type::PadN => Ok(Repr::PadN(opt.data_len())),
|
||||
unknown_type @ Type::Unknown(_) => Ok(Repr::Unknown {
|
||||
type_: unknown_type,
|
||||
length: opt.data_len(),
|
||||
data: opt.data(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,18 +248,15 @@ impl<'a> Repr<'a> {
|
|||
pub fn buffer_len(&self) -> usize {
|
||||
match *self {
|
||||
Repr::Pad1 => 1,
|
||||
Repr::PadN(length) =>
|
||||
field::DATA(length).end,
|
||||
Repr::Unknown{ length, .. } =>
|
||||
field::DATA(length).end,
|
||||
Repr::PadN(length) => field::DATA(length).end,
|
||||
Repr::Unknown { length, .. } => field::DATA(length).end,
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a high-level representation into an IPv6 Extension Header Option.
|
||||
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, opt: &mut Ipv6Option<&'a mut T>) {
|
||||
match *self {
|
||||
Repr::Pad1 =>
|
||||
opt.set_option_type(Type::Pad1),
|
||||
Repr::Pad1 => opt.set_option_type(Type::Pad1),
|
||||
Repr::PadN(len) => {
|
||||
opt.set_option_type(Type::PadN);
|
||||
opt.set_data_len(len);
|
||||
|
@ -267,7 +265,11 @@ impl<'a> Repr<'a> {
|
|||
*x = 0
|
||||
}
|
||||
}
|
||||
Repr::Unknown{ type_, length, data } => {
|
||||
Repr::Unknown {
|
||||
type_,
|
||||
length,
|
||||
data,
|
||||
} => {
|
||||
opt.set_option_type(type_);
|
||||
opt.set_data_len(length);
|
||||
opt.data_mut().copy_from_slice(&data[..length as usize]);
|
||||
|
@ -278,11 +280,12 @@ impl<'a> Repr<'a> {
|
|||
|
||||
/// A iterator for IPv6 options.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Ipv6OptionsIterator<'a> {
|
||||
pos: usize,
|
||||
length: usize,
|
||||
data: &'a [u8],
|
||||
hit_error: bool
|
||||
hit_error: bool,
|
||||
}
|
||||
|
||||
impl<'a> Ipv6OptionsIterator<'a> {
|
||||
|
@ -298,7 +301,8 @@ impl<'a> Ipv6OptionsIterator<'a> {
|
|||
Ipv6OptionsIterator {
|
||||
pos: 0,
|
||||
hit_error: false,
|
||||
length, data
|
||||
length,
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -311,18 +315,16 @@ impl<'a> Iterator for Ipv6OptionsIterator<'a> {
|
|||
// If we still have data to parse and we have not previously
|
||||
// hit an error, attempt to parse the next option.
|
||||
match Ipv6Option::new_checked(&self.data[self.pos..]) {
|
||||
Ok(hdr) => {
|
||||
match Repr::parse(&hdr) {
|
||||
Ok(repr) => {
|
||||
self.pos += repr.buffer_len();
|
||||
Some(Ok(repr))
|
||||
}
|
||||
Err(e) => {
|
||||
self.hit_error = true;
|
||||
Some(Err(e))
|
||||
}
|
||||
Ok(hdr) => match Repr::parse(&hdr) {
|
||||
Ok(repr) => {
|
||||
self.pos += repr.buffer_len();
|
||||
Some(Ok(repr))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.hit_error = true;
|
||||
Some(Err(e))
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
self.hit_error = true;
|
||||
Some(Err(e))
|
||||
|
@ -340,12 +342,9 @@ impl<'a> fmt::Display for Repr<'a> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "IPv6 Option ")?;
|
||||
match *self {
|
||||
Repr::Pad1 =>
|
||||
write!(f, "{} ", Type::Pad1),
|
||||
Repr::PadN(len) =>
|
||||
write!(f, "{} length={} ", Type::PadN, len),
|
||||
Repr::Unknown{ type_, length, .. } =>
|
||||
write!(f, "{} length={} ", type_, length),
|
||||
Repr::Pad1 => write!(f, "{} ", Type::Pad1),
|
||||
Repr::PadN(len) => write!(f, "{} length={} ", Type::PadN, len),
|
||||
Repr::Unknown { type_, length, .. } => write!(f, "{} length={} ", type_, length),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -354,35 +353,49 @@ impl<'a> fmt::Display for Repr<'a> {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
static IPV6OPTION_BYTES_PAD1: [u8; 1] = [0x0];
|
||||
static IPV6OPTION_BYTES_PADN: [u8; 3] = [0x1, 0x1, 0x0];
|
||||
static IPV6OPTION_BYTES_PAD1: [u8; 1] = [0x0];
|
||||
static IPV6OPTION_BYTES_PADN: [u8; 3] = [0x1, 0x1, 0x0];
|
||||
static IPV6OPTION_BYTES_UNKNOWN: [u8; 5] = [0xff, 0x3, 0x0, 0x0, 0x0];
|
||||
|
||||
#[test]
|
||||
fn test_check_len() {
|
||||
let bytes = [0u8];
|
||||
// zero byte buffer
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&bytes[..0]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&bytes[..0]).check_len()
|
||||
);
|
||||
// pad1
|
||||
assert_eq!(Ok(()),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1).check_len());
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1).check_len()
|
||||
);
|
||||
|
||||
// padn with truncated data
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN[..2]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN[..2]).check_len()
|
||||
);
|
||||
// padn
|
||||
assert_eq!(Ok(()),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN).check_len());
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN).check_len()
|
||||
);
|
||||
|
||||
// unknown option type with truncated data
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..4]).check_len());
|
||||
assert_eq!(Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..1]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..4]).check_len()
|
||||
);
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..1]).check_len()
|
||||
);
|
||||
// unknown type
|
||||
assert_eq!(Ok(()),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN).check_len());
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN).check_len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -399,7 +412,7 @@ mod test {
|
|||
assert_eq!(opt.option_type(), Type::Pad1);
|
||||
|
||||
// two octets of padding
|
||||
let bytes: [u8; 2] = [0x1, 0x0];
|
||||
let bytes: [u8; 2] = [0x1, 0x0];
|
||||
let opt = Ipv6Option::new_unchecked(&bytes);
|
||||
assert_eq!(opt.option_type(), Type::PadN);
|
||||
assert_eq!(opt.data_len(), 0);
|
||||
|
@ -411,14 +424,14 @@ mod test {
|
|||
assert_eq!(opt.data(), &[0]);
|
||||
|
||||
// extra bytes in buffer
|
||||
let bytes: [u8; 10] = [0x1, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff];
|
||||
let bytes: [u8; 10] = [0x1, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff];
|
||||
let opt = Ipv6Option::new_unchecked(&bytes);
|
||||
assert_eq!(opt.option_type(), Type::PadN);
|
||||
assert_eq!(opt.data_len(), 7);
|
||||
assert_eq!(opt.data(), &[0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
// unrecognized option
|
||||
let bytes: [u8; 1] = [0xff];
|
||||
let bytes: [u8; 1] = [0xff];
|
||||
let opt = Ipv6Option::new_unchecked(&bytes);
|
||||
assert_eq!(opt.option_type(), Type::Unknown(255));
|
||||
|
||||
|
@ -444,7 +457,14 @@ mod test {
|
|||
let data = [0u8; 3];
|
||||
let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN);
|
||||
let unknown = Repr::parse(&opt).unwrap();
|
||||
assert_eq!(unknown, Repr::Unknown { type_: Type::Unknown(255), length: 3, data: &data });
|
||||
assert_eq!(
|
||||
unknown,
|
||||
Repr::Unknown {
|
||||
type_: Type::Unknown(255),
|
||||
length: 3,
|
||||
data: &data
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -462,7 +482,11 @@ mod test {
|
|||
assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_PADN);
|
||||
|
||||
let data = [0u8; 3];
|
||||
let repr = Repr::Unknown { type_: Type::Unknown(255), length: 3, data: &data };
|
||||
let repr = Repr::Unknown {
|
||||
type_: Type::Unknown(255),
|
||||
length: 3,
|
||||
data: &data,
|
||||
};
|
||||
let mut bytes = [254u8; 5]; // don't assume bytes are initialized to zero
|
||||
let mut opt = Ipv6Option::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut opt);
|
||||
|
@ -485,10 +509,10 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_options_iter() {
|
||||
let options = [0x00, 0x01, 0x01, 0x00,
|
||||
0x01, 0x02, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x11,
|
||||
0x00, 0x01, 0x08, 0x00];
|
||||
let options = [
|
||||
0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x11, 0x00, 0x01,
|
||||
0x08, 0x00,
|
||||
];
|
||||
|
||||
let mut iterator = Ipv6OptionsIterator::new(&options, 0);
|
||||
assert_eq!(iterator.next(), None);
|
||||
|
@ -501,8 +525,14 @@ mod test {
|
|||
(2, Ok(Repr::PadN(2))) => continue,
|
||||
(3, Ok(Repr::PadN(0))) => continue,
|
||||
(4, Ok(Repr::Pad1)) => continue,
|
||||
(5, Ok(Repr::Unknown { type_: Type::Unknown(0x11), length: 0, .. })) =>
|
||||
continue,
|
||||
(
|
||||
5,
|
||||
Ok(Repr::Unknown {
|
||||
type_: Type::Unknown(0x11),
|
||||
length: 0,
|
||||
..
|
||||
}),
|
||||
) => continue,
|
||||
(6, Err(Error::Truncated)) => continue,
|
||||
(i, res) => panic!("Unexpected option `{:?}` at index {}", res, i),
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use core::fmt;
|
||||
use crate::{Error, Result};
|
||||
use core::fmt;
|
||||
|
||||
use crate::wire::IpProtocol as Protocol;
|
||||
use crate::wire::Ipv6Address as Address;
|
||||
|
@ -37,22 +37,23 @@ enum_with_unknown! {
|
|||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Type::Type0 => write!(f, "Type0"),
|
||||
Type::Nimrod => write!(f, "Nimrod"),
|
||||
Type::Type2 => write!(f, "Type2"),
|
||||
Type::Rpl => write!(f, "Rpl"),
|
||||
Type::Experiment1 => write!(f, "Experiment1"),
|
||||
Type::Experiment2 => write!(f, "Experiment2"),
|
||||
Type::Reserved => write!(f, "Reserved"),
|
||||
Type::Unknown(id) => write!(f, "{}", id)
|
||||
Type::Type0 => write!(f, "Type0"),
|
||||
Type::Nimrod => write!(f, "Nimrod"),
|
||||
Type::Type2 => write!(f, "Type2"),
|
||||
Type::Rpl => write!(f, "Rpl"),
|
||||
Type::Experiment1 => write!(f, "Experiment1"),
|
||||
Type::Experiment2 => write!(f, "Experiment2"),
|
||||
Type::Reserved => write!(f, "Reserved"),
|
||||
Type::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A read/write wrapper around an IPv6 Routing Header buffer.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Header<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
// Format of the Routing Header
|
||||
|
@ -75,15 +76,15 @@ mod field {
|
|||
use crate::wire::field::*;
|
||||
|
||||
// Minimum size of the header.
|
||||
pub const MIN_HEADER_SIZE: usize = 4;
|
||||
pub const MIN_HEADER_SIZE: usize = 4;
|
||||
|
||||
// 8-bit identifier of the header immediately following this header.
|
||||
pub const NXT_HDR: usize = 0;
|
||||
pub const NXT_HDR: usize = 0;
|
||||
// 8-bit unsigned integer. Length of the DATA field in 8-octet units,
|
||||
// not including the first 8 octets.
|
||||
pub const LENGTH: usize = 1;
|
||||
pub const LENGTH: usize = 1;
|
||||
// 8-bit identifier of a particular Routing header variant.
|
||||
pub const TYPE: usize = 2;
|
||||
pub const TYPE: usize = 2;
|
||||
// 8-bit unsigned integer. The number of route segments remaining.
|
||||
pub const SEG_LEFT: usize = 3;
|
||||
// Variable-length field. Routing-Type-specific data.
|
||||
|
@ -129,9 +130,9 @@ mod field {
|
|||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
// 8-bit field containing the CmprI and CmprE values.
|
||||
pub const CMPR: usize = 4;
|
||||
pub const CMPR: usize = 4;
|
||||
// 8-bit field containing the Pad value.
|
||||
pub const PAD: usize = 5;
|
||||
pub const PAD: usize = 5;
|
||||
// Variable length field containing addresses
|
||||
pub fn ADDRESSES(length_field: u8) -> Field {
|
||||
let data = DATA(length_field);
|
||||
|
@ -233,7 +234,6 @@ impl<T: AsRef<[u8]>> Header<T> {
|
|||
data[field::CMPR] >> 4
|
||||
}
|
||||
|
||||
|
||||
/// Return the number of prefix octects elided from the last address (`addresses[n]`).
|
||||
///
|
||||
/// # Panics
|
||||
|
@ -315,7 +315,7 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> {
|
|||
data[7] = 0;
|
||||
}
|
||||
|
||||
_ => panic!("Unrecognized routing type when clearing reserved fields.")
|
||||
_ => panic!("Unrecognized routing type when clearing reserved fields."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -389,63 +389,62 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Header<&'a T> {
|
|||
|
||||
/// A high-level representation of an IPv6 Routing Header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum Repr<'a> {
|
||||
Type2 {
|
||||
/// The type of header immediately following the Routing header.
|
||||
next_header: Protocol,
|
||||
next_header: Protocol,
|
||||
/// Length of the Routing header in 8-octet units, not including the first 8 octets.
|
||||
length: u8,
|
||||
length: u8,
|
||||
/// Number of route segments remaining.
|
||||
segments_left: u8,
|
||||
segments_left: u8,
|
||||
/// The home address of the destination mobile node.
|
||||
home_address: Address,
|
||||
},
|
||||
Rpl {
|
||||
/// The type of header immediately following the Routing header.
|
||||
next_header: Protocol,
|
||||
next_header: Protocol,
|
||||
/// Length of the Routing header in 8-octet units, not including the first 8 octets.
|
||||
length: u8,
|
||||
length: u8,
|
||||
/// Number of route segments remaining.
|
||||
segments_left: u8,
|
||||
segments_left: u8,
|
||||
/// Number of prefix octets from each segment, except the last segment, that are elided.
|
||||
cmpr_i: u8,
|
||||
cmpr_i: u8,
|
||||
/// Number of prefix octets from the last segment that are elided.
|
||||
cmpr_e: u8,
|
||||
cmpr_e: u8,
|
||||
/// Number of octets that are used for padding after `address[n]` at the end of the
|
||||
/// RPL Source Route Header.
|
||||
pad: u8,
|
||||
pad: u8,
|
||||
/// Vector of addresses, numbered 1 to `n`.
|
||||
addresses: &'a[u8],
|
||||
addresses: &'a [u8],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse an IPv6 Routing Header and return a high-level representation.
|
||||
pub fn parse<T>(header: &'a Header<&'a T>) -> Result<Repr<'a>> where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(header: &'a Header<&'a T>) -> Result<Repr<'a>>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
match header.routing_type() {
|
||||
Type::Type2 => {
|
||||
Ok(Repr::Type2 {
|
||||
next_header: header.next_header(),
|
||||
length: header.header_len(),
|
||||
segments_left: header.segments_left(),
|
||||
home_address: header.home_address(),
|
||||
})
|
||||
}
|
||||
Type::Rpl => {
|
||||
Ok(Repr::Rpl {
|
||||
next_header: header.next_header(),
|
||||
length: header.header_len(),
|
||||
segments_left: header.segments_left(),
|
||||
cmpr_i: header.cmpr_i(),
|
||||
cmpr_e: header.cmpr_e(),
|
||||
pad: header.pad(),
|
||||
addresses: header.addresses(),
|
||||
})
|
||||
}
|
||||
Type::Type2 => Ok(Repr::Type2 {
|
||||
next_header: header.next_header(),
|
||||
length: header.header_len(),
|
||||
segments_left: header.segments_left(),
|
||||
home_address: header.home_address(),
|
||||
}),
|
||||
Type::Rpl => Ok(Repr::Rpl {
|
||||
next_header: header.next_header(),
|
||||
length: header.header_len(),
|
||||
segments_left: header.segments_left(),
|
||||
cmpr_i: header.cmpr_i(),
|
||||
cmpr_e: header.cmpr_e(),
|
||||
pad: header.pad(),
|
||||
addresses: header.addresses(),
|
||||
}),
|
||||
|
||||
_ => Err(Error::Unrecognized)
|
||||
_ => Err(Error::Unrecognized),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,16 +452,19 @@ impl<'a> Repr<'a> {
|
|||
/// representation.
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
match self {
|
||||
&Repr::Rpl { length, .. } | &Repr::Type2 { length, .. } => {
|
||||
field::DATA(length).end
|
||||
}
|
||||
&Repr::Rpl { length, .. } | &Repr::Type2 { length, .. } => field::DATA(length).end,
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a high-level representation into an IPv6 Routing Header.
|
||||
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) {
|
||||
match *self {
|
||||
Repr::Type2 { next_header, length, segments_left, home_address } => {
|
||||
Repr::Type2 {
|
||||
next_header,
|
||||
length,
|
||||
segments_left,
|
||||
home_address,
|
||||
} => {
|
||||
header.set_next_header(next_header);
|
||||
header.set_header_len(length);
|
||||
header.set_routing_type(Type::Type2);
|
||||
|
@ -470,7 +472,15 @@ impl<'a> Repr<'a> {
|
|||
header.clear_reserved();
|
||||
header.set_home_address(home_address);
|
||||
}
|
||||
Repr::Rpl { next_header, length, segments_left, cmpr_i, cmpr_e, pad, addresses } => {
|
||||
Repr::Rpl {
|
||||
next_header,
|
||||
length,
|
||||
segments_left,
|
||||
cmpr_i,
|
||||
cmpr_e,
|
||||
pad,
|
||||
addresses,
|
||||
} => {
|
||||
header.set_next_header(next_header);
|
||||
header.set_header_len(length);
|
||||
header.set_routing_type(Type::Rpl);
|
||||
|
@ -488,11 +498,31 @@ impl<'a> Repr<'a> {
|
|||
impl<'a> fmt::Display for Repr<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Repr::Type2 { next_header, length, segments_left, home_address } => {
|
||||
write!(f, "IPv6 Routing next_hdr={} length={} type={} seg_left={} home_address={}",
|
||||
next_header, length, Type::Type2, segments_left, home_address)
|
||||
Repr::Type2 {
|
||||
next_header,
|
||||
length,
|
||||
segments_left,
|
||||
home_address,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"IPv6 Routing next_hdr={} length={} type={} seg_left={} home_address={}",
|
||||
next_header,
|
||||
length,
|
||||
Type::Type2,
|
||||
segments_left,
|
||||
home_address
|
||||
)
|
||||
}
|
||||
Repr::Rpl { next_header, length, segments_left, cmpr_i, cmpr_e, pad, .. } => {
|
||||
Repr::Rpl {
|
||||
next_header,
|
||||
length,
|
||||
segments_left,
|
||||
cmpr_i,
|
||||
cmpr_e,
|
||||
pad,
|
||||
..
|
||||
} => {
|
||||
write!(f, "IPv6 Routing next_hdr={} length={} type={} seg_left={} cmpr_i={} cmpr_e={} pad={}",
|
||||
next_header, length, Type::Rpl, segments_left, cmpr_i, cmpr_e, pad)
|
||||
}
|
||||
|
@ -505,12 +535,10 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
// A Type 2 Routing Header
|
||||
static BYTES_TYPE2: [u8; 24] = [0x6, 0x2, 0x2, 0x1,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x1];
|
||||
static BYTES_TYPE2: [u8; 24] = [
|
||||
0x6, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||
];
|
||||
|
||||
// A representation of a Type 2 Routing header
|
||||
static REPR_TYPE2: Repr = Repr::Type2 {
|
||||
|
@ -521,16 +549,11 @@ mod test {
|
|||
};
|
||||
|
||||
// A Source Routing Header with full IPv6 addresses in bytes
|
||||
static BYTES_SRH_FULL: [u8; 40] = [0x6, 0x4, 0x3, 0x2,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0xfd, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x2,
|
||||
0xfd, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x3, 0x1];
|
||||
static BYTES_SRH_FULL: [u8; 40] = [
|
||||
0x6, 0x4, 0x3, 0x2, 0x0, 0x0, 0x0, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x3, 0x1,
|
||||
];
|
||||
|
||||
// A representation of a Source Routing Header with full IPv6 addresses
|
||||
static REPR_SRH_FULL: Repr = Repr::Rpl {
|
||||
|
@ -540,21 +563,16 @@ mod test {
|
|||
cmpr_i: 0,
|
||||
cmpr_e: 0,
|
||||
pad: 0,
|
||||
addresses: &[0xfd, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x2,
|
||||
0xfd, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x3, 0x1]
|
||||
addresses: &[
|
||||
0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1,
|
||||
],
|
||||
};
|
||||
|
||||
// A Source Routing Header with elided IPv6 addresses in bytes
|
||||
static BYTES_SRH_ELIDED: [u8; 16] = [0x6, 0x1, 0x3, 0x2,
|
||||
0xfe, 0x50, 0x0, 0x0,
|
||||
0x2, 0x3, 0x1, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0];
|
||||
static BYTES_SRH_ELIDED: [u8; 16] = [
|
||||
0x6, 0x1, 0x3, 0x2, 0xfe, 0x50, 0x0, 0x0, 0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
];
|
||||
|
||||
// A representation of a Source Routing Header with elided IPv6 addresses
|
||||
static REPR_SRH_ELIDED: Repr = Repr::Rpl {
|
||||
|
@ -564,20 +582,37 @@ mod test {
|
|||
cmpr_i: 15,
|
||||
cmpr_e: 14,
|
||||
pad: 5,
|
||||
addresses: &[0x2, 0x3, 0x1, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0]
|
||||
addresses: &[0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0],
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_check_len() {
|
||||
// less than min header size
|
||||
assert_eq!(Err(Error::Truncated), Header::new(&BYTES_TYPE2[..3]).check_len());
|
||||
assert_eq!(Err(Error::Truncated), Header::new(&BYTES_SRH_FULL[..3]).check_len());
|
||||
assert_eq!(Err(Error::Truncated), Header::new(&BYTES_SRH_ELIDED[..3]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new(&BYTES_TYPE2[..3]).check_len()
|
||||
);
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new(&BYTES_SRH_FULL[..3]).check_len()
|
||||
);
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new(&BYTES_SRH_ELIDED[..3]).check_len()
|
||||
);
|
||||
// less than specfied length field
|
||||
assert_eq!(Err(Error::Truncated), Header::new(&BYTES_TYPE2[..23]).check_len());
|
||||
assert_eq!(Err(Error::Truncated), Header::new(&BYTES_SRH_FULL[..39]).check_len());
|
||||
assert_eq!(Err(Error::Truncated), Header::new(&BYTES_SRH_ELIDED[..11]).check_len());
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new(&BYTES_TYPE2[..23]).check_len()
|
||||
);
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new(&BYTES_SRH_FULL[..39]).check_len()
|
||||
);
|
||||
assert_eq!(
|
||||
Err(Error::Truncated),
|
||||
Header::new(&BYTES_SRH_ELIDED[..11]).check_len()
|
||||
);
|
||||
// valid
|
||||
assert_eq!(Ok(()), Header::new(&BYTES_TYPE2[..]).check_len());
|
||||
assert_eq!(Ok(()), Header::new(&BYTES_SRH_FULL[..]).check_len());
|
||||
|
|
246
src/wire/mld.rs
246
src/wire/mld.rs
|
@ -6,9 +6,9 @@
|
|||
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::wire::icmpv6::{field, Message, Packet};
|
||||
use crate::wire::Ipv6Address;
|
||||
use crate::{Error, Result};
|
||||
|
||||
enum_with_unknown! {
|
||||
/// MLDv2 Multicast Listener Report Record Type. See [RFC 3810 § 5.2.12] for
|
||||
|
@ -166,8 +166,9 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
|
||||
/// A read/write wrapper around an MLDv2 Listener Report Message Address Record.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct AddressRecord<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> AddressRecord<T> {
|
||||
|
@ -295,6 +296,7 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> AddressRecord<T> {
|
|||
|
||||
/// A high-level representation of an MLDv2 packet header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Repr<'a> {
|
||||
Query {
|
||||
max_resp_code: u16,
|
||||
|
@ -303,58 +305,61 @@ pub enum Repr<'a> {
|
|||
qrv: u8,
|
||||
qqic: u8,
|
||||
num_srcs: u16,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
Report {
|
||||
nr_mcast_addr_rcrds: u16,
|
||||
data: &'a [u8]
|
||||
}
|
||||
data: &'a [u8],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse an MLDv2 packet and return a high-level representation.
|
||||
pub fn parse<T>(packet: &Packet<&'a T>) -> Result<Repr<'a>>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
match packet.msg_type() {
|
||||
Message::MldQuery => {
|
||||
Ok(Repr::Query {
|
||||
max_resp_code: packet.max_resp_code(),
|
||||
mcast_addr: packet.mcast_addr(),
|
||||
s_flag: packet.s_flag(),
|
||||
qrv: packet.qrv(),
|
||||
qqic: packet.qqic(),
|
||||
num_srcs: packet.num_srcs(),
|
||||
data: packet.payload()
|
||||
})
|
||||
},
|
||||
Message::MldReport => {
|
||||
Ok(Repr::Report {
|
||||
nr_mcast_addr_rcrds: packet.nr_mcast_addr_rcrds(),
|
||||
data: packet.payload()
|
||||
})
|
||||
},
|
||||
_ => Err(Error::Unrecognized)
|
||||
Message::MldQuery => Ok(Repr::Query {
|
||||
max_resp_code: packet.max_resp_code(),
|
||||
mcast_addr: packet.mcast_addr(),
|
||||
s_flag: packet.s_flag(),
|
||||
qrv: packet.qrv(),
|
||||
qqic: packet.qqic(),
|
||||
num_srcs: packet.num_srcs(),
|
||||
data: packet.payload(),
|
||||
}),
|
||||
Message::MldReport => Ok(Repr::Report {
|
||||
nr_mcast_addr_rcrds: packet.nr_mcast_addr_rcrds(),
|
||||
data: packet.payload(),
|
||||
}),
|
||||
_ => Err(Error::Unrecognized),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the length of a packet that will be emitted from this high-level representation.
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
match self {
|
||||
Repr::Query { .. } => {
|
||||
field::QUERY_NUM_SRCS.end
|
||||
}
|
||||
Repr::Report { .. } => {
|
||||
field::NR_MCAST_RCRDS.end
|
||||
}
|
||||
Repr::Query { .. } => field::QUERY_NUM_SRCS.end,
|
||||
Repr::Report { .. } => field::NR_MCAST_RCRDS.end,
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a high-level representation into an MLDv2 packet.
|
||||
pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||
where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
match self {
|
||||
Repr::Query { max_resp_code, mcast_addr, s_flag,
|
||||
qrv, qqic, num_srcs, data } => {
|
||||
Repr::Query {
|
||||
max_resp_code,
|
||||
mcast_addr,
|
||||
s_flag,
|
||||
qrv,
|
||||
qqic,
|
||||
num_srcs,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::MldQuery);
|
||||
packet.set_msg_code(0);
|
||||
packet.clear_reserved();
|
||||
|
@ -369,8 +374,11 @@ impl<'a> Repr<'a> {
|
|||
packet.set_qqic(*qqic);
|
||||
packet.set_num_srcs(*num_srcs);
|
||||
packet.payload_mut().copy_from_slice(&data[..]);
|
||||
},
|
||||
Repr::Report { nr_mcast_addr_rcrds, data } => {
|
||||
}
|
||||
Repr::Report {
|
||||
nr_mcast_addr_rcrds,
|
||||
data,
|
||||
} => {
|
||||
packet.set_msg_type(Message::MldReport);
|
||||
packet.set_msg_code(0);
|
||||
packet.clear_reserved();
|
||||
|
@ -383,74 +391,49 @@ impl<'a> Repr<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::Icmpv6Repr;
|
||||
use crate::wire::icmpv6::Message;
|
||||
use super::*;
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::icmpv6::Message;
|
||||
use crate::wire::Icmpv6Repr;
|
||||
|
||||
static QUERY_PACKET_BYTES: [u8; 44] =
|
||||
[0x82, 0x00, 0x73, 0x74,
|
||||
0x04, 0x00, 0x00, 0x00,
|
||||
0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x0a, 0x12, 0x00, 0x01,
|
||||
0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02];
|
||||
static QUERY_PACKET_BYTES: [u8; 44] = [
|
||||
0x82, 0x00, 0x73, 0x74, 0x04, 0x00, 0x00, 0x00, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x12, 0x00, 0x01, 0xff, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
];
|
||||
|
||||
static QUERY_PACKET_PAYLOAD: [u8; 16] =
|
||||
[0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02];
|
||||
static QUERY_PACKET_PAYLOAD: [u8; 16] = [
|
||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02,
|
||||
];
|
||||
|
||||
static REPORT_PACKET_BYTES: [u8; 44] =
|
||||
[0x8f, 0x00, 0x73, 0x85,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x01, 0x00, 0x00, 0x01,
|
||||
0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02];
|
||||
|
||||
static REPORT_PACKET_PAYLOAD: [u8; 36] =
|
||||
[0x01, 0x00, 0x00, 0x01,
|
||||
0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0xff, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02];
|
||||
static REPORT_PACKET_BYTES: [u8; 44] = [
|
||||
0x8f, 0x00, 0x73, 0x85, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
];
|
||||
|
||||
static REPORT_PACKET_PAYLOAD: [u8; 36] = [
|
||||
0x01, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
];
|
||||
|
||||
fn create_repr<'a>(ty: Message) -> Icmpv6Repr<'a> {
|
||||
match ty {
|
||||
Message::MldQuery => {
|
||||
Icmpv6Repr::Mld(Repr::Query {
|
||||
max_resp_code: 0x400,
|
||||
mcast_addr: Ipv6Address::LINK_LOCAL_ALL_NODES,
|
||||
s_flag: true,
|
||||
qrv: 0x02,
|
||||
qqic: 0x12,
|
||||
num_srcs: 0x01,
|
||||
data: &QUERY_PACKET_PAYLOAD
|
||||
})
|
||||
},
|
||||
Message::MldReport => {
|
||||
Icmpv6Repr::Mld(Repr::Report {
|
||||
nr_mcast_addr_rcrds: 1,
|
||||
data: &REPORT_PACKET_PAYLOAD
|
||||
})
|
||||
},
|
||||
Message::MldQuery => Icmpv6Repr::Mld(Repr::Query {
|
||||
max_resp_code: 0x400,
|
||||
mcast_addr: Ipv6Address::LINK_LOCAL_ALL_NODES,
|
||||
s_flag: true,
|
||||
qrv: 0x02,
|
||||
qqic: 0x12,
|
||||
num_srcs: 0x01,
|
||||
data: &QUERY_PACKET_PAYLOAD,
|
||||
}),
|
||||
Message::MldReport => Icmpv6Repr::Mld(Repr::Report {
|
||||
nr_mcast_addr_rcrds: 1,
|
||||
data: &REPORT_PACKET_PAYLOAD,
|
||||
}),
|
||||
_ => {
|
||||
panic!("Message type must be a MLDv2 message type");
|
||||
}
|
||||
|
@ -465,12 +448,14 @@ mod test {
|
|||
assert_eq!(packet.checksum(), 0x7374);
|
||||
assert_eq!(packet.max_resp_code(), 0x0400);
|
||||
assert_eq!(packet.mcast_addr(), Ipv6Address::LINK_LOCAL_ALL_NODES);
|
||||
assert_eq!(packet.s_flag(), true);
|
||||
assert!(packet.s_flag());
|
||||
assert_eq!(packet.qrv(), 0x02);
|
||||
assert_eq!(packet.qqic(), 0x12);
|
||||
assert_eq!(packet.num_srcs(), 0x01);
|
||||
assert_eq!(Ipv6Address::from_bytes(packet.payload()),
|
||||
Ipv6Address::LINK_LOCAL_ALL_ROUTERS);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_bytes(packet.payload()),
|
||||
Ipv6Address::LINK_LOCAL_ALL_ROUTERS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -485,10 +470,14 @@ mod test {
|
|||
packet.set_qrv(0x02);
|
||||
packet.set_qqic(0x12);
|
||||
packet.set_num_srcs(0x01);
|
||||
packet.payload_mut().copy_from_slice(Ipv6Address::LINK_LOCAL_ALL_ROUTERS.as_bytes());
|
||||
packet
|
||||
.payload_mut()
|
||||
.copy_from_slice(Ipv6Address::LINK_LOCAL_ALL_ROUTERS.as_bytes());
|
||||
packet.clear_reserved();
|
||||
packet.fill_checksum(&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into());
|
||||
packet.fill_checksum(
|
||||
&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &QUERY_PACKET_BYTES[..]);
|
||||
}
|
||||
|
||||
|
@ -504,8 +493,10 @@ mod test {
|
|||
assert_eq!(addr_rcrd.aux_data_len(), 0x00);
|
||||
assert_eq!(addr_rcrd.num_srcs(), 0x01);
|
||||
assert_eq!(addr_rcrd.mcast_addr(), Ipv6Address::LINK_LOCAL_ALL_NODES);
|
||||
assert_eq!(Ipv6Address::from_bytes(addr_rcrd.payload()),
|
||||
Ipv6Address::LINK_LOCAL_ALL_ROUTERS);
|
||||
assert_eq!(
|
||||
Ipv6Address::from_bytes(addr_rcrd.payload()),
|
||||
Ipv6Address::LINK_LOCAL_ALL_ROUTERS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -522,31 +513,38 @@ mod test {
|
|||
addr_rcrd.set_aux_data_len(0);
|
||||
addr_rcrd.set_num_srcs(1);
|
||||
addr_rcrd.set_mcast_addr(Ipv6Address::LINK_LOCAL_ALL_NODES);
|
||||
addr_rcrd.payload_mut()
|
||||
addr_rcrd
|
||||
.payload_mut()
|
||||
.copy_from_slice(Ipv6Address::LINK_LOCAL_ALL_ROUTERS.as_bytes());
|
||||
}
|
||||
packet.fill_checksum(&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into());
|
||||
packet.fill_checksum(
|
||||
&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &REPORT_PACKET_BYTES[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_repr_parse() {
|
||||
let packet = Packet::new_unchecked(&QUERY_PACKET_BYTES[..]);
|
||||
let repr = Icmpv6Repr::parse(&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&packet,
|
||||
&ChecksumCapabilities::default());
|
||||
let repr = Icmpv6Repr::parse(
|
||||
&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(repr, Ok(create_repr(Message::MldQuery)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_repr_parse() {
|
||||
let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]);
|
||||
let repr = Icmpv6Repr::parse(&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&packet,
|
||||
&ChecksumCapabilities::default());
|
||||
let repr = Icmpv6Repr::parse(
|
||||
&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(repr, Ok(create_repr(Message::MldReport)));
|
||||
}
|
||||
|
||||
|
@ -555,10 +553,12 @@ mod test {
|
|||
let mut bytes = [0x2a; 44];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes[..]);
|
||||
let repr = create_repr(Message::MldQuery);
|
||||
repr.emit(&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&mut packet,
|
||||
&ChecksumCapabilities::default());
|
||||
repr.emit(
|
||||
&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&mut packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &QUERY_PACKET_BYTES[..]);
|
||||
}
|
||||
|
||||
|
@ -567,10 +567,12 @@ mod test {
|
|||
let mut bytes = [0x2a; 44];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes[..]);
|
||||
let repr = create_repr(Message::MldReport);
|
||||
repr.emit(&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&mut packet,
|
||||
&ChecksumCapabilities::default());
|
||||
repr.emit(
|
||||
&Ipv6Address::LINK_LOCAL_ALL_NODES.into(),
|
||||
&Ipv6Address::LINK_LOCAL_ALL_ROUTERS.into(),
|
||||
&mut packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &REPORT_PACKET_BYTES[..]);
|
||||
}
|
||||
}
|
||||
|
|
383
src/wire/mod.rs
383
src/wire/mod.rs
|
@ -72,154 +72,341 @@ let mut buffer = vec![0; repr.buffer_len() + repr.payload_len];
|
|||
|
||||
mod field {
|
||||
pub type Field = ::core::ops::Range<usize>;
|
||||
pub type Rest = ::core::ops::RangeFrom<usize>;
|
||||
pub type Rest = ::core::ops::RangeFrom<usize>;
|
||||
}
|
||||
|
||||
pub mod pretty_print;
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
mod ethernet;
|
||||
#[cfg(all(feature = "proto-ipv4", feature = "ethernet"))]
|
||||
#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
|
||||
mod arp;
|
||||
#[cfg(feature = "proto-dhcpv4")]
|
||||
pub(crate) mod dhcpv4;
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
mod ethernet;
|
||||
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
|
||||
mod icmp;
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
mod icmpv4;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod icmpv6;
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
pub mod ieee802154;
|
||||
#[cfg(feature = "proto-igmp")]
|
||||
mod igmp;
|
||||
pub(crate) mod ip;
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
mod ipv4;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod ipv6;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod ipv6option;
|
||||
mod ipv6fragment;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod ipv6hopbyhop;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod ipv6fragment;
|
||||
mod ipv6option;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod ipv6routing;
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
mod icmpv4;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod icmpv6;
|
||||
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
|
||||
mod icmp;
|
||||
#[cfg(feature = "proto-igmp")]
|
||||
mod igmp;
|
||||
#[cfg(all(feature = "proto-ipv6", feature = "ethernet"))]
|
||||
mod ndisc;
|
||||
#[cfg(all(feature = "proto-ipv6", feature = "ethernet"))]
|
||||
mod ndiscoption;
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
mod mld;
|
||||
mod udp;
|
||||
#[cfg(all(
|
||||
feature = "proto-ipv6",
|
||||
any(feature = "medium-ethernet", feature = "medium-ieee802154")
|
||||
))]
|
||||
mod ndisc;
|
||||
#[cfg(all(
|
||||
feature = "proto-ipv6",
|
||||
any(feature = "medium-ethernet", feature = "medium-ieee802154")
|
||||
))]
|
||||
mod ndiscoption;
|
||||
#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))]
|
||||
mod sixlowpan;
|
||||
mod tcp;
|
||||
#[cfg(feature = "proto-dhcpv4")]
|
||||
pub(crate) mod dhcpv4;
|
||||
mod udp;
|
||||
|
||||
use crate::{phy::Medium, Error};
|
||||
|
||||
pub use self::pretty_print::PrettyPrinter;
|
||||
|
||||
#[cfg(feature = "ethernet")]
|
||||
pub use self::ethernet::{EtherType as EthernetProtocol,
|
||||
Address as EthernetAddress,
|
||||
Frame as EthernetFrame,
|
||||
HEADER_LEN as ETHERNET_HEADER_LEN,
|
||||
Repr as EthernetRepr};
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
pub use self::ethernet::{
|
||||
Address as EthernetAddress, EtherType as EthernetProtocol, Frame as EthernetFrame,
|
||||
Repr as EthernetRepr, HEADER_LEN as ETHERNET_HEADER_LEN,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "proto-ipv4", feature = "ethernet"))]
|
||||
pub use self::arp::{Hardware as ArpHardware,
|
||||
Operation as ArpOperation,
|
||||
Packet as ArpPacket,
|
||||
Repr as ArpRepr};
|
||||
#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))]
|
||||
pub use self::arp::{
|
||||
Hardware as ArpHardware, Operation as ArpOperation, Packet as ArpPacket, Repr as ArpRepr,
|
||||
};
|
||||
|
||||
pub use self::ip::{Version as IpVersion,
|
||||
Protocol as IpProtocol,
|
||||
Address as IpAddress,
|
||||
Endpoint as IpEndpoint,
|
||||
Repr as IpRepr,
|
||||
Cidr as IpCidr};
|
||||
#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))]
|
||||
pub use self::sixlowpan::{
|
||||
iphc::{Packet as SixlowpanIphcPacket, Repr as SixlowpanIphcRepr},
|
||||
nhc::{
|
||||
ExtensionHeaderPacket as SixlowpanExtHeaderPacket,
|
||||
ExtensionHeaderRepr as SixlowpanExtHeaderRepr, Packet as SixlowpanNhcPacket,
|
||||
UdpNhcRepr as SixlowpanUdpRepr, UdpPacket as SixlowpanUdpPacket,
|
||||
},
|
||||
NextHeader as SixlowpanNextHeader,
|
||||
};
|
||||
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
pub use self::ieee802154::{
|
||||
Address as Ieee802154Address, AddressingMode as Ieee802154AddressingMode,
|
||||
Frame as Ieee802154Frame, FrameType as Ieee802154FrameType,
|
||||
FrameVersion as Ieee802154FrameVersion, Pan as Ieee802154Pan, Repr as Ieee802154Repr,
|
||||
};
|
||||
|
||||
pub use self::ip::{
|
||||
Address as IpAddress, Cidr as IpCidr, Endpoint as IpEndpoint, Protocol as IpProtocol,
|
||||
Repr as IpRepr, Version as IpVersion,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub use self::ipv4::{Address as Ipv4Address,
|
||||
Packet as Ipv4Packet,
|
||||
Repr as Ipv4Repr,
|
||||
Cidr as Ipv4Cidr,
|
||||
MIN_MTU as IPV4_MIN_MTU};
|
||||
pub use self::ipv4::{
|
||||
Address as Ipv4Address, Cidr as Ipv4Cidr, Packet as Ipv4Packet, Repr as Ipv4Repr,
|
||||
HEADER_LEN as IPV4_HEADER_LEN, MIN_MTU as IPV4_MIN_MTU,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use self::ipv6::{Address as Ipv6Address,
|
||||
Packet as Ipv6Packet,
|
||||
Repr as Ipv6Repr,
|
||||
Cidr as Ipv6Cidr,
|
||||
MIN_MTU as IPV6_MIN_MTU};
|
||||
pub use self::ipv6::{
|
||||
Address as Ipv6Address, Cidr as Ipv6Cidr, Packet as Ipv6Packet, Repr as Ipv6Repr,
|
||||
HEADER_LEN as IPV6_HEADER_LEN, MIN_MTU as IPV6_MIN_MTU,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use self::ipv6option::{Ipv6Option,
|
||||
Repr as Ipv6OptionRepr,
|
||||
Type as Ipv6OptionType,
|
||||
FailureType as Ipv6OptionFailureType};
|
||||
pub use self::ipv6option::{
|
||||
FailureType as Ipv6OptionFailureType, Ipv6Option, Repr as Ipv6OptionRepr,
|
||||
Type as Ipv6OptionType,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use self::ipv6hopbyhop::{Header as Ipv6HopByHopHeader,
|
||||
Repr as Ipv6HopByHopRepr};
|
||||
pub use self::ipv6hopbyhop::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr};
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use self::ipv6fragment::{Header as Ipv6FragmentHeader,
|
||||
Repr as Ipv6FragmentRepr};
|
||||
pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr};
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use self::ipv6routing::{Header as Ipv6RoutingHeader,
|
||||
Repr as Ipv6RoutingRepr};
|
||||
pub use self::ipv6routing::{Header as Ipv6RoutingHeader, Repr as Ipv6RoutingRepr};
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub use self::icmpv4::{Message as Icmpv4Message,
|
||||
DstUnreachable as Icmpv4DstUnreachable,
|
||||
Redirect as Icmpv4Redirect,
|
||||
TimeExceeded as Icmpv4TimeExceeded,
|
||||
ParamProblem as Icmpv4ParamProblem,
|
||||
Packet as Icmpv4Packet,
|
||||
Repr as Icmpv4Repr};
|
||||
pub use self::icmpv4::{
|
||||
DstUnreachable as Icmpv4DstUnreachable, Message as Icmpv4Message, Packet as Icmpv4Packet,
|
||||
ParamProblem as Icmpv4ParamProblem, Redirect as Icmpv4Redirect, Repr as Icmpv4Repr,
|
||||
TimeExceeded as Icmpv4TimeExceeded,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-igmp")]
|
||||
pub use self::igmp::{Packet as IgmpPacket,
|
||||
Repr as IgmpRepr,
|
||||
IgmpVersion};
|
||||
pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr};
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use self::icmpv6::{Message as Icmpv6Message,
|
||||
DstUnreachable as Icmpv6DstUnreachable,
|
||||
TimeExceeded as Icmpv6TimeExceeded,
|
||||
ParamProblem as Icmpv6ParamProblem,
|
||||
Packet as Icmpv6Packet,
|
||||
Repr as Icmpv6Repr};
|
||||
pub use self::icmpv6::{
|
||||
DstUnreachable as Icmpv6DstUnreachable, Message as Icmpv6Message, Packet as Icmpv6Packet,
|
||||
ParamProblem as Icmpv6ParamProblem, Repr as Icmpv6Repr, TimeExceeded as Icmpv6TimeExceeded,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
|
||||
pub use self::icmp::Repr as IcmpRepr;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "proto-ipv6",
|
||||
any(feature = "medium-ethernet", feature = "medium-ieee802154")
|
||||
))]
|
||||
pub use self::ndisc::{
|
||||
NeighborFlags as NdiscNeighborFlags, Repr as NdiscRepr, RouterFlags as NdiscRouterFlags,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "proto-ipv6", feature = "ethernet"))]
|
||||
pub use self::ndisc::{Repr as NdiscRepr,
|
||||
RouterFlags as NdiscRouterFlags,
|
||||
NeighborFlags as NdiscNeighborFlags};
|
||||
|
||||
#[cfg(all(feature = "proto-ipv6", feature = "ethernet"))]
|
||||
pub use self::ndiscoption::{NdiscOption,
|
||||
Repr as NdiscOptionRepr,
|
||||
Type as NdiscOptionType,
|
||||
PrefixInformation as NdiscPrefixInformation,
|
||||
RedirectedHeader as NdiscRedirectedHeader,
|
||||
PrefixInfoFlags as NdiscPrefixInfoFlags};
|
||||
#[cfg(all(
|
||||
feature = "proto-ipv6",
|
||||
any(feature = "medium-ethernet", feature = "medium-ieee802154")
|
||||
))]
|
||||
pub use self::ndiscoption::{
|
||||
NdiscOption, PrefixInfoFlags as NdiscPrefixInfoFlags,
|
||||
PrefixInformation as NdiscPrefixInformation, RedirectedHeader as NdiscRedirectedHeader,
|
||||
Repr as NdiscOptionRepr, Type as NdiscOptionType,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use self::mld::{AddressRecord as MldAddressRecord,
|
||||
Repr as MldRepr};
|
||||
pub use self::mld::{AddressRecord as MldAddressRecord, Repr as MldRepr};
|
||||
|
||||
pub use self::udp::{Packet as UdpPacket,
|
||||
Repr as UdpRepr};
|
||||
pub use self::udp::{Packet as UdpPacket, Repr as UdpRepr, HEADER_LEN as UDP_HEADER_LEN};
|
||||
|
||||
pub use self::tcp::{SeqNumber as TcpSeqNumber,
|
||||
Packet as TcpPacket,
|
||||
TcpOption,
|
||||
Repr as TcpRepr,
|
||||
Control as TcpControl};
|
||||
pub use self::tcp::{
|
||||
Control as TcpControl, Packet as TcpPacket, Repr as TcpRepr, SeqNumber as TcpSeqNumber,
|
||||
TcpOption, HEADER_LEN as TCP_HEADER_LEN,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-dhcpv4")]
|
||||
pub use self::dhcpv4::{Packet as DhcpPacket,
|
||||
Repr as DhcpRepr,
|
||||
MessageType as DhcpMessageType};
|
||||
pub use self::dhcpv4::{
|
||||
MessageType as DhcpMessageType, Packet as DhcpPacket, Repr as DhcpRepr,
|
||||
CLIENT_PORT as DHCP_CLIENT_PORT, MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT,
|
||||
SERVER_PORT as DHCP_SERVER_PORT,
|
||||
};
|
||||
|
||||
/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum HardwareAddress {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Ethernet(EthernetAddress),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Ieee802154(Ieee802154Address),
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
impl HardwareAddress {
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
HardwareAddress::Ethernet(addr) => addr.as_bytes(),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
HardwareAddress::Ieee802154(addr) => addr.as_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Query wether the address is an unicast address.
|
||||
pub fn is_unicast(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
HardwareAddress::Ethernet(addr) => addr.is_unicast(),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
HardwareAddress::Ieee802154(addr) => addr.is_unicast(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Query wether the address is a broadcast address.
|
||||
pub fn is_broadcast(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
HardwareAddress::Ethernet(addr) => addr.is_broadcast(),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
HardwareAddress::Ieee802154(addr) => addr.is_broadcast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
impl core::fmt::Display for HardwareAddress {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
HardwareAddress::Ethernet(addr) => write!(f, "{}", addr),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
HardwareAddress::Ieee802154(addr) => write!(f, "{}", addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
impl From<EthernetAddress> for HardwareAddress {
|
||||
fn from(addr: EthernetAddress) -> Self {
|
||||
HardwareAddress::Ethernet(addr)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
impl From<Ieee802154Address> for HardwareAddress {
|
||||
fn from(addr: Ieee802154Address) -> Self {
|
||||
HardwareAddress::Ieee802154(addr)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "medium-ieee802154"))]
|
||||
pub const MAX_HARDWARE_ADDRESS_LEN: usize = 6;
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
pub const MAX_HARDWARE_ADDRESS_LEN: usize = 8;
|
||||
|
||||
/// Unparsed hardware address.
|
||||
///
|
||||
/// Used to make NDISC parsing agnostic of the hardware medium in use.
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RawHardwareAddress {
|
||||
len: u8,
|
||||
data: [u8; MAX_HARDWARE_ADDRESS_LEN],
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
impl RawHardwareAddress {
|
||||
pub fn from_bytes(addr: &[u8]) -> Self {
|
||||
let mut data = [0u8; MAX_HARDWARE_ADDRESS_LEN];
|
||||
data[..addr.len()].copy_from_slice(addr);
|
||||
|
||||
Self {
|
||||
len: addr.len() as u8,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.data[..self.len as usize]
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len as usize
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub fn parse(&self, medium: Medium) -> Result<HardwareAddress, Error> {
|
||||
match medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => {
|
||||
if self.len() < 6 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
Ok(HardwareAddress::Ethernet(EthernetAddress::from_bytes(
|
||||
self.as_bytes(),
|
||||
)))
|
||||
}
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => {
|
||||
if self.len() < 8 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
Ok(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(
|
||||
self.as_bytes(),
|
||||
)))
|
||||
}
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
impl core::fmt::Display for RawHardwareAddress {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
for (i, &b) in self.as_bytes().iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ":")?;
|
||||
}
|
||||
write!(f, "{:02x}", b)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
impl From<EthernetAddress> for RawHardwareAddress {
|
||||
fn from(addr: EthernetAddress) -> Self {
|
||||
Self::from_bytes(addr.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
impl From<Ieee802154Address> for RawHardwareAddress {
|
||||
fn from(addr: Ieee802154Address) -> Self {
|
||||
Self::from_bytes(addr.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
impl From<HardwareAddress> for RawHardwareAddress {
|
||||
fn from(addr: HardwareAddress) -> Self {
|
||||
Self::from_bytes(addr.as_bytes())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use bitflags::bitflags;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::time::Duration;
|
||||
use crate::wire::icmpv6::{field, Message, Packet};
|
||||
use crate::wire::{EthernetAddress, Ipv6Repr, Ipv6Packet};
|
||||
use crate::wire::Ipv6Address;
|
||||
use crate::wire::RawHardwareAddress;
|
||||
use crate::wire::{Ipv6Packet, Ipv6Repr};
|
||||
use crate::wire::{NdiscOption, NdiscOptionRepr, NdiscOptionType};
|
||||
use crate::wire::{NdiscPrefixInformation, NdiscRedirectedHeader};
|
||||
use crate::time::Duration;
|
||||
use crate::wire::Ipv6Address;
|
||||
use crate::{Error, Result};
|
||||
|
||||
bitflags! {
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RouterFlags: u8 {
|
||||
const MANAGED = 0b10000000;
|
||||
const OTHER = 0b01000000;
|
||||
|
@ -17,6 +19,7 @@ bitflags! {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct NeighborFlags: u8 {
|
||||
const ROUTER = 0b10000000;
|
||||
const SOLICITED = 0b01000000;
|
||||
|
@ -80,7 +83,6 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Getters for the Neighbor Solicitation message header.
|
||||
/// See [RFC 4861 § 4.3].
|
||||
///
|
||||
|
@ -189,9 +191,10 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
|
||||
/// A high-level representation of an Neighbor Discovery packet header.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Repr<'a> {
|
||||
RouterSolicit {
|
||||
lladdr: Option<EthernetAddress>
|
||||
lladdr: Option<RawHardwareAddress>,
|
||||
},
|
||||
RouterAdvert {
|
||||
hop_limit: u8,
|
||||
|
@ -199,46 +202,49 @@ pub enum Repr<'a> {
|
|||
router_lifetime: Duration,
|
||||
reachable_time: Duration,
|
||||
retrans_time: Duration,
|
||||
lladdr: Option<EthernetAddress>,
|
||||
lladdr: Option<RawHardwareAddress>,
|
||||
mtu: Option<u32>,
|
||||
prefix_info: Option<NdiscPrefixInformation>
|
||||
prefix_info: Option<NdiscPrefixInformation>,
|
||||
},
|
||||
NeighborSolicit {
|
||||
target_addr: Ipv6Address,
|
||||
lladdr: Option<EthernetAddress>
|
||||
lladdr: Option<RawHardwareAddress>,
|
||||
},
|
||||
NeighborAdvert {
|
||||
flags: NeighborFlags,
|
||||
target_addr: Ipv6Address,
|
||||
lladdr: Option<EthernetAddress>
|
||||
lladdr: Option<RawHardwareAddress>,
|
||||
},
|
||||
Redirect {
|
||||
target_addr: Ipv6Address,
|
||||
dest_addr: Ipv6Address,
|
||||
lladdr: Option<EthernetAddress>,
|
||||
redirected_hdr: Option<NdiscRedirectedHeader<'a>>
|
||||
}
|
||||
lladdr: Option<RawHardwareAddress>,
|
||||
redirected_hdr: Option<NdiscRedirectedHeader<'a>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse an NDISC packet and return a high-level representation of the
|
||||
/// packet.
|
||||
pub fn parse<T>(packet: &Packet<&'a T>)
|
||||
-> Result<Repr<'a>>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(packet: &Packet<&'a T>) -> Result<Repr<'a>>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
match packet.msg_type() {
|
||||
Message::RouterSolicit => {
|
||||
let lladdr = if !packet.payload().is_empty() {
|
||||
let opt = NdiscOption::new_checked(packet.payload())?;
|
||||
match opt.option_type() {
|
||||
NdiscOptionType::SourceLinkLayerAddr => Some(opt.link_layer_addr()),
|
||||
_ => { return Err(Error::Unrecognized); }
|
||||
_ => {
|
||||
return Err(Error::Unrecognized);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Repr::RouterSolicit { lladdr })
|
||||
},
|
||||
}
|
||||
Message::RouterAdvert => {
|
||||
let mut offset = 0;
|
||||
let (mut lladdr, mut mtu, mut prefix_info) = (None, None, None);
|
||||
|
@ -249,7 +255,9 @@ impl<'a> Repr<'a> {
|
|||
NdiscOptionRepr::SourceLinkLayerAddr(addr) => lladdr = Some(addr),
|
||||
NdiscOptionRepr::Mtu(val) => mtu = Some(val),
|
||||
NdiscOptionRepr::PrefixInformation(info) => prefix_info = Some(info),
|
||||
_ => { return Err(Error::Unrecognized); }
|
||||
_ => {
|
||||
return Err(Error::Unrecognized);
|
||||
}
|
||||
}
|
||||
offset += opt.buffer_len();
|
||||
}
|
||||
|
@ -259,29 +267,36 @@ impl<'a> Repr<'a> {
|
|||
router_lifetime: packet.router_lifetime(),
|
||||
reachable_time: packet.reachable_time(),
|
||||
retrans_time: packet.retrans_time(),
|
||||
lladdr, mtu, prefix_info
|
||||
lladdr,
|
||||
mtu,
|
||||
prefix_info,
|
||||
})
|
||||
},
|
||||
}
|
||||
Message::NeighborSolicit => {
|
||||
let lladdr = if !packet.payload().is_empty() {
|
||||
let opt = NdiscOption::new_checked(packet.payload())?;
|
||||
match opt.option_type() {
|
||||
NdiscOptionType::SourceLinkLayerAddr => Some(opt.link_layer_addr()),
|
||||
_ => { return Err(Error::Unrecognized); }
|
||||
_ => {
|
||||
return Err(Error::Unrecognized);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Repr::NeighborSolicit {
|
||||
target_addr: packet.target_addr(), lladdr
|
||||
target_addr: packet.target_addr(),
|
||||
lladdr,
|
||||
})
|
||||
},
|
||||
}
|
||||
Message::NeighborAdvert => {
|
||||
let lladdr = if !packet.payload().is_empty() {
|
||||
let opt = NdiscOption::new_checked(packet.payload())?;
|
||||
match opt.option_type() {
|
||||
NdiscOptionType::TargetLinkLayerAddr => Some(opt.link_layer_addr()),
|
||||
_ => { return Err(Error::Unrecognized); }
|
||||
_ => {
|
||||
return Err(Error::Unrecognized);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
@ -289,9 +304,9 @@ impl<'a> Repr<'a> {
|
|||
Ok(Repr::NeighborAdvert {
|
||||
flags: packet.neighbor_flags(),
|
||||
target_addr: packet.target_addr(),
|
||||
lladdr
|
||||
lladdr,
|
||||
})
|
||||
},
|
||||
}
|
||||
Message::Redirect => {
|
||||
let mut offset = 0;
|
||||
let (mut lladdr, mut redirected_hdr) = (None, None);
|
||||
|
@ -301,43 +316,50 @@ impl<'a> Repr<'a> {
|
|||
NdiscOptionType::SourceLinkLayerAddr => {
|
||||
lladdr = Some(opt.link_layer_addr());
|
||||
offset += 8;
|
||||
},
|
||||
}
|
||||
NdiscOptionType::RedirectedHeader => {
|
||||
if opt.data_len() < 6 {
|
||||
return Err(Error::Truncated)
|
||||
return Err(Error::Truncated);
|
||||
} else {
|
||||
let ip_packet =
|
||||
Ipv6Packet::new_unchecked(&opt.data()[offset + 8..]);
|
||||
let ip_repr = Ipv6Repr::parse(&ip_packet)?;
|
||||
let data = &opt.data()[offset + 8 + ip_repr.buffer_len()..];
|
||||
redirected_hdr = Some(NdiscRedirectedHeader {
|
||||
header: ip_repr, data
|
||||
header: ip_repr,
|
||||
data,
|
||||
});
|
||||
offset += 8 + ip_repr.buffer_len() + data.len();
|
||||
}
|
||||
}
|
||||
_ => { return Err(Error::Unrecognized); }
|
||||
_ => {
|
||||
return Err(Error::Unrecognized);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Repr::Redirect {
|
||||
target_addr: packet.target_addr(),
|
||||
dest_addr: packet.dest_addr(),
|
||||
lladdr, redirected_hdr
|
||||
lladdr,
|
||||
redirected_hdr,
|
||||
})
|
||||
},
|
||||
_ => Err(Error::Unrecognized)
|
||||
}
|
||||
_ => Err(Error::Unrecognized),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
match self {
|
||||
&Repr::RouterSolicit { lladdr } => {
|
||||
match lladdr {
|
||||
Some(_) => field::UNUSED.end + 8,
|
||||
None => field::UNUSED.end,
|
||||
}
|
||||
&Repr::RouterSolicit { lladdr } => match lladdr {
|
||||
Some(_) => field::UNUSED.end + 8,
|
||||
None => field::UNUSED.end,
|
||||
},
|
||||
&Repr::RouterAdvert { lladdr, mtu, prefix_info, .. } => {
|
||||
&Repr::RouterAdvert {
|
||||
lladdr,
|
||||
mtu,
|
||||
prefix_info,
|
||||
..
|
||||
} => {
|
||||
let mut offset = 0;
|
||||
if lladdr.is_some() {
|
||||
offset += 8;
|
||||
|
@ -349,14 +371,19 @@ impl<'a> Repr<'a> {
|
|||
offset += 32;
|
||||
}
|
||||
field::RETRANS_TM.end + offset
|
||||
},
|
||||
}
|
||||
&Repr::NeighborSolicit { lladdr, .. } | &Repr::NeighborAdvert { lladdr, .. } => {
|
||||
match lladdr {
|
||||
Some(_) => field::TARGET_ADDR.end + 8,
|
||||
None => field::TARGET_ADDR.end,
|
||||
let mut offset = field::TARGET_ADDR.end;
|
||||
if let Some(lladdr) = lladdr {
|
||||
offset += NdiscOptionRepr::SourceLinkLayerAddr(lladdr).buffer_len();
|
||||
}
|
||||
},
|
||||
&Repr::Redirect { lladdr, redirected_hdr, .. } => {
|
||||
offset
|
||||
}
|
||||
&Repr::Redirect {
|
||||
lladdr,
|
||||
redirected_hdr,
|
||||
..
|
||||
} => {
|
||||
let mut offset = 0;
|
||||
if lladdr.is_some() {
|
||||
offset += 8;
|
||||
|
@ -370,7 +397,9 @@ impl<'a> Repr<'a> {
|
|||
}
|
||||
|
||||
pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||
where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
match *self {
|
||||
Repr::RouterSolicit { lladdr } => {
|
||||
packet.set_msg_type(Message::RouterSolicit);
|
||||
|
@ -380,10 +409,18 @@ impl<'a> Repr<'a> {
|
|||
let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
|
||||
NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Repr::RouterAdvert { hop_limit, flags, router_lifetime, reachable_time,
|
||||
retrans_time, lladdr, mtu, prefix_info } => {
|
||||
Repr::RouterAdvert {
|
||||
hop_limit,
|
||||
flags,
|
||||
router_lifetime,
|
||||
reachable_time,
|
||||
retrans_time,
|
||||
lladdr,
|
||||
mtu,
|
||||
prefix_info,
|
||||
} => {
|
||||
packet.set_msg_type(Message::RouterAdvert);
|
||||
packet.set_msg_code(0);
|
||||
packet.set_current_hop_limit(hop_limit);
|
||||
|
@ -393,10 +430,10 @@ impl<'a> Repr<'a> {
|
|||
packet.set_retrans_time(retrans_time);
|
||||
let mut offset = 0;
|
||||
if let Some(lladdr) = lladdr {
|
||||
let mut opt_pkt =
|
||||
NdiscOption::new_unchecked(packet.payload_mut());
|
||||
NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
|
||||
offset += 8;
|
||||
let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
|
||||
let opt = NdiscOptionRepr::SourceLinkLayerAddr(lladdr);
|
||||
opt.emit(&mut opt_pkt);
|
||||
offset += opt.buffer_len();
|
||||
}
|
||||
if let Some(mtu) = mtu {
|
||||
let mut opt_pkt =
|
||||
|
@ -409,34 +446,44 @@ impl<'a> Repr<'a> {
|
|||
NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]);
|
||||
NdiscOptionRepr::PrefixInformation(prefix_info).emit(&mut opt_pkt)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Repr::NeighborSolicit { target_addr, lladdr } => {
|
||||
Repr::NeighborSolicit {
|
||||
target_addr,
|
||||
lladdr,
|
||||
} => {
|
||||
packet.set_msg_type(Message::NeighborSolicit);
|
||||
packet.set_msg_code(0);
|
||||
packet.clear_reserved();
|
||||
packet.set_target_addr(target_addr);
|
||||
if let Some(lladdr) = lladdr {
|
||||
let mut opt_pkt =
|
||||
NdiscOption::new_unchecked(packet.payload_mut());
|
||||
let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
|
||||
NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Repr::NeighborAdvert { flags, target_addr, lladdr } => {
|
||||
Repr::NeighborAdvert {
|
||||
flags,
|
||||
target_addr,
|
||||
lladdr,
|
||||
} => {
|
||||
packet.set_msg_type(Message::NeighborAdvert);
|
||||
packet.set_msg_code(0);
|
||||
packet.clear_reserved();
|
||||
packet.set_neighbor_flags(flags);
|
||||
packet.set_target_addr(target_addr);
|
||||
if let Some(lladdr) = lladdr {
|
||||
let mut opt_pkt =
|
||||
NdiscOption::new_unchecked(packet.payload_mut());
|
||||
let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
|
||||
NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Repr::Redirect { target_addr, dest_addr, lladdr, redirected_hdr } => {
|
||||
Repr::Redirect {
|
||||
target_addr,
|
||||
dest_addr,
|
||||
lladdr,
|
||||
redirected_hdr,
|
||||
} => {
|
||||
packet.set_msg_type(Message::Redirect);
|
||||
packet.set_msg_code(0);
|
||||
packet.clear_reserved();
|
||||
|
@ -444,11 +491,10 @@ impl<'a> Repr<'a> {
|
|||
packet.set_dest_addr(dest_addr);
|
||||
let offset = match lladdr {
|
||||
Some(lladdr) => {
|
||||
let mut opt_pkt =
|
||||
NdiscOption::new_unchecked(packet.payload_mut());
|
||||
let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut());
|
||||
NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt);
|
||||
8
|
||||
},
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
if let Some(redirected_hdr) = redirected_hdr {
|
||||
|
@ -456,28 +502,24 @@ impl<'a> Repr<'a> {
|
|||
NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]);
|
||||
NdiscOptionRepr::RedirectedHeader(redirected_hdr).emit(&mut opt_pkt);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use super::*;
|
||||
use crate::wire::Icmpv6Repr;
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2};
|
||||
use crate::wire::EthernetAddress;
|
||||
use crate::wire::Icmpv6Repr;
|
||||
|
||||
static ROUTER_ADVERT_BYTES: [u8; 24] =
|
||||
[0x86, 0x00, 0xa9, 0xde,
|
||||
0x40, 0x80, 0x03, 0x84,
|
||||
0x00, 0x00, 0x03, 0x84,
|
||||
0x00, 0x00, 0x03, 0x84,
|
||||
0x01, 0x01, 0x52, 0x54,
|
||||
0x00, 0x12, 0x34, 0x56];
|
||||
static SOURCE_LINK_LAYER_OPT: [u8; 8] =
|
||||
[0x01, 0x01, 0x52, 0x54,
|
||||
0x00, 0x12, 0x34, 0x56];
|
||||
static ROUTER_ADVERT_BYTES: [u8; 24] = [
|
||||
0x86, 0x00, 0xa9, 0xde, 0x40, 0x80, 0x03, 0x84, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03,
|
||||
0x84, 0x01, 0x01, 0x52, 0x54, 0x00, 0x12, 0x34, 0x56,
|
||||
];
|
||||
static SOURCE_LINK_LAYER_OPT: [u8; 8] = [0x01, 0x01, 0x52, 0x54, 0x00, 0x12, 0x34, 0x56];
|
||||
|
||||
fn create_repr<'a>() -> Icmpv6Repr<'a> {
|
||||
Icmpv6Repr::Ndisc(Repr::RouterAdvert {
|
||||
|
@ -486,9 +528,9 @@ mod test {
|
|||
router_lifetime: Duration::from_secs(900),
|
||||
reachable_time: Duration::from_millis(900),
|
||||
retrans_time: Duration::from_millis(900),
|
||||
lladdr: Some(EthernetAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56])),
|
||||
lladdr: Some(EthernetAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56]).into()),
|
||||
mtu: None,
|
||||
prefix_info: None
|
||||
prefix_info: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -516,7 +558,9 @@ mod test {
|
|||
packet.set_router_lifetime(Duration::from_secs(900));
|
||||
packet.set_reachable_time(Duration::from_millis(900));
|
||||
packet.set_retrans_time(Duration::from_millis(900));
|
||||
packet.payload_mut().copy_from_slice(&SOURCE_LINK_LAYER_OPT[..]);
|
||||
packet
|
||||
.payload_mut()
|
||||
.copy_from_slice(&SOURCE_LINK_LAYER_OPT[..]);
|
||||
packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
|
||||
assert_eq!(&packet.into_inner()[..], &ROUTER_ADVERT_BYTES[..]);
|
||||
}
|
||||
|
@ -524,17 +568,28 @@ mod test {
|
|||
#[test]
|
||||
fn test_router_advert_repr_parse() {
|
||||
let packet = Packet::new_unchecked(&ROUTER_ADVERT_BYTES[..]);
|
||||
assert_eq!(Icmpv6Repr::parse(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2,
|
||||
&packet, &ChecksumCapabilities::default()).unwrap(),
|
||||
create_repr());
|
||||
assert_eq!(
|
||||
Icmpv6Repr::parse(
|
||||
&MOCK_IP_ADDR_1,
|
||||
&MOCK_IP_ADDR_2,
|
||||
&packet,
|
||||
&ChecksumCapabilities::default()
|
||||
)
|
||||
.unwrap(),
|
||||
create_repr()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_router_advert_repr_emit() {
|
||||
let mut bytes = vec![0x2a; 24];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes[..]);
|
||||
create_repr().emit(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2,
|
||||
&mut packet, &ChecksumCapabilities::default());
|
||||
create_repr().emit(
|
||||
&MOCK_IP_ADDR_1,
|
||||
&MOCK_IP_ADDR_2,
|
||||
&mut packet,
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &ROUTER_ADVERT_BYTES[..]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use core::fmt;
|
||||
use byteorder::{NetworkEndian, ByteOrder};
|
||||
use bitflags::bitflags;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::fmt;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::time::Duration;
|
||||
use crate::wire::{EthernetAddress, Ipv6Address, Ipv6Packet, Ipv6Repr};
|
||||
use crate::wire::{Ipv6Address, Ipv6Packet, Ipv6Repr, MAX_HARDWARE_ADDRESS_LEN};
|
||||
use crate::{Error, Result};
|
||||
|
||||
use crate::wire::RawHardwareAddress;
|
||||
|
||||
enum_with_unknown! {
|
||||
/// NDISC Option Type
|
||||
|
@ -27,15 +29,16 @@ impl fmt::Display for Type {
|
|||
match self {
|
||||
Type::SourceLinkLayerAddr => write!(f, "source link-layer address"),
|
||||
Type::TargetLinkLayerAddr => write!(f, "target link-layer address"),
|
||||
Type::PrefixInformation => write!(f, "prefix information"),
|
||||
Type::RedirectedHeader => write!(f, "redirected header"),
|
||||
Type::Mtu => write!(f, "mtu"),
|
||||
Type::Unknown(id) => write!(f, "{}", id)
|
||||
Type::PrefixInformation => write!(f, "prefix information"),
|
||||
Type::RedirectedHeader => write!(f, "redirected header"),
|
||||
Type::Mtu => write!(f, "mtu"),
|
||||
Type::Unknown(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PrefixInfoFlags: u8 {
|
||||
const ON_LINK = 0b10000000;
|
||||
const ADDRCONF = 0b01000000;
|
||||
|
@ -46,8 +49,9 @@ bitflags! {
|
|||
///
|
||||
/// [NDISC Option]: https://tools.ietf.org/html/rfc4861#section-4.6
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct NdiscOption<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
// Format of an NDISC Option
|
||||
|
@ -65,11 +69,11 @@ mod field {
|
|||
use crate::wire::field::*;
|
||||
|
||||
// 8-bit identifier of the type of option.
|
||||
pub const TYPE: usize = 0;
|
||||
pub const TYPE: usize = 0;
|
||||
// 8-bit unsigned integer. Length of the option, in units of 8 octests.
|
||||
pub const LENGTH: usize = 1;
|
||||
pub const LENGTH: usize = 1;
|
||||
// Minimum length of an option.
|
||||
pub const MIN_OPT_LEN: usize = 8;
|
||||
pub const MIN_OPT_LEN: usize = 8;
|
||||
// Variable-length field. Option-Type-specific data.
|
||||
pub fn DATA(length: u8) -> Field {
|
||||
2..length as usize * 8
|
||||
|
@ -80,9 +84,6 @@ mod field {
|
|||
// | Type | Length | Link-Layer Address ...
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
// Link-Layer Address
|
||||
pub const LL_ADDR: Field = 2..8;
|
||||
|
||||
// Prefix Information Option fields.
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type | Length | Prefix Length |L|A| Reserved1 |
|
||||
|
@ -103,17 +104,17 @@ mod field {
|
|||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
// Prefix length.
|
||||
pub const PREFIX_LEN: usize = 2;
|
||||
pub const PREFIX_LEN: usize = 2;
|
||||
// Flags field of prefix header.
|
||||
pub const FLAGS: usize = 3;
|
||||
pub const FLAGS: usize = 3;
|
||||
// Valid lifetime.
|
||||
pub const VALID_LT: Field = 4..8;
|
||||
pub const VALID_LT: Field = 4..8;
|
||||
// Preferred lifetime.
|
||||
pub const PREF_LT: Field = 8..12;
|
||||
pub const PREF_LT: Field = 8..12;
|
||||
// Reserved bits
|
||||
pub const PREF_RESERVED: Field = 12..16;
|
||||
// Prefix
|
||||
pub const PREFIX: Field = 16..32;
|
||||
pub const PREFIX: Field = 16..32;
|
||||
|
||||
// Redirected Header Option fields.
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
@ -127,10 +128,10 @@ mod field {
|
|||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
// Reserved bits.
|
||||
pub const IP_RESERVED: Field = 4..8;
|
||||
pub const IP_RESERVED: Field = 4..8;
|
||||
// Redirected header IP header + data.
|
||||
pub const IP_DATA: usize = 8;
|
||||
pub const REDIR_MIN_SZ: usize = 48;
|
||||
pub const IP_DATA: usize = 8;
|
||||
pub const REDIR_MIN_SZ: usize = 48;
|
||||
|
||||
// MTU Option fields
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
@ -140,7 +141,7 @@ mod field {
|
|||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
// MTU
|
||||
pub const MTU: Field = 4..8;
|
||||
pub const MTU: Field = 4..8;
|
||||
}
|
||||
|
||||
/// Core getter methods relevant to any type of NDISC option.
|
||||
|
@ -178,16 +179,11 @@ impl<T: AsRef<[u8]>> NdiscOption<T> {
|
|||
Err(Error::Truncated)
|
||||
} else {
|
||||
match self.option_type() {
|
||||
Type::SourceLinkLayerAddr | Type::TargetLinkLayerAddr | Type::Mtu =>
|
||||
Ok(()),
|
||||
Type::PrefixInformation if data_range.end >= field::PREFIX.end =>
|
||||
Ok(()),
|
||||
Type::RedirectedHeader if data_range.end >= field::REDIR_MIN_SZ =>
|
||||
Ok(()),
|
||||
Type::Unknown(_) =>
|
||||
Ok(()),
|
||||
_ =>
|
||||
Err(Error::Truncated),
|
||||
Type::SourceLinkLayerAddr | Type::TargetLinkLayerAddr | Type::Mtu => Ok(()),
|
||||
Type::PrefixInformation if data_range.end >= field::PREFIX.end => Ok(()),
|
||||
Type::RedirectedHeader if data_range.end >= field::REDIR_MIN_SZ => Ok(()),
|
||||
Type::Unknown(_) => Ok(()),
|
||||
_ => Err(Error::Truncated),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,9 +213,10 @@ impl<T: AsRef<[u8]>> NdiscOption<T> {
|
|||
impl<T: AsRef<[u8]>> NdiscOption<T> {
|
||||
/// Return the Source/Target Link-layer Address.
|
||||
#[inline]
|
||||
pub fn link_layer_addr(&self) -> EthernetAddress {
|
||||
pub fn link_layer_addr(&self) -> RawHardwareAddress {
|
||||
let len = MAX_HARDWARE_ADDRESS_LEN.min(self.data_len() as usize * 8 - 2);
|
||||
let data = self.buffer.as_ref();
|
||||
EthernetAddress::from_bytes(&data[field::LL_ADDR])
|
||||
RawHardwareAddress::from_bytes(&data[2..len + 2])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,9 +297,9 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
|
|||
impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
|
||||
/// Set the Source/Target Link-layer Address.
|
||||
#[inline]
|
||||
pub fn set_link_layer_addr(&mut self, addr: EthernetAddress) {
|
||||
pub fn set_link_layer_addr(&mut self, addr: RawHardwareAddress) {
|
||||
let data = self.buffer.as_mut();
|
||||
data[field::LL_ADDR].copy_from_slice(addr.as_bytes())
|
||||
data[2..2 + addr.len()].copy_from_slice(addr.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,7 +366,6 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> NdiscOption<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NdiscOption<&'a mut T> {
|
||||
/// Return a mutable pointer to the option data.
|
||||
#[inline]
|
||||
|
@ -393,39 +389,44 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for NdiscOption<&'a T> {
|
|||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PrefixInformation {
|
||||
pub prefix_len: u8,
|
||||
pub flags: PrefixInfoFlags,
|
||||
pub valid_lifetime: Duration,
|
||||
pub preferred_lifetime: Duration,
|
||||
pub prefix: Ipv6Address
|
||||
pub prefix: Ipv6Address,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RedirectedHeader<'a> {
|
||||
pub header: Ipv6Repr,
|
||||
pub data: &'a [u8]
|
||||
pub data: &'a [u8],
|
||||
}
|
||||
|
||||
/// A high-level representation of an NDISC Option.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Repr<'a> {
|
||||
SourceLinkLayerAddr(EthernetAddress),
|
||||
TargetLinkLayerAddr(EthernetAddress),
|
||||
SourceLinkLayerAddr(RawHardwareAddress),
|
||||
TargetLinkLayerAddr(RawHardwareAddress),
|
||||
PrefixInformation(PrefixInformation),
|
||||
RedirectedHeader(RedirectedHeader<'a>),
|
||||
Mtu(u32),
|
||||
Unknown {
|
||||
type_: u8,
|
||||
type_: u8,
|
||||
length: u8,
|
||||
data: &'a [u8]
|
||||
data: &'a [u8],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse an NDISC Option and return a high-level representation.
|
||||
pub fn parse<T>(opt: &'a NdiscOption<&'a T>) -> Result<Repr<'a>>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
match opt.option_type() {
|
||||
Type::SourceLinkLayerAddr => {
|
||||
if opt.data_len() == 1 {
|
||||
|
@ -433,14 +434,14 @@ impl<'a> Repr<'a> {
|
|||
} else {
|
||||
Err(Error::Malformed)
|
||||
}
|
||||
},
|
||||
}
|
||||
Type::TargetLinkLayerAddr => {
|
||||
if opt.data_len() == 1 {
|
||||
Ok(Repr::TargetLinkLayerAddr(opt.link_layer_addr()))
|
||||
} else {
|
||||
Err(Error::Malformed)
|
||||
}
|
||||
},
|
||||
}
|
||||
Type::PrefixInformation => {
|
||||
if opt.data_len() == 4 {
|
||||
Ok(Repr::PrefixInformation(PrefixInformation {
|
||||
|
@ -448,12 +449,12 @@ impl<'a> Repr<'a> {
|
|||
flags: opt.prefix_flags(),
|
||||
valid_lifetime: opt.valid_lifetime(),
|
||||
preferred_lifetime: opt.preferred_lifetime(),
|
||||
prefix: opt.prefix()
|
||||
prefix: opt.prefix(),
|
||||
}))
|
||||
} else {
|
||||
Err(Error::Malformed)
|
||||
}
|
||||
},
|
||||
}
|
||||
Type::RedirectedHeader => {
|
||||
// If the options data length is less than 6, the option
|
||||
// does not have enough data to fill out the IP header
|
||||
|
@ -465,60 +466,66 @@ impl<'a> Repr<'a> {
|
|||
let ip_repr = Ipv6Repr::parse(&ip_packet)?;
|
||||
Ok(Repr::RedirectedHeader(RedirectedHeader {
|
||||
header: ip_repr,
|
||||
data: &opt.data()[field::IP_DATA + ip_repr.buffer_len()..]
|
||||
data: &opt.data()[field::IP_DATA + ip_repr.buffer_len()..],
|
||||
}))
|
||||
}
|
||||
},
|
||||
}
|
||||
Type::Mtu => {
|
||||
if opt.data_len() == 1 {
|
||||
Ok(Repr::Mtu(opt.mtu()))
|
||||
} else {
|
||||
Err(Error::Malformed)
|
||||
}
|
||||
},
|
||||
Type::Unknown(id) => {
|
||||
Ok(Repr::Unknown {
|
||||
type_: id,
|
||||
length: opt.data_len(),
|
||||
data: opt.data()
|
||||
})
|
||||
}
|
||||
Type::Unknown(id) => Ok(Repr::Unknown {
|
||||
type_: id,
|
||||
length: opt.data_len(),
|
||||
data: opt.data(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the length of a header that will be emitted from this high-level representation.
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
match self {
|
||||
&Repr::SourceLinkLayerAddr(_) | &Repr::TargetLinkLayerAddr(_) =>
|
||||
field::LL_ADDR.end,
|
||||
&Repr::PrefixInformation(_) =>
|
||||
field::PREFIX.end,
|
||||
&Repr::RedirectedHeader(RedirectedHeader { header, data }) =>
|
||||
field::IP_DATA + header.buffer_len() + data.len(),
|
||||
&Repr::Mtu(_) =>
|
||||
field::MTU.end,
|
||||
&Repr::Unknown { length, .. } =>
|
||||
field::DATA(length).end
|
||||
&Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => {
|
||||
let len = 2 + addr.len();
|
||||
// Round up to next multiple of 8
|
||||
(len + 7) / 8 * 8
|
||||
}
|
||||
&Repr::PrefixInformation(_) => field::PREFIX.end,
|
||||
&Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
|
||||
field::IP_DATA + header.buffer_len() + data.len()
|
||||
}
|
||||
&Repr::Mtu(_) => field::MTU.end,
|
||||
&Repr::Unknown { length, .. } => field::DATA(length).end,
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a high-level representation into an NDISC Option.
|
||||
pub fn emit<T>(&self, opt: &mut NdiscOption<&'a mut T>)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||
where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
match *self {
|
||||
Repr::SourceLinkLayerAddr(addr) => {
|
||||
opt.set_option_type(Type::SourceLinkLayerAddr);
|
||||
opt.set_data_len(1);
|
||||
let opt_len = addr.len() + 2;
|
||||
opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8.
|
||||
opt.set_link_layer_addr(addr);
|
||||
},
|
||||
}
|
||||
Repr::TargetLinkLayerAddr(addr) => {
|
||||
opt.set_option_type(Type::TargetLinkLayerAddr);
|
||||
opt.set_data_len(1);
|
||||
let opt_len = addr.len() + 2;
|
||||
opt.set_data_len(((opt_len + 7) / 8) as u8); // round to next multiple of 8.
|
||||
opt.set_link_layer_addr(addr);
|
||||
},
|
||||
}
|
||||
Repr::PrefixInformation(PrefixInformation {
|
||||
prefix_len, flags, valid_lifetime,
|
||||
preferred_lifetime, prefix
|
||||
prefix_len,
|
||||
flags,
|
||||
valid_lifetime,
|
||||
preferred_lifetime,
|
||||
prefix,
|
||||
}) => {
|
||||
opt.clear_prefix_reserved();
|
||||
opt.set_option_type(Type::PrefixInformation);
|
||||
|
@ -528,10 +535,8 @@ impl<'a> Repr<'a> {
|
|||
opt.set_valid_lifetime(valid_lifetime);
|
||||
opt.set_preferred_lifetime(preferred_lifetime);
|
||||
opt.set_prefix(prefix);
|
||||
},
|
||||
Repr::RedirectedHeader(RedirectedHeader {
|
||||
header, data
|
||||
}) => {
|
||||
}
|
||||
Repr::RedirectedHeader(RedirectedHeader { header, data }) => {
|
||||
let data_len = data.len() / 8;
|
||||
opt.clear_redirected_reserved();
|
||||
opt.set_option_type(Type::RedirectedHeader);
|
||||
|
@ -547,7 +552,11 @@ impl<'a> Repr<'a> {
|
|||
opt.set_data_len(1);
|
||||
opt.set_mtu(mtu);
|
||||
}
|
||||
Repr::Unknown { type_: id, length, data } => {
|
||||
Repr::Unknown {
|
||||
type_: id,
|
||||
length,
|
||||
data,
|
||||
} => {
|
||||
opt.set_option_type(Type::Unknown(id));
|
||||
opt.set_data_len(length);
|
||||
opt.data_mut().copy_from_slice(data);
|
||||
|
@ -562,67 +571,61 @@ impl<'a> fmt::Display for Repr<'a> {
|
|||
match *self {
|
||||
Repr::SourceLinkLayerAddr(addr) => {
|
||||
write!(f, "SourceLinkLayer addr={}", addr)
|
||||
},
|
||||
}
|
||||
Repr::TargetLinkLayerAddr(addr) => {
|
||||
write!(f, "TargetLinkLayer addr={}", addr)
|
||||
},
|
||||
}
|
||||
Repr::PrefixInformation(PrefixInformation {
|
||||
prefix, prefix_len,
|
||||
..
|
||||
prefix, prefix_len, ..
|
||||
}) => {
|
||||
write!(f, "PrefixInformation prefix={}/{}", prefix, prefix_len)
|
||||
},
|
||||
Repr::RedirectedHeader(RedirectedHeader {
|
||||
header,
|
||||
..
|
||||
}) => {
|
||||
}
|
||||
Repr::RedirectedHeader(RedirectedHeader { header, .. }) => {
|
||||
write!(f, "RedirectedHeader header={}", header)
|
||||
},
|
||||
}
|
||||
Repr::Mtu(mtu) => {
|
||||
write!(f, "MTU mtu={}", mtu)
|
||||
},
|
||||
Repr::Unknown { type_: id, length, .. } => {
|
||||
}
|
||||
Repr::Unknown {
|
||||
type_: id, length, ..
|
||||
} => {
|
||||
write!(f, "Unknown({}) length={}", id, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for NdiscOption<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
match NdiscOption::new_checked(buffer) {
|
||||
Err(err) => return write!(f, "{}({})", indent, err),
|
||||
Ok(ndisc) => {
|
||||
match Repr::parse(&ndisc) {
|
||||
Err(_) => Ok(()),
|
||||
Ok(repr) => {
|
||||
write!(f, "{}{}", indent, repr)
|
||||
}
|
||||
Ok(ndisc) => match Repr::parse(&ndisc) {
|
||||
Err(_) => Ok(()),
|
||||
Ok(repr) => {
|
||||
write!(f, "{}{}", indent, repr)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Error;
|
||||
use super::{NdiscOption, PrefixInfoFlags, PrefixInformation, Repr, Type};
|
||||
use crate::time::Duration;
|
||||
use crate::wire::{EthernetAddress, Ipv6Address};
|
||||
use super::{NdiscOption, Type, PrefixInfoFlags, PrefixInformation, Repr};
|
||||
use crate::Error;
|
||||
|
||||
static PREFIX_OPT_BYTES: [u8; 32] = [
|
||||
0x03, 0x04, 0x40, 0xc0,
|
||||
0x00, 0x00, 0x03, 0x84,
|
||||
0x00, 0x00, 0x03, 0xe8,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0xfe, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01
|
||||
0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00,
|
||||
0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01,
|
||||
];
|
||||
|
||||
#[test]
|
||||
|
@ -631,7 +634,10 @@ mod test {
|
|||
assert_eq!(opt.option_type(), Type::PrefixInformation);
|
||||
assert_eq!(opt.data_len(), 4);
|
||||
assert_eq!(opt.prefix_len(), 64);
|
||||
assert_eq!(opt.prefix_flags(), PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF);
|
||||
assert_eq!(
|
||||
opt.prefix_flags(),
|
||||
PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF
|
||||
);
|
||||
assert_eq!(opt.valid_lifetime(), Duration::from_secs(900));
|
||||
assert_eq!(opt.preferred_lifetime(), Duration::from_secs(1000));
|
||||
assert_eq!(opt.prefix(), Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1));
|
||||
|
@ -653,11 +659,11 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_short_packet() {
|
||||
assert_eq!(NdiscOption::new_checked(&[0x00, 0x00]), Err(Error::Truncated));
|
||||
let bytes = [
|
||||
0x03, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
];
|
||||
assert_eq!(
|
||||
NdiscOption::new_checked(&[0x00, 0x00]),
|
||||
Err(Error::Truncated)
|
||||
);
|
||||
let bytes = [0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
assert_eq!(NdiscOption::new_checked(&bytes), Err(Error::Truncated));
|
||||
}
|
||||
|
||||
|
@ -666,13 +672,17 @@ mod test {
|
|||
let mut bytes = [0x01, 0x01, 0x54, 0x52, 0x00, 0x12, 0x23, 0x34];
|
||||
let addr = EthernetAddress([0x54, 0x52, 0x00, 0x12, 0x23, 0x34]);
|
||||
{
|
||||
assert_eq!(Repr::parse(&NdiscOption::new_unchecked(&bytes)),
|
||||
Ok(Repr::SourceLinkLayerAddr(addr)));
|
||||
assert_eq!(
|
||||
Repr::parse(&NdiscOption::new_unchecked(&bytes)),
|
||||
Ok(Repr::SourceLinkLayerAddr(addr.into()))
|
||||
);
|
||||
}
|
||||
bytes[0] = 0x02;
|
||||
{
|
||||
assert_eq!(Repr::parse(&NdiscOption::new_unchecked(&bytes)),
|
||||
Ok(Repr::TargetLinkLayerAddr(addr)));
|
||||
assert_eq!(
|
||||
Repr::parse(&NdiscOption::new_unchecked(&bytes)),
|
||||
Ok(Repr::TargetLinkLayerAddr(addr.into()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -683,9 +693,12 @@ mod test {
|
|||
flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
|
||||
valid_lifetime: Duration::from_secs(900),
|
||||
preferred_lifetime: Duration::from_secs(1000),
|
||||
prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)
|
||||
prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
|
||||
});
|
||||
assert_eq!(Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)), Ok(repr));
|
||||
assert_eq!(
|
||||
Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)),
|
||||
Ok(repr)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -696,7 +709,7 @@ mod test {
|
|||
flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
|
||||
valid_lifetime: Duration::from_secs(900),
|
||||
preferred_lifetime: Duration::from_secs(1000),
|
||||
prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)
|
||||
prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
|
||||
});
|
||||
let mut opt = NdiscOption::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut opt);
|
||||
|
@ -706,6 +719,9 @@ mod test {
|
|||
#[test]
|
||||
fn test_repr_parse_mtu() {
|
||||
let bytes = [0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc];
|
||||
assert_eq!(Repr::parse(&NdiscOption::new_unchecked(&bytes)), Ok(Repr::Mtu(1500)));
|
||||
assert_eq!(
|
||||
Repr::parse(&NdiscOption::new_unchecked(&bytes)),
|
||||
Ok(Repr::Mtu(1500))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,9 +34,10 @@ use core::marker::PhantomData;
|
|||
|
||||
/// Indentation state.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PrettyIndent {
|
||||
prefix: &'static str,
|
||||
level: usize
|
||||
level: usize,
|
||||
}
|
||||
|
||||
impl PrettyIndent {
|
||||
|
@ -71,24 +72,27 @@ pub trait PrettyPrint {
|
|||
///
|
||||
/// `pretty_print` accepts a buffer and not a packet wrapper because the packet might
|
||||
/// be truncated, and so it might not be possible to create the packet wrapper.
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, fmt: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result;
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
fmt: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result;
|
||||
}
|
||||
|
||||
/// Wrapper for using a `PrettyPrint` where a `Display` is expected.
|
||||
pub struct PrettyPrinter<'a, T: PrettyPrint> {
|
||||
prefix: &'static str,
|
||||
buffer: &'a dyn AsRef<[u8]>,
|
||||
phantom: PhantomData<T>
|
||||
prefix: &'static str,
|
||||
buffer: &'a dyn AsRef<[u8]>,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: PrettyPrint> PrettyPrinter<'a, T> {
|
||||
/// Format the listing with the recorded parameters when Display::fmt is called.
|
||||
pub fn new(prefix: &'static str, buffer: &'a dyn AsRef<[u8]>) -> PrettyPrinter<'a, T> {
|
||||
PrettyPrinter {
|
||||
prefix: prefix,
|
||||
buffer: buffer,
|
||||
phantom: PhantomData
|
||||
prefix: prefix,
|
||||
buffer: buffer,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
572
src/wire/tcp.rs
572
src/wire/tcp.rs
|
@ -1,16 +1,17 @@
|
|||
use core::{i32, ops, cmp, fmt};
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::{cmp, fmt, i32, ops};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::{IpProtocol, IpAddress};
|
||||
use crate::wire::ip::checksum;
|
||||
use crate::wire::{IpAddress, IpProtocol};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// A TCP sequence number.
|
||||
///
|
||||
/// A sequence number is a monotonically advancing integer modulo 2<sup>32</sup>.
|
||||
/// Sequence numbers do not have a discontiguity when compared pairwise across a signed overflow.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SeqNumber(pub i32);
|
||||
|
||||
impl fmt::Display for SeqNumber {
|
||||
|
@ -67,8 +68,9 @@ impl cmp::PartialOrd for SeqNumber {
|
|||
|
||||
/// A read/write wrapper around a Transmission Control Protocol packet buffer.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
mod field {
|
||||
|
@ -78,12 +80,12 @@ mod field {
|
|||
|
||||
pub const SRC_PORT: Field = 0..2;
|
||||
pub const DST_PORT: Field = 2..4;
|
||||
pub const SEQ_NUM: Field = 4..8;
|
||||
pub const ACK_NUM: Field = 8..12;
|
||||
pub const FLAGS: Field = 12..14;
|
||||
pub const SEQ_NUM: Field = 4..8;
|
||||
pub const ACK_NUM: Field = 8..12;
|
||||
pub const FLAGS: Field = 12..14;
|
||||
pub const WIN_SIZE: Field = 14..16;
|
||||
pub const CHECKSUM: Field = 16..18;
|
||||
pub const URGENT: Field = 18..20;
|
||||
pub const URGENT: Field = 18..20;
|
||||
|
||||
pub fn OPTIONS(length: u8) -> Field {
|
||||
URGENT.end..(length as usize)
|
||||
|
@ -97,16 +99,18 @@ mod field {
|
|||
pub const FLG_URG: u16 = 0x020;
|
||||
pub const FLG_ECE: u16 = 0x040;
|
||||
pub const FLG_CWR: u16 = 0x080;
|
||||
pub const FLG_NS: u16 = 0x100;
|
||||
pub const FLG_NS: u16 = 0x100;
|
||||
|
||||
pub const OPT_END: u8 = 0x00;
|
||||
pub const OPT_NOP: u8 = 0x01;
|
||||
pub const OPT_MSS: u8 = 0x02;
|
||||
pub const OPT_WS: u8 = 0x03;
|
||||
pub const OPT_WS: u8 = 0x03;
|
||||
pub const OPT_SACKPERM: u8 = 0x04;
|
||||
pub const OPT_SACKRNG: u8 = 0x05;
|
||||
pub const OPT_SACKRNG: u8 = 0x05;
|
||||
}
|
||||
|
||||
pub const HEADER_LEN: usize = field::URGENT.end;
|
||||
|
||||
impl<T: AsRef<[u8]>> Packet<T> {
|
||||
/// Imbue a raw octet buffer with TCP packet structure.
|
||||
pub fn new_unchecked(buffer: T) -> Packet<T> {
|
||||
|
@ -285,8 +289,12 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
pub fn segment_len(&self) -> usize {
|
||||
let data = self.buffer.as_ref();
|
||||
let mut length = data.len() - self.header_len() as usize;
|
||||
if self.syn() { length += 1 }
|
||||
if self.fin() { length += 1 }
|
||||
if self.syn() {
|
||||
length += 1
|
||||
}
|
||||
if self.fin() {
|
||||
length += 1
|
||||
}
|
||||
length
|
||||
}
|
||||
|
||||
|
@ -329,13 +337,14 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
/// # Fuzzing
|
||||
/// This function always returns `true` when fuzzing.
|
||||
pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
|
||||
if cfg!(fuzzing) { return true }
|
||||
if cfg!(fuzzing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let data = self.buffer.as_ref();
|
||||
checksum::combine(&[
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp,
|
||||
data.len() as u32),
|
||||
checksum::data(data)
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32),
|
||||
checksum::data(data),
|
||||
]) == !0
|
||||
}
|
||||
}
|
||||
|
@ -401,7 +410,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_fin(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_FIN } else { raw & !field::FLG_FIN };
|
||||
let raw = if value {
|
||||
raw | field::FLG_FIN
|
||||
} else {
|
||||
raw & !field::FLG_FIN
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -410,7 +423,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_syn(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_SYN } else { raw & !field::FLG_SYN };
|
||||
let raw = if value {
|
||||
raw | field::FLG_SYN
|
||||
} else {
|
||||
raw & !field::FLG_SYN
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -419,7 +436,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_rst(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_RST } else { raw & !field::FLG_RST };
|
||||
let raw = if value {
|
||||
raw | field::FLG_RST
|
||||
} else {
|
||||
raw & !field::FLG_RST
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -428,7 +449,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_psh(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_PSH } else { raw & !field::FLG_PSH };
|
||||
let raw = if value {
|
||||
raw | field::FLG_PSH
|
||||
} else {
|
||||
raw & !field::FLG_PSH
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -437,7 +462,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_ack(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_ACK } else { raw & !field::FLG_ACK };
|
||||
let raw = if value {
|
||||
raw | field::FLG_ACK
|
||||
} else {
|
||||
raw & !field::FLG_ACK
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -446,7 +475,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_urg(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_URG } else { raw & !field::FLG_URG };
|
||||
let raw = if value {
|
||||
raw | field::FLG_URG
|
||||
} else {
|
||||
raw & !field::FLG_URG
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -455,7 +488,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_ece(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_ECE } else { raw & !field::FLG_ECE };
|
||||
let raw = if value {
|
||||
raw | field::FLG_ECE
|
||||
} else {
|
||||
raw & !field::FLG_ECE
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -464,7 +501,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_cwr(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_CWR } else { raw & !field::FLG_CWR };
|
||||
let raw = if value {
|
||||
raw | field::FLG_CWR
|
||||
} else {
|
||||
raw & !field::FLG_CWR
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -473,7 +514,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
pub fn set_ns(&mut self, value: bool) {
|
||||
let data = self.buffer.as_mut();
|
||||
let raw = NetworkEndian::read_u16(&data[field::FLAGS]);
|
||||
let raw = if value { raw | field::FLG_NS } else { raw & !field::FLG_NS };
|
||||
let raw = if value {
|
||||
raw | field::FLG_NS
|
||||
} else {
|
||||
raw & !field::FLG_NS
|
||||
};
|
||||
NetworkEndian::write_u16(&mut data[field::FLAGS], raw)
|
||||
}
|
||||
|
||||
|
@ -517,9 +562,8 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
let checksum = {
|
||||
let data = self.buffer.as_ref();
|
||||
!checksum::combine(&[
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp,
|
||||
data.len() as u32),
|
||||
checksum::data(data)
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32),
|
||||
checksum::data(data),
|
||||
])
|
||||
};
|
||||
self.set_checksum(checksum)
|
||||
|
@ -550,6 +594,7 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
|
|||
|
||||
/// A representation of a single TCP option.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum TcpOption<'a> {
|
||||
EndOfList,
|
||||
NoOperation,
|
||||
|
@ -557,7 +602,7 @@ pub enum TcpOption<'a> {
|
|||
WindowScale(u8),
|
||||
SackPermitted,
|
||||
SackRange([Option<(u32, u32)>; 3]),
|
||||
Unknown { kind: u8, data: &'a [u8] }
|
||||
Unknown { kind: u8, data: &'a [u8] },
|
||||
}
|
||||
|
||||
impl<'a> TcpOption<'a> {
|
||||
|
@ -576,24 +621,18 @@ impl<'a> TcpOption<'a> {
|
|||
length = *buffer.get(1).ok_or(Error::Truncated)? as usize;
|
||||
let data = buffer.get(2..length).ok_or(Error::Truncated)?;
|
||||
match (kind, length) {
|
||||
(field::OPT_END, _) |
|
||||
(field::OPT_NOP, _) =>
|
||||
unreachable!(),
|
||||
(field::OPT_MSS, 4) =>
|
||||
option = TcpOption::MaxSegmentSize(NetworkEndian::read_u16(data)),
|
||||
(field::OPT_MSS, _) =>
|
||||
return Err(Error::Malformed),
|
||||
(field::OPT_WS, 3) =>
|
||||
option = TcpOption::WindowScale(data[0]),
|
||||
(field::OPT_WS, _) =>
|
||||
return Err(Error::Malformed),
|
||||
(field::OPT_SACKPERM, 2) =>
|
||||
option = TcpOption::SackPermitted,
|
||||
(field::OPT_SACKPERM, _) =>
|
||||
return Err(Error::Malformed),
|
||||
(field::OPT_END, _) | (field::OPT_NOP, _) => unreachable!(),
|
||||
(field::OPT_MSS, 4) => {
|
||||
option = TcpOption::MaxSegmentSize(NetworkEndian::read_u16(data))
|
||||
}
|
||||
(field::OPT_MSS, _) => return Err(Error::Malformed),
|
||||
(field::OPT_WS, 3) => option = TcpOption::WindowScale(data[0]),
|
||||
(field::OPT_WS, _) => return Err(Error::Malformed),
|
||||
(field::OPT_SACKPERM, 2) => option = TcpOption::SackPermitted,
|
||||
(field::OPT_SACKPERM, _) => return Err(Error::Malformed),
|
||||
(field::OPT_SACKRNG, n) => {
|
||||
if n < 10 || (n-2) % 8 != 0 {
|
||||
return Err(Error::Malformed)
|
||||
if n < 10 || (n - 2) % 8 != 0 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
if n > 26 {
|
||||
// It's possible for a remote to send 4 SACK blocks, but extremely rare.
|
||||
|
@ -617,19 +656,16 @@ impl<'a> TcpOption<'a> {
|
|||
*nmut = if left < data.len() {
|
||||
let mid = left + 4;
|
||||
let right = mid + 4;
|
||||
let range_left = NetworkEndian::read_u32(
|
||||
&data[left..mid]);
|
||||
let range_right = NetworkEndian::read_u32(
|
||||
&data[mid..right]);
|
||||
let range_left = NetworkEndian::read_u32(&data[left..mid]);
|
||||
let range_right = NetworkEndian::read_u32(&data[mid..right]);
|
||||
Some((range_left, range_right))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
});
|
||||
option = TcpOption::SackRange(sack_ranges);
|
||||
},
|
||||
(_, _) =>
|
||||
option = TcpOption::Unknown { kind, data }
|
||||
}
|
||||
(_, _) => option = TcpOption::Unknown { kind, data },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -644,7 +680,7 @@ impl<'a> TcpOption<'a> {
|
|||
TcpOption::WindowScale(_) => 3,
|
||||
TcpOption::SackPermitted => 2,
|
||||
TcpOption::SackRange(s) => s.iter().filter(|s| s.is_some()).count() * 8 + 2,
|
||||
TcpOption::Unknown { data, .. } => 2 + data.len()
|
||||
TcpOption::Unknown { data, .. } => 2 + data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -652,23 +688,21 @@ impl<'a> TcpOption<'a> {
|
|||
let length;
|
||||
match *self {
|
||||
TcpOption::EndOfList => {
|
||||
length = 1;
|
||||
length = 1;
|
||||
// There may be padding space which also should be initialized.
|
||||
for p in buffer.iter_mut() {
|
||||
*p = field::OPT_END;
|
||||
}
|
||||
}
|
||||
TcpOption::NoOperation => {
|
||||
length = 1;
|
||||
length = 1;
|
||||
buffer[0] = field::OPT_NOP;
|
||||
}
|
||||
_ => {
|
||||
length = self.buffer_len();
|
||||
length = self.buffer_len();
|
||||
buffer[1] = length as u8;
|
||||
match self {
|
||||
&TcpOption::EndOfList |
|
||||
&TcpOption::NoOperation =>
|
||||
unreachable!(),
|
||||
&TcpOption::EndOfList | &TcpOption::NoOperation => unreachable!(),
|
||||
&TcpOption::MaxSegmentSize(value) => {
|
||||
buffer[0] = field::OPT_MSS;
|
||||
NetworkEndian::write_u16(&mut buffer[2..], value)
|
||||
|
@ -682,14 +716,21 @@ impl<'a> TcpOption<'a> {
|
|||
}
|
||||
&TcpOption::SackRange(slice) => {
|
||||
buffer[0] = field::OPT_SACKRNG;
|
||||
slice.iter().filter(|s| s.is_some()).enumerate().for_each(|(i, s)| {
|
||||
let (first, second) = *s.as_ref().unwrap();
|
||||
let pos = i * 8 + 2;
|
||||
NetworkEndian::write_u32(&mut buffer[pos..], first);
|
||||
NetworkEndian::write_u32(&mut buffer[pos+4..], second);
|
||||
});
|
||||
slice
|
||||
.iter()
|
||||
.filter(|s| s.is_some())
|
||||
.enumerate()
|
||||
.for_each(|(i, s)| {
|
||||
let (first, second) = *s.as_ref().unwrap();
|
||||
let pos = i * 8 + 2;
|
||||
NetworkEndian::write_u32(&mut buffer[pos..], first);
|
||||
NetworkEndian::write_u32(&mut buffer[pos + 4..], second);
|
||||
});
|
||||
}
|
||||
&TcpOption::Unknown { kind, data: provided } => {
|
||||
&TcpOption::Unknown {
|
||||
kind,
|
||||
data: provided,
|
||||
} => {
|
||||
buffer[0] = kind;
|
||||
buffer[2..].copy_from_slice(provided)
|
||||
}
|
||||
|
@ -702,12 +743,13 @@ impl<'a> TcpOption<'a> {
|
|||
|
||||
/// The possible control flags of a Transmission Control Protocol packet.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Control {
|
||||
None,
|
||||
Psh,
|
||||
Syn,
|
||||
Fin,
|
||||
Rst
|
||||
Rst,
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
|
@ -715,8 +757,8 @@ impl Control {
|
|||
/// Return the length of a control flag, in terms of sequence space.
|
||||
pub fn len(self) -> usize {
|
||||
match self {
|
||||
Control::Syn | Control::Fin => 1,
|
||||
_ => 0
|
||||
Control::Syn | Control::Fin => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -724,54 +766,63 @@ impl Control {
|
|||
pub fn quash_psh(self) -> Control {
|
||||
match self {
|
||||
Control::Psh => Control::None,
|
||||
_ => self
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A high-level representation of a Transmission Control Protocol packet.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr<'a> {
|
||||
pub src_port: u16,
|
||||
pub dst_port: u16,
|
||||
pub control: Control,
|
||||
pub seq_number: SeqNumber,
|
||||
pub ack_number: Option<SeqNumber>,
|
||||
pub window_len: u16,
|
||||
pub src_port: u16,
|
||||
pub dst_port: u16,
|
||||
pub control: Control,
|
||||
pub seq_number: SeqNumber,
|
||||
pub ack_number: Option<SeqNumber>,
|
||||
pub window_len: u16,
|
||||
pub window_scale: Option<u8>,
|
||||
pub max_seg_size: Option<u16>,
|
||||
pub sack_permitted: bool,
|
||||
pub sack_ranges: [Option<(u32, u32)>; 3],
|
||||
pub payload: &'a [u8]
|
||||
pub sack_ranges: [Option<(u32, u32)>; 3],
|
||||
pub payload: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
/// Parse a Transmission Control Protocol packet and return a high-level representation.
|
||||
pub fn parse<T>(packet: &Packet<&'a T>, src_addr: &IpAddress, dst_addr: &IpAddress,
|
||||
checksum_caps: &ChecksumCapabilities) -> Result<Repr<'a>>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(
|
||||
packet: &Packet<&'a T>,
|
||||
src_addr: &IpAddress,
|
||||
dst_addr: &IpAddress,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) -> Result<Repr<'a>>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
// Source and destination ports must be present.
|
||||
if packet.src_port() == 0 { return Err(Error::Malformed) }
|
||||
if packet.dst_port() == 0 { return Err(Error::Malformed) }
|
||||
if packet.src_port() == 0 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
if packet.dst_port() == 0 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
// Valid checksum is expected.
|
||||
if checksum_caps.tcp.rx() && !packet.verify_checksum(src_addr, dst_addr) {
|
||||
return Err(Error::Checksum)
|
||||
return Err(Error::Checksum);
|
||||
}
|
||||
|
||||
let control =
|
||||
match (packet.syn(), packet.fin(), packet.rst(), packet.psh()) {
|
||||
(false, false, false, false) => Control::None,
|
||||
(false, false, false, true) => Control::Psh,
|
||||
(true, false, false, _) => Control::Syn,
|
||||
(false, true, false, _) => Control::Fin,
|
||||
(false, false, true , _) => Control::Rst,
|
||||
_ => return Err(Error::Malformed)
|
||||
};
|
||||
let ack_number =
|
||||
match packet.ack() {
|
||||
true => Some(packet.ack_number()),
|
||||
false => None
|
||||
};
|
||||
let control = match (packet.syn(), packet.fin(), packet.rst(), packet.psh()) {
|
||||
(false, false, false, false) => Control::None,
|
||||
(false, false, false, true) => Control::Psh,
|
||||
(true, false, false, _) => Control::Syn,
|
||||
(false, true, false, _) => Control::Fin,
|
||||
(false, false, true, _) => Control::Rst,
|
||||
_ => return Err(Error::Malformed),
|
||||
};
|
||||
let ack_number = match packet.ack() {
|
||||
true => Some(packet.ack_number()),
|
||||
false => None,
|
||||
};
|
||||
// The PSH flag is ignored.
|
||||
// The URG flag and the urgent field is ignored. This behavior is standards-compliant,
|
||||
// however, most deployed systems (e.g. Linux) are *not* standards-compliant, and would
|
||||
|
@ -787,41 +838,44 @@ impl<'a> Repr<'a> {
|
|||
match option {
|
||||
TcpOption::EndOfList => break,
|
||||
TcpOption::NoOperation => (),
|
||||
TcpOption::MaxSegmentSize(value) =>
|
||||
max_seg_size = Some(value),
|
||||
TcpOption::MaxSegmentSize(value) => max_seg_size = Some(value),
|
||||
TcpOption::WindowScale(value) => {
|
||||
// RFC 1323: Thus, the shift count must be limited to 14 (which allows windows
|
||||
// of 2**30 = 1 Gbyte). If a Window Scale option is received with a shift.cnt
|
||||
// value exceeding 14, the TCP should log the error but use 14 instead of the
|
||||
// specified value.
|
||||
window_scale = if value > 14 {
|
||||
net_debug!("{}:{}:{}:{}: parsed window scaling factor >14, setting to 14", src_addr, packet.src_port(), dst_addr, packet.dst_port());
|
||||
net_debug!(
|
||||
"{}:{}:{}:{}: parsed window scaling factor >14, setting to 14",
|
||||
src_addr,
|
||||
packet.src_port(),
|
||||
dst_addr,
|
||||
packet.dst_port()
|
||||
);
|
||||
Some(14)
|
||||
} else {
|
||||
Some(value)
|
||||
};
|
||||
},
|
||||
TcpOption::SackPermitted =>
|
||||
sack_permitted = true,
|
||||
TcpOption::SackRange(slice) =>
|
||||
sack_ranges = slice,
|
||||
}
|
||||
TcpOption::SackPermitted => sack_permitted = true,
|
||||
TcpOption::SackRange(slice) => sack_ranges = slice,
|
||||
_ => (),
|
||||
}
|
||||
options = next_options;
|
||||
}
|
||||
|
||||
Ok(Repr {
|
||||
src_port: packet.src_port(),
|
||||
dst_port: packet.dst_port(),
|
||||
control: control,
|
||||
seq_number: packet.seq_number(),
|
||||
ack_number: ack_number,
|
||||
window_len: packet.window_len(),
|
||||
src_port: packet.src_port(),
|
||||
dst_port: packet.dst_port(),
|
||||
control: control,
|
||||
seq_number: packet.seq_number(),
|
||||
ack_number: ack_number,
|
||||
window_len: packet.window_len(),
|
||||
window_scale: window_scale,
|
||||
max_seg_size: max_seg_size,
|
||||
sack_permitted: sack_permitted,
|
||||
sack_ranges: sack_ranges,
|
||||
payload: packet.payload()
|
||||
sack_ranges: sack_ranges,
|
||||
payload: packet.payload(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -840,9 +894,11 @@ impl<'a> Repr<'a> {
|
|||
if self.sack_permitted {
|
||||
length += 2;
|
||||
}
|
||||
let sack_range_len: usize = self.sack_ranges.iter().map(
|
||||
|o| o.map(|_| 8).unwrap_or(0)
|
||||
).sum();
|
||||
let sack_range_len: usize = self
|
||||
.sack_ranges
|
||||
.iter()
|
||||
.map(|o| o.map(|_| 8).unwrap_or(0))
|
||||
.sum();
|
||||
if sack_range_len > 0 {
|
||||
length += sack_range_len + 2;
|
||||
}
|
||||
|
@ -852,23 +908,21 @@ impl<'a> Repr<'a> {
|
|||
length
|
||||
}
|
||||
|
||||
/// Return the length of the header for the TCP protocol.
|
||||
///
|
||||
/// Per RFC 6691, this should be used for MSS calculations. It may be smaller than the buffer
|
||||
/// space required to accomodate this packet's data.
|
||||
pub fn mss_header_len(&self) -> usize {
|
||||
field::URGENT.end
|
||||
}
|
||||
|
||||
/// Return the length of a packet that will be emitted from this high-level representation.
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
self.header_len() + self.payload.len()
|
||||
}
|
||||
|
||||
/// Emit a high-level representation into a Transmission Control Protocol packet.
|
||||
pub fn emit<T>(&self, packet: &mut Packet<&mut T>, src_addr: &IpAddress, dst_addr: &IpAddress,
|
||||
checksum_caps: &ChecksumCapabilities)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
||||
pub fn emit<T>(
|
||||
&self,
|
||||
packet: &mut Packet<&mut T>,
|
||||
src_addr: &IpAddress,
|
||||
dst_addr: &IpAddress,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) where
|
||||
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
|
||||
{
|
||||
packet.set_src_port(self.src_port);
|
||||
packet.set_dst_port(self.dst_port);
|
||||
packet.set_seq_number(self.seq_number);
|
||||
|
@ -878,24 +932,28 @@ impl<'a> Repr<'a> {
|
|||
packet.clear_flags();
|
||||
match self.control {
|
||||
Control::None => (),
|
||||
Control::Psh => packet.set_psh(true),
|
||||
Control::Syn => packet.set_syn(true),
|
||||
Control::Fin => packet.set_fin(true),
|
||||
Control::Rst => packet.set_rst(true)
|
||||
Control::Psh => packet.set_psh(true),
|
||||
Control::Syn => packet.set_syn(true),
|
||||
Control::Fin => packet.set_fin(true),
|
||||
Control::Rst => packet.set_rst(true),
|
||||
}
|
||||
packet.set_ack(self.ack_number.is_some());
|
||||
{
|
||||
let mut options = packet.options_mut();
|
||||
if let Some(value) = self.max_seg_size {
|
||||
let tmp = options; options = TcpOption::MaxSegmentSize(value).emit(tmp);
|
||||
let tmp = options;
|
||||
options = TcpOption::MaxSegmentSize(value).emit(tmp);
|
||||
}
|
||||
if let Some(value) = self.window_scale {
|
||||
let tmp = options; options = TcpOption::WindowScale(value).emit(tmp);
|
||||
let tmp = options;
|
||||
options = TcpOption::WindowScale(value).emit(tmp);
|
||||
}
|
||||
if self.sack_permitted {
|
||||
let tmp = options; options = TcpOption::SackPermitted.emit(tmp);
|
||||
let tmp = options;
|
||||
options = TcpOption::SackPermitted.emit(tmp);
|
||||
} else if self.ack_number.is_some() && self.sack_ranges.iter().any(|s| s.is_some()) {
|
||||
let tmp = options; options = TcpOption::SackRange(self.sack_ranges).emit(tmp);
|
||||
let tmp = options;
|
||||
options = TcpOption::SackRange(self.sack_ranges).emit(tmp);
|
||||
}
|
||||
|
||||
if !options.is_empty() {
|
||||
|
@ -923,8 +981,8 @@ impl<'a> Repr<'a> {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
match self.control {
|
||||
_ if !self.payload.is_empty() => false,
|
||||
Control::Syn | Control::Fin | Control::Rst => false,
|
||||
Control::None | Control::Psh => true
|
||||
Control::Syn | Control::Fin | Control::Rst => false,
|
||||
Control::None | Control::Psh => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -932,15 +990,28 @@ impl<'a> Repr<'a> {
|
|||
impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// Cannot use Repr::parse because we don't have the IP addresses.
|
||||
write!(f, "TCP src={} dst={}",
|
||||
self.src_port(), self.dst_port())?;
|
||||
if self.syn() { write!(f, " syn")? }
|
||||
if self.fin() { write!(f, " fin")? }
|
||||
if self.rst() { write!(f, " rst")? }
|
||||
if self.psh() { write!(f, " psh")? }
|
||||
if self.ece() { write!(f, " ece")? }
|
||||
if self.cwr() { write!(f, " cwr")? }
|
||||
if self.ns() { write!(f, " ns" )? }
|
||||
write!(f, "TCP src={} dst={}", self.src_port(), self.dst_port())?;
|
||||
if self.syn() {
|
||||
write!(f, " syn")?
|
||||
}
|
||||
if self.fin() {
|
||||
write!(f, " fin")?
|
||||
}
|
||||
if self.rst() {
|
||||
write!(f, " rst")?
|
||||
}
|
||||
if self.psh() {
|
||||
write!(f, " psh")?
|
||||
}
|
||||
if self.ece() {
|
||||
write!(f, " ece")?
|
||||
}
|
||||
if self.cwr() {
|
||||
write!(f, " cwr")?
|
||||
}
|
||||
if self.ns() {
|
||||
write!(f, " ns")?
|
||||
}
|
||||
write!(f, " seq={}", self.seq_number())?;
|
||||
if self.ack() {
|
||||
write!(f, " ack={}", self.ack_number())?;
|
||||
|
@ -953,24 +1024,18 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
|||
|
||||
let mut options = self.options();
|
||||
while !options.is_empty() {
|
||||
let (next_options, option) =
|
||||
match TcpOption::parse(options) {
|
||||
Ok(res) => res,
|
||||
Err(err) => return write!(f, " ({})", err)
|
||||
};
|
||||
let (next_options, option) = match TcpOption::parse(options) {
|
||||
Ok(res) => res,
|
||||
Err(err) => return write!(f, " ({})", err),
|
||||
};
|
||||
match option {
|
||||
TcpOption::EndOfList => break,
|
||||
TcpOption::NoOperation => (),
|
||||
TcpOption::MaxSegmentSize(value) =>
|
||||
write!(f, " mss={}", value)?,
|
||||
TcpOption::WindowScale(value) =>
|
||||
write!(f, " ws={}", value)?,
|
||||
TcpOption::SackPermitted =>
|
||||
write!(f, " sACK")?,
|
||||
TcpOption::SackRange(slice) =>
|
||||
write!(f, " sACKr{:?}", slice)?, // debug print conveniently includes the []s
|
||||
TcpOption::Unknown { kind, .. } =>
|
||||
write!(f, " opt({})", kind)?,
|
||||
TcpOption::MaxSegmentSize(value) => write!(f, " mss={}", value)?,
|
||||
TcpOption::WindowScale(value) => write!(f, " ws={}", value)?,
|
||||
TcpOption::SackPermitted => write!(f, " sACK")?,
|
||||
TcpOption::SackRange(slice) => write!(f, " sACKr{:?}", slice)?, // debug print conveniently includes the []s
|
||||
TcpOption::Unknown { kind, .. } => write!(f, " opt({})", kind)?,
|
||||
}
|
||||
options = next_options;
|
||||
}
|
||||
|
@ -980,14 +1045,13 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
|||
|
||||
impl<'a> fmt::Display for Repr<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TCP src={} dst={}",
|
||||
self.src_port, self.dst_port)?;
|
||||
write!(f, "TCP src={} dst={}", self.src_port, self.dst_port)?;
|
||||
match self.control {
|
||||
Control::Syn => write!(f, " syn")?,
|
||||
Control::Fin => write!(f, " fin")?,
|
||||
Control::Rst => write!(f, " rst")?,
|
||||
Control::Psh => write!(f, " psh")?,
|
||||
Control::None => ()
|
||||
Control::None => (),
|
||||
}
|
||||
write!(f, " seq={}", self.seq_number)?;
|
||||
if let Some(ack_number) = self.ack_number {
|
||||
|
@ -1002,23 +1066,26 @@ impl<'a> fmt::Display for Repr<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
match Packet::new_checked(buffer) {
|
||||
Err(err) => write!(f, "{}({})", indent, err),
|
||||
Ok(packet) => write!(f, "{}{}", indent, packet)
|
||||
Err(err) => write!(f, "{}({})", indent, err),
|
||||
Ok(packet) => write!(f, "{}{}", indent, packet),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::Ipv4Address;
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
const SRC_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 1]);
|
||||
|
@ -1026,22 +1093,16 @@ mod test {
|
|||
const DST_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 2]);
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static PACKET_BYTES: [u8; 28] =
|
||||
[0xbf, 0x00, 0x00, 0x50,
|
||||
0x01, 0x23, 0x45, 0x67,
|
||||
0x89, 0xab, 0xcd, 0xef,
|
||||
0x60, 0x35, 0x01, 0x23,
|
||||
0x01, 0xb6, 0x02, 0x01,
|
||||
0x03, 0x03, 0x0c, 0x01,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static PACKET_BYTES: [u8; 28] = [
|
||||
0xbf, 0x00, 0x00, 0x50, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x60, 0x35, 0x01,
|
||||
0x23, 0x01, 0xb6, 0x02, 0x01, 0x03, 0x03, 0x0c, 0x01, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static OPTION_BYTES: [u8; 4] =
|
||||
[0x03, 0x03, 0x0c, 0x01];
|
||||
static OPTION_BYTES: [u8; 4] = [0x03, 0x03, 0x0c, 0x01];
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static PAYLOAD_BYTES: [u8; 4] =
|
||||
[0xaa, 0x00, 0x00, 0xff];
|
||||
static PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
|
@ -1052,18 +1113,18 @@ mod test {
|
|||
assert_eq!(packet.seq_number(), SeqNumber(0x01234567));
|
||||
assert_eq!(packet.ack_number(), SeqNumber(0x89abcdefu32 as i32));
|
||||
assert_eq!(packet.header_len(), 24);
|
||||
assert_eq!(packet.fin(), true);
|
||||
assert_eq!(packet.syn(), false);
|
||||
assert_eq!(packet.rst(), true);
|
||||
assert_eq!(packet.psh(), false);
|
||||
assert_eq!(packet.ack(), true);
|
||||
assert_eq!(packet.urg(), true);
|
||||
assert!(packet.fin());
|
||||
assert!(!packet.syn());
|
||||
assert!(packet.rst());
|
||||
assert!(!packet.psh());
|
||||
assert!(packet.ack());
|
||||
assert!(packet.urg());
|
||||
assert_eq!(packet.window_len(), 0x0123);
|
||||
assert_eq!(packet.urgent_at(), 0x0201);
|
||||
assert_eq!(packet.checksum(), 0x01b6);
|
||||
assert_eq!(packet.options(), &OPTION_BYTES[..]);
|
||||
assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]);
|
||||
assert_eq!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()), true);
|
||||
assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1108,28 +1169,25 @@ mod test {
|
|||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static SYN_PACKET_BYTES: [u8; 24] =
|
||||
[0xbf, 0x00, 0x00, 0x50,
|
||||
0x01, 0x23, 0x45, 0x67,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x50, 0x02, 0x01, 0x23,
|
||||
0x7a, 0x8d, 0x00, 0x00,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static SYN_PACKET_BYTES: [u8; 24] = [
|
||||
0xbf, 0x00, 0x00, 0x50, 0x01, 0x23, 0x45, 0x67, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x01,
|
||||
0x23, 0x7a, 0x8d, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
fn packet_repr() -> Repr<'static> {
|
||||
Repr {
|
||||
src_port: 48896,
|
||||
dst_port: 80,
|
||||
seq_number: SeqNumber(0x01234567),
|
||||
ack_number: None,
|
||||
window_len: 0x0123,
|
||||
src_port: 48896,
|
||||
dst_port: 80,
|
||||
seq_number: SeqNumber(0x01234567),
|
||||
ack_number: None,
|
||||
window_len: 0x0123,
|
||||
window_scale: None,
|
||||
control: Control::Syn,
|
||||
control: Control::Syn,
|
||||
max_seg_size: None,
|
||||
sack_permitted: false,
|
||||
sack_ranges: [None, None, None],
|
||||
payload: &PAYLOAD_BYTES
|
||||
sack_ranges: [None, None, None],
|
||||
payload: &PAYLOAD_BYTES,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1137,7 +1195,13 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_parse() {
|
||||
let packet = Packet::new_unchecked(&SYN_PACKET_BYTES[..]);
|
||||
let repr = Repr::parse(&packet, &SRC_ADDR.into(), &DST_ADDR.into(), &ChecksumCapabilities::default()).unwrap();
|
||||
let repr = Repr::parse(
|
||||
&packet,
|
||||
&SRC_ADDR.into(),
|
||||
&DST_ADDR.into(),
|
||||
&ChecksumCapabilities::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(repr, packet_repr());
|
||||
}
|
||||
|
||||
|
@ -1147,7 +1211,12 @@ mod test {
|
|||
let repr = packet_repr();
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut packet, &SRC_ADDR.into(), &DST_ADDR.into(), &ChecksumCapabilities::default());
|
||||
repr.emit(
|
||||
&mut packet,
|
||||
&SRC_ADDR.into(),
|
||||
&DST_ADDR.into(),
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &SYN_PACKET_BYTES[..]);
|
||||
}
|
||||
|
||||
|
@ -1160,57 +1229,62 @@ mod test {
|
|||
}
|
||||
|
||||
macro_rules! assert_option_parses {
|
||||
($opt:expr, $data:expr) => ({
|
||||
($opt:expr, $data:expr) => {{
|
||||
assert_eq!(TcpOption::parse($data), Ok((&[][..], $opt)));
|
||||
let buffer = &mut [0; 40][..$opt.buffer_len()];
|
||||
assert_eq!($opt.emit(buffer), &mut []);
|
||||
assert_eq!(&*buffer, $data);
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tcp_options() {
|
||||
assert_option_parses!(TcpOption::EndOfList,
|
||||
&[0x00]);
|
||||
assert_option_parses!(TcpOption::NoOperation,
|
||||
&[0x01]);
|
||||
assert_option_parses!(TcpOption::MaxSegmentSize(1500),
|
||||
&[0x02, 0x04, 0x05, 0xdc]);
|
||||
assert_option_parses!(TcpOption::WindowScale(12),
|
||||
&[0x03, 0x03, 0x0c]);
|
||||
assert_option_parses!(TcpOption::SackPermitted,
|
||||
&[0x4, 0x02]);
|
||||
assert_option_parses!(TcpOption::SackRange([Some((500, 1500)), None, None]),
|
||||
&[0x05, 0x0a,
|
||||
0x00, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x05, 0xdc]);
|
||||
assert_option_parses!(TcpOption::SackRange([Some((875, 1225)), Some((1500, 2500)), None]),
|
||||
&[0x05, 0x12,
|
||||
0x00, 0x00, 0x03, 0x6b, 0x00, 0x00, 0x04, 0xc9,
|
||||
0x00, 0x00, 0x05, 0xdc, 0x00, 0x00, 0x09, 0xc4]);
|
||||
assert_option_parses!(TcpOption::SackRange([Some((875000, 1225000)),
|
||||
Some((1500000, 2500000)),
|
||||
Some((876543210, 876654320))]),
|
||||
&[0x05, 0x1a,
|
||||
0x00, 0x0d, 0x59, 0xf8, 0x00, 0x12, 0xb1, 0x28,
|
||||
0x00, 0x16, 0xe3, 0x60, 0x00, 0x26, 0x25, 0xa0,
|
||||
0x34, 0x3e, 0xfc, 0xea, 0x34, 0x40, 0xae, 0xf0]);
|
||||
assert_option_parses!(TcpOption::Unknown { kind: 12, data: &[1, 2, 3][..] },
|
||||
&[0x0c, 0x05, 0x01, 0x02, 0x03])
|
||||
assert_option_parses!(TcpOption::EndOfList, &[0x00]);
|
||||
assert_option_parses!(TcpOption::NoOperation, &[0x01]);
|
||||
assert_option_parses!(TcpOption::MaxSegmentSize(1500), &[0x02, 0x04, 0x05, 0xdc]);
|
||||
assert_option_parses!(TcpOption::WindowScale(12), &[0x03, 0x03, 0x0c]);
|
||||
assert_option_parses!(TcpOption::SackPermitted, &[0x4, 0x02]);
|
||||
assert_option_parses!(
|
||||
TcpOption::SackRange([Some((500, 1500)), None, None]),
|
||||
&[0x05, 0x0a, 0x00, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x05, 0xdc]
|
||||
);
|
||||
assert_option_parses!(
|
||||
TcpOption::SackRange([Some((875, 1225)), Some((1500, 2500)), None]),
|
||||
&[
|
||||
0x05, 0x12, 0x00, 0x00, 0x03, 0x6b, 0x00, 0x00, 0x04, 0xc9, 0x00, 0x00, 0x05, 0xdc,
|
||||
0x00, 0x00, 0x09, 0xc4
|
||||
]
|
||||
);
|
||||
assert_option_parses!(
|
||||
TcpOption::SackRange([
|
||||
Some((875000, 1225000)),
|
||||
Some((1500000, 2500000)),
|
||||
Some((876543210, 876654320))
|
||||
]),
|
||||
&[
|
||||
0x05, 0x1a, 0x00, 0x0d, 0x59, 0xf8, 0x00, 0x12, 0xb1, 0x28, 0x00, 0x16, 0xe3, 0x60,
|
||||
0x00, 0x26, 0x25, 0xa0, 0x34, 0x3e, 0xfc, 0xea, 0x34, 0x40, 0xae, 0xf0
|
||||
]
|
||||
);
|
||||
assert_option_parses!(
|
||||
TcpOption::Unknown {
|
||||
kind: 12,
|
||||
data: &[1, 2, 3][..]
|
||||
},
|
||||
&[0x0c, 0x05, 0x01, 0x02, 0x03]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_malformed_tcp_options() {
|
||||
assert_eq!(TcpOption::parse(&[]),
|
||||
Err(Error::Truncated));
|
||||
assert_eq!(TcpOption::parse(&[0xc]),
|
||||
Err(Error::Truncated));
|
||||
assert_eq!(TcpOption::parse(&[0xc, 0x05, 0x01, 0x02]),
|
||||
Err(Error::Truncated));
|
||||
assert_eq!(TcpOption::parse(&[0xc, 0x01]),
|
||||
Err(Error::Truncated));
|
||||
assert_eq!(TcpOption::parse(&[0x2, 0x02]),
|
||||
Err(Error::Malformed));
|
||||
assert_eq!(TcpOption::parse(&[0x3, 0x02]),
|
||||
Err(Error::Malformed));
|
||||
assert_eq!(TcpOption::parse(&[]), Err(Error::Truncated));
|
||||
assert_eq!(TcpOption::parse(&[0xc]), Err(Error::Truncated));
|
||||
assert_eq!(
|
||||
TcpOption::parse(&[0xc, 0x05, 0x01, 0x02]),
|
||||
Err(Error::Truncated)
|
||||
);
|
||||
assert_eq!(TcpOption::parse(&[0xc, 0x01]), Err(Error::Truncated));
|
||||
assert_eq!(TcpOption::parse(&[0x2, 0x02]), Err(Error::Malformed));
|
||||
assert_eq!(TcpOption::parse(&[0x3, 0x02]), Err(Error::Malformed));
|
||||
}
|
||||
}
|
||||
|
|
168
src/wire/udp.rs
168
src/wire/udp.rs
|
@ -1,15 +1,16 @@
|
|||
use core::fmt;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use core::fmt;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::phy::ChecksumCapabilities;
|
||||
use crate::wire::{IpProtocol, IpAddress};
|
||||
use crate::wire::ip::checksum;
|
||||
use crate::wire::{IpAddress, IpProtocol};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// A read/write wrapper around an User Datagram Protocol packet buffer.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Packet<T: AsRef<[u8]>> {
|
||||
buffer: T
|
||||
buffer: T,
|
||||
}
|
||||
|
||||
mod field {
|
||||
|
@ -19,7 +20,7 @@ mod field {
|
|||
|
||||
pub const SRC_PORT: Field = 0..2;
|
||||
pub const DST_PORT: Field = 2..4;
|
||||
pub const LENGTH: Field = 4..6;
|
||||
pub const LENGTH: Field = 4..6;
|
||||
pub const CHECKSUM: Field = 6..8;
|
||||
|
||||
pub fn PAYLOAD(length: u16) -> Field {
|
||||
|
@ -27,6 +28,8 @@ mod field {
|
|||
}
|
||||
}
|
||||
|
||||
pub const HEADER_LEN: usize = field::CHECKSUM.end;
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
impl<T: AsRef<[u8]>> Packet<T> {
|
||||
/// Imbue a raw octet buffer with UDP packet structure.
|
||||
|
@ -54,13 +57,13 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
/// [set_len]: #method.set_len
|
||||
pub fn check_len(&self) -> Result<()> {
|
||||
let buffer_len = self.buffer.as_ref().len();
|
||||
if buffer_len < field::CHECKSUM.end {
|
||||
if buffer_len < HEADER_LEN {
|
||||
Err(Error::Truncated)
|
||||
} else {
|
||||
let field_len = self.len() as usize;
|
||||
if buffer_len < field_len {
|
||||
Err(Error::Truncated)
|
||||
} else if field_len < field::CHECKSUM.end {
|
||||
} else if field_len < HEADER_LEN {
|
||||
Err(Error::Malformed)
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -110,13 +113,14 @@ impl<T: AsRef<[u8]>> Packet<T> {
|
|||
/// # Fuzzing
|
||||
/// This function always returns `true` when fuzzing.
|
||||
pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
|
||||
if cfg!(fuzzing) { return true }
|
||||
if cfg!(fuzzing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let data = self.buffer.as_ref();
|
||||
checksum::combine(&[
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp,
|
||||
self.len() as u32),
|
||||
checksum::data(&data[..self.len() as usize])
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32),
|
||||
checksum::data(&data[..self.len() as usize]),
|
||||
]) == !0
|
||||
}
|
||||
}
|
||||
|
@ -170,9 +174,8 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|||
let checksum = {
|
||||
let data = self.buffer.as_ref();
|
||||
!checksum::combine(&[
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp,
|
||||
self.len() as u32),
|
||||
checksum::data(&data[..self.len() as usize])
|
||||
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32),
|
||||
checksum::data(&data[..self.len() as usize]),
|
||||
])
|
||||
};
|
||||
// UDP checksum value of 0 means no checksum; if the checksum really is zero,
|
||||
|
@ -199,54 +202,64 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
|
|||
|
||||
/// A high-level representation of an User Datagram Protocol packet.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Repr<'a> {
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Repr {
|
||||
pub src_port: u16,
|
||||
pub dst_port: u16,
|
||||
pub payload: &'a [u8]
|
||||
}
|
||||
|
||||
impl<'a> Repr<'a> {
|
||||
impl Repr {
|
||||
/// Parse an User Datagram Protocol packet and return a high-level representation.
|
||||
pub fn parse<T>(packet: &Packet<&'a T>, src_addr: &IpAddress, dst_addr: &IpAddress,
|
||||
checksum_caps: &ChecksumCapabilities) -> Result<Repr<'a>>
|
||||
where T: AsRef<[u8]> + ?Sized {
|
||||
pub fn parse<T>(
|
||||
packet: &Packet<&T>,
|
||||
src_addr: &IpAddress,
|
||||
dst_addr: &IpAddress,
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) -> Result<Repr>
|
||||
where
|
||||
T: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
// Destination port cannot be omitted (but source port can be).
|
||||
if packet.dst_port() == 0 { return Err(Error::Malformed) }
|
||||
if packet.dst_port() == 0 {
|
||||
return Err(Error::Malformed);
|
||||
}
|
||||
// Valid checksum is expected...
|
||||
if checksum_caps.udp.rx() && !packet.verify_checksum(src_addr, dst_addr) {
|
||||
match (src_addr, dst_addr) {
|
||||
// ... except on UDP-over-IPv4, where it can be omitted.
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
(&IpAddress::Ipv4(_), &IpAddress::Ipv4(_))
|
||||
if packet.checksum() == 0 => (),
|
||||
_ => {
|
||||
return Err(Error::Checksum)
|
||||
}
|
||||
(&IpAddress::Ipv4(_), &IpAddress::Ipv4(_)) if packet.checksum() == 0 => (),
|
||||
_ => return Err(Error::Checksum),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Repr {
|
||||
src_port: packet.src_port(),
|
||||
dst_port: packet.dst_port(),
|
||||
payload: packet.payload()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the length of a packet that will be emitted from this high-level representation.
|
||||
pub fn buffer_len(&self) -> usize {
|
||||
field::CHECKSUM.end + self.payload.len()
|
||||
/// Return the length of the packet header that will be emitted from this high-level representation.
|
||||
pub fn header_len(&self) -> usize {
|
||||
HEADER_LEN
|
||||
}
|
||||
|
||||
/// Emit a high-level representation into an User Datagram Protocol packet.
|
||||
pub fn emit<T: ?Sized>(&self, packet: &mut Packet<&mut T>,
|
||||
src_addr: &IpAddress,
|
||||
dst_addr: &IpAddress,
|
||||
checksum_caps: &ChecksumCapabilities)
|
||||
where T: AsRef<[u8]> + AsMut<[u8]> {
|
||||
pub fn emit<T: ?Sized>(
|
||||
&self,
|
||||
packet: &mut Packet<&mut T>,
|
||||
src_addr: &IpAddress,
|
||||
dst_addr: &IpAddress,
|
||||
payload_len: usize,
|
||||
emit_payload: impl FnOnce(&mut [u8]),
|
||||
checksum_caps: &ChecksumCapabilities,
|
||||
) where
|
||||
T: AsRef<[u8]> + AsMut<[u8]>,
|
||||
{
|
||||
packet.set_src_port(self.src_port);
|
||||
packet.set_dst_port(self.dst_port);
|
||||
packet.set_len((field::CHECKSUM.end + self.payload.len()) as u16);
|
||||
packet.payload_mut().copy_from_slice(self.payload);
|
||||
packet.set_len((HEADER_LEN + payload_len) as u16);
|
||||
emit_payload(packet.payload_mut());
|
||||
|
||||
if checksum_caps.udp.tx() {
|
||||
packet.fill_checksum(src_addr, dst_addr)
|
||||
|
@ -261,35 +274,42 @@ impl<'a> Repr<'a> {
|
|||
impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// Cannot use Repr::parse because we don't have the IP addresses.
|
||||
write!(f, "UDP src={} dst={} len={}",
|
||||
self.src_port(), self.dst_port(), self.payload().len())
|
||||
write!(
|
||||
f,
|
||||
"UDP src={} dst={} len={}",
|
||||
self.src_port(),
|
||||
self.dst_port(),
|
||||
self.payload().len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Repr<'a> {
|
||||
impl fmt::Display for Repr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UDP src={} dst={} len={}",
|
||||
self.src_port, self.dst_port, self.payload.len())
|
||||
write!(f, "UDP src={} dst={}", self.src_port, self.dst_port)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::wire::pretty_print::{PrettyPrint, PrettyIndent};
|
||||
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
|
||||
|
||||
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
||||
fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent) -> fmt::Result {
|
||||
fn pretty_print(
|
||||
buffer: &dyn AsRef<[u8]>,
|
||||
f: &mut fmt::Formatter,
|
||||
indent: &mut PrettyIndent,
|
||||
) -> fmt::Result {
|
||||
match Packet::new_checked(buffer) {
|
||||
Err(err) => write!(f, "{}({})", indent, err),
|
||||
Ok(packet) => write!(f, "{}{}", indent, packet)
|
||||
Err(err) => write!(f, "{}({})", indent, err),
|
||||
Ok(packet) => write!(f, "{}{}", indent, packet),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
use crate::wire::Ipv4Address;
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
const SRC_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 1]);
|
||||
|
@ -297,20 +317,17 @@ mod test {
|
|||
const DST_ADDR: Ipv4Address = Ipv4Address([192, 168, 1, 2]);
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static PACKET_BYTES: [u8; 12] =
|
||||
[0xbf, 0x00, 0x00, 0x35,
|
||||
0x00, 0x0c, 0x12, 0x4d,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static PACKET_BYTES: [u8; 12] = [
|
||||
0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static NO_CHECKSUM_PACKET: [u8; 12] =
|
||||
[0xbf, 0x00, 0x00, 0x35,
|
||||
0x00, 0x0c, 0x00, 0x00,
|
||||
0xaa, 0x00, 0x00, 0xff];
|
||||
static NO_CHECKSUM_PACKET: [u8; 12] = [
|
||||
0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xff,
|
||||
];
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static PAYLOAD_BYTES: [u8; 4] =
|
||||
[0xaa, 0x00, 0x00, 0xff];
|
||||
static PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
|
@ -321,7 +338,7 @@ mod test {
|
|||
assert_eq!(packet.len(), 12);
|
||||
assert_eq!(packet.checksum(), 0x124d);
|
||||
assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]);
|
||||
assert_eq!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()), true);
|
||||
assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -359,11 +376,10 @@ mod test {
|
|||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
fn packet_repr() -> Repr<'static> {
|
||||
fn packet_repr() -> Repr {
|
||||
Repr {
|
||||
src_port: 48896,
|
||||
dst_port: 53,
|
||||
payload: &PAYLOAD_BYTES
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,8 +387,13 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_parse() {
|
||||
let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
|
||||
let repr = Repr::parse(&packet, &SRC_ADDR.into(), &DST_ADDR.into(),
|
||||
&ChecksumCapabilities::default()).unwrap();
|
||||
let repr = Repr::parse(
|
||||
&packet,
|
||||
&SRC_ADDR.into(),
|
||||
&DST_ADDR.into(),
|
||||
&ChecksumCapabilities::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(repr, packet_repr());
|
||||
}
|
||||
|
||||
|
@ -380,10 +401,16 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_emit() {
|
||||
let repr = packet_repr();
|
||||
let mut bytes = vec![0xa5; repr.buffer_len()];
|
||||
let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()];
|
||||
let mut packet = Packet::new_unchecked(&mut bytes);
|
||||
repr.emit(&mut packet, &SRC_ADDR.into(), &DST_ADDR.into(),
|
||||
&ChecksumCapabilities::default());
|
||||
repr.emit(
|
||||
&mut packet,
|
||||
&SRC_ADDR.into(),
|
||||
&DST_ADDR.into(),
|
||||
PAYLOAD_BYTES.len(),
|
||||
|payload| payload.copy_from_slice(&PAYLOAD_BYTES),
|
||||
&ChecksumCapabilities::default(),
|
||||
);
|
||||
assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]);
|
||||
}
|
||||
|
||||
|
@ -391,8 +418,13 @@ mod test {
|
|||
#[cfg(feature = "proto-ipv4")]
|
||||
fn test_checksum_omitted() {
|
||||
let packet = Packet::new_unchecked(&NO_CHECKSUM_PACKET[..]);
|
||||
let repr = Repr::parse(&packet, &SRC_ADDR.into(), &DST_ADDR.into(),
|
||||
&ChecksumCapabilities::default()).unwrap();
|
||||
let repr = Repr::parse(
|
||||
&packet,
|
||||
&SRC_ADDR.into(),
|
||||
&DST_ADDR.into(),
|
||||
&ChecksumCapabilities::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(repr, packet_repr());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
use std::cell::RefCell;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::env;
|
||||
use std::process::exit;
|
||||
use getopts::Options;
|
||||
use smoltcp::phy::{PcapLinkType, PcapSink};
|
||||
use smoltcp::time::Instant;
|
||||
use getopts::Options;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
fn convert(packet_filename: &Path, pcap_filename: &Path, link_type: PcapLinkType)
|
||||
-> io::Result<()> {
|
||||
fn convert(
|
||||
packet_filename: &Path,
|
||||
pcap_filename: &Path,
|
||||
link_type: PcapLinkType,
|
||||
) -> io::Result<()> {
|
||||
let mut packet_file = File::open(packet_filename)?;
|
||||
let mut packet = Vec::new();
|
||||
packet_file.read_to_end(&mut packet)?;
|
||||
|
||||
let pcap = RefCell::new(Vec::new());
|
||||
PcapSink::global_header(&pcap, link_type);
|
||||
PcapSink::packet(&pcap, Instant::from_millis(0), &packet[..]);
|
||||
|
||||
let mut pcap_file = File::create(pcap_filename)?;
|
||||
pcap_file.write_all(&pcap.borrow()[..])?;
|
||||
PcapSink::global_header(&mut pcap_file, link_type);
|
||||
PcapSink::packet(&mut pcap_file, Instant::from_millis(0), &packet[..]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -35,31 +34,37 @@ fn main() {
|
|||
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("h", "help", "print this help menu");
|
||||
opts.optopt("t", "link-type", "set link type (one of: ethernet ip)", "TYPE");
|
||||
opts.optopt(
|
||||
"t",
|
||||
"link-type",
|
||||
"set link type (one of: ethernet ip)",
|
||||
"TYPE",
|
||||
);
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
return
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let link_type =
|
||||
match matches.opt_str("t").as_ref().map(|s| &s[..]) {
|
||||
Some("ethernet") => Some(PcapLinkType::Ethernet),
|
||||
Some("ip") => Some(PcapLinkType::Ip),
|
||||
_ => None
|
||||
};
|
||||
let link_type = match matches.opt_str("t").as_ref().map(|s| &s[..]) {
|
||||
Some("ethernet") => Some(PcapLinkType::Ethernet),
|
||||
Some("ip") => Some(PcapLinkType::Ip),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if matches.opt_present("h") || matches.free.len() != 2 || link_type.is_none() {
|
||||
print_usage(&program, opts);
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
match convert(Path::new(&matches.free[0]),
|
||||
Path::new(&matches.free[1]),
|
||||
link_type.unwrap()) {
|
||||
match convert(
|
||||
Path::new(&matches.free[0]),
|
||||
Path::new(&matches.free[1]),
|
||||
link_type.unwrap(),
|
||||
) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
eprintln!("Cannot convert packet to pcap: {}", e);
|
||||
|
|
Loading…
Reference in New Issue