espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.76k stars 7.3k forks source link

DHCP for bridge mode (IDFGH-3778) #5697

Closed romatetemadze closed 1 year ago

romatetemadze commented 4 years ago

Unable to make two clients connect via esp32 (ETH + WIFI AP) Based on eth2ap example

Development Kit: [ESP32-Ethernet-Kit V1.1] Module or chip used: [ESP32-WROVER-B] IDF version: v4.1-rc Build System: [CMake] Compiler version: xtensa-esp32-elf-gcc (crosstool-NG crosstool-ng-1.22.0-80-g6c4433a5) 5.2.0 Operating System: [Windows] Using an IDE?: [Eclipse] Power Supply: [USB]

Spent like two weeks trying to solve this, but apparently its not something I can do. My case is as follows:

DHCP interceptor in pkt_wifi2eth & pkt_eth2wifi

Guess its doable to pass the packet back if its a DHCP. Not sure if the MAC setting of the WIFI interface affects such approach.

I have tried to set WIFI AP as default with DHCP

Played a lot with custom subnets, tried NAT but well, its not right way, hence asking here. I believe that following changes can be made:

  1. Let WIFI start even without ETH being UP
  2. Let both interfaces wait for clients
  3. If ETH is UP and WIFI AP has at least one station, enable bridge queue and stack_input = pkt_eth2wifi for ETH (this may be tricky to change on fly)
  4. If either party is asking for an IP, provide them from single instance of DHCP (common lease base, IP range and etc).

This may be a nice solution for automotive industry, when car is ENET equipped, and most of modern software providers are working with Android/iOS for diagnostics and programming. In such case existence of single DHCP instance is vital on both interfaces

enet_wifi

Alvin1Zhang commented 4 years ago

Thanks for raising this feature request.

liuzfesp commented 4 years ago

HI @romatetemadze,

Not sure I have understood your requirements:

Could you give more description about why the NAT is not suitable for you?

romatetemadze commented 4 years ago

HI @romatetemadze,

Not sure I have understood your requirements:

  • The key point to your application is to configure the IP of WiFi and ETH client, right?
  • Your recommended solution is to support single DHCP server on ESP32 side and filter DHCP packets on ESP32
  • The NAT method is not suitable for you.

Could you give more description about why the NAT is not suitable for you?

Hello, thanks for answering. I need single broadcast domain between eth/wifi clients due to UDP broadcast requirements. That makes NAT not suitable for the process. And in terms of the requirement, well, I just need to merge etherner & wifi AP on layer 2, but as well provide DHCP service for both.

You can think of an Ethernet+wifi switch that has DHCP server built in.

liuzfesp commented 4 years ago

Well, so it's a very special requirement and I'm afraid it's NOT common enough to put it into IDF.

If ETH is UP and WIFI AP has at least one station, enable bridge queue and stack_input = pkt_eth2wifi for ETH (this may be tricky to change on fly)

If only ETH is UP, will the TCPIP stack be up also? If yes, then it may be problematic, consider following scenario:

romatetemadze commented 4 years ago

Well, so it's a very special requirement and I'm afraid it's NOT common enough to put it into IDF.

If ETH is UP and WIFI AP has at least one station, enable bridge queue and stack_input = pkt_eth2wifi for ETH (this may be tricky to change on fly)

If only ETH is UP, will the TCPIP stack be up also? If yes, then it may be problematic, consider following scenario:

  • The ETH client already setup a TCP connection with ESP32
  • A station is connected to the ESP32 AP Then the TCP connection will be affected since the TCPIP stack has been stopped.

Thanks for the answer. Well, my suggested scenario was just based on the eth2ap and the way it works. Ideally, transparency between ETH andAP is what I need (so, usual switching between Ethernet and WIFI on layer 2). There are no TCP connections with ESP32 itself (if I understood the watch-out correctly).

