Merge pull request #33910 from Faless/net/android_mlock

Acquire MulticastLock on Android when using broadcast/multicast
This commit is contained in:
Rémi Verschelde 2019-12-14 21:53:08 +01:00 committed by GitHub
commit d3a07d3550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 362 additions and 8 deletions

View file

@ -69,7 +69,7 @@ public:
virtual bool is_open() const = 0;
virtual int get_available_bytes() const = 0;
virtual void set_broadcasting_enabled(bool p_enabled) = 0;
virtual Error set_broadcasting_enabled(bool p_enabled) = 0; // Returns OK if the socket option has been set successfully.
virtual void set_blocking_enabled(bool p_enabled) = 0;
virtual void set_ipv6_only_enabled(bool p_enabled) = 0;
virtual void set_tcp_no_delay_enabled(bool p_enabled) = 0;

View file

@ -37,6 +37,12 @@ void PacketPeerUDP::set_blocking_mode(bool p_enable) {
blocking = p_enable;
}
void PacketPeerUDP::set_broadcast_enabled(bool p_enabled) {
broadcast = p_enabled;
if (_sock.is_valid() && _sock->is_open())
_sock->set_broadcasting_enabled(p_enabled);
}
Error PacketPeerUDP::join_multicast_group(IP_Address p_multi_address, String p_if_name) {
ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
@ -47,6 +53,7 @@ Error PacketPeerUDP::join_multicast_group(IP_Address p_multi_address, String p_i
Error err = _sock->open(NetSocket::TYPE_UDP, ip_type);
ERR_FAIL_COND_V(err != OK, err);
_sock->set_blocking_enabled(false);
_sock->set_broadcasting_enabled(broadcast);
}
return _sock->join_multicast_group(p_multi_address, p_if_name);
}
@ -122,6 +129,7 @@ Error PacketPeerUDP::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
ERR_FAIL_COND_V(err != OK, err);
_sock->set_blocking_enabled(false);
_sock->set_broadcasting_enabled(broadcast);
}
do {
@ -165,6 +173,7 @@ Error PacketPeerUDP::listen(int p_port, const IP_Address &p_bind_address, int p_
_sock->set_blocking_enabled(false);
_sock->set_reuse_address_enabled(true);
_sock->set_broadcasting_enabled(broadcast);
err = _sock->bind(p_bind_address, p_port);
if (err != OK) {
@ -258,6 +267,7 @@ 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("set_broadcast_enabled", "enabled"), &PacketPeerUDP::set_broadcast_enabled);
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);
}
@ -267,6 +277,7 @@ PacketPeerUDP::PacketPeerUDP() :
queue_count(0),
peer_port(0),
blocking(true),
broadcast(false),
_sock(Ref<NetSocket>(NetSocket::create())) {
rb.resize(16);
}

View file

@ -53,6 +53,7 @@ protected:
IP_Address peer_addr;
int peer_port;
bool blocking;
bool broadcast;
Ref<NetSocket> _sock;
static void _bind_methods();
@ -77,6 +78,7 @@ 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;
void set_broadcast_enabled(bool p_enabled);
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);

View file

