MSS de la opción TCP de pila de protocolo del kernel de Linux

Tabla de contenido

1 descripción general de MSS

2 Apretón de manos de tres vías del cliente

2.1 Enviar valor de opción MSS del segmento SYN

2.1.1 tcp_advertise_mss ()

2.1.2 Inicialización de tp-> advmss

2.2 Recibir segmento SYN + ACK

3 Apretón de manos de tres vías en el lado del servidor

3.1 Recepción de segmento SYN

3.2 Recibir mensaje ACK

3.2.1 Inicializar advmss

3.3 Enviar segmento SYN + ACK

4 Resumen del apretón de manos de tres vías

5 Obtenga MSS durante el envío

5.1 tcp_current_mss ()

5.2 tcp_sync_mss ()

5.2.1 tcp_mtu_to_mss ()

6 Resumen


1 descripción general de MSS

El concepto de MSS (Maximum Segment Size) se refiere al tamaño máximo de segmento que puede recibir la capa TCP, este valor solo incluye la parte de datos del segmento TCP, excluyendo la parte de opción.

Además, hay una opción MSS en el encabezado TCP. Durante el protocolo de enlace de tres vías, el remitente de TCP utiliza esta opción para decirle a la otra parte el tamaño máximo de segmento que puede aceptar. El valor de esta opción solo aparecerá en el segmento SYN, es decir, los dos primeros del protocolo de enlace de tres vías Veces .

Aunque la opción MSS existe como opción, en principio es prescindible, pero la mayoría de los procesos de comunicación TCP actuales llevarán esta opción, por lo que se puede decir que es la opción más importante en TCP.

En cuanto al contenido de MSS, incluye principalmente dos aspectos:

  1. ¿Cómo se determinan las opciones de MSS en el segmento SYN y el segmento SYN + ACK (algunos materiales mencionarán RMSS, es decir, recibir MSS)?
  2. ¿Cómo juega un papel MSS en el proceso de envío de paquetes después de que se establece la conexión (el SMSS mencionado en algunos materiales, es decir, enviar MSS)?

A partir del proceso de protocolo de enlace de tres vías entre el cliente y el servidor, podemos ver cómo se determina el RMSS. Desde el proceso de envío de datos en el estado conectado, podemos ver cómo funciona el SMSS.

2 Apretón de manos de tres vías del cliente

Las oportunidades para que el cliente procese MSS son:

  1. Al enviar el segmento SYN, dígale al servidor el MSS que el extremo local puede recibir;
  2. Reciba el MSS notificado por el servidor después de recibir el SYN + ACK.

2.1 Enviar valor de opción MSS del segmento SYN

El segmento SYN también se envía a través de tcp_transmit_skb (). En esta función, se llama a tcp_syn_build_options () para construir las opciones transportadas en el segmento SYN. ​​El código es el siguiente:

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
			    gfp_t gfp_mask)
{
...
	//如果发送的是SYN段,则调用tcp_syn_build_options()构造要携带的TCP选项,其中
	//MSS选项的值就是tcp_advertise_mss()的返回值
	if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) {
		tcp_syn_build_options((__be32 *)(th + 1),
				      tcp_advertise_mss(sk),
				      (sysctl_flags & SYSCTL_FLAG_TSTAMPS),
				      (sysctl_flags & SYSCTL_FLAG_SACK),
				      (sysctl_flags & SYSCTL_FLAG_WSCALE),
				      tp->rx_opt.rcv_wscale,
				      tcb->when,
				      tp->rx_opt.ts_recent,
#ifdef CONFIG_TCP_MD5SIG
				      md5 ? &md5_hash_location :
#endif
				      NULL);
	}
...
}

2.1.1 tcp_advertise_mss ()

Advertise significa publicidad Esta función se utiliza para calcular el valor de MSS a decir al extremo opuesto, correspondiente a la TCB, que en realidad calcula el valor de tp-> advmss en base a la MTU del dispositivo local.

