Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onvif discovery crash in windows #45

Closed
Viper-Bit opened this issue Aug 19, 2023 · 6 comments
Closed

onvif discovery crash in windows #45

Viper-Bit opened this issue Aug 19, 2023 · 6 comments

Comments

@Viper-Bit
Copy link

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
Copy link
Owner

faithoflifedev commented Aug 23, 2023

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
Copy link
Author

@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
Copy link
Owner

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
Copy link
Author

@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
Copy link
Owner

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
Copy link
Owner

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants