Linux下Sniffer程序的实现(PF_PACKET,SOCK_RAW,recvfrom,htons(ETH_P_IP),setsockopt(filter))-程序员宅基地

技术标签: linux_socket编程  

作者:Gianluca Insolvibile
整理:Seal(永远的FLASH)
日期:2004-11-05

嗅探——Sniffer技术是网络安全领域里一项非常重要的技术!对于“Hacker”来说,他们可以以非常隐蔽的方式得到网络中传输的大量的敏感信息,如Telnet,ftp帐号和密码等等明文传送的信息!与主动扫描相比,嗅探的行为更加难以被察觉,操作起来也不是很复杂!对于网络管理人员来说,可以利用嗅探技术对网络活动进行监控,并及时发现各种攻击行为!
在这篇文章里,我们主要探讨在Linux下如何利用C语言来实现一个Sniffer!我们将假设所有的主机在一个局域网内。
    
首先,我们将简短的回顾一下一个普通的以太网卡是怎么工作的!(如果你对这方面的知识早已熟悉,那么你可以直接跳到下一段)来源于应用程序的IP报文被封装成以太网帧(这
是在以太网上传播的数据报文的名称),它是底层链路层报文上面的一层报文,包含有源地址
报文和一些需要用来传送至目标主机的信息。通常情况下,目的IP地址对应着一个6字节的目的以太网址(经常叫做MAC地址),它们之间通过ARP协议进行映射!就这样,包含着以太网帧的报文从源主机传输到目的主机,中间经过一些网络设备,如交换机,路由器等等,当然,因为我们的前提是主机在同一网内,所以我们的讨论不涉及以上这些网络设备!

    在链路层中并不存在路线的概念,换句话说,源主机发出的帧不会直接指向目的主机,
而是基于广播方式传播,网络中的所有网卡都能看到它的传输。每个网卡会检查帧开始的6个字节(目的主机的MAC地址),但是只有一个网卡会发现自己的地址和其相符合,然后它接收这个帧,这个帧会被网络驱动程序分解,原来的IP报文将通过网络协议栈传送至接收的应用程序!

更准确的说,网络驱动程序会检查帧中报文头部的协议标识,以确定接收数据的上层协
议!大多数情况下,上层是IP协议,所以接收机制将去掉IP报文头部,然后把剩下的传送
至UDP或者TCP接收机制!这些协议,将把报文送到socket-handling机制,它将最后把报
文数据变成应用程序可接收的方式发送出去。在这个过程中,报文将失去所有的和其有关的
网络信息,比如源地址(IP和MAC),端口号,IP选择,TCP参数等等!所以如果目的主机没
有一个包含正确参数的打开端口,那么这个报文将被丢弃而且永远不会被送到应用层去!

因此我们在进行网络嗅探的时候有两个不同的问题:一个和以太网址有关,我们不能抓
到不是发给自己主机的包,另一个和协议栈的运行过程有关,我们需要一个SOCKET去监听每
个端口,得到那些没有被丢弃的报文!

第一个问题不是最根本的,因为我们可能不会对发往其他主机的报文有兴趣而只想嗅探
所有发往自己主机的报文。第二个问题是必须要解决的,下面我们将看到这个问题是怎么样
一步一步解决的!

当你打开一个标准的SOCKET套接字时,你需要指明你将使用哪个协议簇,大多数情况下
我们一般用PF_UNIX在本地机器间进行通信,PF_INET在基于IPv4协议簇基础之上进行通信,
你还需要指明所用的协议类型及与协议簇相关的确切数值,,在PF_INET协议簇中,常用的有
SOCK_STREAM(与TCP相关),SOCK_DGRAM(与UDP相关)。在把报文发送到应用程序前内核对
其的处理与SOCKET类型有关,你指定的协议将处理报文在SOCKET的传输!(具体细节问题你
可以man socket(3))

在LINUX内核版本中(2.0 releases),一个名为PF_PACKET的协议簇被加了进来!这个簇允许应用程序直接利用网络驱动程序发送和接收报文,避免了原来的协议栈处理过程,在这种情况下,所有SOCKET发出的报文直接送到以太网卡接口,而接口收到的任何报文将直接送到应用程序

The PF_PACKET协议簇支持两个稍微有点不同的SOCKET类型,SOCK_DGRAM和SOCK_RAW。
前者让内核处理添加或者去除以太网报文头部工作,而后者则让应用程序对以太网报文头部
有完全的控制!在SOCKET调用中的协议类型必须符合/usr/include/linux/if_ether.h
中定义的以太网IDs中的一个,除非遇到特别声明的协议,一般你可以用ETH_P_IP来处理
IP的一组协议(TCP,UDP,ICMP,raw IP等等)因为它们容易受到一些很严重的安全问题的牵
连(比如你可以伪造一个MAC地址),所以只有具有root权限才可以使用PF_PACKET-family
socket.这也就是为什么只有具有root权限后才能运行嗅探器的原因!