Will give you a live example. I have a small WIFI router at home, which can be put in BRIDGE mode, so WIFI is transparently translated to ethernet on MAC level, and vice versa. On the same time, it has some sort of a virtual interface (with it's OWN MAC address) which is running DHCP service (regardless of presence of clients at either side). This is what I want to achieve with ESP32.

So far, I can catch packet in the code below, analyze if it's DHCP, and respond back with DHCP offer/lease instead of forwarding to opposite media. Similar approach for pkt_wifi2eth side. However, it's already in DHCP driver in ESP, just need good understanding how to run such setup, which I am lacking.

bool dhcp_processor(flow_control_msg_t msg) {
    // check DHCP if discover or request
    if (msg.len>300 && msg.buffer[42]==0x01) {    // DHCP detected
    uint8_t mtype=msg.buffer[258];

        switch (mtype) {
        case 0x35:  // DHCP discover
            // RESPOND WITH OFFER AND PUT PRE-LEASE RECORD
            break;
        case 0x36:  // DHCP request
            // RESPOND WITH ACK AND PUT LEASE RECORD
            break;
        default:
            break;
      }
   }
}

// Forward packets from Ethernet to Wi-Fi
// Note that, Ethernet works faster than Wi-Fi on ESP32,
// so we need to add an extra queue to balance their speed difference.
static esp_err_t pkt_eth2wifi(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t len, void* priv)
{
    esp_err_t ret = ESP_OK;
    flow_control_msg_t msg = {
        .packet = buffer,
        .length = len
    };

    // check if DHCP
    if (dhcp_processor(&msg)) {
        return ESP_OK;
    }

    if (xQueueSend(flow_control_queue, &msg, pdMS_TO_TICKS(FLOW_CONTROL_QUEUE_TIMEOUT_MS)) != pdTRUE) {
        ESP_LOGE(TAG, "send flow control message failed or timeout");
        free(buffer);
        ret = ESP_FAIL;
    }
    return ret;
}
freakyxue commented 4 years ago

hi @romatetemadze I have a question:

At present, IP address is obtained by bridge, but IDF has not achieved this way.

romatetemadze commented 4 years ago

hi @romatetemadze I have a question:

  • Eth and WiFi will get the address through the DHCP request. Why do you need to get the address through the DHCP bridge.

At present, IP address is obtained by bridge, but IDF has not achieved this way.

Hello,

The point is that in my setup neither side is running any DHCP, means clients end up with no IP. I would live with that, but Android is unable to join WIFI AP if IP is not provided.

I actually wrote a workaround that catches DHCP option 53 and provides IP/Route to either side, so I guess its good enough.

However, I believe such scenario may exist in many different cases, when we want to consolidate ETH and WIFI clients by providing them with IP's from esp32.

static void sendDhcp(int dType, uint32_t dTransId) {
    int res = 0;
    uint32_t timeout = 0;
    do {
        vTaskDelay(pdMS_TO_TICKS(timeout));
        timeout += 2;
        switch (dType) {
            case 2: //offer

                // set Transaction ID for the offer
                dhcpOffer[61]   = lastDhpIp;
                dhcpOffer[46]   = dTransId >> 24;
                dhcpOffer[47]   = dTransId >> 16;
                dhcpOffer[48]   = dTransId >> 8;
                dhcpOffer[49]   = dTransId;

                // set response MAC
                for (int i=0;i<6;i++) {
                    dhcpOffer[70+i] = lastDhpReqMac[i];
                }

                // set lease time
                dhcpOffer[295] = 0xFF;
                dhcpOffer[296] = 0xFF;

                res = esp_wifi_internal_tx(ESP_IF_WIFI_AP, dhcpOffer, sizeof(dhcpOffer));
                lastDhpIp++;
                break;
            case 5: //ack

                // set ACK to the requested IP
                dhcpAck[61]     = lastReqIp;

                // set Transaction ID for the offer
                dhcpAck[46]     = dTransId >> 24;
                dhcpAck[47]     = dTransId >> 16;
                dhcpAck[48]     = dTransId >> 8;
                dhcpAck[49]     = dTransId;

                // set response MAC
                for (int i=0;i<6;i++) {
                    dhcpAck[70+i] = lastDhpReqMac[i];
                }

                // set lease time
                dhcpOffer[295] = 0xFF;
                dhcpOffer[296] = 0xFF;

                res = esp_wifi_internal_tx(ESP_IF_WIFI_AP, dhcpAck, sizeof(dhcpOffer));
                break;
            default:
                break;
        }

    } while (res && timeout < FLOW_CONTROL_WIFI_SEND_TIMEOUT_MS);
    if (res != ESP_OK) {
        ESP_LOGE(TAG, "WiFi send packet failed: %d", res);
    } else {
        ESP_LOGI(TAG, "DHCP Sent DHCP type %i", dType);
    }
}

And then, on wifi receive, I am checking if its DHCP and responding back if yes. Otherwise, bridging to ETH:

// Forward packets from Wi-Fi to Ethernet, and handle DHCP.
static esp_err_t pkt_wifi2eth(void *buffer, uint16_t len, void *eb)
{
    if (len>281
            && ((uint8_t *)buffer)[278]==0x63
            && ((uint8_t *)buffer)[279]==0x82
            && ((uint8_t *)buffer)[280]==0x53
            && ((uint8_t *)buffer)[281]==0x63
        )
    {
        uint8_t type= ((uint8_t *)buffer)[282];

        if (type==0x35) {
            ESP_LOG_BUFFER_HEXDUMP("wifi2ETH", buffer, len, ESP_LOG_INFO);
            uint8_t subType= ((uint8_t *)buffer)[284];
            uint32_t transId=0;
            ESP_LOGI(TAG, "DHCP Traffic Detected");
            switch (subType) {
                case 0x01:  // Discover
                    transId=((uint8_t *)buffer)[46] << 24 | ((uint8_t *)buffer)[47] << 16 | ((uint8_t *)buffer)[48] << 8 | ((uint8_t *)buffer)[49];
                    for (int i=0;i<6;i++) {
                        lastDhpReqMac[i] = ((uint8_t *)buffer)[70+i];
                    }
                    // Sending Offer
                    sendDhcp(2, transId);
                    ESP_LOGI(TAG, "DHCP Discover Detected from %02X%02X%02X%02X%02X%02X, TxID: %X. Offering IP: %i", lastDhpReqMac[0], lastDhpReqMac[1],lastDhpReqMac[2],lastDhpReqMac[3],lastDhpReqMac[4],lastDhpReqMac[5],transId, lastDhpIp);
                    break;
                case 0x02:  // Offer
                    ESP_LOGI(TAG, "DHCP Offer Detected");
                    break;
                case 0x03:  // Request
                    transId=((uint8_t *)buffer)[46] << 24 | ((uint8_t *)buffer)[47] << 16 | ((uint8_t *)buffer)[48] << 8 | ((uint8_t *)buffer)[49];
                    lastReqIp=((uint8_t *)buffer)[299];
                    ESP_LOGI(TAG, "DHCP Request Detected, TxID: %X. Confirmed IP: %i", transId, lastReqIp);
                    sendDhcp(5, transId);
                    break;
                case 0x05:  // Ack
                    ESP_LOGI(TAG, "DHCP Ack Detected");
                    break;
                default:
                    break;
            }
        }
    }

    if (s_ethernet_is_connected) {
        if (esp_eth_transmit(s_eth_handle, buffer, len) != ESP_OK) {
            ESP_LOGE(TAG, "Ethernet send packet failed");
        }
    }
    esp_wifi_internal_free_rx_buffer(eb);
    return ESP_OK;
}

Need to do same for ETH inbound.

freakyxue commented 4 years ago

hi @romatetemadze I think you can solve this problem by setting the static IP address. This method of DHCP bridge is not desirable

romatetemadze commented 4 years ago

hi @romatetemadze I think you can solve this problem by setting the static IP address. This method of DHCP bridge is not desirable

Thanks for the response. The CAR in my scenario does not provide any means to set IP manually.

As I correct solution, I believe this should be done:

  1. Enable Bridge mode as is
  2. Add new virtual esp32 net interface and join it the bridge, so all 3 interfaces are in same MAC space.
  3. Run IP and DHCP on that new interface, so either side will be able to get response from it, and gain IP's as well as get ARP discovery responses about DHCP server IP.

There are many routers that allow doing such setup, which is not for general internet access, but mostly for local interconnection between wired and wireless embed clients.

I don't see why it may not be a desirable mode of operations?

freakyxue commented 4 years ago

hi @romatetemadze I see what you mean. You want to add a virtual network card to IDF for bridge communication, right? If yes, this is a new feature, but I feel that this feature uses very few scenarios

romatetemadze commented 4 years ago

hi @romatetemadze I see what you mean. You want to add a virtual network card to IDF for bridge communication, right? If yes, this is a new feature, but I feel that this feature uses very few scenarios

I believe quite of P2P application would be seeking for such solution. Regardless, I'd anyways provide a virtual interface feature, as its will be commonly used in quite of networking setups with interconnecting two different subnets without NAT'ing the traffic, but routing it.

Also, having DHCP standalone would allow interface to be brought down/up without having to restart the DHCP service, which could find some use too.

Would you suggest me to open a new feature request while closing this one? Re-reading the subject of this issue, I believe it does say what some applications are looking for - DHCP for interconnecting wired and wireless clients, I'd say it's somewhat

TCPIP network for mix of wired and wireless clients with common services (HTTP Server, DHCP, DNS, etc)

Sounds like a valuable feature, there are no SOC's which can provide it, and many standalone industrial provisioning servers could be built on ESP32 (there's still STA interface for centralized control or uplink provision if needed). Big mesh'es are usually providing "local" DHCP helpers to avoid huge broadcast traffic over wireless uplinks.

