收集 srflx candidate 的原理是,向 STUN server 发送一个 UDP 包(叫 STUN Binding request),server 会把这个包里的源 IP 地址、UDP 端口返回给客户端(叫 STUN Binding response),这个 IP 和端口将来可能可以用来和其他客户端建立 P2P 连接。关于 STUN 协议的具体内容,可以查阅 RFC Session Traversal Utilities for NAT (STUN)。
收集 srflx candicate 时可以复用收集 host candidate 时创建的 socket 对象,这一逻辑通过 PORTALLOCATOR_ENABLE_SHARED_SOCKET
flag 控制,默认是开启的。
复用 socket 的情况下,AllocationSequence::CreateStunPorts
函数会直接返回,因为早在 AllocationSequence::CreateUDPPorts
函数的执行过程中,就已经执行了 STUN Binding request 的发送逻辑。
///
收集 sflx candidate 流程
/// 前面是有发送 binding request 的流程的
===> SocketDispatcher::OnEvent(uint32_t ff, int err) ===> SignalReadEvent(this);
===> AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) ===> SignalReadPacket(this, buf_, static_cast<size_t>(len), remote_addr,
(timestamp > -1 ? timestamp : TimeMicros()));
===> AllocationSequence::OnReadPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
const int64_t& packet_time_us) ===> udp_port_->HandleIncomingPacket(socket, data, size, remote_addr,
packet_time_us);
===> UDPPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
int64_t packet_time_us) ===> OnReadPacket(socket, data, size, remote_addr, packet_time_us);
===> UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
const int64_t& packet_time_us) ===> requests_.CheckResponse(data, size);
===> StunRequestManager::CheckResponse(const char* data, size_t size) ===> return CheckResponse(response.get());
===> StunRequestManager::CheckResponse(StunMessage* msg)===> request->OnResponse(msg);
===> StunBindingRequest::OnResponse(StunMessage* response) override ===> port_->OnStunBindingRequestSucceeded(this->Elapsed(), server_addr_, addr);
===> UDPPort::OnStunBindingRequestSucceeded ===> AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address,
UDP_PROTOCOL_NAME, "", "", STUN_PORT_TYPE,
ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false); / is_final 又是 false
===> Port::AddAddress ===> FinishAddingAddress(c, is_final);
===> Port::FinishAddingAddress ===> SignalCandidateReady(this, c);
===> BasicPortAllocatorSession::OnCandidateReady ===> SignalCandidatesReady(this, candidates);
===> P2PTransportChannel::OnCandidatesReady ===> SignalCandidateGathered(this, candidates[i]);
===> JsepTransportController::OnTransportCandidateGathered_n ===> SignalIceCandidatesGathered(transport_name, {candidate});
===> PeerConnection::OnTransportControllerCandidatesGathered ===> OnIceCandidate(std::move(candidate));
===> PeerConnection::OnIceCandidate ===> Observer()->OnIceCandidate(candidate.get());
===> Conductor::OnIceCandidate ===> SendMessage(writer.write(jmessage));
具体代码调用逻辑如下:
发送 STUN Binding request
UDPPort::OnLocalAddressReady 上面已经分析过
===>
MaybePrepareStunCandidate();
void UDPPort::MaybePrepareStunCandidate() {
// Sending binding request to the STUN server if address is available to
// prepare STUN candidate.
if (!server_addresses_.empty()) {
SendStunBindingRequests(); //
} else {
// Port is done allocating candidates.
MaybeSetPortCompleteOrError();
}
}
void UDPPort::SendStunBindingRequests() {
// We will keep pinging the stun server to make sure our NAT pin-hole stays
// open until the deadline (specified in SendStunBindingRequest).
RTC_DCHECK(requests_.empty());
for (ServerAddresses::const_iterator it = server_addresses_.begin();
it != server_addresses_.end(); ++it) {
SendStunBindingRequest(*it); //
}
}
void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) {
if (stun_addr.IsUnresolvedIP()) {
ResolveStunAddress(stun_addr);
} else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
// Check if |server_addr_| is compatible with the port's ip.
if (IsCompatibleAddress(stun_addr)) {
requests_.Send( class StunBindingRequest : public StunRequest
new StunBindingRequest(this, stun_addr, rtc::TimeMillis())); StunBindingRequest 中保存 UDPPort 的 this 指针到 port_ 成变中
} else {
// Since we can't send stun messages to the server, we should mark this
// port ready.
const char* reason = "STUN server address is incompatible.";
RTC_LOG(LS_WARNING) << reason;
OnStunBindingOrResolveRequestFailed(stun_addr, SERVER_NOT_REACHABLE_ERROR,
reason);
}
}
}
void StunRequestManager::Send(StunRequest* request) {
SendDelayed(request, 0); ///
}
void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
request->set_manager(this); ///
RTC_DCHECK(requests_.find(request->id()) == requests_.end());
request->set_origin(origin_);
request->Construct();
requests_[request->id()] = request; / 将 class StunBindingRequest : public StunRequest 原始消息进行了保存
if (delay > 0) {
thread_->PostDelayed(RTC_FROM_HERE, delay, request, MSG_STUN_SEND, NULL);
} else {
thread_->Send(RTC_FROM_HERE, request, MSG_STUN_SEND, NULL); ///
}
}
void StunRequest::OnMessage(rtc::Message* pmsg) {
RTC_DCHECK(manager_ != NULL);
RTC_DCHECK(pmsg->message_id == MSG_STUN_SEND);
if (timeout_) {
OnTimeout();
delete this;
return;
}
tstamp_ = rtc::TimeMillis();
rtc::ByteBufferWriter buf;
msg_->Write(&buf);
manager_->SignalSendPacket(buf.Data(), buf.Length(), this); / StunRequestManager->SignalSendPacket , 具体见 UDPPort::Init
OnSent();
manager_->thread_->PostDelayed(RTC_FROM_HERE, resend_delay(), this,
MSG_STUN_SEND, NULL);
}
bool UDPPort::Init() {
stun_keepalive_lifetime_ = GetStunKeepaliveLifetime();
if (!SharedSocket()) {
RTC_DCHECK(socket_ == nullptr);
socket_ = socket_factory()->CreateUdpSocket(
rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
if (!socket_) {
RTC_LOG(LS_WARNING) << ToString() << ": UDP socket creation failed";
return false;
}
socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
}
socket_->SignalSentPacket.connect(this, &UDPPort::OnSentPacket);
socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend);
socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady);
requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket); StunRequestManager requests_;
return true;
}
void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
StunBindingRequest* sreq = static_cast<StunBindingRequest*>(req);
rtc::PacketOptions options(StunDscpValue());
options.info_signaled_after_sent.packet_type = rtc::PacketType::kStunMessage;
CopyPortInformationToPacketInfo(&options.info_signaled_after_sent);
if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0) { // AsyncUDPSocket::SendTo 发往 stun server
RTC_LOG_ERR_EX(LERROR, socket_->GetError()) << "sendto";
}
stats_.stun_binding_requests_sent++;
}
int AsyncUDPSocket::SendTo(const void* pv,
size_t cb,
const SocketAddress& addr,
const rtc::PacketOptions& options) {
rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis(),
options.info_signaled_after_sent);
CopySocketInformationToPacketInfo(cb, *this, true, &sent_packet.info); class SocketDispatcher : public Dispatcher, public PhysicalSocket
int ret = socket_->SendTo(pv, cb, addr); / socket_ 实际上就是 SocketDispatcher 的指针,具体见 BasicPacketSocketFactory::CreateUdpSocket
SignalSentPacket(this, sent_packet); / SocketDispatcher::SendTo 就是 PhysicalSocket::SendTo
return ret; / SignalSentPacket 来自 AsyncPacketSocket , 因为 class AsyncUDPSocket : public AsyncPacketSocket
} / 这里 SignalSentPacket 就会触发 UDPPort::OnSentPacket (见下面分析) , 具体见 UDPPort::Init
AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket) : socket_(socket) {
size_ = BUF_SIZE;
buf_ = new char[size_];
// The socket should start out readable but not writable.
socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent);
socket_->SignalWriteEvent.connect(this, &AsyncUDPSocket::OnWriteEvent);
}
int PhysicalSocket::SendTo(const void* buffer,
size_t length,
const SocketAddress& addr) {
sockaddr_storage saddr;
size_t len = addr.ToSockAddrStorage(&saddr);
int sent =
DoSendTo(s_, static_cast<const char*>(buffer), static_cast<int>(length),
#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
// Suppress SIGPIPE. See above for explanation.
MSG_NOSIGNAL,
#else
0,
#endif
reinterpret_cast<sockaddr*>(&saddr), static_cast<int>(len));
UpdateLastError();
MaybeRemapSendError();
// We have seen minidumps where this may be false.
RTC_DCHECK(sent <= static_cast<int>(length));
if ((sent > 0 && sent < static_cast<int>(length)) ||
(sent < 0 && IsBlockingError(GetError()))) {
EnableEvents(DE_WRITE);
}
return sent;
}
int PhysicalSocket::DoSendTo(SOCKET socket,
const char* buf,
int len,
int flags,
const struct sockaddr* dest_addr,
socklen_t addrlen) {
return ::sendto(socket, buf, len, flags, dest_addr, addrlen); //
}
接收 STUN Binding response
事件循环参考前面描述,这里直接借用前面分析的结果
void SocketDispatcher::OnEvent(uint32_t ff, int err) {
#if defined(WEBRTC_USE_EPOLL)
// Remember currently enabled events so we can combine multiple changes
// into one update call later.
// The signal handlers might re-enable events disabled here, so we can't
// keep a list of events to disable at the end of the method. This list
// would not be updated with the events enabled by the signal handlers.
StartBatchedEventUpdates();
#endif
// Make sure we deliver connect/accept first. Otherwise, consumers may see
// something like a READ followed by a CONNECT, which would be odd.
if ((ff & DE_CONNECT) != 0) {
DisableEvents(DE_CONNECT);
SignalConnectEvent(this);
}
if ((ff & DE_ACCEPT) != 0) {
DisableEvents(DE_ACCEPT);
SignalReadEvent(this);
}
if ((ff & DE_READ) != 0) {
DisableEvents(DE_READ);
SignalReadEvent(this);// 在 BasicPacketSocketFactory::CreateUdpSocket 中将 SocketDispatcher 的指针传递到了 AsyncUDPSocket ,
}// 并在 AsyncUDPSocket 的构造函数中设置了 SocketDispatcher 的 SignalReadEvent 和 SignalWriteEvent ,分别将其绑定到了 AsyncUDPSocket::OnReadEvent AsyncUDPSocket::OnWriteEvent
if ((ff & DE_WRITE) != 0) {
DisableEvents(DE_WRITE);
SignalWriteEvent(this);
}
if ((ff & DE_CLOSE) != 0) {
// The socket is now dead to us, so stop checking it.
SetEnabledEvents(0);
SignalCloseEvent(this, err);
}
#if defined(WEBRTC_USE_EPOLL)
FinishBatchedEventUpdates();
#endif
}
void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) {
RTC_DCHECK(socket_.get() == socket);
SocketAddress remote_addr;
int64_t timestamp;
int len = socket_->RecvFrom(buf_, size_, &remote_addr, ×tamp); /
if (len < 0) {
// An error here typically means we got an ICMP error in response to our
// send datagram, indicating the remote address was unreachable.
// When doing ICE, this kind of thing will often happen.
// TODO: Do something better like forwarding the error to the user.
SocketAddress local_addr = socket_->GetLocalAddress();
RTC_LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToSensitiveString()
<< "] receive failed with error " << socket_->GetError();
return;
}
// TODO: Make sure that we got all of the packet.
// If we did not, then we should resize our buffer to be large enough.
SignalReadPacket(this, buf_, static_cast<size_t>(len), remote_addr,
(timestamp > -1 ? timestamp : TimeMicros())); AsyncUDPSocket 的 SignalReadPacket 事件来自其父类 AsyncPacketSocket
} // AsyncPacketSocket 的 SignalReadPacket 事件是在 AllocationSequence::Init 中设置,将其绑定到了 AllocationSequence::OnReadPacket
void AllocationSequence::OnReadPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
const int64_t& packet_time_us) {
RTC_DCHECK(socket == udp_socket_.get()); udp_socket_ 是 AsyncPacketSocket 指针,且该指针指向 AsyncUDPSocket
bool turn_port_found = false;
// Try to find the TurnPort that matches the remote address. Note that the
// message could be a STUN binding response if the TURN server is also used as
// a STUN server. We don't want to parse every message here to check if it is
// a STUN binding response, so we pass the message to TurnPort regardless of
// the message type. The TurnPort will just ignore the message since it will
// not find any request by transaction ID.
for (auto* port : relay_ports_) {
if (port->CanHandleIncomingPacketsFrom(remote_addr)) {
if (port->HandleIncomingPacket(socket, data, size, remote_addr,
packet_time_us)) {
return;
}
turn_port_found = true;
}
}
if (udp_port_) { udp_port_ 是 UDPPort 指针(UDPPort内部包含 rtc::AsyncPacketSocket 指针,实际上指向 AsyncUDPSocket)
const ServerAddresses& stun_servers = udp_port_->server_addresses();
// Pass the packet to the UdpPort if there is no matching TurnPort, or if
// the TURN server is also a STUN server.
if (!turn_port_found ||
stun_servers.find(remote_addr) != stun_servers.end()) {
RTC_DCHECK(udp_port_->SharedSocket());
udp_port_->HandleIncomingPacket(socket, data, size, remote_addr,
packet_time_us); // UDPPort::HandleIncomingPacket
}
}
}
bool UDPPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
int64_t packet_time_us) {
// All packets given to UDP port will be consumed.
OnReadPacket(socket, data, size, remote_addr, packet_time_us);
return true;
}
void UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
const int64_t& packet_time_us) {
RTC_DCHECK(socket == socket_);
RTC_DCHECK(!remote_addr.IsUnresolvedIP());
// Look for a response from the STUN server.
// Even if the response doesn't match one of our outstanding requests, we
// will eat it because it might be a response to a retransmitted packet, and
// we already cleared the request when we got the first response.
if (server_addresses_.find(remote_addr) != server_addresses_.end()) {
requests_.CheckResponse(data, size); StunRequestManager::CheckResponse
return;
}
if (Connection* conn = GetConnection(remote_addr)) {
conn->OnReadPacket(data, size, packet_time_us);
} else {
Port::OnReadPacket(data, size, remote_addr, PROTO_UDP);
}
}
bool StunRequestManager::CheckResponse(const char* data, size_t size) {
// Check the appropriate bytes of the stream to see if they match the
// transaction ID of a response we are expecting.
if (size < 20)
return false;
std::string id;
id.append(data + kStunTransactionIdOffset, kStunTransactionIdLength);
RequestMap::iterator iter = requests_.find(id);
if (iter == requests_.end()) {
// TODO(pthatcher): Log unknown responses without being too spammy
// in the logs.
return false;
}
// Parse the STUN message and continue processing as usual.
rtc::ByteBufferReader buf(data, size);
std::unique_ptr<StunMessage> response(iter->second->msg_->CreateNew());
if (!response->Read(&buf)) {
RTC_LOG(LS_WARNING) << "Failed to read STUN response "
<< rtc::hex_encode(id);
return false;
}
return CheckResponse(response.get()); //
}
bool StunRequestManager::CheckResponse(StunMessage* msg) {
RequestMap::iterator iter = requests_.find(msg->transaction_id());
if (iter == requests_.end()) { // requests_ 原始消息的保存参考见 StunRequestManager::SendDelayed
// TODO(pthatcher): Log unknown responses without being too spammy
// in the logs.
return false;
}
StunRequest* request = iter->second; requests_ 中实际上保存的是 StunBindingRequest class StunBindingRequest : public StunRequest
if (!msg->GetNonComprehendedAttributes().empty()) {
// If a response contains unknown comprehension-required attributes, it's
// simply discarded and the transaction is considered failed. See RFC5389
// sections 7.3.3 and 7.3.4.
RTC_LOG(LS_ERROR) << ": Discarding response due to unknown "
"comprehension-required attribute.";
delete request;
return false;
} else if (msg->type() == GetStunSuccessResponseType(request->type())) {
request->OnResponse(msg); StunBindingRequest::OnResponse 直接在 StunBindingRequest 声明中定义
} else if (msg->type() == GetStunErrorResponseType(request->type())) {
request->OnErrorResponse(msg);
} else {
RTC_LOG(LERROR) << "Received response with wrong type: " << msg->type()
<< " (expecting "
<< GetStunSuccessResponseType(request->type()) << ")";
return false;
}
delete request;
return true;
}
void StunBindingRequest::OnResponse(StunMessage* response) override {
const StunAddressAttribute* addr_attr =
response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
if (!addr_attr) {
RTC_LOG(LS_ERROR) << "Binding response missing mapped address.";
} else if (addr_attr->family() != STUN_ADDRESS_IPV4 &&
addr_attr->family() != STUN_ADDRESS_IPV6) {
RTC_LOG(LS_ERROR) << "Binding address has bad family";
} else {
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port()); /// port_ 是 UDPPort 指针,具体参考上面的 UDPPort::SendStunBindingRequest
port_->OnStunBindingRequestSucceeded(this->Elapsed(), server_addr_, addr); // UDPPort::OnStunBindingRequestSucceeded
}
// The keep-alive requests will be stopped after its lifetime has passed.
if (WithinLifetime(rtc::TimeMillis())) {
port_->requests_.SendDelayed(
new StunBindingRequest(port_, server_addr_, start_time_),
port_->stun_keepalive_delay());
}
}
void UDPPort::OnStunBindingRequestSucceeded(
int rtt_ms,
const rtc::SocketAddress& stun_server_addr,
const rtc::SocketAddress& stun_reflected_addr) {
RTC_DCHECK(stats_.stun_binding_responses_received <
stats_.stun_binding_requests_sent);
stats_.stun_binding_responses_received++;
stats_.stun_binding_rtt_ms_total += rtt_ms;
stats_.stun_binding_rtt_ms_squared_total += rtt_ms * rtt_ms;
if (bind_request_succeeded_servers_.find(stun_server_addr) !=
bind_request_succeeded_servers_.end()) {
return;
}
bind_request_succeeded_servers_.insert(stun_server_addr);
// If socket is shared and |stun_reflected_addr| is equal to local socket
// address, or if the same address has been added by another STUN server,
// then discarding the stun address.
// For STUN, related address is the local socket address.
if ((!SharedSocket() || stun_reflected_addr != socket_->GetLocalAddress()) &&
!HasCandidateWithAddress(stun_reflected_addr)) {
rtc::SocketAddress related_address = socket_->GetLocalAddress();
// If we can't stamp the related address correctly, empty it to avoid leak.
if (!MaybeSetDefaultLocalAddress(&related_address)) {
related_address =
rtc::EmptySocketAddressWithFamily(related_address.family());
}
rtc::StringBuilder url;
url << "stun:" << stun_server_addr.ipaddr().ToString() << ":"
<< stun_server_addr.port();
AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address,
UDP_PROTOCOL_NAME, "", "", STUN_PORT_TYPE,
ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false); / is_final 又是 false
}
MaybeSetPortCompleteOrError();
}
// 前面收集 host candidate 的时候也调用了 Port::AddAddress 具体参考前面
void Port::AddAddress(const rtc::SocketAddress& address,
const rtc::SocketAddress& base_address,
const rtc::SocketAddress& related_address,
const std::string& protocol,
const std::string& relay_protocol,
const std::string& tcptype,
const std::string& type,
uint32_t type_preference,
uint32_t relay_preference,
const std::string& url,
bool is_final) {
if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
RTC_DCHECK(!tcptype.empty());
}
std::string foundation =
ComputeFoundation(type, protocol, relay_protocol, base_address);
Candidate c(component_, protocol, address, 0U, username_fragment(), password_, / 创建 candidate
type, generation_, foundation, network_->id(), network_cost_);
c.set_priority(
c.GetPriority(type_preference, network_->preference(), relay_preference));
c.set_relay_protocol(relay_protocol);
c.set_tcptype(tcptype);
c.set_network_name(network_->name());
c.set_network_type(network_->type());
c.set_url(url);
c.set_related_address(related_address);
bool pending = MaybeObfuscateAddress(&c, type, is_final);
if (!pending) {
FinishAddingAddress(c, is_final);
}
}
void Port::FinishAddingAddress(const Candidate& c, bool is_final) {
candidates_.push_back(c);
SignalCandidateReady(this, c); / 触发 BasicPortAllocatorSession::OnCandidateReady 具体参考前面
/// 注意这里的 this 是 Port 的指针,而实际上指向的是 UDPPort (收集 sflx candidate 使用的就是 UDPPort)
/// 这里触发调用 BasicPortAllocatorSession::OnCandidateReady , 此函数内部又触发 SignalPortReady(this, port);
/// 从而再次引发槽函数 P2PTransportChannel::OnPortReady 的调用,执行 ports_.push_back(port) ,从而将指向 TurnPort 的 Port
/// 指针保存到 ports_ ,所以在 P2PTransportChannel::CreateConnection 中调用 port->CreateConnection 创建 connection 时
会调用 UDPPort::CreateConnection 或者 TurnPort::CreateConnection
PostAddAddress(is_final);
}