PF_PACKET-family 协议簇可以很容易解决协议栈处理嗅探来的数据报文时候遇到的问
题!我们一起来看看程序1,我们打开一个属于PF_PACKET-family 协议簇的SOCKET,指定
一个SOCK_RAW socket类型和IP相关协议类型。这时我们开始从SOCKET抓包,在一些相关
检查后.我们开始得到从链路层和IP层抓来的头部信息,。通过阅读程序一,你将会发现让应
用程序从网络层抓包其实并不难!

Example 1.
#include <stdio.h>
#include <errno.h>  
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>  
#include <linux/in.h>
#include <linux/if_ether.h>

int main(int argc, char **argv) {
  int sock, n;
  char buffer[2048];
  unsigned char *iphead, *ethhead;
  
  if ( (sock=socket(PF_PACKET, SOCK_RAW,
                    htons(ETH_P_IP)))<0) {
    perror("socket");
    exit(1);
  }

  while (1) {
    printf("----------\n");
    n = recvfrom(sock,buffer,2048,0,NULL,NULL);
    printf("%d bytes read\n",n);

    /* Check to see if the packet contains at least
     * complete Ethernet (14), IP (20) and TCP/UDP
     * (8) headers.
     */
    if (n<42) {
      perror("recvfrom():");
      printf("Incomplete packet (errno is %d)\n",
             errno);
      close(sock);
      exit(0);
    }

    ethhead = buffer;
    printf("Source MAC address: "
           "%02x:%02x:%02x:%02x:%02x:%02x\n",
           ethhead[0],ethhead[1],ethhead[2],
           ethhead[3],ethhead[4],ethhead[5]);
    printf("Destination MAC address: "
           "%02x:%02x:%02x:%02x:%02x:%02x\n",
           ethhead[6],ethhead[7],ethhead[8],
           ethhead[9],ethhead[10],ethhead[11]);

    iphead = buffer+14; /* Skip Ethernet header */
    if (*iphead==0x45) { /* Double check for IPv4
                          * and no options present */
      printf("Source host %d.%d.%d.%d\n",
             iphead[12],iphead[13],
             iphead[14],iphead[15]);
      printf("Dest host %d.%d.%d.%d\n",
             iphead[16],iphead[17],
             iphead[18],iphead[19]);
      printf("Source,Dest ports %d,%d\n",
             (iphead[20]<<8)+iphead[21],
             (iphead[22]<<8)+iphead[23]);
      printf("Layer-4 protocol %d\n",iphead[9]);
    }
  }
  
}


PF_PACKET协议簇可以让一个应用程序把数据包变成似乎从网络层接收的样子,但是
没有办法抓到那些不是发向自己主机的包。正如我们前面看到的,网卡丢弃所有不含有主机
MAC地址的数据包,这是因为网卡处于非混杂模式,即每个网卡只处理源地址是它自己的帧!
只有三个例外:如果一个帧的目的MAC地址是一个受限的广播地址(255.255.255.255)那么
它将被所有的网卡接收:如果一个帧的目的地址是组播地址,那么它将被那些打开组播接收
功能的网卡所接收;网卡如被设置成混杂模式,那么它将接收所有流经它的数据包

最后一种情况当然是我们最感兴趣的了,把网卡设置成混杂模式,我们只需要发出一个
特殊的ioctl()调用在那个网卡上打开一个socket,因为这是一个具有危险性的操作,所以这
个调用只有具有root权限的用户才可完成,假设那个“sock”包含一个已经打开的socket,

下面的代码将完成这个操作:

strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
ioctl(sock, SIOCGIFFLAGS, &ethreq);
ethreq.ifr_flags |= IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, &ethreq);



下面我们来看一个完整的例子:

Example 2.

#include <stdio.h>
#include <string.h>
#include <errno.h>  
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>  
#include <linux/in.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <sys/ioctl.h>

