如何使用原始套接字ping localhost?

时间:2013-10-17 11:40:15

标签: c linux sockets raw-sockets loopback

该程序从某些来源获得misc以太网流量,更改IP并应将其重定向到localhost(例如,它应该用于ssh连接)并从localhost发回答案。我使用了以下代码:

    int bind_if(int raw , char *device , int protocol) { 
        struct sockaddr_ll sll;
        struct ifreq ifr; bzero(&sll , sizeof(sll));
        bzero(&ifr , sizeof(ifr)); 
        strncpy((char *)ifr.ifr_name ,device , IFNAMSIZ); 
        //copy device name to ifr 
        if((ioctl(raw , SIOCGIFINDEX , &ifr)) == -1)
        { 
            perror("Unable to find interface index");
            return -1; 
        }
        sll.sll_family = AF_PACKET; 
        sll.sll_ifindex = ifr.ifr_ifindex; 
        sll.sll_protocol = htons(protocol); 
        if((bind(raw , (struct sockaddr *)&sll , sizeof(sll))) ==-1)
        {
            perror("bind: ");
            return -1;
        }
        return 0;
    } 

    int _create_input_socket(int *s, char* interface)
    {
        struct packet_mreq mreq;
        struct ifreq if_idx;
        int sockopt;
        int ifnumber = 0;
        if ((*s = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 1)
        {
                perror("cannot create socket");
                return -1;
        }
        memset(&if_idx, 0, sizeof(struct ifreq));
        strncpy(if_idx.ifr_name, interface, IFNAMSIZ-1);
        if (ioctl(*s, SIOCGIFINDEX, &if_idx) < 0)
        {
            perror("SIOCGIFINDEX for interface failed");
                close(*s);
            return -1;
        }
        ifnumber = if_idx.ifr_ifindex;

            /* Get the current flags that the device might have */
            if (ioctl(*s, SIOCGIFFLAGS, &if_idx) <0)
        {
            perror("SIOCGIFFLAGS failed");
                close(*s);
            return -1;
        }
        /* Set the old flags plus the IFF_PROMISC flag */
        if_idx.ifr_flags |= IFF_PROMISC;
        if (ioctl(*s, SIOCSIFFLAGS, &if_idx) == -1)
        {
            perror("SIOCSIFFLAGS while adding IFF_PROMISC failed");
                close(*s);
        }
        int flags = fcntl(*s, F_GETFL, 0);
        int ret = fcntl(*s, F_SETFL, flags | O_NONBLOCK);
        printf("ret = %d\n", ret);
        if(ret < 0)
        {
            perror("fcntl for O_NONBLOCK failed");
                close(*s);
                return -1;
        }

        if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)) == -1) {
            perror("setsockopt SO_REUSEADDR failed");
            close(*s);
                    return -1;
        }
        if( bind_if(*s, interface, ETH_P_ALL) == -1 )
        {
            close(*s);
            return -1;
        }
        if (setsockopt(*s, SOL_SOCKET, SO_BINDTODEVICE, interface, IFNAMSIZ-1) == -1)   {
            perror("SO_BINDTODEVICE");
            close(*s);
            return -1;
        }

        memset(&mreq,0,sizeof(mreq));
        mreq.mr_ifindex = ifnumber;
        mreq.mr_type = PACKET_MR_PROMISC;
        mreq.mr_alen = 6;

        if (setsockopt(*s,SOL_PACKET,PACKET_ADD_MEMBERSHIP,
             (void*)&mreq,(socklen_t)sizeof(mreq)) < 0)
               perror("setsockopt(PACKET_ADD_MEMBERSHIP)");
        printf("create socket %d for iface %s(%d)\n", *s, interface, ifnumber);
        return ifnumber;
    }

    void SendTo(int sock, int ifindex, const unsigned char*buffer, int buffer_size)
    {
        struct sockaddr_ll socket_address;
        memset(&socket_address, 0, sizeof(socket_address));
        socket_address.sll_ifindex = ifindex;
        socket_address.sll_halen = ETH_ALEN;
        socket_address.sll_family = htons(PF_PACKET);
        socket_address.sll_protocol = htons(ETH_P_ALL);
        socket_address.sll_hatype = ARPHRD_ETHER;
        memcpy(socket_address.sll_addr, buffer, 6);
        if (sendto(sock, buffer, buffer_size, 0, (struct         sockaddr*)&socket_address, sizeof(socket_address)) < 0)
            printf("Send failed due error %d\n", errno);
    }

在main()的某处:

    ifindex_lo = _create_input_socket(&socket_loopback, "lo");
    ...
    SendTo(socket_loopback, ifindex_lo, buffer, size);

我在loopback接口上使用tcpdump检查它是如何工作的,当我通过程序发送ping时:

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
00:10:24.269431 IP localhost.localdomain > localhost.localdomain: ICMP echo request, id 29046, seq 1, length 64
00:10:25.269125 IP localhost.localdomain > localhost.localdomain: ICMP echo request, id 29046, seq 2, length 64

当我从linux命令执行实际ping时:

00:43:49.228192 IP localhost.localdomain > localhost.localdomain: ICMP echo request, id 535, seq 1, length 64
00:43:49.228219 IP localhost.localdomain > localhost.localdomain: ICMP echo reply, id 535, seq 1, length 64
00:43:50.227183 IP localhost.localdomain > localhost.localdomain: ICMP echo request, id 535, seq 2, length 64
00:43:50.227203 IP localhost.localdomain > localhost.localdomain: ICMP echo reply, id 535, seq 2, length 64

我检查了数据包 - 除了icmp序列号外,其外观相同。所有其他流量的情况也是如此:系统不会回复我的程序生成的流量,但会通过其他来源生成的流量进行回复。我对loopback接口上sendto的这种行为感到困惑。任何人都知道如何避免这种情况?

1 个答案:

答案 0 :(得分:0)

尝试检查完整的数据包,包括以太网头(-e到tcpdump)并确保它们是正确的。

我认为在struct sockaddr_ll.sll_protocol中设置为ETH_P_ALL的SendTo()可能会弄乱您的数据包。使用错误的协议可以解释为什么它们没有被提供给适当的协议处理程序。

尝试从struct sockaddr_ll.sll_family删除struct sockaddr_ll.sll_ifindexSendTo()以外的所有内容。如果不起作用,请手动将.sll_protocol设置为您发送的类型。 man-page可以解释为当socket是SOCK_RAW时sll_protocol没有影响,但我认为它可能。

man 7 packet:

  

发送数据包时,指定sll_family就足够了,   sll_addr,sll_halen,sll_ifindex。其他字段应为0。   sll_hatype和sll_pkttype设置为收到的数据包   信息。对于绑定只有sll_protocol和sll_ifindex是   使用

你可以发布你正在使用的所有代码,但它已经很长了。如果你把一个非常短的程序放在一起,表现出相同的症状,那就最好了。