From 148cf48d2b68354881066e2587e2673c91d2619a Mon Sep 17 00:00:00 2001 From: hrxi Date: Sat, 28 Dec 2019 03:11:51 +0100 Subject: 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 ``` --- plugins/ice/vapi/metadata/Nice-0.1.metadata | 4 + plugins/ice/vapi/nice.vapi | 385 ++++++++++++++++++++++++++++ 2 files changed, 389 insertions(+) create mode 100644 plugins/ice/vapi/metadata/Nice-0.1.metadata create mode 100644 plugins/ice/vapi/nice.vapi (limited to 'plugins/ice/vapi') 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); +} -- cgit v1.2.3-70-g09d2 From d703b7c09d5eea81ec383fd09c9d320199e6d577 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 21 Mar 2021 12:41:27 +0100 Subject: Add libnice-based ICE-UDP implementation as plugin --- plugins/ice/CMakeLists.txt | 5 +- plugins/ice/src/module.vala | 42 +++++ plugins/ice/src/plugin.vala | 63 +++++-- plugins/ice/src/transport_parameters.vala | 261 ++++++++++++++++++++++++++++ plugins/ice/src/util.vala | 18 ++ plugins/ice/vapi/metadata/Nice-0.1.metadata | 4 + plugins/ice/vapi/nice.vapi | 9 +- 7 files changed, 386 insertions(+), 16 deletions(-) create mode 100644 plugins/ice/src/module.vala create mode 100644 plugins/ice/src/transport_parameters.vala create mode 100644 plugins/ice/src/util.vala (limited to 'plugins/ice/vapi') diff --git a/plugins/ice/CMakeLists.txt b/plugins/ice/CMakeLists.txt index 76dba28f..90fe5b7d 100644 --- a/plugins/ice/CMakeLists.txt +++ b/plugins/ice/CMakeLists.txt @@ -10,6 +10,9 @@ find_packages(ICE_PACKAGES REQUIRED vala_precompile(ICE_VALA_C SOURCES src/plugin.vala + src/module.vala + src/transport_parameters.vala + src/util.vala src/register_plugin.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi @@ -21,7 +24,7 @@ OPTIONS --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi ) -add_definitions(${VALA_CFLAGS}) +add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="ice") add_library(ice SHARED ${ICE_VALA_C}) target_link_libraries(ice libdino ${ICE_PACKAGES}) set_target_properties(ice PROPERTIES PREFIX "") diff --git a/plugins/ice/src/module.vala b/plugins/ice/src/module.vala new file mode 100644 index 00000000..e961ffb6 --- /dev/null +++ b/plugins/ice/src/module.vala @@ -0,0 +1,42 @@ +using Gee; +using Xmpp; +using Xmpp.Xep; + +public class Dino.Plugins.Ice.Module : JingleIceUdp.Module { + + public string? stun_ip = null; + public uint stun_port = 0; + public string? turn_ip = null; + public Xep.ExternalServiceDiscovery.Service? turn_service = null; + + private weak Nice.Agent? agent; + + private Nice.Agent get_agent() { + Nice.Agent? agent = this.agent; + if (agent == null) { + agent = new Nice.Agent(MainContext.@default(), Nice.Compatibility.RFC5245); + if (stun_ip != null) { + agent.stun_server = stun_ip; + agent.stun_server_port = stun_port; + } + agent.ice_tcp = false; + agent.set_software("Dino"); + agent.weak_ref(agent_unweak); + this.agent = agent; + debug("STUN server for libnice %s %u", agent.stun_server, agent.stun_server_port); + } + return agent; + } + + public override Jingle.TransportParameters create_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid) { + return new TransportParameters(get_agent(), turn_service, turn_ip, components, local_full_jid, peer_full_jid); + } + + public override Jingle.TransportParameters parse_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws Jingle.IqError { + return new TransportParameters(get_agent(), turn_service, turn_ip, components, local_full_jid, peer_full_jid, transport); + } + + private void agent_unweak() { + this.agent = null; + } +} \ No newline at end of file diff --git a/plugins/ice/src/plugin.vala b/plugins/ice/src/plugin.vala index f1c41a27..3ee8a72a 100644 --- a/plugins/ice/src/plugin.vala +++ b/plugins/ice/src/plugin.vala @@ -1,30 +1,71 @@ using Gee; -using Nice; +using Dino.Entities; using Xmpp; +using Xmpp.Xep; -namespace Dino.Plugins.Ice { +private extern const size_t NICE_ADDRESS_STRING_LEN; -public class Plugin : RootInterface, Object { +public class Dino.Plugins.Ice.Plugin : RootInterface, Object { public Dino.Application app; public void registered(Dino.Application app) { + Nice.debug_enable(true); this.app = app; + app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { + list.add(new Module()); + }); 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); + stream.get_module(Socks5Bytestreams.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses); }); + app.stream_interactor.stream_negotiated.connect(on_stream_negotiated); } - 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); + private async void on_stream_negotiated(Account account, XmppStream stream) { + Module? ice_udp_module = stream.get_module(JingleIceUdp.Module.IDENTITY) as Module; + if (ice_udp_module == null) return; + Gee.List services = yield ExternalServiceDiscovery.request_services(stream); + foreach (Xep.ExternalServiceDiscovery.Service service in services) { + if (service.transport == "udp" && (service.ty == "stun" || service.ty == "turn")) { + InetAddress ip = yield lookup_ipv4_addess(service.host); + if (ip == null) continue; + + if (service.ty == "stun") { + debug("Server offers STUN server: %s:%u, resolved to %s", service.host, service.port, ip.to_string()); + ice_udp_module.stun_ip = ip.to_string(); + ice_udp_module.stun_port = service.port; + } else if (service.ty == "turn") { + debug("Server offers TURN server: %s:%u, resolved to %s", service.host, service.port, ip.to_string()); + ice_udp_module.turn_ip = ip.to_string(); + ice_udp_module.turn_service = service; + } + } + } + if (ice_udp_module.stun_ip == null) { + InetAddress ip = yield lookup_ipv4_addess("stun.l.google.com"); + if (ip == null) return; + + debug("Using fallback STUN server: stun.l.google.com:19302, resolved to %s", ip.to_string()); + + ice_udp_module.stun_ip = ip.to_string(); + ice_udp_module.stun_port = 19302; } - return result; } public void shutdown() { // Nothing to do } -} -} + private async InetAddress? lookup_ipv4_addess(string host) { + try { + Resolver resolver = Resolver.get_default(); + GLib.List? ips = yield resolver.lookup_by_name_async(host); + foreach (GLib.InetAddress ina in ips) { + if (ina.get_family() != SocketFamily.IPV4) continue; + return ina; + } + } catch (Error e) { + warning("Failed looking up IP address of %s", host); + } + return null; + } +} \ No newline at end of file diff --git a/plugins/ice/src/transport_parameters.vala b/plugins/ice/src/transport_parameters.vala new file mode 100644 index 00000000..acb44852 --- /dev/null +++ b/plugins/ice/src/transport_parameters.vala @@ -0,0 +1,261 @@ +using Gee; +using Xmpp; +using Xmpp.Xep; + + +public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransportParameters { + private Nice.Agent agent; + private uint stream_id; + private bool we_want_connection; + private bool remote_credentials_set; + private Map connections = new HashMap(); + + private class DatagramConnection : Jingle.DatagramConnection { + private Nice.Agent agent; + private uint stream_id; + private string? error; + private ulong sent; + private ulong sent_reported; + private ulong recv; + private ulong recv_reported; + private ulong datagram_received_id; + + public DatagramConnection(Nice.Agent agent, uint stream_id, uint8 component_id) { + this.agent = agent; + this.stream_id = stream_id; + this.component_id = component_id; + this.datagram_received_id = this.datagram_received.connect((datagram) => { + recv += datagram.length; + if (recv > recv_reported + 100000) { + debug("Received %lu bytes via stream %u component %u", recv, stream_id, component_id); + recv_reported = recv; + } + }); + } + + public override async void terminate(bool we_terminated, string? reason_string = null, string? reason_text = null) { + yield base.terminate(we_terminated, reason_string, reason_text); + this.disconnect(datagram_received_id); + agent = null; + } + + public override void send_datagram(Bytes datagram) { + if (this.agent != null && is_component_ready(agent, stream_id, component_id)) { + agent.send(stream_id, component_id, datagram.get_data()); + sent += datagram.length; + if (sent > sent_reported + 100000) { + debug("Sent %lu bytes via stream %u component %u", sent, stream_id, component_id); + sent_reported = sent; + } + } + } + } + + public TransportParameters(Nice.Agent agent, Xep.ExternalServiceDiscovery.Service? turn_service, string? turn_ip, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode? node = null) { + base(components, local_full_jid, peer_full_jid, node); + this.we_want_connection = (node == null); + this.agent = agent; + agent.candidate_gathering_done.connect(on_candidate_gathering_done); + agent.initial_binding_request_received.connect(on_initial_binding_request_received); + agent.component_state_changed.connect(on_component_state_changed); + agent.new_selected_pair_full.connect(on_new_selected_pair_full); + agent.new_candidate_full.connect(on_new_candidate); + + agent.controlling_mode = !incoming; + stream_id = agent.add_stream(components); + + if (turn_ip != null) { + for (uint8 component_id = 1; component_id <= components; component_id++) { + agent.set_relay_info(stream_id, component_id, turn_ip, turn_service.port, turn_service.username, turn_service.password, Nice.RelayType.UDP); + debug("TURN info (component %i) %s:%u", component_id, turn_ip, turn_service.port); + } + } + string ufrag; + string pwd; + agent.get_local_credentials(stream_id, out ufrag, out pwd); + init(ufrag, pwd); + + for (uint8 component_id = 1; component_id <= components; component_id++) { + // We don't properly get local candidates before this call + agent.attach_recv(stream_id, component_id, MainContext.@default(), on_recv); + } + + agent.gather_candidates(stream_id); + } + + private void on_candidate_gathering_done(uint stream_id) { + if (stream_id != this.stream_id) return; + debug("on_candidate_gathering_done in %u", stream_id); + + for (uint8 i = 1; i <= components; i++) { + foreach (unowned Nice.Candidate nc in agent.get_local_candidates(stream_id, i)) { + if (nc.transport == Nice.CandidateTransport.UDP) { + JingleIceUdp.Candidate? candidate = candidate_to_jingle(nc); + if (candidate == null) continue; + debug("Local candidate summary: %s", agent.generate_local_candidate_sdp(nc)); + } + } + } + } + + private void on_new_candidate(Nice.Candidate nc) { + if (nc.stream_id != stream_id) return; + JingleIceUdp.Candidate? candidate = candidate_to_jingle(nc); + if (candidate == null) return; + + if (nc.transport == Nice.CandidateTransport.UDP) { + // Execution was in the agent thread before + add_local_candidate_threadsafe(candidate); + } + } + + public override void handle_transport_accept(StanzaNode transport) throws Jingle.IqError { + debug("on_transport_accept from %s", peer_full_jid.to_string()); + base.handle_transport_accept(transport); + } + + public override void handle_transport_info(StanzaNode transport) throws Jingle.IqError { + debug("on_transport_info from %s", peer_full_jid.to_string()); + base.handle_transport_info(transport); + + if (!we_want_connection) return; + + if (remote_ufrag != null && remote_pwd != null && !remote_credentials_set) { + agent.set_remote_credentials(stream_id, remote_ufrag, remote_pwd); + remote_credentials_set = true; + } + for (uint8 i = 1; i <= components; i++) { + SList candidates = new SList(); + foreach (JingleIceUdp.Candidate candidate in remote_candidates) { + if (candidate.component == i) { + Nice.Candidate nc = candidate_to_nice(candidate); + candidates.append(nc); + } + } + int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); + debug("Updated to %i remote candidates for candidate %u via transport info", new_candidates, i); + } + } + + public override void create_transport_connection(XmppStream stream, Jingle.Content content) { + debug("create_transport_connection: %s", content.session.sid); + debug("local_credentials: %s %s", local_ufrag, local_pwd); + debug("remote_credentials: %s %s", remote_ufrag, remote_pwd); + debug("expected incoming credentials: %s %s", local_ufrag + ":" + remote_ufrag, local_pwd); + debug("expected outgoing credentials: %s %s", remote_ufrag + ":" + local_ufrag, remote_pwd); + + we_want_connection = true; + + if (remote_ufrag != null && remote_pwd != null && !remote_credentials_set) { + agent.set_remote_credentials(stream_id, remote_ufrag, remote_pwd); + remote_credentials_set = true; + } + for (uint8 i = 1; i <= components; i++) { + SList candidates = new SList(); + foreach (JingleIceUdp.Candidate candidate in remote_candidates) { + if (candidate.ip.has_prefix("fe80::")) continue; + if (candidate.component == i) { + Nice.Candidate nc = candidate_to_nice(candidate); + candidates.append(nc); + debug("remote candidate: %s", agent.generate_local_candidate_sdp(nc)); + } + } + int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); + debug("Initiated component %u with %i remote candidates", i, new_candidates); + + connections[i] = new DatagramConnection(agent, stream_id, i); + content.set_transport_connection(connections[i], i); + } + base.create_transport_connection(stream, content); + } + + private void on_component_state_changed(uint stream_id, uint component_id, uint state) { + if (stream_id != this.stream_id) return; + debug("stream %u component %u state changed to %s", stream_id, component_id, agent.get_component_state(stream_id, component_id).to_string()); + if (is_component_ready(agent, stream_id, component_id) && connections.has_key((uint8) component_id) && !connections[(uint8)component_id].ready) { + connections[(uint8)component_id].ready = true; + } + } + + private void on_initial_binding_request_received(uint stream_id) { + if (stream_id != this.stream_id) return; + debug("initial_binding_request_received"); + } + + private void on_new_selected_pair_full(uint stream_id, uint component_id, Nice.Candidate p1, Nice.Candidate p2) { + if (stream_id != this.stream_id) return; + debug("new_selected_pair_full %u [%s, %s]", component_id, agent.generate_local_candidate_sdp(p1), agent.generate_local_candidate_sdp(p2)); + } + + private void on_recv(Nice.Agent agent, uint stream_id, uint component_id, uint8[] data) { + if (stream_id != this.stream_id) return; + if (is_component_ready(agent, stream_id, component_id) && connections.has_key((uint8) component_id)) { + connections[(uint8) component_id].datagram_received(new Bytes(data)); + } else { + debug("on_recv stream %u component %u length %u", stream_id, component_id, data.length); + } + } + + private static Nice.Candidate candidate_to_nice(JingleIceUdp.Candidate c) { + Nice.CandidateType type; + switch (c.type_) { + case JingleIceUdp.Candidate.Type.HOST: type = Nice.CandidateType.HOST; break; + case JingleIceUdp.Candidate.Type.PRFLX: type = Nice.CandidateType.PEER_REFLEXIVE; break; + case JingleIceUdp.Candidate.Type.RELAY: type = Nice.CandidateType.RELAYED; break; + case JingleIceUdp.Candidate.Type.SRFLX: type = Nice.CandidateType.SERVER_REFLEXIVE; break; + default: assert_not_reached(); + } + + Nice.Candidate candidate = new Nice.Candidate(type); + candidate.component_id = c.component; + char[] foundation = new char[Nice.CANDIDATE_MAX_FOUNDATION]; + string foundation_str = c.foundation.to_string(); + Memory.copy(foundation, foundation_str.data, foundation_str.length); + candidate.foundation = foundation; + candidate.addr = Nice.Address(); + candidate.addr.init(); + candidate.addr.set_from_string(c.ip); + candidate.addr.set_port(c.port); + candidate.priority = c.priority; + if (c.rel_addr != null) { + candidate.base_addr = Nice.Address(); + candidate.base_addr.init(); + candidate.base_addr.set_from_string(c.rel_addr); + candidate.base_addr.set_port(c.rel_port); + } + candidate.transport = Nice.CandidateTransport.UDP; + return candidate; + } + + private static JingleIceUdp.Candidate? candidate_to_jingle(Nice.Candidate nc) { + JingleIceUdp.Candidate candidate = new JingleIceUdp.Candidate(); + switch (nc.type) { + case Nice.CandidateType.HOST: candidate.type_ = JingleIceUdp.Candidate.Type.HOST; break; + case Nice.CandidateType.PEER_REFLEXIVE: candidate.type_ = JingleIceUdp.Candidate.Type.PRFLX; break; + case Nice.CandidateType.RELAYED: candidate.type_ = JingleIceUdp.Candidate.Type.RELAY; break; + case Nice.CandidateType.SERVER_REFLEXIVE: candidate.type_ = JingleIceUdp.Candidate.Type.SRFLX; break; + default: assert_not_reached(); + } + candidate.component = (uint8) nc.component_id; + candidate.foundation = (uint8) int.parse((string)nc.foundation); + candidate.generation = 0; + candidate.id = Random.next_int().to_string("%08x"); // TODO + + char[] res = new char[NICE_ADDRESS_STRING_LEN]; + nc.addr.to_string(res); + candidate.ip = (string) res; + candidate.network = 0; // TODO + candidate.port = (uint16) nc.addr.get_port(); + candidate.priority = nc.priority; + candidate.protocol = "udp"; + if (nc.base_addr.is_valid() && !nc.base_addr.equal(nc.addr)) { + res = new char[NICE_ADDRESS_STRING_LEN]; + nc.base_addr.to_string(res); + candidate.rel_addr = (string) res; + candidate.rel_port = (uint16) nc.base_addr.get_port(); + } + if (candidate.ip.has_prefix("fe80::")) return null; + + return candidate; + } +} \ No newline at end of file diff --git a/plugins/ice/src/util.vala b/plugins/ice/src/util.vala new file mode 100644 index 00000000..dd89d2f4 --- /dev/null +++ b/plugins/ice/src/util.vala @@ -0,0 +1,18 @@ +using Gee; + +namespace Dino.Plugins.Ice { + +internal static bool is_component_ready(Nice.Agent agent, uint stream_id, uint component_id) { + var state = agent.get_component_state(stream_id, component_id); + return state == Nice.ComponentState.CONNECTED || state == Nice.ComponentState.READY; +} + +internal 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; +} + +} \ No newline at end of file diff --git a/plugins/ice/vapi/metadata/Nice-0.1.metadata b/plugins/ice/vapi/metadata/Nice-0.1.metadata index 437da816..d6899f87 100644 --- a/plugins/ice/vapi/metadata/Nice-0.1.metadata +++ b/plugins/ice/vapi/metadata/Nice-0.1.metadata @@ -1,4 +1,8 @@ Nice cheader_filename="nice.h" +Address.to_string.dst type="char[]" Agent.new_reliable#constructor name="create_reliable" +Agent.attach_recv skip=false +Agent.send.buf type="uint8[]" array_length_idx=2 +AgentRecvFunc.buf type="uint8[]" array_length_idx=3 PseudoTcpCallbacks#record skip PseudoTcpSocket#class skip diff --git a/plugins/ice/vapi/nice.vapi b/plugins/ice/vapi/nice.vapi index aa45cf08..39768b9b 100644 --- a/plugins/ice/vapi/nice.vapi +++ b/plugins/ice/vapi/nice.vapi @@ -8,6 +8,7 @@ namespace Nice { public Agent (GLib.MainContext ctx, Nice.Compatibility compat); public bool add_local_address (Nice.Address addr); public uint add_stream (uint n_components); + public bool attach_recv (uint stream_id, uint component_id, GLib.MainContext ctx, Nice.AgentRecvFunc func); [Version (since = "0.1.16")] public async void close_async (); [CCode (cname = "nice_agent_new_reliable", has_construct_function = false)] @@ -58,7 +59,7 @@ namespace Nice { 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); + public int send (uint stream_id, uint component_id, [CCode (array_length_cname = "len", array_length_pos = 2.5, array_length_type = "guint", type = "const gchar*")] uint8[] 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); @@ -209,7 +210,7 @@ namespace Nice { 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); + public void to_string ([CCode (array_length = false, type = "gchar*")] char[] dst); } [CCode (cheader_filename = "nice.h", has_type_id = false)] [Version (since = "0.1.5")] @@ -343,8 +344,8 @@ namespace Nice { 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", instance_pos = 4.9)] + public delegate void AgentRecvFunc (Nice.Agent agent, uint stream_id, uint component_id, [CCode (array_length_cname = "len", array_length_pos = 3.5, array_length_type = "guint", type = "gchar*")] uint8[] 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")] -- cgit v1.2.3-70-g09d2 From ec35f95e13f4f2f756c81a35ded0980245acc5f4 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Wed, 24 Mar 2021 14:12:42 +0100 Subject: Add initial support for DTLS-SRTP --- cmake/FindGnuTLS.cmake | 13 + libdino/src/service/calls.vala | 22 +- plugins/ice/CMakeLists.txt | 7 +- plugins/ice/src/dtls_srtp.vala | 247 ++++++++++++ plugins/ice/src/transport_parameters.vala | 48 ++- plugins/ice/vapi/gnutls.vapi | 419 +++++++++++++++++++++ plugins/rtp/CMakeLists.txt | 1 + .../src/module/xep/0166_jingle/reason_element.vala | 1 + xmpp-vala/src/module/xep/0166_jingle/session.vala | 38 +- .../xep/0167_jingle_rtp/content_parameters.vala | 5 +- .../xep/0167_jingle_rtp/jingle_rtp_module.vala | 2 +- .../xep/0167_jingle_rtp/session_info_type.vala | 2 +- .../0176_jingle_ice_udp/jingle_ice_udp_module.vala | 1 + .../0176_jingle_ice_udp/transport_parameters.vala | 27 ++ 14 files changed, 791 insertions(+), 42 deletions(-) create mode 100644 cmake/FindGnuTLS.cmake create mode 100644 plugins/ice/src/dtls_srtp.vala create mode 100644 plugins/ice/vapi/gnutls.vapi (limited to 'plugins/ice/vapi') diff --git a/cmake/FindGnuTLS.cmake b/cmake/FindGnuTLS.cmake new file mode 100644 index 00000000..6b27abd7 --- /dev/null +++ b/cmake/FindGnuTLS.cmake @@ -0,0 +1,13 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(GnuTLS + PKG_CONFIG_NAME gnutls + LIB_NAMES gnutls + INCLUDE_NAMES gnutls/gnutls.h + INCLUDE_DIR_SUFFIXES gnutls gnutls/include + DEPENDS GLib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GnuTLS + REQUIRED_VARS GnuTLS_LIBRARY + VERSION_VAR GnuTLS_VERSION) \ No newline at end of file diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index 5224bdd1..54c353b0 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -125,7 +125,7 @@ namespace Dino { call.state = Call.State.ESTABLISHING; if (sessions.has_key(call)) { - foreach (Xep.Jingle.Content content in sessions[call].contents.values) { + foreach (Xep.Jingle.Content content in sessions[call].contents) { content.accept(); } } else { @@ -146,7 +146,7 @@ namespace Dino { call.state = Call.State.DECLINED; if (sessions.has_key(call)) { - foreach (Xep.Jingle.Content content in sessions[call].contents.values) { + foreach (Xep.Jingle.Content content in sessions[call].contents) { content.reject(); } remove_call_from_datastructures(call); @@ -223,16 +223,6 @@ namespace Dino { foreach (Jid full_jid in full_jids) { bool supports_rtc = yield stream.get_module(Xep.JingleRtp.Module.IDENTITY).is_available(stream, full_jid); if (!supports_rtc) continue; - - // dtls support indicates webRTC support. Clients tend to not do normal ice udp in that case. Except Dino. - bool supports_dtls = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(conversation.account, full_jid, "urn:xmpp:jingle:apps:dtls:0"); - if (supports_dtls) { - Xep.ServiceDiscovery.Identity? identity = yield stream_interactor.get_module(EntityInfo.IDENTITY).get_identity(conversation.account, full_jid); - bool is_dino = identity != null && identity.name == "Dino"; - - if (!is_dino) continue; - } - ret.add(full_jid); } return ret; @@ -253,7 +243,7 @@ namespace Dino { private void on_incoming_call(Account account, Xep.Jingle.Session session) { bool counterpart_wants_video = false; - foreach (Xep.Jingle.Content content in session.contents.values) { + foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter == null) continue; if (rtp_content_parameter.media == "video" && session.senders_include_us(content.senders)) { @@ -391,7 +381,7 @@ namespace Dino { on_incoming_content_add(stream, call, session, content) ); - foreach (Xep.Jingle.Content content in session.contents.values) { + foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter == null) continue; @@ -446,7 +436,7 @@ namespace Dino { Xep.Jingle.Module jingle_module = stream_interactor.module_manager.get_module(account, Xep.Jingle.Module.IDENTITY); jingle_module.session_initiate_received.connect((stream, session) => { - foreach (Xep.Jingle.Content content in session.contents.values) { + foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter != null) { on_incoming_call(account, session); @@ -460,7 +450,7 @@ namespace Dino { if (!call_by_sid[account].has_key(session.sid)) return; Call call = call_by_sid[account][session.sid]; - foreach (Xep.Jingle.Content content in session.contents.values) { + foreach (Xep.Jingle.Content content in session.contents) { if (name == null || content.content_name == name) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; if (rtp_content_parameter != null) { diff --git a/plugins/ice/CMakeLists.txt b/plugins/ice/CMakeLists.txt index 90fe5b7d..38025aa0 100644 --- a/plugins/ice/CMakeLists.txt +++ b/plugins/ice/CMakeLists.txt @@ -2,6 +2,7 @@ find_packages(ICE_PACKAGES REQUIRED Gee GLib GModule + GnuTLS GObject GTK3 Nice @@ -9,8 +10,9 @@ find_packages(ICE_PACKAGES REQUIRED vala_precompile(ICE_VALA_C SOURCES - src/plugin.vala + src/dtls_srtp.vala src/module.vala + src/plugin.vala src/transport_parameters.vala src/util.vala src/register_plugin.vala @@ -18,6 +20,7 @@ CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi + ${CMAKE_BINARY_DIR}/exports/crypto.vapi PACKAGES ${ICE_PACKAGES} OPTIONS @@ -26,7 +29,7 @@ OPTIONS add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="ice") add_library(ice SHARED ${ICE_VALA_C}) -target_link_libraries(ice libdino ${ICE_PACKAGES}) +target_link_libraries(ice libdino crypto-vala ${ICE_PACKAGES}) set_target_properties(ice PROPERTIES PREFIX "") set_target_properties(ice PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) diff --git a/plugins/ice/src/dtls_srtp.vala b/plugins/ice/src/dtls_srtp.vala new file mode 100644 index 00000000..a21c242b --- /dev/null +++ b/plugins/ice/src/dtls_srtp.vala @@ -0,0 +1,247 @@ +using GnuTLS; + +public class DtlsSrtp { + + public signal void send_data(uint8[] data); + + private X509.Certificate[] own_cert; + private X509.PrivateKey private_key; + private Cond buffer_cond = new Cond(); + private Mutex buffer_mutex = new Mutex(); + private Gee.LinkedList buffer_queue = new Gee.LinkedList(); + private uint pull_timeout = uint.MAX; + private string peer_fingerprint; + + private Crypto.Srtp.Session encrypt_session; + private Crypto.Srtp.Session decrypt_session; + + public static DtlsSrtp setup() throws GLib.Error { + var obj = new DtlsSrtp(); + obj.generate_credentials(); + return obj; + } + + internal string get_own_fingerprint(DigestAlgorithm digest_algo) { + return format_certificate(own_cert[0], digest_algo); + } + + public void set_peer_fingerprint(string fingerprint) { + this.peer_fingerprint = fingerprint; + } + + public uint8[] process_incoming_data(uint component_id, uint8[] data) { + if (decrypt_session != null) { + if (component_id == 1) return decrypt_session.decrypt_rtp(data); + if (component_id == 2) return decrypt_session.decrypt_rtcp(data); + } else if (component_id == 1) { + on_data_rec(data); + } + return null; + } + + public uint8[] process_outgoing_data(uint component_id, uint8[] data) { + if (encrypt_session != null) { + if (component_id == 1) return encrypt_session.encrypt_rtp(data); + if (component_id == 2) return encrypt_session.encrypt_rtcp(data); + } + return null; + } + + public void on_data_rec(owned uint8[] data) { + buffer_mutex.lock(); + buffer_queue.add(new Bytes.take(data)); + buffer_cond.signal(); + buffer_mutex.unlock(); + } + + private void generate_credentials() throws GLib.Error { + int err = 0; + + private_key = X509.PrivateKey.create(); + err = private_key.generate(PKAlgorithm.RSA, 2048); + throw_if_error(err); + + var start_time = new DateTime.now_local().add_days(1); + var end_time = start_time.add_days(2); + + X509.Certificate cert = X509.Certificate.create(); + cert.set_key(private_key); + cert.set_version(1); + cert.set_activation_time ((time_t) start_time.to_unix ()); + cert.set_expiration_time ((time_t) end_time.to_unix ()); + + uint32 serial = 1; + cert.set_serial(&serial, sizeof(uint32)); + + cert.sign(cert, private_key); + + own_cert = new X509.Certificate[] { (owned)cert }; + } + + public async void setup_dtls_connection(bool server) { + InitFlags server_or_client = server ? InitFlags.SERVER : InitFlags.CLIENT; + debug("Setting up DTLS connection. We're %s", server_or_client.to_string()); + + CertificateCredentials cert_cred = CertificateCredentials.create(); + int err = cert_cred.set_x509_key(own_cert, private_key); + throw_if_error(err); + + Session? session = Session.create(server_or_client | InitFlags.DATAGRAM); + session.enable_heartbeat(1); + session.set_srtp_profile_direct("SRTP_AES128_CM_HMAC_SHA1_80"); + session.set_credentials(GnuTLS.CredentialsType.CERTIFICATE, cert_cred); + session.server_set_request(CertificateRequest.REQUEST); + session.set_priority_from_string("NORMAL:!VERS-TLS-ALL:+VERS-DTLS-ALL:+CTYPE-CLI-X509"); + + session.set_transport_pointer(this); + session.set_pull_function(pull_function); + session.set_pull_timeout_function(pull_timeout_function); + session.set_push_function(push_function); + session.set_verify_function(verify_function); + + Thread thread = new Thread (null, () => { + DateTime maximum_time = new DateTime.now_utc().add_seconds(20); + do { + err = session.handshake(); + + DateTime current_time = new DateTime.now_utc(); + if (maximum_time.compare(current_time) < 0) { + warning("DTLS handshake timeouted"); + return -1; + } + } while (err < 0 && !((ErrorCode)err).is_fatal()); + Idle.add(setup_dtls_connection.callback); + return err; + }); + yield; + err = thread.join(); + + uint8[] km = new uint8[150]; + Datum? client_key, client_salt, server_key, server_salt; + session.get_srtp_keys(km, km.length, out client_key, out client_salt, out server_key, out server_salt); + if (client_key == null || client_salt == null || server_key == null || server_salt == null) { + warning("SRTP client/server key/salt null"); + } + + Crypto.Srtp.Session encrypt_session = new Crypto.Srtp.Session(Crypto.Srtp.Encryption.AES_CM, Crypto.Srtp.Authentication.HMAC_SHA1, 10, Crypto.Srtp.Prf.AES_CM, 0); + Crypto.Srtp.Session decrypt_session = new Crypto.Srtp.Session(Crypto.Srtp.Encryption.AES_CM, Crypto.Srtp.Authentication.HMAC_SHA1, 10, Crypto.Srtp.Prf.AES_CM, 0); + + if (server) { + encrypt_session.setkey(server_key.extract(), server_salt.extract()); + decrypt_session.setkey(client_key.extract(), client_salt.extract()); + } else { + encrypt_session.setkey(client_key.extract(), client_salt.extract()); + decrypt_session.setkey(server_key.extract(), server_salt.extract()); + } + + this.encrypt_session = (owned)encrypt_session; + this.decrypt_session = (owned)decrypt_session; + } + + private static ssize_t pull_function(void* transport_ptr, uint8[] buffer) { + DtlsSrtp self = transport_ptr as DtlsSrtp; + + self.buffer_mutex.lock(); + while (self.buffer_queue.size == 0) { + self.buffer_cond.wait(self.buffer_mutex); + } + owned Bytes data = self.buffer_queue.remove_at(0); + self.buffer_mutex.unlock(); + + uint8[] data_uint8 = Bytes.unref_to_data(data); + Memory.copy(buffer, data_uint8, data_uint8.length); + + // The callback should return 0 on connection termination, a positive number indicating the number of bytes received, and -1 on error. + return (ssize_t)data.length; + } + + private static int pull_timeout_function(void* transport_ptr, uint ms) { + DtlsSrtp self = transport_ptr as DtlsSrtp; + + DateTime current_time = new DateTime.now_utc(); + current_time.add_seconds(ms/1000); + int64 end_time = current_time.to_unix(); + + self.buffer_mutex.lock(); + while (self.buffer_queue.size == 0) { + self.buffer_cond.wait_until(self.buffer_mutex, end_time); + + DateTime new_current_time = new DateTime.now_utc(); + if (new_current_time.compare(current_time) > 0) { + break; + } + } + self.buffer_mutex.unlock(); + + // The callback should return 0 on timeout, a positive number if data can be received, and -1 on error. + return 1; + } + + private static ssize_t push_function(void* transport_ptr, uint8[] buffer) { + DtlsSrtp self = transport_ptr as DtlsSrtp; + self.send_data(buffer); + + // The callback should return a positive number indicating the bytes sent, and -1 on error. + return (ssize_t)buffer.length; + } + + private static int verify_function(Session session) { + DtlsSrtp self = session.get_transport_pointer() as DtlsSrtp; + try { + bool valid = self.verify_peer_cert(session); + if (!valid) { + warning("DTLS certificate invalid. Aborting handshake."); + return 1; + } + } catch (Error e) { + warning("Error during DTLS certificate validation: %s. Aborting handshake.", e.message); + return 1; + } + + // The callback function should return 0 for the handshake to continue or non-zero to terminate. + return 0; + } + + private bool verify_peer_cert(Session session) throws GLib.Error { + unowned Datum[] cert_datums = session.get_peer_certificates(); + if (cert_datums.length == 0) { + warning("No peer certs"); + return false; + } + if (cert_datums.length > 1) warning("More than one peer cert"); + + X509.Certificate peer_cert = X509.Certificate.create(); + peer_cert.import(ref cert_datums[0], CertificateFormat.DER); + + string peer_fp_str = format_certificate(peer_cert, DigestAlgorithm.SHA256); + if (peer_fp_str.down() != this.peer_fingerprint.down()) { + warning("First cert in peer cert list doesn't equal advertised one %s vs %s", peer_fp_str, this.peer_fingerprint); + return false; + } + + return true; + } + + private string format_certificate(X509.Certificate certificate, DigestAlgorithm digest_algo) { + uint8[] buf = new uint8[512]; + size_t buf_out_size = 512; + certificate.get_fingerprint(digest_algo, buf, ref buf_out_size); + + var sb = new StringBuilder(); + for (int i = 0; i < buf_out_size; i++) { + sb.append("%02x".printf(buf[i])); + if (i < buf_out_size - 1) { + sb.append(":"); + } + } + return sb.str; + } + + private uint8[] uint8_pt_to_a(uint8* data, uint size) { + uint8[size] ret = new uint8[size]; + for (int i = 0; i < size; i++) { + ret[i] = data[i]; + } + return ret; + } +} \ No newline at end of file diff --git a/plugins/ice/src/transport_parameters.vala b/plugins/ice/src/transport_parameters.vala index a8172678..5b6431c2 100644 --- a/plugins/ice/src/transport_parameters.vala +++ b/plugins/ice/src/transport_parameters.vala @@ -9,9 +9,11 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private bool we_want_connection; private bool remote_credentials_set; private Map connections = new HashMap(); + private DtlsSrtp? dtls_srtp; private class DatagramConnection : Jingle.DatagramConnection { private Nice.Agent agent; + private DtlsSrtp? dtls_srtp; private uint stream_id; private string? error; private ulong sent; @@ -20,8 +22,9 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private ulong recv_reported; private ulong datagram_received_id; - public DatagramConnection(Nice.Agent agent, uint stream_id, uint8 component_id) { + public DatagramConnection(Nice.Agent agent, DtlsSrtp? dtls_srtp, uint stream_id, uint8 component_id) { this.agent = agent; + this.dtls_srtp = dtls_srtp; this.stream_id = stream_id; this.component_id = component_id; this.datagram_received_id = this.datagram_received.connect((datagram) => { @@ -41,7 +44,12 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport public override void send_datagram(Bytes datagram) { if (this.agent != null && is_component_ready(agent, stream_id, component_id)) { - agent.send(stream_id, component_id, datagram.get_data()); + uint8[] encrypted_data = null; + if (dtls_srtp != null) { + encrypted_data = dtls_srtp.process_outgoing_data(component_id, datagram.get_data()); + if (encrypted_data == null) return; + } + agent.send(stream_id, component_id, encrypted_data ?? datagram.get_data()); sent += datagram.length; if (sent > sent_reported + 100000) { debug("Sent %lu bytes via stream %u component %u", sent, stream_id, component_id); @@ -55,6 +63,20 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport base(components, local_full_jid, peer_full_jid, node); this.we_want_connection = (node == null); this.agent = agent; + + if (this.peer_fingerprint != null || !incoming) { + dtls_srtp = DtlsSrtp.setup(); + dtls_srtp.send_data.connect((data) => { + agent.send(stream_id, 1, data); + }); + this.own_fingerprint = dtls_srtp.get_own_fingerprint(GnuTLS.DigestAlgorithm.SHA256); + if (incoming) { + dtls_srtp.set_peer_fingerprint(this.peer_fingerprint); + } else { + dtls_srtp.setup_dtls_connection(true); + } + } + agent.candidate_gathering_done.connect(on_candidate_gathering_done); agent.initial_binding_request_received.connect(on_initial_binding_request_received); agent.component_state_changed.connect(on_component_state_changed); @@ -112,6 +134,12 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport public override void handle_transport_accept(StanzaNode transport) throws Jingle.IqError { debug("on_transport_accept from %s", peer_full_jid.to_string()); base.handle_transport_accept(transport); + + if (dtls_srtp != null && peer_fingerprint != null) { + dtls_srtp.set_peer_fingerprint(this.peer_fingerprint); + } else { + dtls_srtp = null; + } } public override void handle_transport_info(StanzaNode transport) throws Jingle.IqError { @@ -163,9 +191,16 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); debug("Initiated component %u with %i remote candidates", i, new_candidates); - connections[i] = new DatagramConnection(agent, stream_id, i); + connections[i] = new DatagramConnection(agent, dtls_srtp, stream_id, i); content.set_transport_connection(connections[i], i); } + + if (incoming && dtls_srtp != null) { + Jingle.DatagramConnection rtp_datagram = (Jingle.DatagramConnection) content.get_transport_connection(1); + rtp_datagram.notify["ready"].connect(() => { + dtls_srtp.setup_dtls_connection(false); + }); + } base.create_transport_connection(stream, content); } @@ -194,12 +229,17 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private void on_recv(Nice.Agent agent, uint stream_id, uint component_id, uint8[] data) { if (stream_id != this.stream_id) return; + uint8[] decrypt_data = null; + if (dtls_srtp != null) { + decrypt_data = dtls_srtp.process_incoming_data(component_id, data); + if (decrypt_data == null) return; + } may_consider_ready(stream_id, component_id); if (connections.has_key((uint8) component_id)) { if (!connections[(uint8) component_id].ready) { debug("on_recv stream %u component %u when state %s", stream_id, component_id, agent.get_component_state(stream_id, component_id).to_string()); } - connections[(uint8) component_id].datagram_received(new Bytes(data)); + connections[(uint8) component_id].datagram_received(new Bytes(decrypt_data ?? data)); } else { debug("on_recv stream %u component %u length %u", stream_id, component_id, data.length); } diff --git a/plugins/ice/vapi/gnutls.vapi b/plugins/ice/vapi/gnutls.vapi new file mode 100644 index 00000000..a8f75e14 --- /dev/null +++ b/plugins/ice/vapi/gnutls.vapi @@ -0,0 +1,419 @@ +[CCode (cprefix = "gnutls_", lower_case_cprefix = "gnutls_", cheader_filename = "gnutls/gnutls.h")] +namespace GnuTLS { + + public int global_init(); + + [CCode (cname = "gnutls_pull_func", has_target = false)] + public delegate ssize_t PullFunc(void* transport_ptr, [CCode (ctype = "void*", array_length_type="size_t")] uint8[] array); + + [CCode (cname = "gnutls_pull_timeout_func", has_target = false)] + public delegate int PullTimeoutFunc(void* transport_ptr, uint ms); + + [CCode (cname = "gnutls_push_func", has_target = false)] + public delegate ssize_t PushFunc(void* transport_ptr, [CCode (ctype = "void*", array_length_type="size_t")] uint8[] array); + + [CCode (cname = "gnutls_certificate_verify_function", has_target = false)] + public delegate int VerifyFunc(Session session); + + [Compact] + [CCode (cname = "struct gnutls_session_int", free_function = "gnutls_deinit")] + public class Session { + + public static Session? create(int con_end) throws GLib.Error { + Session result; + var ret = init(out result, con_end); + throw_if_error(ret); + return result; + } + + [CCode (cname = "gnutls_init")] + private static int init(out Session session, int con_end); + + [CCode (cname = "gnutls_transport_set_push_function")] + public void set_push_function(PushFunc func); + + [CCode (cname = "gnutls_transport_set_pull_function")] + public void set_pull_function(PullFunc func); + + [CCode (cname = "gnutls_transport_set_pull_timeout_function")] + public void set_pull_timeout_function(PullTimeoutFunc func); + + [CCode (cname = "gnutls_transport_set_ptr")] + public void set_transport_pointer(void* ptr); + + [CCode (cname = "gnutls_transport_get_ptr")] + public void* get_transport_pointer(); + + [CCode (cname = "gnutls_heartbeat_enable")] + public int enable_heartbeat(uint type); + + [CCode (cname = "gnutls_certificate_server_set_request")] + public void server_set_request(CertificateRequest req); + + [CCode (cname = "gnutls_credentials_set")] + public int set_credentials_(CredentialsType type, void* cred); + [CCode (cname = "gnutls_credentials_set_")] + public void set_credentials(CredentialsType type, void* cred) throws GLib.Error { + int err = set_credentials_(type, cred); + throw_if_error(err); + } + + [CCode (cname = "gnutls_priority_set_direct")] + public int set_priority_from_string_(string priority, out unowned string err_pos = null); + [CCode (cname = "gnutls_priority_set_direct_")] + public void set_priority_from_string(string priority, out unowned string err_pos = null) throws GLib.Error { + int err = set_priority_from_string_(priority, out err_pos); + throw_if_error(err); + } + + [CCode (cname = "gnutls_srtp_set_profile_direct")] + public int set_srtp_profile_direct_(string profiles, out unowned string err_pos = null); + [CCode (cname = "gnutls_srtp_set_profile_direct_")] + public void set_srtp_profile_direct(string profiles, out unowned string err_pos = null) throws GLib.Error { + int err = set_srtp_profile_direct_(profiles, out err_pos); + throw_if_error(err); + } + + [CCode (cname = "gnutls_transport_set_int")] + public void transport_set_int(int fd); + + [CCode (cname = "gnutls_handshake")] + public int handshake(); + + [CCode (cname = "gnutls_srtp_get_keys")] + public int get_srtp_keys_(void *key_material, uint32 key_material_size, out Datum client_key, out Datum client_salt, out Datum server_key, out Datum server_salt); + [CCode (cname = "gnutls_srtp_get_keys_")] + public void get_srtp_keys(void *key_material, uint32 key_material_size, out Datum client_key, out Datum client_salt, out Datum server_key, out Datum server_salt) throws GLib.Error { + get_srtp_keys_(key_material, key_material_size, out client_key, out client_salt, out server_key, out server_salt); + } + + [CCode (cname = "gnutls_certificate_get_peers", array_length_type = "unsigned int")] + public unowned Datum[]? get_peer_certificates(); + + [CCode (cname = "gnutls_session_set_verify_function")] + public void set_verify_function(VerifyFunc func); + } + + [Compact] + [CCode (cname = "struct gnutls_certificate_credentials_st", free_function = "gnutls_certificate_free_credentials", cprefix = "gnutls_certificate_")] + public class CertificateCredentials { + + [CCode (cname = "gnutls_certificate_allocate_credentials")] + private static int allocate(out CertificateCredentials credentials); + + public static CertificateCredentials create() throws GLib.Error { + CertificateCredentials result; + var ret = allocate (out result); + throw_if_error(ret); + return result; + } + + public void get_x509_crt(uint index, [CCode (array_length_type = "unsigned int")] out unowned X509.Certificate[] x509_ca_list); + + public int set_x509_key(X509.Certificate[] cert_list, X509.PrivateKey key); + } + + [CCode (cheader_filename = "gnutls/x509.h", cprefix = "GNUTLS_")] + namespace X509 { + + [Compact] + [CCode (cname = "struct gnutls_x509_crt_int", cprefix = "gnutls_x509_crt_", free_function = "gnutls_x509_crt_deinit")] + public class Certificate { + + [CCode (cname = "gnutls_x509_crt_init")] + private static int init (out Certificate cert); + public static Certificate create() throws GLib.Error { + Certificate result; + var ret = init (out result); + throw_if_error(ret); + return result; + } + + [CCode (cname = "gnutls_x509_crt_import")] + public int import_(ref Datum data, CertificateFormat format); + [CCode (cname = "gnutls_x509_crt_import_")] + public void import(ref Datum data, CertificateFormat format) throws GLib.Error { + int err = import_(ref data, format); + throw_if_error(err); + } + + [CCode (cname = "gnutls_x509_crt_set_version")] + public int set_version_(uint version); + [CCode (cname = "gnutls_x509_crt_set_version_")] + public void set_version(uint version) throws GLib.Error { + int err = set_version_(version); + throw_if_error(err); + } + + [CCode (cname = "gnutls_x509_crt_set_key")] + public int set_key_(PrivateKey key); + [CCode (cname = "gnutls_x509_crt_set_key_")] + public void set_key(PrivateKey key) throws GLib.Error { + int err = set_key_(key); + throw_if_error(err); + } + + [CCode (cname = "gnutls_x509_crt_set_activation_time")] + public int set_activation_time_(time_t act_time); + [CCode (cname = "gnutls_x509_crt_set_activation_time_")] + public void set_activation_time(time_t act_time) throws GLib.Error { + int err = set_activation_time_(act_time); + throw_if_error(err); + } + + [CCode (cname = "gnutls_x509_crt_set_expiration_time")] + public int set_expiration_time_(time_t exp_time); + [CCode (cname = "gnutls_x509_crt_set_expiration_time_")] + public void set_expiration_time(time_t exp_time) throws GLib.Error { + int err = set_expiration_time_(exp_time); + throw_if_error(err); + } + + [CCode (cname = "gnutls_x509_crt_set_serial")] + public int set_serial_(void* serial, size_t serial_size); + [CCode (cname = "gnutls_x509_crt_set_serial_")] + public void set_serial(void* serial, size_t serial_size) throws GLib.Error { + int err = set_serial_(serial, serial_size); + throw_if_error(err); + } + + [CCode (cname = "gnutls_x509_crt_sign")] + public int sign_(Certificate issuer, PrivateKey issuer_key); + [CCode (cname = "gnutls_x509_crt_sign_")] + public void sign(Certificate issuer, PrivateKey issuer_key) throws GLib.Error { + int err = sign_(issuer, issuer_key); + throw_if_error(err); + } + + [CCode (cname = "gnutls_x509_crt_get_fingerprint")] + public int get_fingerprint_(DigestAlgorithm algo, void* buf, ref size_t buf_size); + [CCode (cname = "gnutls_x509_crt_get_fingerprint_")] + public void get_fingerprint(DigestAlgorithm algo, void* buf, ref size_t buf_size) throws GLib.Error { + int err = get_fingerprint_(algo, buf, ref buf_size); + throw_if_error(err); + } + } + + [Compact] + [CCode (cname = "struct gnutls_x509_privkey_int", cprefix = "gnutls_x509_privkey_", free_function = "gnutls_x509_privkey_deinit")] + public class PrivateKey { + private static int init (out PrivateKey key); + public static PrivateKey create () throws GLib.Error { + PrivateKey result; + var ret = init (out result); + throw_if_error(ret); + return result; + } + + public int generate(PKAlgorithm algo, uint bits, uint flags = 0); + } + + } + + [CCode (cname = "gnutls_certificate_request_t", cprefix = "GNUTLS_CERT_", has_type_id = false)] + public enum CertificateRequest { + IGNORE, + REQUEST, + REQUIRE + } + + [CCode (cname = "gnutls_pk_algorithm_t", cprefix = "GNUTLS_PK_", has_type_id = false)] + public enum PKAlgorithm { + UNKNOWN, + RSA, + DSA; + } + + [CCode (cname = "gnutls_digest_algorithm_t", cprefix = "GNUTLS_DIG_", has_type_id = false)] + public enum DigestAlgorithm { + NULL, + MD5, + SHA1, + RMD160, + MD2, + SHA224, + SHA256, + SHA384, + SHA512; + } + + [Flags] + [CCode (cname = "gnutls_init_flags_t", cprefix = "GNUTLS_", has_type_id = false)] + public enum InitFlags { + SERVER, + CLIENT, + DATAGRAM + } + + [CCode (cname = "gnutls_credentials_type_t", cprefix = "GNUTLS_CRD_", has_type_id = false)] + public enum CredentialsType { + CERTIFICATE, + ANON, + SRP, + PSK, + IA + } + + [CCode (cname = "gnutls_x509_crt_fmt_t", cprefix = "GNUTLS_X509_FMT_", has_type_id = false)] + public enum CertificateFormat { + DER, + PEM + } + + [Flags] + [CCode (cname = "gnutls_certificate_status_t", cprefix = "GNUTLS_CERT_", has_type_id = false)] + public enum CertificateStatus { + INVALID, // will be set if the certificate was not verified. + REVOKED, // in X.509 this will be set only if CRLs are checked + SIGNER_NOT_FOUND, + SIGNER_NOT_CA, + INSECURE_ALGORITHM + } + + [SimpleType] + [CCode (cname = "gnutls_datum_t", has_type_id = false)] + public struct Datum { + public uint8* data; + public uint size; + + public uint8[] extract() { + uint8[size] ret = new uint8[size]; + for (int i = 0; i < size; i++) { + ret[i] = data[i]; + } + return ret; + } + } + + // Gnutls error codes. The mapping to a TLS alert is also shown in comments. + [CCode (cname = "int", cprefix = "GNUTLS_E_", lower_case_cprefix = "gnutls_error_", has_type_id = false)] + public enum ErrorCode { + SUCCESS, + UNKNOWN_COMPRESSION_ALGORITHM, + UNKNOWN_CIPHER_TYPE, + LARGE_PACKET, + UNSUPPORTED_VERSION_PACKET, // GNUTLS_A_PROTOCOL_VERSION + UNEXPECTED_PACKET_LENGTH, // GNUTLS_A_RECORD_OVERFLOW + INVALID_SESSION, + FATAL_ALERT_RECEIVED, + UNEXPECTED_PACKET, // GNUTLS_A_UNEXPECTED_MESSAGE + WARNING_ALERT_RECEIVED, + ERROR_IN_FINISHED_PACKET, + UNEXPECTED_HANDSHAKE_PACKET, + UNKNOWN_CIPHER_SUITE, // GNUTLS_A_HANDSHAKE_FAILURE + UNWANTED_ALGORITHM, + MPI_SCAN_FAILED, + DECRYPTION_FAILED, // GNUTLS_A_DECRYPTION_FAILED, GNUTLS_A_BAD_RECORD_MAC + MEMORY_ERROR, + DECOMPRESSION_FAILED, // GNUTLS_A_DECOMPRESSION_FAILURE + COMPRESSION_FAILED, + AGAIN, + EXPIRED, + DB_ERROR, + SRP_PWD_ERROR, + INSUFFICIENT_CREDENTIALS, + HASH_FAILED, + BASE64_DECODING_ERROR, + MPI_PRINT_FAILED, + REHANDSHAKE, // GNUTLS_A_NO_RENEGOTIATION + GOT_APPLICATION_DATA, + RECORD_LIMIT_REACHED, + ENCRYPTION_FAILED, + PK_ENCRYPTION_FAILED, + PK_DECRYPTION_FAILED, + PK_SIGN_FAILED, + X509_UNSUPPORTED_CRITICAL_EXTENSION, + KEY_USAGE_VIOLATION, + NO_CERTIFICATE_FOUND, // GNUTLS_A_BAD_CERTIFICATE + INVALID_REQUEST, + SHORT_MEMORY_BUFFER, + INTERRUPTED, + PUSH_ERROR, + PULL_ERROR, + RECEIVED_ILLEGAL_PARAMETER, // GNUTLS_A_ILLEGAL_PARAMETER + REQUESTED_DATA_NOT_AVAILABLE, + PKCS1_WRONG_PAD, + RECEIVED_ILLEGAL_EXTENSION, + INTERNAL_ERROR, + DH_PRIME_UNACCEPTABLE, + FILE_ERROR, + TOO_MANY_EMPTY_PACKETS, + UNKNOWN_PK_ALGORITHM, + // returned if libextra functionality was requested but + // gnutls_global_init_extra() was not called. + + INIT_LIBEXTRA, + LIBRARY_VERSION_MISMATCH, + // returned if you need to generate temporary RSA + // parameters. These are needed for export cipher suites. + + NO_TEMPORARY_RSA_PARAMS, + LZO_INIT_FAILED, + NO_COMPRESSION_ALGORITHMS, + NO_CIPHER_SUITES, + OPENPGP_GETKEY_FAILED, + PK_SIG_VERIFY_FAILED, + ILLEGAL_SRP_USERNAME, + SRP_PWD_PARSING_ERROR, + NO_TEMPORARY_DH_PARAMS, + // For certificate and key stuff + + ASN1_ELEMENT_NOT_FOUND, + ASN1_IDENTIFIER_NOT_FOUND, + ASN1_DER_ERROR, + ASN1_VALUE_NOT_FOUND, + ASN1_GENERIC_ERROR, + ASN1_VALUE_NOT_VALID, + ASN1_TAG_ERROR, + ASN1_TAG_IMPLICIT, + ASN1_TYPE_ANY_ERROR, + ASN1_SYNTAX_ERROR, + ASN1_DER_OVERFLOW, + OPENPGP_UID_REVOKED, + CERTIFICATE_ERROR, + CERTIFICATE_KEY_MISMATCH, + UNSUPPORTED_CERTIFICATE_TYPE, // GNUTLS_A_UNSUPPORTED_CERTIFICATE + X509_UNKNOWN_SAN, + OPENPGP_FINGERPRINT_UNSUPPORTED, + X509_UNSUPPORTED_ATTRIBUTE, + UNKNOWN_HASH_ALGORITHM, + UNKNOWN_PKCS_CONTENT_TYPE, + UNKNOWN_PKCS_BAG_TYPE, + INVALID_PASSWORD, + MAC_VERIFY_FAILED, // for PKCS #12 MAC + CONSTRAINT_ERROR, + WARNING_IA_IPHF_RECEIVED, + WARNING_IA_FPHF_RECEIVED, + IA_VERIFY_FAILED, + UNKNOWN_ALGORITHM, + BASE64_ENCODING_ERROR, + INCOMPATIBLE_CRYPTO_LIBRARY, + INCOMPATIBLE_LIBTASN1_LIBRARY, + OPENPGP_KEYRING_ERROR, + X509_UNSUPPORTED_OID, + RANDOM_FAILED, + BASE64_UNEXPECTED_HEADER_ERROR, + OPENPGP_SUBKEY_ERROR, + CRYPTO_ALREADY_REGISTERED, + HANDSHAKE_TOO_LARGE, + UNIMPLEMENTED_FEATURE, + APPLICATION_ERROR_MAX, // -65000 + APPLICATION_ERROR_MIN; // -65500 + + [CCode (cname = "gnutls_error_is_fatal")] + public bool is_fatal(); + + [CCode (cname = "gnutls_perror")] + public void print(); + + [CCode (cname = "gnutls_strerror")] + public unowned string to_string(); + } + + public void throw_if_error(int err_int) throws GLib.Error { + ErrorCode error = (ErrorCode)err_int; + if (error != ErrorCode.SUCCESS) { + throw new GLib.Error(-1, error, "%s%s", error.to_string(), error.is_fatal() ? " fatal" : ""); + } + } +} \ No newline at end of file diff --git a/plugins/rtp/CMakeLists.txt b/plugins/rtp/CMakeLists.txt index 5311fac3..8ce2a7c6 100644 --- a/plugins/rtp/CMakeLists.txt +++ b/plugins/rtp/CMakeLists.txt @@ -2,6 +2,7 @@ find_packages(RTP_PACKAGES REQUIRED Gee GLib GModule + GnuTLS GObject GTK3 Gst diff --git a/xmpp-vala/src/module/xep/0166_jingle/reason_element.vala b/xmpp-vala/src/module/xep/0166_jingle/reason_element.vala index 1cbdf936..4d47d4cd 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/reason_element.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/reason_element.vala @@ -24,6 +24,7 @@ namespace Xmpp.Xep.Jingle.ReasonElement { BUSY, CANCEL, DECLINE, + GONE, SUCCESS }; } \ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0166_jingle/session.vala b/xmpp-vala/src/module/xep/0166_jingle/session.vala index e9ad9169..2d359f01 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/session.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/session.vala @@ -24,9 +24,10 @@ public class Xmpp.Xep.Jingle.Session : Object { public Jid peer_full_jid { get; private set; } public bool we_initiated { get; private set; } - public HashMap contents = new HashMap(); + public HashMap contents_map = new HashMap(); + public Gee.List contents = new ArrayList(); // Keep the order contents - public SecurityParameters? security { get { return contents.values.to_array()[0].security_params; } } + public SecurityParameters? security { get { return contents.to_array()[0].security_params; } } public Session.initiate_sent(XmppStream stream, string sid, Jid local_full_jid, Jid peer_full_jid) { this.stream = stream; @@ -94,7 +95,7 @@ public class Xmpp.Xep.Jingle.Session : Object { } else if (action.has_prefix("transport-")) { ContentNode content_node = get_single_content_node(jingle); - if (!contents.has_key(content_node.name)) { + if (!contents_map.has_key(content_node.name)) { throw new IqError.BAD_REQUEST("unknown content"); } @@ -102,7 +103,7 @@ public class Xmpp.Xep.Jingle.Session : Object { throw new IqError.BAD_REQUEST("missing transport node"); } - Content content = contents[content_node.name]; + Content content = contents_map[content_node.name]; if (content_node.creator != content.content_creator) { throw new IqError.BAD_REQUEST("unknown content; creator"); @@ -128,11 +129,11 @@ public class Xmpp.Xep.Jingle.Session : Object { } else if (action == "description-info") { ContentNode content_node = get_single_content_node(jingle); - if (!contents.has_key(content_node.name)) { + if (!contents_map.has_key(content_node.name)) { throw new IqError.BAD_REQUEST("unknown content"); } - Content content = contents[content_node.name]; + Content content = contents_map[content_node.name]; if (content_node.creator != content.content_creator) { throw new IqError.BAD_REQUEST("unknown content; creator"); @@ -149,7 +150,8 @@ public class Xmpp.Xep.Jingle.Session : Object { } internal void insert_content(Content content) { - this.contents[content.content_name] = content; + this.contents_map[content.content_name] = content; + this.contents.add(content); content.set_session(this); } @@ -209,7 +211,8 @@ public class Xmpp.Xep.Jingle.Session : Object { public async void add_content(Content content) { content.session = this; - this.contents[content.content_name] = content; + this.contents_map[content.content_name] = content; + contents.add(content); StanzaNode content_add_node = new StanzaNode.build("jingle", NS_URI) .add_self_xmlns() @@ -228,9 +231,9 @@ public class Xmpp.Xep.Jingle.Session : Object { private void handle_content_accept(ContentNode content_node) throws IqError { if (content_node.description == null || content_node.transport == null) throw new IqError.BAD_REQUEST("missing description or transport node"); - if (!contents.has_key(content_node.name)) throw new IqError.BAD_REQUEST("unknown content"); + if (!contents_map.has_key(content_node.name)) throw new IqError.BAD_REQUEST("unknown content"); - Content content = contents[content_node.name]; + Content content = contents_map[content_node.name]; if (content_node.creator != content.content_creator) warning("Counterpart accepts content with an unexpected `creator`"); if (content_node.senders != content.senders) warning("Counterpart accepts content with an unexpected `senders`"); @@ -242,7 +245,7 @@ public class Xmpp.Xep.Jingle.Session : Object { private void handle_content_modify(XmppStream stream, StanzaNode jingle_node, Iq.Stanza iq) throws IqError { ContentNode content_node = get_single_content_node(jingle_node); - Content? content = contents[content_node.name]; + Content? content = contents_map[content_node.name]; if (content == null) throw new IqError.BAD_REQUEST("no such content"); if (content_node.creator != content.content_creator) throw new IqError.BAD_REQUEST("mismatching creator"); @@ -301,7 +304,7 @@ public class Xmpp.Xep.Jingle.Session : Object { } } - foreach (Content content in contents.values) { + foreach (Content content in contents) { content.terminate(false, reason_name, reason_text); } @@ -336,7 +339,7 @@ public class Xmpp.Xep.Jingle.Session : Object { .add_self_xmlns() .put_attribute("action", "session-accept") .put_attribute("sid", sid); - foreach (Content content in contents.values) { + foreach (Content content in contents) { StanzaNode content_node = new StanzaNode.build("content", NS_URI) .put_attribute("creator", "initiator") .put_attribute("name", content.content_name) @@ -345,12 +348,13 @@ public class Xmpp.Xep.Jingle.Session : Object { .put_node(content.transport_params.to_transport_stanza_node()); jingle.put_node(content_node); } + Iq.Stanza iq = new Iq.Stanza.set(jingle) { to=peer_full_jid }; stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); - foreach (Content content in contents.values) { - content.on_accept(stream); + foreach (Content content2 in contents) { + content2.on_accept(stream); } state = State.ACTIVE; @@ -359,7 +363,7 @@ public class Xmpp.Xep.Jingle.Session : Object { internal void accept_content(Content content) { if (state == State.INITIATE_RECEIVED) { bool all_accepted = true; - foreach (Content c in contents.values) { + foreach (Content c in contents) { if (c.state != Content.State.WANTS_TO_BE_ACCEPTED) { all_accepted = false; } @@ -413,7 +417,7 @@ public class Xmpp.Xep.Jingle.Session : Object { } else { reason_str = "local session-terminate"; } - foreach (Content content in contents.values) { + foreach (Content content in contents) { content.terminate(true, reason_name, reason_text); } } diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala index cca03543..32ea1df6 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala @@ -34,7 +34,7 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { this.parent = parent; this.media = media; this.ssrc = ssrc; - this.rtcp_mux = rtcp_mux; + this.rtcp_mux = true; this.bandwidth = bandwidth; this.bandwidth_type = bandwidth_type; this.encryption_required = encryption_required; @@ -175,6 +175,9 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { ret.put_node(new StanzaNode.build("encryption", NS_URI) .put_node(local_crypto.to_xml())); } + if (rtcp_mux) { + ret.put_node(new StanzaNode.build("rtcp-mux", NS_URI)); + } return ret; } } \ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala index 23aee6c9..3a9ea09f 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala @@ -84,7 +84,7 @@ public abstract class Module : XmppStreamModule { Jid receiver_full_jid = session.peer_full_jid; Jingle.Content? content = null; - foreach (Jingle.Content c in session.contents.values) { + foreach (Jingle.Content c in session.contents) { Parameters? parameters = c.content_params as Parameters; if (parameters == null) continue; diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/session_info_type.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/session_info_type.vala index d36255f0..32cd9016 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/session_info_type.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/session_info_type.vala @@ -50,7 +50,7 @@ namespace Xmpp.Xep.JingleRtp { public void send_mute(Jingle.Session session, bool mute, string media) { string node_name = mute ? "mute" : "unmute"; - foreach (Jingle.Content content in session.contents.values) { + foreach (Jingle.Content content in session.contents) { Parameters? parameters = content.content_params as Parameters; if (parameters != null && parameters.media == media) { StanzaNode session_info_content = new StanzaNode.build(node_name, NS_URI).add_self_xmlns().put_attribute("name", content.content_name); diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala index 9ed494ff..4b7c7a36 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala @@ -12,6 +12,7 @@ public abstract class Module : XmppStreamModule, Jingle.Transport { public override void attach(XmppStream stream) { stream.get_module(Jingle.Module.IDENTITY).register_transport(this); stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); + stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, "urn:xmpp:jingle:apps:dtls:0"); } public override void detach(XmppStream stream) { stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI); diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala index 8b8aa07d..3c69d0af 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala @@ -13,6 +13,9 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T public ConcurrentList unsent_local_candidates = new ConcurrentList(Candidate.equals_func); public Gee.List remote_candidates = new ArrayList(Candidate.equals_func); + public string? own_fingerprint = null; + public string? peer_fingerprint = null; + public Jid local_full_jid { get; private set; } public Jid peer_full_jid { get; private set; } private uint8 components_; @@ -34,6 +37,11 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T foreach (StanzaNode candidateNode in node.get_subnodes("candidate")) { remote_candidates.add(Candidate.parse(candidateNode)); } + + StanzaNode? fingerprint_node = node.get_subnode("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); + if (fingerprint_node != null) { + peer_fingerprint = fingerprint_node.get_deep_string_content(); + } } } @@ -57,6 +65,20 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T .add_self_xmlns() .put_attribute("ufrag", local_ufrag) .put_attribute("pwd", local_pwd); + + if (own_fingerprint != null) { + var fingerprint_node = new StanzaNode.build("fingerprint", "urn:xmpp:jingle:apps:dtls:0") + .add_self_xmlns() + .put_attribute("hash", "sha-256") + .put_node(new StanzaNode.text(own_fingerprint)); + if (incoming) { + fingerprint_node.put_attribute("setup", "active"); + } else { + fingerprint_node.put_attribute("setup", "actpass"); + } + node.put_node(fingerprint_node); + } + foreach (Candidate candidate in unsent_local_candidates) { node.put_node(candidate.to_xml()); } @@ -72,6 +94,11 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T foreach (StanzaNode candidateNode in node.get_subnodes("candidate")) { remote_candidates.add(Candidate.parse(candidateNode)); } + + StanzaNode? fingerprint_node = node.get_subnode("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); + if (fingerprint_node != null) { + peer_fingerprint = fingerprint_node.get_deep_string_content(); + } } public virtual void handle_transport_info(StanzaNode node) throws Jingle.IqError { -- cgit v1.2.3-70-g09d2 From e9ff660537f7c00281395f8a001f7554e116efff Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 11 Apr 2021 15:12:26 +0200 Subject: Fix usage of old libnice versions --- plugins/ice/src/transport_parameters.vala | 8 +++----- plugins/ice/vapi/metadata/Nice-0.1.metadata | 3 +++ plugins/ice/vapi/nice.vapi | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'plugins/ice/vapi') diff --git a/plugins/ice/src/transport_parameters.vala b/plugins/ice/src/transport_parameters.vala index 6d160c62..8766e744 100644 --- a/plugins/ice/src/transport_parameters.vala +++ b/plugins/ice/src/transport_parameters.vala @@ -176,8 +176,7 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport SList candidates = new SList(); foreach (JingleIceUdp.Candidate candidate in remote_candidates) { if (candidate.component == i) { - Nice.Candidate nc = candidate_to_nice(candidate); - candidates.append(nc); + candidates.append(candidate_to_nice(candidate)); } } int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); @@ -203,9 +202,8 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport foreach (JingleIceUdp.Candidate candidate in remote_candidates) { if (candidate.ip.has_prefix("fe80::")) continue; if (candidate.component == i) { - Nice.Candidate nc = candidate_to_nice(candidate); - candidates.append(nc); - debug("remote candidate: %s", agent.generate_local_candidate_sdp(nc)); + candidates.append(candidate_to_nice(candidate)); + debug("remote candidate: %s", agent.generate_local_candidate_sdp(candidate_to_nice(candidate))); } } int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); diff --git a/plugins/ice/vapi/metadata/Nice-0.1.metadata b/plugins/ice/vapi/metadata/Nice-0.1.metadata index d6899f87..7fcf046a 100644 --- a/plugins/ice/vapi/metadata/Nice-0.1.metadata +++ b/plugins/ice/vapi/metadata/Nice-0.1.metadata @@ -6,3 +6,6 @@ Agent.send.buf type="uint8[]" array_length_idx=2 AgentRecvFunc.buf type="uint8[]" array_length_idx=3 PseudoTcpCallbacks#record skip PseudoTcpSocket#class skip + +# Not yet supported by vapigen +# Candidate copy_function="nice_candidate_copy" free_function="nice_candidate_free" type_id="" diff --git a/plugins/ice/vapi/nice.vapi b/plugins/ice/vapi/nice.vapi index 39768b9b..540e2b4e 100644 --- a/plugins/ice/vapi/nice.vapi +++ b/plugins/ice/vapi/nice.vapi @@ -164,7 +164,7 @@ namespace Nice { [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 ()")] + [CCode (cheader_filename = "nice.h", copy_function = "nice_candidate_copy", free_function = "nice_candidate_free")] [Compact] public class Candidate { public Nice.Address addr; -- cgit v1.2.3-70-g09d2 From 328c3cf37f296f4519829afd03d21f94ea4153e5 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Mon, 12 Apr 2021 22:21:28 +0200 Subject: Fix bad syntax in gnutls vapi, add libsrtp2 dependency to CI builds --- .github/workflows/build.yml | 2 +- plugins/ice/vapi/gnutls.vapi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/ice/vapi') diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ec82dd5..ce12d441 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: sudo apt-get update - - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-3-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-3-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev - run: ./configure --with-tests --with-libsignal-in-tree - run: make - run: build/xmpp-vala-test diff --git a/plugins/ice/vapi/gnutls.vapi b/plugins/ice/vapi/gnutls.vapi index a8f75e14..bc3f13d0 100644 --- a/plugins/ice/vapi/gnutls.vapi +++ b/plugins/ice/vapi/gnutls.vapi @@ -277,7 +277,7 @@ namespace GnuTLS { public uint size; public uint8[] extract() { - uint8[size] ret = new uint8[size]; + uint8[] ret = new uint8[size]; for (int i = 0; i < size; i++) { ret[i] = data[i]; } -- cgit v1.2.3-70-g09d2