Análisis del código fuente del protocolo WEBRTC TURN

WebRTC es un marco de medios de código abierto lanzado por Google en 2011. Puede realizar comunicaciones de audio y video en tiempo real en el navegador. Está diseñado para la comunicación P2P. Los desarrolladores también pueden construir sus propios servidores como un extremo de la comunicación. En los siguientes escenarios con condiciones de red estrictas, la comunicación no se puede establecer directamente y el reenvío debe reenviarse con la ayuda del servidor de tránsito TURN (Traversal Using Relays around NAT).

  1. Un extremo es un NAT simétrico y el otro extremo es un NAT de cono restringido por puerto o también un NAT simétrico , por lo que no se puede establecer P2P. Esta herramienta puede detectar el tipo de NAT de su propia red https://github.com/aarant/pynat
  2. En entornos con restricciones estrictas en la salida de la red, como bancos y agencias gubernamentales, las direcciones IP de la red externa que requieren acceso deben agregarse a su lista blanca de puerta de enlace. El servidor TURN se puede implementar de forma independiente y la dirección IP pública de TURN se puede agregar a la lista blanca.
  3. Un firewall con requisitos de seguridad extremadamente estrictos no permite la comunicación UDP, e incluso solo permite el tráfico TLS sobre el puerto 443.

análisis de procesos TURN


El protocolo se divide en tres partes : 1. Crear recursos
de transmisión en el servidor TURN  , que se denomina asignación 2. Modo de indicación para transmitir datos 3. Modo de canal para transmitir datos Cabe señalar que estas dos formas de transmisión de datos son paralelas . El diagrama de secuencia del proceso de tres partes es el siguiente (enlace de imagen  https://justme0.com/assets/pic/turn/seq.svg  ), el cliente se refiere al código WebRTC y el servidor se refiere al código pion/turn .




 

1. Crear recursos de asignación

La asignación es el recurso asignado por el servidor TURN al cliente. La estructura de datos es la siguiente y se enumeran los campos principales (consulte  https://github.com/pion/turn/blob/master/internal/allocation/allocation. ir #L23 para más detalles  ). Identifique una asignación con el quintuple fiveTuple <clientIP, clientPort, svrIP, svrPort, protocol>. El documento RFC más reciente del protocolo estipula TCP/UDP/TLS/DTLS. Pion aún no es compatible con DTLS. El puerto de escucha del servidor svrPort predeterminado es 3478 para TCP/UDP, 5349 para TLS/DTLS.

// FiveTuple is the combination (client IP address and port, server IP
// address and port, and transport protocol (currently one of UDP,
// TCP, or TLS)) used to communicate between the client and the
// server.  The 5-tuple uniquely identifies this communication
// stream.  The 5-tuple also uniquely identifies the Allocation on
// the server.
type FiveTuple struct {
	Protocol
	SrcAddr, DstAddr net.Addr
}

type Allocation struct {
	RelayAddr           net.Addr
	Protocol            Protocol
	TurnSocket          net.PacketConn
	RelaySocket         net.PacketConn
	fiveTuple           *FiveTuple
	permissionsLock     sync.RWMutex
	permissions         map[string]*Permission
	channelBindingsLock sync.RWMutex
	channelBindings     []*ChannelBind
	lifetimeTimer       *time.Timer
}

estructura de asignación




Preste especial atención a los campos de permisos y channelBindings en la estructura de datos.La clave de los permisos es la dirección del par, y channelBindings es una matriz, que también se identifica por la dirección del par. El flujo se describe a continuación con referencia al diagrama de secuencia.


1.1 Solicitud de enlace STUN


Al igual que la función STUN, devuelve la IP y el puerto del cliente, que se utiliza para informar el final de su propia dirección de exportación, recopilar candidatos locales y el servidor no tiene estado.

1.2 solicitud de asignación


Solicitud de asignación de recursos. El parámetro de solicitud identifica si UDP o TCP están entre TURN y el par. El cliente WebRTC codifica UDP. Tenga en cuenta que el documento RFC es una especificación y WebRTC es una implementación. No implementa todas las funciones especificadas en el estándar. Como se puede ver en el código fuente a continuación, la solicitud no contiene el código MAC AttrMessageIntegrity, devuelve un código de error 401 CodeUnauthorized y devuelve un reino y un número aleatorio. El anti-error aquí es un proceso normal, que consiste en llevar al final algunos parámetros del servidor.

func authenticateRequest(r Request, m *stun.Message, callingMethod stun.Method) (stun.MessageIntegrity, bool, error) {
	respondWithNonce := func(responseCode stun.ErrorCode) (stun.MessageIntegrity, bool, error) {
		nonce, err := buildNonce()
		if err != nil {
			return nil, false, err
		}

		// Nonce has already been taken
		if _, keyCollision := r.Nonces.LoadOrStore(nonce, time.Now()); keyCollision {
			return nil, false, errDuplicatedNonce
		}

		return nil, false, buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID,
			stun.NewType(callingMethod, stun.ClassErrorResponse),
			&stun.ErrorCodeAttribute{Code: responseCode},
			stun.NewNonce(nonce),
			stun.NewRealm(r.Realm),
		)...)
	}

	if !m.Contains(stun.AttrMessageIntegrity) {
		return respondWithNonce(stun.CodeUnauthorized)
	}
	...
}



