Openwrt ntpd and ntpq time server

root@OpenWrt:~# ntpd -w -d -p 192.168.10.196
ntpd: sending query to 192.168.10.196
ntpd: reply from 192.168.10.196: offset:-0.002124 delay:0.010506 status:0x24 strat:1 refid:0x00535047 rootdelay:0.000000 reach:0x01
ntpd: sending query to 192.168.10.196
ntpd: reply from 192.168.10.196: offset:-0.001935 delay:0.008853 status:0x24 strat:1 refid:0x00535047 rootdelay:0.000000 reach:0x03
ntpd: sending query to 192.168.10.196
ntpd: reply from 192.168.10.196: offset:+0.003002 delay:0.008686 status:0x24 strat:1 refid:0x00535047 rootdelay:0.000000 reach:0x07


uci show system | grep ntp

cat /etc/config/system

/etc/init.d/sysntpd status
Usage: ntpd [-dnqNwl] [-I IFACE] [-S PROG] [-p PEER]...

NTP client/server

	-d[d]	Verbose
	-n	Run in foreground
	-q	Quit after clock is set
	-N	Run at high priority
	-w	Do not set time (only query peers), implies -n
	-S PROG	Run PROG after stepping time, stratum change, and every 11 min
	-p PEER	Obtain time from PEER (may be repeated)
	-l	Also run as server on port 123
	-I IFACE Bind server to IFACE, implies -l

Pi5 PTP clock – /dev/ptp0

  • Pi5 has ptp
root@lp-arm-5:~# ethtool -T eth0
Time stamping parameters for eth0:
Capabilities:
	hardware-transmit
	software-transmit
	hardware-receive
	software-receive
	software-system-clock
	hardware-raw-clock
Hardware timestamp provider index: 0
Hardware timestamp provider qualifier: Precise (IEEE 1588 quality)
Hardware Transmit Timestamp Modes:
	off
	on
	onestep-sync
Hardware Receive Filter Modes:
	none
	all

root@lp-arm-5:~# phc_ctl /dev/ptp0 get
phc_ctl[644486.511]: clock time is 1787465854.590438111 or Sun Aug 23 11:47:34 2026

root@lp-arm-5:~# phc_ctl /dev/ptp0 caps
phc_ctl[644504.310]: 
capabilities:
  64000000 maximum frequency adjustment (ppb)
  0 programable alarms
  0 external time stamp channels
  0 programmable periodic signals
  0 configurable input/output pins
  has pulse per second support
  doesn't have cross timestamping support
  doesn't have adjust phase support


root@lp-arm-5:~# date
sudo phc_ctl /dev/ptp0 get
sudo hwclock -r
Sun Aug 23 13:21:04 IST 2026
phc_ctl[645456.567]: clock time is 1787466824.601451911 or Sun Aug 23 12:03:44 2026

2026-08-23 13:21:04.580550+05:30

Rpi4 NTP server and PPM Time drift test

  • So pi has 1PPM error rate on it’s clock. it should dirft ~1second per day
systemctl status systemd-timesyncd

root@lp-arm-5:~# timedatectl timesync-status
       Server: 192.168.10.196 (192.168.10.196)
Poll interval: 34min 8s (min: 32s; max 34min 8s)
         Leap: normal
      Version: 4
      Stratum: 1
    Reference: GPS
    Precision: 1us (-20)
Root distance: 0 (max: 5s)
       Offset: -79.759ms
        Delay: 177.920ms
       Jitter: 392.723ms
 Packet count: 437

root@lp-arm-5:~# timedatectl show-timesync
SystemNTPServers=192.168.10.196
FallbackNTPServers=0.debian.pool.ntp.org 1.debian.pool.ntp.org
ServerName=192.168.10.196
ServerAddress=192.168.10.196
RootDistanceMaxUSec=5s
PollIntervalMinUSec=32s
PollIntervalMaxUSec=34min 8s
PollIntervalUSec=34min 8s
NTPMessage={ Leap=0, Version=4, Mode=4, Stratum=1, Precision=-20, RootDelay=0, RootDispersion=0, Reference=GPS, OriginateTimestamp=Sat 2026-08-22 13:55:50 IST, ReceiveTimestamp=Sat 2026-08-22 13:55:50 IST, TransmitTimestamp=Sat 2026-08-22 13:55:50 IST, DestinationTimestamp=Sat 2026-08-22 13:55:50 IST, Ignored=yes, PacketCount=437, Jitter=392.723ms }
Frequency=278345