int main(int argc, char **argv) {
  int sock, n;
  char buffer[2048];
  unsigned char *iphead, *ethhead;
  struct ifreq ethreq;
  
  if ( (sock=socket(PF_PACKET, SOCK_RAW,
                    htons(ETH_P_IP)))<0) {
    perror("socket");
    exit(1);
  }

  /* Set the network card in promiscuos mode */
  strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
  if (ioctl(sock,SIOCGIFFLAGS,?req)==-1) {
    perror("ioctl");
    close(sock);
    exit(1);
  }
  ethreq.ifr_flags|=IFF_PROMISC;
  if (ioctl(sock,SIOCSIFFLAGS,?req)==-1) {
    perror("ioctl");
    close(sock);
    exit(1);
  }
  
  while (1) {
    printf("----------\n");
    n = recvfrom(sock,buffer,2048,0,NULL,NULL);
    printf("%d bytes read\n",n);

    /* Check to see if the packet contains at least
     * complete Ethernet (14), IP (20) and TCP/UDP
     * (8) headers.
     */
    if (n<42) {
      perror("recvfrom():");
      printf("Incomplete packet (errno is %d)\n",
             errno);
      close(sock);
      exit(0);
    }

    ethhead = buffer;
    printf("Source MAC address: "
           "%02x:%02x:%02x:%02x:%02x:%02x\n",
           ethhead[0],ethhead[1],ethhead[2],
           ethhead[3],ethhead[4],ethhead[5]);
    printf("Destination MAC address: "
           "%02x:%02x:%02x:%02x:%02x:%02x\n",
           ethhead[6],ethhead[7],ethhead[8],
           ethhead[9],ethhead[10],ethhead[11]);

    iphead = buffer+14; /* Skip Ethernet header */
    if (*iphead==0x45) { /* Double check for IPv4
                          * and no options present */
      printf("Source host %d.%d.%d.%d\n",
             iphead[12],iphead[13],
             iphead[14],iphead[15]);
      printf("Dest host %d.%d.%d.%d\n",
             iphead[16],iphead[17],
             iphead[18],iphead[19]);
      printf("Source,Dest ports %d,%d\n",
             (iphead[20]<<8)+iphead[21],
             (iphead[22]<<8)+iphead[23]);
      printf("Layer-4 protocol %d\n",iphead[9]);
    }
  }
  
}
如果我们在一个与局域网相连的主机上以root权限编译和运行这个程序,你将会看到流
经的所有数据包,即使它不是发向自己主机的,这是因为你的网卡现在处于混杂模式,你可
以很容易的用ifconfig这个命令看到!

注意,如果你的局域网用的是交换机而不是集线器,那么你只能抓到在你主机所在的交
换分支中的数据包,这是由于交换的工作原理,在这种情况下,你没有什么其他可做的了(除非你利用MAC地址欺骗来瞒过交换机,这个内容超出本文讨论的范围了),如果你想知道这方面更多的信息,你可以去www.google.com,那里你将会搜索到很多你需要的东西!

我们关于嗅探方面的问题似乎都解决了,但是还有一个问题值得我们去思考!如果我们
实验Example2的时候正好遇到了网络拥塞,我们将会看到我们的嗅探器将打出非常多的数据,随着网络拥塞的加剧,主机将不能顺畅的执行这个程序,嗅探器这时会开始丢包!

解决这个问题的方法就是对你抓到的包进行过滤,使它只显示那些你感兴趣的那些部分!
在源代码中多使用一些if语句,这将有益于去除那些不需要的信息,当然这样也许效率不是
很高!内核仍然将处理所有的报文,这将浪费进程时间。

最好的解决办法就是把过滤过程尽可能早的放报文进程链(packet-processing chain)
中(它开始于链路层,终止于应用层)LINUX的内核允许我们把一个名为LPF的过滤器直接放到PF_PACKET protocol-processing routines(在网卡接收中断执行后立即执行)中,过滤器将决定哪些包被送到应用程序,哪些包被丢弃!

为了做到尽可能的灵活,这个过滤程序可以根据使用者的定义来运行!这个程序是由一种名为BPF的伪机器码写成的。BPF看上去很象带着一对寄存器和保存值的汇编语言,完成数学运算和条件分支程序!过滤程序检查每个包,BP进程操作的内存空间包含着报文数据!过滤的结果是一个整数,指出有多少字节的报文需要送到应用层。这是一个更进一步的优点,因为我们一般只对报文的前面几个字节感兴趣,这样可以避免复制过量的数据以节省进程时间。

虽然BPF语言非常简单易学,但是大多数人还是习惯于人类可以阅读的表达方式!所以
为了不用BPF语言去描述那些细节和代码,我们将下面开始讨论如何得到一个过滤器的代码!

首先,你需要安装tcpdump,可以从LBL得到!但是,如果你正在读本文,那似乎说明
你已经会使用tcpdump了!第一个版本的作者也是那些写BPF的人,事实上,tcpdump要用
到BPF,它要用到一个叫libpcap的类库,以此去抓包和过滤。这个类库是一个与操作系统
无关的独立的包,为BPF的实现提供支持,当在装有LINUX的机器上使用时,BPF的功能就
由LINUX包过滤器实现了!