Después de recibir la respuesta, configure el reino y el número aleatorio en el terminal, y calcule el código MAC = MD5 (nombre de usuario ":" reino ":" SASLprep (contraseña)). El código MAC se llevará en el siguiente paso. Consulte TurnAllocateRequest :: OnAuthChallenge  https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/turn_port.cc;l=1439


1.3 La segunda solicitud de asignación


El terminal transporta el código MAC, el reino y el número aleatorio devuelto en el paso anterior, solicita la asignación nuevamente, el servidor verifica el código MAC, crea la estructura de datos de asignación después de aceptar y asigna un temporizador al campo LifetimeTimer. El tiempo de vida por defecto es de 10 minutos, el terminal WebRTC enviará un latido para mantener vivo con un minuto de anticipación, es decir, enviará un latido cada 9 minutos, si el cliente quiere liberar recursos, complete el tiempo de vida con 0 en el parámetro de solicitud de actualización , que se discutirá en la siguiente sección. Después de asignar los recursos, el proceso del servidor vincula un puerto UDP para comunicarse con el par y el bucle for espera para recibir paquetes del par.
 

func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketConn, requestedPort int, lifetime time.Duration) (*Allocation, error) {
	...
	go a.packetHandler(m)
	...
}

// 从relay端口(UDP)收包处理
func (a *Allocation) packetHandler(m *Manager) {
	buffer := make([]byte, rtpMTU)

	for {
		n, srcAddr, err := a.RelaySocket.ReadFrom(buffer)
		if err != nil {
			m.DeleteAllocation(a.fiveTuple)
			return
		}

		a.log.Debugf("relay socket %s received %d bytes from %s",
			a.RelaySocket.LocalAddr().String(),
			n,
			srcAddr.String())
		...
	}
}


1.4 solicitud de actualización de asignación


El recurso se libera después de especificar cuánto tiempo (es decir, caducar) se especifica en el parámetro de solicitud. Si se pasa 0, significa que el recurso se libera inmediatamente, y caducar se actualiza regularmente al final, que es para mantenerse activo periódicamente.


2. Modo de indicación para transmitir datos

Descripción general de los métodos de indicación de RFC


2.1 permiso de creación


Antes de transferir datos en modo de indicación, primero solicite permiso. Al agregar un candidato remoto en el terminal, se crea un objeto TurnEntry y se envía una solicitud de permiso de creación en el constructor, y la dirección del par se incluye en el parámetro de solicitud.
 

TurnEntry::TurnEntry(TurnPort* port, Connection* conn, int channel_id)
    : port_(port),
      channel_id_(channel_id),
      ext_addr_(conn->remote_candidate().address()),
      state_(STATE_UNBOUND),
      connections_({conn}) {
  // Creating permission for `ext_addr_`.
  SendCreatePermissionRequest(0);
}


de  https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/turn_port.cc;l=1764
 

crear paquete de captura de permisos



Después de que el servidor recibe la señalización, agrega un kv al mapa de permisos de la estructura de asignación, la clave es la dirección del par, identifica el permiso y crea un temporizador al mismo tiempo. Si se agota el tiempo, elimine el permiso. el tiempo de espera es de 5 minutos y finalmente devuelve un paquete al final.

Después de recibir el paquete de respuesta, la terminal está lista para enviar el siguiente latido para mantenerse activo:
 

void TurnEntry::OnCreatePermissionSuccess() {
  RTC_LOG(LS_INFO) << port_->ToString() << ": Create permission for "
                   << ext_addr_.ToSensitiveString() << " succeeded";
  if (port_->callbacks_for_test_) {
    port_->callbacks_for_test_->OnTurnCreatePermissionResult(
        TURN_SUCCESS_RESULT_CODE);
  }

  // If `state_` is STATE_BOUND, the permission will be refreshed
  // by ChannelBindRequest.
  if (state_ != STATE_BOUND) {
    // Refresh the permission request about 1 minute before the permission
    // times out.
    TimeDelta delay = kTurnPermissionTimeout - TimeDelta::Minutes(1);
    SendCreatePermissionRequest(delay.ms());
    RTC_LOG(LS_INFO) << port_->ToString()
                     << ": Scheduled create-permission-request in "
                     << delay.ms() << "ms.";
  }
}