freakyxue commented 4 years ago

hi @romatetemadze If you set both AP and eth to host mode, I think this problem can be solved. Because the DHCP server is bound to netif, they do not interfere with each other. Both AP's DHCP and eth's DHCP can assign IP addresses to the following nodes

romatetemadze commented 3 years ago

Hello folks,

So, any suggestion how to bring a virtual interface with DHCP server into the same bridge as eth/wifi? That definitely has quite of applications.

freakyxue commented 3 years ago

hi @romatetemadze Summarize the above discussion.

I think this is a big feature. We will discuss whether to add this function internally.

romatetemadze commented 3 years ago

Hello @freakyxue ,

Did any discussion happen on this matter so far? I am unable to fully solve this by intercepting DHCP traffic, as them comes ARP and some other services to make a "fake" interface. Furthermore, I would like to run a socket server on some of interfaces to server an application needs.

Another question for same problem:

That would solve my problem in fact. Thanks in advance,

xueyunfei998 commented 3 years ago

hi @romatetemadze Now I'm doing a double port DHCP, Esp32 is set to softAP and eth host mode,Eth host mode can assign IP address to Ethernet devices.

Do you think this plan is suitable for you?

Harvie commented 3 years ago

I think that being able to setup bridge between wifi and ethernet in such way that DHCP packets get passed is important in some cases. Especialy with stuff like 802.3cg Ethernet. These technologies will gain popularity soon. I am currently working on LWIP support for similar (but proprietary) interface and it would make lot of sense to transparently bridge LAN with 802.3cg (and similar) networks including DHCP. For example if you have critical building automation running in HVAC/boiler rooms and basement corridors, the wifi coverage is often at premium and traditional ethernet setup might be an overkill for such applications. Passive 802.3cg (or similar) network makes lot of sense and ESP32 is powerfull enough to provide cost effective bridge to wired/wireless bridge to this building automation network segment without having to setup routing to other appliances in such buildings.

xueyunfei998 commented 3 years ago

hi @Harvie

Now the dual network port function of DHCP has been developed. For example, as mentioned above, different network segments between Ethernet and WiFi can communicate normally.

This feature will be released in version 4.4.

Harvie commented 3 years ago

Now the dual network port function of DHCP has been developed.

Not sure why would i need that. I already have DHCP server in my network. So if there is properly implemented bridge on esp32 it will pass DHCP packets through it without need for any DHCP specific code running on ESP32. But it makes sense to run DHCP client on the bridge interface, so ESP32 can acquire dynamic adress from the network.

xueyunfei998 commented 3 years ago

hi @Harvie esp-idf/examples/ethernet/eth2ap/main/ethernet_example_main.c

This example realizes the transmission from eth to AP. You can see if it can meet your needs?

romatetemadze commented 2 years ago

hi @Harvie

Now the dual network port function of DHCP has been developed. For example, as mentioned above, different network segments between Ethernet and WiFi can communicate normally.

This feature will be released in version 4.4.

This is awesome. Gotta test it 👍

romatetemadze commented 2 years ago

hi @Harvie

Now the dual network port function of DHCP has been developed. For example, as mentioned above, different network segments between Ethernet and WiFi can communicate normally.

This feature will be released in version 4.4.

Could you please point me to a source where I can learn about it? Thanks.

romatetemadze commented 2 years ago

