• 如何在VMWare的NAT模式下使用traceroute(解析vmnat的行为)


    原文链接

    本文为CSDN博主「dog250」的原创文章
    原文链接:https://blog.csdn.net/dog250/article/details/52234859


    前面写过一篇《为什么在VMWare的NAT模式下无法使用traceroute》,本文来破除这个结论,展示一种让你在VMWare的NAT模式下可以使用traceroute的方法。

            可能很多人觉得我无聊,使用Bridge模式不就好了吗?...其实,我之所以这么做,是认为作为一个这方面技术的爱好者,一定要有一种死磕和较真的精神,你说不行,我偏偏找出一种可行的办法!促使我写下本文的动力来自于一个疑问:如果说vmnat作为七层的NAT,而且我们也确认了对于TCP而言,它确实是在应用层接管了整个连接,那么对于UDP和ICMP是不是也是这样子呢?如果答案为“是”或者“不是”,这又是为什么呢??

    ......

    现在进入正文。
            首先,我列出一个关于VMWare和traceroute的帖子,来自VMWare的论坛,请点击这里,注意里面有一个附件,展示了在NAT模式下使用traceroute的问题,请自行打开阅读。在前面的文章中,我曾经提到VMWare的NAT是一个七层NAT,通过其打开的socket对数据流进行重新封装达到NAT的目的,但是如果你抓包的话,发现也不尽然。vmnat进程对待TCP和对待UDP/ICMP是不一致的。
    vmnat如何对待TCP?
    很简单,劫持这个Guest OS中发出的连接,并且代表它建立一条到达目标的新连接。
    vmnat如何对待UDP/ICMP?
    这个就有点复杂了,起码要比TCP复杂。vmnat并不会劫持一个UDP或者ICMP数据包,对于从Guest OS中发出去的数据包,vmnat只是简单的修改了源IP地址,并不会触动IP头的其它字段,比如TTL(这是可以简单完成NAT对traceroute支持的关键!),但是对于远端回来的数据包,由于当时的源IP地址已经修改成了Host宿主机的IP地址,因此很显然地,回来的包自然而然的被路由到本地进程,即vmnat进程,数据到达该进程的时候,经过层层解封装,已经没有了IP头甚至UDP/ICMP之类的信息,如果想完成数据包到Guest OS的转发,必须重新封装并发送,因此它会使用本地的即Host OS的协议栈参数对这个转发数据包进行封装。所以说,你会发现下面的场景:
    在Guest OS内ping www.baidu.com

    Guest OS内抓包:




    Host OS内抓包:




    这说明,Guest发往baidu的数据包,vmnat好像只是做了单纯的NAT,而baidu发回Host的数据包中,vmnat将整个IP头都换了。之所以对正向包和反向包采用不同的NAT策略,是有理由的:




    因此,vmnat省去了一半的力气,它只需抓取VMWare虚拟网卡发出的RAW数据包或者直接PACKET抓取,然后简单修改IP地址后再RAW/PACKET发出,就不用管了,直到回来的数据包被协议栈路由到vmnat,只剩下裸数据,被剥离了IP头信息,那么vmnat只需要构造一个IP头在裸数据上,其中的目标IP改成GuestOS的网卡IP,然后注入到VMNet虚拟网卡中即可。
            现在,我们可以解释本文开头的问题了:如果说vmnat作为七层的NAT,而且我们也确认了对于TCP而言,它确实是在应用层接管了整个连接,那么对于UDP和ICMP是不是也是这样子呢?
    如果答案为“是”或者“不是”,这又是为什么呢??
    答案可能是这样的:
    1.vmnat企图通过一种不对称的方式处理NAT,即对于从Guest OS发出的数据包,通过直接抓取VMNet8的裸IP报文,修改源IP地址后发出,对于进入Guest OS的数据包,由于目标地址是本机,理所当然进入vmnat进程,被创建的socket接收,然后通过socket的方式发入VMNet8这个虚拟网卡。
    2.但是对于TCP而言,上述的方式不适用!因为在反向方向,vmnat会打断序列号(数据进入应用层后会剥离IP头和TCP头,丢失一切协议元信息),这就会打断Guest OS到远端的TCP连接。另外,作为TCP的客户端,不发起connect是无法单独创建socket的,因此,简单的办法就是直接接管整个TCP。
    一个更加详细的关于vmnat的图解如下:




    我又一次见证了tun虚拟网卡的思想是多么的伟大!


            好了,接着解决NAT模式下的traceroute问题。理解了以上的结构图,你基本也就知道该怎么做到对traceroute的支持了,vmnat之所以不支持traceroute,只是它没有处理这类数据包而已,相信以后会处理的,再说了处理一下也不难,当前的办法就是替它处理掉这类数据包就OK了。办法非常简单,就是“把ICMP Time-to-live exceeded消息修改相关的IP地址信息后注入到VMNet8这块虚拟网卡中”。
            由于我们可能无力去修改vmnat的行为,因此尝试在应用层做这个Hack往往是无助的。但是我们有一个神器,这就是pcap!下面是一个示意图:




    数据包都拿到手了,还有什么不能做的吗?话说哪怕数据包不在手上,不也是可以自己构造一个吗?
            如果你已经知道了该怎么办,请作为练习自行完成后面的编码调试工作,如果你还不知道或者没有兴趣自己写,那么请继续看,以下的篇幅只写一件事,那就是如何完成代码。起初,我觉得使用scapy这件事是一件超级简单且分分钟搞定的事,但是在Windows上安装scapy却是一件作死的事!!为了安装Python以及其pypcap库,就要各种编译,你就必须整一个完整的编译环境,软粉们都推荐VS,老一代的经理们有的还在使用VC6.0,然而我看到这些就要吐血!终于,我想起了我曾经用过的Dev-C++!
    ...
    既然有了Dev-C++,那么我也就不需要Python了,直接用winpcap会更简单。需要安装winpcap,一般而言只要你装了Wireshark,这个库就自行安装好了,接下来你需要安装的是winpcap的开发包,即WpdPack,我装的是WpdPack_4_1_2版本,随便解压到一个目录即可。下面是一个Step by Step:
    1.用Dev-C++创建一个Windows控制台工程;
    2.点击“项目”-“项目属性”-“文件目录”将WpdPack解压目录下的include目录以及lib目录添加进去;
    3.接着上述步骤2,将WpdPack的lib下的wpcap.lib添加进链接库中;
    4.编写源代码main.c,仅此一个文件足矣!

    前面都是引子,最重要的代码如下:


    #include <stdlib.h>
    #include <winsock2.h>
    #include <unistd.h>
    #include <stdio.h>
    

    #include "pcap.h"

    #define PROT_ICMP 1
    #define PROT_UDP 17

    #define TYPE_TTLEXCEED 11
    #define LEN_ETH 14
    #define LEN_IP 20
    #define LEN_MACADDR 6
    #define LEN_MAXIP 16

    typedef struct ip_header {
    // from Linux kernel
    u_char type_and_ver;
    u_char tos;
    u_short tot_len;
    u_short id;
    u_short frag_off;
    u_char ttl;
    u_char protocol;
    u_short check;
    u_int32_t saddr;
    u_int32_t daddr;
    /*The options start here. */
    }attribute((packed)) ipheader;

    typedef struct icmphdr {
    // from Linux kernel
    u_char type;
    u_char code;
    u_short checksum;
    union {
    struct {
    u_short id;
    u_short sequence;
    } echo;
    u_int32_t gateway;
    struct {
    u_short __unused;
    u_short mtu;
    } frag;
    } un;
    }attribute((packed)) icmpheader;;

    pcap_t *hdto = NULL;
    void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);

    char source[LEN_MAXIP];
    char destination[LEN_MAXIP];
    char mac[LEN_MACADDR];

    // 我使用arp -a|find ...来获取mac地址,而不是使用Win API,因为我恨它们!
    void get_mac(char *src, char *mac)
    {
    char result[256] = {0};
    char cmd[32] = {0};
    FILE *fp;
    int idx, mac_idx;

    <span class="hljs-built_in">sprintf</span>(cmd, <span class="hljs-string">"arp -a|find "%s""</span>, src);
    <span class="hljs-keyword">if</span>((fp = popen(cmd, <span class="hljs-string">"r"</span>)) == <span class="hljs-literal">NULL</span>) {   
    	<span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">if</span> (fgets(result, <span class="hljs-number">256</span>, fp) == <span class="hljs-literal">NULL</span>) {
    	<span class="hljs-keyword">return</span>;
    } 
    pclose(fp);     
    <span class="hljs-keyword">for</span> (idx = <span class="hljs-number">15</span>, mac_idx = <span class="hljs-number">0</span>; idx &lt; <span class="hljs-built_in">strlen</span>(result); idx ++) {
    	<span class="hljs-keyword">if</span> (result[idx] == <span class="hljs-string">'-'</span>){
    		<span class="hljs-keyword">char</span> *str;
    		<span class="hljs-keyword">char</span> base[<span class="hljs-number">4</span>];
    		<span class="hljs-built_in">sprintf</span>(base, <span class="hljs-string">"0x%c%c"</span>, result[idx<span class="hljs-number">-2</span>], result[idx<span class="hljs-number">-1</span>]);
    		mac[mac_idx] = strtol(base, &amp;str, <span class="hljs-number">16</span>);
    		mac_idx++;
    		<span class="hljs-keyword">if</span> (mac_idx == <span class="hljs-number">5</span>) {
    			<span class="hljs-built_in">sprintf</span>(base, <span class="hljs-string">"0x%c%c"</span>, result[idx+<span class="hljs-number">1</span>], result[idx+<span class="hljs-number">2</span>]);	
    			mac[mac_idx] = strtol(base, <span class="hljs-literal">NULL</span>, <span class="hljs-number">16</span>);
    		}
    	} 
    }
    

    }

    static int cksum(u_short *addr, int len)
    {
    int nleft = len;
    u_short *w = addr;
    int sum = 0;
    u_short ret = 0;

    <span class="hljs-keyword">while</span> (nleft &gt; <span class="hljs-number">1</span>)  {
        sum += *w++;
        nleft -= <span class="hljs-number">2</span>;
    }
    
    <span class="hljs-keyword">if</span> (nleft == <span class="hljs-number">1</span>) {
        *(u_char *)(&amp;ret) = *(u_char *)w ;
        sum += ret;
    }
    
    sum = (sum &gt;&gt; <span class="hljs-number">16</span>) + (sum &amp; <span class="hljs-number">0xffff</span>);  
    sum += (sum &gt;&gt; <span class="hljs-number">16</span>);         
    ret = ~sum;              
    <span class="hljs-keyword">return</span> ret;
    

    }

    int main(int argc, char **argv)
    {
    pcap_if_t *alldevs, *phy_if, *virt_if;
    pcap_if_t dev_if,to;
    char *phyaddr, *virtaddr;
    pcap_t *hdfrom;
    char errbuf[PCAP_ERRBUF_SIZE];
    struct bpf_program fcode;
    int opt = 0;
    static const char *optString = "p✌️s:d:";

    opt = getopt(argc, argv, optString);
    <span class="hljs-keyword">while</span>( opt != <span class="hljs-number">-1</span> ) {
        <span class="hljs-keyword">switch</span>( opt ) {                
            <span class="hljs-keyword">case</span> <span class="hljs-string">'p'</span>:
                phyaddr = optarg;
                <span class="hljs-keyword">break</span>; 
            <span class="hljs-keyword">case</span> <span class="hljs-string">'v'</span>:
                virtaddr = optarg;
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">case</span> <span class="hljs-string">'s'</span>:
                <span class="hljs-built_in">strcpy</span>(source, optarg);
                get_mac(source, mac);
                <span class="hljs-keyword">break</span>; 
            <span class="hljs-keyword">case</span> <span class="hljs-string">'d'</span>:
                <span class="hljs-built_in">strcpy</span>(destination, optarg);
                <span class="hljs-keyword">break</span>;    
            <span class="hljs-keyword">default</span>:
                <span class="hljs-built_in">printf</span>(<span class="hljs-string">"XXX -p $物理网卡地址 -v $VMNet8的地址 -s $虚拟机的IP $目标IP
    "</span>);
                <span class="hljs-keyword">break</span>;
        }
        opt = getopt( argc, argv, optString );
    }
    
    <span class="hljs-keyword">if</span>(pcap_findalldevs(&amp;alldevs, errbuf) == <span class="hljs-number">-1</span>) {
    	<span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    <span class="hljs-comment">// 这个在Windows上是一件令人悲伤的事情,在Linux上一个“eth0”就能搞定! </span>
    <span class="hljs-keyword">for</span>(dev_if = alldevs; dev_if != <span class="hljs-literal">NULL</span>; dev_if = dev_if-&gt;next) {
    	<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">pcap_addr</span> *<span class="hljs-title">addr</span>;</span>
    	addr = dev_if-&gt;addresses;
    	<span class="hljs-keyword">while</span> (addr) {
    		<span class="hljs-keyword">if</span>(addr-&gt;addr-&gt;sa_family == AF_INET) {
    			<span class="hljs-keyword">char</span> *straddr = inet_ntoa(((struct sockaddr_in*)addr-&gt;addr)-&gt;sin_addr);
    			<span class="hljs-keyword">if</span> (!<span class="hljs-built_in">strcmp</span>(phyaddr, straddr)) {
    				phy_if = dev_if;
    			} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">strcmp</span>(virtaddr, straddr)) {
    				virt_if = dev_if;
    			}
    		} 
    		addr = addr-&gt;next;
    	} 
    }
    pcap_freealldevs(alldevs);
    <span class="hljs-keyword">if</span> (phy_if == <span class="hljs-literal">NULL</span> || virt_if == <span class="hljs-literal">NULL</span>) {
    	<span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    <span class="hljs-comment">// 打开Host OS的出口物理网卡设备 </span>
    <span class="hljs-keyword">if</span> ((hdfrom = pcap_open_live(phy_if-&gt;name, <span class="hljs-number">65536</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1000</span>, errbuf)) == <span class="hljs-literal">NULL</span>) {
    	pcap_freealldevs(alldevs);
    	<span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    
    <span class="hljs-comment">// 打开Host OS在NAT模式下连接Guest OS的VMNet设备,本例为VMNet8  </span>
    <span class="hljs-keyword">if</span> ((hdto = pcap_open_live(virt_if-&gt;name, <span class="hljs-number">65536</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1000</span>, errbuf)) == <span class="hljs-literal">NULL</span>) {
    	pcap_close(hdfrom);
    	<span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    } 
    
    <span class="hljs-comment">// 设置过滤器,只处理TTL过期的ICMP数据包 </span>
    <span class="hljs-keyword">if</span> (pcap_compile(hdfrom, &amp;fcode, <span class="hljs-string">"icmp and icmp[icmptype] == icmp-timxceed"</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0xffffffff</span>) &lt; <span class="hljs-number">0</span>){
    	pcap_close(hdfrom);
    	pcap_close(hdto);
    	<span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;		
    } 
    
    <span class="hljs-keyword">if</span> (pcap_setfilter(hdfrom, &amp;fcode) &lt; <span class="hljs-number">0</span>) {
    	pcap_close(hdfrom);
    	pcap_close(hdto);
    	<span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;			
    }
    
    pcap_loop(hdfrom, <span class="hljs-number">0</span>, packet_handler, <span class="hljs-literal">NULL</span>);
    pcap_close(hdfrom);
    pcap_close(hdto);
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    

    }

    void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
    {
    u_char *buff = NULL;
    ipheader iph = (ipheader)(pkt_data + LEN_ETH);
    if (iph->protocol == PROT_ICMP) {
    icmpheader icmp = (icmpheader)(pkt_data + LEN_ETH + LEN_IP);
    if (icmp->type == TYPE_TTLEXCEED) {
    ipheader iph_1, iph_2 ;
    buff = malloc(header->caplen);
    if (!buff) {
    goto out;
    }
    memcpy(buff, pkt_data, header->caplen);
    memcpy(buff, mac, LEN_MACADDR);
    // 获取IP头,进行地址转换。对于TTL exceeded消息而言,地址转换要转两层,一层是外部的,另外引发这条TTL exceeded消息的内部源报文的地址也要转换。
    iph_1 = (ipheader
    )(buff + LEN_ETH);
    // 实际的地址转换,将目标地址转换成Guest OS的IP地址并重算校验和。
    iph_1->daddr = inet_addr(source);
    iph_1->check = 0;
    iph_1->check = cksum((unsigned short
    )iph_1, LEN_IP);

    		<span class="hljs-comment">// 获取TTL过期消息中封装的“引发该消息的”原始IP数据报的协议头,越过TTL exceeded报文的前8字节,直达原始报文。 </span>
    		iph_2 = (ipheader*)(buff + LEN_ETH + LEN_IP + <span class="hljs-number">8</span>);
    		<span class="hljs-comment">// 转换内部原始IP报文的目源地址为Guest OS的IP地址并重算校验和。 </span>
    		iph_2-&gt;saddr = inet_addr(source);
    		iph_2-&gt;check = <span class="hljs-number">0</span>;
    		iph_2-&gt;check = cksum((<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">short</span>*)iph_2, LEN_IP);
    		
    		<span class="hljs-comment">// traceroute有两种方式,Linux平台默认使用UDP,因此里面封装的是一个UDP报文。 </span>
    		<span class="hljs-keyword">if</span> (iph_2-&gt;protocol == PROT_UDP){
    			<span class="hljs-comment">//TODO</span>
    			<span class="hljs-comment">// 重新计算校验和,注意伪头部 </span>
    		} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (iph_2-&gt;protocol == PROT_ICMP){
    			<span class="hljs-comment">// 否则,如果使用了-I选项,则使用ICMP Echo reuqest进行trace。 </span>
    			<span class="hljs-comment">// </span>
    		} <span class="hljs-keyword">else</span> {
    			<span class="hljs-keyword">goto</span> out;
    		}
    		<span class="hljs-comment">// 将TTL exceeded消息经过NAT后发送到VMNet8这个NAT设备,随后它将会把数据包转给同网段的Guest OS网卡 </span>
    		<span class="hljs-keyword">if</span> (pcap_sendpacket(hdto, buff,	header-&gt;caplen) != <span class="hljs-number">0</span>) {
    			<span class="hljs-keyword">goto</span> out;
    		} 
    	}
    }
    

    out:
    if (buff) {
    free(buff);
    }
    }



    注意这个代码,其实它在Linux下稍微改下头文件也可以轻松编译成功并运行,在写这个代码的时候,最困难的是选择pcap设备的环节,Linux中可以直接使用网卡的name字段,比如“eth0”轻松打开一个pcap句柄,但是在Windows平台,一个网卡的name并不是一个方便可读的字符串,因此就不得不先调用pcap_findalldevs枚举出所有的设备,然后去比对IP地址来获取设备,这样反而要比直接使用网卡的名字来打开句柄方便很多。
            个人认为,Windows平台使用网卡(另外还有磁盘)的名字之所以不方便是因为Windows程序中很少使用命令行操作,大多数都是GUI,而GUI几乎就是让你点击各种空间去选择网卡的,因此一个pcap_if_t对象的name和description字段可能更好的去展示完整的信息,虽然它不像eth0那么简短和直接!

    用法很简单。我以我的环境为例来说明。我的机器配置如下:
    Host OS配置:
    物理网卡地址-192.168.199.195
    VMNet8虚拟网卡地址-192.168.44.1
    Guest OS配置:
    Eth3物理网卡地址-192.168.44.100
    traceroute目标-14.215.177.37
    运行我的程序:
    D:dev atex  -p 192.168.199.195 -v 192.168.44.1 -s 192.168.44.100 -d 14.215.177.37
    此时,Guest Linux上,运行traceroute,结果如下:




    这个小程序是非常好用的,可以在虚拟机中使用traceroute了!我之所以写这么一个小工具,是因为它对于我而言是有用的,因为我的笔记本电脑在公司是DHCP获取的地址,地址配置在无线网卡上,我无法使用桥接,因为我的虚拟机将无法通过认证(公司网络隔离的太狠!),所以,我必须使用NAT,而且我必须使用traceroute,所以就有了上面的代码。


    最后附上Linux的代码

    原作者的代码是运行在windows上的, 稍加修改就可以在linux上跑

    //
    // Created by Kimbing Ng on 3/10/20.
    //
    //
    // Created by Kimbing Ng on 3/10/20.
    //
    
    #include <pcap.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <unistd.h>
    
    #define PROT_ICMP 1
    #define PROT_UDP 17
    
    #define TYPE_TTLEXCEED 11
    #define LEN_ETH 14
    #define LEN_IP 20
    #define LEN_MACADDR 6
    #define LEN_MAXIP 16
    
    typedef struct ip_header {
        // from Linux kernel
        u_char    type_and_ver;
        u_char    tos;
        u_short   tot_len;
        u_short   id;
        u_short   frag_off;
        u_char    ttl;
        u_char    protocol;
        u_short   check;
        u_int32_t saddr;
        u_int32_t daddr;
        /*The options start here. */
    } __attribute__((packed)) ipheader;
    
    typedef struct icmphdr {
        // from Linux kernel
        u_char  type;
        u_char  code;
        u_short checksum;
        union {
            struct {
                u_short id;
                u_short sequence;
            } echo;
            u_int32_t gateway;
            struct {
                u_short __unused;
                u_short mtu;
            } frag;
        } un;
    } __attribute__((packed)) icmpheader;
    ;
    
    
    pcap_t* hdto = NULL;
    
    void packet_handler(u_char*                   param,
                        const struct pcap_pkthdr* header,
                        const u_char*             pkt_data);
    
    char source[LEN_MAXIP];
    char destination[LEN_MAXIP];
    char mac[LEN_MACADDR];
    
    // 我使用arp -a|find ...来获取mac地址,而不是使用Win API,因为我恨它们!
    void get_mac(char* src, char* mac) {
        char  result[256] = {0};
        char  cmd[32]     = {0};
        FILE* fp;
        int   idx, mac_idx;
    
        sprintf(cmd, "arp -a|find "%s"", src);
        if ((fp = popen(cmd, "r")) == NULL) {
            return;
        }
        if (fgets(result, 256, fp) == NULL) {
            return;
        }
        pclose(fp);
        for (idx = 15, mac_idx = 0; idx < strlen(result); idx++) {
            if (result[idx] == '-') {
                char* str;
                char  base[4];
                sprintf(base, "0x%c%c", result[idx - 2], result[idx - 1]);
                mac[mac_idx] = strtol(base, &str, 16);
                mac_idx++;
                if (mac_idx == 5) {
                    sprintf(base, "0x%c%c", result[idx + 1], result[idx + 2]);
                    mac[mac_idx] = strtol(base, NULL, 16);
                }
            }
        }
    }
    
    static int cksum(u_short* addr, int len) {
        int      nleft = len;
        u_short* w     = addr;
        int      sum   = 0;
        u_short  ret   = 0;
    
        while (nleft > 1) {
            sum += *w++;
            nleft -= 2;
        }
    
        if (nleft == 1) {
            *(u_char*)(&ret) = *(u_char*)w;
            sum += ret;
        }
    
        sum = (sum >> 16) + (sum & 0xffff);
        sum += (sum >> 16);
        ret = ~sum;
        return ret;
    }
    
    int main(int argc, char** argv) {
        pcap_if_t *        alldevs, *phy_if, *virt_if;
        pcap_if_t *        dev_if, *to;
        char *             phyaddr, *virtaddr;
        pcap_t*            hdfrom;
        char               errbuf[PCAP_ERRBUF_SIZE];
        struct bpf_program fcode;
        int                opt       = 0;
        static const char* optString = "p:v:s:d:";
    
        opt = getopt(argc, argv, optString);
        while (opt != -1) {
            switch (opt) {
                case 'p': phyaddr = optarg; break;
                case 'v': virtaddr = optarg; break;
                case 's':
                    strcpy(source, optarg);
                    get_mac(source, mac);
                    break;
                case 'd': strcpy(destination, optarg); break;
                default:
                    printf("XXX -p $物理网卡地址 -v $VMNet8的地址 -s $虚拟机的IP "
                           "$目标IP
    ");
                    break;
            }
            opt = getopt(argc, argv, optString);
        }
    
        if (pcap_findalldevs(&alldevs, errbuf) == -1) {
            return -1;
        }
        // 这个在Windows上是一件令人悲伤的事情,在Linux上一个“eth0”就能搞定!
        for (dev_if = alldevs; dev_if != NULL; dev_if = dev_if->next) {
            struct pcap_addr* addr;
            addr = dev_if->addresses;
            while (addr) {
                if (addr->addr->sa_family == AF_INET) {
                    char* straddr =
                            inet_ntoa(((struct sockaddr_in*)addr->addr)->sin_addr);
                    if (!strcmp(phyaddr, straddr)) {
                        phy_if = dev_if;
                    }
                    else if (!strcmp(virtaddr, straddr)) {
                        virt_if = dev_if;
                    }
                }
                addr = addr->next;
            }
        }
        pcap_freealldevs(alldevs);
        if (phy_if == NULL || virt_if == NULL) {
            return -1;
        }
        // 打开Host OS的出口物理网卡设备
        if ((hdfrom = pcap_open_live(phy_if->name, 65536, 1, 1000, errbuf)) ==
            NULL) {
            pcap_freealldevs(alldevs);
            return -1;
        }
    
        // 打开Host OS在NAT模式下连接Guest OS的VMNet设备,本例为VMNet8
        if ((hdto = pcap_open_live(virt_if->name, 65536, 1, 1000, errbuf)) ==
            NULL) {
            pcap_close(hdfrom);
            return -1;
        }
    
        // 设置过滤器,只处理TTL过期的ICMP数据包
        if (pcap_compile(hdfrom, &fcode, "icmp and icmp[icmptype] == icmp-timxceed",
                         1, 0xffffffff) < 0) {
            pcap_close(hdfrom);
            pcap_close(hdto);
            return -1;
        }
    
        if (pcap_setfilter(hdfrom, &fcode) < 0) {
            pcap_close(hdfrom);
            pcap_close(hdto);
            return -1;
        }
    
        pcap_loop(hdfrom, 0, packet_handler, NULL);
        pcap_close(hdfrom);
        pcap_close(hdto);
        return 0;
    }
    
    void packet_handler(u_char*                   param,
                        const struct pcap_pkthdr* header,
                        const u_char*             pkt_data) {
        u_char*   buff = NULL;
        ipheader* iph  = (ipheader*)(pkt_data + LEN_ETH);
        if (iph->protocol == PROT_ICMP) {
            icmpheader* icmp = (icmpheader*)(pkt_data + LEN_ETH + LEN_IP);
            if (icmp->type == TYPE_TTLEXCEED) {
                ipheader *iph_1, *iph_2;
                buff = malloc(header->caplen);
                if (!buff) {
                    goto out;
                }
                memcpy(buff, pkt_data, header->caplen);
                memcpy(buff, mac, LEN_MACADDR);
                // 获取IP头,进行地址转换。对于TTL
                // exceeded消息而言,地址转换要转两层,一层是外部的,另外引发这条TTL
                // exceeded消息的内部源报文的地址也要转换。
                iph_1 = (ipheader*)(buff + LEN_ETH);
                // 实际的地址转换,将目标地址转换成Guest OS的IP地址并重算校验和。
                iph_1->daddr = inet_addr(source);
                iph_1->check = 0;
                iph_1->check = cksum((unsigned short*)iph_1, LEN_IP);
    
                // 获取TTL过期消息中封装的“引发该消息的”原始IP数据报的协议头,越过TTL
                // exceeded报文的前8字节,直达原始报文。
                iph_2 = (ipheader*)(buff + LEN_ETH + LEN_IP + 8);
                // 转换内部原始IP报文的目源地址为Guest OS的IP地址并重算校验和。
                iph_2->saddr = inet_addr(source);
                iph_2->check = 0;
                iph_2->check = cksum((unsigned short*)iph_2, LEN_IP);
    
                // traceroute有两种方式,Linux平台默认使用UDP,因此里面封装的是一个UDP报文。
                if (iph_2->protocol == PROT_UDP) {
                    // TODO
                    // 重新计算校验和,注意伪头部
                }
                else if (iph_2->protocol == PROT_ICMP) {
                    // 否则,如果使用了-I选项,则使用ICMP Echo reuqest进行trace。
                    //
                }
                else {
                    goto out;
                }
                // 将TTL
                // exceeded消息经过NAT后发送到VMNet8这个NAT设备,随后它将会把数据包转给同网段的Guest
                // OS网卡
                if (pcap_sendpacket(hdto, buff, header->caplen) != 0) {
                    goto out;
                }
            }
        }
        out:
        if (buff) {
            free(buff);
        }
    }
    

    首先你要确保你有pcap

    然后编译:

    gcc main.c -lpcap
    
  • 相关阅读:
    docker 入门5
    docker 入门4
    Machine概念和获取帮助 【翻译】
    docker 入门3
    docker 入门2
    docker 入门1
    在生产环境中使用Compose 【翻译】
    docker常用命令整理
    ASP.NET WebAPI 06 HttpMessageHandler管道
    ASP.NET WebAPI 05 参数绑定
  • 原文地址:https://www.cnblogs.com/Kimbing-Ng/p/12457073.html
Copyright © 2020-2023  润新知