faithoflifedev / easy_onvif_workspace

This package works with a variety of ONVIF compatible devices allowing for IP Cameras and NVRs (network video recorders) to be integrated into Dart and Flutter applications.
32 stars 18 forks source link

onvif discovery crash in windows #45

Closed Viper-Bit closed 1 year ago

Viper-Bit commented 1 year ago

hi, i use this code for device descovery:

  final multicastProbe = MulticastProbe();

  await multicastProbe.probe();

  for (var device in multicastProbe.onvifDevices) {
    print(
        '${device.name} ${device.location} ${device.hardware} ${device.xAddr}');
  }

but when i run it my program crashs with bellow error

Unhandled exception:
SocketException: Failed to create datagram socket (OS Error: The requested address is not valid in its context.
, errno = 10049), address = 239.255.255.250, port = 3702
#0      _NativeSocket.bindDatagram (dart:io-patch/socket_patch.dart:1049:7)
<asynchronous suspension>
#1      MulticastProbe.probe (package:easy_onvif/src/multicast_probe.dart:24:5)
<asynchronous suspension>
#2      main (file:///C:/projects/dart/onvif-test/bin/onvif.dart:17:3)
<asynchronous suspension>
faithoflifedev commented 1 year ago

Hi @Viper-Bit ,

Per the WS-Discovery spec - https://en.wikipedia.org/wiki/WS-Discovery#:~:text=Web%20Services%20Dynamic%20Discovery%20(WS,255.250%20or%20FF02%3A%3AC.

"Web Services Dynamic Discovery (WS-Discovery) is a technical specification that defines a multicast discovery protocol to locate services on a local network. It operates over TCP and UDP port 3702 and uses IP multicast address 239.255.255.250 or FF02::C. As the name suggests, the actual communication between nodes is done using web services standards, notably SOAP-over-UDP."

Are you running the code on Windows, maybe the Windows firewall is blocking the connection. I don't think this is a code related issue.

Viper-Bit commented 1 year ago

@faithoflifedev thx for answer, with disabled firewall still get same result i think dart have a problem to create dgram socket on windows with virtual adapters, i wrote a c++ WS-Discovery and use it as a dll in dart in same pc and everything works like charm. and found similar issue in flutter repo #53477

faithoflifedev commented 1 year ago

Thanks for the update @Viper-Bit, I tried the work-arounds suggested in #53477 and none worked for me on Windows 11.

Are you able to provide your c++ code and I can look at incorporating it into this package until a better solution is available?

Viper-Bit commented 1 year ago

@faithoflifedev yes OfCourse,

onvif.cpp

#include "onvif.h"

char preferred_network_address[16];

int setSocketOptions(int socket) {
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 500000;
    int broadcast = 500;
    char loopch = 0;
    int status = 0;
    struct in_addr localInterface;

#ifdef _WIN32
    PMIB_IPADDRTABLE pIPAddrTable;
    DWORD dwSize = 0;
    DWORD dwRetVal = 0;
    IN_ADDR IPAddr;

    pIPAddrTable = (MIB_IPADDRTABLE*)malloc(sizeof(MIB_IPADDRTABLE));
    if (pIPAddrTable) {
        if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) {
            free(pIPAddrTable);
            pIPAddrTable = (MIB_IPADDRTABLE*)malloc(dwSize);
        }
        if (pIPAddrTable == NULL) {
            printf("Memory allocation failed for GetIpAddrTable\n");
            return -1;
        }
    }

    if ((dwRetVal = GetIpAddrTable(pIPAddrTable, &dwSize, 0)) != NO_ERROR) {
        printf("GetIpAddrTable failed with error %d\n", dwRetVal);
        return -1;
    }

    int p = 0;
    while (p < (int)pIPAddrTable->dwNumEntries) {
        IPAddr.S_un.S_addr = (u_long)pIPAddrTable->table[p].dwAddr;
        IPAddr.S_un.S_addr = (u_long)pIPAddrTable->table[p].dwMask;
        if (pIPAddrTable->table[p].dwAddr != inet_addr("127.0.0.1") && pIPAddrTable->table[p].dwMask == inet_addr("255.255.255.0")) {
            if (strlen(preferred_network_address) > 0) {
                localInterface.s_addr = inet_addr(preferred_network_address);
            }
            else {
                localInterface.s_addr = pIPAddrTable->table[p].dwAddr;
            }
            status = setsockopt(socket, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&localInterface, sizeof(localInterface));
            if (status < 0)
                printf("ip_multicast_if error");
            p = (int)pIPAddrTable->dwNumEntries;
        }
        p++;
    }

    if (pIPAddrTable) {
        free(pIPAddrTable);
        pIPAddrTable = NULL;
    }

    status = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&broadcast, sizeof(broadcast));
#else
    if (strlen(preferred_network_address) > 0) {
        localInterface.s_addr = inet_addr(preferred_network_address);
        status = setsockopt(socket, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&localInterface, sizeof(localInterface));
        if (status < 0)
            printf("ip_multicast_if error");
    }
    status = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (struct timeval*)&tv, sizeof(struct timeval));
#endif
    status = setsockopt(socket, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&loopch, sizeof(loopch));
    return 0;
}

