khoih-prog / WebSockets_Generic

WebSocket / Socket.IO Server and Client for Arduino based on RFC6455. Now supporting Adafruit nRF52, Portenta_H7, STM32F/L/H/G/WB/MP1, Teensy, SAM DUE, SAMD21, SAMD51, Arduino SAMD21 (Nano 33 IoT), MKR1000 / MKR1010WiFi, RP2040-based boards using WiFi101, WiFiNINA, WiFi, Ethernet, WT32_ETH01, Teensy 4.1 NativeEthernet/QNEthernet or Portenta_H7 WiFi/Ethernet, etc. so that those boards can be voice-controlled by Alexa. Now supporting websocket only mode for Socket.IO. Ethernet_Generic library is used as default for W5x00
GNU General Public License v3.0
90 stars 22 forks source link

Ethernet with SSL #17

Closed iKK001 closed 2 years ago

iKK001 commented 2 years ago

I am trying to connect to a Socket.IO server with Ethernet and SSL.

The Board: MKR WiFi1010 The Ethernet Shield: MKR ETH stacked on top of the MKR WiFi1010

I was thinking of using the EthernetWebServer_SSL library.

EthernetClient client;
EthernetSSLClient sslClient(client, TAs, (size_t)TAs_NUM);

However, I do not see a way on how to achieve the connection with the SocketIO client.

SocketIOclient socketIO;

Obviously, in my code there is no SSL connection yet. And therefore the connection does not work as can be seen in the following log:

Connected! My IP address: 192.168.1.65
Connecting to WebSockets Server @ IP address: test.test.com, port: 443
[WS] WebSockets_Generic v2.11.1
[WS] [wsIOc] found EIO=4 disable EIO ping on client
[WS] [WS-Client] Connect wss...
Receiving Payload:

[IOc] Disconnected
[WS] [wsIOc] Disconnected!

Below is my entire code. The SSL-certifcate is placed in the trust_anchors.h file as described in the EthernetWebServer_SSL library.

Questions:

  1. How do I link the sslClient to the socketIO client correctly ? Or is this the wrong way at all ?

  2. How would I achieve SSL and Ethernet with the WebSocket_Generic's Socket.IO library ?

  3. Is the call to socketIO.beginSSL(server, serverPort); automatically doing SSL ? Or is there something missing ? How do I achieve SSL with Socket.IO ?

Thanks.

#include <EthernetWebServer_SSL.h>
#include "trust_anchors.h"  // You must have Trihow SSL Certificates here
#include "arduino_secrets.h"
#include <ArduinoJson.h>
#include <Adafruit_NeoPixel.h> 
#include <Ramp.h>

#define BOARD_NAME "ARDUINO_SAMD_MKRWIFI1010"
#define USE_THIS_SS_PIN 5     // MKR ETH shield

#define _WEBSOCKETS_LOGLEVEL_     3
#define WEBSOCKETS_NETWORK_TYPE   NETWORK_WIFININA

// For transport=websocket instead of transport=polling
#define USING_STICKY_SESSION_SIO        false

#include <ArduinoJson.h>

#include <WebSocketsClient_Generic.h>
#include <SocketIOclient_Generic.h>

SocketIOclient socketIO;

byte mac[] = { 0xA8, 0x61, 0x0A, 0xAE, 0x28, 0x9C };    // MAC Adress of Arduino MKR ETH Shield
uint16_t serverPort = SOCKET_IO_PORT; // from arduino_secrets.h
char server[] = SOCKET_IO_SERVER_URL; // from arduino_secrets.h

EthernetClient client;
EthernetSSLClient sslClient(client, TAs, (size_t)TAs_NUM);

unsigned long messageTimestamp = 0;