root@lp-arm-5:~# cat  /etc/systemd/timesyncd.conf
[Time]
NTP=192.168.10.196
FallbackNTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org
[Time]
#NTP=
#FallbackNTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org
#RootDistanceMaxSec=5
#PollIntervalMinSec=32
#PollIntervalMaxSec=2048
#ConnectionRetrySec=30
#SaveIntervalSec=60

  • stoped ntp service
root@lp-arm-5:~# timedatectl  && ntpdate -q 192.168.10.90
               Local time: Sat 2026-08-22 14:55:11 IST
           Universal time: Sat 2026-08-22 09:25:11 UTC
                 RTC time: Sat 2026-08-22 09:25:11
                Time zone: Asia/Kolkata (IST, +0530)
System clock synchronized: yes
              NTP service: inactive
          RTC in local TZ: no
2026-08-22 14:55:11.120999 (+0530) +0.017578 +/- 0.004635 192.168.10.90 s1 no-leap



root@lp-arm-5:~# while true; do     ntpdate -q 192.168.10.90;     sleep 1; done
2026-08-22 14:56:07.223000 (+0530) +0.014346 +/- 0.009613 192.168.10.90 s1 no-leap
2026-08-22 14:56:08.310000 (+0530) +0.016642 +/- 0.008483 192.168.10.90 s1 no-leap
2026-08-22 14:56:09.393000 (+0530) +0.019201 +/- 0.004101 192.168.10.90 s1 no-leap
2026-08-22 14:56:10.473000 (+0530) +0.016223 +/- 0.012315 192.168.10.90 s1 no-leap
2026-08-22 14:56:11.563000 (+0530) +0.022523 +/- 0.005409 192.168.10.90 s1 no-leap
2026-08-22 14:56:12.657000 (+0530) +0.020756 +/- 0.004506 192.168.10.90 s1 no-leap
2026-08-22 14:56:13.748000 (+0530) +0.027691 +/- 0.013002 192.168.10.90 s1 no-leap
2026-08-22 14:56:14.824999 (+0530) +0.020858 +/- 0.004934 192.168.10.90 s1 no-leap
2026-08-22 14:56:15.902000 (+0530) +0.020752 +/- 0.006207 192.168.10.90 s1 no-leap
2026-08-22 14:56:16.980000 (+0530) +0.022216 +/- 0.004206 192.168.10.90 s1 no-leap
2026-08-22 14:56:18.081999 (+0530) +0.021030 +/- 0.007626 192.168.10.90 s1 no-leap
2026-08-22 14:56:19.162000 (+0530) +0.022143 +/- 0.005573 192.168.10.90 s1 no-leap
2026-08-22 14:56:20.237000 (+0530) +0.020466 +/- 0.005176 192.168.10.90 s1 no-leap
2026-08-22 14:56:21.313000 (+0530) +0.018130 +/- 0.005373 192.168.10.90 s1 no-leap
2026-08-22 14:56:22.394000 (+0530) +0.019911 +/- 0.005312 192.168.10.90 s1 no-leap
2026-08-22 14:56:23.501999 (+0530) +0.022654 +/- 0.006490 192.168.10.90 s1 no-leap
2026-08-22 14:56:24.587999 (+0530) +0.023260 +/- 0.011551 192.168.10.90 s1 no-leap
  • After ~12 hours
root@lp-arm-5:~# head /var/log/gnss.log 
===== GNSS NTP measurement Sat Aug 22 23:12:48 IST 2026 =====
2026-08-22 23:11:43.945 offset=158783.000 us drift=N/A us/s drift=N/A ppm
2026-08-22 23:11:45.030 offset=156998.000 us drift=1625.399 us/s drift=1625.399 ppm
2026-08-22 23:11:46.114 offset=160638.000 us drift=-3348.105 us/s drift=-3348.105 ppm
2026-08-22 23:11:47.198 offset=162109.000 us drift=-1357.056 us/s drift=-1357.056 ppm
2026-08-22 23:11:48.281 offset=161996.000 us drift=104.323 us/s drift=104.323 ppm
2026-08-22 23:11:49.388 offset=159383.000 us drift=2412.206 us/s drift=2412.206 ppm
2026-08-22 23:11:50.496 offset=160982.000 us drift=-1443.382 us/s drift=-1443.382 ppm
2026-08-22 23:11:51.578 offset=160685.000 us drift=268.102 us/s drift=268.102 ppm
2026-08-22 23:11:52.694 offset=145077.000 us drift=14426.432 us/s drift=14426.432 ppm

