18.1 Socket 原生套接字抓包

news2024/11/16 15:30:59

原生套接字抓包的实现原理依赖于Windows系统中提供的ioctlsocket函数,该函数可将指定的网卡设置为混杂模式,网卡混杂模式(Promiscuous Mode)是常用于计算机网络抓包的一种模式,也称为监听模式。在混杂模式下,网卡可以收到经过主机的所有数据包,而非只接收它所对应的MAC地址的数据包。

一般情况下,网卡会根据MAC地址过滤数据包,只有MAC地址与网卡所对应的设备的通信数据包才会被接收和处理,其他数据包则会被忽略。但在混杂模式下,网卡会接收经过它所连接的网络中所有的数据包,这些数据包可以是面向其他设备的通信数据包、广播数据包或多播数据包等。

混杂模式可以通过软件驱动程序或网卡硬件实现。启用混杂模式的主要用途之一是网络抓包分析,使用混杂模式可以捕获网络中所有的数据包,且不仅仅是它所连接的设备的通信数据包。因此,可以完整获取网络中的通信内容,便于进行网络监控、安全风险感知、漏洞检测等操作。

Windows系统下,开启混杂模式可以使用ioctlsocket()函数,该函数原型定义如下:

int ioctlsocket (
   SOCKET s,        //要操作的套接字
   long cmd,        //操作代码
   u_long *argp     //指向操作参数的指针
);

其中,参数说明如下:

  • s: 要执行I/O控制操作的套接字。
  • cmd: 操作代码,用于控制对套接字的特定操作。
  • argp: 与特定请求代码相关联的参数指针。此参数的具体含义取决于请求代码。

在该函数中,参数cmd指定了I/O控制操作代码,是一个整数值,用于控制对套接字的特定操作。argp是一个指向特定请求代码相关联的参数的指针,它的具体含义将取决于请求代码。函数返回值为int类型,表示函数执行结果的状态码,若函数执行成功,则其返回值为0,否则返回一个错误代码,并将错误原因存入errno变量中。

要实现抓包前提是需要先选中绑定到那个网卡,如下InitAndSelectNetworkRawSocket函数则是实现绑定套接字到特定网卡的实现流程,在代码中首先初始化并使用gethostname函数获取到当前主机的主机名,主机IP地址等基本信息,接着通过循环的方式将自身网卡信息追加到g_HostIp全局结构体内进行存储,通过使用一个交互式选择菜单让用户可以选中需要绑定的网卡名称,当用户选中后则下一步是绑定套接字,并通过调用ioctlsocket函数将网卡设置为混杂模式,至此网卡的绑定工作就算结束了,当读者需要操作时只需要对全局变量进行操作即可,而选择函数仅仅只是获取到网卡信息而已并没有实际的作用。

#include <iostream>
#include <WinSock2.h>
#include <ws2tcpip.h>
#include <mstcpip.h>

#pragma comment(lib, "ws2_32.lib")

// 全局结构
typedef struct
{
  int iLen;
  char szIPArray[10][50];
}HOSTIP;

// 全局变量
SOCKET g_RawSocket = 0;
HOSTIP g_HostIp;

