From 148cf48d2b68354881066e2587e2673c91d2619a Mon Sep 17 00:00:00 2001 From: hrxi Date: Sat, 28 Dec 2019 03:11:51 +0100 Subject: [PATCH] Add libnice and listen for direct connections in Jingle SOCKS5 (#608) Add libnice as a plugin. If it is present, use libnice to enumerate local IP addresses and listen on them to support direct connections for Jingle SOCKS5. Tested with Conversations and Gajim. Created the nice.vapi file using ``` vapigen --library nice --pkg gio-2.0 --metadatadir metadata /usr/share/gir-1.0/Nice-0.1.gir ``` --- CMakeLists.txt | 2 +- cmake/FindNice.cmake | 13 + cmake/PkgConfigWithFallback.cmake | 2 +- libdino/src/service/connection_manager.vala | 2 + libdino/src/service/stream_interactor.vala | 5 +- plugins/CMakeLists.txt | 4 + plugins/ice/CMakeLists.txt | 30 ++ plugins/ice/src/plugin.vala | 30 ++ plugins/ice/src/register_plugin.vala | 3 + plugins/ice/vapi/metadata/Nice-0.1.metadata | 4 + plugins/ice/vapi/nice.vapi | 385 ++++++++++++++++++ .../module/xep/0065_socks5_bytestreams.vala | 17 +- .../xep/0260_jingle_socks5_bytestreams.vala | 217 +++++++++- 13 files changed, 698 insertions(+), 16 deletions(-) create mode 100644 cmake/FindNice.cmake create mode 100644 plugins/ice/CMakeLists.txt create mode 100644 plugins/ice/src/plugin.vala create mode 100644 plugins/ice/src/register_plugin.vala create mode 100644 plugins/ice/vapi/metadata/Nice-0.1.metadata create mode 100644 plugins/ice/vapi/nice.vapi diff --git a/CMakeLists.txt b/CMakeLists.txt index b738b585..2af0719b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ else () endif () # Prepare Plugins -set(DEFAULT_PLUGINS omemo;openpgp;http-files) +set(DEFAULT_PLUGINS omemo;openpgp;http-files;ice) foreach (plugin ${DEFAULT_PLUGINS}) if ("$CACHE{DINO_PLUGIN_ENABLED_${plugin}}" STREQUAL "") if (NOT DEFINED DINO_PLUGIN_ENABLED_${plugin}}) diff --git a/cmake/FindNice.cmake b/cmake/FindNice.cmake new file mode 100644 index 00000000..d40fc8c7 --- /dev/null +++ b/cmake/FindNice.cmake @@ -0,0 +1,13 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(Nice + PKG_CONFIG_NAME nice + LIB_NAMES nice + INCLUDE_NAMES nice.h + INCLUDE_DIR_SUFFIXES nice nice/include + DEPENDS GIO +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Nice + REQUIRED_VARS Nice_LIBRARY + VERSION_VAR Nice_VERSION) diff --git a/cmake/PkgConfigWithFallback.cmake b/cmake/PkgConfigWithFallback.cmake index ea14fa23..9124bb35 100644 --- a/cmake/PkgConfigWithFallback.cmake +++ b/cmake/PkgConfigWithFallback.cmake @@ -10,7 +10,7 @@ function(find_pkg_config_with_fallback name) endif(PKG_CONFIG_FOUND) if (${name}_PKG_CONFIG_FOUND) - # Found via pkg-config, using it's result values + # Found via pkg-config, using its result values set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND}) # Try to find real file name of libraries diff --git a/libdino/src/service/connection_manager.vala b/libdino/src/service/connection_manager.vala index e0f4e19c..454bcc2c 100644 --- a/libdino/src/service/connection_manager.vala +++ b/libdino/src/service/connection_manager.vala @@ -8,6 +8,7 @@ namespace Dino { public class ConnectionManager : Object { public signal void stream_opened(Account account, XmppStream stream); + public signal void stream_attached_modules(Account account, XmppStream stream); public signal void connection_state_changed(Account account, ConnectionState state); public signal void connection_error(Account account, ConnectionError error); @@ -225,6 +226,7 @@ public class ConnectionManager : Object { connections[account].established = new DateTime.now_utc(); stream.attached_modules.connect((stream) => { + stream_attached_modules(account, stream); change_connection_state(account, ConnectionState.CONNECTED); }); stream.get_module(Sasl.Module.IDENTITY).received_auth_failure.connect((stream, node) => { diff --git a/libdino/src/service/stream_interactor.vala b/libdino/src/service/stream_interactor.vala index e60a43d6..192460d4 100644 --- a/libdino/src/service/stream_interactor.vala +++ b/libdino/src/service/stream_interactor.vala @@ -11,7 +11,7 @@ public class StreamInteractor : Object { public signal void account_removed(Account account); public signal void stream_resumed(Account account, XmppStream stream); public signal void stream_negotiated(Account account, XmppStream stream); - public signal void attached_modules(Account account, XmppStream stream); + public signal void stream_attached_modules(Account account, XmppStream stream); public ModuleManager module_manager; public ConnectionManager connection_manager; @@ -22,6 +22,9 @@ public class StreamInteractor : Object { connection_manager = new ConnectionManager(module_manager); connection_manager.stream_opened.connect(on_stream_opened); + connection_manager.stream_attached_modules.connect((account, stream) => { + stream_attached_modules(account, stream); + }); } public void connect_account(Account account) { diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6cccec3b..48593c7a 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -2,6 +2,10 @@ if(DINO_PLUGIN_ENABLED_http-files) add_subdirectory(http-files) endif(DINO_PLUGIN_ENABLED_http-files) +if(DINO_PLUGIN_ENABLED_ice) + add_subdirectory(ice) +endif(DINO_PLUGIN_ENABLED_ice) + if(DINO_PLUGIN_ENABLED_openpgp) add_subdirectory(gpgme-vala) add_subdirectory(openpgp) diff --git a/plugins/ice/CMakeLists.txt b/plugins/ice/CMakeLists.txt new file mode 100644 index 00000000..76dba28f --- /dev/null +++ b/plugins/ice/CMakeLists.txt @@ -0,0 +1,30 @@ +find_packages(ICE_PACKAGES REQUIRED + Gee + GLib + GModule + GObject + GTK3 + Nice +) + +vala_precompile(ICE_VALA_C +SOURCES + src/plugin.vala + src/register_plugin.vala +CUSTOM_VAPIS + ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi + ${CMAKE_BINARY_DIR}/exports/dino.vapi + ${CMAKE_BINARY_DIR}/exports/qlite.vapi +PACKAGES + ${ICE_PACKAGES} +OPTIONS + --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi +) + +add_definitions(${VALA_CFLAGS}) +add_library(ice SHARED ${ICE_VALA_C}) +target_link_libraries(ice libdino ${ICE_PACKAGES}) +set_target_properties(ice PROPERTIES PREFIX "") +set_target_properties(ice PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) + +install(TARGETS ice ${PLUGIN_INSTALL}) diff --git a/plugins/ice/src/plugin.vala b/plugins/ice/src/plugin.vala new file mode 100644 index 00000000..f1c41a27 --- /dev/null +++ b/plugins/ice/src/plugin.vala @@ -0,0 +1,30 @@ +using Gee; +using Nice; +using Xmpp; + +namespace Dino.Plugins.Ice { + +public class Plugin : RootInterface, Object { + public Dino.Application app; + + public void registered(Dino.Application app) { + this.app = app; + app.stream_interactor.stream_attached_modules.connect((account, stream) => { + stream.get_module(Xmpp.Xep.Socks5Bytestreams.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses); + }); + } + + private Gee.List get_local_ip_addresses() { + Gee.List result = new ArrayList(); + foreach (string ip_address in Nice.interfaces_get_local_ips(false)) { + result.add(ip_address); + } + return result; + } + + public void shutdown() { + // Nothing to do + } +} + +} diff --git a/plugins/ice/src/register_plugin.vala b/plugins/ice/src/register_plugin.vala new file mode 100644 index 00000000..b2ed56c1 --- /dev/null +++ b/plugins/ice/src/register_plugin.vala @@ -0,0 +1,3 @@ +public Type register_plugin(Module module) { + return typeof (Dino.Plugins.Ice.Plugin); +} diff --git a/plugins/ice/vapi/metadata/Nice-0.1.metadata b/plugins/ice/vapi/metadata/Nice-0.1.metadata new file mode 100644 index 00000000..437da816 --- /dev/null +++ b/plugins/ice/vapi/metadata/Nice-0.1.metadata @@ -0,0 +1,4 @@ +Nice cheader_filename="nice.h" +Agent.new_reliable#constructor name="create_reliable" +PseudoTcpCallbacks#record skip +PseudoTcpSocket#class skip diff --git a/plugins/ice/vapi/nice.vapi b/plugins/ice/vapi/nice.vapi new file mode 100644 index 00000000..aa45cf08 --- /dev/null +++ b/plugins/ice/vapi/nice.vapi @@ -0,0 +1,385 @@ +/* nice.vapi generated by vapigen, do not modify. */ + +[CCode (cprefix = "Nice", gir_namespace = "Nice", gir_version = "0.1", lower_case_cprefix = "nice_")] +namespace Nice { + [CCode (cheader_filename = "nice.h", type_id = "nice_agent_get_type ()")] + public class Agent : GLib.Object { + [CCode (has_construct_function = false)] + public Agent (GLib.MainContext ctx, Nice.Compatibility compat); + public bool add_local_address (Nice.Address addr); + public uint add_stream (uint n_components); + [Version (since = "0.1.16")] + public async void close_async (); + [CCode (cname = "nice_agent_new_reliable", has_construct_function = false)] + [Version (since = "0.0.11")] + public Agent.create_reliable (GLib.MainContext ctx, Nice.Compatibility compat); + [Version (since = "0.1.6")] + public bool forget_relays (uint stream_id, uint component_id); + [CCode (has_construct_function = false)] + [Version (since = "0.1.15")] + public Agent.full (GLib.MainContext ctx, Nice.Compatibility compat, Nice.AgentOption flags); + public bool gather_candidates (uint stream_id); + [Version (since = "0.1.4")] + public string generate_local_candidate_sdp (Nice.Candidate candidate); + [Version (since = "0.1.4")] + public string generate_local_sdp (); + [Version (since = "0.1.4")] + public string generate_local_stream_sdp (uint stream_id, bool include_non_ice); + [Version (since = "0.1.8")] + public Nice.ComponentState get_component_state (uint stream_id, uint component_id); + public Nice.Candidate get_default_local_candidate (uint stream_id, uint component_id); + [Version (since = "0.1.5")] + public GLib.IOStream get_io_stream (uint stream_id, uint component_id); + public GLib.SList get_local_candidates (uint stream_id, uint component_id); + public bool get_local_credentials (uint stream_id, out string ufrag, out string pwd); + public GLib.SList get_remote_candidates (uint stream_id, uint component_id); + public bool get_selected_pair (uint stream_id, uint component_id, Nice.Candidate local, Nice.Candidate remote); + [Version (since = "0.1.5")] + public GLib.Socket? get_selected_socket (uint stream_id, uint component_id); + [Version (since = "0.1.4")] + public unowned string get_stream_name (uint stream_id); + [Version (since = "0.1.4")] + public Nice.Candidate parse_remote_candidate_sdp (uint stream_id, string sdp); + [Version (since = "0.1.4")] + public int parse_remote_sdp (string sdp); + [Version (since = "0.1.4")] + public GLib.SList parse_remote_stream_sdp (uint stream_id, string sdp, string ufrag, string pwd); + [Version (since = "0.1.16")] + public bool peer_candidate_gathering_done (uint stream_id); + [Version (since = "0.1.5")] + public ssize_t recv (uint stream_id, uint component_id, [CCode (array_length_cname = "buf_len", array_length_pos = 3.5, array_length_type = "gsize")] out unowned uint8[] buf, GLib.Cancellable? cancellable = null) throws GLib.Error; + [Version (since = "0.1.5")] + public int recv_messages (uint stream_id, uint component_id, [CCode (array_length_cname = "n_messages", array_length_pos = 3.5, array_length_type = "guint")] out unowned Nice.InputMessage[] messages, GLib.Cancellable? cancellable = null) throws GLib.Error; + [Version (since = "0.1.5")] + public int recv_messages_nonblocking (uint stream_id, uint component_id, [CCode (array_length_cname = "n_messages", array_length_pos = 3.5, array_length_type = "guint")] out unowned Nice.InputMessage[] messages, GLib.Cancellable? cancellable = null) throws GLib.Error; + [Version (since = "0.1.5")] + public ssize_t recv_nonblocking (uint stream_id, uint component_id, [CCode (array_length_cname = "buf_len", array_length_pos = 3.5, array_length_type = "gsize")] out unowned uint8[] buf, GLib.Cancellable? cancellable = null) throws GLib.Error; + public void remove_stream (uint stream_id); + public bool restart (); + [Version (since = "0.1.6")] + public bool restart_stream (uint stream_id); + public int send (uint stream_id, uint component_id, uint len, string buf); + [Version (since = "0.1.5")] + public int send_messages_nonblocking (uint stream_id, uint component_id, [CCode (array_length_cname = "n_messages", array_length_pos = 3.5, array_length_type = "guint")] Nice.OutputMessage[] messages, GLib.Cancellable? cancellable = null) throws GLib.Error; + public bool set_local_credentials (uint stream_id, string ufrag, string pwd); + public void set_port_range (uint stream_id, uint component_id, uint min_port, uint max_port); + public bool set_relay_info (uint stream_id, uint component_id, string server_ip, uint server_port, string username, string password, Nice.RelayType type); + public int set_remote_candidates (uint stream_id, uint component_id, GLib.SList candidates); + public bool set_remote_credentials (uint stream_id, string ufrag, string pwd); + public bool set_selected_pair (uint stream_id, uint component_id, string lfoundation, string rfoundation); + public bool set_selected_remote_candidate (uint stream_id, uint component_id, Nice.Candidate candidate); + [Version (since = "0.0.10")] + public void set_software (string software); + [Version (since = "0.1.4")] + public bool set_stream_name (uint stream_id, string name); + [Version (since = "0.0.9")] + public void set_stream_tos (uint stream_id, int tos); + [NoAccessorMethod] + [Version (since = "0.1.8")] + public bool bytestream_tcp { get; } + [NoAccessorMethod] + public uint compatibility { get; construct; } + [NoAccessorMethod] + public bool controlling_mode { get; set; } + [NoAccessorMethod] + [Version (since = "0.1.14")] + public bool force_relay { get; set; } + [NoAccessorMethod] + public bool full_mode { get; construct; } + [NoAccessorMethod] + [Version (since = "0.1.8")] + public bool ice_tcp { get; set; } + [NoAccessorMethod] + [Version (since = "0.1.16")] + public bool ice_trickle { get; set; } + [NoAccessorMethod] + [Version (since = "0.1.8")] + public bool ice_udp { get; set; } + [NoAccessorMethod] + [Version (since = "0.1.8")] + public bool keepalive_conncheck { get; set; } + [NoAccessorMethod] + public void* main_context { get; construct; } + [NoAccessorMethod] + public uint max_connectivity_checks { get; set; } + [NoAccessorMethod] + [Version (since = "0.0.4")] + public string proxy_ip { owned get; set; } + [NoAccessorMethod] + [Version (since = "0.0.4")] + public string proxy_password { owned get; set; } + [NoAccessorMethod] + [Version (since = "0.0.4")] + public uint proxy_port { get; set; } + [NoAccessorMethod] + [Version (since = "0.0.4")] + public uint proxy_type { get; set; } + [NoAccessorMethod] + [Version (since = "0.0.4")] + public string proxy_username { owned get; set; } + [NoAccessorMethod] + [Version (since = "0.0.11")] + public bool reliable { get; construct; } + [NoAccessorMethod] + [Version (since = "0.1.15")] + public uint stun_initial_timeout { get; set construct; } + [NoAccessorMethod] + [Version (since = "0.1.15")] + public uint stun_max_retransmissions { get; set construct; } + [NoAccessorMethod] + public uint stun_pacing_timer { get; set construct; } + [NoAccessorMethod] + [Version (since = "0.1.15")] + public uint stun_reliable_timeout { get; set construct; } + [NoAccessorMethod] + public string stun_server { owned get; set; } + [NoAccessorMethod] + public uint stun_server_port { get; set; } + [NoAccessorMethod] + public bool support_renomination { get; set; } + [NoAccessorMethod] + [Version (since = "0.0.7")] + public bool upnp { get; set construct; } + [NoAccessorMethod] + [Version (since = "0.0.7")] + public uint upnp_timeout { get; set construct; } + public signal void candidate_gathering_done (uint stream_id); + public signal void component_state_changed (uint stream_id, uint component_id, uint state); + public signal void initial_binding_request_received (uint stream_id); + [Version (deprecated = true, deprecated_since = "0.1.8")] + public signal void new_candidate (uint stream_id, uint component_id, string foundation); + [Version (since = "0.1.8")] + public signal void new_candidate_full (Nice.Candidate candidate); + [Version (deprecated = true, deprecated_since = "0.1.8")] + public signal void new_remote_candidate (uint stream_id, uint component_id, string foundation); + [Version (since = "0.1.8")] + public signal void new_remote_candidate_full (Nice.Candidate candidate); + [Version (deprecated = true, deprecated_since = "0.1.8")] + public signal void new_selected_pair (uint stream_id, uint component_id, string lfoundation, string rfoundation); + [Version (since = "0.1.8")] + public signal void new_selected_pair_full (uint stream_id, uint component_id, Nice.Candidate lcandidate, Nice.Candidate rcandidate); + [Version (since = "0.0.11")] + public signal void reliable_transport_writable (uint stream_id, uint component_id); + [Version (since = "0.1.5")] + public signal void streams_removed ([CCode (array_length = false, array_null_terminated = true)] uint[] stream_ids); + } + [CCode (cheader_filename = "nice.h", copy_function = "g_boxed_copy", free_function = "g_boxed_free", type_id = "nice_candidate_get_type ()")] + [Compact] + public class Candidate { + public Nice.Address addr; + public Nice.Address base_addr; + public uint component_id; + [CCode (array_length = false)] + public weak char foundation[33]; + public weak string password; + public uint32 priority; + public void* sockptr; + public uint stream_id; + public Nice.CandidateTransport transport; + public Nice.TurnServer turn; + public Nice.CandidateType type; + public weak string username; + [CCode (has_construct_function = false)] + public Candidate (Nice.CandidateType type); + public Nice.Candidate copy (); + [Version (since = "0.1.15")] + public bool equal_target (Nice.Candidate candidate2); + public void free (); + } + [CCode (cheader_filename = "nice.h", has_type_id = false)] + public struct Address { + [CCode (cname = "s.addr")] + public void* s_addr; + [CCode (cname = "s.ip4")] + public void* s_ip4; + [CCode (cname = "s.ip6")] + public void* s_ip6; + public void copy_to_sockaddr (void* sin); + public bool equal (Nice.Address b); + [Version (since = "0.1.8")] + public bool equal_no_port (Nice.Address b); + public void free (); + public uint get_port (); + public void init (); + public int ip_version (); + public bool is_private (); + public bool is_valid (); + public void set_from_sockaddr (void* sin); + public bool set_from_string (string str); + public void set_ipv4 (uint32 addr_ipv4); + public void set_ipv6 (uint8 addr_ipv6); + public void set_port (uint port); + public void to_string (string dst); + } + [CCode (cheader_filename = "nice.h", has_type_id = false)] + [Version (since = "0.1.5")] + public struct InputMessage { + [CCode (array_length_cname = "n_buffers")] + public weak GLib.InputVector[] buffers; + public int n_buffers; + public Nice.Address from; + public size_t length; + } + [CCode (cheader_filename = "nice.h", has_type_id = false)] + [Version (since = "0.1.5")] + public struct OutputMessage { + [CCode (array_length_cname = "n_buffers")] + public weak GLib.OutputVector[] buffers; + public int n_buffers; + } + [CCode (cheader_filename = "nice.h", cname = "TurnServer", has_type_id = false)] + public struct TurnServer { + public int ref_count; + public Nice.Address server; + public weak string username; + public weak string password; + public Nice.RelayType type; + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_AGENT_OPTION_", has_type_id = false)] + [Flags] + [Version (since = "0.1.15")] + public enum AgentOption { + REGULAR_NOMINATION, + RELIABLE, + LITE_MODE, + ICE_TRICKLE, + SUPPORT_RENOMINATION + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_CANDIDATE_TRANSPORT_", has_type_id = false)] + public enum CandidateTransport { + UDP, + TCP_ACTIVE, + TCP_PASSIVE, + TCP_SO + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_CANDIDATE_TYPE_", has_type_id = false)] + public enum CandidateType { + HOST, + SERVER_REFLEXIVE, + PEER_REFLEXIVE, + RELAYED + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_COMPATIBILITY_", has_type_id = false)] + public enum Compatibility { + RFC5245, + DRAFT19, + GOOGLE, + MSN, + WLM2009, + OC2007, + OC2007R2, + LAST + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_COMPONENT_STATE_", has_type_id = false)] + public enum ComponentState { + DISCONNECTED, + GATHERING, + CONNECTING, + CONNECTED, + READY, + FAILED, + LAST; + [Version (since = "0.1.6")] + public unowned string to_string (); + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_COMPONENT_TYPE_", has_type_id = false)] + public enum ComponentType { + RTP, + RTCP + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_NOMINATION_MODE_", has_type_id = false)] + [Version (since = "0.1.15")] + public enum NominationMode { + REGULAR, + AGGRESSIVE + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_PROXY_TYPE_", has_type_id = false)] + [Version (since = "0.0.4")] + public enum ProxyType { + NONE, + SOCKS5, + HTTP, + LAST + } + [CCode (cheader_filename = "nice.h", cname = "PseudoTcpDebugLevel", cprefix = "PSEUDO_TCP_DEBUG_", has_type_id = false)] + [Version (since = "0.0.11")] + public enum PseudoTcpDebugLevel { + NONE, + NORMAL, + VERBOSE + } + [CCode (cheader_filename = "nice.h", cname = "PseudoTcpShutdown", cprefix = "PSEUDO_TCP_SHUTDOWN_", has_type_id = false)] + [Version (since = "0.1.8")] + public enum PseudoTcpShutdown { + RD, + WR, + RDWR + } + [CCode (cheader_filename = "nice.h", cname = "PseudoTcpState", cprefix = "PSEUDO_TCP_", has_type_id = false)] + [Version (since = "0.0.11")] + public enum PseudoTcpState { + LISTEN, + SYN_SENT, + SYN_RECEIVED, + ESTABLISHED, + CLOSED, + FIN_WAIT_1, + FIN_WAIT_2, + CLOSING, + TIME_WAIT, + CLOSE_WAIT, + LAST_ACK + } + [CCode (cheader_filename = "nice.h", cname = "PseudoTcpWriteResult", cprefix = "WR_", has_type_id = false)] + [Version (since = "0.0.11")] + public enum PseudoTcpWriteResult { + SUCCESS, + TOO_LARGE, + FAIL + } + [CCode (cheader_filename = "nice.h", cprefix = "NICE_RELAY_TYPE_TURN_", has_type_id = false)] + public enum RelayType { + UDP, + TCP, + TLS + } + [CCode (cheader_filename = "nice.h", instance_pos = 5.9)] + public delegate void AgentRecvFunc (Nice.Agent agent, uint stream_id, uint component_id, uint len, string buf); + [CCode (cheader_filename = "nice.h", cname = "NICE_AGENT_MAX_REMOTE_CANDIDATES")] + public const int AGENT_MAX_REMOTE_CANDIDATES; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_DIRECTION_MS_PREF_ACTIVE")] + public const int CANDIDATE_DIRECTION_MS_PREF_ACTIVE; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_DIRECTION_MS_PREF_PASSIVE")] + public const int CANDIDATE_DIRECTION_MS_PREF_PASSIVE; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_MAX_FOUNDATION")] + public const int CANDIDATE_MAX_FOUNDATION; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TRANSPORT_MS_PREF_TCP")] + public const int CANDIDATE_TRANSPORT_MS_PREF_TCP; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TRANSPORT_MS_PREF_UDP")] + public const int CANDIDATE_TRANSPORT_MS_PREF_UDP; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_HOST")] + public const int CANDIDATE_TYPE_PREF_HOST; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_NAT_ASSISTED")] + public const int CANDIDATE_TYPE_PREF_NAT_ASSISTED; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_PEER_REFLEXIVE")] + public const int CANDIDATE_TYPE_PREF_PEER_REFLEXIVE; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_RELAYED")] + public const int CANDIDATE_TYPE_PREF_RELAYED; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_RELAYED_UDP")] + public const int CANDIDATE_TYPE_PREF_RELAYED_UDP; + [CCode (cheader_filename = "nice.h", cname = "NICE_CANDIDATE_TYPE_PREF_SERVER_REFLEXIVE")] + public const int CANDIDATE_TYPE_PREF_SERVER_REFLEXIVE; + [CCode (cheader_filename = "nice.h")] + public static void debug_disable (bool with_stun); + [CCode (cheader_filename = "nice.h")] + public static void debug_enable (bool with_stun); + [CCode (cheader_filename = "nice.h")] + public static string? interfaces_get_ip_for_interface (string interface_name); + [CCode (cheader_filename = "nice.h")] + public static GLib.List interfaces_get_local_interfaces (); + [CCode (cheader_filename = "nice.h")] + public static GLib.List interfaces_get_local_ips (bool include_loopback); + [CCode (cheader_filename = "nice.h", cname = "pseudo_tcp_set_debug_level")] + [Version (since = "0.0.11")] + public static void pseudo_tcp_set_debug_level (Nice.PseudoTcpDebugLevel level); +} diff --git a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala index fdee2411..c184877c 100644 --- a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala @@ -18,21 +18,34 @@ public class Proxy : Object { } } +public delegate Gee.List GetLocalIpAddresses(); + public class Module : XmppStreamModule, Iq.Handler { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0065_socks5_bytestreams"); + private GetLocalIpAddresses? get_local_ip_addresses_impl = null; + public override void attach(XmppStream stream) { stream.add_flag(new Flag()); query_availability.begin(stream); } public override void detach(XmppStream stream) { } - public async void on_iq_set(XmppStream stream, Iq.Stanza iq) { } - public Gee.List get_proxies(XmppStream stream) { return stream.get_flag(Flag.IDENTITY).proxies; } + public void set_local_ip_address_handler(owned GetLocalIpAddresses get_local_ip_addresses) { + get_local_ip_addresses_impl = (owned)get_local_ip_addresses; + } + + public Gee.List get_local_ip_addresses() { + if (get_local_ip_addresses_impl == null) { + return Gee.List.empty(); + } + return get_local_ip_addresses_impl(); + } + private async void query_availability(XmppStream stream) { ServiceDiscovery.ItemsResult? items_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name); if (items_result == null) return; diff --git a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala index ea7ef375..bb31cadc 100644 --- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala @@ -5,6 +5,7 @@ using Xmpp.Xep; namespace Xmpp.Xep.JingleSocks5Bytestreams { private const string NS_URI = "urn:xmpp:jingle:transports:s5b:1"; +private const int NEGOTIATION_TIMEOUT = 3; public class Module : Jingle.Transport, XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0260_jingle_socks5_bytestreams"); @@ -33,7 +34,7 @@ public class Module : Jingle.Transport, XmppStreamModule { public int transport_priority() { return 1; } - private Gee.List get_local_candidates(XmppStream stream) { + private Gee.List get_proxies(XmppStream stream) { Gee.List result = new ArrayList(); int i = 1 << 15; foreach (Socks5Bytestreams.Proxy proxy in stream.get_module(Socks5Bytestreams.Module.IDENTITY).get_proxies(stream)) { @@ -42,18 +43,59 @@ public class Module : Jingle.Transport, XmppStreamModule { } return result; } + private Gee.List start_local_listeners(XmppStream stream, Jid local_full_jid, string dstaddr, out LocalListener? local_listener) { + Gee.List result = new ArrayList(); + SocketListener listener = new SocketListener(); + int i = 1 << 15; + foreach (string ip_address in stream.get_module(Socks5Bytestreams.Module.IDENTITY).get_local_ip_addresses()) { + InetSocketAddress addr = new InetSocketAddress.from_string(ip_address, 0); + SocketAddress effective_any; + string cid = random_uuid(); + try { + listener.add_address(addr, SocketType.STREAM, SocketProtocol.DEFAULT, new StringWrapper(cid), out effective_any); + } catch (Error e) { + continue; + } + InetSocketAddress effective = (InetSocketAddress)effective_any; + result.add(new Candidate.build(cid, ip_address, local_full_jid, (int)effective.port, i, CandidateType.DIRECT)); + i -= 1; + } + if (!result.is_empty) { + local_listener = new LocalListener(listener, dstaddr); + local_listener.start(); + } else { + local_listener = new LocalListener.empty(); + } + return result; + } + private void select_candidates(XmppStream stream, Jid local_full_jid, string dstaddr, Parameters result) { + result.local_candidates.add_all(get_proxies(stream)); + result.local_candidates.add_all(start_local_listeners(stream, local_full_jid, dstaddr, out result.listener)); + result.local_candidates.sort((c1, c2) => { + if (c1.priority < c2.priority) { return 1; } + if (c1.priority > c2.priority) { return -1; } + return 0; + }); + } public Jingle.TransportParameters create_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid) { Parameters result = new Parameters.create(local_full_jid, peer_full_jid, random_uuid()); - result.local_candidates.add_all(get_local_candidates(stream)); + string dstaddr = calculate_dstaddr(result.sid, local_full_jid, peer_full_jid); + select_candidates(stream, local_full_jid, dstaddr, result); return result; } public Jingle.TransportParameters parse_transport_parameters(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws Jingle.IqError { Parameters result = Parameters.parse(local_full_jid, peer_full_jid, transport); - result.local_candidates.add_all(get_local_candidates(stream)); + string dstaddr = calculate_dstaddr(result.sid, local_full_jid, peer_full_jid); + select_candidates(stream, local_full_jid, dstaddr, result); return result; } } +private string calculate_dstaddr(string sid, Jid first_jid, Jid second_jid) { + string hashed = sid + first_jid.to_string() + second_jid.to_string(); + return Checksum.compute_for_string(ChecksumType.SHA1, hashed); +} + public enum CandidateType { ASSISTED, DIRECT, @@ -156,6 +198,142 @@ bool bytes_equal(uint8[] a, uint8[] b) { return true; } +class StringWrapper : GLib.Object { + public string str { get; set; } + + public StringWrapper(string str) { + this.str = str; + } +} + +class LocalListener { + SocketListener? inner; + string dstaddr; + HashMap connections = new HashMap(); + + public LocalListener(SocketListener inner, string dstaddr) { + this.inner = inner; + this.dstaddr = dstaddr; + } + public LocalListener.empty() { + this.inner = null; + this.dstaddr = ""; + } + + public void start() { + if (inner == null) { + return; + } + run.begin(); + } + async void run() { + while (true) { + Object cid; + SocketConnection conn; + try { + conn = yield inner.accept_async(null, out cid); + } catch (Error e) { + break; + } + handle_conn.begin(((StringWrapper)cid).str, conn); + } + } + async void handle_conn(string cid, SocketConnection conn) { + conn.socket.timeout = NEGOTIATION_TIMEOUT; + size_t read; + size_t written; + uint8[] read_buffer = new uint8[1024]; + ByteArray write_buffer = new ByteArray(); + + try { + // 05 SOCKS version 5 + // ?? number of authentication methods + yield conn.input_stream.read_all_async(read_buffer[0:2], GLib.Priority.DEFAULT, null, out read); + if (read != 2) { + throw new IOError.PROXY_FAILED("wanted client hello message consisting of 2 bytes, only got %d bytes".printf((int)read)); + } + if (read_buffer[0] != 0x05 || read_buffer[1] == 0) { + throw new IOError.PROXY_FAILED("wanted 05 xx, got %02x %02x".printf(read_buffer[0], read_buffer[1])); + } + int num_auth_methods = read_buffer[1]; + // ?? authentication method (num_auth_methods times) + yield conn.input_stream.read_all_async(read_buffer[0:num_auth_methods], GLib.Priority.DEFAULT, null, out read); + bool found_null_auth = false; + for (int i = 0; i < read; i++) { + if (read_buffer[i] == 0x00) { + found_null_auth = true; + break; + } + } + if (read != num_auth_methods || !found_null_auth) { + throw new IOError.PROXY_FAILED("peer didn't offer null auth"); + } + // 05 SOCKS version 5 + // 00 nop authentication + yield conn.output_stream.write_all_async({0x05, 0x00}, GLib.Priority.DEFAULT, null, out written); + + // 05 SOCKS version 5 + // 01 connect + // 00 reserved + // 03 address type: domain name + // ?? length of the domain + // .. domain + // 00 port 0 (upper half) + // 00 port 0 (lower half) + yield conn.input_stream.read_all_async(read_buffer[0:4], GLib.Priority.DEFAULT, null, out read); + if (read != 4) { + throw new IOError.PROXY_FAILED("wanted connect message consisting of 4 bytes, only got %d bytes".printf((int)read)); + } + if (read_buffer[0] != 0x05 || read_buffer[1] != 0x01 || read_buffer[3] != 0x03) { + throw new IOError.PROXY_FAILED("wanted 05 00 ?? 03, got %02x %02x %02x %02x".printf(read_buffer[0], read_buffer[1], read_buffer[2], read_buffer[3])); + } + yield conn.input_stream.read_all_async(read_buffer[0:1], GLib.Priority.DEFAULT, null, out read); + if (read != 1) { + throw new IOError.PROXY_FAILED("wanted length of dstaddr consisting of 1 byte, only got %d bytes".printf((int)read)); + } + int dstaddr_len = read_buffer[0]; + yield conn.input_stream.read_all_async(read_buffer[0:dstaddr_len+2], GLib.Priority.DEFAULT, null, out read); + if (read != dstaddr_len + 2) { + throw new IOError.PROXY_FAILED("wanted dstaddr and port consisting of %d bytes, got %d bytes".printf(dstaddr_len + 2, (int)read)); + } + if (!bytes_equal(read_buffer[0:dstaddr_len], dstaddr.data)) { + string repr = ((string)read_buffer[0:dstaddr.length]).make_valid().escape(); + throw new IOError.PROXY_FAILED(@"wanted dstaddr $(dstaddr), got $(repr)"); + } + if (read_buffer[dstaddr_len] != 0x00 || read_buffer[dstaddr_len + 1] != 0x00) { + throw new IOError.PROXY_FAILED("wanted 00 00, got %02x %02x".printf(read_buffer[dstaddr_len], read_buffer[dstaddr_len + 1])); + } + + // 05 SOCKS version 5 + // 00 success + // 00 reserved + // 03 address type: domain name + // ?? length of the domain + // .. domain + // 00 port 0 (upper half) + // 00 port 0 (lower half) + write_buffer.append({0x05, 0x00, 0x00, 0x03}); + write_buffer.append({(uint8)dstaddr.length}); + write_buffer.append(dstaddr.data); + write_buffer.append({0x00, 0x00}); + yield conn.output_stream.write_all_async(write_buffer.data, GLib.Priority.DEFAULT, null, out written); + + conn.socket.timeout = 0; + if (!connections.has_key(cid)) { + connections[cid] = conn; + } + } catch (Error e) { + } + } + + public SocketConnection? get_connection(string cid) { + if (!connections.has_key(cid)) { + return null; + } + return connections[cid]; + } +} + class Parameters : Jingle.TransportParameters, Object { public Jingle.Role role { get; private set; } public string sid { get; private set; } @@ -163,6 +341,7 @@ class Parameters : Jingle.TransportParameters, Object { public string local_dstaddr { get; private set; } public Gee.List local_candidates = new ArrayList(); public Gee.List remote_candidates = new ArrayList(); + public LocalListener? listener = null; Jid local_full_jid; Jid peer_full_jid; @@ -179,10 +358,6 @@ class Parameters : Jingle.TransportParameters, Object { SourceFunc waiting_for_activation_callback; bool waiting_for_activation_error = false; - private static string calculate_dstaddr(string sid, Jid first_jid, Jid second_jid) { - string hashed = sid + first_jid.to_string() + second_jid.to_string(); - return Checksum.compute_for_string(ChecksumType.SHA1, hashed); - } private Parameters(Jingle.Role role, string sid, Jid local_full_jid, Jid peer_full_jid, string? remote_dstaddr) { this.role = role; this.sid = sid; @@ -355,7 +530,21 @@ class Parameters : Jingle.TransportParameters, Object { wait_for_remote_activation.begin(local_selected_candidate, local_selected_candidate_conn); } } else { - connect_to_local_candidate.begin(remote_selected_candidate); + if (remote_selected_candidate.type_ == CandidateType.DIRECT) { + Jingle.Session? strong = session; + if (strong == null) { + return; + } + SocketConnection? conn = listener.get_connection(remote_selected_candidate.cid); + if (conn == null) { + // Remote hasn't actually connected to us?! + strong.set_transport_connection(hack, null); + return; + } + strong.set_transport_connection(hack, conn); + } else { + connect_to_local_candidate.begin(remote_selected_candidate); + } } } public async void wait_for_remote_activation(Candidate candidate, SocketConnection conn) { @@ -425,7 +614,7 @@ class Parameters : Jingle.TransportParameters, Object { } } public async SocketConnection connect_to_socks5(Candidate candidate, string dstaddr) throws Error { - SocketClient socket_client = new SocketClient() { timeout=3 }; + SocketClient socket_client = new SocketClient() { timeout=NEGOTIATION_TIMEOUT }; string address = @"[$(candidate.host)]:$(candidate.port)"; debug("Connecting to SOCKS5 server at %s", address); @@ -444,7 +633,10 @@ class Parameters : Jingle.TransportParameters, Object { yield conn.input_stream.read_all_async(read_buffer[0:2], GLib.Priority.DEFAULT, null, out read); // 05 SOCKS version 5 - // 01 success + // 00 nop authentication + if (read != 2) { + throw new IOError.PROXY_FAILED("wanted 05 00, only got %d bytes".printf((int)read)); + } if (read_buffer[0] != 0x05 || read_buffer[1] != 0x00) { throw new IOError.PROXY_FAILED("wanted 05 00, got %02x %02x".printf(read_buffer[0], read_buffer[1])); } @@ -472,6 +664,9 @@ class Parameters : Jingle.TransportParameters, Object { // .. domain // 00 port 0 (upper half) // 00 port 0 (lower half) + if (read != write_buffer.len) { + throw new IOError.PROXY_FAILED("wanted server success response consisting of %d bytes, only got %d bytes".printf((int)write_buffer.len, (int)read)); + } if (read_buffer[0] != 0x05 || read_buffer[1] != 0x00 || read_buffer[3] != 0x03) { throw new IOError.PROXY_FAILED("wanted 05 00 ?? 03, got %02x %02x %02x %02x".printf(read_buffer[0], read_buffer[1], read_buffer[2], read_buffer[3])); } @@ -486,7 +681,7 @@ class Parameters : Jingle.TransportParameters, Object { throw new IOError.PROXY_FAILED("wanted port 00 00, got %02x %02x".printf(read_buffer[5+dstaddr.length], read_buffer[5+dstaddr.length+1])); } - conn.get_socket().set_timeout(0); + conn.socket.timeout = 0; return conn; }