热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一道腾讯面试题目:没有listen,能否建立TCP连接

TCP与UDP最大的不同,就是有连接的概念,而连接的建立是由内核完成的。系统调用listen,就是为了告诉内核,它要处理发给

TCP与UDP最大的不同,就是有连接的概念,而连接的建立是由内核完成的。系统调用listen,就是为了告诉内核,它要处理发给这个TCP端口的连接请求。所以对于这个题目,最直接的想法就是由应用层自己负责TCP的连接。为了能够收到TCP的握手数据包,可以尝试使用原始套接字来接收IP报文,这样就可以在应用层替代内核做TCP的三次握手了。这个想法不错,可惜现实比较残酷。

当没有对于TCP 套接字处于listen状态时,使用raw socket处理握手报文时,即使收到了syn报文并给对端发送了syn+ack报文,也无法完成连接。因为内核一般会提前发送RST中断该连接。七年前,我当时只知道这个结果。现在呢,可以明确的知道为什么内核会发送RST报文,中断连接。内核在ip_local_deliver_finish先将报文复制一份给原始套接字,然后会继续后面的处理,进入tcp的接收函数tcp_v4_rcv。在这个函数中,要进行套接字的查找。
在这里插入图片描述

因为没有监听的tcp套接字,自然无法找到对应的套接字。于是跳转到no_tcp_socket。
在这里插入图片描述

在这个错误处理中,只要数据包skb的校验和没错,内核就会调用tcp_v4_send_reset发送RST中止这个连接。因此,这个单独使用raw socket的方案是行不通的。我们需要找到一种方法,禁止内核处理该TCP报文。这时,可以使用iptables的NFQUEUE,在网络层将数据包取到应用层,并回复syn+ack,同时在reinject时通知内核该数据包被“偷”了,即NF_STOLEN。这样内核就不会继续处理该数据包skb了。虽然当时我没有继续实验尝试,但理论上,通过这个IPtables的NFQUEUE+NFSTOLEN的方案,是可以实现“没有listen,建立TCP连接”的。

【文章福利】需要各一线互联网大厂面试题及C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)
在这里插入图片描述

在这里插入图片描述

可惜,在与那位同学的讨论中,腾讯面试题目的本意不是这个意思,而是对于普通的TCP套接字来说,如果没有listen调用,是否可以创建连接。即使限定了条件,答案依然是肯定的。只不过限定了条件之后,我们需要确定2个事情:

  1. 与前面类似,如何避免内核发送RST。在不能使用iptable的前提下,这意味着在tcp_v4_rcv中,要能够找到对应的套接字。
  2. 没有listen状态的套接字,内核是否能够完成TCP的三次握手呢?

确定第一个问题,比较简单。只需要对三次握手深入的思考一下,就可以得到答案。在正常的三次握手中,当服务端回复syn+ack时,客户端实际上也没有处于listen状态的套接字,但却可以完成三次握手。这意味着,客户端进行connect调用后,该套接字一定被加入到某个表中,并可以被匹配到。跟踪内核源码tcp_v4_connect->inet_hash_connect->__inet_check_established,可以看到当调用connect时,对应的套接字就被加入了全局的tcp已连接的表中,即tcp_hashinfo.ehash中。对应的匹配TCP套接字过程,如下__inet_lookup_skb->__inet_lookup
在这里插入图片描述

内核是先在已经连接的表中查找,再进行listen表的查找。对于客户端来说,syn+ack报文必然可以在已连接表中匹配上对应的套接字。那么,对于本题目来说,要想两端都可以找到套接字,就要求在报文到达前,两端都调用了connect。也就是说,当两端同时调用connect时,两端的syn包就都可以匹配上本地的套接字。

接下来只需要确定对于客户端套接字来说,收到syn报文,是否可以正常处理。tcp_rcv_synsent_state_process函数是TCP套接字处于synsent状态(即调用了connect)的处理函数。下面是其一部分实现代码:
在这里插入图片描述
在这里插入图片描述

从上,可以得出,对于处于synsent状态的套接字来说,如果收到了syn报文,则会正常回复synack,完成TCP三次握手。

对于腾讯的这道面试题目来说,其答案就是当两端同时发起connect调用时,即使没有listen调用,也可以成功创建TCP连接。

如果去掉“两端”的限制,还有一个答案就是,TCP套接字可以connect它本身bind的地址和端口,也可以达成要求。下面是测试代码,实现了一个TCP套接字成功连接自己,并发送消息。