libpcap提供的一个最有用的函数是pcap_compile(), 它可以把一个输入输出的逻辑表
达式变为BPF代码!tcpdump利用这个函数完成在用户输入的命令行和BPF代码之间的转换!
tcpdump有个我们很感兴趣但是很少使用的参数 ,-d,可以输出BPF代码!

举个例子,如果tcpdump host 192.168.9.10那么将启动嗅探器并只抓到源地址或者目
的地址是192.168.9.10的报文!如果tcpdump -d host 192.168.9.10那么将显示出BPF代
码,见下面

Example 3.
Seal:~# tcpdump -d host 192.168.9.10
(000) ldh      [12]
(001) jeq      #0x800           jt 2    jf 6
(002) ld       [26]
(003) jeq      #0xc0a8090a      jt 12   jf 4
(004) ld       [30]
(005) jeq      #0xc0a8090a      jt 12   jf 13
(006) jeq      #0x806           jt 8    jf 7
(007) jeq      #0x8035          jt 8    jf 13
(008) ld       [28]
(009) jeq      #0xc0a8090a      jt 12   jf 10
(010) ld       [38]
(011) jeq      #0xc0a8090a      jt 12   jf 13
(012) ret      #68
(013) ret      #0

我们简单的分析一下这个代码,0-1,6-7行在确定被抓到的帧是否在传输着IP,ARP或
者RARP协议,通过比较帧的第12格的值与协议的特征值(见/usr/include/linux/if_ether.h)!如果比较失败,那么丢弃这个包!

2-5,8-11行是比较帧的源地址和目的地址是否与192.168.9.10相同!注意,不同的
协议,地址值在帧中的偏移位不同,如果是IP协议,它在26-30,如果是其他协议,它在
28-38如果有一个地址符合,那么帧的前68字节将被送到应用程序去(第12行)

这个过滤器也不是总是有效的,因为它产生于一般的使用BPF的机器,没考虑到一些特
殊结构的机器!在一些特殊情况下,过滤器由PF_PACKET进程运行,也许已经检查过以太协
议了!这个根据你在socket()调用初使化的时候指定的那些协议!如果不是ETH_P_ALL(抓
所有的报文),那么只有那些符合指定的协议类型的报文会流过过滤器!举个例子,如果是
ETH_P_IP socket,我们可以重写一个更快且代码更紧凑的过滤器,代码如下

(000) ld       [26]
(001) jeq      #0xc0a8090a      jt 4    jf 2
(002) ld       [30]
(003) jeq      #0xc0a8090a      jt 4    jf 5
(004) ret      #68(005) ret      #0

安装LPF是一个一往直前的操作,所有你需要做的就是建立一个sock_filter并为
它绑定一个打开的端口!一个过滤器很容易得到,只要把tcpdump -d中的-d换成-dd就可以了!过滤器将显示出一段C代码,你可以把它复制到自己的程序中,见Example 4,这样你就可以通过调用setsockopt()来过滤端口!

Example 4.
Seal:~# tcpdump -dd host 192.168.9.01
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 4, 0x00000800 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 8, 0, 0xc0a80901 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 6, 7, 0xc0a80901 },
{ 0x15, 1, 0, 0x00000806 },
{ 0x15, 0, 5, 0x00008035 },
{ 0x20, 0, 0, 0x0000001c },
{ 0x15, 2, 0, 0xc0a80901 },
{ 0x20, 0, 0, 0x00000026 },
{ 0x15, 0, 1, 0xc0a80901 },
{ 0x6, 0, 0, 0x00000044 },
{ 0x6, 0, 0, 0x00000000 },

A Complete Example

我们将用一个完整的程序Example 5来结束这篇文章

Example 5
#include <stdio.h>
#include <string.h>
#include <errno.h>  
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>  
#include <linux/in.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <linux/filter.h>
#include <sys/ioctl.h>