// -------------------------------------------------------
// 初始化与选择套接字
// -------------------------------------------------------
BOOL InitAndSelectNetworkRawSocket()
{
  // 设置套接字版本
  WSADATA wsaData = { 0 };
  if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
  {
    return FALSE;
  }
  // 创建原始套接字
  // Windows无法抓取RawSocket MAC层的数据包,只能抓到IP层及以上的数据包
  g_RawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
  // g_RawSocket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  if (INVALID_SOCKET == g_RawSocket)
  {
    WSACleanup();
    return FALSE;
  }

  // 绑定到接口 获取本机名
  char szHostName[MAX_PATH] = { 0 };
  if (SOCKET_ERROR == ::gethostname(szHostName, MAX_PATH))
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }

  // 根据本机名获取本机IP地址
  hostent* lpHostent = ::gethostbyname(szHostName);
  if (NULL == lpHostent)
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }

  // IP地址转换并保存IP地址
  g_HostIp.iLen = 0;
  strcpy(g_HostIp.szIPArray[g_HostIp.iLen], "127.0.0.1");
  g_HostIp.iLen++;
  char* lpszHostIP = NULL;

  while (NULL != (lpHostent->h_addr_list[(g_HostIp.iLen - 1)]))
  {
    lpszHostIP = inet_ntoa(*(in_addr*)lpHostent->h_addr_list[(g_HostIp.iLen - 1)]);
    strcpy(g_HostIp.szIPArray[g_HostIp.iLen], lpszHostIP);
    g_HostIp.iLen++;
  }

  // 选择IP地址对应的网卡来嗅探
  printf("选择侦听网卡 \n\n");
  for (int i = 0; i < g_HostIp.iLen; i++)
  {
    printf("\t [*] 序号: %d \t IP地址: %s \n", i, g_HostIp.szIPArray[i]);
  }

  printf("\n 选择网卡序号: ");
  int iChoose = 0;
  scanf("%d", &iChoose);

  // 如果选择超出范围则直接终止
  if ((0 > iChoose) || (iChoose >= g_HostIp.iLen))
  {
    exit(0);
  }
  if ((0 <= iChoose) && (iChoose < g_HostIp.iLen))
  {
    lpszHostIP = g_HostIp.szIPArray[iChoose];
  }

  // 构造地址结构
  sockaddr_in SockAddr = { 0 };
  RtlZeroMemory(&SockAddr, sizeof(sockaddr_in));
  SockAddr.sin_addr.S_un.S_addr = inet_addr(lpszHostIP);
  SockAddr.sin_family = AF_INET;
  SockAddr.sin_port = htons(0);

  // 绑定套接字
  if (SOCKET_ERROR == bind(g_RawSocket, (sockaddr*)(&SockAddr), sizeof(sockaddr_in)))
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }

  // 设置混杂模式 抓取所有经过网卡的数据包
  DWORD dwSetVal = 1;
  if (SOCKET_ERROR == ioctlsocket(g_RawSocket, SIO_RCVALL, &dwSetVal))
  {
    closesocket(g_RawSocket);
    WSACleanup();
    return FALSE;
  }
  return TRUE;
}

int main(int argc, char *argv[])
{
  // 选择网卡并设置网络为非阻塞模式
  BOOL SelectFlag = InitAndSelectNetworkRawSocket();
  if (SelectFlag == TRUE)
  {
    printf("[*] 网卡已被选中 套接字ID = %d | 套接字IP = %s \n", g_RawSocket,g_HostIp.szIPArray);
  }

  system("pause");
  return 0;
}

读者可自行编译并以管理员身份运行上述代码片段,当读者运行后会看到如下图所示的代码片段,此处笔者就选择三号网卡进行绑定操作,当绑定后此时套接字ID对应的则是特定的网卡,后续的操作均可针对此套接字ID进行,如下图所示;

当读者有了设置混杂模式的功能则下一步就是抓包了,抓包的实现很简单,只需要在开启了非阻塞混杂模式的网卡上使用recvfrom函数循环进行监听即可,当有数据包产生时则直接输出iRecvBytes中所存储的数据即可,这段代码的实现如下所示;

int main(int argc, char *argv[])
{
  // 选择网卡并设置网络为非阻塞模式
  BOOL init_flag = InitAndSelectNetworkRawSocket();
  if (init_flag == FALSE)
  {
    return 0;
  }

  sockaddr_in RecvAddr = { 0 };
  int iRecvBytes = 0;
  int iRecvAddrLen = sizeof(sockaddr_in);

  // 定义缓冲区长度
  DWORD dwBufSize = 12000;
  BYTE* lpRecvBuf = new BYTE[dwBufSize];

  // 循环接收接收
  while (1)
  {
    RtlZeroMemory(&RecvAddr, iRecvAddrLen);
    iRecvBytes = recvfrom(g_RawSocket, (char*)lpRecvBuf, dwBufSize, 0, (sockaddr*)(&RecvAddr), &iRecvAddrLen);
    if (0 < iRecvBytes)
    {
      // 接收数据包并输出
      printf("[接收数据包] %s \n", lpRecvBuf);
    }
  }

  // 释放内存
  delete[]lpRecvBuf;
  lpRecvBuf = NULL;

  // 关闭套接字
  Sleep(500);
  closesocket(g_RawSocket);
  WSACleanup();
  return 0;
}

当读者选择网卡后即可看到如下所示的输出结果,这些数据则是经过网卡192.168.9.125的所有数据,由于此处没有解码和区分数据包类型所以显示出的字符串并没有任何意义,如下图所示;

接下来我们就需要根据不同的数据包类型对这些数据进行解包操作,在解包之前我们需要先来定义几个关键的数据包结构体,如下代码中ether_header代表的是以太网包头结构,该结构占用14个字节的存储空间,arp_header则是ARP结构体,该结构体占用28个字节,ARK结构中还存在一个ARK报文结构,该结构占用42字节的内存长度,接着分别顶一个ipv4_headeripv6_headertcp_headerudp_header等结构体,这些结构体的完整定义如下所示;