static __u16 tcp_advertise_mss(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	//获取路由
	struct dst_entry *dst = __sk_dst_get(sk);
	//用tp->advmss初始化临时变量mss
	int mss = tp->advmss;

	//如果路由有效并且路由中的MSS比当前值小,那么用路由中的MSS更新tp->advmss
	//因为路由中的MSS是根据网络设备的MTU得来的,必须尊重,可以认为路由中的MSS
	//取值为min(65536-40, MTU-40),其中65535为IP报文的最大长度
	if (dst && dst_metric(dst, RTAX_ADVMSS) < mss) {
		mss = dst_metric(dst, RTAX_ADVMSS);
		tp->advmss = mss;
	}
	//返回确定的mss
	return (__u16)mss;
}

Como se puede ver en lo anterior, tcp_advertise_mss () en realidad toma el valor mínimo del tp-> advmss actual y la tabla de enrutamiento MSS, y este último es el MTU-40 de la tarjeta de red local. A continuación, debemos continuar mirando el tp-> advmss inicial Cómo se determina el valor.

2.1.2 Inicialización de tp-> advmss

Buscando el código podemos encontrar que la inicialización de este campo se completa en tcp_connect_init (), al cual es llamado por tcp_connect (), lo que significa que la inicialización del valor se realiza durante el envío del segmento SYN. ​​El código es el siguiente:

static void tcp_connect_init(struct sock *sk)
{
...
	//即也是来源于本端MTU
	tp->advmss = dst_metric(dst, RTAX_ADVMSS);
...
}

Para resumir: el valor de la opción MSS transportado en el segmento SYN es en realidad el MTU-40 del dispositivo de red local.

2.2 Recibir segmento SYN + ACK

El segmento SYN + ACK recibido se procesa en tcp_rcv_synsent_state_process (), en el que se llama a tcp_paser_option () para analizar las opciones transportadas en el segmento SYN. ​​El código relacionado con la opción MSS es el siguiente:

void tcp_parse_options(struct sk_buff *skb, struct tcp_options_received *opt_rx,
		       int estab)
{
...
	switch (opcode) {
	case TCPOPT_MSS:
		if (opsize == TCPOLEN_MSS && th->syn && !estab) {
			//取得选项中携带的MSS值记录到临时变量in_mss中
			u16 in_mss = ntohs(get_unaligned((__be16 *)ptr));
			if (in_mss) {
				//user_mss是应用程序通过TCP选项TCP_MAXSEG设定的,如果不设置,默认为0;
				//如果设定了user_mss并且设定的值小于对端通告的,那么调整in_mss为两者中的最小值。
				if (opt_rx->user_mss &&
					opt_rx->user_mss < in_mss)
					in_mss = opt_rx->user_mss;
				//将min(user_mss, in_mss)记录到mss_clamp中
				opt_rx->mss_clamp = in_mss;
			}
		}
		break;
	}
	...
}

Resumen: Después de recibir el MSS notificado por el servidor, el cliente lo compara con el valor de MSS establecido por la aplicación a través de TCP_MAXSEG, y guarda el valor menor de los dos en tp-> rx_opt.mss_clamp. Este valor será Afecta la determinación del cliente de enviar MSS, ver más abajo.

3 Apretón de manos de tres vías en el lado del servidor

La oportunidad para que el servidor procese MSS es:

  1. Procesar el MSS notificado por el cliente después de recibir el segmento SYN;
  2. Determinación del valor de la opción MSS transportada al enviar SYN + ACK;

3.1 Recepción de segmento SYN

El procesamiento central del segmento SYN se completa en tcp_v4_conn_request (), y el contenido relacionado con el análisis de opciones de MSS es el siguiente:

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
...
	//将SYN段中携带的选项先解析到临时变量tmp_opt中
	struct tcp_options_received tmp_opt;
	struct request_sock *req;
...

	//先清空临时变量
	tcp_clear_options(&tmp_opt);
	//将临时变量中的mss_clamp初始化为536
	tmp_opt.mss_clamp = 536;
	//user_mss设置为用户通过套接字选项TCP_MAXSEG设定的值
	tmp_opt.user_mss  = tcp_sk(sk)->rx_opt.user_mss;
	//该函数上面已经见过,它会比较SYN段中携带的MSS和user_mss,
	//然后取二者中较小者保存在tmp_opt.mss_clamp中
	tcp_parse_options(skb, &tmp_opt, 0);
...
	//初始化连接请求块
	tcp_openreq_init(req, &tmp_opt, skb);
...
}

