ofekp/TinyUPnP

ESP32 arduino with Ethernet.

zekageri opened this issue · 19 comments

I'am using the Ethernet library with my esp32. In the TinyUPnP.cpp file i replaced the calls for WiFi.localIP() and WiFi.gatewayIP() with ETH.localIP() and ETH.gatewayIP(). After that i commented out the wifi connectivity test inside the cpp file like this:

boolean TinyUPnP::testConnectivity(unsigned long startTime) {
    debugPrint(F("Testing WiFi connection for ["));
    debugPrint(ETH.localIP().toString());
    debugPrint("]");
    /*
    while (WiFi.status() != WL_CONNECTED) {
        if (_timeoutMs > 0 && startTime > 0 && (millis() - startTime > _timeoutMs)) {
            debugPrint(F(" ==> Timeout expired while verifying WiFi connection"));
            _wifiClient.stop();
            return false;
        }
        delay(200);
        debugPrint(".");
    }*/
    debugPrintln(" ==> GOOD");  // \n

    debugPrint(F("Testing internet connection"));
    _wifiClient.connect(connectivityTestIp, 80);
    while (!_wifiClient.connected()) {
        if (startTime + TCP_CONNECTION_TIMEOUT_MS > millis()) {
            debugPrintln(F(" ==> BAD"));
            _wifiClient.stop();
            return false;
        }
    }

    debugPrintln(F(" ==> GOOD"));
    _wifiClient.stop();
    return true;
}

I'am calling the setup function of the UPNP after i got an IP from ETH. Like this:

boolean Can_Init_UPNP = false;
void WiFiEvent(WiFiEvent_t event){
    switch(event) {
        case SYSTEM_EVENT_ETH_START:
            //set eth hostname here
            ETH_setHostname(hostName);
            break;
        case SYSTEM_EVENT_ETH_CONNECTED:
            //ethernet connected (if manual IP)
            //WiFi.enableIpV6();
            //tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_ETH);
            break;
        case SYSTEM_EVENT_ETH_GOT_IP:
            ETH_Got_IP = true;
            Display_UI();
            vTaskDelete(ETH_Timeout_Task_Handle);
            Can_Init_UPNP = true;
            break;
        case SYSTEM_EVENT_ETH_DISCONNECTED:
            ETH_Got_IP = false;
            NoIp_Kiir();
            ETH_Ip_Timeout_Start();
            vTaskDelete(Weather_API_Task_Handle);
            vTaskDelete(HoliDay_API_Task_Handle);
            break;
        default:
            break;
    }
}

static const inline void Init_UPNP(int LISTEN_PORT, int LEASE_DURATION, String FRIENDLY_NAME){
    portMappingResult portMappingAdded;
    tinyUPnP->addPortMappingConfig(ETH_localIP(), LISTEN_PORT, RULE_PROTOCOL_TCP, LEASE_DURATION, FRIENDLY_NAME);
    portMappingAdded = tinyUPnP->commitPortMappings();
/*
    tinyUPnP->addPortMappingConfig(ETH_localIP(), LISTEN_PORT, RULE_PROTOCOL_TCP, LEASE_DURATION, FRIENDLY_NAME);
    while (portMappingAdded != SUCCESS && portMappingAdded != ALREADY_MAPPED) {
        portMappingAdded = tinyUPnP->commitPortMappings();
        if (portMappingAdded != SUCCESS && portMappingAdded != ALREADY_MAPPED) {
            tinyUPnP->printAllPortMappings();
            //Error_iras("This was printed because adding the required port mapping failed");
        }
    }
    portMappingAdded = tinyUPnP->commitPortMappings();*/
    //EasyDDNS.service("HITEC_MyHome");
    //EasyDDNS.client("HITEC", "admin", "admin");
}

static const inline void UPNP_Loop(){
    //EasyDDNS.update(300000);
    tinyUPnP->updatePortMappings(600000);
}

void loop() {
  ArduinoOTA.handle();
  if(Can_Init_UPNP){
    Can_Init_UPNP = false;
    Init_UPNP(80, 20000, "HITEC_MyHome");
  }
  UPNP_Loop();
}

And here is the DUMP that i got in a TXT file:
UPNP Dump.txt

LATEST esp crash exception separately:

Flushing the rest of the response
port [1900] actionPort [1900]
isGatewayInfoValid [192.168.10.1] port [1900] path [/igd.xml] actionPort [1900] actionPath [/ipc] serviceTypeName [urn:schemas-upnp-org:service:WANIP
Gateway info is valid
Verify port mapping for rule [HITEC_MyHome]
16e0ae:0x3ffb1e10 0x400efc9d:0x3ffb1e40 0x400efd35:0x3ffb1e60 0x400edd72:0x3ffb1e80 0x400ef3ee:0x3ffb1f10 0x400d9b49:0x3ffb1f50 0x400f2669:0x3ffb1fb0 0x40089525:0x3ffb1fd0
  #0  0x40154728:0x3ffb1dc0 in WiFiClientRxBuffer::read(unsigned char*, unsigned int) at C:\Users\Dr.Random\.platformio\packages\framework-arduinoespressif32\libraries\WiFi\src/WiFiClient.cpp:564
  #1  0x40154ae2:0x3ffb1de0 in WiFiClient::read(unsigned char*, unsigned int) at C:\Users\Dr.Random\.platformio\packages\framework-arduinoespressif32\libraries\WiFi\src/WiFiClient.cpp:564
  #2  0x4016e0ae:0x3ffb1e10 in WiFiClient::read() at C:\Users\Dr.Random\.platformio\packages\framework-arduinoespressif32\libraries\WiFi\src/WiFiClient.cpp:564
  #3  0x400efc9d:0x3ffb1e40 in Stream::timedRead() at C:\Users\Dr.Random\.platformio\packages\framework-arduinoespressif32\cores\esp32/Stream.cpp:76 
  #4  0x400efd35:0x3ffb1e60 in Stream::readStringUntil(char) at C:\Users\Dr.Random\.platformio\packages\framework-arduinoespressif32\cores\esp32/Stream.cpp:76
  #5  0x400edd72:0x3ffb1e80 in TinyUPnP::verifyPortMapping(_gatewayInfo*, _upnpRule*) at lib\TinyUPnP-3.1.4\src/TinyUPnP.cpp:1010
  #6  0x400ef3ee:0x3ffb1f10 in TinyUPnP::commitPortMappings() at lib\TinyUPnP-3.1.4\src/TinyUPnP.cpp:135
  #7  0x400d9b49:0x3ffb1f50 in loop() at src/Own_Headers/ModBus.h:279 (discriminator 1)
      (inlined by) loop() at src/main.cpp:26 (discriminator 1)
  #8  0x400f2669:0x3ffb1fb0 in loopTask(void*) at C:\Users\Dr.Random\.platformio\packages\framework-arduinoespressif32\cores\esp32/main.cpp:19       
  #9  0x40089525:0x3ffb1fd0 in vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c:355 (discriminator 1)

Rebooting...

Okay i did the following. I created a separate task for the UPNP init on the core 0. Like this:

TinyUPnP tinyUPnP(20000);

    static const inline void Init_UPNP_Task(int Stack_Depth,int Core,int Priority){
        xTaskCreatePinnedToCore(UPNP_Task,"UPNP_Task",Stack_Depth,NULL,Priority,&UPNP_Task_Handle,Core);
    }

// ETHERNET CALLBACK 
    void WiFiEvent(WiFiEvent_t event){
        switch(event) {
            case SYSTEM_EVENT_ETH_START:
                //set eth hostname here
                ETH_setHostname(hostName);
                break;
            case SYSTEM_EVENT_ETH_CONNECTED:
                //ethernet connected (if manual IP)
                //WiFi.enableIpV6();
                //tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_ETH);
                break;
            case SYSTEM_EVENT_ETH_GOT_IP:
                ETH_Got_IP = true;
                Init_UPNP_Task(5024,0,0);   // HERE IAM CALLING THE TASK CREATE BECAUSE WE GOT AN IP! ( tried on core 1 but millis() overflow )
                break;
            case SYSTEM_EVENT_ETH_DISCONNECTED:
                ETH_Got_IP = false;
                break;
            default:
                break;
        }
    }


static const inline void Init_UPNP(int LISTEN_PORT, int LEASE_DURATION, String FRIENDLY_NAME){
        boolean portMappingAdded = false;
        tinyUPnP.addPortMappingConfig(ETH_localIP(), LISTEN_PORT, RULE_PROTOCOL_TCP, LEASE_DURATION, FRIENDLY_NAME);
        while (!portMappingAdded) {
            portMappingAdded = tinyUPnP.commitPortMappings();
            Serial.println("");
        
            if (!portMappingAdded) {
                tinyUPnP.printAllPortMappings();
                Serial.println(F("This was printed because adding the required port mapping failed"));
                vTaskDelay(3000);
            }
        }
        
        Serial.println("UPnP done");
    }

    void UPNP_Task( void * parameter ){
        Init_UPNP(80, 20000, "MyHome");
        vTaskDelete(NULL);
        for ever{
            //tinyUPnP.updatePortMappings(600000);
            vTaskDelay(1);
        }
        vTaskDelay(1);
    }

When the ESP gets an IP address from gateway its starting a task for init the UPNP. Task starting everything is fine, nothing crashes on core 0. But in the end i get this message:

TCP connection timeout while retrieving port mappings

Timeout expired while trying to add a port mapping

UPnP done

**Here is the dump from the debug log:
UPNP_Dump_2.txt
**

My router is behind a NAT device.
So maybe that can cause some problem for the UPnP. Is it possible that this wont work if my router is using NAT?

I will test on a router with not NAT but public IP today.

EDIT: I tested on a network with PUBLIC IP but the result is the same. It seems to me that it doesn't matter if the network is NAT or simple PUBLIC.

ofekp commented

NAT is not the issue.
You still have [E][WiFiClient.cpp:392] write(): fail on fd 60, errno: 104, "Connection reset by peer" errors, before the TCP connection timeout. You must address these first.
This error is from the depth of ESP32 package and I think it will be hard to make it work the way you're trying to do it. What is that hack with the WiFiEvent? Are you working with a reference somewhere, can you give me a link?
I think if you want this to work there are no shortcuts, you need to replace the WiFi packages with ETH ones.

ETH inherits almost everything from the WiFi lib. The only thing I had to modify is the localip and gateway calls. Everything else comes from WiFi. The thing is that the esp trying a bunch of times, and every time that issue comes out but eventually I can see the esp in my upnp list on the gateway.after it is there ( like 5 or 10 mins after simultaneous tries ) I can load the esps website trought the IP of my gateway but I can't reach it from the internet because it is not a public IP because of the NAT gateway that gives me IPs. My router have two IPs from the NAT device. One is the IP that I can see if I search on Google for the sentence "what is my IP" this is the public IP of the NAT router and one that my gateway gets. More absurd thing is that after the esp is on the UPNP list it is trying to add itself and can't see that it is already there.
Here is the ETH.h build in the esp32 Arduino.

ETH

So to narrow down this ethernet a little bit.

I'm using the Ethernet like this: Ethernet Sketch
For this to work you need a "special" ethernet board.

Ethernet official example

This is how i solved my ethernet problem

ofekp commented

The public IP and the port should get you to the device assuming the rule is found among the forwarding rules in your gateway router. Some routers will not allow loopback, so try to reach the public IP and port from outside your network, for example from your mobile phone browser while being connected to LTE and not to the local WiFi.
That error seems to be that the socket lost track of where it is..

I tried that. I can not reach the web from the public IP from another network. But I can reach it inside the network. :/

ofekp commented

can you show me the rule as it is in the router please?

I can't right now. Unfortunately I can reach that router the following Monday. :(
But iam sure that it is in a right way.

ofekp commented

what port did you use? not 80 right?

I tried with 80 (currently it is) and 8800 and 9000 and idk a bunch more. :/ But in the end port 80 was added

ofekp commented

port 80 is problematic sometimes, would you try 16232 for example?

I will try it thank you for your help. You are a great man. I hope it will work eventually.

I changed to the suggested port ( 16232 ) and the messages are the same.
Here is the UPnP Dump.txt

Now i will wait if it appears in my routeer UPnP list.

Currently it looks like this:

UPnP List

I waited like 2 hours with port 16232 but nothing. After that i changed it to port 80 and waited like 20 min and again it is there but with the same UPnP dump to the serial. And can't reach from publicIP.

port80

ofekp commented

Somewhere in here L986-L1003 you get the following message:
[E][WiFiClient.cpp:392] write(): fail on fd 61, errno: 104, "Connection reset by peer"
This must be solved since this is probably why you get the timeouts.

Can you use TCP dump/Wireshark and see what the ESP is actually sending?
I don't think it sends things correctly, probably only part of the intended packet. I need to see it.

When looking at your the partial code you provided here, you have some deviations from the example, for one, you miss the ethernet stop event. I don't have experience with the ETH library but this seems important.

image

You can see in the example they never issue a request when the ethernet is not connected:
https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/ETH_LAN8720/ETH_LAN8720.ino#L75-L81

Can you check this too?

I will definietly check it next monday. I really appreciate your time you took to help.

I don't see the reason for the timeout in the ETH stop event because i initiate the request after i got connected and i got an IP.
So the connection is already made and every other thing with ETH is working.

For example i'm currently using other http requests such as weather API call and a bunh more. And these are working properly.

Here is an example that i use to get the current weather data with ETHERNET with http request.

String httpGETRequest(const char* serverName) {
  HTTPClient http;
  http.begin(serverName);
  int httpResponseCode = http.GET();
  String payload = "{}"; 
  if (httpResponseCode>0) {payload = http.getString();}
  else {
    String Error = "Get API Error with responseCode: " + String(httpResponseCode);
    Error_iras(Error);Send_Async(Error,";APIError");
    LastWeather_Data = "error";
  }
  http.end();
  return payload;
}
ofekp commented

Those package are very different, my package is requiring the use of UDP too and I am not sure what events this triggers in the ETH. I think we should first try this before we try to move forward with other solutions.
We can do this, simply add a print to all the events as they appear in the example code, and attach a log after you let it run for a while (let's say 3-5 cycles of tries)
Please try to use the same method I use for debug prints. I suggest to also add prints to existing events, this will tell us that the prints work from within the events callback.
If there is no close event or any other event from the example code, we can continue to try to think of something else to do.
Sounds good?

Sorry for the late reply. You are a very kind man and I really appreciate your help. I will try this in the following days.
Thank you.