EthernetOnTCP--基于Qt QSslSocket 套接字在PCAP 集线器上实现以太网隧道

news2024/11/19 11:29:40

在上一篇文章中,我们使用PCAP建立了本地的软件集线器(Hub)。考虑到较远距离的跨车间调试,有必要使用Tcp连接构造一个以太网的隧道,使得两个车间之间的调试设备可以虚拟的连接在一个Hub上。当然,我们可以使用QTcpSocket实现连接,或者尝试一下QSslSocket安全套接字,恰好使用前序文章中生成的证书,来玩一玩Ssl连接。

完整工程参考上一篇文章中的Git链接。

具有远程Ssl隧道的调试工作站的原理图如下:
远程调试工作站

1. 点对点TCP连接

我们希望两个调试工作站上的PCAPHub进程可以通过一个点对点的信道连接起来。工作站A 处于监听模式,工作站B处于Client模式。
PCAP集线器这样,一对TCP连接相当于一根逻辑的网线,与各个本地网口是对等的关系。上图中,所有远程的MAC地址都会出现在“TCP隧道对端连接的MAC栏”中。

2. 搭建SSL服务器

在Qt6中,设置了QSslServer类,可以直接构造Ssl服务器。但在Qt5中,并没有这个类。为了兼容Qt5 ,要重载QTcpServer,构造一个SslServer

//"sslserver.h"
class SSLServer : public QTcpServer
{
	Q_OBJECT
public:
	explicit SSLServer(QObject *parent = nullptr);
protected:
	void incomingConnection(qintptr socketDescriptor) override;
signals:
	void sig_newClient(qintptr socketDescriptor);
};
//CPP
void SSLServer::incomingConnection(qintptr socketDescriptor)
{
	emit sig_newClient(socketDescriptor);
}

而后,在QObject派生的TcpTunnel类中进行响应,sig_newClient会把套接字描述符泵给tcpTunnel 。在源码中,您会发现这些信号与曹都是在独立的线程中运行的,而不是主UI线程。tcpTunnel 类就是用来实例化工作在QThread上的对象,从而使用QThread独立的消息循环驱动信号、事件的流转。

class tcpTunnel : public QObject
{
	//...
	//Tunnel
protected:
	SSLServer * m_svr = nullptr;
	QTcpSocket * m_sock = nullptr;
protected slots:
	void slot_new_connection(qintptr socketDescriptor);
};
//New Connections
void tcpTunnel::slot_new_connection(qintptr socketDescriptor)
{
	QTcpSocket * sock = m_bSSL? new QSslSocket(this):new QTcpSocket(this);
	if (sock->setSocketDescriptor(socketDescriptor)) {
		connect(sock,&QTcpSocket::readyRead,this,&tcpTunnel::slot_read_sock);
		//SSL Handshake
		QSslSocket * sslsock = qobject_cast<QSslSocket*>(sock);
		if (sslsock)
		{
			QString strCerPath =  ":/certs/svr_cert.pem";
			QString strPkPath =  ":/certs/svr_privkey.pem";
			sslsock->setLocalCertificate(strCerPath);
			sslsock->setPrivateKey(strPkPath);
			sslsock->startServerEncryption();
		}
	}
}

这里需要解释的有以下几点:

  1. QSslSocket与QTcpSocket具备高度一致的状态机,也就是state()函数的行为高度一致。正因为如此,界面上通过一个简单的标记,可以开启或者关闭Ssl功能。
  2. 作为服务器套接字,要在握手前指定证书。这里使用范例证书直接嵌入在资源里,是有问题的。因为范例证书的服务器地址是127.0.0.1,和真实环境不同,会导致客户端连接失败。本例子里,客户端会忽略地址不一致的错误,这是一种不好的行为。在生产环境下,还是要根据具体情况准备合适的证书。

有了上述逻辑,tcpTunnel 就能够在客户端到来时,响应消息并接受连接开始握手。

3 客户端发起连接