static inline void tcp_openreq_init(struct request_sock *req,
				    struct tcp_options_received *rx_opt,
				    struct sk_buff *skb)
{
...
	//最终确定下来的MSS记录到了连接请求块的mss字段中
	req->mss = rx_opt->mss_clamp;
...
}

3.2 Recibir mensaje ACK

El mss registrado en el req recibirá el mensaje ACK del cliente después de que se complete el protocolo de enlace de tres vías. Cuando el servidor crea el TCB del sub-socket, se asignará a tp-> rx_opt.mss_clamp. El código es el siguiente:

struct sock *tcp_create_openreq_child(struct sock *sk, struct request_sock *req, struct sk_buff *skb)
{
...
	newtp->rx_opt.mss_clamp = req->mss;
...
}

Resumen: Desde el punto de vista del código, de hecho, después de que el servidor recibe el SYN, el flujo de procesamiento para las opciones de MSS es el mismo que el flujo de procesamiento para las opciones de MSS después de que el cliente recibe el segmento SYN + ACK.

3.2.1 Inicializar advmss

En lo anterior, no vimos la inicialización del lado del servidor de tp-> advmss. De hecho, este proceso se ejecuta después de recibir el tercer mensaje ACK del protocolo de enlace. El código es el siguiente:

struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
				  struct request_sock *req,
				  struct dst_entry *dst)
{
...
	//取值就是路由中的MSS值
	newtp->advmss = dst_metric(dst, RTAX_ADVMSS);
...
}

3.3 Enviar segmento SYN + ACK

El proceso de envío del segmento SYN + ACK se completa principalmente con tcp_v4_send_synack (), y el contenido relacionado con la opción MSS es el siguiente:

static int tcp_v4_send_synack(struct sock *sk, struct request_sock *req,
			      struct dst_entry *dst)
{
...
	//创建SYN+ACK段
	skb = tcp_make_synack(sk, dst, req);
...
}

struct sk_buff *tcp_make_synack(struct sock *sk, struct dst_entry *dst,
				struct request_sock *req)
{
...
	//为SYN+ACK段构造TCP选项,第二个参数就是要发送给客户端的MSS,来自于路由
	tcp_syn_build_options((__be32 *)(th + 1), dst_metric(dst, RTAX_ADVMSS), ireq->tstamp_ok,
			      ireq->sack_ok, ireq->wscale_ok, ireq->rcv_wscale,
			      TCP_SKB_CB(skb)->when,
			      req->ts_recent,
			      (
#ifdef CONFIG_TCP_MD5SIG
			       md5 ? &md5_hash_location :
#endif
			       NULL)
			      );
...
}

Resumen: Desde el punto de vista del código, de hecho, cuando el servidor envía el segmento SYN + ACK, el flujo de procesamiento para las opciones de MSS es el mismo que el flujo de procesamiento para las opciones de MSS cuando el cliente envía el segmento SYN.

4 Resumen del apretón de manos de tres vías

Como puede ver en el código anterior, ya sea un servidor o un cliente, tienen la misma selección del valor de MSS anunciado al par. Todos se toman del MTU-40 de la tarjeta de red local, y este valor se registrará en tp -> advmss; después de recibir el MSS notificado por el par, compárelo con el tp-> rx_opt.user_mss establecido por la aplicación, y guarde el más pequeño de los dos en tp-> rx_opt.mss_clamp, mss_clamp afectará La selección de enviar MSS durante el envío.

5 Obtenga MSS durante el envío

Se puede ver en tcp_sendmsg () de la transmisión de datos TCP que la lógica central de tcp_sendmsg () es cortar los datos que se enviarán en skb de acuerdo con MSS. En el proceso, el MSS de envío actual se determina llamando a tcp_current_mss (). Como se mencionó anteriormente en esta nota, el MSS notificado por el par finalmente se guarda en tp-> rx_opt. Mss_clamp después de la corrección. Según la razón, podemos usar este valor como envío de MSS, pero no es tan simple, como sigue:

int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
		size_t size)
{
...
	//获取当前有效的MSS,如果没有带外数据,就可以使用更大的段(TSO相关)
	mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));
	size_goal = tp->xmit_size_goal;
...
}

5.1 tcp_current_mss ()