@ -47,6 +47,7 @@
<description>
Joins 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.
Note: Some Android devices might require the [code]CHANGE_WIFI_MULTICAST_STATE[/code] permission for multicast to work.
</description>
</method>
<method name="leave_multicast_group">
@ -76,6 +77,16 @@
If [code]bind_address[/code] is set to any valid address (e.g. [code]"192.168.1.101"[/code], [code]"::1"[/code], etc), the peer will only listen on the interface with that addresses (or fail if no interface with the given address exists).
</description>
</method>
<method name="set_broadcast_enabled">
<return type="void">
</return>
<argument index="0" name="enabled" type="bool">
</argument>
<description>
Enable or disable sending of broadcast packets (e.g. [code]set_dest_address("255.255.255.255", 4343)[/code]. This option is disabled by default.
Note: Some Android devices might require the [code]CHANGE_WIFI_MULTICAST_STATE[/code] permission and this option to be enabled to receive broadcast packets too.
</description>
</method>
<method name="set_dest_address">
<return type="int" enum="Error">
</return>
@ -85,6 +96,7 @@
</argument>
<description>
Sets the destination address and port for sending packets and variables. A hostname will be resolved using DNS if needed.
Note: [method set_broadcast_enabled] must be enabled before sending packets to a broadcast address (e.g. [code]255.255.255.255[/code]).
</description>
</method>
<method name="wait">

View file

@ -333,9 +333,10 @@ Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) {
set_ipv6_only_enabled(ip_type != IP::TYPE_ANY);
}
if (protocol == IPPROTO_UDP && ip_type != IP::TYPE_IPV6) {
// Enable broadcasting for UDP sockets if it's not IPv6 only (IPv6 has no broadcast option).
set_broadcasting_enabled(true);
if (protocol == IPPROTO_UDP) {
// Make sure to disable broadcasting for UDP sockets.
// Depending on the OS, this option might or might not be enabled by default. Let's normalize it.
set_broadcasting_enabled(false);
}
_is_stream = p_sock_type == TYPE_TCP;
@ -603,15 +604,18 @@ Error NetSocketPosix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP
return OK;
}
void NetSocketPosix::set_broadcasting_enabled(bool p_enabled) {
ERR_FAIL_COND(!is_open());
Error NetSocketPosix::set_broadcasting_enabled(bool p_enabled) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
// IPv6 has no broadcast support.
ERR_FAIL_COND(_ip_type == IP::TYPE_IPV6);
if (_ip_type == IP::TYPE_IPV6)
return ERR_UNAVAILABLE;
int par = p_enabled ? 1 : 0;
if (setsockopt(_sock, SOL_SOCKET, SO_BROADCAST, SOCK_CBUF(&par), sizeof(int)) != 0) {
WARN_PRINT("Unable to change broadcast setting");
return FAILED;
}
return OK;
}
void NetSocketPosix::set_blocking_enabled(bool p_enabled) {

View file

@ -89,7 +89,7 @@ public:
virtual bool is_open() const;
virtual int get_available_bytes() const;
virtual void set_broadcasting_enabled(bool p_enabled);
virtual Error set_broadcasting_enabled(bool p_enabled);
virtual void set_blocking_enabled(bool p_enabled);
virtual void set_ipv6_only_enabled(bool p_enabled);
virtual void set_tcp_no_delay_enabled(bool p_enabled);

View file

@ -12,6 +12,7 @@ android_files = [
'file_access_jandroid.cpp',
'dir_access_jandroid.cpp',
'thread_jandroid.cpp',
'net_socket_android.cpp',
'audio_driver_jandroid.cpp',
'java_godot_lib_jni.cpp',
'java_class_wrapper.cpp',

View file

@ -96,6 +96,7 @@ import java.util.Locale;
import javax.microedition.khronos.opengles.GL10;
import org.godotengine.godot.input.GodotEditText;
import org.godotengine.godot.payments.PaymentsManager;
import org.godotengine.godot.utils.GodotNetUtils;
import org.godotengine.godot.utils.PermissionsUtil;
import org.godotengine.godot.xr.XRMode;
@ -243,6 +244,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
private Sensor mGyroscope;
public static GodotIO io;
public static GodotNetUtils netUtils;
static SingletonBase[] singletons = new SingletonBase[MAX_SINGLETONS];
static int singleton_count = 0;
@ -502,6 +504,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
io = new GodotIO(this);
io.unique_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
GodotLib.io = io;
netUtils = new GodotNetUtils(this);
mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);

View file

@ -0,0 +1,84 @@
/*************************************************************************/
/* GodotNetUtils.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
package org.godotengine.godot.utils;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.util.Log;
import org.godotengine.godot.Godot;
/**
* This class handles Android-specific networking functions.
* For now, it only provides access to WifiManager.MulticastLock, which is needed on some devices
* to receive broadcast and multicast packets.
*/
public class GodotNetUtils {
/* A single, reference counted, multicast lock, or null if permission CHANGE_WIFI_MULTICAST_STATE is missing */
private WifiManager.MulticastLock multicastLock;
public GodotNetUtils(Godot p_activity) {
if (PermissionsUtil.hasManifestPermission(p_activity, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) {
WifiManager wifi = (WifiManager)p_activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
multicastLock = wifi.createMulticastLock("GodotMulticastLock");
multicastLock.setReferenceCounted(true);
}
}
/**
* Acquire the multicast lock. This is required on some devices to receive broadcast/multicast packets.
* This is done automatically by Godot when enabling broadcast or joining a multicast group on a socket.
*/
public void multicastLockAcquire() {
if (multicastLock == null)
return;
try {
multicastLock.acquire();
} catch (RuntimeException e) {
Log.e("Godot", "Exception during multicast lock acquire: " + e);
}
}
/**
* Release the multicast lock.
* This is done automatically by Godot when the lock is no longer needed by a socket.
*/
public void multicastLockRelease() {
if (multicastLock == null)
return;
try {
multicastLock.release();
} catch (RuntimeException e) {
Log.e("Godot", "Exception during multicast lock release: " + e);
}
}
}

View file

@ -161,6 +161,24 @@ public final class PermissionsUtil {
return dangerousPermissions.toArray(new String[0]);
}
/**
* Check if the given permission is in the AndroidManifest.xml file.
* @param activity the caller activity for this method.
* @param permission the permession to look for in the manifest file.
* @return "true" if the permission is in the manifest file of the activity, "false" otherwise.
*/
public static boolean hasManifestPermission(Godot activity, String permission) {
try {
for (String p : getManifestPermissions(activity)) {
if (permission.equals(p))
return true;
}
} catch (PackageManager.NameNotFoundException e) {
}
return false;
}
/**
* Returns the permissions defined in the AndroidManifest.xml file.
* @param activity the caller activity for this method.

View file

@ -43,6 +43,7 @@
#include "java_class_wrapper.h"
#include "main/input_default.h"
#include "main/main.h"
#include "net_socket_android.h"
#include "os_android.h"
#include "string_android.h"
#include "thread_jandroid.h"
@ -635,6 +636,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
DirAccessJAndroid::setup(godot_io_java->get_instance());
AudioDriverAndroid::setup(godot_io_java->get_instance());
NetSocketAndroid::setup(godot_java->get_member_object("netUtils", "Lorg/godotengine/godot/utils/GodotNetUtils;", env));
os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion);

View file

@ -0,0 +1,136 @@
/*************************************************************************/
/* net_socket_android.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "net_socket_android.h"
#include "thread_jandroid.h"
jobject NetSocketAndroid::net_utils = 0;
jclass NetSocketAndroid::cls = 0;
jmethodID NetSocketAndroid::_multicast_lock_acquire = 0;
jmethodID NetSocketAndroid::_multicast_lock_release = 0;
void NetSocketAndroid::setup(jobject p_net_utils) {
JNIEnv *env = ThreadAndroid::get_env();
net_utils = env->NewGlobalRef(p_net_utils);
jclass c = env->GetObjectClass(net_utils);
cls = (jclass)env->NewGlobalRef(c);
_multicast_lock_acquire = env->GetMethodID(cls, "multicastLockAcquire", "()V");
_multicast_lock_release = env->GetMethodID(cls, "multicastLockRelease", "()V");
}
void NetSocketAndroid::multicast_lock_acquire() {
if (_multicast_lock_acquire) {
JNIEnv *env = ThreadAndroid::get_env();
env->CallVoidMethod(net_utils, _multicast_lock_acquire);
}
}
void NetSocketAndroid::multicast_lock_release() {
if (_multicast_lock_release) {
JNIEnv *env = ThreadAndroid::get_env();
env->CallVoidMethod(net_utils, _multicast_lock_release);
}
}
NetSocket *NetSocketAndroid::_create_func() {
return memnew(NetSocketAndroid);
}
void NetSocketAndroid::make_default() {
_create = _create_func;
}
NetSocketAndroid::NetSocketAndroid() :
wants_broadcast(false),
multicast_groups(0) {
}
NetSocketAndroid::~NetSocketAndroid() {
close();
}
void NetSocketAndroid::close() {
NetSocketPosix::close();
if (wants_broadcast)
multicast_lock_release();
if (multicast_groups)
multicast_lock_release();
wants_broadcast = false;
multicast_groups = 0;
}
Error NetSocketAndroid::set_broadcasting_enabled(bool p_enabled) {
Error err = NetSocketPosix::set_broadcasting_enabled(p_enabled);
if (err != OK)
return err;
if (p_enabled != wants_broadcast) {
if (p_enabled) {
multicast_lock_acquire();
} else {
multicast_lock_release();
}
wants_broadcast = p_enabled;
}
return OK;
}
Error NetSocketAndroid::join_multicast_group(const IP_Address &p_multi_address, String p_if_name) {
Error err = NetSocketPosix::join_multicast_group(p_multi_address, p_if_name);
if (err != OK)
return err;
if (!multicast_groups)
multicast_lock_acquire();
multicast_groups++;
return OK;
}
Error NetSocketAndroid::leave_multicast_group(const IP_Address &p_multi_address, String p_if_name) {
Error err = NetSocketPosix::leave_multicast_group(p_multi_address, p_if_name);
if (err != OK)
return err;
ERR_FAIL_COND_V(multicast_groups == 0, ERR_BUG);
multicast_groups--;
if (!multicast_groups)
multicast_lock_release();
return OK;
}

View file

@ -0,0 +1,78 @@
/*************************************************************************/
/* net_socket_android.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef NET_SOCKET_ANDROID_H
#define NET_SOCKET_ANDROID_H
#include "drivers/unix/net_socket_posix.h"
#include <jni.h>
/**
* Specialized NetSocket implementation for Android.
*
* Some devices requires Android-specific code to acquire a MulticastLock
* before sockets are allowed to receive broadcast and multicast packets.
* This implementation calls into Java code and automatically acquire/release
* the lock when broadcasting is enabled/disabled on a socket, or that socket
* joins/leaves a multicast group.
*/
class NetSocketAndroid : public NetSocketPosix {
private:
static jobject net_utils;
static jclass cls;
static jmethodID _multicast_lock_acquire;
static jmethodID _multicast_lock_release;
bool wants_broadcast;
int multicast_groups;
static void multicast_lock_acquire();
static void multicast_lock_release();
protected:
static NetSocket *_create_func();
public:
static void make_default();
static void setup(jobject p_net_utils);
virtual void close();
virtual Error set_broadcasting_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);
NetSocketAndroid();
~NetSocketAndroid();
};
#endif

View file

@ -43,6 +43,7 @@
#include "dir_access_jandroid.h"
#include "file_access_jandroid.h"
#include "net_socket_android.h"
#include <dlfcn.h>
@ -106,6 +107,8 @@ void OS_Android::initialize_core() {
DirAccess::make_default<DirAccessJAndroid>(DirAccess::ACCESS_RESOURCES);
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
NetSocketAndroid::make_default();
}
void OS_Android::set_opengl_extensions(const char *p_gl_extensions) {