#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include #define LOCAL_IP_ADDR (0x7F000001)
#define LOCAL_TCP_PORT (34567)int main(void)
{struct sockaddr_in local, peer;int ret;char buf[128];int sock = socket(AF_INET, SOCK_STREAM, 0);memset(&local, 0, sizeof(local));memset(&peer, 0, sizeof(peer));local.sin_family = AF_INET;local.sin_port = htons(LOCAL_TCP_PORT);local.sin_addr.s_addr = htonl(LOCAL_IP_ADDR);peer = local; int flag = 1;ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));if (ret == -1) {printf("Fail to setsocket SO_REUSEADDR: %s\n", strerror(errno));exit(1);}ret = bind(sock, (const struct sockaddr *)&local, sizeof(local));ret = connect(sock, (const struct sockaddr *)&peer, sizeof(peer));if (ret) {printf("Fail to connect myself: %s\n", strerror(errno));exit(1);}printf("Connect to myself successfully\n");strcpy(buf, "Hello, myself~");send(sock, buf, strlen(buf), 0);memset(buf, 0, sizeof(buf));recv(sock, buf, sizeof(buf), 0);printf("Recv the msg: %s\n", buf);close(sock);return 0;
}

其连接截图如下:
在这里插入图片描述

从截图中,可以看到TCP套接字成功的“连接”了自己,并发送和接收了数据包围。netstat的输出更证明了TCP的两端地址和端口是完全相同的。


推荐阅读
  • 小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限
    小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限 ... [详细]
  • Python 数据可视化实战指南
    本文详细介绍如何使用 Python 进行数据可视化,涵盖从环境搭建到具体实例的全过程。 ... [详细]
  • 如何在Linux服务器上配置MySQL和Tomcat的开机自动启动
    在Linux服务器上部署Web项目时,通常需要确保MySQL和Tomcat服务能够随系统启动而自动运行。本文将详细介绍如何在Linux环境中配置MySQL和Tomcat的开机自启动,以确保服务的稳定性和可靠性。通过合理的配置,可以有效避免因服务未启动而导致的项目故障。 ... [详细]
  • 本文详细介绍了在 CentOS 7 系统中配置 fstab 文件以实现开机自动挂载 NFS 共享目录的方法,并解决了常见的配置失败问题。 ... [详细]
  • 在CentOS 7环境中安装配置Redis及使用Redis Desktop Manager连接时的注意事项与技巧
    在 CentOS 7 环境中安装和配置 Redis 时,需要注意一些关键步骤和最佳实践。本文详细介绍了从安装 Redis 到配置其基本参数的全过程,并提供了使用 Redis Desktop Manager 连接 Redis 服务器的技巧和注意事项。此外,还探讨了如何优化性能和确保数据安全,帮助用户在生产环境中高效地管理和使用 Redis。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 帝国CMS中的信息归档功能详解及其重要性
    本文详细解析了帝国CMS中的信息归档功能,并探讨了其在内容管理中的重要性。通过归档功能,用户可以有效地管理和组织大量内容,提高网站的运行效率和用户体验。此外,文章还介绍了如何利用该功能进行数据备份和恢复,确保网站数据的安全性和完整性。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
  • 优化后的标题:PHP分布式高并发秒杀系统设计与实现
    PHPSeckill是一个基于PHP、Lua和Redis构建的高效分布式秒杀系统。该项目利用php_apcu扩展优化性能,实现了高并发环境下的秒杀功能。系统设计充分考虑了分布式架构的可扩展性和稳定性,适用于大规模用户同时访问的场景。项目代码已开源,可在Gitee平台上获取。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 在 Ubuntu 中遇到 Samba 服务器故障时,尝试卸载并重新安装 Samba 发现配置文件未重新生成。本文介绍了解决该问题的方法。 ... [详细]
  • 使用虚拟机配置服务器
    本文详细介绍了如何使用虚拟机配置服务器,包括购买云服务器的操作步骤、系统默认配置以及相关注意事项。通过这些步骤,您可以高效地配置和管理您的服务器。 ... [详细]
  • 在MySQL中更新密码时,首先需要在DOS窗口中切换到mysql安装目录,并使用`--skip-grant-tables`参数启动MySQL服务,以跳过权限表验证。接着,在MySQL命令行中执行相应的SQL语句来设置新密码。完成密码更新后,重启MySQL服务以使更改生效。此外,对于电脑快捷方式的修改,可以通过右键点击快捷方式,选择“属性”,在弹出的窗口中进行路径或目标的修改,最后点击“应用”和“确定”保存更改。 ... [详细]
  • 本文源自极分享,详细内容请参阅原文。技术债务如同信用卡负债,随着时间推移,修复成本会越来越高,因此程序员必须对此有深刻认识。此外,团队应致力于培养一种持续维护和优化代码的文化,以减少技术债务的累积。 ... [详细]
  • 字节跳动深圳研发中心安全业务团队正在火热招募人才! ... [详细]
author-avatar
200355人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有