WebRTC (Web Real-Time Communication) has transformed how we communicate over the internet, enabling real-time audio, video, and data sharing directly between browsers and devices. However, one persistent challenge has been the reliance on DNS (Domain Name System) resolution for establishing peer connections, especially when using TURN (Traversal Using Relays Around NAT) servers. At Twinlife, we’ve tackled this issue head-on by providing hostname resolution directly in the WebRTC peer connection factory. This innovation eliminates unnecessary DNS requests, resulting in faster, more reliable connections—especially in environments where DNS latency or failures can disrupt communication. This improvement also enhance the user's privacy by avoiding unecessary DNS requests.
Avoiding DNS resolution in WebRTC connections
By Stephane Carrez2026-05-05 08:31:002 comments
The Problem: DNS Dependencies in WebRTC
Traditionally, WebRTC relies on DNS to resolve hostnames (e.g., turn.example.com) to IP addresses before establishing a connection via TURN servers. While this works in most cases, it introduces several challenges:
Latency: DNS lookups add delays, especially in mobile or high-latency networks.
Reliability: DNS failures or misconfigurations can break connections, even if the TURN server is operational.
Privacy: DNS queries can expose metadata about the user’s communication patterns.
Complexity: Applications must handle DNS errors gracefully, adding complexity to the codebase. For applications like TwinMe and Skred, where real-time communication and user experience are critical, these challenges are unacceptable.
Our Solution: Hostname Resolution in the Peer Connection Factory
To address these issues, we modified the WebRTC library to provide hostnames and their associated IP addresses directly within the peer connection factory. Here’s how it works:
- when our signaling server gives a TURN configuration, it indicates the turn server configuration to use but also the IPv4 and IPv6 addresses of these turn server. The device will not have to make these DNS resolutions.
- the peer connection factory is configured with the static list of hostnames and IP addresses that are provided by our signaling server.
- when WebRTC has to resolve one of our TURN server, it first looks in the static list of hostnames and find the entries directly.