hi @romatetemadze Now I'm doing a double port DHCP, Esp32 is set to softAP and eth host mode,Eth host mode can assign IP address to Ethernet devices.

  • Mobile devices can communicate with Ethernet devices by connecting to SoftAP.
  • I set SoftAP to 4 network segments and eth host to 5 network segments. After testing, I found that only need to open the IP_FORWARD, TCP UDP communication can be established between mobile devices and Ethernet devices without opening NAT.

Do you think this plan is suitable for you?

It sounds like exactly what I need. Can yoi kindly provide an example?

gmdriscoll commented 2 years ago

If I am following this thread correctly, would this solution also open up the possibility of allowing an ESP32 to OTA over wifi using another ESP32 running the new "bridge" mode as the internet access point? I have a battery operated wireless mesh esp32 that I am trying to update remotely, but no wifi routers are allowed in the environment. There is another ESP32 processor running the cloud access through hardwired ethernet.

xueyunfei998 commented 2 years ago

hi @gmdriscoll

Idf has existing solutions to your needs.

location: esp-idf/examples/ethernet/eth2ap/main/ethernet_example_main.c

The specific scheme is as follows:

d57b7960-fb6d-435c-af02-a055ac8585fc

gmdriscoll commented 2 years ago

@xueyunfei998 - Thank you. Not sure how I missed that.

romatetemadze commented 2 years ago

esp-idf/examples/ethernet/eth2ap

Hello, thanks for the reply, but this is not what we are looking for.

Its just a bridge mode, which means nothing is getting to the application layer, just a bypass between ethernet wouter and wifi client.

We need a virtual interface that will allow DHCP at both interfaces at same time.

romatetemadze commented 2 years ago

If I am following this thread correctly, would this solution also open up the possibility of allowing an ESP32 to OTA over wifi using another ESP32 running the new "bridge" mode as the internet access point? I have a battery operated wireless mesh esp32 that I am trying to update remotely, but no wifi routers are allowed in the environment. There is another ESP32 processor running the cloud access through hardwired ethernet.

Its easy to host a webserver on the esp32 and do OTA as a client. You just need to do a POST (with binary) that will be used as an OTA endpoint. Here's my code of the OTA update:

// Embed WEB pages:
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
extern const uint8_t index_html_end[] asm("_binary_index_html_end");

// httpd init:
esp_err_t http_server_init(void)
{
    static httpd_handle_t http_server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    //config.max_open_sockets=5;
    //config.core_id=1;
    config.lru_purge_enable = true;
    config.server_port=5377;
    config.stack_size=8192;
    config.recv_wait_timeout  = 16;
    config.send_wait_timeout  = 16;

    if (httpd_start(&http_server, &config) == ESP_OK) {
        httpd_register_uri_handler(http_server, &index_get);   // landing page with the update form
        httpd_register_uri_handler(http_server, &update_post); // update call
    }
    ESP_LOGW("*HTTPD* UP\r\n");
    return http_server == NULL ? ESP_FAIL : ESP_OK;
}

// handlers
esp_err_t index_get_handler(httpd_req_t *req)
{

    httpd_resp_send(req, (const char *) index_html_start, index_html_end - index_html_start);
    return ESP_OK;
}

httpd_uri_t update_post = {
    .uri      = "/update",
    .method   = 3,
    .handler  = update_post_handler,
    .user_ctx = NULL
};

// procedures
esp_err_t index_get_handler(httpd_req_t *req)
{
    httpd_resp_send(req, (const char *) index_html_start, index_html_end - index_html_start);
    return ESP_OK;
}

esp_err_t update_post_handler(httpd_req_t *req)
{
    ESP_LOGW(TAG,"OTA Started..");
    char buf[2000];
    esp_ota_handle_t ota_handle;
    int remaining = req->content_len;
    ESP_LOGW(TAG,"*FLASHER* Total Len: %d %08X\r\n", remaining, remaining);
    const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
    int targetOffset = ota_partition->address;
    ESP_LOGW("*FLASHER* write Label: %s\r\n", ota_partition->label);
    ESP_LOGW("*FLASHER* write Location: %08X\r\n", targetOffset);
    ESP_LOGW("*FLASHER* write Size to flash: %08X\r\n", ota_partition->size);
    ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle));
    esp_err_t writeErr=ESP_OK;
    while (remaining > 0) {
        int recv_len = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));
        total_bytes+=recv_len;

        // Timeout Error: Just retry
        if (recv_len == HTTPD_SOCK_ERR_TIMEOUT) {
            ESP_LOGW("*FLASHER* socket timeout: %s\r\n", esp_err_to_name(writeErr));
            continue;

        // Serious Error: Abort OTA
        } else if (recv_len <= 0) {
            ESP_LOGW("*FLASHER* Critical error: %s", esp_err_to_name(recv_len));
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Protocol Error");
            return ESP_FAIL;
        }

        // Successful Upload: Flash firmware chunk
        writeErr=esp_ota_write(ota_handle, (const void *)buf, recv_len);

        if ( writeErr!= ESP_OK) {
            ESP_LOGW("*FLASHER* Failed to write chunk: %s\r\n", esp_err_to_name(writeErr));
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Wrong firmware file!");
            return ESP_FAIL;
        }
        remaining -= recv_len;
    }

    // Validate and switch to new OTA image and reboot
    ESP_LOGW(TAG,"OTA firmware check");

    int retryA = 0;
    while(true) {
        esp_err_t e1 = esp_ota_end(ota_handle);
        if (e1 != ESP_OK) {
            retryA++;
            ESP_LOGW("*FLASHER* Failed OTA> %s try:%i\r\n", esp_err_to_name(e1),retryA);
            if (retryA>100) {
                httpd_resp_send_err(req, HTTPD_505_VERSION_NOT_SUPPORTED, "Validation / Activation Error");
                return ESP_FAIL;
            }
            vTaskDelay(10);
        } else {
            break;
        }
    }

    int retryB = 0;
    while(true) {
        esp_err_t e2 = esp_ota_set_boot_partition(ota_partition);
        if (e2 != ESP_OK) {
            retryB++;
            ESP_LOGW("*FLASHER* Failed OTAEND>%s try:%i\r\n", esp_err_to_name(e2),retryB);
            if (retryB>100) {
                httpd_resp_send_err(req, HTTPD_505_VERSION_NOT_SUPPORTED, "Validation / Activation Error");
                return ESP_FAIL;
            }
            vTaskDelay(10);
        } else {
            break;
        }
    }

    httpd_resp_sendstr(req, "<text style='font-family: Arial; font-size: 16px; color: #DA8B20;'>Done, rebooting.</text>");
    vTaskDelay(500);
    esp_restart();      // restart when done
    return ESP_OK;
}