int main(int argc, char **argv) {
  int sock, n;
  char buffer[2048];
  unsigned char *iphead, *ethhead;
  struct ifreq ethreq;

  /*
    udp and host 192.168.9.10 and src port 5000
    (000) ldh      [12]
    (001) jeq      #0x800           jt 2        jf 14
    (002) ldb      [23]
    (003) jeq      #0x11            jt 4        jf 14
    (004) ld       [26]
    (005) jeq      #0xc0a8090a      jt 8        jf 6
    (006) ld       [30]
    (007) jeq      #0xc0a8090a      jt 8        jf 14
    (008) ldh      [20]
    (009) jset     #0x1fff          jt 14       jf 10
    (010) ldxb     4*([14]&0xf)
    (011) ldh      [x + 14]
    (012) jeq      #0x1388          jt 13       jf 14
    (013) ret      #68
    (014) ret      #0
  */
  struct sock_filter BPF_code[]= {
    { 0x28, 0, 0, 0x0000000c },
    { 0x15, 0, 12, 0x00000800 },
    { 0x30, 0, 0, 0x00000017 },
    { 0x15, 0, 10, 0x00000011 },
    { 0x20, 0, 0, 0x0000001a },
    { 0x15, 2, 0, 0xc0a8090a },
    { 0x20, 0, 0, 0x0000001e },
    { 0x15, 0, 6, 0xc0a8090a },
    { 0x28, 0, 0, 0x00000014 },
    { 0x45, 4, 0, 0x00001fff },
    { 0xb1, 0, 0, 0x0000000e },
    { 0x48, 0, 0, 0x0000000e },
    { 0x15, 0, 1, 0x00001388 },
    { 0x6, 0, 0, 0x00000044 },
    { 0x6, 0, 0, 0x00000000 }
  };                            
  struct sock_fprog Filter;
    
  Filter.len = 15;
  Filter.filter = BPF_code;
  
  if ( (sock=socket(PF_PACKET, SOCK_RAW,
                    htons(ETH_P_IP)))<0) {
    perror("socket");
    exit(1);
  }

  /* Set the network card in promiscuos mode */
  strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
  if (ioctl(sock,SIOCGIFFLAGS,?req)==-1) {
    perror("ioctl");
    close(sock);
    exit(1);
  }
  ethreq.ifr_flags|=IFF_PROMISC;
  if (ioctl(sock,SIOCSIFFLAGS,?req)==-1) {
    perror("ioctl");
    close(sock);
    exit(1);
  }

  /* Attach the filter to the socket */
  if(setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER,
                &Filter, sizeof(Filter))<0){
    perror("setsockopt");
    close(sock);
    exit(1);
  }

  while (1) {
    printf("----------\n");
    n = recvfrom(sock,buffer,2048,0,NULL,NULL);
    printf("%d bytes read\n",n);

    /* Check to see if the packet contains at least
     * complete Ethernet (14), IP (20) and TCP/UDP
     * (8) headers.
     */
    if (n<42) {
      perror("recvfrom():");
      printf("Incomplete packet (errno is %d)\n",
             errno);
      close(sock);
      exit(0);
    }

    ethhead = buffer;
    printf("Source MAC address: "
           "%02x:%02x:%02x:%02x:%02x:%02x\n",
           ethhead[0],ethhead[1],ethhead[2],
           ethhead[3],ethhead[4],ethhead[5]);
    printf("Destination MAC address: "
           "%02x:%02x:%02x:%02x:%02x:%02x\n",
           ethhead[6],ethhead[7],ethhead[8],
           ethhead[9],ethhead[10],ethhead[11]);

    iphead = buffer+14; /* Skip Ethernet  header */
    if (*iphead==0x45) { /* Double check for IPv4
                          * and no options present */
      printf("Source host %d.%d.%d.%d\n",
             iphead[12],iphead[13],
             iphead[14],iphead[15]);
      printf("Dest host %d.%d.%d.%d\n",
             iphead[16],iphead[17],
             iphead[18],iphead[19]);
      printf("Source,Dest ports %d,%d\n",
             (iphead[20]<<8)+iphead[21],
             (iphead[22]<<8)+iphead[23]);
      printf("Layer-4 protocol %d\n",iphead[9]);
    }
  }
  
}


这个程序和开始的前两个程序很类似,只是增加了LSP代码和setsockopt()调用,这个
过滤器被用来嗅探UDP报文(源地址或者目的地址为192.168.9.10且源地址的端口为5000)
为了测试这个程序,你需要一个简单的方法去生成不断的UDP报文(如发送或者接受IP),而且你可以把程序修改为你指定的机器的IP地址,你只需要把代码中的0xc0a8090a替换成你要的IP地址的16进制形式!

最后想说的就是关闭程序后如果我们没有重新设置网卡,那么它还是处于混杂模式!你
可以加一个Control-C信号句柄来使网卡在程序关闭前恢复以前的默认配置。

进行网络嗅探对于诊断网络运行错误或者进行流量分析都是非常重要的!有些时候常用
的工具如tcpdump或者Ethereal不能非常符合我们的需要的时候,我们可以自己写一个嗅探
器!为此我们应该感谢LPF,它使我们做到这点变的很容易!

 
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cos_sin_tan/article/details/8131024

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线