void socketIOEvent(socketIOmessageType_t type, uint8_t* payload, size_t length) {

  Serial.println("Receiving Payload:") ;
  Serial.println((char*)payload);

  switch (type) {
    case sIOtype_DISCONNECT:
      Serial.println("[IOc] Disconnected");
      break;
    case sIOtype_CONNECT:
      Serial.print("[IOc] Connected to url: ");
      Serial.println((char*)payload);

      // join default namespace (no auto join in Socket.IO V3)
      socketIO.send(sIOtype_CONNECT, "/");

      break;
    case sIOtype_EVENT:
      Serial.print("[IOc] Get event: ");
      Serial.println((char*)payload);

      break;
    case sIOtype_ACK:
      Serial.print("[IOc] Get ack: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;
    case sIOtype_ERROR:
      Serial.print("[IOc] Get error: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;
    case sIOtype_BINARY_EVENT:
      Serial.print("[IOc] Get binary: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;
    case sIOtype_BINARY_ACK:
      Serial.print("[IOc] Get binary ack: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;

    default:
      Serial.println("[IOc] Default case...");
      break;
  }
}

void setup() {  

  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  delay(2000);

  Ethernet.init(USE_THIS_SS_PIN);

  Ethernet.begin(mac);

  Serial.print(F("Connected! My IP address: "));
  Serial.println(Ethernet.localIP());

  // give the Ethernet shield a second to initialize:
  delay(2000);

  // server address, port and URL
  Serial.print("Connecting to WebSockets Server @ IP address: ");
  Serial.print(server);
  Serial.print(", port: ");
  Serial.println(serverPort);

  // setReconnectInterval to 10s, new from v2.5.1 to avoid flooding server. Default is 0.5s
  socketIO.setReconnectInterval(10000);

  socketIO.setExtraHeaders(SOCKET_IO_AUTHORIZATION);  // from arduino_secrets.h

  socketIO.beginSSL(server, serverPort);

  socketIO.onEvent(socketIOEvent);
}

void loop() {

    socketIO.loop();

    uint64_t now = millis();

    if (now - messageTimestamp > 30000) {

        messageTimestamp = now;

        // create JSON message for Socket.IO (event)
        DynamicJsonDocument doc(1024);
        JsonArray array = doc.to<JsonArray>();

        // add event name    // Hint: socket.on('event_name', ....
        array.add("subscribe");

        // add payload (parameters) for the event
        JsonObject param = array.createNestedObject();

        param["userName"] = "Default Test";
        param["sessionId"] = "th-VIZsyFibjFj1xGLPL4fk";

        // JSON to String (serializion)
        String output;
        output += "0";  // same as Web
        serializeJson(doc, output);

        // Send event
        socketIO.sendEVENT(output); 

        // Print JSON for debugging
        Serial.println(output);
    }
}
khoih-prog commented 2 years ago

The Board: MKR WiFi1010 The Ethernet Shield: MKR ETH stacked on top of the MKR WiFi1010

Sorry I don't have MKR WiFi1010 + MKR ETH to test and help.

Moreover, please post the complete code ( such as server, etc.) so that anybody with the same hardware can help.

Currently, wss (SSL) is supporting only ESP8266 (check WebSocketClientSSL.ino#L6 and esp32 with ssl don't work #705 ).

So it's even requiring a lot of work to rewrite the library to add support to normal wss to other boards, not talking about Socket.IO with SSL.

SSL also requires a lot more knowledge and board's resource / capability and you have to be careful before make it a must for your design. Something we can do easily using PC / Laptop can't be expected to fulfill easily in this embedded world.

Your request is so abnormal and I'm sorry I can't spend time here, unless there are many many more requests in the future so that I can reopen the issue.

iKK001 commented 2 years ago

Our customers (Retail stores) do not have reliable WiFi but they have reliable Router-Switches installed. Therefore I need a working example of SSL-Ethernet and Socket.IO.

I cannot show the server-code since it is prohibited by the customer.

What I don't undersrand:

With SSL-WiFi I have it working (ie. using the "certificate download" with the help of the Arduino IDE Tool, and running your WiFi-Socket.IO example.

And with "I have it working", I mean: it works on MKRWiFi1010, RP2040 Connect as well as a Adafruit Feather M0 boards !!! (i.e. not only onn ESP8266 as you write - but rather a whole bunch of M0-Cortex based boards).

Why can't your library work with the same SSL-certificate mecano for Ethernet as it does for WiFiNINA examples?

At least, your Websocket_generic-->Generic examples also contain SocketIO-Ethernet examples. However it is very intransparent whether there is a chance to get it to run with SSL-Ethernet.

Aren't there any Ethernet-Shield examples that should work just the same way as WiFI-examples ?

All I need is to drive the Socket.IO Example via Ethernet connected to the internet instead of WiFi.

iKK001 commented 2 years ago

You wrote "Your request is so abnormal". Just my question: How do you connect to the Internet securely then ? I always thought that a LAN connection should be SSL in order to securely access the Internet. How do you connect your Socket.IO implementation to the outer world in a secure fasion ? Since your Socket.IO Ethernet example does not work with SSL, how do you make it secure then ? Thank you for answering.

khoih-prog commented 2 years ago

I think you're selecting the wrong board to start.

I suggest that you now use the WT32_ETH01 with the built-in Ethernet and the powerful WiFi/Ethernet SSL of the ESP32 core.

I'll publish a new release v2.12.0 to support this SSL feature for WT32_ETH01

This is the test example based on yours

/****************************************************************************************************************************
  WT32_ETH01_SSL_SIO.ino
  For WT32_ETH01

  Based on and modified from WebSockets libarary https://github.com/Links2004/arduinoWebSockets
  to support other boards such as  SAMD21, SAMD51, Adafruit's nRF52 boards, etc.

  Built by Khoi Hoang https://github.com/khoih-prog/WebSockets_Generic
  Licensed under MIT license
*****************************************************************************************************************************/

#if !defined(ESP32)
  #error This code is intended to run only on the ESP32-based WT32_ETH01 boards ! Please check your Tools->Board setting.
#endif

#define _WEBSOCKETS_LOGLEVEL_     4

#define WEBSOCKETS_NETWORK_TYPE   NETWORK_ESP32_ETH

#include <WebServer_WT32_ETH01.h>

#include "arduino_secrets.h"
#include <ArduinoJson.h>

#define BOARD_NAME "WT32_ETH01"

#define _WEBSOCKETS_LOGLEVEL_     4

#define WEBSOCKETS_NETWORK_TYPE   NETWORK_ESP32_ETH

// For transport=websocket instead of transport=polling
#define USING_STICKY_SESSION_SIO        true

#include <ArduinoJson.h>

#include <WebSocketsClient_Generic.h>
#include <SocketIOclient_Generic.h>

SocketIOclient socketIO;

// Select the IP address according to your local network
IPAddress myIP(192, 168, 2, 232);
IPAddress myGW(192, 168, 2, 1);
IPAddress mySN(255, 255, 255, 0);

// Google DNS Server IP
IPAddress myDNS(8, 8, 8, 8);

//#define USE_SSL         false
#define USE_SSL         true

#if USE_SSL
  uint16_t serverPort = SOCKET_IO_PORT; // from arduino_secrets.h
  char server[] = SOCKET_IO_SERVER_URL; // from arduino_secrets.h
#else
  // To run a local WebSocket Server
  char server[]        = "192.168.2.30";
  uint16_t serverPort =    8080;
#endif

unsigned long messageTimestamp = 0;

void socketIOEvent(socketIOmessageType_t type, uint8_t* payload, size_t length)
{
  Serial.println("Receiving Payload:") ;
  Serial.println((char*)payload);

  switch (type)
  {
    case sIOtype_DISCONNECT:
      Serial.println("[IOc] Disconnected");
      break;
    case sIOtype_CONNECT:
      Serial.print("[IOc] Connected to url: ");
      Serial.println((char*)payload);

      // join default namespace (no auto join in Socket.IO V3)
      socketIO.send(sIOtype_CONNECT, "/");

      break;
    case sIOtype_EVENT:
      Serial.print("[IOc] Get event: ");
      Serial.println((char*)payload);

      break;
    case sIOtype_ACK:
      Serial.print("[IOc] Get ack: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;
    case sIOtype_ERROR:
      Serial.print("[IOc] Get error: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;
    case sIOtype_BINARY_EVENT:
      Serial.print("[IOc] Get binary: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;
    case sIOtype_BINARY_ACK:
      Serial.print("[IOc] Get binary ack: ");
      Serial.println(length);

      //hexdump(payload, length);
      break;

    default:
      Serial.println("[IOc] Default case...");
      break;
  }
}

void setup()
{
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial);

  delay(2000);

  // To be called before ETH.begin()
  WT32_ETH01_onEvent();

  //bool begin(uint8_t phy_addr=ETH_PHY_ADDR, int power=ETH_PHY_POWER, int mdc=ETH_PHY_MDC, int mdio=ETH_PHY_MDIO,
  //           eth_phy_type_t type=ETH_PHY_TYPE, eth_clock_mode_t clk_mode=ETH_CLK_MODE);
  //ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_TYPE, ETH_CLK_MODE);
  ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER);

  // Static IP, leave without this line to get IP via DHCP
  //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0);
  //ETH.config(myIP, myGW, mySN, myDNS);

  WT32_ETH01_waitForConnect();

  Serial.println();

  // Client address
  Serial.print("WebSockets Client started @ IP address: ");
  Serial.println(ETH.localIP());

  // give the Ethernet shield a second to initialize:
  delay(2000);

  // server address, port and URL
  Serial.print("Connecting to WebSockets Server @ IP address: ");
  Serial.print(server);
  Serial.print(", port: ");
  Serial.println(serverPort);

  // setReconnectInterval to 10s, new from v2.5.1 to avoid flooding server. Default is 0.5s
  socketIO.setReconnectInterval(10000);

  //socketIO.setExtraHeaders(SOCKET_IO_AUTHORIZATION);  // from arduino_secrets.h
  socketIO.setExtraHeaders("Authorization: 1234567890");

#if USE_SSL
  socketIO.beginSSL(server, serverPort);
#else
  socketIO.begin(server, serverPort, "/");
#endif

  socketIO.onEvent(socketIOEvent);
}

void loop()
{

  socketIO.loop();

  uint64_t now = millis();

  if (now - messageTimestamp > 30000)
  {
    messageTimestamp = now;

    // create JSON message for Socket.IO (event)
    DynamicJsonDocument doc(1024);
    JsonArray array = doc.to<JsonArray>();

    // add event name    // Hint: socket.on('event_name', ....
    array.add("subscribe");

    // add payload (parameters) for the event
    JsonObject param = array.createNestedObject();

    param["userName"] = "Default Test";
    param["sessionId"] = "th-VIZsyFibjFj1xGLPL4fk";

    // JSON to String (serializion)
    String output;
    output += "0";  // same as Web
    serializeJson(doc, output);

    // Send event
    socketIO.sendEVENT(output);

    // Print JSON for debugging
    Serial.println(output);
  }
}

Terminal output

ETH Started
ETH Connected
ETH MAC: A8:48:FA:08:4B:FF, IPv4: 192.168.2.97
FULL_DUPLEX, 100Mbps

WebSockets Client started @ IP address: 192.168.2.97
Connecting to WebSockets Server @ IP address: 192.168.2.30, port: 8080
[WS] [WS-Client] beginSocketIO with const char
[WS] WebSockets_Generic v2.11.1
[WS] [WS-Client] Connect ws...
[WS] [WS-Client] Calling _client.tcp->connect, _host =192.168.2.30, port =8080
[WS] [WS-Client] Calling _client.tcp->connect, _connectResult =1
[WS] [WS-Client] connectedCb
[WS] [WS-Client][connectedCb] Connected to Host:192.168.2.30, Port:8080
[WS] [WS-Client] [sendHeader] Sending header...
[WS] sendHeader: client->cKey = mCkeaffS6MCGIBiQ87KlVw==
[WS] [WS-Client] [sendHeader] Handshake:GET /&transport=websocket HTTP/1.1
Host: 192.168.2.30:8080
Connection: keep-alive
Authorization: 1234567890
User-Agent: arduino-WebSocket-Client

[WS] [write] n:152, t:10134
[WS] [write] Write, Length :152, Left :0
[WS] [WS-Client] [sendHeader] Sending header... Done (us):22048
[WS] [WS-Client][handleHeader] RX:HTTP/1.1 200 OK
[WS] [WS-Client][handleHeader] RX:Date: Fri, 28 Jan 2022 05:40:27 GMT
[WS] [WS-Client][handleHeader] RX:Connection: keep-alive
[WS] [WS-Client][handleHeader] RX:Keep-Alive: timeout=5
[WS] [WS-Client][handleHeader] RX:Content-Length: 9
[WS] [WS-Client][handleHeader] Header read fin.
[WS] [WS-Client][handleHeader] Client settings:
[WS] [WS-Client][handleHeader] - cURL:/
[WS] [WS-Client][handleHeader] - cKey:mCkeaffS6MCGIBiQ87KlVw==
[WS] [WS-Client][handleHeader] Server header:
[WS] [WS-Client][handleHeader] - cCode:200
[WS] [WS-Client][handleHeader] - cIsUpgrade:0
[WS] [WS-Client][handleHeader] - cIsWebsocket:1
[WS] [WS-Client][handleHeader] - cAccept:
[WS] [WS-Client][handleHeader] - cProtocol:arduino
[WS] [WS-Client][handleHeader] - cExtensions:
[WS] [WS-Client][handleHeader] - cVersion:0
[WS] [WS-Client][handleHeader] - cSessionId:
[WS] [WS-Client][handleHeader] Still missing cSessionId try Socket.IO
[WS] [WS-Client][handleHeader] socket.io json: worker: 4
[WS] [WS-Client][handleHeader] RX:worker: 4

Many of my libraries are supporting WT32_ETH01 , such as

  1. WebServer_WT32_ETH01
  2. EthernetWebServer ...

Try to be familiar with them before moving forward.

khoih-prog commented 2 years ago

Hi @iKK001

The new WebSockets_Generic releases v12.12.0 has just been published. Your insistence, leading to this new release, is noted in Contributions and Thanks

Best Regards and Good Luck,


Major Release v2.12.0

  1. Add SSL support to ESP32-based WT32_ETH01 boards using LAN8720 Ethernet
  2. Add WT32_ETH01-related WT32_ETH01_SSL_SIO example
  3. Update Packages' Patches
iKK001 commented 2 years ago

Dear @khoih-prog, thank you very much. I appreciate your work.

As for the M0 Cortex based Baords: Could't you use the same mechano as used in Examples-->EthernetWebServer_SSL-->WebClient_SSL ? (i.e. with a trust_anchor). All it would take is to replace the WebSocket-client (or SocketIO-client) by this sslClient. Or is this too much of a dream ?

Screenshot 2022-01-28 at 09 28 18

RQnet commented 2 years ago

hi, is this working now with mkr1010?

iKK001 commented 2 years ago

nope - not that I know

RQnet commented 2 years ago

What a pity. I like Khoihs libraries. I can connect with "socketIO.begin(..)." but with "socketIO.beginSSL(...)" it doesn´t work with my MKR1010. I got just this message:

[WS] WebSockets_Generic v2.12.0 [WS] [wsIOc] found EIO=4 disable EIO ping on client [WS] [WS-Client] Connect wss... [WN] r:Check&WLost [WN] r:WLost.ReconW ..... .... disconnecting from server.

in this case i can´t use it. SSL is required. If some found a way to work with this and MKR1010 please share.

RQnet commented 2 years ago

is it planned to make it work for MKR1010?

RQnet commented 2 years ago

one more question. the server don´t parse special characters (+,&...) in password. How can I work with it?