在tcpTunnel类的槽函数里,会发起指向服务器的连接. 这里需要注意的是,要及时响应QSslSocket::sslErrors信号,并处理几类证书错误,以便在含有错误的证书情况下,依旧可以建立连接。相关的错误是:

  • QSslError::CertificateUntrusted 不信任的证书
  • QSslError::CertificateNotYetValid 尚未生效的证书(未来的时刻)
  • QSslError::CertificateExpired 过期的证书
  • QSslError::HostNameMismatch 证书的服务器地址和当前连接的HostAddress不同。

此外,对于Qt5,由于信号QSslSocket::sslErrors存在重载,导致必须要进行显式的类型约束,才能进行functional 样式的connect。Qt6里已经避免了这个问题。

//H
class tcpTunnel : public QObject
{
public slots:
	void startWork(QString address, QString port, bool listen,bool ssl);
};
//CPP
void tcpTunnel::startWork(QString address, QString port, bool ssl)
{
	if (m_bSSL)
	{
		QSslSocket * sslsock = new QSslSocket(this);
		connect(sslsock,static_cast<void (QSslSocket::*)(const QList <QSslError> &)>(&QSslSocket::sslErrors),
						[this,sslsock](const QList <QSslError> & err)->void
		{
					QList<QSslError> errIgnore;
					foreach (QSslError e, err)
					{
						emit sig_message(tr("SSL Error %1:%2 .").arg((int)e.error()).arg(e.errorString()));
						if (e.error()==QSslError::HostNameMismatch) errIgnore<<e;
						else if (e.error()==QSslError::CertificateUntrusted) errIgnore<<e;
						else if (e.error()==QSslError::CertificateNotYetValid) errIgnore<<e;
						else if (e.error()==QSslError::CertificateExpired) errIgnore<<e;
					}
					sslsock->ignoreSslErrors(errIgnore);
		});
				m_sock = sslsock;
				sslsock->connectToHostEncrypted(m_str_addr,m_n_port);
	}
	else
	{
		m_sock = new QTcpSocket(this);
		m_sock->connectToHost(QHostAddress(m_str_addr),m_n_port);
	}
	connect(m_sock,&QTcpSocket::readyRead,this,&tcpTunnel::slot_read_sock);
	emit sig_message(tr("connecting to: %1:%2").arg(m_str_addr).arg(m_n_port));
}

4. Ethernet on TCP 协议

在上一篇文章里,对于抓取的以太网数据,存储在一个环状高速缓存中。如果通过tcp传输以太网数据,需要设计一种切割包的协议。我们用最简单的方法来做。

Magic长度数据
4Bytes2Bytes UShortN1 Bytes
4Bytes2Bytes UShortN2 Bytes
4Bytes2Bytes UShortN3 Bytes
……

这样,只需要在接收时,检测并取数据即可完成分包。发包的代码如下:

			quint64 rp = PCAPIO::pcap_recv_pos;
			while (m_nTPos < rp) {
				const int nPOS = m_nTPos % PCAPIO_BUFCNT;
				const int fromID = PCAPIO::global_buffer[nPOS].from_id;
				if (fromID !=TCPTUNID
						&& PCAPIO::global_buffer[nPOS].len < 65536
						&& PCAPIO::global_buffer[nPOS].len >0)
				{
					const unsigned char  hd[] = {0x18u,0x24u,0x7eu,0x69u};
					const unsigned short len = PCAPIO::global_buffer[nPOS].len;
					m_sock->write((const char *)hd,4);
					m_sock->write((const char *)&len,2);
					m_sock->write(PCAPIO::global_buffer[nPOS].data.constData(),len);
				}
				++m_nTPos;
		}

收包的代码如下:

class tcpTunnel : public QObject
{
	void dealPack(const  char * pack, const int len);
	//Tunnel
private:
	quint64 m_nTPos = 0;
	QByteArray m_package_array;
protected slots:
	void slot_read_sock();
};

