From 8f992b082c696e1469d30e3a6a738c32be1570e8 Mon Sep 17 00:00:00 2001 From: IFindMe <167074133+IFindMe@users.noreply.github.com> Date: Wed, 12 Aug 2026 05:52:38 -0400 Subject: [PATCH 1/5] feat(teensy41): extend M0 with DNS and TCP diagnostics --- firmware/teensy41/src/main.cpp | 80 ++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/firmware/teensy41/src/main.cpp b/firmware/teensy41/src/main.cpp index 793b3fe..ec11ad4 100644 --- a/firmware/teensy41/src/main.cpp +++ b/firmware/teensy41/src/main.cpp @@ -4,12 +4,61 @@ using namespace qindesign::network; EthernetUDP udp; +EthernetClient tcp; static void printAddress(const char *label, const IPAddress &address) { Serial.print(label); Serial.println(address); } +static bool waitForDhcp(uint32_t timeoutMs) { + const uint32_t start = millis(); + while (Ethernet.localIP() == INADDR_NONE && millis() - start < timeoutMs) { + Ethernet.maintain(); + delay(100); + } + return Ethernet.localIP() != INADDR_NONE; +} + +static bool testDns() { + IPAddress address; + if (!Ethernet.hostByName("example.com", address)) { + Serial.println("[FAIL] DNS lookup example.com"); + return false; + } + Serial.print("[PASS] DNS example.com -> "); + Serial.println(address); + return true; +} + +static bool testTcp() { + Serial.println("[TEST] TCP example.com:80"); + if (!tcp.connect("example.com", 80)) { + Serial.println("[FAIL] TCP connection"); + return false; + } + + tcp.println("GET / HTTP/1.1"); + tcp.println("Host: example.com"); + tcp.println("Connection: close"); + tcp.println(); + + const uint32_t start = millis(); + bool received = false; + while (millis() - start < 5000) { + Ethernet.maintain(); + if (tcp.available()) { + received = true; + break; + } + delay(10); + } + + tcp.stop(); + Serial.println(received ? "[PASS] TCP data received" : "[FAIL] TCP data timeout"); + return received; +} + void setup() { Serial.begin(115200); delay(1000); @@ -20,27 +69,19 @@ void setup() { Ethernet.begin(); - const uint32_t start = millis(); - while (!Ethernet.linkStatus() && millis() - start < 10000) { + const uint32_t linkStart = millis(); + while (!Ethernet.linkStatus() && millis() - linkStart < 10000) { delay(100); } - if (Ethernet.linkStatus()) { - Serial.println("[PASS] Ethernet link detected"); - } else { - Serial.println("[FAIL] Ethernet link not detected"); - } - - const uint32_t dhcpStart = millis(); - while (Ethernet.localIP() == INADDR_NONE && millis() - dhcpStart < 20000) { - Ethernet.maintain(); - delay(100); - } + Serial.println(Ethernet.linkStatus() + ? "[PASS] Ethernet link detected" + : "[FAIL] Ethernet link not detected"); - if (Ethernet.localIP() != INADDR_NONE) { - Serial.println("[PASS] IPv4 address acquired"); - } else { + if (!waitForDhcp(20000)) { Serial.println("[FAIL] DHCP did not provide an IPv4 address"); + } else { + Serial.println("[PASS] IPv4 address acquired"); } printAddress("IP: ", Ethernet.localIP()); @@ -55,7 +96,12 @@ void setup() { Serial.println("[FAIL] UDP socket initialization"); } - Serial.println("M0 hardware test ready."); + if (Ethernet.localIP() != INADDR_NONE) { + testDns(); + testTcp(); + } + + Serial.println("M0 network validation complete."); } void loop() { From 449d23d673f1ce46caae01e6c8c75bad74879e50 Mon Sep 17 00:00:00 2001 From: IFindMe <167074133+IFindMe@users.noreply.github.com> Date: Wed, 12 Aug 2026 05:52:51 -0400 Subject: [PATCH 2/5] chore(teensy41): keep M0 PlatformIO configuration From 639a46ed59ef2f26e31380782801a4193954b646 Mon Sep 17 00:00:00 2001 From: IFindMe <167074133+IFindMe@users.noreply.github.com> Date: Wed, 12 Aug 2026 05:53:00 -0400 Subject: [PATCH 3/5] feat(teensy41): define M1 cryptography interface --- firmware/teensy41/include/ts_crypto.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 firmware/teensy41/include/ts_crypto.h diff --git a/firmware/teensy41/include/ts_crypto.h b/firmware/teensy41/include/ts_crypto.h new file mode 100644 index 0000000..c72c009 --- /dev/null +++ b/firmware/teensy41/include/ts_crypto.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace ts::crypto { + +constexpr size_t kKeySize = 32; +constexpr size_t kHashSize = 32; + +// M1 interface. The implementation will be backed by an audited crypto +// library after host and Teensy test vectors are established. +bool randomBytes(uint8_t *out, size_t length); +bool x25519PublicKey(uint8_t publicKey[kKeySize], const uint8_t privateKey[kKeySize]); +bool x25519(uint8_t sharedSecret[kKeySize], const uint8_t privateKey[kKeySize], const uint8_t peerPublicKey[kKeySize]); +bool blake2s(uint8_t digest[kHashSize], const uint8_t *message, size_t length); + +} // namespace ts::crypto From 496215b11d115918f14dfd4640573e151e032a86 Mon Sep 17 00:00:00 2001 From: IFindMe <167074133+IFindMe@users.noreply.github.com> Date: Wed, 12 Aug 2026 05:53:06 -0400 Subject: [PATCH 4/5] docs(teensy41): define M1 cryptography requirements --- firmware/teensy41/docs/m1-crypto.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 firmware/teensy41/docs/m1-crypto.md diff --git a/firmware/teensy41/docs/m1-crypto.md b/firmware/teensy41/docs/m1-crypto.md new file mode 100644 index 0000000..e17ed00 --- /dev/null +++ b/firmware/teensy41/docs/m1-crypto.md @@ -0,0 +1,19 @@ +# M1 — cryptography + +M1 establishes the cryptographic foundation required before implementing the Tailscale control/data planes. + +## Required primitives + +- CSPRNG / hardware-backed entropy source +- X25519 / Curve25519 +- ChaCha20-Poly1305 +- BLAKE2s +- constant-time operations and secure key wiping + +## Test strategy + +Every primitive gets known-answer tests on the host and the same vectors on Teensy 4.1. Shared-secret agreement is tested with two independent key pairs. AEAD tests cover valid, modified-ciphertext, modified-AAD, and nonce-reuse rejection at the protocol layer. + +Do not put production keys or Tailscale auth keys in examples, tests, CI logs, or source control. + +Tailscale nodes use machine and node key pairs; private keys stay on the device while public node keys are distributed by the control plane. Auth keys are only provisioning credentials and are not a replacement for the node's private key. From 0767fa7598092556d14ad0f681bfeaf47f5813cb Mon Sep 17 00:00:00 2001 From: IFindMe <167074133+IFindMe@users.noreply.github.com> Date: Wed, 12 Aug 2026 05:53:11 -0400 Subject: [PATCH 5/5] ci(teensy41): add PlatformIO firmware build --- .github/workflows/teensy41.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/teensy41.yml diff --git a/.github/workflows/teensy41.yml b/.github/workflows/teensy41.yml new file mode 100644 index 0000000..6531735 --- /dev/null +++ b/.github/workflows/teensy41.yml @@ -0,0 +1,25 @@ +name: Teensy 4.1 firmware + +on: + push: + paths: + - 'firmware/teensy41/**' + - '.github/workflows/teensy41.yml' + pull_request: + paths: + - 'firmware/teensy41/**' + - '.github/workflows/teensy41.yml' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install PlatformIO + run: pip install platformio + - name: Build Teensy 4.1 firmware + working-directory: firmware/teensy41 + run: pio run -e teensy41