下一页 上一页 目录

6. 使用 Divert Sockets

本节将为您提供如何使用 divert sockets 的示例,以及它们与其他数据包拦截机制的不同之处。

6.1 Divert sockets 与其他机制的比较

存在其他具有类似功能的机制。以下是它们的不同之处

Netlink sockets

Netlink sockets 可以像 divert sockets 一样通过使用防火墙过滤器来拦截数据包。它们有一个特殊的类型 (AF_NETLINK),表面上看起来做的事情相同。两个主要的区别是

公平地说,netlink sockets 的范围比这更广。一般来说,netlink 机制旨在允许内核和用户空间之间的通信。例如,存在 netlink 路由 sockets,允许您与路由子系统通信。但是,作为一种数据包拦截机制,它们不如 divert sockets 强大。

Raw sockets

RAW sockets 可能是监听流量的好方法(尤其是在 Linux 下,RAW sockets 可以监听 TCP 和 UDP 流量,尽管大多数其他 UNI* 系统不允许这样做),但 RAW socket 无法阻止数据包在 IP 协议栈中传播 - 它只是给您数据包的副本,并且无法将其注入到入站(堆栈向上方向)- 只能注入出站。此外,您只能通过协议号来过滤数据包,协议号在您打开 RAW socket 时指定。防火墙和 RAW sockets 之间没有联系。

libpcap

libpcap 更广为人知的是它所促进的工具 - tcpdump,它允许您监听到达您接口的流量(无论是 ppp 还是 eth 或其他)。对于以太网,它还可以将您的 NIC 置于混杂模式,以便它将不仅链路层寻址到它的流量,而且寻址到同一网段上其他设备的流量转发到 IP 层。当然,libpcap 不允许以任何方式阻止数据包传播,也不允许注入。事实上,libpcap 在许多方面与 divert sockets 是正交的。

6.2 关于防火墙链的讨论

Linux 为您提供三个默认链:input、output 和 forward。还有记账链,但在这里无关紧要。根据数据包的来源,它会遍历这些链中的一个或多个

Input 链

由所有进入主机的包遍历 - 寻址到它的包和将由它转发的包。

Output 链

由所有源于主机的数据包和所有转发的数据包遍历

Forward 链

仅由转发的数据包遍历。

转发数据包遍历链的顺序是

  1. Input
  2. Forward
  3. Output
如果您对可能或可能不源于您主机的某种类型的数据包感兴趣,这有时会给拦截带来问题。很多时候不清楚应该使用哪个链。

作为经验法则,forward 链应仅用于过滤转发的数据包,这些数据包不是源于您的主机,也不是寻址到您的主机。如果您对转发的数据包以及源于或寻址到您的主机的数据包的组合感兴趣,则请改用 input 或 output 链。同时在 forward 和 input 或 output 链上拦截相同类型的数据包会在重新注入中产生问题,更重要的是,这是不必要的。

6.3 使用 ipchains

您需要从网站检索的 ipchains 的修补版本是一个允许您从 shell 修改防火墙规则的工具(大多数人想要这样做)。也可以通过编程方式设置防火墙规则。请参阅此示例代码 - 设置 DIVERT 规则类似于设置 REDIRECT 规则 - 将 DIVERT 指定为目标和 divert 端口,您就设置好了。

用于设置防火墙规则的 ipchains 语法保持不变。要指定 DIVERT 规则,您必须指定 -j DIVERT <端口号> 作为目标,其他一切保持不变。例如

ipchains -A input -p ICMP -j DIVERT 1234
将为 ICMP 数据包设置一个 divert 规则,以将 ICMP 数据包从 input 链分流到端口 1234。

以下部分介绍如何将 ipchains 与拦截器用户空间程序结合使用。

6.4 简单的示例

示例程序

这是一个示例程序,它从 divert socket 读取数据包,显示它们,然后将它们重新注入回去。它要求在命令行上指定 divert 端口。

#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <signal.h>

#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <net/if.h>
#include <sys/param.h>

#include <linux/types.h>
#include <linux/icmp.h>
#include <linux/ip_fw.h>

#define IPPROTO_DIVERT 254
#define BUFSIZE 65535

char *progname;

#ifdef FIREWALL

char *fw_policy="DIVERT";
char *fw_chain="output";
struct ip_fw fw;
struct ip_fwuser ipfu;
struct ip_fwchange ipfc;
int fw_sock;

/* remove the firewall rule when exit */
void intHandler (int signo) {

  if (setsockopt(fw_sock, IPPROTO_IP, IP_FW_DELETE, &ipfc, sizeof(ipfc))==-1) {
    fprintf(stderr, "%s: could not remove rule: %s\n", progname, strerror(errno));
    exit(2);
  }

  close(fw_sock);
  exit(0);
}

#endif

int main(int argc, char** argv) {
  int fd, rawfd, fdfw, ret, n;
  int on=1;
  struct sockaddr_in bindPort, sin;
  int sinlen;
  struct iphdr *hdr;
  unsigned char packet[BUFSIZE];
  struct in_addr addr;
  int i, direction;
  struct ip_mreq mreq;

  if (argc!=2) {
    fprintf(stderr, "Usage: %s <port number>\n", argv[0]);
    exit(1); 
  }
  progname=argv[0];

  fprintf(stderr,"%s:Creating a socket\n",argv[0]);
  /* open a divert socket */
  fd=socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT);

  if (fd==-1) {
    fprintf(stderr,"%s:We could not open a divert socket\n",argv[0]);
    exit(1);
  }

  bindPort.sin_family=AF_INET;
  bindPort.sin_port=htons(atol(argv[1]));
  bindPort.sin_addr.s_addr=0;

  fprintf(stderr,"%s:Binding a socket\n",argv[0]);
  ret=bind(fd, &bindPort, sizeof(struct sockaddr_in));

  if (ret!=0) {
    close(fd);
    fprintf(stderr, "%s: Error bind(): %s",argv[0],strerror(ret));
    exit(2);
  }
