tcp_v4_connect函数的解析

2023-09-13 16:28:40

源码:

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
  // 解析输入的地址结构
  struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
  // 获取 TCP 协议栈的全局 death_row 对象
  struct inet_timewait_death_row *tcp_death_row;
  // 获取输入的套接字的 inet_sock 和 tcp_sock 结构
  struct inet_sock *inet = inet_sk(sk);
  struct tcp_sock *tp = tcp_sk(sk);
  // 获取输入套接字的 IP 选项
  struct ip_options_rcu *inet_opt;
  // 获取套接字的网络命名空间
  struct net *net = sock_net(sk);
  __be16 orig_sport, orig_dport;
  __be32 daddr, nexthop;
  struct flowi4 *fl4;
  struct rtable *rt;
  int err;

  // 检查地址的有效性
  if (addr_len < sizeof(struct sockaddr_in))
    return -EINVAL;
  if (usin->sin_family != AF_INET)
    return -EAFNOSUPPORT;

  // 获取目标地址和下一跳地址
  nexthop = daddr = usin->sin_addr.s_addr;
  inet_opt = rcu_dereference_protected(inet->inet_opt,
               lockdep_sock_is_held(sk));
  if (inet_opt && inet_opt->opt.srr) {
    if (!daddr)
      return -EINVAL;
    nexthop = inet_opt->opt.faddr;
  }

  // 保存原始的源端口号和目的端口号
  orig_sport = inet->inet_sport;
  orig_dport = usin->sin_port;
  // 获取并设置用于连接的路由
  fl4 = &inet->cork.fl.u.ip4;
  rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
            sk->sk_bound_dev_if, IPPROTO_TCP, orig_sport,
            orig_dport, sk);
  // 检查路由连接的结果
  if (IS_ERR(rt)) {
    err = PTR_ERR(rt);
    if (err == -ENETUNREACH)
      IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
    return err;
  }

  // 检查路由是否指向多播或广播地址
  if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
    ip_rt_put(rt);
    return -ENETUNREACH;
  }

  // 更新目标地址为路由的目标地址
  if (!inet_opt || !inet_opt->opt.srr)
    daddr = fl4->daddr;

  // 获取 TCP 协议栈的 death_row 对象
  tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row;

  // 更新源 IP 地址
  if (!inet->inet_saddr) {
    err = inet_bhash2_update_saddr(sk,  &fl4->saddr, AF_INET);
    if (err) {
      ip_rt_put(rt);
      return err;
    }
  } else {
    sk_rcv_saddr_set(sk, inet->inet_saddr);
  }

  // 重置 TCP 相关的状态
  if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
    tp->rx_opt.ts_recent    = 0;
    tp->rx_opt.ts_recent_stamp = 0;
    if (likely(!tp->repair))
      WRITE_ONCE(tp->write_seq, 0);
  }

  // 设置目的端口号和目标地址
  inet->inet_dport = usin->sin_port;
  sk_daddr_set(sk, daddr);

  // 设置扩展头长度
  inet_csk(sk)->icsk_ext_hdr_len = 0;
  if (inet_opt)
    inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen;

  // 设置最大报文段长度
  tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT;

  // 设置套接字的状态为 SYN-SENT,并将其插入哈希表
  tcp_set_state(sk, TCP_SYN_SENT);
  err = inet_hash_connect(tcp_death_row, sk);
  if (err)
    goto failure;

  // 设置套接字的转发散列值
  sk_set_txhash(sk);

  // 设置新的端口并更新路由表
  rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
             inet->inet_sport, inet->inet_dport, sk);
  // 检查路由表更新的结果
  if (IS_ERR(rt)) {
    err = PTR_ERR(rt);
    rt = NULL;
    goto failure;
  }

  // 设置套接字的 GSO 类型并设置能力
  sk->sk_gso_type = SKB_GSO_TCPV4;
  sk_setup_caps(sk, &rt->dst);
  rt = NULL;

  // 设置初始化的 TCP 序列号和时间戳
  if (likely(!tp->repair)) {
    if (!tp->write_seq)
      WRITE_ONCE(tp->write_seq,
           secure_tcp_seq(inet->inet_saddr,
              inet->inet_daddr,
              inet->inet_sport,
              usin->sin_port));
    tp->tsoffset = secure_tcp_ts_off(net, inet->inet_saddr,
             inet->inet_daddr);
  }

  // 为套接字分配一个随机的标识符
  inet->inet_id = get_random_u16();

  // 如果启用了 TCP 快速打开,进行相应处理
  if (tcp_fastopen_defer_connect(sk, &err))
    return err;
  if (err)
    goto failure;

  // 发送 TCP 连接请求
  err = tcp_connect(sk);
  if (err)
    goto failure;

  return 0;