Why This Matters
Faster Connections
By eliminating DNS lookups during the connection process, we reduce latency and speed up the establishment of peer connections. This is particularly beneficial for:
- Mobile users on unstable networks.
- Applications requiring instant connectivity, such as video calls or messaging.
Improved Reliability
DNS is a common point of failure in real-time communication. By resolving hostnames upfront and caching the results, we ensure that:
- Connections are not disrupted by DNS outages.
- Users experience fewer dropped calls or failed messages.
Enhanced Privacy
DNS queries can leak information about the user’s activities. By minimizing DNS requests, we reduce the risk of exposing sensitive metadata, aligning with our commitment to user privacy in Skred and TwinMe.
Simplified Error Handling
Applications no longer need to handle DNS-related errors during connection setup. This simplifies the codebase and reduces the risk of bugs related to network issues.
Configuring the PeerConnectionFactory
A typical peer connection factory is created and configured by the following Java code (See PeerConnectionServiceImpl class):
List<IceServer> iceServers = new ArrayList<>(0);
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
configuration.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
configuration.enableImplicitRollback = true;
configuration.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
configuration.turnPortPrunePolicy = PeerConnection.PortPrunePolicy.PRUNE_BASED_ON_PRIORITY;
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
// Disable SRTP_AES128_CM_SHA1_32 and enable SRTP_AEAD_AES_256_GCM.
CryptoOptions.Builder cryptoBuilder = CryptoOptions.builder();
cryptoBuilder.setEnableAes128Sha1_32CryptoCipher(false);
cryptoBuilder.setEnableGcmCryptoSuites(true);
cryptoBuilder.setRequireFrameEncryption(false);
cryptoBuilder.setEnableEncryptedRtpHeaderExtensions(false);
configuration.cryptoOptions = cryptoBuilder.createCryptoOptions();
And we only have to provide a list of ServerAddr objects that defines the hostname corresponding IPv4 and IPv6 addresses:
List<PeerConnection.ServerAddr> hostnames = new ArrayList<>();
hostnames.add(new PeerConnection.ServerAddr(hostname.hostname, hostname.ipv4, hostname.ipv6));
configuration.hostAddresses = hostnames;
With this simple configuration, we are done with the DNS resolution. A similar implementation is made for our iOS applications because we are using the exact same WebRTC library sources but compiled for iOS.
Implementation Details
Let's look at how it was implemented in the WebRTC side. We will not dig into the JNI adaptations which bring nothing to this article, instead we will show you the C++ implementation changes. The first part is the declaration of the webrtc::StaticHostname class that represents our static hostname with its IP addresses. The declaration looks like webrtc::StaticHostname:
namespace webrtc {
class StaticHostname {
public:
std::string hostname;
IPAddress ipv4;
IPAddress ipv6;
StaticHostname() {}
StaticHostname(const StaticHostname& host) : hostname(host.hostname), ipv4(host.ipv4), ipv6(host.ipv6) {}
bool operator==(const StaticHostname& host) const {
return hostname == host.hostname && ipv4 == host.ipv4 && ipv6 == host.ipv6;
}
StaticHostname& operator=(const StaticHostname& second) {
hostname = second.hostname;
ipv4 = second.ipv4;
ipv6 = second.ipv6;
return *this;
}
};
}
The JNI layer makes the transformation and adaptation of the Java object into a webrtc::StaticHostname object.
In WebRTC sources, the DNS resolutions are handled by the webrtc::AsyncDnsResolver class and we added a simple constructor that gets a vector of hostnames :
namespace webrtc {
class RTC_EXPORT AsyncDnsResolver : public AsyncDnsResolverInterface {
public:
AsyncDnsResolver(const std::vector<webrtc::StaticHostname> *hostnames);
...
};
}
The DNS resolution is implemented by the asynchronous Start method. It gets the socket address to resolve with the family (basically IPv4 or IPv6) and it will provide the result by calling a callback at a later time. The change we made was rather simple: we just have to look at the list of hostnames passed in the constructor to find a match and if we get it it is returned immediately (See AsyncDnsResolver::Start).
void AsyncDnsResolver::Start(const SocketAddress& addr,
int family,
absl::AnyInvocable<void()> callback) {
RTC_DCHECK_RUN_ON(&result_.sequence_checker_);
RTC_CHECK(!state_);
state_ = State::Create();
result_.addr_ = addr;
callback_ = std::move(callback);
if (hostnames_) {
for (const webrtc::StaticHostname& hostname : *hostnames_) {
if (hostname.hostname == addr.hostname()) {
std::vector<IPAddress> addresses;
if ((family == AF_INET || family == AF_UNSPEC) && hostname.ipv4.family() == AF_INET) {
addresses.push_back(hostname.ipv4);
}
if ((family == AF_INET6 || family == AF_UNSPEC) && hostname.ipv6.family() == AF_INET6) {
addresses.push_back(hostname.ipv6);
}
int error = 0;
state_->PostToCallbackTaskQueue(
SafeTask(safety_.flag(), [this, error, addresses = std::move(addresses)]() {
RTC_DCHECK_RUN_ON(&result_.sequence_checker_);
state_ = nullptr;
result_.addresses_ = addresses;
result_.error_ = error;
std::move(callback_)();
}));
return;
}
}
RTC_LOG(LS_INFO) << "Static hostname not found " << addr.ToString();
}
// --twinlife 2023-07-11: provide hostname resolution
...
}
Conclusion
By moving hostname resolution into the peer connection factory configuration, we’ve made WebRTC more efficient, reliable, and private. This aligns perfectly with Twinlife’s mission to deliver seamless, user-centric communication tools like Twinme and Skred. If you want to learn more about these changes, you can study the sources which are available in our WebRTC GitHub repositories.
2 comments
Add a comment