#ifdef FIREWALL
  /* fill in the rule first */
  bzero(&fw, sizeof (struct ip_fw));
  fw.fw_proto=1; /* ICMP */
  fw.fw_redirpt=htons(bindPort.sin_port);
  fw.fw_spts[1]=0xffff;
  fw.fw_dpts[1]=0xffff;
  fw.fw_outputsize=0xffff;

  /* fill in the fwuser structure */
  ipfu.ipfw=fw;
  memcpy(ipfu.label, fw_policy, strlen(fw_policy));

  /* fill in the fwchange structure */
  ipfc.fwc_rule=ipfu;
  memcpy(ipfc.fwc_label, fw_chain, strlen(fw_chain));

  /* open a socket */
  if ((fw_sock=socket(AF_INET, SOCK_RAW, IPPROTO_RAW))==-1) {
    fprintf(stderr, "%s: could not create a raw socket: %s\n", argv[0], strerror(errno));
    exit(2);
  }

  /* write a rule into it */
  if (setsockopt(fw_sock, IPPROTO_IP, IP_FW_APPEND, &ipfc, sizeof(ipfc))==-1) {
    fprintf(stderr, "%s could not set rule: %s\n", argv[0], strerror(errno));
    exit(2);
  }
 
  /* install signal handler to delete the rule */
  signal(SIGINT, intHandler);
#endif /* FIREWALL */
  
  printf("%s: Waiting for data...\n",argv[0]);
  /* read data in */
  sinlen=sizeof(struct sockaddr_in);
  while(1) {
    n=recvfrom(fd, packet, BUFSIZE, 0, &sin, &sinlen);
    hdr=(struct iphdr*)packet;
    
    printf("%s: The packet looks like this:\n",argv[0]);
        for( i=0; i<40; i++) {
                printf("%02x ", (int)*(packet+i));
                if (!((i+1)%16)) printf("\n");
        };
    printf("\n"); 

    addr.s_addr=hdr->saddr;
    printf("%s: Source address: %s\n",argv[0], inet_ntoa(addr));
    addr.s_addr=hdr->daddr;
    printf("%s: Destination address: %s\n", argv[0], inet_ntoa(addr));
    printf("%s: Receiving IF address: %s\n", argv[0], inet_ntoa(sin.sin_addr));
    printf("%s: Protocol number: %i\n", argv[0], hdr->protocol);

    /* reinjection */

#ifdef MULTICAST 
   if (IN_MULTICAST((ntohl(hdr->daddr)))) {
        printf("%s: Multicast address!\n", argv[0]);
        addr.s_addr = hdr->saddr;
        errno = 0;
        if (sin.sin_addr.s_addr == 0)
            printf("%s: set_interface returns %i with errno =%i\n", argv[0], setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)), errno);
    }
#endif

#ifdef REINJECT
   printf("%s Reinjecting DIVERT %i bytes\n", argv[0], n);
   n=sendto(fd, packet, n ,0, &sin, sinlen);
   printf("%s: %i bytes reinjected.\n", argv[0], n); 

   if (n<=0) 
     printf("%s: Oops: errno = %i\n", argv[0], errno);
   if (errno == EBADRQC)
     printf("errno == EBADRQC\n");
   if (errno == ENETUNREACH)
     printf("errno == ENETUNREACH\n");
#endif
  }
}

您可以简单地复制粘贴代码并使用您喜欢的编译器编译它。如果您想启用重新注入 - 使用 -DREINJECT 标志编译它,否则它将仅执行拦截。

为了使其工作,按照上面的描述编译内核和 ipchains-1.3.8。将规则插入到任何防火墙链中:input、output 或 forward,然后发送与规则匹配的数据包,并观看它们飞过屏幕 - 您的拦截器程序将显示它们,然后重新注入它们,如果已正确编译。

例如

ipchains -A output -p TCP -s 172.16.128.10 -j DIVERT 4321
interceptor 4321
将分流并显示源自主机 172.16.128.10 的所有 TCP 数据包(例如,如果您的主机是网关)。它将在输出链上,在它们上线之前拦截它们。

如果您没有将 pass through 选项编译到内核中,那么插入规则实际上会在防火墙中为您指定的数据包创建 DENY 规则,直到您启动拦截器程序。请参阅上面关于此的更多信息

如果您想通过您的程序设置防火墙规则,请使用 -DFIREWALL 选项编译它,它将从 output 链分流所有 ICMP 数据包。当您使用 Ctrl-C 退出程序时,它还将从防火墙中删除 DIVERT 规则。在这种情况下,使用 pass-through 与非 pass-through divert sockets 几乎没有区别。

6.5 无限可能

至于您可以将 divert sockets 用于什么 - 您的想象力将是限制因素。我很想听听关于利用 divert sockets 的应用程序。

所以,玩得开心!


下一页 上一页 目录