failure:
  // 失败时处理的操作
  tcp_set_state(sk, TCP_CLOSE);
  inet_bhash2_reset_saddr(sk);
  ip_rt_put(rt);
  sk->sk_route_caps = 0;
  inet->inet_dport = 0;
  return err;
}

EXPORT_SYMBOL(tcp_v4_connect);

tcp_v4_connect() 函数是在 Linux 内核中的 net/ipv4/tcp_ipv4.c 文件中定义的。它用于在 IPv4 网络上建立 TCP 连接。
该函数接受一个 TCP 套接字(sock)、目标地址(uaddr)和地址长度(addr_len)作为参数。
函数的主要功能包括解析地址、配置路由、设置套接字状态、分配序列号、设置时间戳、发送连接请求等步骤,最终返回连接的结果。

问题1:TCP 协议栈的 death_row 对象是什么?

TCP 协议栈的 death_row 对象是一个全局的数据结构,用于管理网络连接的生命周期。它的主要作用是处理关闭的连接,并在适当的时间回收连接资源。

在 TCP 协议中,当一条连接结束时(如连接被关闭或出现错误),该连接不会立即被释放,而是被放置在 death_row 中。death_row 在一段时间后会检查这些连接,并判断是否可以安全地回收它们。这段时间通常称为 “TIME_WAIT” 状态的时间。

在 TIME_WAIT 状态下,TCP 协议栈会保留连接的信息,以便处理网络中可能延迟到达的重复数据包。这样可以确保在网络中的所有数据包都被正确处理,从而保证可靠的连接关闭。

death_row 对象负责管理 TIME_WAIT 状态的连接。它维护了一个定时器,定期检查连接是否可以被回收。当连接经过一定时间后,death_row 将安全地释放这些连接占用的资源,以便可以重用这些资源来建立新的连接。

总的来说**,TCP 协议栈的 death_row 对象是用于管理关闭的 TCP 连接,并实现连接的安全释放和资源回收。**

问题2:struct flowi4在哪定义的?
struct flowi4 结构体是在 Linux 内核的头文件 include/net/flow.h 中定义的。该头文件是网络子系统中的一个重要头文件,包含了与网络流量处理相关的结构体和函数的声明。

下面是 struct flowi4 结构体的定义:

struct flowi4 {
    __aligned_u32    daddr;             /* Destination address */
    __aligned_u32    saddr;             /* Source address */
    __aligned_u16    flowi4_oif;        /* Output interface index */
    __aligned_u8     flowi4_tos;        /* TOS / traffic class */
#define       FLOWI4_TOS_MASK        0x1E
#define       FLOWI4_TOS_SHIFT       1
    __aligned_u8     flowi4_scope;      /* Scope */
#define       FLOWI4_SCOPE_MASK      0x0F
#define       FLOWI4_SCOPE_SHIFT     0
    __aligned_u32    flowi4_proto;      /* Protocol */
    __aligned_u8     flowi4_flags;      /* Flags */
    __aligned_u8     flowi4_secid;      /* SELinux security ID */
};

struct flowi4 结构体用于表示 IPv4 流量的路由查找信息。它的成员变量包括目标地址(daddr)、源地址(saddr)、输出接口索引(flowi4_oif)、TOS/流量类别(flowi4_tos)、作用域(flowi4_scope)、协议(flowi4_proto)、标志(flowi4_flags)和 SELinux 安全标识符(flowi4_secid)等。

该结构体提供了描述和标识 IPv4 流量路由的必要信息,用于在网络栈中进行路由查找和转发决策。

问题3:sk_daddr_set是什么?inet_csk是什么?在哪里定义的?

sk_daddr_set() 是 Linux 内核中的一个函数,用于设置套接字(socket)的目标地址(destination address)。它在文件 net/core/sock.c 中定义。

sk_daddr_set() 函数的定义如下:

static inline void sk_daddr_set(struct sock *sk, __be32 daddr)
{
    sk->__sk_common.skc_daddr = daddr;
}

sk_daddr_set() 函数通过修改套接字的 skc_daddr 成员变量来设置目标地址。这个成员变量保存了套接字关联的目标 IP 地址。

inet_csk 是一个宏,用于获取 TCP 套接字(struct sock)中 inet 控制块(struct inet_connection_sock)的指针。它定义在 include/net/inet_connection_sock.h 中。

下面是 inet_csk 宏的定义:

#define inet_csk(sk)        ((struct inet_connection_sock *)(sk)->sk_prot->data)

通过这个宏,可以方便地访问和操作与 TCP 连接相关的状态和参数,例如 TCP 的扩展头长度、SACK(Selective Acknowledgment)选项等。