#pragma pack(1)

/*以太网帧头格式结构体 14个字节*/
typedef struct ether_header
{
    unsigned char ether_dhost[6];  // 目的MAC地址
    unsigned char ether_shost[6];  // 源MAC地址
    unsigned short ether_type;     // eh_type 的值需要考察上一层的协议,如果为ip则为0x0800
}ETHERHEADER, * PETHERHEADER;

/*以ARP字段结构体 28个字节*/
typedef struct arp_header
{
    unsigned short arp_hrd;
    unsigned short arp_pro;
    unsigned char arp_hln;
    unsigned char arp_pln;
    unsigned short arp_op;
    unsigned char arp_sourha[6];
    unsigned long arp_sourpa;
    unsigned char arp_destha[6];
    unsigned long arp_destpa;
}ARPHEADER, * PARPHEADER;

/*ARP报文结构体 42个字节*/
typedef struct arp_packet
{
    ETHERHEADER etherHeader;
    ARPHEADER   arpHeader;
}ARPPACKET, * PARPPACKET;

/*IPv4报头结构体 20个字节*/
typedef struct ipv4_header
{
    unsigned char ipv4_ver_hl;        // Version(4 bits) + Internet Header Length(4 bits)长度按4字节对齐
    unsigned char ipv4_stype;         // 服务类型
    unsigned short ipv4_plen;         // 总长度(包含IP数据头,TCP数据头以及数据)
    unsigned short ipv4_pidentify;    // ID定义单独IP
    unsigned short ipv4_flag_offset;  // 标志位偏移量
    unsigned char ipv4_ttl;           // 生存时间
    unsigned char ipv4_pro;           // 协议类型
    unsigned short ipv4_crc;          // 校验和
    unsigned long  ipv4_sourpa;       // 源IP地址
    unsigned long  ipv4_destpa;       // 目的IP地址
}IPV4HEADER, * PIPV4HEADER;

/*IPv6报头结构体 40个字节*/
typedef struct ipv6_header
{
    unsigned char ipv6_ver_hl;
    unsigned char ipv6_priority;
    unsigned short ipv6_lable;
    unsigned short ipv6_plen;
    unsigned char  ipv6_nextheader;
    unsigned char  ipv6_limits;
    unsigned char ipv6_sourpa[16];
    unsigned char ipv6_destpa[16];
}IPV6HEADER, * PIPV6HEADER;

/*TCP报头结构体 20个字节*/
typedef struct tcp_header
{
    unsigned short tcp_sourport;     // 源端口
    unsigned short tcp_destport;     // 目的端口
    unsigned long  tcp_seqnu;        // 序列号
    unsigned long  tcp_acknu;        // 确认号
    unsigned char  tcp_hlen;         // 4位首部长度
    unsigned char  tcp_reserved;     // 标志位
    unsigned short tcp_window;       // 窗口大小
    unsigned short tcp_chksum;       // 检验和
    unsigned short tcp_urgpoint;     // 紧急指针
}TCPHEADER, * PTCPHEADER;

/*UDP报头结构体 8个字节*/
typedef struct udp_header
{
    unsigned short udp_sourport;   // 源端口 
    unsigned short udp_destport;   // 目的端口
    unsigned short udp_hlen;       // 长度
    unsigned short udp_crc;        // 校验和
}UDPHEADER, * PUDPHEADER;
#pragma pack()

当有了结构体的定义部分,则实现对数据包的解析只需要判断数据包的类型并使用不同的结构体对数据包进行解包打印即可,如下是实现数据包解析的完整代码,在代码中分别实现了几个核心函数,其中printData函数可以实现对特定内存数据的十六进制格式输出方便检查输出效果,函数AnalyseRecvPacket_All用于解析除去TCP/UDP格式的其他数据包,AnalyseRecvPacket_TCP用于解析TCP数据,AnalyseRecvPacket_UDP用于解析UDP数据,在主函数中通过使用ip->ipv4_pro判断数据包的具体类型,并根据类型的不同依次调用不同的函数实现数据包解析。

