Page 1 of 1

ESP32 WiFi/Ethernet bridge with single DHCP server on both

Posted: Thu Oct 09, 2025 2:15 pm
by theGtknerd
I am trying to create a low-latency device similar to a router without NAT, without internet, and no buffering. All devices on WiFi (softAP mode) get the Ethernet traffic and vice versa. A DHCP server would operate on both WiFi and Ethernet using the same subnet/address range. Years ago when I visited this problem, the answer was to manually direct the traffic to the other interface. I never implemented any of these suggestions. Now with AI and the https://github.com/espressif/esp-iot-bridge library I hoped to pull together a better codebase, but alas. I have had a DHCP server on Ethernet or a DHCP server on WiFi, but I can't seem to get the same DHCP server on both. I am using esp-idf which is still an uphill battle, as I am used to Arduino/PlatformIO. Here's my current code.

Code: Select all

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_bridge.h"
#include "esp_bridge_internal.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_eth.h"
#include "nvs_flash.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include "esp_netif_br_glue.h"
#include "esp_http_server.h"
#include "driver/gpio.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "lwip/ip_addr.h"
#include "dhcpserver/dhcpserver.h"
#include "netif/ethernet.h"
#include <sys/socket.h>

static const char *TAG = "AOG-BRIDGE";

// WiFi AP Configuration
#define WIFI_SSID      "AOG hub"
#define WIFI_PASS      "password"
#define WIFI_CHANNEL   1
#define MAX_STA_CONN   6

// Network Configuration
#define BRIDGE_IP      "192.168.5.1"
#define BRIDGE_NETMASK "255.255.255.0"
#define BRIDGE_GW      "192.168.5.1"
#define DHCP_START     "192.168.5.2"
#define DHCP_END       "192.168.5.254"

// ETH Configuration
#define ETH_PHY_ADDR        1
#define ETH_PHY_POWER_PIN   16
#define ETH_MDC_GPIO        23
#define ETH_MDIO_GPIO       18

// Global handles
esp_netif_ip_info_t ip;

// NVS Keys
#define NVS_NAMESPACE "wifi_config"
#define NVS_SSID_KEY  "ssid"
#define NVS_PASS_KEY  "password"

static void setup_dhcp_server(void)
{
    ESP_LOGI(TAG, "Configuring DHCP Server...");

    // Create default AP netif configuration
    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_WIFI_AP();
    
    // Create the netif instance
    esp_netif_t *br_netif = esp_netif_new(&cfg);
    if (br_netif == NULL) {
        ESP_LOGE(TAG, "Failed to get bridge netif!");
        return;
    }
    
    esp_netif_dhcps_stop(br_netif);

    // Set static IP for the bridge interface
    esp_netif_ip_info_t ip_info;
    memset(&ip_info, 0, sizeof(esp_netif_ip_info_t));
    inet_pton(AF_INET, BRIDGE_IP, &ip_info.ip);
    inet_pton(AF_INET, BRIDGE_NETMASK, &ip_info.netmask);
    inet_pton(AF_INET, BRIDGE_GW, &ip_info.gw);
    esp_netif_set_ip_info(br_netif, &ip_info);

    // Configure DHCP server
    dhcps_lease_t lease;
    lease.enable = true;
    inet_pton(AF_INET, DHCP_START, &lease.start_ip);
    inet_pton(AF_INET, DHCP_END, &lease.end_ip);

    esp_netif_dhcps_option(br_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease));
    esp_err_t err = esp_netif_dhcps_start(br_netif);
    
    if(err == ESP_OK){
        ESP_LOGI(TAG, "DHCP Server Started");
        ESP_LOGI(TAG, "  IP Pool: %s - %s", DHCP_START, DHCP_END);
    }
    else{
        ESP_LOGE(TAG, "Failed to start DHCP Server: %d", err);
    }
}