de  https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/turn_port.cc;l=1846


2.2 indicación


La señalización que transmite datos desde el terminal al servidor se denomina indicación de envío, y la señalización enviada desde el servidor al terminal se denomina indicación de datos. (Siento que este nombre no corresponde bien)

La sobrecarga de la indicación de envío es mayor que la del canal, con 36B, que especifica la dirección del par, es decir, a quién enviarlo. El servidor verificará si existe el permiso de acuerdo con la dirección del par y, si existe, extraerá los datos transportados y los enviará al par.

enviar paquete de captura de indicación



Al recibir el paquete del par, verifique si el modo de canal está establecido. Si está establecido, se prefiere el modo de canal. Si no está establecido, el modo de permiso se usa para enviarlo de vuelta al final. La estructura de indicación de datos es la igual que la indicación de envío.

Después de leer el método de transmisión anterior, ¿crees que hay algún problema?

Tenga en cuenta que el cliente puede especificar arbitrariamente la dirección del par al crear el permiso. Si el servicio se implementa en la intranet, el usuario puede escanear maliciosamente el servidor de la intranet, similar a la vulnerabilidad SSRF (falsificación de solicitud del lado del servidor), consulte este informe  https://hackerone.com/reports/333419?from_wecom=1

El atacante puede enviar conexiones TCP a la red interna mediante el establecimiento de `XOR-PEER-ADDRESS` del mensaje de conexión TURN (método `0x000A`,  https:// tools.ietf.org/html /rfc6062#section-4.3 ) a una dirección IPv4 privada.

Los paquetes UDP se pueden enviar mediante proxy configurando `XOR-PEER-ADDRESS` en una IP privada en la indicación de mensaje de envío TURN (método `0x0006`,  https://tools.ietf.org/html/rfc5766#section-10  ).

Por ejemplo, si un servidor en la intranet proporciona servicios HTTP para el uso de la intranet, al crear recursos, especifique el método TCP entre TURN y el par (pion/turn no es compatible, coturn es compatible) y, posteriormente, especifique el par al crear el permiso y enviar la indicación. address es la dirección de intranet (agotamiento de fuerza bruta), y la solicitud HTTP se envuelve en el protocolo TURN, de modo que se obtienen los datos en el servidor de intranet.

El método de transmisión que se presentará en la siguiente sección también tiene este problema. Como solución, si es solo para la transferencia WebRTC, y WebRTC solo usa UDP, puede deshabilitar la función de TURN para asignar puertos de retransmisión TCP y luego bloquear el Puertos UDP de protocolos de uso común. Para verificar la dirección del par, puede leer en detalle lo que dijo el autor en la publicación original.


3. Forma de canal para transmitir datos.

Descripción general de los métodos de canal de RFC


Solicitud de vinculación de 3.1 canales

Similar al método de permiso, solicita crear un canal antes de enviar datos. Solicita crear un canal cuando el objeto TurnEntry envía datos reales. Consulte el código a continuación, palabra clave SendChannelBindRequest.

int TurnEntry::Send(const void* data,
                    size_t size,
                    bool payload,
                    const rtc::PacketOptions& options) {
  rtc::ByteBufferWriter buf;
  if (state_ != STATE_BOUND ||
      !port_->TurnCustomizerAllowChannelData(data, size, payload)) {
    // If we haven't bound the channel yet, we have to use a Send Indication.
    // The turn_customizer_ can also make us use Send Indication.
    TurnMessage msg(TURN_SEND_INDICATION);
    msg.AddAttribute(std::make_unique<StunXorAddressAttribute>(
        STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_));
    msg.AddAttribute(
        std::make_unique<StunByteStringAttribute>(STUN_ATTR_DATA, data, size));

    port_->TurnCustomizerMaybeModifyOutgoingStunMessage(&msg);

    const bool success = msg.Write(&buf);
    RTC_DCHECK(success);

    // If we're sending real data, request a channel bind that we can use later.
    if (state_ == STATE_UNBOUND && payload) {
      SendChannelBindRequest(0);
      state_ = STATE_BINDING;
    }
  } else {
    // If the channel is bound, we can send the data as a Channel Message.
    buf.WriteUInt16(channel_id_);
    buf.WriteUInt16(static_cast<uint16_t>(size));
    buf.WriteBytes(reinterpret_cast<const char*>(data), size);
  }
  rtc::PacketOptions modified_options(options);
  modified_options.info_signaled_after_sent.turn_overhead_bytes =
      buf.Length() - size;
  return port_->Send(buf.Data(), buf.Length(), modified_options);
}


de  https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/turn_port.cc;l=1846

Hay un número de canal de campo importante en la solicitud de creación, que es proporcionado por el terminal Generado e identificado este canal, se estipula el valor del número de canal

- 0x0000-0x3FFF: no se puede utilizar como número de canal
- 0x4000-0x7FFF: el valor (16383) que se puede utilizar como número de canal
- 0x8000 -0xFFFF: Valor reservado, reservado para uso futuro

captura de paquetes de solicitud de enlace de canal




Después de recibir la solicitud de creación, el servidor agrega un elemento a la matriz channelBindings de la estructura de asignación. El número de canal o la dirección del par identifica el canal y crea un temporizador con un tiempo de espera de 10 minutos. Si el canal se ha creado, la actualización caduca. Además, la solicitud de vinculación actualizará la caducidad del permiso . Finalmente devuelva el paquete al final.

Después de recibir el paquete de retorno, el terminal también está listo para que se mantenga vivo el siguiente latido, y el latido se actualiza un minuto antes del tiempo de espera.

void TurnChannelBindRequest::OnResponse(StunMessage* response) {
  RTC_LOG(LS_INFO) << port_->ToString()
                   << ": TURN channel bind requested successfully, id="
                   << rtc::hex_encode(id())
                   << ", code=0"  // Makes logging easier to parse.
                      ", rtt="
                   << Elapsed();

  if (entry_) {
    entry_->OnChannelBindSuccess();
    // Refresh the channel binding just under the permission timeout
    // threshold. The channel binding has a longer lifetime, but
    // this is the easiest way to keep both the channel and the
    // permission from expiring.
    TimeDelta delay = kTurnPermissionTimeout - TimeDelta::Minutes(1);
    entry_->SendChannelBindRequest(delay.ms());
    RTC_LOG(LS_INFO) << port_->ToString() << ": Scheduled channel bind in "
                     << delay.ms() << "ms.";
  }
}


de  https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/turn_port.cc;l=1732

Datos de 3.2 canales


Como se puede ver en el código TurnEntry::Send() que envía datos reales en el apartado anterior, si se ha creado el modo canal al enviar datos, se transmitirá en modo canal (es decir, se envía el modo canal prioritario ), y su sobrecarga es solo 4B, en comparación con el modo de indicación Mucho menos sobrecarga:

captura de paquetes de datos de canal




Después de leer los dos métodos de transmisión anteriores, tengo una pregunta. El método de canal tiene menos sobrecarga que el método de permiso, y el vencimiento del permiso también se puede actualizar cuando se envía el latido. Para decirlo sin rodeos, la función es más poderosa. .¿Es posible usar solo el método del canal?Al principio (constructor de TurnEntry) ¿usar el enlace del canal en lugar del permiso de creación?

Esta pregunta se hizo en stackoverflow  https://stackoverflow.com/questions/75611078/why-not-use-only-channel-data-in-webrtc-turn-client  , un jefe de WebRTC mencionó el documento ICE RFC 5245, es Se recomienda crear un canal después de que se complete el proceso ICE, es decir, crear un par de candidatos después de seleccionarlo. De hecho, la documentación de TURN no se preocupa en absoluto por los datos específicos transportados. El concepto de candidato pertenece a WebRTC ICE, que se recomienda en la documentación de ICE. Creo que el lado nativo se puede optimizar y cambiar para usar solo el canal. método.

Finalmente, en resumen, el fondo del protocolo de la clase de solicitud necesita autenticar el código MAC , incluidas las solicitudes de asignación y actualización, las solicitudes de creación de permisos y las solicitudes de vinculación de canales. Hay tres tipos de temporizadores involucrados en TURN , correspondientes a las tres partes en el texto, y la terminal necesita enviar "latidos" para mantenerse activo regularmente, y el permiso puede mantenerse activo mediante enlace de canal.

Análisis del código fuente del protocolo WEBRTC TURN original-Conocimiento 

★La tarjeta de presentación al final del artículo puede recibir materiales de aprendizaje de desarrollo de audio y video de forma gratuita, incluidos (FFmpeg, webRTC, rtmp, hls, rtsp, ffplay, srs) y hojas de ruta de aprendizaje de audio y video, etc.

¡vea abajo!

 

Supongo que te gusta

Origin blog.csdn.net/yinshipin007/article/details/132307374
Recomendado
Clasificación