Esta interfaz se utiliza para obtener el MSS actualmente válido, y establecerá la variable tp-> xmit_size_goal de acuerdo con el tamaño de la MTU. Esta variable se usará para organizar el skb en el futuro. Su valor está relacionado con las características de TSO, GSO, etc., que no es el propósito de esta nota Ignore el tema de discusión.

/* Compute the current effective MSS, taking SACKs and IP options,
 * and even PMTU discovery events into account.
 *
 * LARGESEND note: !urg_mode is overkill, only frames up to snd_up
 * cannot be large. However, taking into account rare use of URG, this
 * is not a big flaw.
 */
unsigned int tcp_current_mss(struct sock *sk, int large_allowed)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct dst_entry *dst = __sk_dst_get(sk);
	//下面最终决策出的当前有效发送MSS值会记录到该变量中
	u32 mss_now;
	u16 xmit_size_goal;
	int doing_tso = 0;

	//mss_now来自于tp->mss_cache,一脸懵逼,到目前为止还没见过该字段(见下文)
	mss_now = tp->mss_cache;

	//判断是否允许TSO,忽略
	if (large_allowed && sk_can_gso(sk) && !tp->urg_mode)
		doing_tso = 1;

	if (dst) {
		//获取路由中保存的PMTU值
		u32 mtu = dst_mtu(dst);
		//icsk_pmut_cookie为上次缓存的PMTU值,其初始值为本端MTU大小,
		//如果二者不等,则说明PMTU发生了变化,需要调用tcp_sync_mss()更新MSS
		if (mtu != inet_csk(sk)->icsk_pmtu_cookie)
			mss_now = tcp_sync_mss(sk, mtu);
	}
	//如果TCP有SACK选项,则从MSS中减去相应的开销
	if (tp->rx_opt.eff_sacks)
		mss_now -= (TCPOLEN_SACK_BASE_ALIGNED + (tp->rx_opt.eff_sacks * TCPOLEN_SACK_PERBLOCK));

#ifdef CONFIG_TCP_MD5SIG
	if (tp->af_specific->md5_lookup(sk, sk))
		mss_now -= TCPOLEN_MD5SIG_ALIGNED;
#endif

	//下面的代码用来确定xmit_size_goal的值,该值和TSO相关,先忽略
	xmit_size_goal = mss_now;
	if (doing_tso) {
		xmit_size_goal = (65535 -
				  inet_csk(sk)->icsk_af_ops->net_header_len -
				  inet_csk(sk)->icsk_ext_hdr_len -
				  tp->tcp_header_len);

		xmit_size_goal = tcp_bound_to_half_wnd(tp, xmit_size_goal);
		xmit_size_goal -= (xmit_size_goal % mss_now);
	}
	tp->xmit_size_goal = xmit_size_goal;
	//返回当前有效的MSS值
	return mss_now;
}

5.2 tcp_sync_mss ()

Esta función utiliza el parámetro pmtu para actualizar los campos relacionados con PMTU, donde icsk-> icsk_pmtu_cookie guarda el valor de PMTU previamente almacenado en caché. Después de calcular el MSS según el valor de PMTU, el resultado del cálculo se guarda en tp-> mss_cache. Este valor es el último MSS actual valor.

/* This function synchronize snd mss to current pmtu/exthdr set.

   tp->rx_opt.user_mss is mss set by user by TCP_MAXSEG. It does NOT counts
   for TCP options, but includes only bare TCP header.

   tp->rx_opt.mss_clamp is mss negotiated at connection setup.
   It is minimum of user_mss and mss received with SYN.
   It also does not include TCP options.

   inet_csk(sk)->icsk_pmtu_cookie is last pmtu, seen by this function.

   tp->mss_cache is current effective sending mss, including
   all tcp options except for SACKs. It is evaluated,
   taking into account current pmtu, but never exceeds
   tp->rx_opt.mss_clamp.

   NOTE1. rfc1122 clearly states that advertised MSS
   DOES NOT include either tcp or ip options.

   NOTE2. inet_csk(sk)->icsk_pmtu_cookie and tp->mss_cache
   are READ ONLY outside this function.		--ANK (980731)
 */