index.html web page:

<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title>MHD Pro</title>
        <script>
            function startUpload() {
                var otafile = document.getElementById("otafile").files;
                if (otafile.length == 0) {
                    alert("No file selected!");
                } else {
                    document.getElementById("otafile").disabled = true;
                    document.getElementById("upload").disabled = true;
                    var file = otafile[0];
                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState == 4) {
                            if (xhr.status == 200) {
                                document.open();
                                document.write(xhr.responseText);
                                document.close();
                            } else if (xhr.status == 0) {
                                alert("Server closed the connection abruptly!");
                                location.reload()
                            } else {
                                alert(xhr.status + " Error!\n" + xhr.responseText);
                                location.reload()
                            }
                        }
                    };
                    xhr.upload.onprogress = function (e) {
                        var progress = document.getElementById("progress");
                        progress.textContent = "Updating firmware.. " + (e.loaded / e.total * 100).toFixed(0) + "%";
                    };
                    xhr.open("POST", "/update", true);
                    xhr.send(file);
                }
            }
        </script>
    </head>
    <body>
        <div>
            <input type="file" id="otafile" name="otafile" />
        </div>
        <div>
            <button id="upload" type="button" onclick="startUpload()">Program</button>
        </div>
    </body>
</html>

And this is from the other ESP32 (new firmware is stored at OTA_0 partition of a host esp32). Obviously I've cut quite of internal/commercial logic related to the version control, encryption and etc.