void tcpTunnel::slot_read_sock()
{
	QByteArray arrData = m_sock->readAll();
	m_package_array.append(arrData);
	while (static_cast<size_t>(m_package_array.size())>=6)
	{
		//检查Magic
		int goodoff = 0;
		while (!(m_package_array[0+goodoff]==(char)0x18 && m_package_array[1+goodoff]==(char)0x24
				 &&m_package_array[2+goodoff]==(char)0x7E  &&m_package_array[3+goodoff]==(char)0x69 ))
		{
			++goodoff;
			if (goodoff+3>= m_package_array.size())
				break;
		}
		if (goodoff)
			m_package_array.remove(0,goodoff);
		if (m_package_array.size()< 6 )
			break;
		const unsigned short  * ptrlen = (const unsigned short *)
				(m_package_array.constData() + 4);
		const unsigned short datalen = *ptrlen;
		if (m_package_array.size()<datalen+6)
			break;
		const char * dptr = m_package_array.constData()+6;
		//Enqueue入队
		dealPack(dptr,datalen);
		//清除当前包
		m_package_array.remove(0,6+datalen);
	}
}

5 避免嵌套风暴

通过上述操作,理论上可以把远程的以太网包带到本地。但是,由于TCP隧道本身也运行在以太网协议上,如果PCAP在抓取的时候,把TCP隧道的内容也给抓了,那样的话会出现流量风暴。

如何避免流量风暴呢?只要在抓取前,把 “not tcp port 12345” 这样的过滤条件追加在用户的条件之后,即可避免风暴生成。

//2. Run Cap Thread on interface.
	void recv_loop(QString itstr, QString filterStr, int id, int tcp_exlude,std::function<void (QString) > msg)
	{
		while (!pcap_stop)
		{
			pcap_t *handle = NULL;
			//...
			struct bpf_program filter;
			//Combine Filter
			QString ExFlt ;
			if (tcp_exlude > 0 && tcp_exlude < 65536)
			{
				if (filterStr.trimmed().length())
					ExFlt = "(" + filterStr.trimmed() + QString(") and not tcp port %1" ).arg(tcp_exlude);
				else
					ExFlt = QString("not tcp port %1" ).arg(tcp_exlude);
			}
			else
				ExFlt = filterStr.trimmed();
			//Compile Filter
			if (ExFlt.size())
			{
				std::string filter_app = ExFlt.toStdString();
				pcap_compile(handle, &filter, filter_app.c_str(), 0, net);
				pcap_setfilter(handle, &filter);
			}
		}
	}
}

当然,有时候这样做还不够,还要结合各个网口的抓包条件,共同进行约束。原则上,所有和隧道相关的流量都要排除在PCAP条件之外。

比如,如果这个TCP隧道是通过ssh的代理进行 -L或者-R接续的,则连带SSH也要排除。同时,由于HUB是低效的广播,要把类似远程桌面3389这种持续的流量全给关了。否则,可能会和设备抢夺带宽。

6 在HUB和交换机模式间灵活切换

我们只需要在像网卡回放时,通过一个开关来控制是否回放属于其他所有网口的内容,即可使得本网口工作于 集线器 或者 交换机模式下了。

	struct tag_packages{
		int from_id;//From which port
		int to_id;//To which port
		int len;
		QByteArray data;
	};
	void recv_loop(QString itstr, QString filterStr, int id, int tcp_exlude,std::function<void (QString) > msg)
	{
					//...Dst Mac
					const bool newDstMac = pcap_ports.contains(mac_dst);
					if (newDstMac)
					{
						if(pcap_ports[mac_dst].dtmLastAck.msecsTo(dtm) <=CAP_FADE_MSECS)
							dst_id = pcap_ports[mac_dst].curr_id;
					}
	}	
	//3. Run Send Thread on interface
	void send_loop(QString itstr,int id, bool bSwitchMod,std::function<void (QString) > msg)
	{
						if (global_buffer[pos].from_id!=id &&
						((!bSwitchMod)||(global_buffer[pos].to_id==id || global_buffer[pos].to_id==-1))
						)
				{
				}
	}

