From b574e476ec59c9cc0eee8ccf8e3093df62d79acd Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Tue, 7 May 2019 10:17:00 +0200 Subject: [PATCH 1/2] Implement IP.get_local_interfaces. Allow getting interfaces names and assigned names. On UWP this is not supported, and the function will return one interface for each local address (with interface name the local address itself). --- core/io/ip.cpp | 36 +++++++++++++++++++ core/io/ip.h | 11 +++++- core/io/ip_address.cpp | 3 ++ doc/classes/IP.xml | 16 +++++++++ drivers/unix/ip_unix.cpp | 78 ++++++++++++++++++++++++++-------------- drivers/unix/ip_unix.h | 2 +- 6 files changed, 117 insertions(+), 29 deletions(-) diff --git a/core/io/ip.cpp b/core/io/ip.cpp index 420e48f839e..3d87131b51c 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -234,6 +234,41 @@ Array IP::_get_local_addresses() const { return addresses; } +Array IP::_get_local_interfaces() const { + + Array results; + Map interfaces; + get_local_interfaces(&interfaces); + for (Map::Element *E = interfaces.front(); E; E = E->next()) { + Interface_Info &c = E->get(); + Dictionary rc; + rc["name"] = c.name; + rc["friendly"] = c.name_friendly; + rc["index"] = c.index; + + Array ips; + for (const List::Element *F = c.ip_addresses.front(); F; F = F->next()) { + ips.push_front(F->get()); + } + rc["addresses"] = ips; + + results.push_front(rc); + } + + return results; +} + +void IP::get_local_addresses(List *r_addresses) const { + + Map interfaces; + get_local_interfaces(&interfaces); + for (Map::Element *E = interfaces.front(); E; E = E->next()) { + for (const List::Element *F = E->get().ip_addresses.front(); F; F = F->next()) { + r_addresses->push_front(F->get()); + } + } +} + void IP::_bind_methods() { ClassDB::bind_method(D_METHOD("resolve_hostname", "host", "ip_type"), &IP::resolve_hostname, DEFVAL(IP::TYPE_ANY)); @@ -242,6 +277,7 @@ void IP::_bind_methods() { ClassDB::bind_method(D_METHOD("get_resolve_item_address", "id"), &IP::get_resolve_item_address); ClassDB::bind_method(D_METHOD("erase_resolve_item", "id"), &IP::erase_resolve_item); ClassDB::bind_method(D_METHOD("get_local_addresses"), &IP::_get_local_addresses); + ClassDB::bind_method(D_METHOD("get_local_interfaces"), &IP::_get_local_interfaces); ClassDB::bind_method(D_METHOD("clear_cache", "hostname"), &IP::clear_cache, DEFVAL("")); BIND_ENUM_CONSTANT(RESOLVER_STATUS_NONE); diff --git a/core/io/ip.h b/core/io/ip.h index ead71ebb54b..59b18ef9868 100644 --- a/core/io/ip.h +++ b/core/io/ip.h @@ -73,16 +73,25 @@ protected: virtual IP_Address _resolve_hostname(const String &p_hostname, Type p_type = TYPE_ANY) = 0; Array _get_local_addresses() const; + Array _get_local_interfaces() const; static IP *(*_create)(); public: + struct Interface_Info { + String name; + String name_friendly; + String index; + List ip_addresses; + }; + IP_Address resolve_hostname(const String &p_hostname, Type p_type = TYPE_ANY); // async resolver hostname ResolverID resolve_hostname_queue_item(const String &p_hostname, Type p_type = TYPE_ANY); ResolverStatus get_resolve_item_status(ResolverID p_id) const; IP_Address get_resolve_item_address(ResolverID p_id) const; - virtual void get_local_addresses(List *r_addresses) const = 0; + virtual void get_local_addresses(List *r_addresses) const; + virtual void get_local_interfaces(Map *r_interfaces) const = 0; void erase_resolve_item(ResolverID p_id); void clear_cache(const String &p_hostname = ""); diff --git a/core/io/ip_address.cpp b/core/io/ip_address.cpp index 763a5fbb9ac..9305afac5fa 100644 --- a/core/io/ip_address.cpp +++ b/core/io/ip_address.cpp @@ -40,6 +40,9 @@ IP_Address::operator Variant() const { IP_Address::operator String() const { + if (wildcard) + return "*"; + if (!valid) return ""; diff --git a/doc/classes/IP.xml b/doc/classes/IP.xml index 3616a9ec50d..65b17003335 100644 --- a/doc/classes/IP.xml +++ b/doc/classes/IP.xml @@ -34,6 +34,22 @@ Returns all of the user's current IPv4 and IPv6 addresses as an array. + + + + + Returns all network adapters as an array. + Each adapter is a dictionary of the form: + [codeblock] + { + "index": "1", # Interface index. + "name": "eth0", # Interface name. + "friendly": "Ethernet One", # A friendly name (might be empty). + "addresses": ["192.168.1.101"], # An array of IP addresses associated to this interface. + } + [/codeblock] + + diff --git a/drivers/unix/ip_unix.cpp b/drivers/unix/ip_unix.cpp index b5feaabc328..39ca9a823e4 100644 --- a/drivers/unix/ip_unix.cpp +++ b/drivers/unix/ip_unix.cpp @@ -56,6 +56,7 @@ #endif // MINGW hack #endif #else // UNIX +#include #include #ifdef ANDROID_ENABLED // We could drop this file once we up our API level to 24, @@ -77,6 +78,7 @@ static IP_Address _sockaddr2ip(struct sockaddr *p_addr) { IP_Address ip; + if (p_addr->sa_family == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *)p_addr; ip.set_ipv4((uint8_t *)&(addr->sin_addr)); @@ -129,24 +131,42 @@ IP_Address IP_Unix::_resolve_hostname(const String &p_hostname, Type p_type) { #if defined(UWP_ENABLED) -void IP_Unix::get_local_addresses(List *r_addresses) const { +void IP_Unix::get_local_interfaces(Map *r_interfaces) const { using namespace Windows::Networking; using namespace Windows::Networking::Connectivity; + // Returns addresses, not interfaces. auto hostnames = NetworkInformation::GetHostNames(); for (int i = 0; i < hostnames->Size; i++) { - if (hostnames->GetAt(i)->Type == HostNameType::Ipv4 || hostnames->GetAt(i)->Type == HostNameType::Ipv6 && hostnames->GetAt(i)->IPInformation != nullptr) { + auto hostname = hostnames->GetAt(i); - r_addresses->push_back(IP_Address(String(hostnames->GetAt(i)->CanonicalName->Data()))); + if (hostname->Type != HostNameType::Ipv4 && hostname->Type != HostNameType::Ipv6) + continue; + + String name = hostname->RawName->Data(); + Map::Element *E = r_interfaces->find(name); + if (!E) { + Interface_Info info; + info.name = name; + info.name_friendly = hostname->DisplayName->Data(); + info.index = 0; + E = r_interfaces->insert(name, info); + ERR_CONTINUE(!E); } + + Interface_Info &info = E->get(); + + IP_Address ip = IP_Address(hostname->CanonicalName->Data()); + info.ip_addresses.push_front(ip); } -}; +} + #else -void IP_Unix::get_local_addresses(List *r_addresses) const { +void IP_Unix::get_local_interfaces(Map *r_interfaces) const { ULONG buf_size = 1024; IP_ADAPTER_ADDRESSES *addrs; @@ -173,29 +193,23 @@ void IP_Unix::get_local_addresses(List *r_addresses) const { while (adapter != NULL) { + Interface_Info info; + info.name = adapter->AdapterName; + info.name_friendly = adapter->FriendlyName; + info.index = String::num_uint64(adapter->IfIndex); + IP_ADAPTER_UNICAST_ADDRESS *address = adapter->FirstUnicastAddress; while (address != NULL) { - - IP_Address ip; - - if (address->Address.lpSockaddr->sa_family == AF_INET) { - - SOCKADDR_IN *ipv4 = reinterpret_cast(address->Address.lpSockaddr); - - ip.set_ipv4((uint8_t *)&(ipv4->sin_addr)); - r_addresses->push_back(ip); - - } else if (address->Address.lpSockaddr->sa_family == AF_INET6) { // ipv6 - - SOCKADDR_IN6 *ipv6 = reinterpret_cast(address->Address.lpSockaddr); - - ip.set_ipv6(ipv6->sin6_addr.s6_addr); - r_addresses->push_back(ip); - }; - + int family = address->Address.lpSockaddr->sa_family; + if (family != AF_INET && family != AF_INET6) + continue; + info.ip_addresses.push_front(_sockaddr2ip(address->Address.lpSockaddr)); address = address->Next; - }; + } adapter = adapter->Next; + // Only add interface if it has at least one IP + if (info.ip_addresses.size() > 0) + r_interfaces->insert(info.name, info); }; memfree(addrs); @@ -205,7 +219,7 @@ void IP_Unix::get_local_addresses(List *r_addresses) const { #else // UNIX -void IP_Unix::get_local_addresses(List *r_addresses) const { +void IP_Unix::get_local_interfaces(Map *r_interfaces) const { struct ifaddrs *ifAddrStruct = NULL; struct ifaddrs *ifa = NULL; @@ -222,8 +236,18 @@ void IP_Unix::get_local_addresses(List *r_addresses) const { if (family != AF_INET && family != AF_INET6) continue; - IP_Address ip = _sockaddr2ip(ifa->ifa_addr); - r_addresses->push_back(ip); + Map::Element *E = r_interfaces->find(ifa->ifa_name); + if (!E) { + Interface_Info info; + info.name = ifa->ifa_name; + info.name_friendly = ifa->ifa_name; + info.index = String::num_uint64(if_nametoindex(ifa->ifa_name)); + E = r_interfaces->insert(ifa->ifa_name, info); + ERR_CONTINUE(!E); + } + + Interface_Info &info = E->get(); + info.ip_addresses.push_front(_sockaddr2ip(ifa->ifa_addr)); } if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct); diff --git a/drivers/unix/ip_unix.h b/drivers/unix/ip_unix.h index e36535146e2..f9bb626cc44 100644 --- a/drivers/unix/ip_unix.h +++ b/drivers/unix/ip_unix.h @@ -43,7 +43,7 @@ class IP_Unix : public IP { static IP *_create_unix(); public: - virtual void get_local_addresses(List *r_addresses) const; + virtual void get_local_interfaces(Map *r_interfaces) const; static void make_default(); IP_Unix(); From e5e3f866484709f47c97151e99a302206df1d894 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Thu, 20 Jun 2019 11:36:32 +0200 Subject: [PATCH 2/2] Multicast support in NetSocket/PacketPeerUDP --- core/io/net_socket.h | 2 + core/io/packet_peer_udp.cpp | 23 +++++++++++ core/io/packet_peer_udp.h | 2 + doc/classes/PacketPeerUDP.xml | 23 +++++++++++ drivers/unix/net_socket_posix.cpp | 68 +++++++++++++++++++++++++++++++ drivers/unix/net_socket_posix.h | 3 ++ 6 files changed, 121 insertions(+) diff --git a/core/io/net_socket.h b/core/io/net_socket.h index 94e7ef6f753..3bc13694878 100644 --- a/core/io/net_socket.h +++ b/core/io/net_socket.h @@ -74,6 +74,8 @@ public: virtual void set_ipv6_only_enabled(bool p_enabled) = 0; virtual void set_tcp_no_delay_enabled(bool p_enabled) = 0; virtual void set_reuse_address_enabled(bool p_enabled) = 0; + virtual Error join_multicast_group(const IP_Address &p_multi_address, String p_if_name) = 0; + virtual Error leave_multicast_group(const IP_Address &p_multi_address, String p_if_name) = 0; }; #endif // NET_SOCKET_H diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp index 5912b8df947..7e9471c053e 100644 --- a/core/io/packet_peer_udp.cpp +++ b/core/io/packet_peer_udp.cpp @@ -37,6 +37,27 @@ void PacketPeerUDP::set_blocking_mode(bool p_enable) { blocking = p_enable; } +Error PacketPeerUDP::join_multicast_group(IP_Address p_multi_address, String p_if_name) { + + ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(!p_multi_address.is_valid(), ERR_INVALID_PARAMETER); + + if (!_sock->is_open()) { + IP::Type ip_type = p_multi_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; + Error err = _sock->open(NetSocket::TYPE_UDP, ip_type); + ERR_FAIL_COND_V(err != OK, err); + _sock->set_blocking_enabled(false); + } + return _sock->join_multicast_group(p_multi_address, p_if_name); +} + +Error PacketPeerUDP::leave_multicast_group(IP_Address p_multi_address, String p_if_name) { + + ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(!_sock->is_open(), ERR_UNCONFIGURED); + return _sock->leave_multicast_group(p_multi_address, p_if_name); +} + String PacketPeerUDP::_get_packet_ip() const { return get_packet_address(); @@ -237,6 +258,8 @@ void PacketPeerUDP::_bind_methods() { ClassDB::bind_method(D_METHOD("get_packet_ip"), &PacketPeerUDP::_get_packet_ip); ClassDB::bind_method(D_METHOD("get_packet_port"), &PacketPeerUDP::get_packet_port); ClassDB::bind_method(D_METHOD("set_dest_address", "host", "port"), &PacketPeerUDP::_set_dest_address); + ClassDB::bind_method(D_METHOD("join_multicast_group", "multicast_address", "interface_name"), &PacketPeerUDP::join_multicast_group); + ClassDB::bind_method(D_METHOD("leave_multicast_group", "multicast_address", "interface_name"), &PacketPeerUDP::leave_multicast_group); } PacketPeerUDP::PacketPeerUDP() : diff --git a/core/io/packet_peer_udp.h b/core/io/packet_peer_udp.h index 05931376041..068bd5cd5a2 100644 --- a/core/io/packet_peer_udp.h +++ b/core/io/packet_peer_udp.h @@ -77,6 +77,8 @@ public: Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); int get_available_packet_count() const; int get_max_packet_size() const; + Error join_multicast_group(IP_Address p_multi_address, String p_if_name); + Error leave_multicast_group(IP_Address p_multi_address, String p_if_name); PacketPeerUDP(); ~PacketPeerUDP(); diff --git a/doc/classes/PacketPeerUDP.xml b/doc/classes/PacketPeerUDP.xml index 9843c161089..376818fb86e 100644 --- a/doc/classes/PacketPeerUDP.xml +++ b/doc/classes/PacketPeerUDP.xml @@ -37,6 +37,29 @@ Returns whether this [PacketPeerUDP] is listening. + + + + + + + + + Join the multicast group specified by [code]multicast_address[/code] using the interface identified by [code]interface_name[/code]. + You can join the same multicast group with multiple interfaces. Use [method IP.get_local_interfaces] to know which are available. + + + + + + + + + + + Remove the interface identified by [code]interface_name[/code] from the multicast group specified by [code]multicast_address[/code]. + + diff --git a/drivers/unix/net_socket_posix.cpp b/drivers/unix/net_socket_posix.cpp index 75d51c85034..fec101d969f 100644 --- a/drivers/unix/net_socket_posix.cpp +++ b/drivers/unix/net_socket_posix.cpp @@ -59,6 +59,14 @@ #define MSG_NOSIGNAL SO_NOSIGPIPE #endif +// BSD calls this flag IPV6_JOIN_GROUP +#if !defined(IPV6_ADD_MEMBERSHIP) && defined(IPV6_JOIN_GROUP) +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif +#if !defined(IPV6_DROP_MEMBERSHIP) && defined(IPV6_LEAVE_GROUP) +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif + // Some custom defines to minimize ifdefs #define SOCK_EMPTY -1 #define SOCK_BUF(x) x @@ -227,6 +235,58 @@ bool NetSocketPosix::_can_use_ip(const IP_Address p_ip, const bool p_for_bind) c return true; } +_FORCE_INLINE_ Error NetSocketPosix::_change_multicast_group(IP_Address p_ip, String p_if_name, bool p_add) { + + ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!_can_use_ip(p_ip, false), ERR_INVALID_PARAMETER); + + // Need to force level and af_family to IP(v4) when using dual stacking and provided multicast group is IPv4 + IP::Type type = _ip_type == IP::TYPE_ANY && p_ip.is_ipv4() ? IP::TYPE_IPV4 : _ip_type; + // This needs to be the proper level for the multicast group, no matter if the socket is dual stacking. + int level = type == IP::TYPE_IPV4 ? IPPROTO_IP : IPPROTO_IPV6; + int ret = -1; + + IP_Address if_ip; + uint32_t if_v6id = 0; + Map if_info; + IP::get_singleton()->get_local_interfaces(&if_info); + for (Map::Element *E = if_info.front(); E; E = E->next()) { + IP::Interface_Info &c = E->get(); + if (c.name != p_if_name) + continue; + + if_v6id = (uint32_t)c.index.to_int64(); + if (type == IP::TYPE_IPV6) + break; // IPv6 uses index. + + for (List::Element *F = c.ip_addresses.front(); F; F = F->next()) { + if (!F->get().is_ipv4()) + continue; // Wrong IP type + if_ip = F->get(); + break; + } + break; + } + + if (level == IPPROTO_IP) { + ERR_FAIL_COND_V(!if_ip.is_valid(), ERR_INVALID_PARAMETER); + struct ip_mreq greq; + int sock_opt = p_add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP; + copymem(&greq.imr_multiaddr, p_ip.get_ipv4(), 4); + copymem(&greq.imr_interface, if_ip.get_ipv4(), 4); + ret = setsockopt(_sock, level, sock_opt, (const char *)&greq, sizeof(greq)); + } else { + struct ipv6_mreq greq; + int sock_opt = p_add ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP; + copymem(&greq.ipv6mr_multiaddr, p_ip.get_ipv6(), 16); + greq.ipv6mr_interface = if_v6id; + ret = setsockopt(_sock, level, sock_opt, (const char *)&greq, sizeof(greq)); + } + ERR_FAIL_COND_V(ret != 0, FAILED); + + return OK; +} + void NetSocketPosix::_set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_is_stream) { _sock = p_sock; _ip_type = p_ip_type; @@ -625,3 +685,11 @@ Ref NetSocketPosix::accept(IP_Address &r_ip, uint16_t &r_port) { ns->set_blocking_enabled(false); return Ref(ns); } + +Error NetSocketPosix::join_multicast_group(const IP_Address &p_ip, String p_if_name) { + return _change_multicast_group(p_ip, p_if_name, true); +} + +Error NetSocketPosix::leave_multicast_group(const IP_Address &p_ip, String p_if_name) { + return _change_multicast_group(p_ip, p_if_name, false); +} diff --git a/drivers/unix/net_socket_posix.h b/drivers/unix/net_socket_posix.h index b7fb3fdc946..cf0412311d9 100644 --- a/drivers/unix/net_socket_posix.h +++ b/drivers/unix/net_socket_posix.h @@ -60,6 +60,7 @@ private: NetError _get_socket_error(); void _set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_is_stream); + _FORCE_INLINE_ Error _change_multicast_group(IP_Address p_ip, String p_if_name, bool p_add); protected: static NetSocket *_create_func(); @@ -93,6 +94,8 @@ public: virtual void set_tcp_no_delay_enabled(bool p_enabled); virtual void set_reuse_address_enabled(bool p_enabled); virtual void set_reuse_port_enabled(bool p_enabled); + virtual Error join_multicast_group(const IP_Address &p_multi_address, String p_if_name); + virtual Error leave_multicast_group(const IP_Address &p_multi_address, String p_if_name); NetSocketPosix(); ~NetSocketPosix();