root@lp-arm-5:~# tail /var/log/gnss.log 
2026-08-23 12:07:41.968 offset=395067.000 us drift=-5542.332 us/s drift=-5542.332 ppm
2026-08-23 12:07:43.079 offset=397609.000 us drift=-2343.630 us/s drift=-2343.630 ppm
2026-08-23 12:07:44.187 offset=394536.000 us drift=2763.032 us/s drift=2763.032 ppm
2026-08-23 12:07:45.282 offset=388875.000 us drift=5117.089 us/s drift=5117.089 ppm
2026-08-23 12:07:46.366 offset=394366.000 us drift=-5015.377 us/s drift=-5015.377 ppm
2026-08-23 12:07:47.448 offset=395276.000 us drift=-839.462 us/s drift=-839.462 ppm
2026-08-23 12:07:48.543 offset=393550.000 us drift=1595.229 us/s drift=1595.229 ppm
2026-08-23 12:07:49.660 offset=389780.000 us drift=3440.987 us/s drift=3440.987 ppm
2026-08-23 12:07:50.758 offset=389441.000 us drift=303.221 us/s drift=303.221 ppm
First sample:
2026-08-22 23:11:43.945
offset = 158783 us
       = 158.783 ms

Last sample:
2026-08-23 12:07:50.758
offset = 389441 us
       = 389.441 ms

Therefore:
389.441 ms - 158.783 ms
= 230.658 ms

So if this trend continued, you'd expect roughly:
1 day    ≈ +428 ms
7 days   ≈ +3.0 seconds
30 days  ≈ +12.8 seconds
1 year   ≈ +156 seconds (~2.6 minutes)
  • So the Pi4 clock offset increased by approximately:
    +230.7 ms

Local DNS server and Upstream forward

  • Get the DNS server names and config
@home:~$ resolvectl status
Global
         Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
  resolv.conf mode: stub

Link 2 (enp3s0)
    Current Scopes: none
         Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported

Link 3 (wlo1)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 1.1.1.1
       DNS Servers: 192.168.1.1 1.1.1.1

@home:~$ resolvectl dns
Global:
Link 2 (enp3s0):
Link 3 (wlo1): 192.168.1.1


@home:~$ cat /etc/resolv.conf 
nameserver 127.0.0.53
options edns0 trust-ad
search .
  • to use your local DNS make only 1 DNS server in config and the other DNS server will upstream server of local dns so that they don’t interfere with each other

esp32 s3 issues with USB serial print – CDC/ JTAG

  • USB port can act as USB serial print port
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
build_flags =
	-DARDUINO_USB_MODE=1
	-DARDUINO_USB_CDC_ON_BOOT=1
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM0
monitor_speed = 115200
monitor_dtr = 0
monitor_rts = 0
  • ESP32-S3 USB CDC not being enabled at boot
  • UART with chip CP2102 is not working on my esp32-s3
@home:~$ ls  /dev/ttyUSB*
/dev/ttyUSB0

@home:~$ esptool --chip esp32s3 --port /dev/ttyUSB0 chip_id
Warning: Deprecated: Command 'chip_id' is deprecated. Use 'chip-id' instead.
esptool v5.3.0
Serial port /dev/ttyUSB0:
Connecting......................................

A fatal error occurred: Failed to connect to ESP32-S3: No serial data received.
For troubleshooting steps visit: https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html

@home:~$ esptool --chip esp32s3 --port /dev/ttyUSB0 chip_id
Warning: Deprecated: Command 'chip_id' is deprecated. Use 'chip-id' instead.
esptool v5.3.0
Connected to ESP32-S3 on /dev/ttyUSB0:
Chip type:          ESP32-S3 (QFN56) (revision v0.2)
Features:           Wi-Fi, BT 5 (LE), Dual Core + LP Core, 240MHz, Embedded PSRAM 8MB (AP_3v3)
Crystal frequency:  40MHz
MAC:                28:84:85:xx:xx:xx

Stub flasher running.

Warning: ESP32-S3 has no chip ID. Reading MAC address instead.
MAC:                28:84:85:xx:xx:xx

Hard resetting via RTS pin...

sdc41 with bmp280 issue – wrong ppm value – zero ppm

  • Today got to know why my sdc41 sensor was showing bad data earlier it used to show 500 in day and 600-800ppm in night when door was closed. #sdc41 #esp32
    • It was paired with bmp280 and it was using pressure to Compensate the co2. but bmp 280 was showing bad value randomly about -149 hpa
    • As it was using BMP280 value it was started showing double and after few days it started showing 0 ppm
    • Now i ran simple code to just get value of sdc41 it was showing 23000 which more than current limit of sdc41 which is 5000
    • Ran frc 420 to self calibrate the sensor with current value to 420 reference
  • Ran auto calibration , everything looks good now.
#include <Arduino.h>
#include <Wire.h>
#include <SparkFun_SCD4x_Arduino_Library.h>

// NodeMCU/ESP8266 I2C pins: D2 = SDA, D1 = SCL
static const uint8_t SDA_PIN = D2;
static const uint8_t SCL_PIN = D1;

static const uint16_t MAX_VALID_CO2_PPM = 5000;
static const uint16_t DEFAULT_FRC_REFERENCE_PPM = 420; // Typical fresh-air reference
static const uint32_t FRC_STABILIZE_MS = 180000;       // >= 3 minutes per datasheet
static const float DEFAULT_AMBIENT_PRESSURE_HPA = 1006.0f;

SCD4x scd4x(SCD4x_SENSOR_SCD41);

bool applyAmbientPressureCompensation(float pressureHpa, bool persist = true) {
  if (pressureHpa < 700.0f || pressureHpa > 1200.0f) {
    Serial.println("ERROR: pressure out of expected range (700..1200 hPa).");
    return false;
  }

  const float pressurePa = pressureHpa * 100.0f;

  scd4x.stopPeriodicMeasurement();
  delay(500);

  if (!scd4x.setAmbientPressure(pressurePa)) {
    Serial.println("ERROR: Failed to set ambient pressure compensation.");
    scd4x.startPeriodicMeasurement();
    return false;
  }

  if (persist && !scd4x.persistSettings()) {
    Serial.println("WARNING: Ambient pressure set but persistSettings failed.");
  }

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Failed to restart periodic measurement after pressure update.");
    return false;
  }

  Serial.print("Ambient pressure compensation set to ");
  Serial.print(pressureHpa, 1);
  Serial.println(" hPa");
  return true;
}

bool configureAutoSelfCalibration(bool enabled) {
  scd4x.stopPeriodicMeasurement();
  delay(500);

  if (!scd4x.setAutomaticSelfCalibrationEnabled(enabled)) {
    Serial.println("ERROR: Failed to set ASC state.");
    return false;
  }

  if (!scd4x.persistSettings()) {
    Serial.println("WARNING: ASC updated but persistSettings failed.");
  }

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Failed to restart periodic measurement after ASC config.");
    return false;
  }

  Serial.print("ASC is now ");
  Serial.println(enabled ? "ENABLED" : "DISABLED");
  return true;
}

bool runForcedRecalibration(uint16_t referencePpm) {
  if (referencePpm < 350 || referencePpm > 2000) {
    Serial.println("ERROR: Reference ppm out of expected FRC range (350..2000).");
    return false;
  }

  Serial.println();
  Serial.println("Starting Forced Recalibration (FRC)...");
  Serial.println("Keep sensor in stable, known CO2 environment.");
  Serial.print("Reference CO2 (ppm): ");
  Serial.println(referencePpm);

  scd4x.stopPeriodicMeasurement();
  delay(500);
  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Could not start periodic measurement for FRC preconditioning.");
    return false;
  }

  // Datasheet requirement: run in normal mode for at least 3 minutes before FRC.
  const uint32_t startMs = millis();
  while (millis() - startMs < FRC_STABILIZE_MS) {
    const uint32_t remaining = (FRC_STABILIZE_MS - (millis() - startMs)) / 1000;
    Serial.print("FRC preconditioning... ");
    Serial.print(remaining);
    Serial.println(" s remaining");

    if (scd4x.readMeasurement()) {
      Serial.print("  sample CO2(ppm): ");
      Serial.println(scd4x.getCO2());
    }

    delay(5000);
  }

  scd4x.stopPeriodicMeasurement();
  delay(500);

  float correction = 0.0f;
  if (!scd4x.performForcedRecalibration(referencePpm, &correction)) {
    Serial.println("ERROR: FRC failed.");
    scd4x.startPeriodicMeasurement();
    return false;
  }

  Serial.print("FRC correction applied (ppm): ");
  Serial.println(correction, 1);

  if (!scd4x.persistSettings()) {
    Serial.println("WARNING: FRC succeeded but persistSettings failed.");
  }

  if (!scd4x.reInit()) {
    Serial.println("WARNING: reInit failed after FRC.");
  }

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Could not restart periodic measurement after FRC.");
    return false;
  }

  Serial.println("FRC complete. Sensor restarted in periodic mode.");
  return true;
}