static void testHttpPost(void *pvParameters)
{

    char local_response_buffer[HTTP_CHUNK] = {0};
    tcpWEBUpdateDone = false;
    tcpWEBUpdateResult = false;
    ESP_LOGW("--------->> Running WEB UPDATE..\r\n");
    uint32_t totalPart = 0;
    esp_http_client_config_t config = {
        .url = "http://192.168.90.90:5377/update",
        .method = HTTP_METHOD_POST,
        .timeout_ms = 500000,
        .keep_alive_enable = true,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_err_t err = ESP_FAIL;
    esp_err_t readTest = ESP_FAIL;
    esp_partition_subtype_t partToFind = ESP_PARTITION_SUBTYPE_APP_OTA_0;
    if (needPartition==1) {
        partToFind = ESP_PARTITION_SUBTYPE_APP_OTA_1;
    }
    const esp_partition_t *ota_partInfo = esp_partition_find_first(ESP_PARTITION_TYPE_APP, partToFind, NULL);
    uint32_t d_offset = 0x0000;
    const uint32_t d_size = HTTP_CHUNK;
    uint8_t dataChunk[HTTP_CHUNK+5] = {0x0};
    ESP_LOGW("*FLASHER* Label: %s\r\n", ota_partInfo->label);
    ESP_LOGW("*FLASHER* Location: %08X\r\n", ota_partInfo->address);
    ESP_LOGW("*FLASHER* Size to flash: %08X\r\n", ota_partInfo->size);

    totalPart = ota_partInfo->size;
    int chunksQty =  totalPart / HTTP_CHUNK;
    esp_http_client_set_header(client, "Content-Type", "application/octet-stream");
    esp_http_client_set_header(client, "dnt: 1", "1");
    esp_http_client_set_header(client, "Cache-Control", "no-cache");
    esp_http_client_set_header(client, "Pragma", "no-cache");
    esp_http_client_set_header(client, "Content-Length", ota_partInfo->size); // may need a proper cast to string of a **size**
    esp_http_client_set_header(client, "Connection", "keep-alive");
    esp_http_client_set_header(client, "Accept-Encoding", "gzip, deflate");
    esp_http_client_set_header(client, "Accept", "*/*");
    err = esp_http_client_open(client,totalPart);
    if (err != ESP_OK) {
        ESP_LOGW("*FLASHER* Failed to open HTTP connection: %s\r\n", esp_err_to_name(err));
        return;
    }
    uint32_t dataPointer = ota_partInfo->address+d_offset;
    for (int i=0;i<chunksQty;i++) {
        readTest =  spi_flash_read(dataPointer, dataChunk, d_size);
        if (readTest==ESP_OK) {
            dataPointer+=d_size;
            esp_http_client_write(client, (const char *)dataChunk, d_size);
        } else {
            ESP_LOGW("*FLASHER* Failed to read flash : %s\r\n", esp_err_to_name(readTest));
        }
        vTaskDelay(FLASHER_CHILL);
    }
    int content_length =  esp_http_client_fetch_headers(client);
    int status_code = esp_http_client_get_status_code(client);
    esp_http_client_read(client,local_response_buffer,content_length);
    logToRam(true,"WUPS [%d] %d>>", status_code, content_length);
    for (int i=0;i<content_length;i++) {
        logToRamNoTime(true, "%c",local_response_buffer[i]);
    }
    logToRamNoTime(true, "\r\n");
    esp_err_t clientEr = esp_http_client_close(client);
    clientEr = esp_http_client_cleanup(client);
    if (clientEr!=ESP_OK) {
        ESP_LOGW("WCER %s\r\n", esp_err_to_name(clientEr));
    }
    if (status_code==200) {
        ESP_LOGW("Update done.%s\r\n", esp_err_to_name(clientEr));
    } else {
        tcpWEBUpdateResult = false;
    }
    tcpWEBUpdateDone = true;
    vTaskDelete(NULL);
}
david-cermak commented 2 years ago

Hi @romatetemadze

Support for multiple DHCP sever has been added to IDF in

It is now possible to run two DHCP servers, one on the AP interface, another one on the Ethernet interface. It is also possible to use lwip's native IP forwarding or NAPT to enable further connectivity.

I've created a simple example demonstrating these two interfaces: https://github.com/david-cermak/esp-network-examples/tree/main/eth-dhcps

Please, note, that you would have to set routing rules and fill in the ARP cache directly for the clients to connect to the other interfaces' clients (if the plain IP_FORWARD is used). Also, please use the IDF versions containing

Could you please check the proposed solution and close this issue if it meets your requirements?

romatetemadze commented 2 years ago

Hi @romatetemadze

Support for multiple DHCP sever has been added to IDF in

It is now possible to run two DHCP servers, one on the AP interface, another one on the Ethernet interface. It is also possible to use lwip's native IP forwarding or NAPT to enable further connectivity.

I've created a simple example demonstrating these two interfaces: https://github.com/david-cermak/esp-network-examples/tree/main/eth-dhcps

Please, note, that you would have to set routing rules and fill in the ARP cache directly for the clients to connect to the other interfaces' clients (if the plain IP_FORWARD is used). Also, please use the IDF versions containing

  • 92f7ecd As it introduces ESP_IP_FORWARD allowing packet forwards of a reference type (Otherwise you'd have to enable/implement L2-L3 copy for both WiFi and Ethernet netifs)

Could you please check the proposed solution and close this issue if it meets your requirements?

Hello @david-cermak ,

I will start testing this ASAP. I realize that routing and ARP need to be filled manually I want comms cross interface clients.

I will as well try to implement a simple PAT, so I can simply map TCP ports between clients of different interfaces, as my actual goal is to allow ANY client on AP side to access a VERY SPECIFIC client/ports on the other side.

I will report back, thank you!

Harvie commented 2 years ago

I beleive that running DHCP on bridge does not mean to run two DHCPs on each interface. It means to run single DHCP on bridge which properly forwards DHCP packets between DHCP and both interfaces on L2.

david-cermak commented 2 years ago

I beleive that running DHCP on bridge does not mean to run two DHCPs on each interface.

Yes, this is an IP forwarder, not a bridge. with these two DHCP servers, it's just another way to achieve what @romatetemadze's wanted (and what IDF supports with standard lwip features)

As for the bridge mode, it's possible, too, as someone mentioned above already, starting with eth2ap-example and adding one network interface and some rules for passing packets from the interfaces to the stack and back.

Demonstrated this in a simple project: https://github.com/david-cermak/eth-ap-nat/tree/main/eth-ap-forknetif (seems to work the same way as the IP forwarder, but needs more work and tuning) This would I think work, too (and should be perhaps the correct resolution of an issue called DHCP Bridge), but the current resolution is not well supported in IDF, the app code just seems to be "too hacky"...

I think, a little nicer implementation can be achieved using

romatetemadze commented 2 years ago

Hi @romatetemadze

Support for multiple DHCP sever has been added to IDF in

It is now possible to run two DHCP servers, one on the AP interface, another one on the Ethernet interface. It is also possible to use lwip's native IP forwarding or NAPT to enable further connectivity.

I've created a simple example demonstrating these two interfaces: https://github.com/david-cermak/esp-network-examples/tree/main/eth-dhcps

Please, note, that you would have to set routing rules and fill in the ARP cache directly for the clients to connect to the other interfaces' clients (if the plain IP_FORWARD is used). Also, please use the IDF versions containing

  • 92f7ecd As it introduces ESP_IP_FORWARD allowing packet forwards of a reference type (Otherwise you'd have to enable/implement L2-L3 copy for both WiFi and Ethernet netifs)

Could you please check the proposed solution and close this issue if it meets your requirements?

Hello @david-cermak,

I have tested this, and indeed I am able to communicate with the peer behind ethernet as follows:

Couple of questions:

  1. How comes the ETHERNET client does not need a route back to my side? Does ESP create a temporary translation regardless which side was it initiated from? Or is it an effect of a default gateway? Then why wouldn't windows consider my default gateway in order to reach the 192.168.8.0 subnet?
  2. Is there a way to add options for DHCP to provide routes?
  3. Wouldn't it be possible to have a BRIDGE mode (when we just copy packets between interfaces), while intercepting DHCP packets only and forwarding those back and forth into DHCP handler?

UPDATE - in case of Android connected I have a direct PING to 192.168.8.2 without the need of any routes (default GW is sufficient).

Thanks in advance

david-cermak commented 2 years ago

Hello @romatetemadze

Thanks for testing this and happy to hear that at least the basic communication works!

Let me try to answer your questions:

How comes the ETHERNET client does not need a route back to my side?

I think this is very much platform dependent, but in most cases (including lwip) if the stack cannot find a route it still tries to send it to the default netif. This is probably what you saw on linux (and Android) with the default gateway.

Does ESP create a temporary translation regardless which side was it initiated from?

No translation is happening in lwip if we enable IP_FORWARD only.

Is there a way to add options for DHCP to provide routes?

You mean option 33, to advertize static routes to clients? No, this option is not supported, unfortunately. The dhcp client implementation in lwip supports appending custom options:

https://github.com/espressif/esp-lwip/blob/fb1f3552349bfa53d2977409f204d385ed0aa217/src/core/ipv4/dhcp.c#L88-L90

I was thinking about adding a similar macro to the dhcp server (for another use-case), seems like would help here, as well. User application would have to compose these additional options manually.

Wouldn't it be possible to have a BRIDGE mode

As mentioned in the previous comment you can try to run the example I linked and see how it works. It should behave exactly the same way as the IP forwarder, but the implementation is not very clean and straight forward. This could be however largely adjusted based on your application needs, if you just need the ESP network for assigning DHCP addresses and everything else could be a simple ap2eth bridge, then you can just clone a packet only if it's a DHCP message and pass it to lwip -- everything else could be simply copied between interfaces. (this would perhaps be a little faster, but the IP forwarder is a cleaner solution)

romatetemadze commented 2 years ago

Hello @romatetemadze

Thanks for testing this and happy to hear that at least the basic communication works!

Let me try to answer your questions:

How comes the ETHERNET client does not need a route back to my side?

I think this is very much platform dependent, but in most cases (including lwip) if the stack cannot find a route it still tries to send it to the default netif. This is probably what you saw on linux (and Android) with the default gateway.

Does ESP create a temporary translation regardless which side was it initiated from?

No translation is happening in lwip if we enable IP_FORWARD only.

Is there a way to add options for DHCP to provide routes?

You mean option 33, to advertize static routes to clients? No, this option is not supported, unfortunately. The dhcp client implementation in lwip supports appending custom options:

https://github.com/espressif/esp-lwip/blob/fb1f3552349bfa53d2977409f204d385ed0aa217/src/core/ipv4/dhcp.c#L88-L90

I was thinking about adding a similar macro to the dhcp server (for another use-case), seems like would help here, as well. User application would have to compose these additional options manually.

Wouldn't it be possible to have a BRIDGE mode

As mentioned in the previous comment you can try to run the example I linked and see how it works. It should behave exactly the same way as the IP forwarder, but the implementation is not very clean and straight forward. This could be however largely adjusted based on your application needs, if you just need the ESP network for assigning DHCP addresses and everything else could be a simple ap2eth bridge, then you can just clone a packet only if it's a DHCP message and pass it to lwip -- everything else could be simply copied between interfaces. (this would perhaps be a little faster, but the IP forwarder is a cleaner solution)

Hello @david-cermak, thanks for your replies.

I will only comment on the last part. I like the option to run a ap2eth bridge, however, I am not really sure how to if it's a DHCP message pass it to lwip. Lets say I can check if its DHCP with magic bytes and etc, but not sure how to "pass it to LWIP". Given I already have registered as handler for WIFI or ETHERNET inbound with either of the following callbacks: esp_wifi_internal_reg_rxcb(WIFI_IF_AP, pkt_wifi2eth); and esp_eth_config_t { config.stack_input = pkt_eth2wifi; }

How do I push it "back to LWIP" in case its a DHCP packet? And second question - in case of ap2eth, a single DHCP instance should be sufficient, or do I really need to run two different segments, each with its own DHCP?

PS. I have tried to configure both DHCP (in case of your example) to run same C network, but expectedly it didnt work (IP_FOWARD would get confused I guess). However, both clients got an IP in a same segment (I had WIFI starting with 192.168.4.150 and ethernet with 192.168.4.1)

Thanks and much appreciated!

david-cermak commented 2 years ago

Hello @romatetemadze

Have you checked the example that I posted? https://github.com/david-cermak/eth-ap-nat/tree/main/eth-ap-forknetif I've just started with ap2eth example and added a call to

void copy_packet_to_network(void *buffer, uint16_t len);

to both forwarding functions:

About the questions: 1) How do I push it "back to LWIP" in case its a DHCP packet?

If you look at this copy_packet_to_network() it just copies everything (that has our MAC or is a broadcast) -- you can adapt it to accept only DHCP (UDP on the well-known port)

2) a single DHCP instance should be sufficient, Yes, my example uses just one DHCP server, too (I've called this example a "fork-netif", since it uses only one network interface, that is shared between two physical interfaces, Ethernet and WiFi)

romatetemadze commented 2 years ago

Hello @romatetemadze

Have you checked the example that I posted? https://github.com/david-cermak/eth-ap-nat/tree/main/eth-ap-forknetif I've just started with ap2eth example and added a call to

void copy_packet_to_network(void *buffer, uint16_t len);

to both forwarding functions:

About the questions:

  1. How do I push it "back to LWIP" in case its a DHCP packet?

If you look at this copy_packet_to_network() it just copies everything (that has our MAC or is a broadcast) -- you can adapt it to accept only DHCP (UDP on the well-known port)

  1. a single DHCP instance should be sufficient, Yes, my example uses just one DHCP server, too (I've called this example a "fork-netif", since it uses only one network interface, that is shared between two physical interfaces, Ethernet and WiFi)

Hello @david-cermak,

This does look on the spot! CaptureDD

pinger

I can ping from WIFI to ETHERNET, and initiate socks without any issues, yet DHCP works for both in a same segment, Awesome workaround! Will add few filters to copy only DHCP related traffic and thats it :---) 👍 👍 👍 👍 👍 👍 👍 👍 💯

Any suggestion which buffers are relevant to avoid this? Happens on semi-heavy traffic: errroe

david-cermak commented 2 years ago

@romatetemadze You can try to update the number of Ethernet DMA Tx buffers and their sizes in:

romatetemadze commented 2 years ago

@romatetemadze You can try to update the number of Ethernet DMA Tx buffers and their sizes in:

Thanks, Ive based my project on IPERF example and it works no issues.

One question - I am trying to understand which interface the DHCP client is coming from (during an event IP_EVENT_AP_STAIPASSIGNED). Apparently there's nothing as we have a single virtual netif - any suggestion how to leave the trace to it?

david-cermak commented 2 years ago

The event IP_EVENT_AP_STAIPASSIGNED comes with a reference to the interface:

https://github.com/espressif/esp-idf/blob/31b7694551620522cc3fde0623321a38bfea762f/components/esp_netif/include/esp_netif_types.h#L142-L146

But, as you're saying, this is the one, that's used for both L2 interfaces, so won't help much.

You can always keep track of all incoming (DHCP) packets and their source MAC addresses in an internal lookup table and match it with the MAC addresses from the above event.

romatetemadze commented 2 years ago

IP_EVENT_AP_STAIPASSIGNED

@david-cermak What IDF are you with? I am using 4.3.1 and here's my IP_EVENT_AP_STAIPASSIGNED struc:

/** Event structure for IP_EVENT_AP_STAIPASSIGNED event */
typedef struct {
    esp_ip4_addr_t ip; /*!< IP address which was assigned to the station */
} ip_event_ap_staipassigned_t;

Regardless, I will make own ARP like table to keep the track (based on DHCP request source calls). Thanks again, seems I am about to solve everything as wanted originally!

david-cermak commented 2 years ago

Sorry, the new fields are only available on the latest master, v5.0-dev (the MAC address was added a while ago, but still didn't make it to v4.4).

If you'd like to stay with v4.3.1, it's still possible to call

bool dhcp_search_ip_on_mac(dhcps_t *dhcps, u8_t *mac, ip4_addr_t *ip);

whenever this event comes and search for the items stored in the list of potential clients...

romatetemadze commented 2 years ago

Sorry, the new fields are only available on the latest master, v5.0-dev (the MAC address was added a while ago, but still didn't make it to v4.4).

If you'd like to stay with v4.3.1, it's still possible to call

bool dhcp_search_ip_on_mac(dhcps_t *dhcps, u8_t *mac, ip4_addr_t *ip);

whenever this event comes and search for the items stored in the list of potential clients...

Got it, thanks.

When do you think this would reach a stable release?

mickeyl commented 1 year ago

Thanks for all who participated in this (huge) issue. I've come here since I have the same issue as the original poster (building a bridge/forwarder between a car w/ ENET and a mobile phone) and it's great that there is a solution now.

Since this is based on "manual" forwarding of packets, I reckon the CONFIG_LWIP_IP_FORWARD=y is not necessary in this case?

@david-cermak I'm afraid I didn't understand yet, why CONFIG_LWIP_IP_FORWARD on its own isn't also a viable solution for one DHCP server serving both interfaces and forwarding hence and forth? Could you perhaps expand on that?

Why do we need the promiscuous mode enabled on the wired interface?

And the last question... I guess it doesn't matter which interface the DHCP server is attached to? In my case the wired ethernet is always connected first (since the OBD2 port provides power to the adapter), so it would be better to let it run there.

david-cermak commented 1 year ago

This (very long) thread talks about two options to address this: 1) IP forwarder: All packets are passed via lwIP and forwarded between interfaces 2) Bridge/L2 mode: Packets are passed (or duplicated) manually on L2 layer, before going to lwip

Since this is based on "manual" forwarding of packets, I reckon the CONFIG_LWIP_IP_FORWARD=y is not necessary in this case?

It's not needed for option 2), but it is necessary for or option 1)

why CONFIG_LWIP_IP_FORWARD on its own isn't also a viable solution for one DHCP server serving both interfaces and forwarding hence and forth? Could you perhaps expand on that?

It is possible to run a single DHCP server and use IP_FORWARD=1, but both interfaces would share the same mask/ip-ranges. This should work as well, I think.

Why do we need the promiscuous mode enabled on the wired interface?

This is only needed for option 2) as we're doing the manual forwarding and need to see all the frames (not only for our MAC) to be able to decide where this frame goes.

which interface the DHCP server is attached to? In my case the wired ethernet is always connected first (since the OBD2 port provides power to the adapter), so it would be better to let it run there.

It doesn't matter as long as IP_FORWARD=1. Otherwise, only the interface with the DHCP server would be assigned addresses.

david-cermak commented 1 year ago

When do you think this would reach a stable release?

And If I'm not mistaken, all the IDF changes have reached the stable release of v5.0.2, sorry for the delay. Closing this as resolved.

(please feel free to reopen or comment further on the topic)

Harvie commented 1 year ago

Bridge/L2 mode: Packets are passed (or duplicated) manually on L2 layer, before going to lwip

Does this mean that lwip cannot be configured to create actual L2 bridge between two interfaces and their bridge is actualy L3 only? This is bit dissapointing but explains a lot :-) Especialy the part where it's not espressif's fault for DHCP not working as expected on bridges...

david-cermak commented 1 year ago

Actually, lwIP does support the bridge mode on L2, and we have an example here: https://github.com/espressif/esp-idf/tree/master/examples/network/bridge

this wasn't available, when I was suggesting this manual bridging on eth frame level and/or IP forwarding on L3.

Harvie commented 1 year ago

For me the use case would be following:

Another more common use case might be using esp32 as wifi AP connected to wired ethernet, acquring IP from DHCP while allowing wifi clients to use DHCP server available on wired ethernet...

mickeyl commented 1 year ago

Actually, lwIP does support the bridge mode on L2, and we have an example here: https://github.com/espressif/esp-idf/tree/master/examples/network/bridge

this wasn't available, when I was suggesting this manual bridging on eth frame level and/or IP forwarding on L3.

Oh, that's quite amazing. The said example is for two wired networks, but I guess/hope it can easily be adjusted to feature the builtin WiFi + one wired interface.