//注释很重要,仔细看
unsigned int tcp_sync_mss(struct sock *sk, u32 pmtu)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	int mss_now;

	if (icsk->icsk_mtup.search_high > pmtu)
		icsk->icsk_mtup.search_high = pmtu;
	//根据PMTU值计算MSS,这里除了标准的IP、TCP首部外,还会考虑IP选项、TCP选项
	mss_now = tcp_mtu_to_mss(sk, pmtu);
	//调整MSS为当前发送窗口的一半
	mss_now = tcp_bound_to_half_wnd(tp, mss_now);

	/* And store cached results */
	//将PMTU缓存到icsk_pmtu_cookie中
	icsk->icsk_pmtu_cookie = pmtu;
	if (icsk->icsk_mtup.enabled)
		mss_now = min(mss_now, tcp_mtu_to_mss(sk, icsk->icsk_mtup.search_low));
	//最终决策出来的MSS保存在mss_cache中
	tp->mss_cache = mss_now;

	return mss_now;
}

Nota: Hay varios campos que están fuertemente relacionados con el mecanismo PMTU que aún no han sido aclarados. Después de aclarar, regrese y agregue ...

5.2.1 tcp_mtu_to_mss ()

Esta función convierte el valor de MTU en MSS, considerando la opción IP y la opción TCP.

/* Not accounting for SACKs here. */
int tcp_mtu_to_mss(struct sock *sk, int pmtu)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	int mss_now;

	/* Calculate base mss without TCP options:
	   It is MMS_S - sizeof(tcphdr) of rfc1122
	 */
	//从MTU中减去标准TCP首部、IP首部
	mss_now = pmtu - icsk->icsk_af_ops->net_header_len - sizeof(struct tcphdr);

	/* Clamp it (mss_clamp does not include tcp options) */
	//MSS不能超过对端通告的MSS
	if (mss_now > tp->rx_opt.mss_clamp)
		mss_now = tp->rx_opt.mss_clamp;

	/* Now subtract optional transport overhead */
	//减去扩展首部,启用IPsec时,会有扩展首部
	mss_now -= icsk->icsk_ext_hdr_len;

	/* Then reserve room for full set of TCP options and 8 bytes of data */
	//MSS最小不能小于48字节
	if (mss_now < 48)
		mss_now = 48;

	/* Now subtract TCP options size, not including SACKs */
	//减去TCP选项长度(不包括选择ACK选项),tp->tcp_header_len的值是该TCP连接中可能的最大TCP
	//首部长度,该值是在三次握手过程中根据双方对TCP选项的支持情况确定的
	mss_now -= tp->tcp_header_len - sizeof(struct tcphdr);

	return mss_now;
}

Resumen: De hecho, en el proceso de protocolo de enlace de tres vías, TCP llamará a tcp_sync_mss () varias veces para actualizar el valor de MSS, pero el principio es el mismo. No es más que la MTU del dispositivo o la última actualización de PMTU actual, que no se enumerará aquí.

6 Resumen

Como se mencionó al principio, el contenido relacionado con MSS se puede dividir en dos partes: enviar MSS y recibir MSS. La lógica de procesamiento relacionada de estos dos tipos de MSS en el código se analiza en detalle. Aquí, se usa una imagen para mostrar la relación entre ellos:
Inserte la descripción de la imagen aquí
Figura 30 -9 es la regla de actualización para enviar MSS. La PMTU se actualizará a las métricas de enrutamiento [RTAX_MTU]. Cada vez que la operación de envío, volverá a verificar si el MSS de envío debe actualizarse. En este momento, el MSS en el enrutamiento y el último valor de PMTU almacenado en caché en icsk_pmtu_cookie se verificarán primero. Si los dos no son iguales , Lo que indica que la MTU ha cambiado durante las dos transmisiones, y la ruta MSS se utilizará para actualizar la icsk_pmtu_cookie. Luego, cuando decida enviar el MSS final, también se referirá al valor mss_clamp negociado durante el protocolo de enlace de tres vías para asegurarse de que el valor final no exceda este valor, y mss_clamp está limitado por la opción TCP TCP_MAXSEG.

La figura 30-10 muestra las reglas de determinación del SMS de notificación. Muy directamente, es la MTU del dispositivo de red.

Supongo que te gusta

Origin blog.csdn.net/wangquan1992/article/details/109028964
Recomendado
Clasificación