// 输出数据包
void PrintData(BYTE* lpBuf, int iLen, int iPrintType)
{
  // 16进制
  if (0 == iPrintType)
  {
    for (int i = 0; i < iLen; i++)
    {
      if ((0 == (i % 8)) && (0 != i))
      {
        printf("  ");
      }
      if ((0 == (i % 16)) && (0 != i))
      {
        printf("\n");
      }
      printf("%02x ", lpBuf[i]);

    }
    printf("\n");
  }
  // ASCII编码
  else if (1 == iPrintType)
  {
    for (int i = 0; i < iLen; i++)
    {
      printf("%c", lpBuf[i]);
    }
    printf("\n");
  }
}

// 解析所有其他数据包
void AnalyseRecvPacket_All(BYTE* lpBuf)
{
  struct sockaddr_in saddr, daddr;
  PIPV4HEADER ip = (PIPV4HEADER)lpBuf;
  saddr.sin_addr.s_addr = ip->ipv4_sourpa;
  daddr.sin_addr.s_addr = ip->ipv4_destpa;

  printf("From:%s --> ", inet_ntoa(saddr.sin_addr));
  printf("To:%s\n", inet_ntoa(daddr.sin_addr));
}

// 解析TCP数据包
void AnalyseRecvPacket_TCP(BYTE* lpBuf)
{
  struct sockaddr_in saddr, daddr;
  PIPV4HEADER ip = (PIPV4HEADER)lpBuf;
  PTCPHEADER tcp = (PTCPHEADER)(lpBuf + (ip->ipv4_ver_hl & 0x0F) * 4);
  int hlen = (ip->ipv4_ver_hl & 0x0F) * 4 + tcp->tcp_hlen * 4;

  // 这里要将网络字节序转换为本地字节序
  int dlen = ntohs(ip->ipv4_plen) - hlen;
  saddr.sin_addr.s_addr = ip->ipv4_sourpa;
  daddr.sin_addr.s_addr = ip->ipv4_destpa;

  printf("From:%s:%d --> ", inet_ntoa(saddr.sin_addr), ntohs(tcp->tcp_sourport));
  printf("To:%s:%d  ", inet_ntoa(daddr.sin_addr), ntohs(tcp->tcp_destport));
  printf("ack:%u  syn:%u length=%d\n", tcp->tcp_acknu, tcp->tcp_seqnu, dlen);

  PrintData((lpBuf + hlen), dlen, 0);
}

// 解析UDP数据包
void AnalyseRecvPacket_UDP(BYTE* lpBuf)
{
  struct sockaddr_in saddr, daddr;
  PIPV4HEADER ip = (PIPV4HEADER)lpBuf;
  PUDPHEADER udp = (PUDPHEADER)(lpBuf + (ip->ipv4_ver_hl & 0x0F) * 4);
  int hlen = (int)((ip->ipv4_ver_hl & 0x0F) * 4 + sizeof(UDPHEADER));
  int dlen = (int)(ntohs(udp->udp_hlen) - 8);

  //  int dlen = (int)(udp->udp_hlen - 8);
  saddr.sin_addr.s_addr = ip->ipv4_sourpa;
  daddr.sin_addr.s_addr = ip->ipv4_destpa;
  printf("Protocol:UDP  ");
  printf("From:%s:%d -->", inet_ntoa(saddr.sin_addr), ntohs(udp->udp_sourport));
  printf("To:%s:%d\n", inet_ntoa(daddr.sin_addr), ntohs(udp->udp_destport));

  PrintData((lpBuf + hlen), dlen, 0);
}

int main(int argc, char* argv[])
{
  // 选择网卡,并设置网络为非阻塞模式
  InitAndSelectNetworkRawSocket();

  sockaddr_in RecvAddr = { 0 };
  int iRecvBytes = 0;
  int iRecvAddrLen = sizeof(sockaddr_in);
  DWORD dwBufSize = 12000;
  BYTE* lpRecvBuf = new BYTE[dwBufSize];

  // 循环接收接收
  while (1)
  {
    RtlZeroMemory(&RecvAddr, iRecvAddrLen);
    iRecvBytes = recvfrom(g_RawSocket, (char*)lpRecvBuf, dwBufSize, 0, (sockaddr*)(&RecvAddr), &iRecvAddrLen);
    if (0 < iRecvBytes)
    {
      // 接收数据包解码输出
      // 分析IP包的协议类型
      PIPV4HEADER ip = (PIPV4HEADER)lpRecvBuf;
      switch (ip->ipv4_pro)
      {
      case IPPROTO_ICMP:
      {
                // 分析ICMP
        printf("[ICMP]\n");
        AnalyseRecvPacket_All(lpRecvBuf);
        break;
      }
      case IPPROTO_IGMP:
      {
                // 分析IGMP
        printf("[IGMP]\n");
        AnalyseRecvPacket_All(lpRecvBuf);
        break;
      }
      case IPPROTO_TCP:
      {
        // 分析tcp协议
        printf("[TCP]\n");
        AnalyseRecvPacket_TCP(lpRecvBuf);
        break;
      }
      case IPPROTO_UDP:
      {
        // 分析udp协议
        printf("[UDP]\n");
        AnalyseRecvPacket_UDP(lpRecvBuf);
        break;
      }
      default:
      {
                // 其他数据包
        printf("[OTHER IP]\n");
        AnalyseRecvPacket_All(lpRecvBuf);
        break;
      }
      }
    }
  }

  // 释放内存
  delete[]lpRecvBuf;
  lpRecvBuf = NULL;

  // 关闭套接字
  Sleep(500);
  closesocket(g_RawSocket);
  WSACleanup();
  return 0;
}