使用这个策略,在网口很多时,就能灵活的控制PCAPHub,使他工作在效率与范围兼顾的混合模式。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163394.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ROS】—— 机器人系统仿真 —Rviz中控制机器人模型运动与URDF集成Gazebo(十五)

文章目录前言1. Arbotix使用流程1.1 安装 Arbotix1.2 创建新功能包&#xff0c;准备机器人 urdf、xacro 文件1.3 添加 Arbotix 配置文件1.4 编写 launch 文件配置 Arbotix1.5 启动 launch 文件并控制机器人模型运动2. URDF集成Gazebo2.1 URDF与Gazebo基本集成流程2.2 URDF集成G…

Spring Boot整合Redis(gradle + gson + lombok + redisTemplate)

本文创建了gradle项目&#xff0c;用来整合Spring Boot和Redis&#xff0c;用到了gradle gson lombok redisTemplate等技术。 重要文件&#xff1a; 文件名说明build.gradlegradle配置文件Redis2Application.java启动类Controller.java控制器类StudentService.java业务逻辑…

深入解析Linux虚拟化KVM-Qemu分析之virtio设备

说明&#xff1a; KVM版本&#xff1a;5.9.1QEMU版本&#xff1a;5.0.0工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 先来张图&#xff1a; 图中罗列了四个关键模块&#xff1a;Virtio Device、Virtio Driver、Virtqueue、Notification&#xff08;eventfd…

Linux——简单了解文件与目录结构

1、 Linux 文件 1.1 概述 Linux系统 一切皆文件。 从我们刚接触到Linux系统&#xff0c;就能听到这句话&#xff1a;Linux系统 一切皆文件。 我们来看看Linux文件系统和Windos的差异&#xff1a; Windows &#xff0c;我们知道一台新的电脑到手之后&#xff0c;往往都只有一…

Vue2的双向绑定真的就是观察者模式吗?

导语建议先看看往期的推文&#xff0c;对vue响应式有一定理解后再阅读本文。Vue的双向绑定&#xff08;数据劫持&#xff09;响应式与观察者模式&#xff08;特别是附录&#xff0c;观察者模式与发布订阅模式&#xff09;关于Vue2深入响应式原理&#xff0c;作者原话为&#xf…

搭建nacos环境(保姆级教程)

2.2.1 服务发现中心 根据上节讲解的网关的架构图&#xff0c;要使用网关首先搭建Nacos。 首先搭建Nacos服务发现中心。 在搭建Nacos服务发现中心之前需要搞清楚两个概念&#xff1a;namespace和group namespace&#xff1a;用于区分环境、比如&#xff1a;开发环境、测试环…

【Linux】进程间管道通信、线程池

目录 一、进程间通信的概念 二、匿名管道 2.1 什么是管道 2.2 管道的实现 2.3 管道的使用 三、进程池 3.1 进程池实现逻辑 3.2 模拟任务表 3.3 进程池的创建 四、命名管道 4.1 创建命名管道 4.2 命令管道的使用 一、进程间通信的概念 进程具有独立性&#xff0c;…

面试系列:单点登录的知识(一)

大家好&#xff0c;我是车辙&#xff0c;由于目前接手的业务涉及到了单点登录&#xff0c;所以一直在疯狂的去补充这方面的知识。也写下了这篇面试形式的文章&#xff0c;写的不好大家轻点 Diss。 面试开始 在焦急的等待中&#xff0c;一位看上去比较年轻的小伙子走了过来。我…