int discovery(OnvifDiscoveryData* data, const char * probeMessage, int duration) {
#ifdef _WIN32
    WSADATA wsaData;
    int wsaStartup = WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif

    sockaddr_in broadcast_address = {};

    int broadcast_message_length = strlen(probeMessage);
    int broadcast_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    setSocketOptions(broadcast_socket);

    memset((char*)&broadcast_address, 0, sizeof(broadcast_address));
    broadcast_address.sin_family = AF_INET;
    broadcast_address.sin_port = htons(3702);
    broadcast_address.sin_addr.s_addr = inet_addr("239.255.255.250");
    int status = sendto(broadcast_socket, probeMessage, broadcast_message_length, 0, (struct sockaddr*)&broadcast_address, sizeof(broadcast_address));
    if (status < 0) {
        //error
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(duration));
    int i = 0;
    bool loop = true;
    socklen_t address_size = sizeof(broadcast_address);
    while (loop) {
        int len = recvfrom(broadcast_socket, data->buf[i], sizeof(data->buf[i]), 0, (struct sockaddr*)&broadcast_address, &address_size);
        if (len > 0) {
            i++;
        }
        else {
            loop = false;
            if (len < 0) {
                //error
            }
        }
    }

#ifdef _WIN32
    closesocket(broadcast_socket);
    WSACleanup();
#else
    close(broadcast_socket);
#endif

    return i;
}

onvif.h

#ifndef ONVIF_H
#define ONVIF_H

#include <chrono>
#include <thread>
#include <cstring>

#ifdef _WIN32
#define LIBRARY_API __declspec(dllexport)
#include <ws2tcpip.h>
#include <iphlpapi.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")
#else
#define LIBRARY_API
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif

#ifdef __MINGW32__
#include <ws2tcpip.h>
#endif

#pragma pack (push, 1)
struct OnvifDiscoveryData {
    char buf[128][8192];
};
#pragma pack(pop)

#ifdef __cplusplus
extern "C" {
#endif

    LIBRARY_API int discovery(OnvifDiscoveryData* data, const char* probeMessage, int duration);

#ifdef __cplusplus
}
#endif

#endif

and dart side is:

import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';

final Pointer<T> Function<T extends NativeType>(String symbolName) _lookup =
    () {
  if (Platform.isWindows) {
    return DynamicLibrary.open('onvif.dll').lookup;
  } else if (Platform.isLinux) {
    return DynamicLibrary.open('/usr/local/lib/libonvif.so').lookup;
  } else {
    throw UnimplementedError();
  }
}();

final _discoveryPtr = _lookup<
    NativeFunction<
        Int32 Function(
          Pointer<NativeType>,
          Pointer<NativeType>,
          Int32,
        )>>('discovery');
final _discovery = _discoveryPtr.asFunction<
    int Function(
      Pointer<NativeType>,
      Pointer<NativeType>,
      int,
    )>();

@Packed(1)
sealed class _OnvifDiscoveryData extends Struct {
  @Array<Int8>(128, 8192)
  external Array<Array<Int8>> buf;
}

const _probeMessage = '''
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
    <SOAP-ENV:Header>
        <a:Action SOAP-ENV:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action>
        <a:MessageID>urn:uuid:2809d092-cb6c-476a-9a6f-7ee0123265d3</a:MessageID>
        <a:ReplyTo>
            <a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
        </a:ReplyTo>
        <a:To SOAP-ENV:mustUnderstand="1">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
        <p:Probe xmlns:p="http://schemas.xmlsoap.org/ws/2005/04/discovery">
            <d:Types xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:dp0="http://www.onvif.org/ver10/network/wsdl">dp0:NetworkVideoTransmitter</d:Types>
        </p:Probe>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
''';

Future<void> discovery([Duration duration = const Duration(seconds: 5)]) async {
  final data = calloc<_OnvifDiscoveryData>();

  final probeMessageData = _probeMessage.toNativeUtf8();

  final devices = _discovery(data, probeMessageData, duration.inMilliseconds);

  //for flutter _discovery cant be called in main thread (locks main thread for duration) so _discovery must be called from isolate
  /*
  final devices = await compute(
        (msg) {
      return _discovery(
        Pointer.fromAddress(msg['dataAddress'] as int),
        Pointer.fromAddress(msg['probeAddress'] as int),
        msg['duration'] as int,
      );
    },
    {
      'dataAddress': data.address,
      'probeAddress': probeMessageData.address,
      'duration': duration.inMilliseconds,
    },
  );
   */

  print('Found $devices Devices');
  for (var index = 0; index < devices; index++) {
    print(Pointer.fromAddress(data.address + index * 8192)
        .cast<Utf8>()
        .toDartString());
  }

  malloc.free(probeMessageData);
  calloc.free(data);
}
faithoflifedev commented 1 year ago

Hi @Viper-Bit , as an update on this, I'm currently readying a new release with the above workaround for Windows OS included. It should be available in a day or two. Thanks for your help with this.

faithoflifedev commented 1 year ago

Hi @Viper-Bit , I've just published the new package v2.1.3+1.

Please let me know if this resolves the issue for you, or if you need a better explanation on how to use the fix (see the known issues section of the README).