读者可自行编译并运行上述代码片段,当程序运行后可自行选择希望监控的网卡,当程序中检测到TCP数据包后会输出如下图所示的提示信息,在图中我们可以清晰的看出数据包的流向信息,以及数据包长度数据包内的数据等;

当读者通过使用Ping命令探测目标主机时,此时同样可以抓取到ICMP相关的数据流,只是在数据解析时并没有太规范导致只能看到简单的流向,当然读者也可以自行完善这段代码,让其能够解析更多参数。

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/8e15eea.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

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

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

相关文章

从github下载文件时遇到报错(Unable to render code block)解决办法

1、报错情况 2、解决办法 https://ghproxy.com/ &#xff08;GitHub 文件 , Releases , archive , gist , raw.githubusercontent.com 文件代理加速下载服务&#xff09;

提高生产力,开启高效办公——ConceptDraw Office办公软件套件

在当今快节奏的工作环境中&#xff0c;一款强大的办公软件套件对于提高工作效率和生产力至关重要。ConceptDraw Office&#xff0c;作为一款专业的办公软件套件&#xff0c;凭借其强大的功能和易用性&#xff0c;成为了市场上备受瞩目的办公利器。本文将带您深入了解ConceptDra…

Qt扫盲-QPixmap理论总结

QPixmap 理论总结 一、概述二、读写 Image 文件三、Pixmap 像素图信息四、Pixmap 格式转换五、Pixmap 像素转换 一、概述 QPixmap 也是一个用得很多的描述图像的类&#xff0c;也是界面显示相关的时候用得很多的。 QPixmap类是一个可以用作绘画设备的屏幕外图像表示。Qt提供了…

文生图——DALL-E 3 —论文解读——第一版

概述 本文主要是DALLE 3官方第一版技术报告&#xff08;论文&#xff09;的解读&#xff0c;原文《Improving Image Generation with Better Captions》论文解读。该文要提升文生图的效果&#xff0c;将技术点放到了&#xff0c;提升指令跟随能力上&#xff0c;然后顺藤摸瓜分为…

Linux下挂载大于2T的硬盘

fdisk和gdisk命令的区别 fdisk和gdisk命令都可以给磁盘分区&#xff0c;但是在公司里&#xff0c;如果磁盘大于2T&#xff0c;不能用fdisk分区&#xff0c;只能用gdisk命令分区。 gdisk可以分128个主分区&#xff0c;用gdisk分区没有逻辑分区和扩展分区之说&#xff0c;只有主…

【FPGA零基础学习之旅#17】搭建串口收发与储存双口RAM系统

&#x1f389;欢迎来到FPGA专栏~搭建串口收发与储存双口RAM系统 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0…

超声波清洗机品牌哪些好用?好评不断的超声波清洗机推荐

超声波清洗机目前的使用范围逐渐变广&#xff0c;一开始超声波清洗机只出现在大型的工业领域中的零件清洗&#xff0c;逐渐衍生到现在&#xff0c;出现了小型的超声波清洗机&#xff0c;可以让大家可以在家也使用上超声波清洗机。眼镜是现在大部分都离不开视线辅助的一个工具&a…

越流行的大语言模型越不安全

源自&#xff1a;GoUpSec “人工智能技术与咨询” 发布 安全研究人员用OpenSSF记分卡对GitHub上50个最流行的生成式AI大语言模型项目的安全性进行了评估&#xff0c;结果发现越流行的大语言模型越危险。 近日&#xff0c;安全研究人员用OpenSSF记分卡对GitHub上50个最流…

Powershell脚本自动备份dhcp数据库