void handleSerialCommands() {
  if (!Serial.available()) {
    return;
  }

  String cmd = Serial.readStringUntil('\n');
  cmd.trim();
  cmd.toLowerCase();

  if (cmd == "help") {
    Serial.println("Commands:");
    Serial.println("  help         -> show commands");
    Serial.println("  asc on       -> enable automatic self-calibration");
    Serial.println("  asc off      -> disable automatic self-calibration");
    Serial.println("  frc          -> run forced recalibration with 420 ppm reference");
    Serial.println("  frc <ppm>    -> run forced recalibration with custom reference");
    Serial.println("  pressure     -> set pressure compensation to 1006 hPa");
    return;
  }

  if (cmd == "asc on") {
    configureAutoSelfCalibration(true);
    return;
  }

  if (cmd == "asc off") {
    configureAutoSelfCalibration(false);
    return;
  }

  if (cmd == "frc") {
    runForcedRecalibration(DEFAULT_FRC_REFERENCE_PPM);
    return;
  }

  if (cmd.startsWith("frc ")) {
    int ppm = cmd.substring(4).toInt();
    if (ppm <= 0) {
      Serial.println("ERROR: Invalid ppm. Example: frc 420");
      return;
    }
    runForcedRecalibration((uint16_t)ppm);
    return;
  }

  if (cmd == "pressure") {
    applyAmbientPressureCompensation(DEFAULT_AMBIENT_PRESSURE_HPA, true);
    return;
  }

  Serial.print("Unknown command: ");
  Serial.println(cmd);
  Serial.println("Type 'help' for available commands.");
}

void setup() {
  Serial.begin(115200);
  delay(200);

  Serial.println();
  Serial.println("SCD41 minimal CO2 test starting...");

  Wire.begin(SDA_PIN, SCL_PIN);
  Wire.setClock(100000);

  if (!scd4x.begin(true, true, false)) {
    Serial.println("ERROR: SCD41 init failed. Check wiring, power, and I2C address.");
    while (true) {
      delay(1000);
    }
  }

  // Ensure periodic mode starts from a clean state.
  scd4x.stopPeriodicMeasurement();
  delay(500);

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Could not start low-power periodic measurement.");
    while (true) {
      delay(1000);
    }
  }

  // Recommended for SCD41 long-term stability in real deployments.
  configureAutoSelfCalibration(true);

  // Overwrite any previously saved bad pressure setting (e.g. from a faulty upstream sensor)
  // and persist the correct local pressure compensation.
  applyAmbientPressureCompensation(DEFAULT_AMBIENT_PRESSURE_HPA, true);

  Serial.println("SCD41 initialized. First reading can take ~30s.");
  Serial.println("Type 'help' in Serial Monitor for calibration commands.");
}

void loop() {
  handleSerialCommands();

  if (!scd4x.readMeasurement()) {
    Serial.println("Measurement not ready yet...");
    delay(5000);
    return;
  }

  const uint16_t co2 = scd4x.getCO2();
  const float temperature = scd4x.getTemperature();
  const float humidity = scd4x.getHumidity();

  Serial.print("CO2(ppm): ");
  Serial.print(co2);
  Serial.print("  Temp(C): ");
  Serial.print(temperature, 2);
  Serial.print("  RH(%): ");
  Serial.println(humidity, 2);

  if (co2 == 0) {
    Serial.println("WARNING: CO2 is 0 ppm (invalid on SCD41). Sensor may still be stabilizing or faulty.");
  } else if (co2 > MAX_VALID_CO2_PPM) {
    Serial.println("WARNING: CO2 is above SCD41 range (5000 ppm). Reading likely invalid; run 'frc 420' in fresh air.");
  }

  delay(5000);
}
FRC preconditioning... 24 s remaining
  sample CO2(ppm): 23076