Leetcode:701. 二叉搜索树中的插入操作(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 迭代&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 value &#xff0c;将值插入二…

Codeforces Round #843 (Div. 2)——A,B,C,E

​​​​​​​​​​​Dashboard - Codeforces Round #842 (Div. 2) - Codeforces A: 思维构造 题意&#xff1a;给定一个由 ab 组成的字符串&#xff0c;将该字符串拆分成 3 个部分&#xff08;a&#xff0c;b&#xff0c;c&#xff09;&#xff0c;要求中间部分的字典序最大…

2022 年终总结

在 12 月 31 号晚上这天&#xff0c;打开朋友圈大家都在告别 2022、迎接 2023&#xff0c;我却想不到任何值得发的内容。没有外出体会元旦的节日氛围&#xff0c;也没有观看任何跨年活动&#xff0c;2022 年最后一秒跟全年的 3153.6 万秒没有任何区别。 甚至这篇总结都差点没有…

RK3568源码编译与交叉编译环境搭建

本篇进行飞凌OK3568-C开发板的Linux系统开发需要用的软件交叉编译环境的配置。 对于软件开发&#xff0c;如果只是使用C/C代码&#xff0c;则在自己的Ubuntu虚拟机中添加RK3568对应的交叉编译器(gcc/g)即可&#xff0c;如果要进行Qt开发&#xff0c;则还要再交叉编译与RK3568配…

UDS诊断系列介绍09-1485服务

本文框架1. 系列介绍1.1 14服务概述1.2 85服务概述2. 14服务请求与应答2.1 14服务请求2.2 14服务正响应3. 85服务请求与应答3.1 85服务请求3.2 85服务正响应3.3 否定应答4. Autosar系列文章快速链接1. 系列介绍 UDS&#xff08;Unified Diagnostic Services&#xff09;协议&a…

graalvm+spring-cloud-gateway打造又快又小的类nginx本地网关

前言 网关是微服务架构的入口&#xff0c;外网请求通过网关转发到独立的微服务。项目一般会经过多个环境的测试&#xff0c;最终发布到生产。一个http请求&#xff0c;如&#xff1a;http://public_host/api/v1/some_service/some_path?ab&cd会先经过公网域名&#xff0c…

ThinkPHP5.x未开启强制路由(s参数)RCE

官方公告&#xff1a;https://blog.thinkphp.cn/869075 由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞&#xff0c;受影响的版本包括5.0和5.1版本 ThinkPHP5基础 环境搭建 官网直接下载完整包 https://www.thinkphp.cn/down/870.…

ElasticSearch集群架构及底层原理

前言ElasticSearch考虑到大数据量的情况&#xff0c;集群有很多的部署模式&#xff0c;本篇不会具体进行演示了&#xff0c;只是说明一下有哪些架构可以选&#xff0c;及一些原理的简单介绍&#xff0c;如果要看具体操作的那么可以自行进行搜索&#xff0c;这不是本篇博客要介绍…

OCR文字识别软件哪个好?7大文字识别软件

由于从各种文档中提取文本的需求非常普遍&#xff0c;许多办公软件或公司都提供了OCR工具。在本文中&#xff0c;我们为您推出了一系列功能强大且易于使用的最佳 OCR 软件。 什么是 OCR 软件&#xff1f; OCR 软件是一种程序或工具&#xff0c;可以使用光学字符识别技术识别数…

小红书数据分析网站:揭晓普通博主1个月涨粉百万的密码!

导语&#xff1a; 随着2023年的来临&#xff0c;回首小红书动态&#xff0c;行业热度依旧高涨&#xff0c;越来越多的达人涌入小红书。在时尚领域&#xff0c;更是出现了如氧化菊这样的大势变装博主&#xff01;短短一周涨粉13W的变装博主为何能突围&#xff0c;强势吸睛呢&am…

[LCTF]bestphp2022安洵杯 babyphp

目录 <1> [LCTF]bestphp‘s revenge SoapClient触发反序列化导致ssrf serialize_hander处理session方式不同导致session注入 crlf漏洞 <2> 安洵杯 babyphp SoapClient 触发ssrf session反序列化 利用文件操作原生类读取flag <3> XCTF Final Web1 解…

Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析

Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析 在学习Spring Cloud 时&#xff0c;遇到了授权服务oauth 相关内容时&#xff0c;总是一知半解&#xff0c;因此决定先把Spring Security 、Spring Security Oauth2 等权限、认证相关的内容、原理及设计学习…