├── .github ├── codecov.yml └── workflows │ ├── coverage.yml │ ├── fuzz.yml │ ├── matrix-bot.yml │ ├── rustfmt.yaml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-0BSD.txt ├── README.md ├── benches └── bench.rs ├── build.rs ├── ci.sh ├── examples ├── benchmark.rs ├── client.rs ├── dhcp_client.rs ├── dns.rs ├── httpclient.rs ├── loopback.rs ├── loopback_benchmark.rs ├── multicast.rs ├── multicast6.rs ├── ping.rs ├── server.rs ├── sixlowpan.rs ├── sixlowpan_benchmark.rs ├── tcpdump.rs └── utils.rs ├── fuzz ├── .gitignore ├── Cargo.toml ├── corpus │ └── packet_parser │ │ ├── arp.bin │ │ ├── icmpv4_reply.bin │ │ ├── icmpv4_request.bin │ │ ├── icmpv4_unreachable.bin │ │ ├── icmpv6_nbr_solicitation.bin │ │ ├── tcpv4_data.bin │ │ ├── tcpv4_fin.bin │ │ ├── tcpv4_rst.bin │ │ ├── tcpv4_syn.bin │ │ └── udpv4.bin ├── fuzz_targets │ ├── dhcp_header.rs │ ├── ieee802154_header.rs │ ├── packet_parser.rs │ ├── sixlowpan_packet.rs │ └── tcp_headers.rs └── utils.rs ├── gen_config.py ├── src ├── iface │ ├── fragmentation.rs │ ├── interface │ │ ├── ethernet.rs │ │ ├── ieee802154.rs │ │ ├── ipv4.rs │ │ ├── ipv6.rs │ │ ├── mod.rs │ │ ├── multicast.rs │ │ ├── sixlowpan.rs │ │ ├── tcp.rs │ │ ├── tests │ │ │ ├── ipv4.rs │ │ │ ├── ipv6.rs │ │ │ ├── mod.rs │ │ │ └── sixlowpan.rs │ │ └── udp.rs │ ├── mod.rs │ ├── neighbor.rs │ ├── packet.rs │ ├── route.rs │ ├── rpl │ │ ├── consts.rs │ │ ├── lollipop.rs │ │ ├── mod.rs │ │ ├── of0.rs │ │ ├── parents.rs │ │ ├── rank.rs │ │ ├── relations.rs │ │ └── trickle.rs │ ├── socket_meta.rs │ └── socket_set.rs ├── lib.rs ├── macros.rs ├── parsers.rs ├── phy │ ├── fault_injector.rs │ ├── fuzz_injector.rs │ ├── loopback.rs │ ├── mod.rs │ ├── pcap_writer.rs │ ├── raw_socket.rs │ ├── sys │ │ ├── bpf.rs │ │ ├── linux.rs │ │ ├── mod.rs │ │ ├── raw_socket.rs │ │ └── tuntap_interface.rs │ ├── tracer.rs │ └── tuntap_interface.rs ├── rand.rs ├── socket │ ├── dhcpv4.rs │ ├── dns.rs │ ├── icmp.rs │ ├── mod.rs │ ├── raw.rs │ ├── tcp.rs │ ├── tcp │ │ ├── congestion.rs │ │ └── congestion │ │ │ ├── cubic.rs │ │ │ ├── no_control.rs │ │ │ └── reno.rs │ ├── udp.rs │ └── waker.rs ├── storage │ ├── assembler.rs │ ├── mod.rs │ ├── packet_buffer.rs │ └── ring_buffer.rs ├── tests.rs ├── time.rs └── wire │ ├── arp.rs │ ├── dhcpv4.rs │ ├── dns.rs │ ├── ethernet.rs │ ├── icmp.rs │ ├── icmpv4.rs │ ├── icmpv6.rs │ ├── ieee802154.rs │ ├── igmp.rs │ ├── ip.rs │ ├── ipsec_ah.rs │ ├── ipsec_esp.rs │ ├── ipv4.rs │ ├── ipv6.rs │ ├── ipv6ext_header.rs │ ├── ipv6fragment.rs │ ├── ipv6hbh.rs │ ├── ipv6option.rs │ ├── ipv6routing.rs │ ├── mld.rs │ ├── mod.rs │ ├── ndisc.rs │ ├── ndiscoption.rs │ ├── pretty_print.rs │ ├── rpl.rs │ ├── sixlowpan │ ├── frag.rs │ ├── iphc.rs │ ├── mod.rs │ └── nhc.rs │ ├── tcp.rs │ └── udp.rs ├── tests ├── netsim.rs └── snapshots │ └── netsim__netsim.snap └── utils └── packet2pcap.rs /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | github_checks: 2 | annotations: false 3 | 4 | coverage: 5 | status: 6 | project: 7 | default: 8 | informational: true 9 | patch: 10 | default: 11 | informational: true 12 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | merge_group: 4 | 5 | name: Coverage 6 | 7 | jobs: 8 | coverage: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Install `cargo llvm-cov` 13 | uses: taiki-e/install-action@cargo-llvm-cov 14 | - name: Run Coverage 15 | run: ./ci.sh coverage 16 | - name: Upload coverage to Codecov 17 | uses: codecov/codecov-action@v5 18 | with: 19 | token: ${{ secrets.CODECOV_TOKEN }} 20 | files: lcov.info 21 | fail_ci_if_error: false 22 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | merge_group: 4 | 5 | name: Fuzz 6 | 7 | jobs: 8 | fuzz: 9 | runs-on: ubuntu-22.04 10 | env: 11 | RUSTUP_TOOLCHAIN: nightly 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install cargo-fuzz 15 | run: cargo install cargo-fuzz 16 | - name: Fuzz 17 | run: cargo fuzz run packet_parser -- -max_len=1536 -max_total_time=30 18 | -------------------------------------------------------------------------------- /.github/workflows/matrix-bot.yml: -------------------------------------------------------------------------------- 1 | name: Matrix bot 2 | on: 3 | pull_request_target: 4 | types: [opened, closed] 5 | 6 | jobs: 7 | new-pr: 8 | if: github.event.action == 'opened' && github.repository == 'smoltcp-rs/smoltcp' 9 | runs-on: ubuntu-latest 10 | continue-on-error: true 11 | steps: 12 | - name: send message 13 | uses: s3krit/matrix-message-action@v0.0.3 14 | with: 15 | room_id: ${{ secrets.MATRIX_ROOM_ID }} 16 | access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }} 17 | message: "New PR: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})" 18 | server: "matrix.org" 19 | 20 | merged-pr: 21 | if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.repository == 'smoltcp-rs/smoltcp' 22 | runs-on: ubuntu-latest 23 | continue-on-error: true 24 | steps: 25 | - name: send message 26 | uses: s3krit/matrix-message-action@v0.0.3 27 | with: 28 | room_id: ${{ secrets.MATRIX_ROOM_ID }} 29 | access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }} 30 | message: "PR merged: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})" 31 | server: "matrix.org" 32 | 33 | abandoned-pr: 34 | if: github.event.action == 'closed' && github.event.pull_request.merged == false && github.repository == 'smoltcp-rs/smoltcp' 35 | runs-on: ubuntu-latest 36 | continue-on-error: true 37 | steps: 38 | - name: send message 39 | uses: s3krit/matrix-message-action@v0.0.3 40 | with: 41 | room_id: ${{ secrets.MATRIX_ROOM_ID }} 42 | access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }} 43 | message: "PR closed without merging: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})" 44 | server: "matrix.org" 45 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | merge_group: 4 | 5 | name: Rustfmt check 6 | jobs: 7 | fmt: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Check fmt 12 | run: cargo fmt -- --check 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | merge_group: 4 | 5 | name: Test 6 | 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-22.04 10 | needs: [check-msrv, test-msrv, test-stable, clippy, test-netsim] 11 | steps: 12 | - name: Done 13 | run: exit 0 14 | 15 | check-msrv: 16 | runs-on: ubuntu-22.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Run Checks MSRV 20 | run: ./ci.sh check msrv 21 | 22 | test-msrv: 23 | runs-on: ubuntu-22.04 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Run Tests MSRV 27 | run: ./ci.sh test msrv 28 | 29 | clippy: 30 | runs-on: ubuntu-22.04 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Run Clippy 34 | run: ./ci.sh clippy 35 | 36 | test-stable: 37 | runs-on: ubuntu-22.04 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Run Tests stable 41 | run: ./ci.sh test stable 42 | 43 | test-nightly: 44 | runs-on: ubuntu-22.04 45 | continue-on-error: true 46 | steps: 47 | - uses: actions/checkout@v4 48 | - name: Run Tests nightly 49 | run: ./ci.sh test nightly 50 | 51 | test-netsim: 52 | runs-on: ubuntu-22.04 53 | continue-on-error: true 54 | steps: 55 | - uses: actions/checkout@v4 56 | - name: Run network-simulation tests 57 | run: ./ci.sh netsim 58 | 59 | test-build-16bit: 60 | runs-on: ubuntu-22.04 61 | continue-on-error: true 62 | steps: 63 | - uses: actions/checkout@v4 64 | - name: Build for target with 16 bit pointer 65 | run: ./ci.sh build_16bit 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.pcap 4 | -------------------------------------------------------------------------------- /LICENSE-0BSD.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) smoltcp contributors 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 8 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 9 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 10 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 11 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 12 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | 14 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | mod wire { 4 | use smoltcp::phy::ChecksumCapabilities; 5 | use smoltcp::wire::{IpAddress, IpProtocol}; 6 | #[cfg(feature = "proto-ipv4")] 7 | use smoltcp::wire::{Ipv4Address, Ipv4Packet, Ipv4Repr}; 8 | #[cfg(feature = "proto-ipv6")] 9 | use smoltcp::wire::{Ipv6Address, Ipv6Packet, Ipv6Repr}; 10 | use smoltcp::wire::{TcpControl, TcpPacket, TcpRepr, TcpSeqNumber}; 11 | use smoltcp::wire::{UdpPacket, UdpRepr}; 12 | 13 | extern crate test; 14 | 15 | #[cfg(feature = "proto-ipv6")] 16 | const SRC_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)); 17 | #[cfg(feature = "proto-ipv6")] 18 | const DST_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2)); 19 | 20 | #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] 21 | const SRC_ADDR: IpAddress = IpAddress::Ipv4(Ipv4Address::new(192, 168, 1, 1)); 22 | #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] 23 | const DST_ADDR: IpAddress = IpAddress::Ipv4(Ipv4Address::new(192, 168, 1, 2)); 24 | 25 | #[bench] 26 | #[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))] 27 | fn bench_emit_tcp(b: &mut test::Bencher) { 28 | static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400]; 29 | let repr = TcpRepr { 30 | src_port: 48896, 31 | dst_port: 80, 32 | control: TcpControl::Syn, 33 | seq_number: TcpSeqNumber(0x01234567), 34 | ack_number: None, 35 | window_len: 0x0123, 36 | window_scale: None, 37 | max_seg_size: None, 38 | sack_permitted: false, 39 | sack_ranges: [None, None, None], 40 | payload: &PAYLOAD_BYTES, 41 | timestamp: None, 42 | }; 43 | let mut bytes = vec![0xa5; repr.buffer_len()]; 44 | 45 | b.iter(|| { 46 | let mut packet = TcpPacket::new_unchecked(&mut bytes); 47 | repr.emit( 48 | &mut packet, 49 | &SRC_ADDR, 50 | &DST_ADDR, 51 | &ChecksumCapabilities::default(), 52 | ); 53 | }); 54 | } 55 | 56 | #[bench] 57 | #[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))] 58 | fn bench_emit_udp(b: &mut test::Bencher) { 59 | static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400]; 60 | let repr = UdpRepr { 61 | src_port: 48896, 62 | dst_port: 80, 63 | }; 64 | let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()]; 65 | 66 | b.iter(|| { 67 | let mut packet = UdpPacket::new_unchecked(&mut bytes); 68 | repr.emit( 69 | &mut packet, 70 | &SRC_ADDR, 71 | &DST_ADDR, 72 | PAYLOAD_BYTES.len(), 73 | |buf| buf.copy_from_slice(&PAYLOAD_BYTES), 74 | &ChecksumCapabilities::default(), 75 | ); 76 | }); 77 | } 78 | 79 | #[bench] 80 | #[cfg(feature = "proto-ipv4")] 81 | fn bench_emit_ipv4(b: &mut test::Bencher) { 82 | let repr = Ipv4Repr { 83 | src_addr: Ipv4Address::new(192, 168, 1, 1), 84 | dst_addr: Ipv4Address::new(192, 168, 1, 2), 85 | next_header: IpProtocol::Tcp, 86 | payload_len: 100, 87 | hop_limit: 64, 88 | }; 89 | let mut bytes = vec![0xa5; repr.buffer_len()]; 90 | 91 | b.iter(|| { 92 | let mut packet = Ipv4Packet::new_unchecked(&mut bytes); 93 | repr.emit(&mut packet, &ChecksumCapabilities::default()); 94 | }); 95 | } 96 | 97 | #[bench] 98 | #[cfg(feature = "proto-ipv6")] 99 | fn bench_emit_ipv6(b: &mut test::Bencher) { 100 | let repr = Ipv6Repr { 101 | src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), 102 | dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), 103 | next_header: IpProtocol::Tcp, 104 | payload_len: 100, 105 | hop_limit: 64, 106 | }; 107 | let mut bytes = vec![0xa5; repr.buffer_len()]; 108 | 109 | b.iter(|| { 110 | let mut packet = Ipv6Packet::new_unchecked(&mut bytes); 111 | repr.emit(&mut packet); 112 | }); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::Write; 3 | use std::path::PathBuf; 4 | use std::{env, fs}; 5 | 6 | static CONFIGS: &[(&str, usize)] = &[ 7 | // BEGIN AUTOGENERATED CONFIG FEATURES 8 | // Generated by gen_config.py. DO NOT EDIT. 9 | ("IFACE_MAX_ADDR_COUNT", 2), 10 | ("IFACE_MAX_MULTICAST_GROUP_COUNT", 4), 11 | ("IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT", 4), 12 | ("IFACE_NEIGHBOR_CACHE_COUNT", 8), 13 | ("IFACE_MAX_ROUTE_COUNT", 2), 14 | ("FRAGMENTATION_BUFFER_SIZE", 1500), 15 | ("ASSEMBLER_MAX_SEGMENT_COUNT", 4), 16 | ("REASSEMBLY_BUFFER_SIZE", 1500), 17 | ("REASSEMBLY_BUFFER_COUNT", 1), 18 | ("IPV6_HBH_MAX_OPTIONS", 4), 19 | ("DNS_MAX_RESULT_COUNT", 1), 20 | ("DNS_MAX_SERVER_COUNT", 1), 21 | ("DNS_MAX_NAME_SIZE", 255), 22 | ("RPL_RELATIONS_BUFFER_COUNT", 16), 23 | ("RPL_PARENTS_BUFFER_COUNT", 8), 24 | // END AUTOGENERATED CONFIG FEATURES 25 | ]; 26 | 27 | struct ConfigState { 28 | value: usize, 29 | seen_feature: bool, 30 | seen_env: bool, 31 | } 32 | 33 | fn main() { 34 | // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any 35 | // other file changed. 36 | println!("cargo:rerun-if-changed=build.rs"); 37 | 38 | // Rebuild if config envvar changed. 39 | for (name, _) in CONFIGS { 40 | println!("cargo:rerun-if-env-changed=SMOLTCP_{name}"); 41 | } 42 | 43 | let mut configs = HashMap::new(); 44 | for (name, default) in CONFIGS { 45 | configs.insert( 46 | *name, 47 | ConfigState { 48 | value: *default, 49 | seen_env: false, 50 | seen_feature: false, 51 | }, 52 | ); 53 | } 54 | 55 | for (var, value) in env::vars() { 56 | if let Some(name) = var.strip_prefix("SMOLTCP_") { 57 | let Some(cfg) = configs.get_mut(name) else { 58 | panic!("Unknown env var {name}") 59 | }; 60 | 61 | let Ok(value) = value.parse::() else { 62 | panic!("Invalid value for env var {name}: {value}") 63 | }; 64 | 65 | cfg.value = value; 66 | cfg.seen_env = true; 67 | } 68 | 69 | if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { 70 | if let Some(i) = feature.rfind('_') { 71 | let name = &feature[..i]; 72 | let value = &feature[i + 1..]; 73 | if let Some(cfg) = configs.get_mut(name) { 74 | let Ok(value) = value.parse::() else { 75 | panic!("Invalid value for feature {name}: {value}") 76 | }; 77 | 78 | // envvars take priority. 79 | if !cfg.seen_env { 80 | if cfg.seen_feature { 81 | panic!( 82 | "multiple values set for feature {}: {} and {}", 83 | name, cfg.value, value 84 | ); 85 | } 86 | 87 | cfg.value = value; 88 | cfg.seen_feature = true; 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | let mut data = String::new(); 96 | 97 | for (name, cfg) in &configs { 98 | writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); 99 | } 100 | 101 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 102 | let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); 103 | fs::write(out_file, data).unwrap(); 104 | } 105 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eox pipefail 4 | 5 | export DEFMT_LOG=trace 6 | 7 | MSRV="1.81.0" 8 | 9 | RUSTC_VERSIONS=( 10 | $MSRV 11 | "stable" 12 | "nightly" 13 | ) 14 | 15 | FEATURES_TEST=( 16 | "default" 17 | "std,proto-ipv4" 18 | "std,medium-ethernet,phy-raw_socket,proto-ipv6,socket-udp,socket-dns" 19 | "std,medium-ethernet,phy-tuntap_interface,proto-ipv6,socket-udp" 20 | "std,medium-ethernet,proto-ipv4,proto-ipv4-fragmentation,socket-raw,socket-dns" 21 | "std,medium-ethernet,proto-ipv4,multicast,socket-raw,socket-dns" 22 | "std,medium-ethernet,proto-ipv4,socket-udp,socket-tcp,socket-dns" 23 | "std,medium-ethernet,proto-ipv4,proto-dhcpv4,socket-udp" 24 | "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv6,multicast,proto-rpl,socket-udp,socket-dns" 25 | "std,medium-ethernet,proto-ipv6,socket-tcp" 26 | "std,medium-ethernet,medium-ip,proto-ipv4,socket-icmp,socket-tcp" 27 | "std,medium-ip,proto-ipv6,socket-icmp,socket-tcp" 28 | "std,medium-ieee802154,proto-sixlowpan,socket-udp" 29 | "std,medium-ieee802154,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp" 30 | "std,medium-ieee802154,proto-rpl,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp" 31 | "std,medium-ip,proto-ipv4,proto-ipv6,socket-tcp,socket-udp" 32 | "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv4,proto-ipv6,multicast,proto-rpl,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" 33 | "std,medium-ip,proto-ipv4,proto-ipv6,multicast,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" 34 | "std,medium-ieee802154,medium-ip,proto-ipv4,socket-raw" 35 | "std,medium-ethernet,proto-ipv4,proto-ipsec,socket-raw" 36 | ) 37 | 38 | FEATURES_TEST_NIGHTLY=( 39 | "alloc,medium-ethernet,proto-ipv4,proto-ipv6,socket-raw,socket-udp,socket-tcp,socket-icmp" 40 | ) 41 | 42 | FEATURES_CHECK=( 43 | "medium-ip,medium-ethernet,medium-ieee802154,proto-ipv6,proto-ipv6,multicast,proto-dhcpv4,proto-ipsec,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" 44 | "defmt,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,multicast,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" 45 | "defmt,alloc,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,multicast,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" 46 | "medium-ieee802154,proto-sixlowpan,socket-dns" 47 | ) 48 | 49 | test() { 50 | local version=$1 51 | rustup toolchain install $version 52 | 53 | for features in ${FEATURES_TEST[@]}; do 54 | cargo +$version test --no-default-features --features "$features" 55 | done 56 | 57 | if [[ $version == "nightly" ]]; then 58 | for features in ${FEATURES_TEST_NIGHTLY[@]}; do 59 | cargo +$version test --no-default-features --features "$features" 60 | done 61 | fi 62 | } 63 | 64 | netsim() { 65 | cargo test --release --features _netsim netsim 66 | } 67 | 68 | check() { 69 | local version=$1 70 | rustup toolchain install $version 71 | 72 | export DEFMT_LOG="trace" 73 | 74 | for features in ${FEATURES_CHECK[@]}; do 75 | cargo +$version check --no-default-features --features "$features" 76 | done 77 | 78 | cargo +$version check --examples 79 | 80 | if [[ $version == "nightly" ]]; then 81 | cargo +$version check --benches 82 | fi 83 | } 84 | 85 | clippy() { 86 | rustup toolchain install $MSRV 87 | rustup component add clippy --toolchain=$MSRV 88 | cargo +$MSRV clippy --tests --examples -- -D warnings 89 | } 90 | 91 | build_16bit() { 92 | rustup toolchain install nightly 93 | rustup +nightly component add rust-src 94 | 95 | TARGET_WITH_16BIT_POINTER=msp430-none-elf 96 | for features in ${FEATURES_CHECK[@]}; do 97 | cargo +nightly build -Z build-std=core,alloc --target $TARGET_WITH_16BIT_POINTER --no-default-features --features=$features 98 | done 99 | } 100 | 101 | coverage() { 102 | for features in ${FEATURES_TEST[@]}; do 103 | cargo llvm-cov --no-report --no-default-features --features "$features" 104 | done 105 | cargo llvm-cov report --lcov --output-path lcov.info 106 | } 107 | 108 | if [[ $1 == "test" || $1 == "all" ]]; then 109 | if [[ -n $2 ]]; then 110 | if [[ $2 == "msrv" ]]; then 111 | test $MSRV 112 | else 113 | test $2 114 | fi 115 | else 116 | for version in ${RUSTC_VERSIONS[@]}; do 117 | test $version 118 | done 119 | fi 120 | fi 121 | 122 | if [[ $1 == "check" || $1 == "all" ]]; then 123 | if [[ -n $2 ]]; then 124 | if [[ $2 == "msrv" ]]; then 125 | check $MSRV 126 | else 127 | check $2 128 | fi 129 | else 130 | for version in ${RUSTC_VERSIONS[@]}; do 131 | check $version 132 | done 133 | fi 134 | fi 135 | 136 | if [[ $1 == "clippy" || $1 == "all" ]]; then 137 | clippy 138 | fi 139 | 140 | if [[ $1 == "build_16bit" || $1 == "all" ]]; then 141 | build_16bit 142 | fi 143 | 144 | if [[ $1 == "coverage" || $1 == "all" ]]; then 145 | coverage 146 | fi 147 | 148 | if [[ $1 == "netsim" || $1 == "all" ]]; then 149 | netsim 150 | fi 151 | -------------------------------------------------------------------------------- /examples/benchmark.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use std::cmp; 4 | use std::io::{Read, Write}; 5 | use std::net::TcpStream; 6 | use std::os::unix::io::AsRawFd; 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | use std::thread; 9 | 10 | use smoltcp::iface::{Config, Interface, SocketSet}; 11 | use smoltcp::phy::{wait as phy_wait, Device, Medium}; 12 | use smoltcp::socket::tcp; 13 | use smoltcp::time::{Duration, Instant}; 14 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; 15 | 16 | const AMOUNT: usize = 1_000_000_000; 17 | 18 | enum Client { 19 | Reader, 20 | Writer, 21 | } 22 | 23 | fn client(kind: Client) { 24 | let port = match kind { 25 | Client::Reader => 1234, 26 | Client::Writer => 1235, 27 | }; 28 | let mut stream = TcpStream::connect(("192.168.69.1", port)).unwrap(); 29 | let mut buffer = vec![0; 1_000_000]; 30 | 31 | let start = Instant::now(); 32 | 33 | let mut processed = 0; 34 | while processed < AMOUNT { 35 | let length = cmp::min(buffer.len(), AMOUNT - processed); 36 | let result = match kind { 37 | Client::Reader => stream.read(&mut buffer[..length]), 38 | Client::Writer => stream.write(&buffer[..length]), 39 | }; 40 | match result { 41 | Ok(0) => break, 42 | Ok(result) => { 43 | // print!("(P:{})", result); 44 | processed += result 45 | } 46 | Err(err) => panic!("cannot process: {err}"), 47 | } 48 | } 49 | 50 | let end = Instant::now(); 51 | 52 | let elapsed = (end - start).total_millis() as f64 / 1000.0; 53 | 54 | println!("throughput: {:.3} Gbps", AMOUNT as f64 / elapsed / 0.125e9); 55 | 56 | CLIENT_DONE.store(true, Ordering::SeqCst); 57 | } 58 | 59 | static CLIENT_DONE: AtomicBool = AtomicBool::new(false); 60 | 61 | fn main() { 62 | #[cfg(feature = "log")] 63 | utils::setup_logging("info"); 64 | 65 | let (mut opts, mut free) = utils::create_options(); 66 | utils::add_tuntap_options(&mut opts, &mut free); 67 | utils::add_middleware_options(&mut opts, &mut free); 68 | free.push("MODE"); 69 | 70 | let mut matches = utils::parse_options(&opts, free); 71 | let device = utils::parse_tuntap_options(&mut matches); 72 | let fd = device.as_raw_fd(); 73 | let mut device = 74 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 75 | let mode = match matches.free[0].as_ref() { 76 | "reader" => Client::Reader, 77 | "writer" => Client::Writer, 78 | _ => panic!("invalid mode"), 79 | }; 80 | 81 | let tcp1_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); 82 | let tcp1_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); 83 | let tcp1_socket = tcp::Socket::new(tcp1_rx_buffer, tcp1_tx_buffer); 84 | 85 | let tcp2_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); 86 | let tcp2_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); 87 | let tcp2_socket = tcp::Socket::new(tcp2_rx_buffer, tcp2_tx_buffer); 88 | 89 | let mut config = match device.capabilities().medium { 90 | Medium::Ethernet => { 91 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 92 | } 93 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 94 | Medium::Ieee802154 => todo!(), 95 | }; 96 | config.random_seed = rand::random(); 97 | 98 | let mut iface = Interface::new(config, &mut device, Instant::now()); 99 | iface.update_ip_addrs(|ip_addrs| { 100 | ip_addrs 101 | .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) 102 | .unwrap(); 103 | }); 104 | 105 | let mut sockets = SocketSet::new(vec![]); 106 | let tcp1_handle = sockets.add(tcp1_socket); 107 | let tcp2_handle = sockets.add(tcp2_socket); 108 | let default_timeout = Some(Duration::from_millis(1000)); 109 | 110 | thread::spawn(move || client(mode)); 111 | let mut processed = 0; 112 | while !CLIENT_DONE.load(Ordering::SeqCst) { 113 | let timestamp = Instant::now(); 114 | iface.poll(timestamp, &mut device, &mut sockets); 115 | 116 | // tcp:1234: emit data 117 | let socket = sockets.get_mut::(tcp1_handle); 118 | if !socket.is_open() { 119 | socket.listen(1234).unwrap(); 120 | } 121 | 122 | while socket.can_send() && processed < AMOUNT { 123 | let length = socket 124 | .send(|buffer| { 125 | let length = cmp::min(buffer.len(), AMOUNT - processed); 126 | (length, length) 127 | }) 128 | .unwrap(); 129 | processed += length; 130 | } 131 | 132 | // tcp:1235: sink data 133 | let socket = sockets.get_mut::(tcp2_handle); 134 | if !socket.is_open() { 135 | socket.listen(1235).unwrap(); 136 | } 137 | 138 | while socket.can_recv() && processed < AMOUNT { 139 | let length = socket 140 | .recv(|buffer| { 141 | let length = cmp::min(buffer.len(), AMOUNT - processed); 142 | (length, length) 143 | }) 144 | .unwrap(); 145 | processed += length; 146 | } 147 | 148 | match iface.poll_at(timestamp, &sockets) { 149 | Some(poll_at) if timestamp < poll_at => { 150 | phy_wait(fd, Some(poll_at - timestamp)).expect("wait error"); 151 | } 152 | Some(_) => (), 153 | None => { 154 | phy_wait(fd, default_timeout).expect("wait error"); 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /examples/client.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use log::debug; 4 | use std::os::unix::io::AsRawFd; 5 | use std::str::{self, FromStr}; 6 | 7 | use smoltcp::iface::{Config, Interface, SocketSet}; 8 | use smoltcp::phy::{wait as phy_wait, Device, Medium}; 9 | use smoltcp::socket::tcp; 10 | use smoltcp::time::Instant; 11 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; 12 | 13 | fn main() { 14 | utils::setup_logging(""); 15 | 16 | let (mut opts, mut free) = utils::create_options(); 17 | utils::add_tuntap_options(&mut opts, &mut free); 18 | utils::add_middleware_options(&mut opts, &mut free); 19 | free.push("ADDRESS"); 20 | free.push("PORT"); 21 | 22 | let mut matches = utils::parse_options(&opts, free); 23 | let device = utils::parse_tuntap_options(&mut matches); 24 | 25 | let fd = device.as_raw_fd(); 26 | let mut device = 27 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 28 | let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format"); 29 | let port = u16::from_str(&matches.free[1]).expect("invalid port format"); 30 | 31 | // Create interface 32 | let mut config = match device.capabilities().medium { 33 | Medium::Ethernet => { 34 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 35 | } 36 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 37 | Medium::Ieee802154 => todo!(), 38 | }; 39 | config.random_seed = rand::random(); 40 | 41 | let mut iface = Interface::new(config, &mut device, Instant::now()); 42 | iface.update_ip_addrs(|ip_addrs| { 43 | ip_addrs 44 | .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) 45 | .unwrap(); 46 | ip_addrs 47 | .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) 48 | .unwrap(); 49 | ip_addrs 50 | .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) 51 | .unwrap(); 52 | }); 53 | iface 54 | .routes_mut() 55 | .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) 56 | .unwrap(); 57 | iface 58 | .routes_mut() 59 | .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) 60 | .unwrap(); 61 | 62 | // Create sockets 63 | let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); 64 | let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); 65 | let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); 66 | let mut sockets = SocketSet::new(vec![]); 67 | let tcp_handle = sockets.add(tcp_socket); 68 | 69 | let socket = sockets.get_mut::(tcp_handle); 70 | socket 71 | .connect(iface.context(), (address, port), 49500) 72 | .unwrap(); 73 | 74 | let mut tcp_active = false; 75 | loop { 76 | let timestamp = Instant::now(); 77 | iface.poll(timestamp, &mut device, &mut sockets); 78 | 79 | let socket = sockets.get_mut::(tcp_handle); 80 | if socket.is_active() && !tcp_active { 81 | debug!("connected"); 82 | } else if !socket.is_active() && tcp_active { 83 | debug!("disconnected"); 84 | break; 85 | } 86 | tcp_active = socket.is_active(); 87 | 88 | if socket.may_recv() { 89 | let data = socket 90 | .recv(|data| { 91 | let mut data = data.to_owned(); 92 | if !data.is_empty() { 93 | debug!( 94 | "recv data: {:?}", 95 | str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") 96 | ); 97 | data = data.split(|&b| b == b'\n').collect::>().concat(); 98 | data.reverse(); 99 | data.extend(b"\n"); 100 | } 101 | (data.len(), data) 102 | }) 103 | .unwrap(); 104 | if socket.can_send() && !data.is_empty() { 105 | debug!( 106 | "send data: {:?}", 107 | str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") 108 | ); 109 | socket.send_slice(&data[..]).unwrap(); 110 | } 111 | } else if socket.may_send() { 112 | debug!("close"); 113 | socket.close(); 114 | } 115 | 116 | phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /examples/dhcp_client.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::option_map_unit_fn)] 2 | mod utils; 3 | 4 | use log::*; 5 | use std::os::unix::io::AsRawFd; 6 | 7 | use smoltcp::iface::{Config, Interface, SocketSet}; 8 | use smoltcp::socket::dhcpv4; 9 | use smoltcp::time::Instant; 10 | use smoltcp::wire::{EthernetAddress, IpCidr, Ipv4Cidr}; 11 | use smoltcp::{ 12 | phy::{wait as phy_wait, Device, Medium}, 13 | time::Duration, 14 | }; 15 | 16 | fn main() { 17 | #[cfg(feature = "log")] 18 | utils::setup_logging(""); 19 | 20 | let (mut opts, mut free) = utils::create_options(); 21 | utils::add_tuntap_options(&mut opts, &mut free); 22 | utils::add_middleware_options(&mut opts, &mut free); 23 | 24 | let mut matches = utils::parse_options(&opts, free); 25 | let device = utils::parse_tuntap_options(&mut matches); 26 | let fd = device.as_raw_fd(); 27 | let mut device = 28 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 29 | 30 | // Create interface 31 | let mut config = match device.capabilities().medium { 32 | Medium::Ethernet => { 33 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 34 | } 35 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 36 | Medium::Ieee802154 => todo!(), 37 | }; 38 | config.random_seed = rand::random(); 39 | let mut iface = Interface::new(config, &mut device, Instant::now()); 40 | 41 | // Create sockets 42 | let mut dhcp_socket = dhcpv4::Socket::new(); 43 | 44 | // Set a ridiculously short max lease time to show DHCP renews work properly. 45 | // This will cause the DHCP client to start renewing after 5 seconds, and give up the 46 | // lease after 10 seconds if renew hasn't succeeded. 47 | // IMPORTANT: This should be removed in production. 48 | dhcp_socket.set_max_lease_duration(Some(Duration::from_secs(10))); 49 | 50 | let mut sockets = SocketSet::new(vec![]); 51 | let dhcp_handle = sockets.add(dhcp_socket); 52 | 53 | loop { 54 | let timestamp = Instant::now(); 55 | iface.poll(timestamp, &mut device, &mut sockets); 56 | 57 | let event = sockets.get_mut::(dhcp_handle).poll(); 58 | match event { 59 | None => {} 60 | Some(dhcpv4::Event::Configured(config)) => { 61 | debug!("DHCP config acquired!"); 62 | 63 | debug!("IP address: {}", config.address); 64 | set_ipv4_addr(&mut iface, config.address); 65 | 66 | if let Some(router) = config.router { 67 | debug!("Default gateway: {}", router); 68 | iface.routes_mut().add_default_ipv4_route(router).unwrap(); 69 | } else { 70 | debug!("Default gateway: None"); 71 | iface.routes_mut().remove_default_ipv4_route(); 72 | } 73 | 74 | for (i, s) in config.dns_servers.iter().enumerate() { 75 | debug!("DNS server {}: {}", i, s); 76 | } 77 | } 78 | Some(dhcpv4::Event::Deconfigured) => { 79 | debug!("DHCP lost config!"); 80 | iface.update_ip_addrs(|addrs| addrs.clear()); 81 | iface.routes_mut().remove_default_ipv4_route(); 82 | } 83 | } 84 | 85 | phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); 86 | } 87 | } 88 | 89 | /// Clear any existing IP addresses & add the new one 90 | fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) { 91 | iface.update_ip_addrs(|addrs| { 92 | addrs.clear(); 93 | addrs.push(IpCidr::Ipv4(cidr)).unwrap(); 94 | }); 95 | } 96 | -------------------------------------------------------------------------------- /examples/dns.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use smoltcp::iface::{Config, Interface, SocketSet}; 4 | use smoltcp::phy::Device; 5 | use smoltcp::phy::{wait as phy_wait, Medium}; 6 | use smoltcp::socket::dns::{self, GetQueryResultError}; 7 | use smoltcp::time::Instant; 8 | use smoltcp::wire::{DnsQueryType, EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; 9 | use std::os::unix::io::AsRawFd; 10 | 11 | fn main() { 12 | utils::setup_logging("warn"); 13 | 14 | let (mut opts, mut free) = utils::create_options(); 15 | utils::add_tuntap_options(&mut opts, &mut free); 16 | utils::add_middleware_options(&mut opts, &mut free); 17 | free.push("ADDRESS"); 18 | 19 | let mut matches = utils::parse_options(&opts, free); 20 | let device = utils::parse_tuntap_options(&mut matches); 21 | let fd = device.as_raw_fd(); 22 | let mut device = 23 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 24 | let name = &matches.free[0]; 25 | 26 | // Create interface 27 | let mut config = match device.capabilities().medium { 28 | Medium::Ethernet => { 29 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 30 | } 31 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 32 | Medium::Ieee802154 => todo!(), 33 | }; 34 | config.random_seed = rand::random(); 35 | 36 | let mut iface = Interface::new(config, &mut device, Instant::now()); 37 | iface.update_ip_addrs(|ip_addrs| { 38 | ip_addrs 39 | .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) 40 | .unwrap(); 41 | ip_addrs 42 | .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) 43 | .unwrap(); 44 | ip_addrs 45 | .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) 46 | .unwrap(); 47 | }); 48 | iface 49 | .routes_mut() 50 | .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) 51 | .unwrap(); 52 | iface 53 | .routes_mut() 54 | .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) 55 | .unwrap(); 56 | 57 | // Create sockets 58 | let servers = &[ 59 | Ipv4Address::new(8, 8, 4, 4).into(), 60 | Ipv4Address::new(8, 8, 8, 8).into(), 61 | ]; 62 | let dns_socket = dns::Socket::new(servers, vec![]); 63 | 64 | let mut sockets = SocketSet::new(vec![]); 65 | let dns_handle = sockets.add(dns_socket); 66 | 67 | let socket = sockets.get_mut::(dns_handle); 68 | let query = socket 69 | .start_query(iface.context(), name, DnsQueryType::A) 70 | .unwrap(); 71 | 72 | loop { 73 | let timestamp = Instant::now(); 74 | log::debug!("timestamp {:?}", timestamp); 75 | 76 | iface.poll(timestamp, &mut device, &mut sockets); 77 | 78 | match sockets 79 | .get_mut::(dns_handle) 80 | .get_query_result(query) 81 | { 82 | Ok(addrs) => { 83 | println!("Query done: {addrs:?}"); 84 | break; 85 | } 86 | Err(GetQueryResultError::Pending) => {} // not done yet 87 | Err(e) => panic!("query failed: {e:?}"), 88 | } 89 | 90 | phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/httpclient.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use log::debug; 4 | use std::os::unix::io::AsRawFd; 5 | use std::str::{self, FromStr}; 6 | use url::Url; 7 | 8 | use smoltcp::iface::{Config, Interface, SocketSet}; 9 | use smoltcp::phy::{wait as phy_wait, Device, Medium}; 10 | use smoltcp::socket::tcp; 11 | use smoltcp::time::Instant; 12 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; 13 | 14 | fn main() { 15 | utils::setup_logging(""); 16 | 17 | let (mut opts, mut free) = utils::create_options(); 18 | utils::add_tuntap_options(&mut opts, &mut free); 19 | utils::add_middleware_options(&mut opts, &mut free); 20 | free.push("ADDRESS"); 21 | free.push("URL"); 22 | 23 | let mut matches = utils::parse_options(&opts, free); 24 | let device = utils::parse_tuntap_options(&mut matches); 25 | let fd = device.as_raw_fd(); 26 | let mut device = 27 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 28 | let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format"); 29 | let url = Url::parse(&matches.free[1]).expect("invalid url format"); 30 | 31 | // Create interface 32 | let mut config = match device.capabilities().medium { 33 | Medium::Ethernet => { 34 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 35 | } 36 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 37 | Medium::Ieee802154 => todo!(), 38 | }; 39 | config.random_seed = rand::random(); 40 | 41 | let mut iface = Interface::new(config, &mut device, Instant::now()); 42 | iface.update_ip_addrs(|ip_addrs| { 43 | ip_addrs 44 | .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) 45 | .unwrap(); 46 | ip_addrs 47 | .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) 48 | .unwrap(); 49 | ip_addrs 50 | .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) 51 | .unwrap(); 52 | }); 53 | iface 54 | .routes_mut() 55 | .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) 56 | .unwrap(); 57 | iface 58 | .routes_mut() 59 | .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) 60 | .unwrap(); 61 | 62 | // Create sockets 63 | let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); 64 | let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); 65 | let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); 66 | 67 | let mut sockets = SocketSet::new(vec![]); 68 | let tcp_handle = sockets.add(tcp_socket); 69 | 70 | enum State { 71 | Connect, 72 | Request, 73 | Response, 74 | } 75 | let mut state = State::Connect; 76 | 77 | loop { 78 | let timestamp = Instant::now(); 79 | iface.poll(timestamp, &mut device, &mut sockets); 80 | 81 | let socket = sockets.get_mut::(tcp_handle); 82 | let cx = iface.context(); 83 | 84 | state = match state { 85 | State::Connect if !socket.is_active() => { 86 | debug!("connecting"); 87 | let local_port = 49152 + rand::random::() % 16384; 88 | socket 89 | .connect(cx, (address, url.port().unwrap_or(80)), local_port) 90 | .unwrap(); 91 | State::Request 92 | } 93 | State::Request if socket.may_send() => { 94 | debug!("sending request"); 95 | let http_get = "GET ".to_owned() + url.path() + " HTTP/1.1\r\n"; 96 | socket.send_slice(http_get.as_ref()).expect("cannot send"); 97 | let http_host = "Host: ".to_owned() + url.host_str().unwrap() + "\r\n"; 98 | socket.send_slice(http_host.as_ref()).expect("cannot send"); 99 | socket 100 | .send_slice(b"Connection: close\r\n") 101 | .expect("cannot send"); 102 | socket.send_slice(b"\r\n").expect("cannot send"); 103 | State::Response 104 | } 105 | State::Response if socket.can_recv() => { 106 | socket 107 | .recv(|data| { 108 | println!("{}", str::from_utf8(data).unwrap_or("(invalid utf8)")); 109 | (data.len(), ()) 110 | }) 111 | .unwrap(); 112 | State::Response 113 | } 114 | State::Response if !socket.may_recv() => { 115 | debug!("received complete response"); 116 | break; 117 | } 118 | _ => state, 119 | }; 120 | 121 | phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/loopback.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![allow(unused_mut)] 3 | #![allow(clippy::collapsible_if)] 4 | 5 | #[cfg(feature = "std")] 6 | mod utils; 7 | 8 | use core::str; 9 | use log::{debug, error, info}; 10 | 11 | use smoltcp::iface::{Config, Interface, SocketSet}; 12 | use smoltcp::phy::{Device, Loopback, Medium}; 13 | use smoltcp::socket::tcp; 14 | use smoltcp::time::{Duration, Instant}; 15 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; 16 | 17 | #[cfg(not(feature = "std"))] 18 | mod mock { 19 | use core::cell::Cell; 20 | use smoltcp::time::{Duration, Instant}; 21 | 22 | #[derive(Debug)] 23 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 24 | pub struct Clock(Cell); 25 | 26 | impl Clock { 27 | pub fn new() -> Clock { 28 | Clock(Cell::new(Instant::from_millis(0))) 29 | } 30 | 31 | pub fn advance(&self, duration: Duration) { 32 | self.0.set(self.0.get() + duration) 33 | } 34 | 35 | pub fn elapsed(&self) -> Instant { 36 | self.0.get() 37 | } 38 | } 39 | } 40 | 41 | #[cfg(feature = "std")] 42 | mod mock { 43 | use smoltcp::time::{Duration, Instant}; 44 | use std::sync::atomic::{AtomicUsize, Ordering}; 45 | use std::sync::Arc; 46 | 47 | // should be AtomicU64 but that's unstable 48 | #[derive(Debug, Clone)] 49 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 50 | pub struct Clock(Arc); 51 | 52 | impl Clock { 53 | pub fn new() -> Clock { 54 | Clock(Arc::new(AtomicUsize::new(0))) 55 | } 56 | 57 | pub fn advance(&self, duration: Duration) { 58 | self.0 59 | .fetch_add(duration.total_millis() as usize, Ordering::SeqCst); 60 | } 61 | 62 | pub fn elapsed(&self) -> Instant { 63 | Instant::from_millis(self.0.load(Ordering::SeqCst) as i64) 64 | } 65 | } 66 | } 67 | 68 | fn main() { 69 | let clock = mock::Clock::new(); 70 | let device = Loopback::new(Medium::Ethernet); 71 | 72 | #[cfg(feature = "std")] 73 | let mut device = { 74 | let clock = clock.clone(); 75 | utils::setup_logging_with_clock("", move || clock.elapsed()); 76 | 77 | let (mut opts, mut free) = utils::create_options(); 78 | utils::add_middleware_options(&mut opts, &mut free); 79 | 80 | let mut matches = utils::parse_options(&opts, free); 81 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ true) 82 | }; 83 | 84 | // Create interface 85 | let mut config = match device.capabilities().medium { 86 | Medium::Ethernet => { 87 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 88 | } 89 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 90 | Medium::Ieee802154 => todo!(), 91 | }; 92 | 93 | let mut iface = Interface::new(config, &mut device, Instant::now()); 94 | iface.update_ip_addrs(|ip_addrs| { 95 | ip_addrs 96 | .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)) 97 | .unwrap(); 98 | }); 99 | 100 | // Create sockets 101 | let server_socket = { 102 | // It is not strictly necessary to use a `static mut` and unsafe code here, but 103 | // on embedded systems that smoltcp targets it is far better to allocate the data 104 | // statically to verify that it fits into RAM rather than get undefined behavior 105 | // when stack overflows. 106 | static mut TCP_SERVER_RX_DATA: [u8; 1024] = [0; 1024]; 107 | static mut TCP_SERVER_TX_DATA: [u8; 1024] = [0; 1024]; 108 | let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); 109 | let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); 110 | tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) 111 | }; 112 | 113 | let client_socket = { 114 | static mut TCP_CLIENT_RX_DATA: [u8; 1024] = [0; 1024]; 115 | static mut TCP_CLIENT_TX_DATA: [u8; 1024] = [0; 1024]; 116 | let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_RX_DATA[..] }); 117 | let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_TX_DATA[..] }); 118 | tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) 119 | }; 120 | 121 | let mut sockets: [_; 2] = Default::default(); 122 | let mut sockets = SocketSet::new(&mut sockets[..]); 123 | let server_handle = sockets.add(server_socket); 124 | let client_handle = sockets.add(client_socket); 125 | 126 | let mut did_listen = false; 127 | let mut did_connect = false; 128 | let mut done = false; 129 | while !done && clock.elapsed() < Instant::from_millis(10_000) { 130 | iface.poll(clock.elapsed(), &mut device, &mut sockets); 131 | 132 | let mut socket = sockets.get_mut::(server_handle); 133 | if !socket.is_active() && !socket.is_listening() { 134 | if !did_listen { 135 | debug!("listening"); 136 | socket.listen(1234).unwrap(); 137 | did_listen = true; 138 | } 139 | } 140 | 141 | if socket.can_recv() { 142 | debug!( 143 | "got {:?}", 144 | socket.recv(|buffer| { (buffer.len(), str::from_utf8(buffer).unwrap()) }) 145 | ); 146 | socket.close(); 147 | done = true; 148 | } 149 | 150 | let mut socket = sockets.get_mut::(client_handle); 151 | let cx = iface.context(); 152 | if !socket.is_open() { 153 | if !did_connect { 154 | debug!("connecting"); 155 | socket 156 | .connect(cx, (IpAddress::v4(127, 0, 0, 1), 1234), 65000) 157 | .unwrap(); 158 | did_connect = true; 159 | } 160 | } 161 | 162 | if socket.can_send() { 163 | debug!("sending"); 164 | socket.send_slice(b"0123456789abcdef").unwrap(); 165 | socket.close(); 166 | } 167 | 168 | match iface.poll_delay(clock.elapsed(), &sockets) { 169 | Some(Duration::ZERO) => debug!("resuming"), 170 | Some(delay) => { 171 | debug!("sleeping for {} ms", delay); 172 | clock.advance(delay) 173 | } 174 | None => clock.advance(Duration::from_millis(1)), 175 | } 176 | } 177 | 178 | if done { 179 | info!("done") 180 | } else { 181 | error!("this is taking too long, bailing out") 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /examples/loopback_benchmark.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use log::debug; 4 | 5 | use smoltcp::iface::{Config, Interface, SocketSet}; 6 | use smoltcp::phy::{Device, Loopback, Medium}; 7 | use smoltcp::socket::tcp; 8 | use smoltcp::time::Instant; 9 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; 10 | 11 | fn main() { 12 | let device = Loopback::new(Medium::Ethernet); 13 | 14 | let mut device = { 15 | utils::setup_logging("info"); 16 | 17 | let (mut opts, mut free) = utils::create_options(); 18 | utils::add_middleware_options(&mut opts, &mut free); 19 | 20 | let mut matches = utils::parse_options(&opts, free); 21 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ true) 22 | }; 23 | 24 | // Create interface 25 | let config = match device.capabilities().medium { 26 | Medium::Ethernet => { 27 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 28 | } 29 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 30 | Medium::Ieee802154 => todo!(), 31 | }; 32 | 33 | let mut iface = Interface::new(config, &mut device, Instant::now()); 34 | iface.update_ip_addrs(|ip_addrs| { 35 | ip_addrs 36 | .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)) 37 | .unwrap(); 38 | }); 39 | 40 | // Create sockets 41 | let server_socket = { 42 | let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); 43 | let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); 44 | tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) 45 | }; 46 | 47 | let client_socket = { 48 | let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); 49 | let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); 50 | tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) 51 | }; 52 | 53 | let mut sockets: [_; 2] = Default::default(); 54 | let mut sockets = SocketSet::new(&mut sockets[..]); 55 | let server_handle = sockets.add(server_socket); 56 | let client_handle = sockets.add(client_socket); 57 | 58 | let start_time = Instant::now(); 59 | 60 | let mut did_listen = false; 61 | let mut did_connect = false; 62 | let mut processed = 0; 63 | while processed < 1024 * 1024 * 1024 { 64 | iface.poll(Instant::now(), &mut device, &mut sockets); 65 | 66 | let socket = sockets.get_mut::(server_handle); 67 | if !socket.is_active() && !socket.is_listening() && !did_listen { 68 | debug!("listening"); 69 | socket.listen(1234).unwrap(); 70 | did_listen = true; 71 | } 72 | 73 | while socket.can_recv() { 74 | let received = socket.recv(|buffer| (buffer.len(), buffer.len())).unwrap(); 75 | debug!("got {:?}", received,); 76 | processed += received; 77 | } 78 | 79 | let socket = sockets.get_mut::(client_handle); 80 | let cx = iface.context(); 81 | if !socket.is_open() && !did_connect { 82 | debug!("connecting"); 83 | socket 84 | .connect(cx, (IpAddress::v4(127, 0, 0, 1), 1234), 65000) 85 | .unwrap(); 86 | did_connect = true; 87 | } 88 | 89 | while socket.can_send() { 90 | debug!("sending"); 91 | socket.send(|buffer| (buffer.len(), ())).unwrap(); 92 | } 93 | } 94 | 95 | let duration = Instant::now() - start_time; 96 | println!( 97 | "done in {} s, bandwidth is {} Gbps", 98 | duration.total_millis() as f64 / 1000.0, 99 | (processed as u64 * 8 / duration.total_millis()) as f64 / 1000000.0 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /examples/multicast.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use std::os::unix::io::AsRawFd; 4 | 5 | use smoltcp::iface::{Config, Interface, SocketSet}; 6 | use smoltcp::phy::{wait as phy_wait, Device, Medium}; 7 | use smoltcp::socket::{raw, udp}; 8 | use smoltcp::time::Instant; 9 | use smoltcp::wire::{ 10 | EthernetAddress, IgmpPacket, IgmpRepr, IpAddress, IpCidr, IpProtocol, IpVersion, Ipv4Address, 11 | Ipv4Packet, Ipv6Address, 12 | }; 13 | 14 | const MDNS_PORT: u16 = 5353; 15 | const MDNS_GROUP: Ipv4Address = Ipv4Address::new(224, 0, 0, 251); 16 | 17 | fn main() { 18 | utils::setup_logging("warn"); 19 | 20 | let (mut opts, mut free) = utils::create_options(); 21 | utils::add_tuntap_options(&mut opts, &mut free); 22 | utils::add_middleware_options(&mut opts, &mut free); 23 | 24 | let mut matches = utils::parse_options(&opts, free); 25 | let device = utils::parse_tuntap_options(&mut matches); 26 | let fd = device.as_raw_fd(); 27 | let mut device = 28 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 29 | 30 | // Create interface 31 | let mut config = match device.capabilities().medium { 32 | Medium::Ethernet => { 33 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 34 | } 35 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 36 | Medium::Ieee802154 => todo!(), 37 | }; 38 | config.random_seed = rand::random(); 39 | 40 | let mut iface = Interface::new(config, &mut device, Instant::now()); 41 | iface.update_ip_addrs(|ip_addrs| { 42 | ip_addrs 43 | .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) 44 | .unwrap(); 45 | ip_addrs 46 | .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) 47 | .unwrap(); 48 | ip_addrs 49 | .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) 50 | .unwrap(); 51 | }); 52 | iface 53 | .routes_mut() 54 | .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) 55 | .unwrap(); 56 | iface 57 | .routes_mut() 58 | .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) 59 | .unwrap(); 60 | 61 | // Create sockets 62 | let mut sockets = SocketSet::new(vec![]); 63 | 64 | // Must fit at least one IGMP packet 65 | let raw_rx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; 2], vec![0; 512]); 66 | // Will not send IGMP 67 | let raw_tx_buffer = raw::PacketBuffer::new(vec![], vec![]); 68 | let raw_socket = raw::Socket::new( 69 | IpVersion::Ipv4, 70 | IpProtocol::Igmp, 71 | raw_rx_buffer, 72 | raw_tx_buffer, 73 | ); 74 | let raw_handle = sockets.add(raw_socket); 75 | 76 | // Must fit mDNS payload of at least one packet 77 | let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 4], vec![0; 1024]); 78 | // Will not send mDNS 79 | let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 0]); 80 | let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); 81 | let udp_handle = sockets.add(udp_socket); 82 | 83 | // Join a multicast group to receive mDNS traffic 84 | iface.join_multicast_group(MDNS_GROUP).unwrap(); 85 | 86 | loop { 87 | let timestamp = Instant::now(); 88 | iface.poll(timestamp, &mut device, &mut sockets); 89 | 90 | let socket = sockets.get_mut::(raw_handle); 91 | 92 | if socket.can_recv() { 93 | // For display purposes only - normally we wouldn't process incoming IGMP packets 94 | // in the application layer 95 | match socket.recv() { 96 | Err(e) => println!("Recv IGMP error: {e:?}"), 97 | Ok(buf) => { 98 | Ipv4Packet::new_checked(buf) 99 | .and_then(|ipv4_packet| IgmpPacket::new_checked(ipv4_packet.payload())) 100 | .and_then(|igmp_packet| IgmpRepr::parse(&igmp_packet)) 101 | .map(|igmp_repr| println!("IGMP packet: {igmp_repr:?}")) 102 | .unwrap_or_else(|e| println!("parse IGMP error: {e:?}")); 103 | } 104 | } 105 | } 106 | 107 | let socket = sockets.get_mut::(udp_handle); 108 | if !socket.is_open() { 109 | socket.bind(MDNS_PORT).unwrap() 110 | } 111 | 112 | if socket.can_recv() { 113 | socket 114 | .recv() 115 | .map(|(data, sender)| { 116 | println!("mDNS traffic: {} UDP bytes from {}", data.len(), sender) 117 | }) 118 | .unwrap_or_else(|e| println!("Recv UDP error: {e:?}")); 119 | } 120 | 121 | phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/multicast6.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use std::os::unix::io::AsRawFd; 4 | 5 | use smoltcp::iface::{Config, Interface, SocketSet}; 6 | use smoltcp::phy::wait as phy_wait; 7 | use smoltcp::phy::{Device, Medium}; 8 | use smoltcp::socket::udp; 9 | use smoltcp::time::Instant; 10 | use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv6Address}; 11 | 12 | // Note: If testing with a tap interface in linux, you may need to specify the 13 | // interface index when addressing. E.g., 14 | // 15 | // ``` 16 | // ncat -u ff02::1234%tap0 8123 17 | // ``` 18 | // 19 | // will send packets to the multicast group we join below on tap0. 20 | 21 | const PORT: u16 = 8123; 22 | const GROUP: Ipv6Address = Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x1234); 23 | const LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x101); 24 | const ROUTER_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100); 25 | 26 | fn main() { 27 | utils::setup_logging("warn"); 28 | 29 | let (mut opts, mut free) = utils::create_options(); 30 | utils::add_tuntap_options(&mut opts, &mut free); 31 | utils::add_middleware_options(&mut opts, &mut free); 32 | 33 | let mut matches = utils::parse_options(&opts, free); 34 | let device = utils::parse_tuntap_options(&mut matches); 35 | let fd = device.as_raw_fd(); 36 | let mut device = 37 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 38 | 39 | // Create interface 40 | let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]); 41 | let mut config = match device.capabilities().medium { 42 | Medium::Ethernet => Config::new(ethernet_addr.into()), 43 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 44 | Medium::Ieee802154 => todo!(), 45 | }; 46 | config.random_seed = rand::random(); 47 | 48 | let mut iface = Interface::new(config, &mut device, Instant::now()); 49 | iface.update_ip_addrs(|ip_addrs| { 50 | ip_addrs 51 | .push(IpCidr::new(IpAddress::from(LOCAL_ADDR), 64)) 52 | .unwrap(); 53 | }); 54 | iface 55 | .routes_mut() 56 | .add_default_ipv6_route(ROUTER_ADDR) 57 | .unwrap(); 58 | 59 | // Create sockets 60 | let mut sockets = SocketSet::new(vec![]); 61 | let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 4], vec![0; 1024]); 62 | let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 0]); 63 | let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); 64 | let udp_handle = sockets.add(udp_socket); 65 | 66 | // Join a multicast group 67 | iface.join_multicast_group(GROUP).unwrap(); 68 | 69 | loop { 70 | let timestamp = Instant::now(); 71 | iface.poll(timestamp, &mut device, &mut sockets); 72 | 73 | let socket = sockets.get_mut::(udp_handle); 74 | if !socket.is_open() { 75 | socket.bind(PORT).unwrap() 76 | } 77 | 78 | if socket.can_recv() { 79 | socket 80 | .recv() 81 | .map(|(data, sender)| println!("traffic: {} UDP bytes from {}", data.len(), sender)) 82 | .unwrap_or_else(|e| println!("Recv UDP error: {:?}", e)); 83 | } 84 | 85 | phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/sixlowpan.rs: -------------------------------------------------------------------------------- 1 | //! 6lowpan example 2 | //! 3 | //! This example is designed to run using the Linux ieee802154/6lowpan support, 4 | //! using mac802154_hwsim. 5 | //! 6 | //! mac802154_hwsim allows you to create multiple "virtual" radios and specify 7 | //! which is in range with which. This is very useful for testing without 8 | //! needing real hardware. By default it creates two interfaces `wpan0` and 9 | //! `wpan1` that are in range with each other. You can customize this with 10 | //! the `wpan-hwsim` tool. 11 | //! 12 | //! We'll configure Linux to speak 6lowpan on `wpan0`, and leave `wpan1` 13 | //! unconfigured so smoltcp can use it with a raw socket. 14 | //! 15 | //! # Setup 16 | //! 17 | //! modprobe mac802154_hwsim 18 | //! 19 | //! ip link set wpan0 down 20 | //! ip link set wpan1 down 21 | //! iwpan dev wpan0 set pan_id 0xbeef 22 | //! iwpan dev wpan1 set pan_id 0xbeef 23 | //! ip link add link wpan0 name lowpan0 type lowpan 24 | //! ip link set wpan0 up 25 | //! ip link set wpan1 up 26 | //! ip link set lowpan0 up 27 | //! 28 | //! # Running 29 | //! 30 | //! Run it with `sudo ./target/debug/examples/sixlowpan`. 31 | //! 32 | //! You can set wireshark to sniff on interface `wpan0` to see the packets. 33 | //! 34 | //! Ping it with `ping fe80::180b:4242:4242:4242%lowpan0`. 35 | //! 36 | //! Speak UDP with `nc -uv fe80::180b:4242:4242:4242%lowpan0 6969`. 37 | //! 38 | //! # Teardown 39 | //! 40 | //! rmmod mac802154_hwsim 41 | //! 42 | 43 | mod utils; 44 | 45 | use log::debug; 46 | use std::os::unix::io::AsRawFd; 47 | use std::str; 48 | 49 | use smoltcp::iface::{Config, Interface, SocketSet}; 50 | use smoltcp::phy::{wait as phy_wait, Device, Medium, RawSocket}; 51 | use smoltcp::socket::tcp; 52 | use smoltcp::socket::udp; 53 | use smoltcp::time::Instant; 54 | use smoltcp::wire::{EthernetAddress, Ieee802154Address, Ieee802154Pan, IpAddress, IpCidr}; 55 | 56 | fn main() { 57 | utils::setup_logging(""); 58 | 59 | let (mut opts, mut free) = utils::create_options(); 60 | utils::add_middleware_options(&mut opts, &mut free); 61 | 62 | let mut matches = utils::parse_options(&opts, free); 63 | 64 | let device = RawSocket::new("wpan1", Medium::Ieee802154).unwrap(); 65 | let fd = device.as_raw_fd(); 66 | let mut device = 67 | utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); 68 | 69 | // Create interface 70 | let mut config = match device.capabilities().medium { 71 | Medium::Ethernet => { 72 | Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) 73 | } 74 | Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), 75 | Medium::Ieee802154 => Config::new( 76 | Ieee802154Address::Extended([0x1a, 0x0b, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42]).into(), 77 | ), 78 | }; 79 | config.random_seed = rand::random(); 80 | config.pan_id = Some(Ieee802154Pan(0xbeef)); 81 | 82 | let mut iface = Interface::new(config, &mut device, Instant::now()); 83 | iface.update_ip_addrs(|ip_addrs| { 84 | ip_addrs 85 | .push(IpCidr::new( 86 | IpAddress::v6(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242), 87 | 64, 88 | )) 89 | .unwrap(); 90 | }); 91 | 92 | // Create sockets 93 | let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1280]); 94 | let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1280]); 95 | let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); 96 | 97 | let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); 98 | let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); 99 | let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); 100 | 101 | let mut sockets = SocketSet::new(vec![]); 102 | let udp_handle = sockets.add(udp_socket); 103 | let tcp_handle = sockets.add(tcp_socket); 104 | 105 | let socket = sockets.get_mut::(tcp_handle); 106 | socket.listen(50000).unwrap(); 107 | 108 | let mut tcp_active = false; 109 | 110 | loop { 111 | let timestamp = Instant::now(); 112 | iface.poll(timestamp, &mut device, &mut sockets); 113 | 114 | // udp:6969: respond "hello" 115 | let socket = sockets.get_mut::(udp_handle); 116 | if !socket.is_open() { 117 | socket.bind(6969).unwrap() 118 | } 119 | 120 | let mut buffer = vec![0; 1500]; 121 | let client = match socket.recv() { 122 | Ok((data, endpoint)) => { 123 | debug!( 124 | "udp:6969 recv data: {:?} from {}", 125 | str::from_utf8(data).unwrap(), 126 | endpoint 127 | ); 128 | buffer[..data.len()].copy_from_slice(data); 129 | Some((data.len(), endpoint)) 130 | } 131 | Err(_) => None, 132 | }; 133 | if let Some((len, endpoint)) = client { 134 | debug!( 135 | "udp:6969 send data: {:?}", 136 | str::from_utf8(&buffer[..len]).unwrap() 137 | ); 138 | socket.send_slice(&buffer[..len], endpoint).unwrap(); 139 | } 140 | 141 | let socket = sockets.get_mut::(tcp_handle); 142 | if socket.is_active() && !tcp_active { 143 | debug!("connected"); 144 | } else if !socket.is_active() && tcp_active { 145 | debug!("disconnected"); 146 | } 147 | tcp_active = socket.is_active(); 148 | 149 | if socket.may_recv() { 150 | let data = socket 151 | .recv(|data| { 152 | let data = data.to_owned(); 153 | if !data.is_empty() { 154 | debug!( 155 | "recv data: {:?}", 156 | str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") 157 | ); 158 | } 159 | (data.len(), data) 160 | }) 161 | .unwrap(); 162 | 163 | if socket.can_send() && !data.is_empty() { 164 | debug!( 165 | "send data: {:?}", 166 | str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") 167 | ); 168 | socket.send_slice(&data[..]).unwrap(); 169 | } 170 | } else if socket.may_send() { 171 | debug!("close"); 172 | socket.close(); 173 | } 174 | 175 | phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /examples/tcpdump.rs: -------------------------------------------------------------------------------- 1 | use smoltcp::phy::wait as phy_wait; 2 | use smoltcp::phy::{Device, RawSocket, RxToken}; 3 | use smoltcp::time::Instant; 4 | use smoltcp::wire::{EthernetFrame, PrettyPrinter}; 5 | use std::env; 6 | use std::os::unix::io::AsRawFd; 7 | 8 | fn main() { 9 | let ifname = env::args().nth(1).unwrap(); 10 | let mut socket = RawSocket::new(ifname.as_ref(), smoltcp::phy::Medium::Ethernet).unwrap(); 11 | loop { 12 | phy_wait(socket.as_raw_fd(), None).unwrap(); 13 | let (rx_token, _) = socket.receive(Instant::now()).unwrap(); 14 | rx_token.consume(|buffer| { 15 | println!( 16 | "{}", 17 | PrettyPrinter::>::new("", &buffer) 18 | ); 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smoltcp-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2018" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | arbitrary = { version = "1", features = ["derive"] } 14 | getopts = "0.2" 15 | smoltcp = { path = ".." } 16 | 17 | # Prevent this from interfering with workspaces 18 | [workspace] 19 | members = ["."] 20 | 21 | [[bin]] 22 | name = "packet_parser" 23 | path = "fuzz_targets/packet_parser.rs" 24 | test = false 25 | doc = false 26 | 27 | [[bin]] 28 | name = "tcp_headers" 29 | path = "fuzz_targets/tcp_headers.rs" 30 | test = false 31 | doc = false 32 | 33 | [[bin]] 34 | name = "dhcp_header" 35 | path = "fuzz_targets/dhcp_header.rs" 36 | test = false 37 | doc = false 38 | 39 | [[bin]] 40 | name = "ieee802154_header" 41 | path = "fuzz_targets/ieee802154_header.rs" 42 | test = false 43 | doc = false 44 | 45 | [[bin]] 46 | name = "sixlowpan_packet" 47 | path = "fuzz_targets/sixlowpan_packet.rs" 48 | test = false 49 | doc = false 50 | -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/arp.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/arp.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/icmpv4_reply.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/icmpv4_reply.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/icmpv4_request.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/icmpv4_request.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/icmpv4_unreachable.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/icmpv4_unreachable.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/icmpv6_nbr_solicitation.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/icmpv6_nbr_solicitation.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/tcpv4_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/tcpv4_data.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/tcpv4_fin.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/tcpv4_fin.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/tcpv4_rst.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/tcpv4_rst.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/tcpv4_syn.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/tcpv4_syn.bin -------------------------------------------------------------------------------- /fuzz/corpus/packet_parser/udpv4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoltcp-rs/smoltcp/e2b75e37d732809a84695f5e90f7ee987ce00885/fuzz/corpus/packet_parser/udpv4.bin -------------------------------------------------------------------------------- /fuzz/fuzz_targets/dhcp_header.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use smoltcp::wire::{DhcpPacket, DhcpRepr}; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = match DhcpPacket::new_checked(data) { 7 | Ok(packet) => match DhcpRepr::parse(packet) { 8 | Ok(dhcp_repr) => { 9 | let mut dhcp_payload = vec![0; dhcp_repr.buffer_len()]; 10 | match DhcpPacket::new_checked(&mut dhcp_payload[..]) { 11 | Ok(mut dhcp_packet) => Some(dhcp_repr.emit(&mut dhcp_packet)), 12 | Err(_) => None, 13 | } 14 | } 15 | Err(_) => None, 16 | }, 17 | Err(_) => None, 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/ieee802154_header.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use smoltcp::wire::{Ieee802154Frame, Ieee802154Repr}; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | if let Ok(frame) = Ieee802154Frame::new_checked(data) { 7 | if let Ok(repr) = Ieee802154Repr::parse(frame) { 8 | // The buffer len returns only the length required for emitting the header 9 | // and does not take into account the length of the payload. 10 | let mut buffer = vec![0; repr.buffer_len()]; 11 | 12 | // NOTE: unchecked because the checked version checks if the addressing mode field 13 | // is valid or not. The addressing mode field is required for calculating the length of 14 | // the header, which is used in `check_len`. 15 | let mut frame = Ieee802154Frame::new_unchecked(&mut buffer[..]); 16 | repr.emit(&mut frame); 17 | } 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/packet_parser.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use smoltcp::wire::*; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | format!( 7 | "{}", 8 | PrettyPrinter::>::new("", &data) 9 | ); 10 | }); 11 | -------------------------------------------------------------------------------- /fuzz/utils.rs: -------------------------------------------------------------------------------- 1 | // TODO: this is literally a copy of examples/utils.rs, but without an allow dead code attribute. 2 | // The include logic does not allow having attributes in included files. 3 | 4 | use getopts::{Matches, Options}; 5 | use std::env; 6 | use std::fs::File; 7 | use std::io; 8 | use std::io::Write; 9 | use std::process; 10 | use std::str::{self, FromStr}; 11 | use std::time::{SystemTime, UNIX_EPOCH}; 12 | 13 | use smoltcp::phy::{Device, FaultInjector, Tracer}; 14 | use smoltcp::phy::{PcapMode, PcapWriter}; 15 | use smoltcp::time::Duration; 16 | 17 | pub fn create_options() -> (Options, Vec<&'static str>) { 18 | let mut opts = Options::new(); 19 | opts.optflag("h", "help", "print this help menu"); 20 | (opts, Vec::new()) 21 | } 22 | 23 | pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches { 24 | match options.parse(env::args().skip(1)) { 25 | Err(err) => { 26 | println!("{}", err); 27 | process::exit(1) 28 | } 29 | Ok(matches) => { 30 | if matches.opt_present("h") || matches.free.len() != free.len() { 31 | let brief = format!( 32 | "Usage: {} [OPTION]... {}", 33 | env::args().nth(0).unwrap(), 34 | free.join(" ") 35 | ); 36 | print!("{}", options.usage(&brief)); 37 | process::exit(if matches.free.len() != free.len() { 38 | 1 39 | } else { 40 | 0 41 | }) 42 | } 43 | matches 44 | } 45 | } 46 | } 47 | 48 | pub fn add_middleware_options(opts: &mut Options, _free: &mut Vec<&str>) { 49 | opts.optopt("", "pcap", "Write a packet capture file", "FILE"); 50 | opts.optopt( 51 | "", 52 | "drop-chance", 53 | "Chance of dropping a packet (%)", 54 | "CHANCE", 55 | ); 56 | opts.optopt( 57 | "", 58 | "corrupt-chance", 59 | "Chance of corrupting a packet (%)", 60 | "CHANCE", 61 | ); 62 | opts.optopt( 63 | "", 64 | "size-limit", 65 | "Drop packets larger than given size (octets)", 66 | "SIZE", 67 | ); 68 | opts.optopt( 69 | "", 70 | "tx-rate-limit", 71 | "Drop packets after transmit rate exceeds given limit \ 72 | (packets per interval)", 73 | "RATE", 74 | ); 75 | opts.optopt( 76 | "", 77 | "rx-rate-limit", 78 | "Drop packets after transmit rate exceeds given limit \ 79 | (packets per interval)", 80 | "RATE", 81 | ); 82 | opts.optopt( 83 | "", 84 | "shaping-interval", 85 | "Sets the interval for rate limiting (ms)", 86 | "RATE", 87 | ); 88 | } 89 | 90 | pub fn parse_middleware_options( 91 | matches: &mut Matches, 92 | device: D, 93 | loopback: bool, 94 | ) -> FaultInjector>>> 95 | where 96 | D: Device, 97 | { 98 | let drop_chance = matches 99 | .opt_str("drop-chance") 100 | .map(|s| u8::from_str(&s).unwrap()) 101 | .unwrap_or(0); 102 | let corrupt_chance = matches 103 | .opt_str("corrupt-chance") 104 | .map(|s| u8::from_str(&s).unwrap()) 105 | .unwrap_or(0); 106 | let size_limit = matches 107 | .opt_str("size-limit") 108 | .map(|s| usize::from_str(&s).unwrap()) 109 | .unwrap_or(0); 110 | let tx_rate_limit = matches 111 | .opt_str("tx-rate-limit") 112 | .map(|s| u64::from_str(&s).unwrap()) 113 | .unwrap_or(0); 114 | let rx_rate_limit = matches 115 | .opt_str("rx-rate-limit") 116 | .map(|s| u64::from_str(&s).unwrap()) 117 | .unwrap_or(0); 118 | let shaping_interval = matches 119 | .opt_str("shaping-interval") 120 | .map(|s| u64::from_str(&s).unwrap()) 121 | .unwrap_or(0); 122 | 123 | let pcap_writer: Box; 124 | if let Some(pcap_filename) = matches.opt_str("pcap") { 125 | pcap_writer = Box::new(File::create(pcap_filename).expect("cannot open file")) 126 | } else { 127 | pcap_writer = Box::new(io::sink()) 128 | } 129 | 130 | let seed = SystemTime::now() 131 | .duration_since(UNIX_EPOCH) 132 | .unwrap() 133 | .subsec_nanos(); 134 | 135 | let device = PcapWriter::new( 136 | device, 137 | pcap_writer, 138 | if loopback { 139 | PcapMode::TxOnly 140 | } else { 141 | PcapMode::Both 142 | }, 143 | ); 144 | 145 | let device = Tracer::new(device, |_timestamp, _printer| { 146 | #[cfg(feature = "log")] 147 | trace!("{}", _printer); 148 | }); 149 | let mut device = FaultInjector::new(device, seed); 150 | device.set_drop_chance(drop_chance); 151 | device.set_corrupt_chance(corrupt_chance); 152 | device.set_max_packet_size(size_limit); 153 | device.set_max_tx_rate(tx_rate_limit); 154 | device.set_max_rx_rate(rx_rate_limit); 155 | device.set_bucket_interval(Duration::from_millis(shaping_interval)); 156 | device 157 | } 158 | -------------------------------------------------------------------------------- /gen_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | abspath = os.path.abspath(__file__) 4 | dname = os.path.dirname(abspath) 5 | os.chdir(dname) 6 | 7 | features = [] 8 | 9 | 10 | def feature(name, default, min, max, pow2=None): 11 | vals = set() 12 | val = min 13 | while val <= max: 14 | vals.add(val) 15 | if pow2 == True or (isinstance(pow2, int) and val >= pow2): 16 | val *= 2 17 | else: 18 | val += 1 19 | vals.add(default) 20 | 21 | features.append( 22 | { 23 | "name": name, 24 | "default": default, 25 | "vals": sorted(list(vals)), 26 | } 27 | ) 28 | 29 | 30 | feature("iface_max_addr_count", default=2, min=1, max=8) 31 | feature("iface_max_multicast_group_count", default=4, min=1, max=1024, pow2=8) 32 | feature("iface_max_sixlowpan_address_context_count", default=4, min=1, max=1024, pow2=8) 33 | feature("iface_neighbor_cache_count", default=8, min=1, max=1024, pow2=8) 34 | feature("iface_max_route_count", default=2, min=0, max=1024, pow2=8) 35 | feature("fragmentation_buffer_size", default=1500, min=256, max=65536, pow2=True) 36 | feature("assembler_max_segment_count", default=4, min=1, max=32, pow2=4) 37 | feature("reassembly_buffer_size", default=1500, min=256, max=65536, pow2=True) 38 | feature("reassembly_buffer_count", default=1, min=1, max=32, pow2=4) 39 | feature("ipv6_hbh_max_options", default=4, min=1, max=32, pow2=4) 40 | feature("dns_max_result_count", default=1, min=1, max=32, pow2=4) 41 | feature("dns_max_server_count", default=1, min=1, max=32, pow2=4) 42 | feature("dns_max_name_size", default=255, min=64, max=255, pow2=True) 43 | feature("rpl_relations_buffer_count", default=16, min=1, max=128, pow2=True) 44 | feature("rpl_parents_buffer_count", default=8, min=2, max=32, pow2=True) 45 | 46 | # ========= Update Cargo.toml 47 | 48 | things = "" 49 | for f in features: 50 | name = f["name"].replace("_", "-") 51 | for val in f["vals"]: 52 | things += f"{name}-{val} = []" 53 | if val == f["default"]: 54 | things += " # Default" 55 | things += "\n" 56 | things += "\n" 57 | 58 | SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" 59 | SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" 60 | HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" 61 | with open("Cargo.toml", "r") as f: 62 | data = f.read() 63 | before, data = data.split(SEPARATOR_START, maxsplit=1) 64 | _, after = data.split(SEPARATOR_END, maxsplit=1) 65 | data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after 66 | with open("Cargo.toml", "w") as f: 67 | f.write(data) 68 | 69 | 70 | # ========= Update build.rs 71 | 72 | things = "" 73 | for f in features: 74 | name = f["name"].upper() 75 | things += f' ("{name}", {f["default"]}),\n' 76 | 77 | SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" 78 | SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" 79 | HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" 80 | with open("build.rs", "r") as f: 81 | data = f.read() 82 | before, data = data.split(SEPARATOR_START, maxsplit=1) 83 | _, after = data.split(SEPARATOR_END, maxsplit=1) 84 | data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after 85 | with open("build.rs", "w") as f: 86 | f.write(data) 87 | -------------------------------------------------------------------------------- /src/iface/interface/ethernet.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl InterfaceInner { 4 | pub(super) fn process_ethernet<'frame>( 5 | &mut self, 6 | sockets: &mut SocketSet, 7 | meta: crate::phy::PacketMeta, 8 | frame: &'frame [u8], 9 | fragments: &'frame mut FragmentsBuffer, 10 | ) -> Option> { 11 | let eth_frame = check!(EthernetFrame::new_checked(frame)); 12 | 13 | // Ignore any packets not directed to our hardware address or any of the multicast groups. 14 | if !eth_frame.dst_addr().is_broadcast() 15 | && !eth_frame.dst_addr().is_multicast() 16 | && HardwareAddress::Ethernet(eth_frame.dst_addr()) != self.hardware_addr 17 | { 18 | return None; 19 | } 20 | 21 | match eth_frame.ethertype() { 22 | #[cfg(feature = "proto-ipv4")] 23 | EthernetProtocol::Arp => self.process_arp(self.now, ð_frame), 24 | #[cfg(feature = "proto-ipv4")] 25 | EthernetProtocol::Ipv4 => { 26 | let ipv4_packet = check!(Ipv4Packet::new_checked(eth_frame.payload())); 27 | 28 | self.process_ipv4( 29 | sockets, 30 | meta, 31 | eth_frame.src_addr().into(), 32 | &ipv4_packet, 33 | fragments, 34 | ) 35 | .map(EthernetPacket::Ip) 36 | } 37 | #[cfg(feature = "proto-ipv6")] 38 | EthernetProtocol::Ipv6 => { 39 | let ipv6_packet = check!(Ipv6Packet::new_checked(eth_frame.payload())); 40 | self.process_ipv6(sockets, meta, eth_frame.src_addr().into(), &ipv6_packet) 41 | .map(EthernetPacket::Ip) 42 | } 43 | // Drop all other traffic. 44 | _ => None, 45 | } 46 | } 47 | 48 | pub(super) fn dispatch_ethernet( 49 | &mut self, 50 | tx_token: Tx, 51 | buffer_len: usize, 52 | f: F, 53 | ) -> Result<(), DispatchError> 54 | where 55 | Tx: TxToken, 56 | F: FnOnce(EthernetFrame<&mut [u8]>), 57 | { 58 | let tx_len = EthernetFrame::<&[u8]>::buffer_len(buffer_len); 59 | tx_token.consume(tx_len, |tx_buffer| { 60 | debug_assert!(tx_buffer.as_ref().len() == tx_len); 61 | let mut frame = EthernetFrame::new_unchecked(tx_buffer); 62 | 63 | let src_addr = self.hardware_addr.ethernet_or_panic(); 64 | frame.set_src_addr(src_addr); 65 | 66 | f(frame); 67 | 68 | Ok(()) 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/iface/interface/ieee802154.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl InterfaceInner { 4 | /// Return the next IEEE802.15.4 sequence number. 5 | #[cfg(feature = "medium-ieee802154")] 6 | pub(super) fn next_ieee802154_seq_number(&mut self) -> u8 { 7 | let no = self.sequence_no; 8 | self.sequence_no = self.sequence_no.wrapping_add(1); 9 | no 10 | } 11 | 12 | pub(super) fn process_ieee802154<'output, 'payload: 'output>( 13 | &mut self, 14 | sockets: &mut SocketSet, 15 | meta: PacketMeta, 16 | sixlowpan_payload: &'payload [u8], 17 | _fragments: &'output mut FragmentsBuffer, 18 | ) -> Option> { 19 | let ieee802154_frame = check!(Ieee802154Frame::new_checked(sixlowpan_payload)); 20 | 21 | if ieee802154_frame.frame_type() != Ieee802154FrameType::Data { 22 | return None; 23 | } 24 | 25 | let ieee802154_repr = check!(Ieee802154Repr::parse(&ieee802154_frame)); 26 | 27 | // Drop frames when the user has set a PAN id and the PAN id from frame is not equal to this 28 | // When the user didn't set a PAN id (so it is None), then we accept all PAN id's. 29 | // We always accept the broadcast PAN id. 30 | if self.pan_id.is_some() 31 | && ieee802154_repr.dst_pan_id != self.pan_id 32 | && ieee802154_repr.dst_pan_id != Some(Ieee802154Pan::BROADCAST) 33 | { 34 | net_debug!( 35 | "IEEE802.15.4: dropping {:?} because not our PAN id (or not broadcast)", 36 | ieee802154_repr 37 | ); 38 | return None; 39 | } 40 | 41 | match ieee802154_frame.payload() { 42 | Some(payload) => { 43 | self.process_sixlowpan(sockets, meta, &ieee802154_repr, payload, _fragments) 44 | } 45 | None => None, 46 | } 47 | } 48 | 49 | pub(super) fn dispatch_ieee802154( 50 | &mut self, 51 | ll_dst_a: Ieee802154Address, 52 | tx_token: Tx, 53 | meta: PacketMeta, 54 | packet: Packet, 55 | frag: &mut Fragmenter, 56 | ) { 57 | let ll_src_a = self.hardware_addr.ieee802154_or_panic(); 58 | 59 | // Create the IEEE802.15.4 header. 60 | let ieee_repr = Ieee802154Repr { 61 | frame_type: Ieee802154FrameType::Data, 62 | security_enabled: false, 63 | frame_pending: false, 64 | ack_request: false, 65 | sequence_number: Some(self.next_ieee802154_seq_number()), 66 | pan_id_compression: true, 67 | frame_version: Ieee802154FrameVersion::Ieee802154_2003, 68 | dst_pan_id: self.pan_id, 69 | dst_addr: Some(ll_dst_a), 70 | src_pan_id: self.pan_id, 71 | src_addr: Some(ll_src_a), 72 | }; 73 | 74 | self.dispatch_sixlowpan(tx_token, meta, packet, ieee_repr, frag); 75 | } 76 | 77 | #[cfg(feature = "proto-sixlowpan-fragmentation")] 78 | pub(super) fn dispatch_ieee802154_frag( 79 | &mut self, 80 | tx_token: Tx, 81 | frag: &mut Fragmenter, 82 | ) { 83 | // Create the IEEE802.15.4 header. 84 | let ieee_repr = Ieee802154Repr { 85 | frame_type: Ieee802154FrameType::Data, 86 | security_enabled: false, 87 | frame_pending: false, 88 | ack_request: false, 89 | sequence_number: Some(self.next_ieee802154_seq_number()), 90 | pan_id_compression: true, 91 | frame_version: Ieee802154FrameVersion::Ieee802154_2003, 92 | dst_pan_id: self.pan_id, 93 | dst_addr: Some(frag.sixlowpan.ll_dst_addr), 94 | src_pan_id: self.pan_id, 95 | src_addr: Some(frag.sixlowpan.ll_src_addr), 96 | }; 97 | 98 | self.dispatch_sixlowpan_frag(tx_token, ieee_repr, frag); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/iface/interface/tcp.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use crate::socket::tcp::Socket; 4 | 5 | impl InterfaceInner { 6 | pub(crate) fn process_tcp<'frame>( 7 | &mut self, 8 | sockets: &mut SocketSet, 9 | ip_repr: IpRepr, 10 | ip_payload: &'frame [u8], 11 | ) -> Option> { 12 | let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr()); 13 | let tcp_packet = check!(TcpPacket::new_checked(ip_payload)); 14 | let tcp_repr = check!(TcpRepr::parse( 15 | &tcp_packet, 16 | &src_addr, 17 | &dst_addr, 18 | &self.caps.checksum 19 | )); 20 | 21 | for tcp_socket in sockets 22 | .items_mut() 23 | .filter_map(|i| Socket::downcast_mut(&mut i.socket)) 24 | { 25 | if tcp_socket.accepts(self, &ip_repr, &tcp_repr) { 26 | return tcp_socket 27 | .process(self, &ip_repr, &tcp_repr) 28 | .map(|(ip, tcp)| Packet::new(ip, IpPayload::Tcp(tcp))); 29 | } 30 | } 31 | 32 | if tcp_repr.control == TcpControl::Rst 33 | || ip_repr.dst_addr().is_unspecified() 34 | || ip_repr.src_addr().is_unspecified() 35 | { 36 | // Never reply to a TCP RST packet with another TCP RST packet. We also never want to 37 | // send a TCP RST packet with unspecified addresses. 38 | None 39 | } else { 40 | // The packet wasn't handled by a socket, send a TCP RST packet. 41 | let (ip, tcp) = tcp::Socket::rst_reply(&ip_repr, &tcp_repr); 42 | Some(Packet::new(ip, IpPayload::Tcp(tcp))) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/iface/interface/udp.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[cfg(feature = "socket-dns")] 4 | use crate::socket::dns::Socket as DnsSocket; 5 | 6 | #[cfg(feature = "socket-udp")] 7 | use crate::socket::udp::Socket as UdpSocket; 8 | 9 | impl InterfaceInner { 10 | pub(super) fn process_udp<'frame>( 11 | &mut self, 12 | sockets: &mut SocketSet, 13 | meta: PacketMeta, 14 | handled_by_raw_socket: bool, 15 | ip_repr: IpRepr, 16 | ip_payload: &'frame [u8], 17 | ) -> Option> { 18 | let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr()); 19 | let udp_packet = check!(UdpPacket::new_checked(ip_payload)); 20 | let udp_repr = check!(UdpRepr::parse( 21 | &udp_packet, 22 | &src_addr, 23 | &dst_addr, 24 | &self.caps.checksum 25 | )); 26 | 27 | #[cfg(feature = "socket-udp")] 28 | for udp_socket in sockets 29 | .items_mut() 30 | .filter_map(|i| UdpSocket::downcast_mut(&mut i.socket)) 31 | { 32 | if udp_socket.accepts(self, &ip_repr, &udp_repr) { 33 | udp_socket.process(self, meta, &ip_repr, &udp_repr, udp_packet.payload()); 34 | return None; 35 | } 36 | } 37 | 38 | #[cfg(feature = "socket-dns")] 39 | for dns_socket in sockets 40 | .items_mut() 41 | .filter_map(|i| DnsSocket::downcast_mut(&mut i.socket)) 42 | { 43 | if dns_socket.accepts(&ip_repr, &udp_repr) { 44 | dns_socket.process(self, &ip_repr, &udp_repr, udp_packet.payload()); 45 | return None; 46 | } 47 | } 48 | 49 | // The packet wasn't handled by a socket, send an ICMP port unreachable packet. 50 | match ip_repr { 51 | #[cfg(feature = "proto-ipv4")] 52 | IpRepr::Ipv4(_) if handled_by_raw_socket => None, 53 | #[cfg(feature = "proto-ipv6")] 54 | IpRepr::Ipv6(_) if handled_by_raw_socket => None, 55 | #[cfg(feature = "proto-ipv4")] 56 | IpRepr::Ipv4(ipv4_repr) => { 57 | let payload_len = 58 | icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU, ipv4_repr.buffer_len()); 59 | let icmpv4_reply_repr = Icmpv4Repr::DstUnreachable { 60 | reason: Icmpv4DstUnreachable::PortUnreachable, 61 | header: ipv4_repr, 62 | data: &ip_payload[0..payload_len], 63 | }; 64 | self.icmpv4_reply(ipv4_repr, icmpv4_reply_repr) 65 | } 66 | #[cfg(feature = "proto-ipv6")] 67 | IpRepr::Ipv6(ipv6_repr) => { 68 | let payload_len = 69 | icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len()); 70 | let icmpv6_reply_repr = Icmpv6Repr::DstUnreachable { 71 | reason: Icmpv6DstUnreachable::PortUnreachable, 72 | header: ipv6_repr, 73 | data: &ip_payload[0..payload_len], 74 | }; 75 | self.icmpv6_reply(ipv6_repr, icmpv6_reply_repr) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/iface/mod.rs: -------------------------------------------------------------------------------- 1 | /*! Network interface logic. 2 | 3 | The `iface` module deals with the *network interfaces*. It filters incoming frames, 4 | provides lookup and caching of hardware addresses, and handles management packets. 5 | */ 6 | 7 | mod fragmentation; 8 | mod interface; 9 | #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] 10 | mod neighbor; 11 | mod route; 12 | #[cfg(feature = "proto-rpl")] 13 | mod rpl; 14 | mod socket_meta; 15 | mod socket_set; 16 | 17 | mod packet; 18 | 19 | #[cfg(feature = "multicast")] 20 | pub use self::interface::multicast::MulticastError; 21 | pub use self::interface::{ 22 | Config, Interface, InterfaceInner as Context, PollIngressSingleResult, PollResult, 23 | }; 24 | 25 | pub use self::route::{Route, RouteTableFull, Routes}; 26 | pub use self::socket_set::{SocketHandle, SocketSet, SocketStorage}; 27 | -------------------------------------------------------------------------------- /src/iface/rpl/consts.rs: -------------------------------------------------------------------------------- 1 | pub const SEQUENCE_WINDOW: u8 = 16; 2 | 3 | pub const DEFAULT_MIN_HOP_RANK_INCREASE: u16 = 256; 4 | 5 | pub const DEFAULT_DIO_INTERVAL_MIN: u32 = 12; 6 | pub const DEFAULT_DIO_REDUNDANCY_CONSTANT: usize = 10; 7 | /// This is 20 in the standard, but in Contiki they use: 8 | pub const DEFAULT_DIO_INTERVAL_DOUBLINGS: u32 = 8; 9 | -------------------------------------------------------------------------------- /src/iface/rpl/lollipop.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of sequence counters defined in [RFC 6550 § 7.2]. Values from 128 and greater 2 | //! are used as a linear sequence to indicate a restart and bootstrap the counter. Values less than 3 | //! or equal to 127 are used as a circular sequence number space of size 128. When operating in the 4 | //! circular region, if sequence numbers are detected to be too far apart, then they are not 5 | //! comparable. 6 | //! 7 | //! [RFC 6550 § 7.2]: https://datatracker.ietf.org/doc/html/rfc6550#section-7.2 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 11 | pub struct SequenceCounter(u8); 12 | 13 | impl Default for SequenceCounter { 14 | fn default() -> Self { 15 | // RFC6550 7.2 recommends 240 (256 - SEQUENCE_WINDOW) as the initialization value of the 16 | // counter. 17 | Self(240) 18 | } 19 | } 20 | 21 | impl SequenceCounter { 22 | /// Create a new sequence counter. 23 | /// 24 | /// Use `Self::default()` when a new sequence counter needs to be created with a value that is 25 | /// recommended in RFC6550 7.2, being 240. 26 | pub fn new(value: u8) -> Self { 27 | Self(value) 28 | } 29 | 30 | /// Return the value of the sequence counter. 31 | pub fn value(&self) -> u8 { 32 | self.0 33 | } 34 | 35 | /// Increment the sequence counter. 36 | /// 37 | /// When the sequence counter is greater than or equal to 128, the maximum value is 255. 38 | /// When the sequence counter is less than 128, the maximum value is 127. 39 | /// 40 | /// When an increment of the sequence counter would cause the counter to increment beyond its 41 | /// maximum value, the counter MUST wrap back to zero. 42 | pub fn increment(&mut self) { 43 | let max = if self.0 >= 128 { 255 } else { 127 }; 44 | 45 | self.0 = match self.0.checked_add(1) { 46 | Some(val) if val <= max => val, 47 | _ => 0, 48 | }; 49 | } 50 | } 51 | 52 | impl PartialEq for SequenceCounter { 53 | fn eq(&self, other: &Self) -> bool { 54 | let a = self.value() as usize; 55 | let b = other.value() as usize; 56 | 57 | if ((128..=255).contains(&a) && (0..=127).contains(&b)) 58 | || ((128..=255).contains(&b) && (0..=127).contains(&a)) 59 | { 60 | false 61 | } else { 62 | let result = if a > b { a - b } else { b - a }; 63 | 64 | if result <= super::consts::SEQUENCE_WINDOW as usize { 65 | // RFC1982 66 | a == b 67 | } else { 68 | // This case is actually not comparable. 69 | false 70 | } 71 | } 72 | } 73 | } 74 | 75 | impl PartialOrd for SequenceCounter { 76 | fn partial_cmp(&self, other: &Self) -> Option { 77 | use super::consts::SEQUENCE_WINDOW; 78 | use core::cmp::Ordering; 79 | 80 | let a = self.value() as usize; 81 | let b = other.value() as usize; 82 | 83 | if (128..256).contains(&a) && (0..128).contains(&b) { 84 | if 256 + b - a <= SEQUENCE_WINDOW as usize { 85 | Some(Ordering::Less) 86 | } else { 87 | Some(Ordering::Greater) 88 | } 89 | } else if (128..256).contains(&b) && (0..128).contains(&a) { 90 | if 256 + a - b <= SEQUENCE_WINDOW as usize { 91 | Some(Ordering::Greater) 92 | } else { 93 | Some(Ordering::Less) 94 | } 95 | } else if ((0..128).contains(&a) && (0..128).contains(&b)) 96 | || ((128..256).contains(&a) && (128..256).contains(&b)) 97 | { 98 | let result = if a > b { a - b } else { b - a }; 99 | 100 | if result <= SEQUENCE_WINDOW as usize { 101 | // RFC1982 102 | a.partial_cmp(&b) 103 | } else { 104 | // This case is not comparable. 105 | None 106 | } 107 | } else { 108 | unreachable!(); 109 | } 110 | } 111 | } 112 | 113 | #[cfg(test)] 114 | mod tests { 115 | use super::*; 116 | 117 | #[test] 118 | fn sequence_counter_increment() { 119 | let mut seq = SequenceCounter::new(253); 120 | seq.increment(); 121 | assert_eq!(seq.value(), 254); 122 | seq.increment(); 123 | assert_eq!(seq.value(), 255); 124 | seq.increment(); 125 | assert_eq!(seq.value(), 0); 126 | 127 | let mut seq = SequenceCounter::new(126); 128 | seq.increment(); 129 | assert_eq!(seq.value(), 127); 130 | seq.increment(); 131 | assert_eq!(seq.value(), 0); 132 | } 133 | 134 | #[test] 135 | fn sequence_counter_comparison() { 136 | use core::cmp::Ordering; 137 | 138 | assert!(SequenceCounter::new(240) != SequenceCounter::new(1)); 139 | assert!(SequenceCounter::new(1) != SequenceCounter::new(240)); 140 | assert!(SequenceCounter::new(1) != SequenceCounter::new(240)); 141 | assert!(SequenceCounter::new(240) == SequenceCounter::new(240)); 142 | assert!(SequenceCounter::new(240 - 17) != SequenceCounter::new(240)); 143 | 144 | assert_eq!( 145 | SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(5)), 146 | Some(Ordering::Greater) 147 | ); 148 | assert_eq!( 149 | SequenceCounter::new(250).partial_cmp(&SequenceCounter::new(5)), 150 | Some(Ordering::Less) 151 | ); 152 | assert_eq!( 153 | SequenceCounter::new(5).partial_cmp(&SequenceCounter::new(250)), 154 | Some(Ordering::Greater) 155 | ); 156 | assert_eq!( 157 | SequenceCounter::new(127).partial_cmp(&SequenceCounter::new(129)), 158 | Some(Ordering::Less) 159 | ); 160 | assert_eq!( 161 | SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(121)), 162 | Some(Ordering::Less) 163 | ); 164 | assert_eq!( 165 | SequenceCounter::new(121).partial_cmp(&SequenceCounter::new(120)), 166 | Some(Ordering::Greater) 167 | ); 168 | assert_eq!( 169 | SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(241)), 170 | Some(Ordering::Less) 171 | ); 172 | assert_eq!( 173 | SequenceCounter::new(241).partial_cmp(&SequenceCounter::new(240)), 174 | Some(Ordering::Greater) 175 | ); 176 | assert_eq!( 177 | SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(120)), 178 | Some(Ordering::Equal) 179 | ); 180 | assert_eq!( 181 | SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(240)), 182 | Some(Ordering::Equal) 183 | ); 184 | assert_eq!( 185 | SequenceCounter::new(130).partial_cmp(&SequenceCounter::new(241)), 186 | None 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/iface/rpl/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | mod consts; 4 | mod lollipop; 5 | mod of0; 6 | mod parents; 7 | mod rank; 8 | mod relations; 9 | mod trickle; 10 | -------------------------------------------------------------------------------- /src/iface/rpl/of0.rs: -------------------------------------------------------------------------------- 1 | use super::parents::*; 2 | use super::rank::Rank; 3 | 4 | pub struct ObjectiveFunction0; 5 | 6 | pub(crate) trait ObjectiveFunction { 7 | const OCP: u16; 8 | 9 | /// Return the new calculated Rank, based on information from the parent. 10 | fn rank(current_rank: Rank, parent_rank: Rank) -> Rank; 11 | 12 | /// Return the preferred parent from a given parent set. 13 | fn preferred_parent(parent_set: &ParentSet) -> Option<&Parent>; 14 | } 15 | 16 | impl ObjectiveFunction0 { 17 | const OCP: u16 = 0; 18 | 19 | const RANK_STRETCH: u16 = 0; 20 | const RANK_FACTOR: u16 = 1; 21 | const RANK_STEP: u16 = 3; 22 | 23 | fn rank_increase(parent_rank: Rank) -> u16 { 24 | (Self::RANK_FACTOR * Self::RANK_STEP + Self::RANK_STRETCH) 25 | * parent_rank.min_hop_rank_increase 26 | } 27 | } 28 | 29 | impl ObjectiveFunction for ObjectiveFunction0 { 30 | const OCP: u16 = 0; 31 | 32 | fn rank(_: Rank, parent_rank: Rank) -> Rank { 33 | assert_ne!(parent_rank, Rank::INFINITE); 34 | 35 | Rank::new( 36 | parent_rank.value + Self::rank_increase(parent_rank), 37 | parent_rank.min_hop_rank_increase, 38 | ) 39 | } 40 | 41 | fn preferred_parent(parent_set: &ParentSet) -> Option<&Parent> { 42 | let mut pref_parent: Option<&Parent> = None; 43 | 44 | for (_, parent) in parent_set.parents() { 45 | if pref_parent.is_none() || parent.rank() < pref_parent.unwrap().rank() { 46 | pref_parent = Some(parent); 47 | } 48 | } 49 | 50 | pref_parent 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use crate::iface::rpl::consts::DEFAULT_MIN_HOP_RANK_INCREASE; 57 | 58 | use super::*; 59 | 60 | #[test] 61 | fn rank_increase() { 62 | // 256 (root) + 3 * 256 63 | assert_eq!( 64 | ObjectiveFunction0::rank(Rank::INFINITE, Rank::ROOT), 65 | Rank::new(256 + 3 * 256, DEFAULT_MIN_HOP_RANK_INCREASE) 66 | ); 67 | 68 | // 1024 + 3 * 256 69 | assert_eq!( 70 | ObjectiveFunction0::rank( 71 | Rank::INFINITE, 72 | Rank::new(1024, DEFAULT_MIN_HOP_RANK_INCREASE) 73 | ), 74 | Rank::new(1024 + 3 * 256, DEFAULT_MIN_HOP_RANK_INCREASE) 75 | ); 76 | } 77 | 78 | #[test] 79 | #[should_panic] 80 | fn rank_increase_infinite() { 81 | assert_eq!( 82 | ObjectiveFunction0::rank(Rank::INFINITE, Rank::INFINITE), 83 | Rank::INFINITE 84 | ); 85 | } 86 | 87 | #[test] 88 | fn empty_set() { 89 | assert_eq!( 90 | ObjectiveFunction0::preferred_parent(&ParentSet::default()), 91 | None 92 | ); 93 | } 94 | 95 | #[test] 96 | fn non_empty_set() { 97 | use crate::wire::Ipv6Address; 98 | 99 | let mut parents = ParentSet::default(); 100 | 101 | parents.add( 102 | Ipv6Address::UNSPECIFIED, 103 | Parent::new(0, Rank::ROOT, Default::default(), Ipv6Address::UNSPECIFIED), 104 | ); 105 | 106 | let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 1); 107 | 108 | parents.add( 109 | address, 110 | Parent::new( 111 | 0, 112 | Rank::new(1024, DEFAULT_MIN_HOP_RANK_INCREASE), 113 | Default::default(), 114 | Ipv6Address::UNSPECIFIED, 115 | ), 116 | ); 117 | 118 | assert_eq!( 119 | ObjectiveFunction0::preferred_parent(&parents), 120 | Some(&Parent::new( 121 | 0, 122 | Rank::ROOT, 123 | Default::default(), 124 | Ipv6Address::UNSPECIFIED, 125 | )) 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/iface/rpl/parents.rs: -------------------------------------------------------------------------------- 1 | use crate::wire::Ipv6Address; 2 | 3 | use super::{lollipop::SequenceCounter, rank::Rank}; 4 | use crate::config::RPL_PARENTS_BUFFER_COUNT; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq)] 7 | pub(crate) struct Parent { 8 | rank: Rank, 9 | preference: u8, 10 | version_number: SequenceCounter, 11 | dodag_id: Ipv6Address, 12 | } 13 | 14 | impl Parent { 15 | /// Create a new parent. 16 | pub(crate) fn new( 17 | preference: u8, 18 | rank: Rank, 19 | version_number: SequenceCounter, 20 | dodag_id: Ipv6Address, 21 | ) -> Self { 22 | Self { 23 | rank, 24 | preference, 25 | version_number, 26 | dodag_id, 27 | } 28 | } 29 | 30 | /// Return the Rank of the parent. 31 | pub(crate) fn rank(&self) -> &Rank { 32 | &self.rank 33 | } 34 | } 35 | 36 | #[derive(Debug, Default)] 37 | pub(crate) struct ParentSet { 38 | parents: heapless::LinearMap, 39 | } 40 | 41 | impl ParentSet { 42 | /// Add a new parent to the parent set. The Rank of the new parent should be lower than the 43 | /// Rank of the node that holds this parent set. 44 | pub(crate) fn add(&mut self, address: Ipv6Address, parent: Parent) { 45 | if let Some(p) = self.parents.get_mut(&address) { 46 | *p = parent; 47 | } else if let Err(p) = self.parents.insert(address, parent) { 48 | if let Some((w_a, w_p)) = self.worst_parent() { 49 | if w_p.rank.dag_rank() > parent.rank.dag_rank() { 50 | self.parents.remove(&w_a.clone()).unwrap(); 51 | self.parents.insert(address, parent).unwrap(); 52 | } else { 53 | net_debug!("could not add {} to parent set, buffer is full", address); 54 | } 55 | } else { 56 | unreachable!() 57 | } 58 | } 59 | } 60 | 61 | /// Find a parent based on its address. 62 | pub(crate) fn find(&self, address: &Ipv6Address) -> Option<&Parent> { 63 | self.parents.get(address) 64 | } 65 | 66 | /// Find a mutable parent based on its address. 67 | pub(crate) fn find_mut(&mut self, address: &Ipv6Address) -> Option<&mut Parent> { 68 | self.parents.get_mut(address) 69 | } 70 | 71 | /// Return a slice to the parent set. 72 | pub(crate) fn parents(&self) -> impl Iterator { 73 | self.parents.iter() 74 | } 75 | 76 | /// Find the worst parent that is currently in the parent set. 77 | fn worst_parent(&self) -> Option<(&Ipv6Address, &Parent)> { 78 | self.parents.iter().max_by_key(|(k, v)| v.rank.dag_rank()) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | 86 | #[test] 87 | fn add_parent() { 88 | let mut set = ParentSet::default(); 89 | set.add( 90 | Ipv6Address::UNSPECIFIED, 91 | Parent::new(0, Rank::ROOT, Default::default(), Ipv6Address::UNSPECIFIED), 92 | ); 93 | 94 | assert_eq!( 95 | set.find(&Ipv6Address::UNSPECIFIED), 96 | Some(&Parent::new( 97 | 0, 98 | Rank::ROOT, 99 | Default::default(), 100 | Ipv6Address::UNSPECIFIED 101 | )) 102 | ); 103 | } 104 | 105 | #[test] 106 | fn add_more_parents() { 107 | use super::super::consts::DEFAULT_MIN_HOP_RANK_INCREASE; 108 | let mut set = ParentSet::default(); 109 | 110 | let mut last_address = Ipv6Address::UNSPECIFIED; 111 | for i in 0..RPL_PARENTS_BUFFER_COUNT { 112 | let i = i as u16; 113 | let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, i as _); 114 | last_address = address; 115 | 116 | set.add( 117 | address, 118 | Parent::new( 119 | 0, 120 | Rank::new(256 * i, DEFAULT_MIN_HOP_RANK_INCREASE), 121 | Default::default(), 122 | address, 123 | ), 124 | ); 125 | 126 | assert_eq!( 127 | set.find(&address), 128 | Some(&Parent::new( 129 | 0, 130 | Rank::new(256 * i, DEFAULT_MIN_HOP_RANK_INCREASE), 131 | Default::default(), 132 | address, 133 | )) 134 | ); 135 | } 136 | 137 | // This one is not added to the set, because its Rank is worse than any other parent in the 138 | // set. 139 | let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 8); 140 | set.add( 141 | address, 142 | Parent::new( 143 | 0, 144 | Rank::new(256 * 8, DEFAULT_MIN_HOP_RANK_INCREASE), 145 | Default::default(), 146 | address, 147 | ), 148 | ); 149 | assert_eq!(set.find(&address), None); 150 | 151 | /// This Parent has a better rank than the last one in the set. 152 | let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 9); 153 | set.add( 154 | address, 155 | Parent::new( 156 | 0, 157 | Rank::new(0, DEFAULT_MIN_HOP_RANK_INCREASE), 158 | Default::default(), 159 | address, 160 | ), 161 | ); 162 | assert_eq!( 163 | set.find(&address), 164 | Some(&Parent::new( 165 | 0, 166 | Rank::new(0, DEFAULT_MIN_HOP_RANK_INCREASE), 167 | Default::default(), 168 | address 169 | )) 170 | ); 171 | assert_eq!(set.find(&last_address), None); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/iface/rpl/rank.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the Rank comparison in RPL. 2 | //! 3 | //! A Rank can be thought of as a fixed-point number, where the position of the radix point between 4 | //! the integer part and the fractional part is determined by `MinHopRankIncrease`. 5 | //! `MinHopRankIncrease` is the minimum increase in Rank between a node and any of its DODAG 6 | //! parents. 7 | //! This value is provisined by the DODAG root. 8 | //! 9 | //! When Rank is compared, the integer portion of the Rank is to be used. 10 | //! 11 | //! Meaning of the comparison: 12 | //! - **Rank M is less than Rank N**: the position of M is closer to the DODAG root than the position 13 | //! of N. Node M may safely be a DODAG parent for node N. 14 | //! - **Ranks are equal**: the positions of both nodes within the DODAG and with respect to the DODAG 15 | //! are similar or identical. Routing through a node with equal Rank may cause a routing loop. 16 | //! - **Rank M is greater than Rank N**: the position of node M is farther from the DODAG root 17 | //! than the position of N. Node M may in fact be in the sub-DODAG of node N. If node N selects 18 | //! node M as a DODAG parent, there is a risk of creating a loop. 19 | 20 | use super::consts::DEFAULT_MIN_HOP_RANK_INCREASE; 21 | 22 | /// The Rank is the expression of the relative position within a DODAG Version with regard to 23 | /// neighbors, and it is not necessarily a good indication or a proper expression of a distance or 24 | /// a path cost to the root. 25 | #[derive(Debug, Clone, Copy, Eq)] 26 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 27 | pub struct Rank { 28 | pub(super) value: u16, 29 | pub(super) min_hop_rank_increase: u16, 30 | } 31 | 32 | impl core::fmt::Display for Rank { 33 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 34 | write!(f, "Rank({})", self.dag_rank()) 35 | } 36 | } 37 | 38 | impl Rank { 39 | pub const INFINITE: Self = Rank::new(0xffff, DEFAULT_MIN_HOP_RANK_INCREASE); 40 | 41 | /// The ROOT_RANK is the smallest rank possible. 42 | /// DAG_RANK(ROOT_RANK) should be 1. See RFC6550 § 17. 43 | pub const ROOT: Self = Rank::new(DEFAULT_MIN_HOP_RANK_INCREASE, DEFAULT_MIN_HOP_RANK_INCREASE); 44 | 45 | /// Create a new Rank from some value and a `MinHopRankIncrease`. 46 | /// The `MinHopRankIncrease` is used for calculating the integer part for comparing to other 47 | /// Ranks. 48 | pub const fn new(value: u16, min_hop_rank_increase: u16) -> Self { 49 | assert!(min_hop_rank_increase > 0); 50 | 51 | Self { 52 | value, 53 | min_hop_rank_increase, 54 | } 55 | } 56 | 57 | /// Return the integer part of the Rank. 58 | pub fn dag_rank(&self) -> u16 { 59 | self.value / self.min_hop_rank_increase 60 | } 61 | 62 | /// Return the raw Rank value. 63 | pub fn raw_value(&self) -> u16 { 64 | self.value 65 | } 66 | } 67 | 68 | impl PartialEq for Rank { 69 | fn eq(&self, other: &Self) -> bool { 70 | self.dag_rank() == other.dag_rank() 71 | } 72 | } 73 | 74 | impl PartialOrd for Rank { 75 | fn partial_cmp(&self, other: &Self) -> Option { 76 | self.dag_rank().partial_cmp(&other.dag_rank()) 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | 84 | #[test] 85 | fn calculate_rank() { 86 | let r = Rank::new(27, 16); 87 | assert_eq!(r.dag_rank(), 1) 88 | } 89 | 90 | #[test] 91 | fn comparison() { 92 | let r1 = Rank::ROOT; 93 | let r2 = Rank::new(16, 16); 94 | assert!(r1 == r2); 95 | 96 | let r1 = Rank::new(16, 16); 97 | let r2 = Rank::new(32, 16); 98 | assert!(r1 < r2); 99 | 100 | let r1 = Rank::ROOT; 101 | let r2 = Rank::INFINITE; 102 | assert!(r1 < r2); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/iface/rpl/relations.rs: -------------------------------------------------------------------------------- 1 | use crate::time::Instant; 2 | use crate::wire::Ipv6Address; 3 | 4 | use crate::config::RPL_RELATIONS_BUFFER_COUNT; 5 | 6 | #[derive(Debug)] 7 | pub struct Relation { 8 | destination: Ipv6Address, 9 | next_hop: Ipv6Address, 10 | expiration: Instant, 11 | } 12 | 13 | #[derive(Default, Debug)] 14 | pub struct Relations { 15 | relations: heapless::Vec, 16 | } 17 | 18 | impl Relations { 19 | /// Add a new relation to the buffer. If there was already a relation in the buffer, then 20 | /// update it. 21 | pub fn add_relation( 22 | &mut self, 23 | destination: Ipv6Address, 24 | next_hop: Ipv6Address, 25 | expiration: Instant, 26 | ) { 27 | if let Some(r) = self 28 | .relations 29 | .iter_mut() 30 | .find(|r| r.destination == destination) 31 | { 32 | r.next_hop = next_hop; 33 | r.expiration = expiration; 34 | } else { 35 | let relation = Relation { 36 | destination, 37 | next_hop, 38 | expiration, 39 | }; 40 | 41 | if let Err(e) = self.relations.push(relation) { 42 | net_debug!("Unable to add relation, buffer is full"); 43 | } 44 | } 45 | } 46 | 47 | /// Remove all relation entries for a specific destination. 48 | pub fn remove_relation(&mut self, destination: Ipv6Address) { 49 | self.relations.retain(|r| r.destination != destination) 50 | } 51 | 52 | /// Return the next hop for a specific IPv6 address, if there is one. 53 | pub fn find_next_hop(&mut self, destination: Ipv6Address) -> Option { 54 | self.relations.iter().find_map(|r| { 55 | if r.destination == destination { 56 | Some(r.next_hop) 57 | } else { 58 | None 59 | } 60 | }) 61 | } 62 | 63 | /// Purge expired relations. 64 | pub fn purge(&mut self, now: Instant) { 65 | self.relations.retain(|r| r.expiration > now) 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | use crate::time::Duration; 73 | 74 | fn addresses(count: usize) -> Vec { 75 | (0..count) 76 | .map(|i| Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, i as _)) 77 | .collect() 78 | } 79 | 80 | #[test] 81 | fn add_relation() { 82 | let addrs = addresses(2); 83 | 84 | let mut relations = Relations::default(); 85 | relations.add_relation(addrs[0], addrs[1], Instant::now()); 86 | assert_eq!(relations.relations.len(), 1); 87 | } 88 | 89 | #[test] 90 | fn add_relations_full_buffer() { 91 | let addrs = addresses(crate::config::RPL_RELATIONS_BUFFER_COUNT + 1); 92 | 93 | // Try to add RPL_RELATIONS_BUFFER_COUNT + 1 to the buffer. 94 | // The size of the buffer should still be RPL_RELATIONS_BUFFER_COUNT. 95 | let mut relations = Relations::default(); 96 | for a in addrs { 97 | relations.add_relation(a, a, Instant::now()); 98 | } 99 | 100 | assert_eq!(relations.relations.len(), RPL_RELATIONS_BUFFER_COUNT); 101 | } 102 | 103 | #[test] 104 | fn update_relation() { 105 | let addrs = addresses(3); 106 | 107 | let mut relations = Relations::default(); 108 | relations.add_relation(addrs[0], addrs[1], Instant::now()); 109 | assert_eq!(relations.relations.len(), 1); 110 | 111 | relations.add_relation(addrs[0], addrs[2], Instant::now()); 112 | assert_eq!(relations.relations.len(), 1); 113 | 114 | assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[2])); 115 | } 116 | 117 | #[test] 118 | fn find_next_hop() { 119 | let addrs = addresses(3); 120 | 121 | let mut relations = Relations::default(); 122 | relations.add_relation(addrs[0], addrs[1], Instant::now()); 123 | assert_eq!(relations.relations.len(), 1); 124 | assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[1])); 125 | 126 | relations.add_relation(addrs[0], addrs[2], Instant::now()); 127 | assert_eq!(relations.relations.len(), 1); 128 | assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[2])); 129 | 130 | // Find the next hop of a destination not in the buffer. 131 | assert_eq!(relations.find_next_hop(addrs[1]), None); 132 | } 133 | 134 | #[test] 135 | fn remove_relation() { 136 | let addrs = addresses(2); 137 | 138 | let mut relations = Relations::default(); 139 | relations.add_relation(addrs[0], addrs[1], Instant::now()); 140 | assert_eq!(relations.relations.len(), 1); 141 | 142 | relations.remove_relation(addrs[0]); 143 | assert!(relations.relations.is_empty()); 144 | } 145 | 146 | #[test] 147 | fn purge_relation() { 148 | let addrs = addresses(2); 149 | 150 | let mut relations = Relations::default(); 151 | relations.add_relation(addrs[0], addrs[1], Instant::now() - Duration::from_secs(1)); 152 | 153 | assert_eq!(relations.relations.len(), 1); 154 | 155 | relations.purge(Instant::now()); 156 | assert!(relations.relations.is_empty()); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/iface/socket_meta.rs: -------------------------------------------------------------------------------- 1 | use super::SocketHandle; 2 | use crate::{ 3 | socket::PollAt, 4 | time::{Duration, Instant}, 5 | wire::IpAddress, 6 | }; 7 | 8 | /// Neighbor dependency. 9 | /// 10 | /// This enum tracks whether the socket should be polled based on the neighbor 11 | /// it is going to send packets to. 12 | #[derive(Debug, Default)] 13 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 14 | enum NeighborState { 15 | /// Socket can be polled immediately. 16 | #[default] 17 | Active, 18 | /// Socket should not be polled until either `silent_until` passes or 19 | /// `neighbor` appears in the neighbor cache. 20 | Waiting { 21 | neighbor: IpAddress, 22 | silent_until: Instant, 23 | }, 24 | } 25 | 26 | /// Network socket metadata. 27 | /// 28 | /// This includes things that only external (to the socket, that is) code 29 | /// is interested in, but which are more conveniently stored inside the socket 30 | /// itself. 31 | #[derive(Debug, Default)] 32 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 33 | pub(crate) struct Meta { 34 | /// Handle of this socket within its enclosing `SocketSet`. 35 | /// Mainly useful for debug output. 36 | pub(crate) handle: SocketHandle, 37 | /// See [NeighborState](struct.NeighborState.html). 38 | neighbor_state: NeighborState, 39 | } 40 | 41 | impl Meta { 42 | /// Minimum delay between neighbor discovery requests for this particular 43 | /// socket, in milliseconds. 44 | /// 45 | /// See also `iface::NeighborCache::SILENT_TIME`. 46 | pub(crate) const DISCOVERY_SILENT_TIME: Duration = Duration::from_millis(1_000); 47 | 48 | pub(crate) fn poll_at(&self, socket_poll_at: PollAt, has_neighbor: F) -> PollAt 49 | where 50 | F: Fn(IpAddress) -> bool, 51 | { 52 | match self.neighbor_state { 53 | NeighborState::Active => socket_poll_at, 54 | NeighborState::Waiting { neighbor, .. } if has_neighbor(neighbor) => socket_poll_at, 55 | NeighborState::Waiting { silent_until, .. } => PollAt::Time(silent_until), 56 | } 57 | } 58 | 59 | pub(crate) fn egress_permitted(&mut self, timestamp: Instant, has_neighbor: F) -> bool 60 | where 61 | F: Fn(IpAddress) -> bool, 62 | { 63 | match self.neighbor_state { 64 | NeighborState::Active => true, 65 | NeighborState::Waiting { 66 | neighbor, 67 | silent_until, 68 | } => { 69 | if has_neighbor(neighbor) { 70 | net_trace!( 71 | "{}: neighbor {} discovered, unsilencing", 72 | self.handle, 73 | neighbor 74 | ); 75 | self.neighbor_state = NeighborState::Active; 76 | true 77 | } else if timestamp >= silent_until { 78 | net_trace!( 79 | "{}: neighbor {} silence timer expired, rediscovering", 80 | self.handle, 81 | neighbor 82 | ); 83 | true 84 | } else { 85 | false 86 | } 87 | } 88 | } 89 | } 90 | 91 | pub(crate) fn neighbor_missing(&mut self, timestamp: Instant, neighbor: IpAddress) { 92 | net_trace!( 93 | "{}: neighbor {} missing, silencing until t+{}", 94 | self.handle, 95 | neighbor, 96 | Self::DISCOVERY_SILENT_TIME 97 | ); 98 | self.neighbor_state = NeighborState::Waiting { 99 | neighbor, 100 | silent_until: timestamp + Self::DISCOVERY_SILENT_TIME, 101 | }; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/iface/socket_set.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use managed::ManagedSlice; 3 | 4 | use super::socket_meta::Meta; 5 | use crate::socket::{AnySocket, Socket}; 6 | 7 | /// Opaque struct with space for storing one socket. 8 | /// 9 | /// This is public so you can use it to allocate space for storing 10 | /// sockets when creating an Interface. 11 | #[derive(Debug, Default)] 12 | pub struct SocketStorage<'a> { 13 | inner: Option>, 14 | } 15 | 16 | impl<'a> SocketStorage<'a> { 17 | pub const EMPTY: Self = Self { inner: None }; 18 | } 19 | 20 | /// An item of a socket set. 21 | #[derive(Debug)] 22 | pub(crate) struct Item<'a> { 23 | pub(crate) meta: Meta, 24 | pub(crate) socket: Socket<'a>, 25 | } 26 | 27 | /// A handle, identifying a socket in an Interface. 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] 29 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 30 | pub struct SocketHandle(usize); 31 | 32 | impl fmt::Display for SocketHandle { 33 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 34 | write!(f, "#{}", self.0) 35 | } 36 | } 37 | 38 | /// An extensible set of sockets. 39 | /// 40 | /// The lifetime `'a` is used when storing a `Socket<'a>`. If you're using 41 | /// owned buffers for your sockets (passed in as `Vec`s) you can use 42 | /// `SocketSet<'static>`. 43 | #[derive(Debug)] 44 | pub struct SocketSet<'a> { 45 | sockets: ManagedSlice<'a, SocketStorage<'a>>, 46 | } 47 | 48 | impl<'a> SocketSet<'a> { 49 | /// Create a socket set using the provided storage. 50 | pub fn new(sockets: SocketsT) -> SocketSet<'a> 51 | where 52 | SocketsT: Into>>, 53 | { 54 | let sockets = sockets.into(); 55 | SocketSet { sockets } 56 | } 57 | 58 | /// Add a socket to the set, and return its handle. 59 | /// 60 | /// # Panics 61 | /// This function panics if the storage is fixed-size (not a `Vec`) and is full. 62 | pub fn add>(&mut self, socket: T) -> SocketHandle { 63 | fn put<'a>(index: usize, slot: &mut SocketStorage<'a>, socket: Socket<'a>) -> SocketHandle { 64 | net_trace!("[{}]: adding", index); 65 | let handle = SocketHandle(index); 66 | let mut meta = Meta::default(); 67 | meta.handle = handle; 68 | *slot = SocketStorage { 69 | inner: Some(Item { meta, socket }), 70 | }; 71 | handle 72 | } 73 | 74 | let socket = socket.upcast(); 75 | 76 | for (index, slot) in self.sockets.iter_mut().enumerate() { 77 | if slot.inner.is_none() { 78 | return put(index, slot, socket); 79 | } 80 | } 81 | 82 | match &mut self.sockets { 83 | ManagedSlice::Borrowed(_) => panic!("adding a socket to a full SocketSet"), 84 | #[cfg(feature = "alloc")] 85 | ManagedSlice::Owned(sockets) => { 86 | sockets.push(SocketStorage { inner: None }); 87 | let index = sockets.len() - 1; 88 | put(index, &mut sockets[index], socket) 89 | } 90 | } 91 | } 92 | 93 | /// Get a socket from the set by its handle, as mutable. 94 | /// 95 | /// # Panics 96 | /// This function may panic if the handle does not belong to this socket set 97 | /// or the socket has the wrong type. 98 | pub fn get>(&self, handle: SocketHandle) -> &T { 99 | match self.sockets[handle.0].inner.as_ref() { 100 | Some(item) => { 101 | T::downcast(&item.socket).expect("handle refers to a socket of a wrong type") 102 | } 103 | None => panic!("handle does not refer to a valid socket"), 104 | } 105 | } 106 | 107 | /// Get a mutable socket from the set by its handle, as mutable. 108 | /// 109 | /// # Panics 110 | /// This function may panic if the handle does not belong to this socket set 111 | /// or the socket has the wrong type. 112 | pub fn get_mut>(&mut self, handle: SocketHandle) -> &mut T { 113 | match self.sockets[handle.0].inner.as_mut() { 114 | Some(item) => T::downcast_mut(&mut item.socket) 115 | .expect("handle refers to a socket of a wrong type"), 116 | None => panic!("handle does not refer to a valid socket"), 117 | } 118 | } 119 | 120 | /// Remove a socket from the set, without changing its state. 121 | /// 122 | /// # Panics 123 | /// This function may panic if the handle does not belong to this socket set. 124 | pub fn remove(&mut self, handle: SocketHandle) -> Socket<'a> { 125 | net_trace!("[{}]: removing", handle.0); 126 | match self.sockets[handle.0].inner.take() { 127 | Some(item) => item.socket, 128 | None => panic!("handle does not refer to a valid socket"), 129 | } 130 | } 131 | 132 | /// Get an iterator to the inner sockets. 133 | pub fn iter(&self) -> impl Iterator)> { 134 | self.items().map(|i| (i.meta.handle, &i.socket)) 135 | } 136 | 137 | /// Get a mutable iterator to the inner sockets. 138 | pub fn iter_mut(&mut self) -> impl Iterator)> { 139 | self.items_mut().map(|i| (i.meta.handle, &mut i.socket)) 140 | } 141 | 142 | /// Iterate every socket in this set. 143 | pub(crate) fn items(&self) -> impl Iterator> + '_ { 144 | self.sockets.iter().filter_map(|x| x.inner.as_ref()) 145 | } 146 | 147 | /// Iterate every socket in this set. 148 | pub(crate) fn items_mut(&mut self) -> impl Iterator> + '_ { 149 | self.sockets.iter_mut().filter_map(|x| x.inner.as_mut()) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(test))] 2 | #[cfg(feature = "log")] 3 | macro_rules! net_log { 4 | (trace, $($arg:expr),*) => { log::trace!($($arg),*) }; 5 | (debug, $($arg:expr),*) => { log::debug!($($arg),*) }; 6 | } 7 | 8 | #[cfg(test)] 9 | #[cfg(feature = "log")] 10 | macro_rules! net_log { 11 | (trace, $($arg:expr),*) => { println!($($arg),*) }; 12 | (debug, $($arg:expr),*) => { println!($($arg),*) }; 13 | } 14 | 15 | #[cfg(feature = "defmt")] 16 | macro_rules! net_log { 17 | (trace, $($arg:expr),*) => { defmt::trace!($($arg),*) }; 18 | (debug, $($arg:expr),*) => { defmt::debug!($($arg),*) }; 19 | } 20 | 21 | #[cfg(not(any(feature = "log", feature = "defmt")))] 22 | macro_rules! net_log { 23 | ($level:ident, $($arg:expr),*) => {{ $( let _ = $arg; )* }} 24 | } 25 | 26 | macro_rules! net_trace { 27 | ($($arg:expr),*) => (net_log!(trace, $($arg),*)); 28 | } 29 | 30 | macro_rules! net_debug { 31 | ($($arg:expr),*) => (net_log!(debug, $($arg),*)); 32 | } 33 | 34 | macro_rules! enum_with_unknown { 35 | ( 36 | $( #[$enum_attr:meta] )* 37 | pub enum $name:ident($ty:ty) { 38 | $( 39 | $( #[$variant_attr:meta] )* 40 | $variant:ident = $value:expr 41 | ),+ $(,)? 42 | } 43 | ) => { 44 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 45 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 46 | $( #[$enum_attr] )* 47 | pub enum $name { 48 | $( 49 | $( #[$variant_attr] )* 50 | $variant 51 | ),*, 52 | Unknown($ty) 53 | } 54 | 55 | impl ::core::convert::From<$ty> for $name { 56 | fn from(value: $ty) -> Self { 57 | match value { 58 | $( $value => $name::$variant ),*, 59 | other => $name::Unknown(other) 60 | } 61 | } 62 | } 63 | 64 | impl ::core::convert::From<$name> for $ty { 65 | fn from(value: $name) -> Self { 66 | match value { 67 | $( $name::$variant => $value ),*, 68 | $name::Unknown(other) => other 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | #[cfg(feature = "proto-rpl")] 76 | macro_rules! get { 77 | ($buffer:expr, into: $into:ty, fun: $fun:ident, field: $field:expr $(,)?) => { 78 | { 79 | <$into>::$fun(&$buffer.as_ref()[$field]) 80 | } 81 | }; 82 | 83 | ($buffer:expr, into: $into:ty, field: $field:expr $(,)?) => { 84 | get!($buffer, into: $into, field: $field, shift: 0, mask: 0b1111_1111) 85 | }; 86 | 87 | ($buffer:expr, into: $into:ty, field: $field:expr, mask: $bit_mask:expr $(,)?) => { 88 | get!($buffer, into: $into, field: $field, shift: 0, mask: $bit_mask) 89 | }; 90 | 91 | ($buffer:expr, into: $into:ty, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => { 92 | { 93 | <$into>::from((&$buffer.as_ref()[$field] >> $bit_shift) & $bit_mask) 94 | } 95 | }; 96 | 97 | ($buffer:expr, field: $field:expr $(,)?) => { 98 | get!($buffer, field: $field, shift: 0, mask: 0b1111_1111) 99 | }; 100 | 101 | ($buffer:expr, field: $field:expr, mask: $bit_mask:expr $(,)?) => { 102 | get!($buffer, field: $field, shift: 0, mask: $bit_mask) 103 | }; 104 | 105 | ($buffer:expr, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) 106 | => 107 | { 108 | { 109 | (&$buffer.as_ref()[$field] >> $bit_shift) & $bit_mask 110 | } 111 | }; 112 | 113 | ($buffer:expr, u16, field: $field:expr $(,)?) => { 114 | { 115 | NetworkEndian::read_u16(&$buffer.as_ref()[$field]) 116 | } 117 | }; 118 | 119 | ($buffer:expr, bool, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => { 120 | { 121 | (($buffer.as_ref()[$field] >> $bit_shift) & $bit_mask) == 0b1 122 | } 123 | }; 124 | 125 | ($buffer:expr, u32, field: $field:expr $(,)?) => { 126 | { 127 | NetworkEndian::read_u32(&$buffer.as_ref()[$field]) 128 | } 129 | }; 130 | } 131 | 132 | #[cfg(feature = "proto-rpl")] 133 | macro_rules! set { 134 | ($buffer:expr, address: $address:ident, field: $field:expr $(,)?) => {{ 135 | $buffer.as_mut()[$field].copy_from_slice(&$address.octets()); 136 | }}; 137 | 138 | ($buffer:expr, $value:ident, field: $field:expr $(,)?) => { 139 | set!($buffer, $value, field: $field, shift: 0, mask: 0b1111_1111) 140 | }; 141 | 142 | ($buffer:expr, $value:ident, field: $field:expr, mask: $bit_mask:expr $(,)?) => { 143 | set!($buffer, $value, field: $field, shift: 0, mask: $bit_mask) 144 | }; 145 | 146 | ($buffer:expr, $value:ident, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {{ 147 | let raw = 148 | ($buffer.as_ref()[$field] & !($bit_mask << $bit_shift)) | ($value << $bit_shift); 149 | $buffer.as_mut()[$field] = raw; 150 | }}; 151 | 152 | ($buffer:expr, $value:ident, bool, field: $field:expr, mask: $bit_mask:expr $(,)?) => { 153 | set!($buffer, $value, bool, field: $field, shift: 0, mask: $bit_mask); 154 | }; 155 | 156 | ($buffer:expr, $value:ident, bool, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {{ 157 | let raw = ($buffer.as_ref()[$field] & !($bit_mask << $bit_shift)) 158 | | (if $value { 0b1 } else { 0b0 } << $bit_shift); 159 | $buffer.as_mut()[$field] = raw; 160 | }}; 161 | 162 | ($buffer:expr, $value:ident, u16, field: $field:expr $(,)?) => {{ 163 | NetworkEndian::write_u16(&mut $buffer.as_mut()[$field], $value); 164 | }}; 165 | 166 | ($buffer:expr, $value:ident, u32, field: $field:expr $(,)?) => {{ 167 | NetworkEndian::write_u32(&mut $buffer.as_mut()[$field], $value); 168 | }}; 169 | } 170 | -------------------------------------------------------------------------------- /src/phy/fuzz_injector.rs: -------------------------------------------------------------------------------- 1 | use crate::phy::{self, Device, DeviceCapabilities}; 2 | use crate::time::Instant; 3 | use alloc::vec::Vec; 4 | 5 | // This could be fixed once associated consts are stable. 6 | const MTU: usize = 1536; 7 | 8 | /// Represents a fuzzer. It is expected to replace bytes in the packet with fuzzed data. 9 | pub trait Fuzzer { 10 | /// Modify a single packet with fuzzed data. 11 | fn fuzz_packet(&self, packet_data: &mut [u8]); 12 | } 13 | 14 | /// A fuzz injector device. 15 | /// 16 | /// A fuzz injector is a device that alters packets traversing through it according to the 17 | /// directions of a guided fuzzer. It is designed to support fuzzing internal state machines inside 18 | /// smoltcp, and is not for production use. 19 | #[allow(unused)] 20 | #[derive(Debug)] 21 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 22 | pub struct FuzzInjector { 23 | inner: D, 24 | fuzz_tx: FTx, 25 | fuzz_rx: FRx, 26 | } 27 | 28 | #[allow(unused)] 29 | impl FuzzInjector { 30 | /// Create a fuzz injector device. 31 | pub fn new(inner: D, fuzz_tx: FTx, fuzz_rx: FRx) -> FuzzInjector { 32 | FuzzInjector { 33 | inner, 34 | fuzz_tx, 35 | fuzz_rx, 36 | } 37 | } 38 | 39 | /// Return the underlying device, consuming the fuzz injector. 40 | pub fn into_inner(self) -> D { 41 | self.inner 42 | } 43 | } 44 | 45 | impl Device for FuzzInjector 46 | where 47 | FTx: Fuzzer, 48 | FRx: Fuzzer, 49 | { 50 | type RxToken<'a> 51 | = RxToken<'a, D::RxToken<'a>, FRx> 52 | where 53 | Self: 'a; 54 | type TxToken<'a> 55 | = TxToken<'a, D::TxToken<'a>, FTx> 56 | where 57 | Self: 'a; 58 | 59 | fn capabilities(&self) -> DeviceCapabilities { 60 | let mut caps = self.inner.capabilities(); 61 | if caps.max_transmission_unit > MTU { 62 | caps.max_transmission_unit = MTU; 63 | } 64 | caps 65 | } 66 | 67 | fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 68 | self.inner.receive(timestamp).map(|(rx_token, tx_token)| { 69 | let rx = RxToken { 70 | fuzzer: &mut self.fuzz_rx, 71 | token: rx_token, 72 | }; 73 | let tx = TxToken { 74 | fuzzer: &mut self.fuzz_tx, 75 | token: tx_token, 76 | }; 77 | (rx, tx) 78 | }) 79 | } 80 | 81 | fn transmit(&mut self, timestamp: Instant) -> Option> { 82 | self.inner.transmit(timestamp).map(|token| TxToken { 83 | fuzzer: &mut self.fuzz_tx, 84 | token: token, 85 | }) 86 | } 87 | } 88 | 89 | #[doc(hidden)] 90 | pub struct RxToken<'a, Rx: phy::RxToken, F: Fuzzer + 'a> { 91 | fuzzer: &'a F, 92 | token: Rx, 93 | } 94 | 95 | impl<'a, Rx: phy::RxToken, FRx: Fuzzer> phy::RxToken for RxToken<'a, Rx, FRx> { 96 | fn consume(self, f: F) -> R 97 | where 98 | F: FnOnce(&[u8]) -> R, 99 | { 100 | self.token.consume(|buffer| { 101 | let mut new_buffer: Vec = buffer.to_vec(); 102 | self.fuzzer.fuzz_packet(&mut new_buffer); 103 | f(&mut new_buffer) 104 | }) 105 | } 106 | 107 | fn meta(&self) -> phy::PacketMeta { 108 | self.token.meta() 109 | } 110 | } 111 | 112 | #[doc(hidden)] 113 | pub struct TxToken<'a, Tx: phy::TxToken, F: Fuzzer + 'a> { 114 | fuzzer: &'a F, 115 | token: Tx, 116 | } 117 | 118 | impl<'a, Tx: phy::TxToken, FTx: Fuzzer> phy::TxToken for TxToken<'a, Tx, FTx> { 119 | fn consume(self, len: usize, f: F) -> R 120 | where 121 | F: FnOnce(&mut [u8]) -> R, 122 | { 123 | self.token.consume(len, |buf| { 124 | let result = f(buf); 125 | self.fuzzer.fuzz_packet(buf); 126 | result 127 | }) 128 | } 129 | 130 | fn set_meta(&mut self, meta: phy::PacketMeta) { 131 | self.token.set_meta(meta) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/phy/loopback.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::VecDeque; 2 | use alloc::vec; 3 | use alloc::vec::Vec; 4 | 5 | use crate::phy::{self, ChecksumCapabilities, Device, DeviceCapabilities, Medium}; 6 | use crate::time::Instant; 7 | 8 | /// A loopback device. 9 | #[derive(Debug)] 10 | pub struct Loopback { 11 | pub(crate) queue: VecDeque>, 12 | medium: Medium, 13 | } 14 | 15 | #[allow(clippy::new_without_default)] 16 | impl Loopback { 17 | /// Creates a loopback device. 18 | /// 19 | /// Every packet transmitted through this device will be received through it 20 | /// in FIFO order. 21 | pub fn new(medium: Medium) -> Loopback { 22 | Loopback { 23 | queue: VecDeque::new(), 24 | medium, 25 | } 26 | } 27 | } 28 | 29 | impl Device for Loopback { 30 | type RxToken<'a> = RxToken; 31 | type TxToken<'a> = TxToken<'a>; 32 | 33 | fn capabilities(&self) -> DeviceCapabilities { 34 | DeviceCapabilities { 35 | max_transmission_unit: 65535, 36 | medium: self.medium, 37 | checksum: ChecksumCapabilities::ignored(), 38 | ..DeviceCapabilities::default() 39 | } 40 | } 41 | 42 | fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 43 | self.queue.pop_front().map(move |buffer| { 44 | let rx = RxToken { buffer }; 45 | let tx = TxToken { 46 | queue: &mut self.queue, 47 | }; 48 | (rx, tx) 49 | }) 50 | } 51 | 52 | fn transmit(&mut self, _timestamp: Instant) -> Option> { 53 | Some(TxToken { 54 | queue: &mut self.queue, 55 | }) 56 | } 57 | } 58 | 59 | #[doc(hidden)] 60 | pub struct RxToken { 61 | buffer: Vec, 62 | } 63 | 64 | impl phy::RxToken for RxToken { 65 | fn consume(self, f: F) -> R 66 | where 67 | F: FnOnce(&[u8]) -> R, 68 | { 69 | f(&self.buffer) 70 | } 71 | } 72 | 73 | #[doc(hidden)] 74 | #[derive(Debug)] 75 | pub struct TxToken<'a> { 76 | queue: &'a mut VecDeque>, 77 | } 78 | 79 | impl<'a> phy::TxToken for TxToken<'a> { 80 | fn consume(self, len: usize, f: F) -> R 81 | where 82 | F: FnOnce(&mut [u8]) -> R, 83 | { 84 | let mut buffer = vec![0; len]; 85 | let result = f(&mut buffer); 86 | self.queue.push_back(buffer); 87 | result 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/phy/raw_socket.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::io; 3 | use std::os::unix::io::{AsRawFd, RawFd}; 4 | use std::rc::Rc; 5 | use std::vec::Vec; 6 | 7 | use crate::phy::{self, sys, Device, DeviceCapabilities, Medium}; 8 | use crate::time::Instant; 9 | 10 | /// A socket that captures or transmits the complete frame. 11 | #[derive(Debug)] 12 | pub struct RawSocket { 13 | medium: Medium, 14 | lower: Rc>, 15 | mtu: usize, 16 | } 17 | 18 | impl AsRawFd for RawSocket { 19 | fn as_raw_fd(&self) -> RawFd { 20 | self.lower.borrow().as_raw_fd() 21 | } 22 | } 23 | 24 | impl RawSocket { 25 | /// Creates a raw socket, bound to the interface called `name`. 26 | /// 27 | /// This requires superuser privileges or a corresponding capability bit 28 | /// set on the executable. 29 | pub fn new(name: &str, medium: Medium) -> io::Result { 30 | let mut lower = sys::RawSocketDesc::new(name, medium)?; 31 | lower.bind_interface()?; 32 | 33 | let mut mtu = lower.interface_mtu()?; 34 | 35 | #[cfg(feature = "medium-ieee802154")] 36 | if medium == Medium::Ieee802154 { 37 | // SIOCGIFMTU returns 127 - (ACK_PSDU - FCS - 1) - FCS. 38 | // 127 - (5 - 2 - 1) - 2 = 123 39 | // For IEEE802154, we want to add (ACK_PSDU - FCS - 1), since that is what SIOCGIFMTU 40 | // uses as the size of the link layer header. 41 | // 42 | // https://github.com/torvalds/linux/blob/7475e51b87969e01a6812eac713a1c8310372e8a/net/mac802154/iface.c#L541 43 | mtu += 2; 44 | } 45 | 46 | #[cfg(feature = "medium-ethernet")] 47 | if medium == Medium::Ethernet { 48 | // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.) 49 | // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it. 50 | mtu += crate::wire::EthernetFrame::<&[u8]>::header_len() 51 | } 52 | 53 | Ok(RawSocket { 54 | medium, 55 | lower: Rc::new(RefCell::new(lower)), 56 | mtu, 57 | }) 58 | } 59 | } 60 | 61 | impl Device for RawSocket { 62 | type RxToken<'a> 63 | = RxToken 64 | where 65 | Self: 'a; 66 | type TxToken<'a> 67 | = TxToken 68 | where 69 | Self: 'a; 70 | 71 | fn capabilities(&self) -> DeviceCapabilities { 72 | DeviceCapabilities { 73 | max_transmission_unit: self.mtu, 74 | medium: self.medium, 75 | ..DeviceCapabilities::default() 76 | } 77 | } 78 | 79 | fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 80 | let mut lower = self.lower.borrow_mut(); 81 | let mut buffer = vec![0; self.mtu]; 82 | match lower.recv(&mut buffer[..]) { 83 | Ok(size) => { 84 | buffer.resize(size, 0); 85 | let rx = RxToken { buffer }; 86 | let tx = TxToken { 87 | lower: self.lower.clone(), 88 | }; 89 | Some((rx, tx)) 90 | } 91 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, 92 | Err(err) => panic!("{}", err), 93 | } 94 | } 95 | 96 | fn transmit(&mut self, _timestamp: Instant) -> Option> { 97 | Some(TxToken { 98 | lower: self.lower.clone(), 99 | }) 100 | } 101 | } 102 | 103 | #[doc(hidden)] 104 | pub struct RxToken { 105 | buffer: Vec, 106 | } 107 | 108 | impl phy::RxToken for RxToken { 109 | fn consume(self, f: F) -> R 110 | where 111 | F: FnOnce(&[u8]) -> R, 112 | { 113 | f(&self.buffer[..]) 114 | } 115 | } 116 | 117 | #[doc(hidden)] 118 | pub struct TxToken { 119 | lower: Rc>, 120 | } 121 | 122 | impl phy::TxToken for TxToken { 123 | fn consume(self, len: usize, f: F) -> R 124 | where 125 | F: FnOnce(&mut [u8]) -> R, 126 | { 127 | let mut lower = self.lower.borrow_mut(); 128 | let mut buffer = vec![0; len]; 129 | let result = f(&mut buffer); 130 | match lower.send(&buffer[..]) { 131 | Ok(_) => {} 132 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => { 133 | net_debug!("phy: tx failed due to WouldBlock") 134 | } 135 | Err(err) => panic!("{}", err), 136 | } 137 | result 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/phy/sys/bpf.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::mem; 3 | use std::os::unix::io::{AsRawFd, RawFd}; 4 | 5 | use libc; 6 | 7 | use super::{ifreq, ifreq_for}; 8 | use crate::phy::Medium; 9 | use crate::wire::ETHERNET_HEADER_LEN; 10 | 11 | /// set interface 12 | #[cfg(any( 13 | target_os = "macos", 14 | target_os = "ios", 15 | target_os = "netbsd", 16 | target_os = "openbsd", 17 | target_os = "freebsd" 18 | ))] 19 | const BIOCSETIF: libc::c_ulong = 0x8020426c; 20 | /// get buffer length 21 | #[cfg(any( 22 | target_os = "macos", 23 | target_os = "ios", 24 | target_os = "netbsd", 25 | target_os = "openbsd", 26 | target_os = "freebsd" 27 | ))] 28 | const BIOCGBLEN: libc::c_ulong = 0x40044266; 29 | /// set immediate/nonblocking read 30 | #[cfg(any( 31 | target_os = "macos", 32 | target_os = "ios", 33 | target_os = "netbsd", 34 | target_os = "openbsd", 35 | target_os = "freebsd" 36 | ))] 37 | const BIOCIMMEDIATE: libc::c_ulong = 0x80044270; 38 | /// set bpf_hdr struct size 39 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "netbsd"))] 40 | const SIZEOF_BPF_HDR: usize = 18; 41 | /// set bpf_hdr struct size 42 | #[cfg(any(target_os = "openbsd", target_os = "freebsd"))] 43 | const SIZEOF_BPF_HDR: usize = 24; 44 | /// The actual header length may be larger than the bpf_hdr struct due to aligning 45 | /// see https://github.com/openbsd/src/blob/37ecb4d066e5566411cc16b362d3960c93b1d0be/sys/net/bpf.c#L1649 46 | /// and https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/bsd/net/bpf.c#L3580 47 | /// and https://github.com/NetBSD/src/blob/13d937d9ba3db87c9a898a40a8ed9d2aab2b1b95/sys/net/bpf.c#L1988 48 | /// for FreeBSD, core::mem::size_of::() = 32 when run on a FreeBSD system. 49 | #[cfg(any( 50 | target_os = "macos", 51 | target_os = "ios", 52 | target_os = "netbsd", 53 | target_os = "openbsd", 54 | target_os = "freebsd" 55 | ))] 56 | const BPF_HDRLEN: usize = (((SIZEOF_BPF_HDR + ETHERNET_HEADER_LEN) + mem::align_of::() - 1) 57 | & !(mem::align_of::() - 1)) 58 | - ETHERNET_HEADER_LEN; 59 | 60 | macro_rules! try_ioctl { 61 | ($fd:expr,$cmd:expr,$req:expr) => { 62 | unsafe { 63 | if libc::ioctl($fd, $cmd, $req) == -1 { 64 | return Err(io::Error::last_os_error()); 65 | } 66 | } 67 | }; 68 | } 69 | 70 | #[derive(Debug)] 71 | pub struct BpfDevice { 72 | fd: libc::c_int, 73 | ifreq: ifreq, 74 | } 75 | 76 | impl AsRawFd for BpfDevice { 77 | fn as_raw_fd(&self) -> RawFd { 78 | self.fd 79 | } 80 | } 81 | 82 | fn open_device() -> io::Result { 83 | unsafe { 84 | for i in 0..256 { 85 | let dev = format!("/dev/bpf{}\0", i); 86 | match libc::open( 87 | dev.as_ptr() as *const libc::c_char, 88 | libc::O_RDWR | libc::O_NONBLOCK, 89 | ) { 90 | -1 => continue, 91 | fd => return Ok(fd), 92 | }; 93 | } 94 | } 95 | // at this point, all 256 BPF devices were busy and we weren't able to open any 96 | Err(io::Error::last_os_error()) 97 | } 98 | 99 | impl BpfDevice { 100 | pub fn new(name: &str, _medium: Medium) -> io::Result { 101 | Ok(BpfDevice { 102 | fd: open_device()?, 103 | ifreq: ifreq_for(name), 104 | }) 105 | } 106 | 107 | pub fn bind_interface(&mut self) -> io::Result<()> { 108 | try_ioctl!(self.fd, BIOCSETIF, &mut self.ifreq); 109 | 110 | Ok(()) 111 | } 112 | 113 | /// This in fact does not return the interface's mtu, 114 | /// but it returns the size of the buffer that the app needs to allocate 115 | /// for the BPF device 116 | /// 117 | /// The `SIOGIFMTU` cant be called on a BPF descriptor. There is a workaround 118 | /// to get the actual interface mtu, but this should work better 119 | /// 120 | /// To get the interface MTU, you would need to create a raw socket first, 121 | /// and then call `SIOGIFMTU` for the same interface your BPF device is "bound" to. 122 | /// This MTU that you would get would not include the length of `struct bpf_hdr` 123 | /// which gets prepended to every packet by BPF, 124 | /// and your packet will be truncated if it has the length of the MTU. 125 | /// 126 | /// The buffer size for BPF is usually 4096 bytes, MTU is typically 1500 bytes. 127 | /// You could do something like `mtu += BPF_HDRLEN`, 128 | /// but you must change the buffer size the BPF device expects using `BIOCSBLEN` accordingly, 129 | /// and you must set it before setting the interface with the `BIOCSETIF` ioctl. 130 | /// 131 | /// The reason I said this should work better is because you might see some unexpected behavior, 132 | /// truncated/unaligned packets, I/O errors on read() 133 | /// if you change the buffer size to the actual MTU of the interface. 134 | pub fn interface_mtu(&mut self) -> io::Result { 135 | let mut bufsize: libc::c_int = 1; 136 | try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int); 137 | try_ioctl!(self.fd, BIOCGBLEN, &mut bufsize as *mut libc::c_int); 138 | 139 | Ok(bufsize as usize) 140 | } 141 | 142 | pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result { 143 | unsafe { 144 | let len = libc::read( 145 | self.fd, 146 | buffer.as_mut_ptr() as *mut libc::c_void, 147 | buffer.len(), 148 | ); 149 | 150 | if len == -1 || len < BPF_HDRLEN as isize { 151 | return Err(io::Error::last_os_error()); 152 | } 153 | 154 | let len = len as usize; 155 | 156 | libc::memmove( 157 | buffer.as_mut_ptr() as *mut libc::c_void, 158 | &buffer[BPF_HDRLEN] as *const u8 as *const libc::c_void, 159 | len - BPF_HDRLEN, 160 | ); 161 | 162 | Ok(len) 163 | } 164 | } 165 | 166 | pub fn send(&mut self, buffer: &[u8]) -> io::Result { 167 | unsafe { 168 | let len = libc::write( 169 | self.fd, 170 | buffer.as_ptr() as *const libc::c_void, 171 | buffer.len(), 172 | ); 173 | 174 | if len == -1 { 175 | panic!("{:?}", io::Error::last_os_error()) 176 | } 177 | 178 | Ok(len as usize) 179 | } 180 | } 181 | } 182 | 183 | impl Drop for BpfDevice { 184 | fn drop(&mut self) { 185 | unsafe { 186 | libc::close(self.fd); 187 | } 188 | } 189 | } 190 | 191 | #[cfg(test)] 192 | mod test { 193 | use super::*; 194 | 195 | #[test] 196 | #[cfg(any(target_os = "macos", target_os = "netbsd"))] 197 | fn test_aligned_bpf_hdr_len() { 198 | assert_eq!(18, BPF_HDRLEN); 199 | } 200 | 201 | #[test] 202 | #[cfg(target_os = "openbsd")] 203 | fn test_aligned_bpf_hdr_len() { 204 | assert_eq!(26, BPF_HDRLEN); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/phy/sys/linux.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | pub const SIOCGIFMTU: libc::c_ulong = 0x8921; 4 | pub const SIOCGIFINDEX: libc::c_ulong = 0x8933; 5 | pub const ETH_P_ALL: libc::c_short = 0x0003; 6 | pub const ETH_P_IEEE802154: libc::c_short = 0x00F6; 7 | 8 | // Constant definition as per 9 | // https://github.com/golang/sys/blob/master/unix/zerrors_linux_.go 10 | pub const TUNSETIFF: libc::c_ulong = if cfg!(any( 11 | target_arch = "mips", 12 | all(target_arch = "mips", target_endian = "little"), 13 | target_arch = "mips64", 14 | all(target_arch = "mips64", target_endian = "little"), 15 | target_arch = "powerpc", 16 | target_arch = "powerpc64", 17 | all(target_arch = "powerpc64", target_endian = "little"), 18 | target_arch = "sparc64" 19 | )) { 20 | 0x800454CA 21 | } else { 22 | 0x400454CA 23 | }; 24 | pub const IFF_TUN: libc::c_int = 0x0001; 25 | pub const IFF_TAP: libc::c_int = 0x0002; 26 | pub const IFF_NO_PI: libc::c_int = 0x1000; 27 | -------------------------------------------------------------------------------- /src/phy/sys/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_code)] 2 | 3 | use crate::time::Duration; 4 | use std::os::unix::io::RawFd; 5 | use std::{io, mem, ptr}; 6 | 7 | #[cfg(any(target_os = "linux", target_os = "android"))] 8 | #[path = "linux.rs"] 9 | mod imp; 10 | 11 | #[cfg(all( 12 | feature = "phy-raw_socket", 13 | not(any(target_os = "linux", target_os = "android")), 14 | unix 15 | ))] 16 | pub mod bpf; 17 | #[cfg(all( 18 | feature = "phy-raw_socket", 19 | any(target_os = "linux", target_os = "android") 20 | ))] 21 | pub mod raw_socket; 22 | #[cfg(all( 23 | feature = "phy-tuntap_interface", 24 | any(target_os = "linux", target_os = "android") 25 | ))] 26 | pub mod tuntap_interface; 27 | 28 | #[cfg(all( 29 | feature = "phy-raw_socket", 30 | not(any(target_os = "linux", target_os = "android")), 31 | unix 32 | ))] 33 | pub use self::bpf::BpfDevice as RawSocketDesc; 34 | #[cfg(all( 35 | feature = "phy-raw_socket", 36 | any(target_os = "linux", target_os = "android") 37 | ))] 38 | pub use self::raw_socket::RawSocketDesc; 39 | #[cfg(all( 40 | feature = "phy-tuntap_interface", 41 | any(target_os = "linux", target_os = "android") 42 | ))] 43 | pub use self::tuntap_interface::TunTapInterfaceDesc; 44 | 45 | /// Wait until given file descriptor becomes readable, but no longer than given timeout. 46 | pub fn wait(fd: RawFd, duration: Option) -> io::Result<()> { 47 | unsafe { 48 | let mut readfds = { 49 | let mut readfds = mem::MaybeUninit::::uninit(); 50 | libc::FD_ZERO(readfds.as_mut_ptr()); 51 | libc::FD_SET(fd, readfds.as_mut_ptr()); 52 | readfds.assume_init() 53 | }; 54 | 55 | let mut writefds = { 56 | let mut writefds = mem::MaybeUninit::::uninit(); 57 | libc::FD_ZERO(writefds.as_mut_ptr()); 58 | writefds.assume_init() 59 | }; 60 | 61 | let mut exceptfds = { 62 | let mut exceptfds = mem::MaybeUninit::::uninit(); 63 | libc::FD_ZERO(exceptfds.as_mut_ptr()); 64 | exceptfds.assume_init() 65 | }; 66 | 67 | let mut timeout = libc::timeval { 68 | tv_sec: 0, 69 | tv_usec: 0, 70 | }; 71 | let timeout_ptr = if let Some(duration) = duration { 72 | timeout.tv_sec = duration.secs() as libc::time_t; 73 | timeout.tv_usec = (duration.millis() * 1_000) as libc::suseconds_t; 74 | &mut timeout as *mut _ 75 | } else { 76 | ptr::null_mut() 77 | }; 78 | 79 | let res = libc::select( 80 | fd + 1, 81 | &mut readfds, 82 | &mut writefds, 83 | &mut exceptfds, 84 | timeout_ptr, 85 | ); 86 | if res == -1 { 87 | return Err(io::Error::last_os_error()); 88 | } 89 | Ok(()) 90 | } 91 | } 92 | 93 | #[cfg(all( 94 | any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"), 95 | unix 96 | ))] 97 | #[repr(C)] 98 | #[derive(Debug)] 99 | struct ifreq { 100 | ifr_name: [libc::c_char; libc::IF_NAMESIZE], 101 | ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */ 102 | } 103 | 104 | #[cfg(all( 105 | any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"), 106 | unix 107 | ))] 108 | fn ifreq_for(name: &str) -> ifreq { 109 | let mut ifreq = ifreq { 110 | ifr_name: [0; libc::IF_NAMESIZE], 111 | ifr_data: 0, 112 | }; 113 | for (i, byte) in name.as_bytes().iter().enumerate() { 114 | ifreq.ifr_name[i] = *byte as libc::c_char 115 | } 116 | ifreq 117 | } 118 | 119 | #[cfg(all( 120 | any(target_os = "linux", target_os = "android"), 121 | any(feature = "phy-tuntap_interface", feature = "phy-raw_socket") 122 | ))] 123 | fn ifreq_ioctl( 124 | lower: libc::c_int, 125 | ifreq: &mut ifreq, 126 | cmd: libc::c_ulong, 127 | ) -> io::Result { 128 | unsafe { 129 | let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq); 130 | if res == -1 { 131 | return Err(io::Error::last_os_error()); 132 | } 133 | } 134 | 135 | Ok(ifreq.ifr_data) 136 | } 137 | -------------------------------------------------------------------------------- /src/phy/sys/raw_socket.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::phy::Medium; 3 | use std::os::unix::io::{AsRawFd, RawFd}; 4 | use std::{io, mem}; 5 | 6 | #[derive(Debug)] 7 | pub struct RawSocketDesc { 8 | protocol: libc::c_short, 9 | lower: libc::c_int, 10 | ifreq: ifreq, 11 | } 12 | 13 | impl AsRawFd for RawSocketDesc { 14 | fn as_raw_fd(&self) -> RawFd { 15 | self.lower 16 | } 17 | } 18 | 19 | impl RawSocketDesc { 20 | pub fn new(name: &str, medium: Medium) -> io::Result { 21 | let protocol = match medium { 22 | #[cfg(feature = "medium-ethernet")] 23 | Medium::Ethernet => imp::ETH_P_ALL, 24 | #[cfg(feature = "medium-ip")] 25 | Medium::Ip => imp::ETH_P_ALL, 26 | #[cfg(feature = "medium-ieee802154")] 27 | Medium::Ieee802154 => imp::ETH_P_IEEE802154, 28 | }; 29 | 30 | let lower = unsafe { 31 | let lower = libc::socket( 32 | libc::AF_PACKET, 33 | libc::SOCK_RAW | libc::SOCK_NONBLOCK, 34 | protocol.to_be() as i32, 35 | ); 36 | if lower == -1 { 37 | return Err(io::Error::last_os_error()); 38 | } 39 | lower 40 | }; 41 | 42 | Ok(RawSocketDesc { 43 | protocol, 44 | lower, 45 | ifreq: ifreq_for(name), 46 | }) 47 | } 48 | 49 | pub fn interface_mtu(&mut self) -> io::Result { 50 | ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize) 51 | } 52 | 53 | pub fn bind_interface(&mut self) -> io::Result<()> { 54 | let sockaddr = libc::sockaddr_ll { 55 | sll_family: libc::AF_PACKET as u16, 56 | sll_protocol: self.protocol.to_be() as u16, 57 | sll_ifindex: ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFINDEX)?, 58 | sll_hatype: 1, 59 | sll_pkttype: 0, 60 | sll_halen: 6, 61 | sll_addr: [0; 8], 62 | }; 63 | 64 | unsafe { 65 | let res = libc::bind( 66 | self.lower, 67 | &sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr, 68 | mem::size_of::() as libc::socklen_t, 69 | ); 70 | if res == -1 { 71 | return Err(io::Error::last_os_error()); 72 | } 73 | } 74 | 75 | Ok(()) 76 | } 77 | 78 | pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result { 79 | unsafe { 80 | let len = libc::recv( 81 | self.lower, 82 | buffer.as_mut_ptr() as *mut libc::c_void, 83 | buffer.len(), 84 | 0, 85 | ); 86 | if len == -1 { 87 | return Err(io::Error::last_os_error()); 88 | } 89 | Ok(len as usize) 90 | } 91 | } 92 | 93 | pub fn send(&mut self, buffer: &[u8]) -> io::Result { 94 | unsafe { 95 | let len = libc::send( 96 | self.lower, 97 | buffer.as_ptr() as *const libc::c_void, 98 | buffer.len(), 99 | 0, 100 | ); 101 | if len == -1 { 102 | return Err(io::Error::last_os_error()); 103 | } 104 | Ok(len as usize) 105 | } 106 | } 107 | } 108 | 109 | impl Drop for RawSocketDesc { 110 | fn drop(&mut self) { 111 | unsafe { 112 | libc::close(self.lower); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/phy/sys/tuntap_interface.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{phy::Medium, wire::EthernetFrame}; 3 | use std::io; 4 | use std::os::unix::io::{AsRawFd, RawFd}; 5 | 6 | #[derive(Debug)] 7 | pub struct TunTapInterfaceDesc { 8 | lower: libc::c_int, 9 | mtu: usize, 10 | } 11 | 12 | impl AsRawFd for TunTapInterfaceDesc { 13 | fn as_raw_fd(&self) -> RawFd { 14 | self.lower 15 | } 16 | } 17 | 18 | impl TunTapInterfaceDesc { 19 | pub fn new(name: &str, medium: Medium) -> io::Result { 20 | let lower = unsafe { 21 | let lower = libc::open( 22 | "/dev/net/tun\0".as_ptr() as *const libc::c_char, 23 | libc::O_RDWR | libc::O_NONBLOCK, 24 | ); 25 | if lower == -1 { 26 | return Err(io::Error::last_os_error()); 27 | } 28 | lower 29 | }; 30 | 31 | let mut ifreq = ifreq_for(name); 32 | Self::attach_interface_ifreq(lower, medium, &mut ifreq)?; 33 | let mtu = Self::mtu_ifreq(medium, &mut ifreq)?; 34 | 35 | Ok(TunTapInterfaceDesc { lower, mtu }) 36 | } 37 | 38 | pub fn from_fd(fd: RawFd, mtu: usize) -> io::Result { 39 | Ok(TunTapInterfaceDesc { lower: fd, mtu }) 40 | } 41 | 42 | fn attach_interface_ifreq( 43 | lower: libc::c_int, 44 | medium: Medium, 45 | ifr: &mut ifreq, 46 | ) -> io::Result<()> { 47 | let mode = match medium { 48 | #[cfg(feature = "medium-ip")] 49 | Medium::Ip => imp::IFF_TUN, 50 | #[cfg(feature = "medium-ethernet")] 51 | Medium::Ethernet => imp::IFF_TAP, 52 | #[cfg(feature = "medium-ieee802154")] 53 | Medium::Ieee802154 => todo!(), 54 | }; 55 | ifr.ifr_data = mode | imp::IFF_NO_PI; 56 | ifreq_ioctl(lower, ifr, imp::TUNSETIFF).map(|_| ()) 57 | } 58 | 59 | fn mtu_ifreq(medium: Medium, ifr: &mut ifreq) -> io::Result { 60 | let lower = unsafe { 61 | let lower = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP); 62 | if lower == -1 { 63 | return Err(io::Error::last_os_error()); 64 | } 65 | lower 66 | }; 67 | 68 | let ip_mtu = ifreq_ioctl(lower, ifr, imp::SIOCGIFMTU).map(|mtu| mtu as usize); 69 | 70 | unsafe { 71 | libc::close(lower); 72 | } 73 | 74 | // Propagate error after close, to ensure we always close. 75 | let ip_mtu = ip_mtu?; 76 | 77 | // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.) 78 | // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it. 79 | let mtu = match medium { 80 | #[cfg(feature = "medium-ip")] 81 | Medium::Ip => ip_mtu, 82 | #[cfg(feature = "medium-ethernet")] 83 | Medium::Ethernet => ip_mtu + EthernetFrame::<&[u8]>::header_len(), 84 | #[cfg(feature = "medium-ieee802154")] 85 | Medium::Ieee802154 => todo!(), 86 | }; 87 | 88 | Ok(mtu) 89 | } 90 | 91 | pub fn interface_mtu(&self) -> io::Result { 92 | Ok(self.mtu) 93 | } 94 | 95 | pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result { 96 | unsafe { 97 | let len = libc::read( 98 | self.lower, 99 | buffer.as_mut_ptr() as *mut libc::c_void, 100 | buffer.len(), 101 | ); 102 | if len == -1 { 103 | return Err(io::Error::last_os_error()); 104 | } 105 | Ok(len as usize) 106 | } 107 | } 108 | 109 | pub fn send(&mut self, buffer: &[u8]) -> io::Result { 110 | unsafe { 111 | let len = libc::write( 112 | self.lower, 113 | buffer.as_ptr() as *const libc::c_void, 114 | buffer.len(), 115 | ); 116 | if len == -1 { 117 | return Err(io::Error::last_os_error()); 118 | } 119 | Ok(len as usize) 120 | } 121 | } 122 | } 123 | 124 | impl Drop for TunTapInterfaceDesc { 125 | fn drop(&mut self) { 126 | unsafe { 127 | libc::close(self.lower); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/phy/tracer.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use crate::phy::{self, Device, DeviceCapabilities, Medium}; 4 | use crate::time::Instant; 5 | use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; 6 | 7 | /// A tracer device. 8 | /// 9 | /// A tracer is a device that pretty prints all packets traversing it 10 | /// using the provided writer function, and then passes them to another 11 | /// device. 12 | pub struct Tracer { 13 | inner: D, 14 | writer: fn(Instant, Packet), 15 | } 16 | 17 | impl Tracer { 18 | /// Create a tracer device. 19 | pub fn new(inner: D, writer: fn(timestamp: Instant, packet: Packet)) -> Tracer { 20 | Tracer { inner, writer } 21 | } 22 | 23 | /// Get a reference to the underlying device. 24 | /// 25 | /// Even if the device offers reading through a standard reference, it is inadvisable to 26 | /// directly read from the device as doing so will circumvent the tracing. 27 | pub fn get_ref(&self) -> &D { 28 | &self.inner 29 | } 30 | 31 | /// Get a mutable reference to the underlying device. 32 | /// 33 | /// It is inadvisable to directly read from the device as doing so will circumvent the tracing. 34 | pub fn get_mut(&mut self) -> &mut D { 35 | &mut self.inner 36 | } 37 | 38 | /// Return the underlying device, consuming the tracer. 39 | pub fn into_inner(self) -> D { 40 | self.inner 41 | } 42 | } 43 | 44 | impl Device for Tracer { 45 | type RxToken<'a> 46 | = RxToken> 47 | where 48 | Self: 'a; 49 | type TxToken<'a> 50 | = TxToken> 51 | where 52 | Self: 'a; 53 | 54 | fn capabilities(&self) -> DeviceCapabilities { 55 | self.inner.capabilities() 56 | } 57 | 58 | fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 59 | let medium = self.inner.capabilities().medium; 60 | self.inner.receive(timestamp).map(|(rx_token, tx_token)| { 61 | let rx = RxToken { 62 | token: rx_token, 63 | writer: self.writer, 64 | medium, 65 | timestamp, 66 | }; 67 | let tx = TxToken { 68 | token: tx_token, 69 | writer: self.writer, 70 | medium, 71 | timestamp, 72 | }; 73 | (rx, tx) 74 | }) 75 | } 76 | 77 | fn transmit(&mut self, timestamp: Instant) -> Option> { 78 | let medium = self.inner.capabilities().medium; 79 | self.inner.transmit(timestamp).map(|tx_token| TxToken { 80 | token: tx_token, 81 | medium, 82 | writer: self.writer, 83 | timestamp, 84 | }) 85 | } 86 | } 87 | 88 | #[doc(hidden)] 89 | pub struct RxToken { 90 | token: Rx, 91 | writer: fn(Instant, Packet), 92 | medium: Medium, 93 | timestamp: Instant, 94 | } 95 | 96 | impl phy::RxToken for RxToken { 97 | fn consume(self, f: F) -> R 98 | where 99 | F: FnOnce(&[u8]) -> R, 100 | { 101 | self.token.consume(|buffer| { 102 | (self.writer)( 103 | self.timestamp, 104 | Packet { 105 | buffer, 106 | medium: self.medium, 107 | prefix: "<- ", 108 | }, 109 | ); 110 | f(buffer) 111 | }) 112 | } 113 | 114 | fn meta(&self) -> phy::PacketMeta { 115 | self.token.meta() 116 | } 117 | } 118 | 119 | #[doc(hidden)] 120 | pub struct TxToken { 121 | token: Tx, 122 | writer: fn(Instant, Packet), 123 | medium: Medium, 124 | timestamp: Instant, 125 | } 126 | 127 | impl phy::TxToken for TxToken { 128 | fn consume(self, len: usize, f: F) -> R 129 | where 130 | F: FnOnce(&mut [u8]) -> R, 131 | { 132 | self.token.consume(len, |buffer| { 133 | let result = f(buffer); 134 | (self.writer)( 135 | self.timestamp, 136 | Packet { 137 | buffer, 138 | medium: self.medium, 139 | prefix: "-> ", 140 | }, 141 | ); 142 | result 143 | }) 144 | } 145 | 146 | fn set_meta(&mut self, meta: phy::PacketMeta) { 147 | self.token.set_meta(meta) 148 | } 149 | } 150 | 151 | pub struct Packet<'a> { 152 | buffer: &'a [u8], 153 | medium: Medium, 154 | prefix: &'static str, 155 | } 156 | 157 | impl<'a> fmt::Display for Packet<'a> { 158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 159 | let mut indent = PrettyIndent::new(self.prefix); 160 | match self.medium { 161 | #[cfg(feature = "medium-ethernet")] 162 | Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print( 163 | &self.buffer, 164 | f, 165 | &mut indent, 166 | ), 167 | #[cfg(feature = "medium-ip")] 168 | Medium::Ip => match crate::wire::IpVersion::of_packet(self.buffer) { 169 | #[cfg(feature = "proto-ipv4")] 170 | Ok(crate::wire::IpVersion::Ipv4) => { 171 | crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print( 172 | &self.buffer, 173 | f, 174 | &mut indent, 175 | ) 176 | } 177 | #[cfg(feature = "proto-ipv6")] 178 | Ok(crate::wire::IpVersion::Ipv6) => { 179 | crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print( 180 | &self.buffer, 181 | f, 182 | &mut indent, 183 | ) 184 | } 185 | _ => f.write_str("unrecognized IP version"), 186 | }, 187 | #[cfg(feature = "medium-ieee802154")] 188 | Medium::Ieee802154 => Ok(()), // XXX 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/phy/tuntap_interface.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::io; 3 | use std::os::unix::io::{AsRawFd, RawFd}; 4 | use std::rc::Rc; 5 | use std::vec::Vec; 6 | 7 | use crate::phy::{self, sys, Device, DeviceCapabilities, Medium}; 8 | use crate::time::Instant; 9 | 10 | /// A virtual TUN (IP) or TAP (Ethernet) interface. 11 | #[derive(Debug)] 12 | pub struct TunTapInterface { 13 | lower: Rc>, 14 | mtu: usize, 15 | medium: Medium, 16 | } 17 | 18 | impl AsRawFd for TunTapInterface { 19 | fn as_raw_fd(&self) -> RawFd { 20 | self.lower.borrow().as_raw_fd() 21 | } 22 | } 23 | 24 | impl TunTapInterface { 25 | /// Attaches to a TUN/TAP interface called `name`, or creates it if it does not exist. 26 | /// 27 | /// If `name` is a persistent interface configured with UID of the current user, 28 | /// no special privileges are needed. Otherwise, this requires superuser privileges 29 | /// or a corresponding capability set on the executable. 30 | pub fn new(name: &str, medium: Medium) -> io::Result { 31 | let lower = sys::TunTapInterfaceDesc::new(name, medium)?; 32 | let mtu = lower.interface_mtu()?; 33 | Ok(TunTapInterface { 34 | lower: Rc::new(RefCell::new(lower)), 35 | mtu, 36 | medium, 37 | }) 38 | } 39 | 40 | /// Attaches to a TUN/TAP interface specified by file descriptor `fd`. 41 | /// 42 | /// On platforms like Android, a file descriptor to a tun interface is exposed. 43 | /// On these platforms, a TunTapInterface cannot be instantiated with a name. 44 | pub fn from_fd(fd: RawFd, medium: Medium, mtu: usize) -> io::Result { 45 | let lower = sys::TunTapInterfaceDesc::from_fd(fd, mtu)?; 46 | Ok(TunTapInterface { 47 | lower: Rc::new(RefCell::new(lower)), 48 | mtu, 49 | medium, 50 | }) 51 | } 52 | } 53 | 54 | impl Device for TunTapInterface { 55 | type RxToken<'a> = RxToken; 56 | type TxToken<'a> = TxToken; 57 | 58 | fn capabilities(&self) -> DeviceCapabilities { 59 | DeviceCapabilities { 60 | max_transmission_unit: self.mtu, 61 | medium: self.medium, 62 | ..DeviceCapabilities::default() 63 | } 64 | } 65 | 66 | fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 67 | let mut lower = self.lower.borrow_mut(); 68 | let mut buffer = vec![0; self.mtu]; 69 | match lower.recv(&mut buffer[..]) { 70 | Ok(size) => { 71 | buffer.resize(size, 0); 72 | let rx = RxToken { buffer }; 73 | let tx = TxToken { 74 | lower: self.lower.clone(), 75 | }; 76 | Some((rx, tx)) 77 | } 78 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, 79 | Err(err) => panic!("{}", err), 80 | } 81 | } 82 | 83 | fn transmit(&mut self, _timestamp: Instant) -> Option> { 84 | Some(TxToken { 85 | lower: self.lower.clone(), 86 | }) 87 | } 88 | } 89 | 90 | #[doc(hidden)] 91 | pub struct RxToken { 92 | buffer: Vec, 93 | } 94 | 95 | impl phy::RxToken for RxToken { 96 | fn consume(self, f: F) -> R 97 | where 98 | F: FnOnce(&[u8]) -> R, 99 | { 100 | f(&self.buffer[..]) 101 | } 102 | } 103 | 104 | #[doc(hidden)] 105 | pub struct TxToken { 106 | lower: Rc>, 107 | } 108 | 109 | impl phy::TxToken for TxToken { 110 | fn consume(self, len: usize, f: F) -> R 111 | where 112 | F: FnOnce(&mut [u8]) -> R, 113 | { 114 | let mut lower = self.lower.borrow_mut(); 115 | let mut buffer = vec![0; len]; 116 | let result = f(&mut buffer); 117 | match lower.send(&buffer[..]) { 118 | Ok(_) => {} 119 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => { 120 | net_debug!("phy: tx failed due to WouldBlock") 121 | } 122 | Err(err) => panic!("{}", err), 123 | } 124 | result 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/rand.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_code)] 2 | #![allow(unused)] 3 | 4 | #[derive(Debug)] 5 | pub(crate) struct Rand { 6 | state: u64, 7 | } 8 | 9 | impl Rand { 10 | pub(crate) const fn new(seed: u64) -> Self { 11 | Self { state: seed } 12 | } 13 | 14 | pub(crate) fn rand_u32(&mut self) -> u32 { 15 | // sPCG32 from https://www.pcg-random.org/paper.html 16 | // see also https://nullprogram.com/blog/2017/09/21/ 17 | const M: u64 = 0xbb2efcec3c39611d; 18 | const A: u64 = 0x7590ef39; 19 | 20 | let s = self.state.wrapping_mul(M).wrapping_add(A); 21 | self.state = s; 22 | 23 | let shift = 29 - (s >> 61); 24 | (s >> shift) as u32 25 | } 26 | 27 | pub(crate) fn rand_u16(&mut self) -> u16 { 28 | let n = self.rand_u32(); 29 | (n ^ (n >> 16)) as u16 30 | } 31 | 32 | pub(crate) fn rand_source_port(&mut self) -> u16 { 33 | loop { 34 | let res = self.rand_u16(); 35 | if res > 1024 { 36 | return res; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/socket/mod.rs: -------------------------------------------------------------------------------- 1 | /*! Communication between endpoints. 2 | 3 | The `socket` module deals with *network endpoints* and *buffering*. 4 | It provides interfaces for accessing buffers of data, and protocol state machines 5 | for filling and emptying these buffers. 6 | 7 | The programming interface implemented here differs greatly from the common Berkeley socket 8 | interface. Specifically, in the Berkeley interface the buffering is implicit: 9 | the operating system decides on the good size for a buffer and manages it. 10 | The interface implemented by this module uses explicit buffering: you decide on the good 11 | size for a buffer, allocate it, and let the networking stack use it. 12 | */ 13 | 14 | use crate::iface::Context; 15 | use crate::time::Instant; 16 | 17 | #[cfg(feature = "socket-dhcpv4")] 18 | pub mod dhcpv4; 19 | #[cfg(feature = "socket-dns")] 20 | pub mod dns; 21 | #[cfg(feature = "socket-icmp")] 22 | pub mod icmp; 23 | #[cfg(feature = "socket-raw")] 24 | pub mod raw; 25 | #[cfg(feature = "socket-tcp")] 26 | pub mod tcp; 27 | #[cfg(feature = "socket-udp")] 28 | pub mod udp; 29 | 30 | #[cfg(feature = "async")] 31 | mod waker; 32 | 33 | #[cfg(feature = "async")] 34 | pub(crate) use self::waker::WakerRegistration; 35 | 36 | /// Gives an indication on the next time the socket should be polled. 37 | #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy)] 38 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 39 | pub(crate) enum PollAt { 40 | /// The socket needs to be polled immediately. 41 | Now, 42 | /// The socket needs to be polled at given [Instant][struct.Instant]. 43 | Time(Instant), 44 | /// The socket does not need to be polled unless there are external changes. 45 | Ingress, 46 | } 47 | 48 | /// A network socket. 49 | /// 50 | /// This enumeration abstracts the various types of sockets based on the IP protocol. 51 | /// To downcast a `Socket` value to a concrete socket, use the [AnySocket] trait, 52 | /// e.g. to get `udp::Socket`, call `udp::Socket::downcast(socket)`. 53 | /// 54 | /// It is usually more convenient to use [SocketSet::get] instead. 55 | /// 56 | /// [AnySocket]: trait.AnySocket.html 57 | /// [SocketSet::get]: struct.SocketSet.html#method.get 58 | #[derive(Debug)] 59 | pub enum Socket<'a> { 60 | #[cfg(feature = "socket-raw")] 61 | Raw(raw::Socket<'a>), 62 | #[cfg(feature = "socket-icmp")] 63 | Icmp(icmp::Socket<'a>), 64 | #[cfg(feature = "socket-udp")] 65 | Udp(udp::Socket<'a>), 66 | #[cfg(feature = "socket-tcp")] 67 | Tcp(tcp::Socket<'a>), 68 | #[cfg(feature = "socket-dhcpv4")] 69 | Dhcpv4(dhcpv4::Socket<'a>), 70 | #[cfg(feature = "socket-dns")] 71 | Dns(dns::Socket<'a>), 72 | } 73 | 74 | impl<'a> Socket<'a> { 75 | pub(crate) fn poll_at(&self, cx: &mut Context) -> PollAt { 76 | match self { 77 | #[cfg(feature = "socket-raw")] 78 | Socket::Raw(s) => s.poll_at(cx), 79 | #[cfg(feature = "socket-icmp")] 80 | Socket::Icmp(s) => s.poll_at(cx), 81 | #[cfg(feature = "socket-udp")] 82 | Socket::Udp(s) => s.poll_at(cx), 83 | #[cfg(feature = "socket-tcp")] 84 | Socket::Tcp(s) => s.poll_at(cx), 85 | #[cfg(feature = "socket-dhcpv4")] 86 | Socket::Dhcpv4(s) => s.poll_at(cx), 87 | #[cfg(feature = "socket-dns")] 88 | Socket::Dns(s) => s.poll_at(cx), 89 | } 90 | } 91 | } 92 | 93 | /// A conversion trait for network sockets. 94 | pub trait AnySocket<'a> { 95 | fn upcast(self) -> Socket<'a>; 96 | fn downcast<'c>(socket: &'c Socket<'a>) -> Option<&'c Self> 97 | where 98 | Self: Sized; 99 | fn downcast_mut<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self> 100 | where 101 | Self: Sized; 102 | } 103 | 104 | macro_rules! from_socket { 105 | ($socket:ty, $variant:ident) => { 106 | impl<'a> AnySocket<'a> for $socket { 107 | fn upcast(self) -> Socket<'a> { 108 | Socket::$variant(self) 109 | } 110 | 111 | fn downcast<'c>(socket: &'c Socket<'a>) -> Option<&'c Self> { 112 | #[allow(unreachable_patterns)] 113 | match socket { 114 | Socket::$variant(socket) => Some(socket), 115 | _ => None, 116 | } 117 | } 118 | 119 | fn downcast_mut<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self> { 120 | #[allow(unreachable_patterns)] 121 | match socket { 122 | Socket::$variant(socket) => Some(socket), 123 | _ => None, 124 | } 125 | } 126 | } 127 | }; 128 | } 129 | 130 | #[cfg(feature = "socket-raw")] 131 | from_socket!(raw::Socket<'a>, Raw); 132 | #[cfg(feature = "socket-icmp")] 133 | from_socket!(icmp::Socket<'a>, Icmp); 134 | #[cfg(feature = "socket-udp")] 135 | from_socket!(udp::Socket<'a>, Udp); 136 | #[cfg(feature = "socket-tcp")] 137 | from_socket!(tcp::Socket<'a>, Tcp); 138 | #[cfg(feature = "socket-dhcpv4")] 139 | from_socket!(dhcpv4::Socket<'a>, Dhcpv4); 140 | #[cfg(feature = "socket-dns")] 141 | from_socket!(dns::Socket<'a>, Dns); 142 | -------------------------------------------------------------------------------- /src/socket/tcp/congestion.rs: -------------------------------------------------------------------------------- 1 | use crate::time::Instant; 2 | 3 | use super::RttEstimator; 4 | 5 | pub(super) mod no_control; 6 | 7 | #[cfg(feature = "socket-tcp-cubic")] 8 | pub(super) mod cubic; 9 | 10 | #[cfg(feature = "socket-tcp-reno")] 11 | pub(super) mod reno; 12 | 13 | #[allow(unused_variables)] 14 | pub(super) trait Controller { 15 | /// Returns the number of bytes that can be sent. 16 | fn window(&self) -> usize; 17 | 18 | /// Set the remote window size. 19 | fn set_remote_window(&mut self, remote_window: usize) {} 20 | 21 | fn on_ack(&mut self, now: Instant, len: usize, rtt: &RttEstimator) {} 22 | 23 | fn on_retransmit(&mut self, now: Instant) {} 24 | 25 | fn on_duplicate_ack(&mut self, now: Instant) {} 26 | 27 | fn pre_transmit(&mut self, now: Instant) {} 28 | 29 | fn post_transmit(&mut self, now: Instant, len: usize) {} 30 | 31 | /// Set the maximum segment size. 32 | fn set_mss(&mut self, mss: usize) {} 33 | } 34 | 35 | #[derive(Debug)] 36 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 37 | pub(super) enum AnyController { 38 | None(no_control::NoControl), 39 | 40 | #[cfg(feature = "socket-tcp-reno")] 41 | Reno(reno::Reno), 42 | 43 | #[cfg(feature = "socket-tcp-cubic")] 44 | Cubic(cubic::Cubic), 45 | } 46 | 47 | impl AnyController { 48 | /// Create a new congestion controller. 49 | /// `AnyController::new()` selects the best congestion controller based on the features. 50 | /// 51 | /// - If `socket-tcp-cubic` feature is enabled, it will use `Cubic`. 52 | /// - If `socket-tcp-reno` feature is enabled, it will use `Reno`. 53 | /// - If both `socket-tcp-cubic` and `socket-tcp-reno` features are enabled, it will use `Cubic`. 54 | /// - `Cubic` is more efficient regarding throughput. 55 | /// - `Reno` is more conservative and is suitable for low-power devices. 56 | /// - If no congestion controller is available, it will use `NoControl`. 57 | /// 58 | /// Users can also select a congestion controller manually by [`super::Socket::set_congestion_control()`] 59 | /// method at run-time. 60 | #[allow(unreachable_code)] 61 | #[inline] 62 | pub fn new() -> Self { 63 | #[cfg(feature = "socket-tcp-cubic")] 64 | { 65 | return AnyController::Cubic(cubic::Cubic::new()); 66 | } 67 | 68 | #[cfg(feature = "socket-tcp-reno")] 69 | { 70 | return AnyController::Reno(reno::Reno::new()); 71 | } 72 | 73 | AnyController::None(no_control::NoControl) 74 | } 75 | 76 | #[inline] 77 | pub fn inner_mut(&mut self) -> &mut dyn Controller { 78 | match self { 79 | AnyController::None(n) => n, 80 | 81 | #[cfg(feature = "socket-tcp-reno")] 82 | AnyController::Reno(r) => r, 83 | 84 | #[cfg(feature = "socket-tcp-cubic")] 85 | AnyController::Cubic(c) => c, 86 | } 87 | } 88 | 89 | #[inline] 90 | pub fn inner(&self) -> &dyn Controller { 91 | match self { 92 | AnyController::None(n) => n, 93 | 94 | #[cfg(feature = "socket-tcp-reno")] 95 | AnyController::Reno(r) => r, 96 | 97 | #[cfg(feature = "socket-tcp-cubic")] 98 | AnyController::Cubic(c) => c, 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/socket/tcp/congestion/no_control.rs: -------------------------------------------------------------------------------- 1 | use super::Controller; 2 | 3 | #[derive(Debug)] 4 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 5 | pub struct NoControl; 6 | 7 | impl Controller for NoControl { 8 | fn window(&self) -> usize { 9 | usize::MAX 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/socket/tcp/congestion/reno.rs: -------------------------------------------------------------------------------- 1 | use crate::{socket::tcp::RttEstimator, time::Instant}; 2 | 3 | use super::Controller; 4 | 5 | #[derive(Debug)] 6 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 7 | pub struct Reno { 8 | cwnd: usize, 9 | min_cwnd: usize, 10 | ssthresh: usize, 11 | rwnd: usize, 12 | } 13 | 14 | impl Reno { 15 | pub fn new() -> Self { 16 | Reno { 17 | cwnd: 1024 * 2, 18 | min_cwnd: 1024 * 2, 19 | ssthresh: usize::MAX, 20 | rwnd: 64 * 1024, 21 | } 22 | } 23 | } 24 | 25 | impl Controller for Reno { 26 | fn window(&self) -> usize { 27 | self.cwnd 28 | } 29 | 30 | fn on_ack(&mut self, _now: Instant, len: usize, _rtt: &RttEstimator) { 31 | let len = if self.cwnd < self.ssthresh { 32 | // Slow start. 33 | len 34 | } else { 35 | self.ssthresh = self.cwnd; 36 | self.min_cwnd 37 | }; 38 | 39 | self.cwnd = self 40 | .cwnd 41 | .saturating_add(len) 42 | .min(self.rwnd) 43 | .max(self.min_cwnd); 44 | } 45 | 46 | fn on_duplicate_ack(&mut self, _now: Instant) { 47 | self.ssthresh = (self.cwnd >> 1).max(self.min_cwnd); 48 | } 49 | 50 | fn on_retransmit(&mut self, _now: Instant) { 51 | self.cwnd = (self.cwnd >> 1).max(self.min_cwnd); 52 | } 53 | 54 | fn set_mss(&mut self, mss: usize) { 55 | self.min_cwnd = mss; 56 | } 57 | 58 | fn set_remote_window(&mut self, remote_window: usize) { 59 | if self.rwnd < remote_window { 60 | self.rwnd = remote_window; 61 | } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod test { 67 | use crate::time::Instant; 68 | 69 | use super::*; 70 | 71 | #[test] 72 | fn test_reno() { 73 | let remote_window = 64 * 1024; 74 | let now = Instant::from_millis(0); 75 | 76 | for i in 0..10 { 77 | for j in 0..9 { 78 | let mut reno = Reno::new(); 79 | reno.set_mss(1480); 80 | 81 | // Set remote window. 82 | reno.set_remote_window(remote_window); 83 | 84 | reno.on_ack(now, 4096, &RttEstimator::default()); 85 | 86 | let mut n = i; 87 | for _ in 0..j { 88 | n *= i; 89 | } 90 | 91 | if i & 1 == 0 { 92 | reno.on_retransmit(now); 93 | } else { 94 | reno.on_duplicate_ack(now); 95 | } 96 | 97 | let elapsed = Instant::from_millis(1000); 98 | reno.on_ack(elapsed, n, &RttEstimator::default()); 99 | 100 | let cwnd = reno.window(); 101 | println!("Reno: elapsed = {}, cwnd = {}", elapsed, cwnd); 102 | 103 | assert!(cwnd >= reno.min_cwnd); 104 | assert!(reno.window() <= remote_window); 105 | } 106 | } 107 | } 108 | 109 | #[test] 110 | fn reno_min_cwnd() { 111 | let remote_window = 64 * 1024; 112 | let now = Instant::from_millis(0); 113 | 114 | let mut reno = Reno::new(); 115 | reno.set_remote_window(remote_window); 116 | 117 | for _ in 0..100 { 118 | reno.on_retransmit(now); 119 | assert!(reno.window() >= reno.min_cwnd); 120 | } 121 | } 122 | 123 | #[test] 124 | fn reno_set_rwnd() { 125 | let mut reno = Reno::new(); 126 | reno.set_remote_window(64 * 1024 * 1024); 127 | 128 | println!("{reno:?}"); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/socket/waker.rs: -------------------------------------------------------------------------------- 1 | use core::task::Waker; 2 | 3 | /// Utility struct to register and wake a waker. 4 | #[derive(Debug)] 5 | pub struct WakerRegistration { 6 | waker: Option, 7 | } 8 | 9 | impl WakerRegistration { 10 | pub const fn new() -> Self { 11 | Self { waker: None } 12 | } 13 | 14 | /// Register a waker. Overwrites the previous waker, if any. 15 | pub fn register(&mut self, w: &Waker) { 16 | match self.waker { 17 | // Optimization: If both the old and new Wakers wake the same task, we can simply 18 | // keep the old waker, skipping the clone. (In most executor implementations, 19 | // cloning a waker is somewhat expensive, comparable to cloning an Arc). 20 | Some(ref w2) if (w2.will_wake(w)) => {} 21 | // In all other cases 22 | // - we have no waker registered 23 | // - we have a waker registered but it's for a different task. 24 | // then clone the new waker and store it 25 | _ => self.waker = Some(w.clone()), 26 | } 27 | } 28 | 29 | /// Wake the registered waker, if any. 30 | pub fn wake(&mut self) { 31 | self.waker.take().map(|w| w.wake()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/storage/mod.rs: -------------------------------------------------------------------------------- 1 | /*! Specialized containers. 2 | 3 | The `storage` module provides containers for use in other modules. 4 | The containers support both pre-allocated memory, without the `std` 5 | or `alloc` crates being available, and heap-allocated memory. 6 | */ 7 | 8 | mod assembler; 9 | mod packet_buffer; 10 | mod ring_buffer; 11 | 12 | pub use self::assembler::Assembler; 13 | pub use self::packet_buffer::{PacketBuffer, PacketMetadata}; 14 | pub use self::ring_buffer::RingBuffer; 15 | 16 | /// A trait for setting a value to a known state. 17 | /// 18 | /// In-place analog of Default. 19 | pub trait Resettable { 20 | fn reset(&mut self); 21 | } 22 | 23 | /// Error returned when enqueuing into a full buffer. 24 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 25 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 26 | pub struct Full; 27 | 28 | /// Error returned when dequeuing from an empty buffer. 29 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 30 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 31 | pub struct Empty; 32 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use crate::iface::*; 4 | use crate::phy::{self, Device, DeviceCapabilities, Medium}; 5 | use crate::time::Instant; 6 | use crate::wire::*; 7 | 8 | pub(crate) fn setup<'a>(medium: Medium) -> (Interface, SocketSet<'a>, TestingDevice) { 9 | let mut device = TestingDevice::new(medium); 10 | 11 | let config = Config::new(match medium { 12 | #[cfg(feature = "medium-ethernet")] 13 | Medium::Ethernet => { 14 | HardwareAddress::Ethernet(EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02])) 15 | } 16 | #[cfg(feature = "medium-ip")] 17 | Medium::Ip => HardwareAddress::Ip, 18 | #[cfg(feature = "medium-ieee802154")] 19 | Medium::Ieee802154 => HardwareAddress::Ieee802154(Ieee802154Address::Extended([ 20 | 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 21 | ])), 22 | }); 23 | 24 | let mut iface = Interface::new(config, &mut device, Instant::ZERO); 25 | 26 | #[cfg(feature = "proto-ipv4")] 27 | { 28 | iface.update_ip_addrs(|ip_addrs| { 29 | ip_addrs 30 | .push(IpCidr::new(IpAddress::v4(192, 168, 1, 1), 24)) 31 | .unwrap(); 32 | ip_addrs 33 | .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)) 34 | .unwrap(); 35 | }); 36 | } 37 | 38 | #[cfg(feature = "proto-ipv6")] 39 | { 40 | iface.update_ip_addrs(|ip_addrs| { 41 | ip_addrs 42 | .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) 43 | .unwrap(); 44 | ip_addrs 45 | .push(IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128)) 46 | .unwrap(); 47 | ip_addrs 48 | .push(IpCidr::new(IpAddress::v6(0xfdbe, 0, 0, 0, 0, 0, 0, 1), 64)) 49 | .unwrap(); 50 | }); 51 | } 52 | 53 | (iface, SocketSet::new(vec![]), device) 54 | } 55 | 56 | /// A testing device. 57 | #[derive(Debug)] 58 | pub struct TestingDevice { 59 | pub(crate) tx_queue: VecDeque>, 60 | pub(crate) rx_queue: VecDeque>, 61 | max_transmission_unit: usize, 62 | medium: Medium, 63 | } 64 | 65 | #[allow(clippy::new_without_default)] 66 | impl TestingDevice { 67 | /// Creates a testing device. 68 | /// 69 | /// Every packet transmitted through this device will be received through it 70 | /// in FIFO order. 71 | pub fn new(medium: Medium) -> Self { 72 | TestingDevice { 73 | tx_queue: VecDeque::new(), 74 | rx_queue: VecDeque::new(), 75 | max_transmission_unit: match medium { 76 | #[cfg(feature = "medium-ethernet")] 77 | Medium::Ethernet => 1514, 78 | #[cfg(feature = "medium-ip")] 79 | Medium::Ip => 1500, 80 | #[cfg(feature = "medium-ieee802154")] 81 | Medium::Ieee802154 => 1500, 82 | }, 83 | medium, 84 | } 85 | } 86 | } 87 | 88 | impl Device for TestingDevice { 89 | type RxToken<'a> = RxToken; 90 | type TxToken<'a> = TxToken<'a>; 91 | 92 | fn capabilities(&self) -> DeviceCapabilities { 93 | DeviceCapabilities { 94 | medium: self.medium, 95 | max_transmission_unit: self.max_transmission_unit, 96 | ..DeviceCapabilities::default() 97 | } 98 | } 99 | 100 | fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { 101 | self.rx_queue.pop_front().map(move |buffer| { 102 | let rx = RxToken { buffer }; 103 | let tx = TxToken { 104 | queue: &mut self.tx_queue, 105 | }; 106 | (rx, tx) 107 | }) 108 | } 109 | 110 | fn transmit(&mut self, _timestamp: Instant) -> Option> { 111 | Some(TxToken { 112 | queue: &mut self.tx_queue, 113 | }) 114 | } 115 | } 116 | 117 | #[doc(hidden)] 118 | pub struct RxToken { 119 | buffer: Vec, 120 | } 121 | 122 | impl phy::RxToken for RxToken { 123 | fn consume(self, f: F) -> R 124 | where 125 | F: FnOnce(&[u8]) -> R, 126 | { 127 | f(&self.buffer) 128 | } 129 | } 130 | 131 | #[doc(hidden)] 132 | #[derive(Debug)] 133 | pub struct TxToken<'a> { 134 | queue: &'a mut VecDeque>, 135 | } 136 | 137 | impl<'a> phy::TxToken for TxToken<'a> { 138 | fn consume(self, len: usize, f: F) -> R 139 | where 140 | F: FnOnce(&mut [u8]) -> R, 141 | { 142 | let mut buffer = vec![0; len]; 143 | let result = f(&mut buffer); 144 | self.queue.push_back(buffer); 145 | result 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/wire/icmp.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "proto-ipv4")] 2 | use crate::wire::icmpv4; 3 | #[cfg(feature = "proto-ipv6")] 4 | use crate::wire::icmpv6; 5 | 6 | #[derive(Clone, PartialEq, Eq, Debug)] 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 8 | pub enum Repr<'a> { 9 | #[cfg(feature = "proto-ipv4")] 10 | Ipv4(icmpv4::Repr<'a>), 11 | #[cfg(feature = "proto-ipv6")] 12 | Ipv6(icmpv6::Repr<'a>), 13 | } 14 | #[cfg(feature = "proto-ipv4")] 15 | impl<'a> From> for Repr<'a> { 16 | fn from(s: icmpv4::Repr<'a>) -> Self { 17 | Repr::Ipv4(s) 18 | } 19 | } 20 | #[cfg(feature = "proto-ipv6")] 21 | impl<'a> From> for Repr<'a> { 22 | fn from(s: icmpv6::Repr<'a>) -> Self { 23 | Repr::Ipv6(s) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/wire/ipsec_esp.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, Result}; 2 | use byteorder::{ByteOrder, NetworkEndian}; 3 | 4 | /// A read/write wrapper around an IPSec Encapsulating Security Payload (ESP) packet buffer. 5 | #[derive(Debug, PartialEq, Eq)] 6 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 7 | pub struct Packet> { 8 | buffer: T, 9 | } 10 | 11 | mod field { 12 | use crate::wire::field::Field; 13 | 14 | pub const SPI: Field = 0..4; 15 | pub const SEQUENCE_NUMBER: Field = 4..8; 16 | } 17 | 18 | impl> Packet { 19 | /// Imbue a raw octet buffer with IPsec Encapsulating Security Payload packet structure. 20 | pub const fn new_unchecked(buffer: T) -> Packet { 21 | Packet { buffer } 22 | } 23 | 24 | /// Shorthand for a combination of [new_unchecked] and [check_len]. 25 | /// 26 | /// [new_unchecked]: #method.new_unchecked 27 | /// [check_len]: #method.check_len 28 | pub fn new_checked(buffer: T) -> Result> { 29 | let packet = Self::new_unchecked(buffer); 30 | packet.check_len()?; 31 | Ok(packet) 32 | } 33 | 34 | /// Ensure that no accessor method will panic if called. 35 | /// Returns `Err(Error)` if the buffer is too short. 36 | pub fn check_len(&self) -> Result<()> { 37 | let data = self.buffer.as_ref(); 38 | let len = data.len(); 39 | if len < field::SEQUENCE_NUMBER.end { 40 | Err(Error) 41 | } else { 42 | Ok(()) 43 | } 44 | } 45 | 46 | /// Consume the packet, returning the underlying buffer. 47 | pub fn into_inner(self) -> T { 48 | self.buffer 49 | } 50 | 51 | /// Return the security parameters index 52 | pub fn security_parameters_index(&self) -> u32 { 53 | let field = &self.buffer.as_ref()[field::SPI]; 54 | NetworkEndian::read_u32(field) 55 | } 56 | 57 | /// Return sequence number 58 | pub fn sequence_number(&self) -> u32 { 59 | let field = &self.buffer.as_ref()[field::SEQUENCE_NUMBER]; 60 | NetworkEndian::read_u32(field) 61 | } 62 | } 63 | 64 | impl> AsRef<[u8]> for Packet { 65 | fn as_ref(&self) -> &[u8] { 66 | self.buffer.as_ref() 67 | } 68 | } 69 | 70 | impl + AsMut<[u8]>> Packet { 71 | /// Set security parameters index field 72 | fn set_security_parameters_index(&mut self, value: u32) { 73 | let data = self.buffer.as_mut(); 74 | NetworkEndian::write_u32(&mut data[field::SPI], value) 75 | } 76 | 77 | /// Set sequence number 78 | fn set_sequence_number(&mut self, value: u32) { 79 | let data = self.buffer.as_mut(); 80 | NetworkEndian::write_u32(&mut data[field::SEQUENCE_NUMBER], value) 81 | } 82 | } 83 | 84 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 85 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 86 | pub struct Repr { 87 | security_parameters_index: u32, 88 | sequence_number: u32, 89 | } 90 | 91 | impl Repr { 92 | /// Parse an IPSec Encapsulating Security Payload packet and return a high-level representation. 93 | pub fn parse>(packet: &Packet) -> Result { 94 | packet.check_len()?; 95 | Ok(Repr { 96 | security_parameters_index: packet.security_parameters_index(), 97 | sequence_number: packet.sequence_number(), 98 | }) 99 | } 100 | 101 | /// Return the length of a packet that will be emitted from this high-level representation. 102 | pub const fn buffer_len(&self) -> usize { 103 | field::SEQUENCE_NUMBER.end 104 | } 105 | 106 | /// Emit a high-level representation into an IPSec Encapsulating Security Payload. 107 | pub fn emit + AsMut<[u8]>>(&self, packet: &mut Packet) { 108 | packet.set_security_parameters_index(self.security_parameters_index); 109 | packet.set_sequence_number(self.sequence_number); 110 | } 111 | } 112 | 113 | #[cfg(test)] 114 | mod test { 115 | use super::*; 116 | 117 | static PACKET_BYTES: [u8; 136] = [ 118 | 0xfb, 0x51, 0x28, 0xa6, 0x00, 0x00, 0x00, 0x02, 0x5d, 0xbe, 0x2d, 0x56, 0xd4, 0x6a, 0x57, 119 | 0xf5, 0xfc, 0x69, 0x8b, 0x3c, 0xa6, 0xb6, 0x88, 0x3a, 0x6c, 0xc1, 0x33, 0x92, 0xdb, 0x40, 120 | 0xab, 0x11, 0x54, 0xb4, 0x0f, 0x22, 0x4d, 0x37, 0x3a, 0x06, 0x94, 0x1e, 0xd4, 0x25, 0xaf, 121 | 0xf0, 0xb0, 0x11, 0x1f, 0x07, 0x96, 0x2a, 0xa7, 0x20, 0xb1, 0xf5, 0x52, 0xb2, 0x12, 0x46, 122 | 0xd6, 0xa5, 0x13, 0x4e, 0x97, 0x75, 0x44, 0x19, 0xc7, 0x29, 0x35, 0xc5, 0xed, 0xa4, 0x0c, 123 | 0xe7, 0x87, 0xec, 0x9c, 0xb1, 0x12, 0x42, 0x74, 0x7c, 0x12, 0x3c, 0x7f, 0x44, 0x9c, 0x6b, 124 | 0x46, 0x27, 0x28, 0xd2, 0x0e, 0xb1, 0x28, 0xd3, 0xd8, 0xc2, 0xd1, 0xac, 0x25, 0xfe, 0xef, 125 | 0xed, 0x13, 0xfd, 0x8f, 0x18, 0x9c, 0x2d, 0xb1, 0x0e, 0x50, 0xe9, 0xaa, 0x65, 0x93, 0x56, 126 | 0x40, 0x43, 0xa3, 0x72, 0x54, 0xba, 0x1b, 0xb1, 0xaf, 0xca, 0x04, 0x15, 0xf9, 0xef, 0xb7, 127 | 0x1d, 128 | ]; 129 | 130 | #[test] 131 | fn test_deconstruct() { 132 | let packet = Packet::new_unchecked(&PACKET_BYTES[..]); 133 | assert_eq!(packet.security_parameters_index(), 0xfb5128a6); 134 | assert_eq!(packet.sequence_number(), 2); 135 | } 136 | 137 | #[test] 138 | fn test_construct() { 139 | let mut bytes = vec![0xa5; 8]; 140 | let mut packet = Packet::new_unchecked(&mut bytes); 141 | packet.set_security_parameters_index(0xfb5128a6); 142 | packet.set_sequence_number(2); 143 | assert_eq!(&bytes, &PACKET_BYTES[..8]); 144 | } 145 | #[test] 146 | fn test_check_len() { 147 | assert!(matches!(Packet::new_checked(&PACKET_BYTES[..7]), Err(_))); 148 | assert!(matches!(Packet::new_checked(&PACKET_BYTES[..]), Ok(_))); 149 | } 150 | 151 | fn packet_repr() -> Repr { 152 | Repr { 153 | security_parameters_index: 0xfb5128a6, 154 | sequence_number: 2, 155 | } 156 | } 157 | 158 | #[test] 159 | fn test_parse() { 160 | let packet = Packet::new_unchecked(&PACKET_BYTES[..]); 161 | assert_eq!(Repr::parse(&packet).unwrap(), packet_repr()); 162 | } 163 | 164 | #[test] 165 | fn test_emit() { 166 | let mut bytes = vec![0x17; 8]; 167 | let mut packet = Packet::new_unchecked(&mut bytes); 168 | packet_repr().emit(&mut packet); 169 | assert_eq!(&bytes, &PACKET_BYTES[..8]); 170 | } 171 | 172 | #[test] 173 | fn test_buffer_len() { 174 | let header = Packet::new_unchecked(&PACKET_BYTES[..]); 175 | let repr = Repr::parse(&header).unwrap(); 176 | assert_eq!(repr.buffer_len(), 8); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/wire/ipv6hbh.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, Result}; 2 | use crate::config; 3 | use crate::wire::ipv6option::RouterAlert; 4 | use heapless::Vec; 5 | 6 | /// A read/write wrapper around an IPv6 Hop-by-Hop Header buffer. 7 | pub struct Header> { 8 | buffer: T, 9 | } 10 | 11 | impl> Header { 12 | /// Create a raw octet buffer with an IPv6 Hop-by-Hop Header structure. 13 | pub const fn new_unchecked(buffer: T) -> Self { 14 | Header { buffer } 15 | } 16 | 17 | /// Shorthand for a combination of [new_unchecked] and [check_len]. 18 | /// 19 | /// [new_unchecked]: #method.new_unchecked 20 | /// [check_len]: #method.check_len 21 | pub fn new_checked(buffer: T) -> Result { 22 | let header = Self::new_unchecked(buffer); 23 | header.check_len()?; 24 | Ok(header) 25 | } 26 | 27 | /// Ensure that no accessor method will panic if called. 28 | /// Returns `Err(Error)` if the buffer is too short. 29 | /// 30 | /// The result of this check is invalidated by calling [set_header_len]. 31 | /// 32 | /// [set_header_len]: #method.set_header_len 33 | pub fn check_len(&self) -> Result<()> { 34 | if self.buffer.as_ref().is_empty() { 35 | return Err(Error); 36 | } 37 | 38 | Ok(()) 39 | } 40 | 41 | /// Consume the header, returning the underlying buffer. 42 | pub fn into_inner(self) -> T { 43 | self.buffer 44 | } 45 | } 46 | 47 | impl<'a, T: AsRef<[u8]> + ?Sized> Header<&'a T> { 48 | /// Return the options of the IPv6 Hop-by-Hop header. 49 | pub fn options(&self) -> &'a [u8] { 50 | self.buffer.as_ref() 51 | } 52 | } 53 | 54 | impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Header<&'a mut T> { 55 | /// Return a mutable pointer to the options of the IPv6 Hop-by-Hop header. 56 | pub fn options_mut(&mut self) -> &mut [u8] { 57 | self.buffer.as_mut() 58 | } 59 | } 60 | 61 | /// A high-level representation of an IPv6 Hop-by-Hop Header. 62 | #[derive(Debug, PartialEq, Eq, Clone)] 63 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 64 | pub struct Repr<'a> { 65 | pub options: Vec, { config::IPV6_HBH_MAX_OPTIONS }>, 66 | } 67 | 68 | impl<'a> Repr<'a> { 69 | /// Parse an IPv6 Hop-by-Hop Header and return a high-level representation. 70 | pub fn parse(header: &'a Header<&'a T>) -> Result> 71 | where 72 | T: AsRef<[u8]> + ?Sized, 73 | { 74 | header.check_len()?; 75 | 76 | let mut options = Vec::new(); 77 | 78 | let iter = Ipv6OptionsIterator::new(header.options()); 79 | 80 | for option in iter { 81 | let option = option?; 82 | 83 | if let Err(e) = options.push(option) { 84 | net_trace!("error when parsing hop-by-hop options: {}", e); 85 | break; 86 | } 87 | } 88 | 89 | Ok(Self { options }) 90 | } 91 | 92 | /// Return the length, in bytes, of a header that will be emitted from this high-level 93 | /// representation. 94 | pub fn buffer_len(&self) -> usize { 95 | self.options.iter().map(|o| o.buffer_len()).sum() 96 | } 97 | 98 | /// Emit a high-level representation into an IPv6 Hop-by-Hop Header. 99 | pub fn emit + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) { 100 | let mut buffer = header.options_mut(); 101 | 102 | for opt in &self.options { 103 | opt.emit(&mut Ipv6Option::new_unchecked( 104 | &mut buffer[..opt.buffer_len()], 105 | )); 106 | buffer = &mut buffer[opt.buffer_len()..]; 107 | } 108 | } 109 | 110 | /// The hop-by-hop header containing a MLDv2 router alert option 111 | pub fn mldv2_router_alert() -> Self { 112 | let mut options = Vec::new(); 113 | options 114 | .push(Ipv6OptionRepr::RouterAlert( 115 | RouterAlert::MulticastListenerDiscovery, 116 | )) 117 | .unwrap(); 118 | Self { options } 119 | } 120 | 121 | /// Append a PadN option to the vector of hop-by-hop options 122 | pub fn push_padn_option(&mut self, n: u8) { 123 | self.options.push(Ipv6OptionRepr::PadN(n)).unwrap(); 124 | } 125 | } 126 | 127 | #[cfg(test)] 128 | mod tests { 129 | use super::*; 130 | use crate::wire::Error; 131 | 132 | // A Hop-by-Hop Option header with a PadN option of option data length 4. 133 | static REPR_PACKET_PAD4: [u8; 6] = [0x1, 0x4, 0x0, 0x0, 0x0, 0x0]; 134 | 135 | // A Hop-by-Hop Option header with a PadN option of option data length 12. 136 | static REPR_PACKET_PAD12: [u8; 14] = [ 137 | 0x1, 0x0C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 138 | ]; 139 | 140 | #[test] 141 | fn test_check_len() { 142 | // zero byte buffer 143 | assert_eq!( 144 | Err(Error), 145 | Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len() 146 | ); 147 | // valid 148 | assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len()); 149 | // valid 150 | assert_eq!( 151 | Ok(()), 152 | Header::new_unchecked(&REPR_PACKET_PAD12).check_len() 153 | ); 154 | } 155 | 156 | #[test] 157 | fn test_repr_parse_valid() { 158 | let header = Header::new_unchecked(&REPR_PACKET_PAD4); 159 | let repr = Repr::parse(&header).unwrap(); 160 | 161 | let mut options = Vec::new(); 162 | options.push(Ipv6OptionRepr::PadN(4)).unwrap(); 163 | assert_eq!(repr, Repr { options }); 164 | 165 | let header = Header::new_unchecked(&REPR_PACKET_PAD12); 166 | let repr = Repr::parse(&header).unwrap(); 167 | 168 | let mut options = Vec::new(); 169 | options.push(Ipv6OptionRepr::PadN(12)).unwrap(); 170 | assert_eq!(repr, Repr { options }); 171 | } 172 | 173 | #[test] 174 | fn test_repr_emit() { 175 | let mut options = Vec::new(); 176 | options.push(Ipv6OptionRepr::PadN(4)).unwrap(); 177 | let repr = Repr { options }; 178 | 179 | let mut bytes = [0u8; 6]; 180 | let mut header = Header::new_unchecked(&mut bytes); 181 | repr.emit(&mut header); 182 | 183 | assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..]); 184 | 185 | let mut options = Vec::new(); 186 | options.push(Ipv6OptionRepr::PadN(12)).unwrap(); 187 | let repr = Repr { options }; 188 | 189 | let mut bytes = [0u8; 14]; 190 | let mut header = Header::new_unchecked(&mut bytes); 191 | repr.emit(&mut header); 192 | 193 | assert_eq!(header.into_inner(), &REPR_PACKET_PAD12[..]); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/wire/pretty_print.rs: -------------------------------------------------------------------------------- 1 | /*! Pretty-printing of packet representation. 2 | 3 | The `pretty_print` module provides bits and pieces for printing concise, 4 | easily human readable packet listings. 5 | 6 | # Example 7 | 8 | A packet can be formatted using the `PrettyPrinter` wrapper: 9 | 10 | ```rust 11 | use smoltcp::wire::*; 12 | let buffer = vec![ 13 | // Ethernet II 14 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 15 | 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 16 | 0x08, 0x00, 17 | // IPv4 18 | 0x45, 0x00, 0x00, 0x20, 19 | 0x00, 0x00, 0x40, 0x00, 20 | 0x40, 0x01, 0xd2, 0x79, 21 | 0x11, 0x12, 0x13, 0x14, 22 | 0x21, 0x22, 0x23, 0x24, 23 | // ICMPv4 24 | 0x08, 0x00, 0x8e, 0xfe, 25 | 0x12, 0x34, 0xab, 0xcd, 26 | 0xaa, 0x00, 0x00, 0xff 27 | ]; 28 | 29 | let result = "\ 30 | EthernetII src=11-12-13-14-15-16 dst=01-02-03-04-05-06 type=IPv4\n\ 31 | \\ IPv4 src=17.18.19.20 dst=33.34.35.36 proto=ICMP (checksum incorrect)\n \ 32 | \\ ICMPv4 echo request id=4660 seq=43981 len=4\ 33 | "; 34 | 35 | #[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4"))] 36 | assert_eq!( 37 | result, 38 | &format!("{}", PrettyPrinter::>::new("", &buffer)) 39 | ); 40 | ``` 41 | */ 42 | 43 | use core::fmt; 44 | use core::marker::PhantomData; 45 | 46 | /// Indentation state. 47 | #[derive(Debug)] 48 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 49 | pub struct PrettyIndent { 50 | prefix: &'static str, 51 | level: usize, 52 | } 53 | 54 | impl PrettyIndent { 55 | /// Create an indentation state. The entire listing will be indented by the width 56 | /// of `prefix`, and `prefix` will appear at the start of the first line. 57 | pub fn new(prefix: &'static str) -> PrettyIndent { 58 | PrettyIndent { prefix, level: 0 } 59 | } 60 | 61 | /// Increase indentation level. 62 | pub fn increase(&mut self, f: &mut fmt::Formatter) -> fmt::Result { 63 | writeln!(f)?; 64 | self.level += 1; 65 | Ok(()) 66 | } 67 | } 68 | 69 | impl fmt::Display for PrettyIndent { 70 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | if self.level == 0 { 72 | write!(f, "{}", self.prefix) 73 | } else { 74 | write!(f, "{0:1$}{0:2$}\\ ", "", self.prefix.len(), self.level - 1) 75 | } 76 | } 77 | } 78 | 79 | /// Interface for printing listings. 80 | pub trait PrettyPrint { 81 | /// Write a concise, formatted representation of a packet contained in the provided 82 | /// buffer, and any nested packets it may contain. 83 | /// 84 | /// `pretty_print` accepts a buffer and not a packet wrapper because the packet might 85 | /// be truncated, and so it might not be possible to create the packet wrapper. 86 | fn pretty_print( 87 | buffer: &dyn AsRef<[u8]>, 88 | fmt: &mut fmt::Formatter, 89 | indent: &mut PrettyIndent, 90 | ) -> fmt::Result; 91 | } 92 | 93 | /// Wrapper for using a `PrettyPrint` where a `Display` is expected. 94 | pub struct PrettyPrinter<'a, T: PrettyPrint> { 95 | prefix: &'static str, 96 | buffer: &'a dyn AsRef<[u8]>, 97 | phantom: PhantomData, 98 | } 99 | 100 | impl<'a, T: PrettyPrint> PrettyPrinter<'a, T> { 101 | /// Format the listing with the recorded parameters when Display::fmt is called. 102 | pub fn new(prefix: &'static str, buffer: &'a dyn AsRef<[u8]>) -> PrettyPrinter<'a, T> { 103 | PrettyPrinter { 104 | prefix: prefix, 105 | buffer: buffer, 106 | phantom: PhantomData, 107 | } 108 | } 109 | } 110 | 111 | impl<'a, T: PrettyPrint + AsRef<[u8]>> PrettyPrinter<'a, T> { 112 | /// Create a `PrettyPrinter` which prints the given object. 113 | pub fn print(printable: &'a T) -> PrettyPrinter<'a, T> { 114 | PrettyPrinter { 115 | prefix: "", 116 | buffer: printable, 117 | phantom: PhantomData, 118 | } 119 | } 120 | } 121 | 122 | impl<'a, T: PrettyPrint> fmt::Display for PrettyPrinter<'a, T> { 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 124 | T::pretty_print(&self.buffer, f, &mut PrettyIndent::new(self.prefix)) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tests/snapshots/netsim__netsim.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/netsim.rs 3 | expression: s 4 | snapshot_kind: text 5 | --- 6 | buf\loss 0.000 0.001 0.010 0.020 0.050 0.100 0.200 0.300 7 | 128 1279.98 1255.76 1054.15 886.36 538.66 227.84 33.99 7.18 8 | 256 2559.91 2507.27 2100.03 1770.30 1070.71 468.24 66.71 14.35 9 | 512 5119.63 5011.95 4172.36 3531.57 2098.73 942.38 144.73 29.45 10 | 1024 10238.50 10023.19 8340.90 7084.25 4003.34 1869.94 290.74 60.92 11 | 2048 17535.11 17171.82 14093.50 12063.90 7205.27 3379.12 824.76 131.54 12 | 4096 35062.41 33852.31 27011.08 22073.09 13680.70 7631.11 1617.81 302.65 13 | 8192 77374.28 72409.99 58428.68 48310.75 29123.30 14314.36 2880.39 551.60 14 | 16384 161842.28 159448.56 141467.31 127073.06 78239.08 38637.20 7565.64 1112.31 15 | 32768 322944.88 314313.90 266384.37 245985.29 138762.29 83162.99 10739.10 1951.95 16 | -------------------------------------------------------------------------------- /utils/packet2pcap.rs: -------------------------------------------------------------------------------- 1 | use getopts::Options; 2 | use smoltcp::phy::{PcapLinkType, PcapSink}; 3 | use smoltcp::time::Instant; 4 | use std::env; 5 | use std::fs::File; 6 | use std::io::{self, Read}; 7 | use std::path::Path; 8 | use std::process::exit; 9 | 10 | fn convert( 11 | packet_filename: &Path, 12 | pcap_filename: &Path, 13 | link_type: PcapLinkType, 14 | ) -> io::Result<()> { 15 | let mut packet_file = File::open(packet_filename)?; 16 | let mut packet = Vec::new(); 17 | packet_file.read_to_end(&mut packet)?; 18 | 19 | let mut pcap_file = File::create(pcap_filename)?; 20 | PcapSink::global_header(&mut pcap_file, link_type); 21 | PcapSink::packet(&mut pcap_file, Instant::from_millis(0), &packet[..]); 22 | 23 | Ok(()) 24 | } 25 | 26 | fn print_usage(program: &str, opts: Options) { 27 | let brief = format!("Usage: {program} [options] INPUT OUTPUT"); 28 | print!("{}", opts.usage(&brief)); 29 | } 30 | 31 | fn main() { 32 | let args: Vec = env::args().collect(); 33 | let program = args[0].clone(); 34 | 35 | let mut opts = Options::new(); 36 | opts.optflag("h", "help", "print this help menu"); 37 | opts.optopt( 38 | "t", 39 | "link-type", 40 | "set link type (one of: ethernet ip)", 41 | "TYPE", 42 | ); 43 | 44 | let matches = match opts.parse(&args[1..]) { 45 | Ok(m) => m, 46 | Err(e) => { 47 | eprintln!("{e}"); 48 | return; 49 | } 50 | }; 51 | 52 | let link_type = match matches.opt_str("t").as_ref().map(|s| &s[..]) { 53 | Some("ethernet") => Some(PcapLinkType::Ethernet), 54 | Some("ip") => Some(PcapLinkType::Ip), 55 | _ => None, 56 | }; 57 | 58 | if matches.opt_present("h") || matches.free.len() != 2 || link_type.is_none() { 59 | print_usage(&program, opts); 60 | return; 61 | } 62 | 63 | match convert( 64 | Path::new(&matches.free[0]), 65 | Path::new(&matches.free[1]), 66 | link_type.unwrap(), 67 | ) { 68 | Ok(()) => (), 69 | Err(e) => { 70 | eprintln!("Cannot convert packet to pcap: {e}"); 71 | exit(1); 72 | } 73 | } 74 | } 75 | --------------------------------------------------------------------------------