FRC preconditioning... 19 s remaining
  sample CO2(ppm): 23076
FRC preconditioning... 14 s remaining
  sample CO2(ppm): 23077
FRC preconditioning... 9 s remaining
  sample CO2(ppm): 23075
FRC preconditioning... 4 s remaining
  sample CO2(ppm): 23078
FRC correction applied (ppm): -22518.0
FRC complete. Sensor restarted in periodic mode.
Measurement not ready yet...
CO2(ppm): 471  Temp(C): 30.83  RH(%): 73.06
CO2(ppm): 472  Temp(C): 30.39  RH(%): 74.56
CO2(ppm): 488  Temp(C): 30.14  RH(%): 75.75
CO2(ppm): 486  Temp(C): 29.92  RH(%): 76.85
CO2(ppm): 469  Temp(C): 29.68  RH(%): 77.88

raspberry pi useful commands

  • See voltage of all pin – can be used in TRNG
root@lp-arm-5:~# vcgencmd pmic_read_adc
 3V7_WL_SW_A current(0)=0.09271335A
   3V3_SYS_A current(1)=0.07026696A
   1V8_SYS_A current(2)=0.12589500A
  DDR_VDD2_A current(3)=0.00292779A
  DDR_VDDQ_A current(4)=0.00000000A
   1V1_SYS_A current(5)=0.20299340A
    0V8_SW_A current(6)=0.35523850A
  VDD_CORE_A current(7)=0.68761000A
   3V3_DAC_A current(17)=0.00000000A
   3V3_ADC_A current(18)=0.00006105A
   0V8_AON_A current(16)=0.00323565A
      HDMI_A current(22)=0.01172160A
 3V7_WL_SW_V volt(8)=3.69977600V
   3V3_SYS_V volt(9)=3.30632200V
   1V8_SYS_V volt(10)=1.79975400V
  DDR_VDD2_V volt(11)=1.10732500V
  DDR_VDDQ_V volt(12)=0.60402870V
   1V1_SYS_V volt(13)=1.10256300V
    0V8_SW_V volt(14)=0.79926660V
  VDD_CORE_V volt(15)=0.72097620V
   3V3_DAC_V volt(20)=3.30768900V
   3V3_ADC_V volt(21)=3.30585700V
   0V8_AON_V volt(19)=0.79736190V
      HDMI_V volt(23)=5.02902000V
     EXT5V_V volt(24)=5.02098000V
      BATT_V volt(25)=0.00000000V

Openwrt openvpn client with local network

  • Zone – Add

Example (/etc/config/firewall):

config zone
    option name 'vpn'
    list network 'vpn'
    option input 'ACCEPT'
    option output 'ACCEPT'
    option forward 'ACCEPT'
    option masq '1'
    option mtu_fix '1'

config forwarding
    option src 'lan'
    option dest 'vpn'

And define the network:

config interface 'vpn'
    option proto 'none'
    option device 'tun0'

Then restart:

/etc/init.d/network restart
/etc/init.d/firewall restart

Run this on OpenWrt:

tcpdump -ni tun0 icmp

Then, from your laptop:

ping 192.168.0.1

  • Static route
  • Install openvpn
opkg install openvpn-openssl luci-app-openvpn
opkg install openvpn-easy-rsa
/etc/openvpn/client.ovpn

/etc/init.d/openvpn enable
/etc/init.d/openvpn start

/etc/init.d/openvpn status

logread -f openvpn

Openwrt and local router static route

  • static route on wifi router
  • static route on openwrt
  • network digram
  • Now i can do ssh from pi5 to ubuntu-laptop on 192.168.1.145 even when i disconnected form wifi on ubuntu-laptop.
root@lp-arm-5:~# ssh root@192.168.1.145 
root@192.168.1.145's password: 
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.17.0-14-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

Expanded Security Maintenance for Applications is not enabled.

376 updates can be applied immediately.
364 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

89 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm

Last login: Sat Jul 25 18:59:08 2026 from 192.168.10.118