更多推荐

vite+vue3+ts项目基础配置

1.配置项目启动自动打开浏览器在package.json文件中:"scripts":{"dev":"vite",//项目初始化之后默认是这样的-->"dev":"vite--open",//改成这样,加上--open,这样是默认打开浏览器页面},2.项目中eslint校验工具的配置首先安装eslintpnpmiesli

python机器人编程——用python实现一个写字机器人

目录一、前言二、整体框架2.1系统构成2.2硬件介绍2.2.1主要组成部分2.2.2机械结构2.2.3驱动及控制主板PS电机驱动原理简介:2.2.4其余部分2.3机器人python程序框架2.3.1通信服务模块2.3.2消息处理模块2.3.3轨迹解析模块2.3.4机械臂逆解模块2.3.5写字板模块三、机械臂的建模3.1

问道管理:机器人产业迎催化 黄金价格或将突破前高

昨日,沪指盘中震动下探,一度跌近1%逼近3100点,尾盘逐步止跌;深成指、创业板指均跌超1%。截至收盘,沪指跌0.45%报3123.07点,深成指跌1.14%报10255.87点,创业板指跌1.14%报2027.73点,科创50指数跌1.39%;两市合计成交6982亿元,北向资金净卖出近66亿元。行业方面,软件、轿车、

如何利用Java实现 AI 人脸融合特效

Java实现AI人脸融合特效项目背景AI人脸融合特效的原理代码实现第一步:调用token接口人脸融合部分工具类最终效果图项目背景最近自从chat-gpt爆火以来,AI技术在人工智能领域持续迭代的创新,为人们的生活带来了许多震撼的应用。比如其中的,AI人脸融合特效,在各大抖音、B站等平台上,越来越火热,基于这,我也打算利

使用 Next.js、Langchain 和 OpenAI 构建 AI 聊天机器人

在当今时代,将AI体验集成到您的Web应用程序中变得越来越重要。LangChain与Next.js的强大功能相结合,提供了一种无缝的方式来将AI驱动的功能引入您的应用程序。在本指南中,我们将学习如何使用Next.js,LangChain,OpenAILLM和VercelAISDK构建AI聊天机器人。文章目录Langch

细说 Spring Cloud Gateway

1.SpringCloudGateway简介与核心概念在微服务架构中,API网关是一个非常重要的组件,它可以帮助我们实现服务的路由、负载均衡、认证授权等功能。SpringCloudGateway是SpringCloud官方推出的一个基于Spring5、SpringBoot2和ProjectReactor的API网关实现

Linux centOS yum install MySQL5.7

下载并安装MySQLYUM仓库wgethttps://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpmsudoyumlocalinstallmysql57-community-release-el7-11.noarch.rpm这将为您的CentO

PKE 安全性的提升方式:Naor-Yung、Fischlin、Fujisaki-Okamoto

参考文献;[NY90]NaorM,YungM.Public-keycryptosystemsprovablysecureagainstchosenciphertextattacks[C]//Proceedingsofthetwenty-secondannualACMsymposiumonTheoryofcomputin

深入解析Perlin Simplex噪声函数:在C++中构建现代、高效、免费的3D图形背景

引言在计算机图形中,噪声是一个经常被讨论的话题。无论是为了制造自然的纹理,还是为了模拟复杂的现实世界现象,噪声函数都在其中起着关键作用。而在众多噪声函数中,PerlinSimplex噪声无疑是最受欢迎的一种。其原因不仅在于其干净、快速的特性,更因为其所提供的连续性和一致性非常适合图形渲染。本文将为你展示如何在C++中实

8路光栅尺磁栅尺编码器或16路高速DI脉冲信号转Modbus TCP网络模块 YL99-RJ45

特点:●光栅尺磁栅尺解码转换成标准ModbusTCP协议●高速光栅尺磁栅尺4倍频计数,频率可达5MHz●模块可以输出5V的电源给光栅尺或传感器供电●支持8个光栅尺同时计数,可识别正反转●可以设置作为16路独立DI高速计数器●可网页直接查看所有数据无需其他软件●编码器计数值和DI计数都支持断电自动保存●DI输入和网络通信

每天几道Java面试题:集合(第四天)

目录第四幕、第一场)大厦楼下门口第二场)大门口友情提醒背面试题很枯燥,加入一些戏剧场景故事人物来加深记忆。PS:点击文章目录可直接跳转到文章指定位置。第四幕、第一场)大厦楼下门口【面试者老王,门卫甲,门卫乙,面试者奥斯卡】门卫甲:天下熙熙皆为利来,天下攘攘皆为利往,像门卫乙和我这样不为名利专心看门,世界上又有多少人呢?

热文推荐