diff options
| -rw-r--r-- | libdino/meson.build | 2 | ||||
| -rw-r--r-- | libdino/src/service/ice.vala (renamed from plugins/ice/src/transport_parameters.vala) | 157 | ||||
| -rw-r--r-- | libdino/src/service/module_manager.vala | 5 | ||||
| -rw-r--r-- | libdino/src/service/util.vala | 12 | ||||
| -rw-r--r-- | libdino/vapi/metadata/Nice-0.1.metadata (renamed from plugins/ice/vapi/metadata/Nice-0.1.metadata) | 0 | ||||
| -rw-r--r-- | libdino/vapi/nice.vapi (renamed from plugins/ice/vapi/nice.vapi) | 0 | ||||
| -rw-r--r-- | plugins/ice/meson.build | 25 | ||||
| -rw-r--r-- | plugins/ice/src/module.vala | 57 | ||||
| -rw-r--r-- | plugins/ice/src/plugin.vala | 104 | ||||
| -rw-r--r-- | plugins/ice/src/register_plugin.vala | 3 | ||||
| -rw-r--r-- | plugins/ice/src/util.vala | 18 | ||||
| -rw-r--r-- | plugins/meson.build | 1 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala | 4 | ||||
| -rw-r--r-- | xmpp-vala/src/module/xep/0177_jingle_raw_udp.vala | 4 |
14 files changed, 173 insertions, 219 deletions
diff --git a/libdino/meson.build b/libdino/meson.build index 67ac6a83..e680b662 100644 --- a/libdino/meson.build +++ b/libdino/meson.build @@ -9,6 +9,7 @@ dependencies = [ dep_gmodule, dep_libsoup, dep_libsrtp2, + dep_nice, dep_sqlite3, dep_xmpp_vala ] @@ -63,6 +64,7 @@ sources = files( 'src/service/file_manager.vala', 'src/service/file_transfer_storage.vala', 'src/service/history_sync.vala', + 'src/service/ice.vala', 'src/service/jingle_file_transfers.vala', 'src/service/message_correction.vala', 'src/service/message_deletion.vala', diff --git a/plugins/ice/src/transport_parameters.vala b/libdino/src/service/ice.vala index 122207c5..0e168be9 100644 --- a/plugins/ice/src/transport_parameters.vala +++ b/libdino/src/service/ice.vala @@ -4,8 +4,150 @@ using Dino.Crypto; using Xmpp; using Xmpp.Xep; +private extern const size_t NICE_ADDRESS_STRING_LEN; -public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransportParameters { +public class Dino.IceUdpModule : Xmpp.Xep.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 static weak Nice.Agent? agent; + private HashMap<string, DtlsSrtp.CredentialsCapsule> cerds = new HashMap<string, DtlsSrtp.CredentialsCapsule>(); + + private uint timeout = 0; + + public override void attach(XmppStream stream) { + base.attach(stream); + external_discovery_refresh_services.begin(stream); + } + public override void detach(XmppStream stream) { + base.detach(stream); + stop_external_discovery_refresh_services(); + } + + private Nice.Agent get_agent() { + Nice.Agent? agent = this.agent; + if (agent == null) { + Nice.debug_enable(true); + 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; + } + + private void agent_unweak() { + this.agent = null; + } + + public override Jingle.TransportParameters create_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid) { + DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); + return new IceUdpTransportParameters(get_agent(), cred, 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 { + DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); + return new IceUdpTransportParameters(get_agent(), cred, turn_service, turn_ip, components, local_full_jid, peer_full_jid, transport); + } + + private DtlsSrtp.CredentialsCapsule? get_create_credentials(Jid local_full_jid, Jid peer_full_jid) { + string from_to_id = local_full_jid.to_string() + peer_full_jid.to_string(); + try { + if (!cerds.has_key(from_to_id)) cerds[from_to_id] = DtlsSrtp.generate_credentials(); + } catch (GLib.Error e) { + warning("Error creating dtls credentials: %s", e.message); + } + return cerds[from_to_id]; + } + + private void stop_external_discovery_refresh_services() { + if (timeout != 0) { + Source.remove(timeout); + timeout = 0; + } + } + + private async void external_discovery_refresh_services(XmppStream stream) { + stop_external_discovery_refresh_services(); + Gee.List<Xep.ExternalServiceDiscovery.Service> 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 (ip.is_any || ip.is_link_local || ip.is_loopback || ip.is_multicast || ip.is_site_local) { + warning("Ignoring STUN/TURN server at %s", service.host); + continue; + } + + if (service.ty == "stun") { + debug("Server offers STUN server: %s:%u, resolved to %s", service.host, service.port, ip.to_string()); + stun_ip = ip.to_string(); + 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()); + turn_ip = ip.to_string(); + turn_service = service; + } + } + } + if (stun_ip == null) { + InetAddress ip = yield lookup_ipv4_addess("stun.dino.im"); + if (ip == null) return; + + debug("Using fallback STUN server: stun.dino.im:7886, resolved to %s", ip.to_string()); + + stun_ip = ip.to_string(); + stun_port = 7886; + } + + // Refresh TURN credentials before they expire + if (turn_service != null) { + DateTime? expires = turn_service.expires; + if (expires != null) { + uint refresh_delay = (uint) (expires.to_unix() - new DateTime.now_utc().to_unix()) / 2; + + if (refresh_delay >= 300 && refresh_delay <= (int64) uint.MAX) { // 5 min < refetch < max + debug("Re-fetching TURN credentials in %u sec", refresh_delay); + + stop_external_discovery_refresh_services(); + timeout = Timeout.add_seconds((uint) refresh_delay, () => { + timeout = 0; + external_discovery_refresh_services.begin(stream); + return false; + }); + } else { + warning("TURN credentials' expiry time = %u, *not* re-fetching", refresh_delay); + } + } + } + } + + private async InetAddress? lookup_ipv4_addess(string host) { + try { + Resolver resolver = Resolver.get_default(); + GLib.List<GLib.InetAddress>? ips = yield resolver.lookup_by_name_async(host); + foreach (GLib.InetAddress ina in ips) { + if (ina.get_family() != SocketFamily.IPV4) continue; + return ina; + } + } catch (GLib.Error e) { + warning("Failed looking up IP address of %s", host); + } + return null; + } +} + +public class Dino.IceUdpTransportParameters : JingleIceUdp.IceUdpTransportParameters { private Nice.Agent agent; private uint stream_id; private bool we_want_connection; @@ -15,6 +157,11 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private MainContext thread_context; private MainLoop thread_loop; + private 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; + } + private class DatagramConnection : Jingle.DatagramConnection { private Nice.Agent agent; private DtlsSrtp.Handler? dtls_srtp_handler; @@ -22,7 +169,7 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private string? error; private ulong datagram_received_id; - public DatagramConnection(Nice.Agent agent, DtlsSrtp.Handler? dtls_srtp_handler, uint stream_id, uint8 component_id) { + internal DatagramConnection(Nice.Agent agent, DtlsSrtp.Handler? dtls_srtp_handler, uint stream_id, uint8 component_id) { this.agent = agent; this.dtls_srtp_handler = dtls_srtp_handler; this.stream_id = stream_id; @@ -65,7 +212,7 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport } } - public TransportParameters(Nice.Agent agent, DtlsSrtp.CredentialsCapsule? credentials, Xep.ExternalServiceDiscovery.Service? turn_service, string? turn_ip, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode? node = null) { + internal IceUdpTransportParameters(Nice.Agent agent, DtlsSrtp.CredentialsCapsule? credentials, 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; @@ -126,11 +273,11 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport agent.gather_candidates(stream_id); } - private static DtlsSrtp.Handler setup_dtls(TransportParameters tp, DtlsSrtp.CredentialsCapsule credentials) { + private static DtlsSrtp.Handler setup_dtls(IceUdpTransportParameters tp, DtlsSrtp.CredentialsCapsule credentials) { var weak_self = WeakRef(tp); DtlsSrtp.Handler dtls_srtp = new DtlsSrtp.Handler.with_cert(credentials); dtls_srtp.send_data.connect((data) => { - TransportParameters self = (TransportParameters) weak_self.get(); + IceUdpTransportParameters self = (IceUdpTransportParameters) weak_self.get(); if (self != null) self.agent.send(self.stream_id, 1, data); }); return dtls_srtp; diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala index f7cad0fe..a6efe629 100644 --- a/libdino/src/service/module_manager.vala +++ b/libdino/src/service/module_manager.vala @@ -85,7 +85,7 @@ public class ModuleManager { module_map[account].add(new Xep.InBandRegistration.Module()); module_map[account].add(new Xep.HttpFileUpload.Module()); module_map[account].add(new Xep.Reactions.Module()); - module_map[account].add(new Xep.Socks5Bytestreams.Module()); + module_map[account].add(new Xep.Socks5Bytestreams.Module.with_local_ip_address_handler(Util.get_local_ip_addresses)); module_map[account].add(new Xep.InBandBytestreams.Module()); module_map[account].add(new Xep.Jingle.Module()); module_map[account].add(new Xep.JingleSocks5Bytestreams.Module()); @@ -97,11 +97,12 @@ public class ModuleManager { module_map[account].add(new Xep.JingleMessageInitiation.Module()); module_map[account].add(new Xep.OccupantIds.Module()); module_map[account].add(new Xep.MessageRetraction.Module()); - module_map[account].add(new Xep.JingleRawUdp.Module()); + module_map[account].add(new Xep.JingleRawUdp.Module.with_local_ip_address_handler(Util.get_local_ip_addresses)); module_map[account].add(new Xep.Muji.Module()); module_map[account].add(new Xep.CallInvites.Module()); module_map[account].add(new Xep.Coin.Module()); module_map[account].add(new Xep.MessageDisplayedSynchronization.Module()); + module_map[account].add(new IceUdpModule()); initialize_account_modules(account, module_map[account]); } } diff --git a/libdino/src/service/util.vala b/libdino/src/service/util.vala index ac596cad..577bc6cc 100644 --- a/libdino/src/service/util.vala +++ b/libdino/src/service/util.vala @@ -1,8 +1,6 @@ using Dino.Entities; -namespace Dino { - -public class Util { +namespace Dino.Util { public static Message.Type get_message_type_for_conversation(Conversation conversation) { switch (conversation.type_) { case Conversation.Type.CHAT: @@ -41,6 +39,12 @@ public class Util { } return false; } -} + public static Gee.List<string> get_local_ip_addresses() { + Gee.List<string> result = new Gee.ArrayList<string>(); + foreach (string ip_address in Nice.interfaces_get_local_ips(false)) { + result.add(ip_address); + } + return result; + } } diff --git a/plugins/ice/vapi/metadata/Nice-0.1.metadata b/libdino/vapi/metadata/Nice-0.1.metadata index 7fcf046a..7fcf046a 100644 --- a/plugins/ice/vapi/metadata/Nice-0.1.metadata +++ b/libdino/vapi/metadata/Nice-0.1.metadata diff --git a/plugins/ice/vapi/nice.vapi b/libdino/vapi/nice.vapi index 540e2b4e..540e2b4e 100644 --- a/plugins/ice/vapi/nice.vapi +++ b/libdino/vapi/nice.vapi diff --git a/plugins/ice/meson.build b/plugins/ice/meson.build deleted file mode 100644 index 19030259..00000000 --- a/plugins/ice/meson.build +++ /dev/null @@ -1,25 +0,0 @@ -dependencies = [ - dep_dino, - dep_gee, - dep_gio, - dep_glib, - dep_gmodule, - dep_nice, - dep_xmpp_vala, -] -sources = files( - 'src/module.vala', - 'src/plugin.vala', - 'src/transport_parameters.vala', - 'src/util.vala', - 'src/register_plugin.vala', -) -c_args = vala_c_args + [ - '-DG_LOG_DOMAIN="ice"', -] -vala_args = [ - '--vapidir', meson.current_source_dir() / 'vapi', -] -lib_ice = shared_library('ice', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, install: true, install_dir: get_option('libdir') / get_option('plugindir'), install_rpath: default_install_rpath) -dep_ice = declare_dependency(link_with: lib_ice, include_directories: include_directories('.')) -summary('Peer-to-peer communication (ice)', dep_ice.found(), bool_yn: true, section: 'Plugins') diff --git a/plugins/ice/src/module.vala b/plugins/ice/src/module.vala deleted file mode 100644 index c3c92124..00000000 --- a/plugins/ice/src/module.vala +++ /dev/null @@ -1,57 +0,0 @@ -using Gee; - -using Dino.Crypto; -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 HashMap<string, DtlsSrtp.CredentialsCapsule> cerds = new HashMap<string, DtlsSrtp.CredentialsCapsule>(); - - 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) { - DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); - return new TransportParameters(get_agent(), cred, 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 { - DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); - return new TransportParameters(get_agent(), cred, turn_service, turn_ip, components, local_full_jid, peer_full_jid, transport); - } - - private DtlsSrtp.CredentialsCapsule? get_create_credentials(Jid local_full_jid, Jid peer_full_jid) { - string from_to_id = local_full_jid.to_string() + peer_full_jid.to_string(); - try { - if (!cerds.has_key(from_to_id)) cerds[from_to_id] = DtlsSrtp.generate_credentials(); - } catch (GLib.Error e) { - warning("Error creating dtls credentials: %s", e.message); - } - return cerds[from_to_id]; - } - - 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 deleted file mode 100644 index d1655a74..00000000 --- a/plugins/ice/src/plugin.vala +++ /dev/null @@ -1,104 +0,0 @@ -using Gee; -using Dino.Entities; -using Xmpp; -using Xmpp.Xep; - -private extern const size_t NICE_ADDRESS_STRING_LEN; - -public class Dino.Plugins.Ice.Plugin : RootInterface, Object { - private HashMap<Account, uint> timeouts = new HashMap<Account, uint>(Account.hash_func, Account.equals_func); - - 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) => { - if (stream.get_module(Socks5Bytestreams.Module.IDENTITY) != null) { - stream.get_module(Socks5Bytestreams.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses); - } - if (stream.get_module(JingleRawUdp.Module.IDENTITY) != null) { - stream.get_module(JingleRawUdp.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses); - } - }); - app.stream_interactor.stream_negotiated.connect(external_discovery_refresh_services); - } - - private async void external_discovery_refresh_services(Account account, XmppStream stream) { - Module? ice_udp_module = stream.get_module(JingleIceUdp.Module.IDENTITY) as Module; - if (ice_udp_module == null) return; - - Gee.List<Xep.ExternalServiceDiscovery.Service> 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 (ip.is_any || ip.is_link_local || ip.is_loopback || ip.is_multicast || ip.is_site_local) { - warning("Ignoring STUN/TURN server at %s", service.host); - 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.dino.im"); - if (ip == null) return; - - debug("Using fallback STUN server: stun.dino.im:7886, resolved to %s", ip.to_string()); - - ice_udp_module.stun_ip = ip.to_string(); - ice_udp_module.stun_port = 7886; - } - - // Refresh TURN credentials before they expire - if (ice_udp_module.turn_service != null) { - DateTime? expires = ice_udp_module.turn_service.expires; - if (expires != null) { - uint refresh_delay = (uint) (expires.to_unix() - new DateTime.now_utc().to_unix()) / 2; - - if (refresh_delay >= 300 && refresh_delay <= (int64) uint.MAX) { // 5 min < refetch < max - debug("Re-fetching TURN credentials in %u sec", refresh_delay); - - timeouts.unset(account); - timeouts[account] = Timeout.add_seconds((uint) refresh_delay, () => { - external_discovery_refresh_services.begin(account, stream); - return false; - }); - } else { - warning("TURN credentials' expiry time = %u, *not* re-fetching", refresh_delay); - } - } - } - } - - public void shutdown() { - // Nothing to do - } - - private async InetAddress? lookup_ipv4_addess(string host) { - try { - Resolver resolver = Resolver.get_default(); - GLib.List<GLib.InetAddress>? 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/register_plugin.vala b/plugins/ice/src/register_plugin.vala deleted file mode 100644 index b2ed56c1..00000000 --- a/plugins/ice/src/register_plugin.vala +++ /dev/null @@ -1,3 +0,0 @@ -public Type register_plugin(Module module) { - return typeof (Dino.Plugins.Ice.Plugin); -} diff --git a/plugins/ice/src/util.vala b/plugins/ice/src/util.vala deleted file mode 100644 index dd89d2f4..00000000 --- a/plugins/ice/src/util.vala +++ /dev/null @@ -1,18 +0,0 @@ -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<string> get_local_ip_addresses() { - Gee.List<string> result = new ArrayList<string>(); - 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/meson.build b/plugins/meson.build index 8aa05215..3f804fcb 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -1,4 +1,3 @@ -subdir('ice') subdir('notification-sound') subdir('omemo') subdir('openpgp') diff --git a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala index 97674f3f..9cfb4afc 100644 --- a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala @@ -25,6 +25,10 @@ public class Module : XmppStreamModule, Iq.Handler { private GetLocalIpAddresses? get_local_ip_addresses_impl = null; + public Module.with_local_ip_address_handler(owned GetLocalIpAddresses get_local_ip_addresses) { + set_local_ip_address_handler((owned) get_local_ip_addresses); + } + public override void attach(XmppStream stream) { stream.add_flag(new Flag()); query_availability.begin(stream); diff --git a/xmpp-vala/src/module/xep/0177_jingle_raw_udp.vala b/xmpp-vala/src/module/xep/0177_jingle_raw_udp.vala index 200cdfa9..b59c1b06 100644 --- a/xmpp-vala/src/module/xep/0177_jingle_raw_udp.vala +++ b/xmpp-vala/src/module/xep/0177_jingle_raw_udp.vala @@ -13,6 +13,10 @@ namespace Xmpp.Xep.JingleRawUdp { private GetLocalIpAddresses? get_local_ip_addresses_impl = null; + public Module.with_local_ip_address_handler(owned GetLocalIpAddresses get_local_ip_addresses) { + set_local_ip_address_handler((owned) get_local_ip_addresses); + } + 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); |