文章目录 为什么要备份DHCP数据库呢&#xff1f;在PowerShell中自动备份DHCP数据库1&#xff0c;创建备份目录2&#xff0c;判断备份路径是否存在3&#xff0c;备份DHCP数据库4&#xff0c;完整自动备份脚本5&#xff0c;安排定期备份 推荐阅读 为什么要备份DHCP数据库呢&#…

故障解析丨Clone节点导致主从故障

1.背景概述 在一次主从复制架构中&#xff0c;由于主节点binlog损坏&#xff0c;导致从节点无法正常同步数据&#xff0c;只能重做从节点&#xff1b;因此使用MySQL 8.0.17开始提供的clone技术进行恢复&#xff0c;恢复后的2天都发生了主从报错数据冲突。 通过解析binlog发现…

网页轮播图

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>纯CSS实现轮播图(自动轮播)</title><style&…

Warning: ‘Destination Folder‘ contains 1 space.【Anaconda安装】

报错内容如下&#xff1a; 意思就是说你的安装路径下不要有空格哈哈&#xff0c;有空格就不行&#xff0c;比如&#xff1a; "D:\Program Files\Anaconda3"中间就有空格&#xff0c;Program与Files之间。 换个路径&#xff0c;例如&#xff1a; 就可以了。

EmbedChain:比LangChain更加轻量化的LLM框架

一、前言 在之前的文章中&#xff0c;我们研究了如何使用LangChain结合大型语言模型&#xff08;LLM&#xff09;API来构建用户友好且直观的聊天机器人。现在&#xff0c;我们将探索一个新的Python包来进一步简化LangChain的实现。只需3-4行代码&#xff0c;我们就可以轻松地与…

Runner GoUI自动化测试发布

构建自动化测试体系是当下每个项目团队愿意去做的&#xff0c;自动化测试减少重复操作节省人力成本。 RunnerGo UI自动化平台 RunnerGo提供从API管理到API性能再到可视化的API自动化、UI自动化测试功能模块&#xff0c;覆盖了整个产品测试周期。 RunnerGo UI自动化基于Selen…

APUS入驻百度灵境矩阵,普惠AI大模型插件能力

10月17日&#xff0c;APUS出席百度世界大会2023。会上&#xff0c;百度公布了灵境矩阵业务进展&#xff0c;APUS作为灵境矩阵首批合作伙伴正与百度携手拓展大模型能力边界、构建大模型应用生态。 百度认为&#xff0c;大模型将繁荣AI应用生态&#xff0c;在生态搭建过程中&…

springboot maven项目环境搭建idea

springboot maven项目环境搭建idea 文章目录 springboot maven项目环境搭建idea用到的软件idea下载和安装java下载和安装maven下载和安装安装maven添加JAVA_HOME路径&#xff0c;增加JRE环境修改conf/settings.xml&#xff0c;请参考以下 项目idea配置打开现有项目run或build打…

uview 1 uni-app表单 number digit 的输入框有初始化赋值后,但是校验失败

背景&#xff1a; 在onReady初始化规则 onReady() { this.$refs.uForm.setRules(this.rules); }, 同时&#xff1a;ref,model,rules,props都要配置好。 报错 当input框限定type为number&#xff0c;digit类型有初始值不做修改动作,直接提交会报错&#xff0c;验…

仿美团外卖微信小程序源码/美团外卖优惠券领劵小程序-自带流量主模式

源码简介&#xff1a; 仿美团外卖微信小程序源码&#xff0c;它是美团外卖优惠券领劵小程序&#xff0c;还自带流量主模式。可以领取外卖优惠券的小程序。实用方便。 美团优惠券小程序带举牌小人带菜谱流量主模式&#xff0c;挺多外卖小程序的&#xff0c;但是都没有搭建教程…

Leetcode每日一题6.05:二叉树搜索树BST

二叉搜索树&#xff08;BST&#xff09; 根节点大于等于左子树所有节点&#xff0c;小于等于右子树所有节点。 二叉搜索树中序遍历即为节点从小到大排序。 230. 二叉搜索树中第K小的元素 题目描述&#xff1a; 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &a…

tcpdump 异常错误

tcpdump 进行抓包的时候&#xff0c;-w 提示 Permission denied&#xff1a; sudo tcpdump -w test1.log tcpdump: test1.log: Permission denied 开始以为是用户权限的问题&#xff0c;后来换用 root 账户还是不行&#xff0c;经搜索&#xff0c;是 AppArmor 的问题。 解决方…