static void wifi_init_softap(void)
{
    char ssid[32];
    char password[64];
    
    ESP_LOGI(TAG, "Initializing WiFi Access Point...");
    
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
    wifi_config_t wifi_config = {
        .ap = {
            .ssid_len = strlen(ssid),
            .channel = WIFI_CHANNEL,
            .max_connection = MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    
    strcpy((char*)wifi_config.ap.ssid, ssid);
    strcpy((char*)wifi_config.ap.password, password);
    
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_start());
    
    ESP_LOGI(TAG, "WiFi AP Started");
    ESP_LOGI(TAG, "  SSID: %s", ssid);
    ESP_LOGI(TAG, "  Password: %s", password);
    ESP_LOGI(TAG, "  IP: %s", BRIDGE_IP);
    
    vTaskDelay(pdMS_TO_TICKS(1000));
}

static void eth_init(void) {
    ESP_LOGI(TAG, "Initializing Ethernet...");
    
    // Power up PHY
    gpio_set_direction(ETH_PHY_POWER_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(ETH_PHY_POWER_PIN, 1);
    vTaskDelay(pdMS_TO_TICKS(500));
    
    // MAC and PHY configuration

    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
    phy_config.phy_addr = ETH_PHY_ADDR;
    phy_config.reset_gpio_num = -1;
    
    eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
    esp32_emac_config.smi_gpio.mdc_num = ETH_MDC_GPIO;
    esp32_emac_config.smi_gpio.mdio_num = ETH_MDIO_GPIO;
    
    ESP_LOGI(TAG, "Setting MAC config...");

    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
    esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config);
    
    esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
    esp_eth_handle_t eth_handle = NULL;
    
    ESP_LOGI(TAG, "Installing Ethernet driver...");

    ESP_ERROR_CHECK(esp_eth_driver_install(&eth_config, &eth_handle));

    // Get the bridge network interface
    //uint8_t macAddress[6];
    //esp_efuse_mac_get_default(macAddress);
    //esp_netif_t *bridge_netif = esp_bridge_create_eth_netif(&ip, &macAddress, true, false);
    //if (bridge_netif == NULL) {
        //ESP_LOGE(TAG, "Failed to get bridge netif handle!");
        //return;
    //}

    //ESP_LOGI(TAG, "Attaching Ethernet to the bridge...");
    //esp_netif_br_glue_handle_t netif_br_glue = esp_netif_br_glue_new();
    //ESP_ERROR_CHECK(esp_netif_br_glue_add_port(netif_br_glue, eth_handle));
    //ESP_ERROR_CHECK(esp_netif_attach(br_netif, esp_eth_new_netif_glue(eth_handle)));
    int err = ip_napt_enable_netif(eth_handle, 0); // Disable NAPT for ethernet interface
    if(err == 0){
        ESP_LOGE(TAG, "Failed to disable NAPT for ethernet interface: %d", err);
    }
    ESP_ERROR_CHECK(esp_eth_start(eth_handle));
    
    ESP_LOGI(TAG, "Ethernet initialization complete");
    ESP_LOGI(TAG, "  Configured IP: %s (bridged with WiFi AP)", BRIDGE_IP);
}

void app_main(void)
{
    ESP_LOGI(TAG, "\n====================================");
    ESP_LOGI(TAG, "WT32-ETH01 Ethernet-WiFi Bridge");
    ESP_LOGI(TAG, "Shared Network: 192.168.5.0/24");
    ESP_LOGI(TAG, "====================================\n");
    
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    
    // Initialize network interface
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    esp_bridge_create_all_netif();
    
    //IP4_ADDR(&ip.ip, BRIDGE_IP[0], BRIDGE_IP[1], BRIDGE_IP[2], BRIDGE_IP[3]);
    //IP4_ADDR(&ip.gw, BRIDGE_GW[0], BRIDGE_GW[1], BRIDGE_GW[2], BRIDGE_GW[3]);
    //IP4_ADDR(&ip.netmask, BRIDGE_NETMASK[0], BRIDGE_NETMASK[1], BRIDGE_NETMASK[2], BRIDGE_NETMASK[3]);

    // Initialize WiFi AP first
    wifi_init_softap();
    
    // Initialize Ethernet
    eth_init();

    // Setup DHCP server
    setup_dhcp_server();
    
    
    ESP_LOGI(TAG, "\n====================================");
    ESP_LOGI(TAG, "Bridge Ready!");
    ESP_LOGI(TAG, "====================================");
    ESP_LOGI(TAG, "Web Interface: http://192.168.5.1");
    ESP_LOGI(TAG, "Network: 192.168.5.0/24");
    ESP_LOGI(TAG, "Gateway: 192.168.5.1");
    ESP_LOGI(TAG, "DHCP Pool: 192.168.5.2-254");
    ESP_LOGI(TAG, "====================================\n");
}
And the output log:

Code: Select all

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:7176
load:0x40078000,len:15564
ho 0 tail 12 room 4
load:0x40080400,len:4
--- 0x40080400: _init at ??:?
load:0x40080404,len:3904
entry 0x40080640
I (30) boot: ESP-IDF v5.3-dirty 2nd stage bootloader
I (30) boot: compile time Oct  8 2025 15:09:12
I (31) boot: Multicore bootloader
I (35) boot: chip revision: v1.1
I (39) boot.esp32: SPI Speed      : 40MHz
I (43) boot.esp32: SPI Mode       : DIO
I (48) boot.esp32: SPI Flash Size : 2MB
I (52) boot: Enabling RNG early entropy source...
I (58) boot: Partition Table:
I (61) boot: ## Label            Usage          Type ST Offset   Length
I (69) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (76) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (84) boot:  2 factory          factory app      00 00 00010000 00100000
I (91) boot: End of partition table
I (95) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=2377ch (145276) map
I (154) esp_image: segment 1: paddr=000337a4 vaddr=3ffbdb60 size=0576ch ( 22380) load
I (162) esp_image: segment 2: paddr=00038f18 vaddr=40080000 size=07100h ( 28928) load
I (174) esp_image: segment 3: paddr=00040020 vaddr=400d0020 size=85474h (545908) map
I (361) esp_image: segment 4: paddr=000c549c vaddr=40087100 size=11ac4h ( 72388) load
I (402) boot: Loaded app from partition at offset 0x10000
I (402) boot: Disabling RNG early entropy source...
I (414) cpu_start: Multicore app
I (423) cpu_start: Pro cpu start user code
I (423) cpu_start: cpu freq: 160000000 Hz
I (423) app_init: Application information:
I (426) app_init: Project name:     wt32-bridge
I (431) app_init: App version:      487c30e
I (436) app_init: Compile time:     Oct  9 2025 09:29:36
I (442) app_init: ELF file SHA256:  972090a89...
I (447) app_init: ESP-IDF:          v5.3-dirty
I (452) efuse_init: Min chip rev:     v0.0
I (457) efuse_init: Max chip rev:     v3.99 
I (462) efuse_init: Chip rev:         v1.1
I (467) heap_init: Initializing. RAM available for dynamic allocation:
I (474) heap_init: At 3FFAE6E0 len 0000F480 (61 KiB): DRAM
I (480) heap_init: At 3FFC6F68 len 00019098 (100 KiB): DRAM
I (487) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (493) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (499) heap_init: At 40098BC4 len 0000743C (29 KiB): IRAM
I (507) spi_flash: detected chip: generic
I (510) spi_flash: flash io: dio
W (514) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (528) coexist: coex firmware version: dab85ae96
I (533) main_task: Started on CPU0
I (543) main_task: Calling app_main()
I (543) AOG-BRIDGE: 
====================================
I (543) AOG-BRIDGE: WT32-ETH01 Ethernet-WiFi Bridge
I (543) AOG-BRIDGE: Shared Network: 192.168.5.0/24
I (553) AOG-BRIDGE: ====================================

I (583) bridge_common: esp-iot-bridge version: 1.0.1
I (593) wifi:wifi driver task: 3ffb7c90, prio:23, stack:6656, core=0
I (593) wifi:wifi firmware version: 0caa81945
I (603) wifi:wifi certification version: v7.0
I (603) wifi:config NVS flash: enabled
I (603) wifi:config nano formating: disabled
I (603) wifi:Init data frame dynamic rx buffer num: 32
I (613) wifi:Init static rx mgmt buffer num: 5
I (613) wifi:Init management short buffer num: 32
I (613) wifi:Init dynamic tx buffer num: 32
I (623) wifi:Init static rx buffer size: 1600
I (623) wifi:Init static rx buffer num: 10
I (633) wifi:Init dynamic rx buffer num: 32
I (633) wifi_init: rx ba win: 6
I (633) wifi_init: accept mbox: 6
I (643) wifi_init: tcpip mbox: 32
I (643) wifi_init: udp mbox: 6
I (653) wifi_init: tcp mbox: 6
I (653) wifi_init: tcp tx win: 5760
I (653) wifi_init: tcp rx win: 5760
I (663) wifi_init: tcp mss: 1440
I (663) wifi_init: WiFi IRAM OP enabled
I (673) wifi_init: WiFi RX IRAM OP enabled
I (673) phy_init: phy_version 4830,54550f7,Jun 20 2024,14:22:08
I (753) wifi:mode : null
I (753) wifi:mode : softAP (a0:a3:b3:23:6c:01)
I (763) wifi:Total power save buffer number: 16
I (763) wifi:Init max length of beacon: 752/752
I (763) wifi:Init max length of beacon: 752/752
Add netif ap with 487c30e(commit id)
I (763) esp_netif_lwip: DHCP server started on interface WIFI_AP_DEF with IP: 192.168.4.1
I (773) bridge_common: netif list add success
I (783) bridge_common: [WIFI_AP_DEF ]
I (783) bridge_common: Set ip info:192.168.5.1
I (793) bridge_wifi: SoftAP IP network segment has changed, deauth all station
I (803) wifi:Disabled PMF config for SoftAP
I (803) wifi:Total power save buffer number: 16
I (803) bridge_common: DHCPS Restart, deauth all station
I (803) esp_netif_lwip: DHCP server started on interface WIFI_AP_DEF with IP: 192.168.5.1
I (823) bridge_common: [WIFI_AP_DEF ]Name Server1: 114.114.114.114
I (823) AOG-BRIDGE: Initializing WiFi Access Point...
I (833) AOG-BRIDGE: WiFi AP Started
I (833) AOG-BRIDGE:   SSID: 7&  �0�?
I (843) AOG-BRIDGE:   Password: 
I (843) AOG-BRIDGE:   IP: 192.168.5.1
I (1853) AOG-BRIDGE: Initializing Ethernet...
I (2353) AOG-BRIDGE: Setting MAC config...
I (2353) AOG-BRIDGE: Installing Ethernet driver...
E (2373) AOG-BRIDGE: Failed to disable NAPT for ethernet interface: 0
I (4773) AOG-BRIDGE: Ethernet initialization complete
I (4773) AOG-BRIDGE:   Configured IP: 192.168.5.1 (bridged with WiFi AP)
I (4773) AOG-BRIDGE: Configuring DHCP Server...
E (4773) esp_netif_lwip: esp_netif_new_api: Failed to configure netif with config=0x3ffb1dd0 (config or if_key is NULL or duplicate key)
E (4793) AOG-BRIDGE: Failed to get bridge netif!
I (4793) AOG-BRIDGE: 
====================================
I (4803) AOG-BRIDGE: Bridge Ready!
I (4803) AOG-BRIDGE: ====================================
I (4813) AOG-BRIDGE: Web Interface: http://192.168.5.1
I (4813) AOG-BRIDGE: Network: 192.168.5.0/24
I (4823) AOG-BRIDGE: Gateway: 192.168.5.1
I (4823) AOG-BRIDGE: DHCP Pool: 192.168.5.2-254
I (4833) AOG-BRIDGE: ====================================

I (4843) main_task: Returned from app_main()
I am quite definite the NAPT is enabled on ethernet, and if I disable it in code it fails, and if I disable using `menuconfig` it re-enables itself the next time I load the config. At one point the ethernet had a DHCP server, and the WiFi had not, but I don't know what code that was (several iterations ago).