带数据的ICMP请求

时间:2017-10-19 11:29:29

标签: c sockets icmp

我想在最后发送带有一些数据的ICMP请求消息。我只能发送没有数据的简单ICMP请求。当我想在缓冲区中的icmp结构的末尾添加一些字符时,不发送任何ICMP请求。当我删除结束字符或将sendto函数中发送消息的大小更改为sizeof(icmp)时,消息将正常发送。有什么问题?

ping函数中的参数很好。

void ping(struct sockaddr_in *addr) {
    int sd;
    const int val=255;
    struct sockaddr_in r_addr;
    sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
    if ( sd < 0 ) {
        perror("socket");
        return;
    }
    if ( setsockopt(sd, SOL_IP, IP_TTL, &val, sizeof(val)) != 0)
        perror("Set TTL option");
    if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 )
        perror("Request nonblocking I/O");
    socklen_t len=sizeof(r_addr);
    struct icmp *icmp_send, *icmp_recv;
    icmp_send->icmp_type = ICMP_ECHO;
    icmp_send->icmp_code = 0;
    icmp_send->icmp_id = getpid();
    icmp_send->icmp_seq = 1;
    icmp_send->icmp_cksum = 0;
    icmp_send->icmp_cksum = checksum(icmp_send, sizeof(icmp_send));
    unsigned char buff[2000];
    unsigned char*p = buff;
    memcpy(buff, icmp_send, sizeof(icmp)); 
    p += sizeof(icmp);
    *p = 'A';
    if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1 , 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
                printf("Send err.\n");
    }
}

1 个答案:

答案 0 :(得分:2)

您需要使用附加到标头的字节重新计算ICMP标头中的校验和。

实现这一目标的最佳方法是初始化* icmp_send以指向您的buff字符串,然后在那里破坏您的字段。这样可以避免从memcpy* icmp_send buff。这是一个例子。

unsigned char buff[2000];
unsigned char *p = buff;
struct icmp *icmp_send;

icmp_send = (struct icmp *)buff;
icmp_send->icmp_type = ICMP_ECHO;
icmp_send->icmp_code = 0;
icmp_send->icmp_id = getpid();
icmp_send->icmp_cksum = 0;
icmp_send->icmp_seq = htons(1);
p += sizeof(icmp_send);
*p = 'A';

icmp_send->icmp_cksum = checksum(buff, sizeof(icmp_send) + 1);

if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1, 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
            printf("Send err.\n");
} 

在上面的示例中,您可以调整要传输的有效负载的大小(sizeof(icmp_send) + 1)为任何值。

更新:正如Matteo所评论的那样,将buff转换为struct icmp指向访问权限的指针然后修改ICMP字段可能不安全如果您的平台要求您处理访问权限正确地错位结构中的字段。在通过网络发送结构之前,单独编写结构,然后将memcpy写入buff是这种情况的方法。

回到您的代码,您将在代码示例中获得更多详细信息,并在此后完成代码。这里的想法是在指针之间计算和复制值* icmp_sendbuff不相关。

此代码发送带有数据的ICMP回应请求,接收ICMP回送回复。与tcpdump一起测试以验证主机之间是否正在交换数据包。

...
unsigned char*p = buff;
p += sizeof(icmp_send);
*p = 'A';

// 'A' has been appended into buff right after the header in the previous
// lines, we therefore need to compute the checksum using buff, without
// overwriting 'A', this is why we copy the 8 byte long ICMP header here.
//
// buff looks like [ ICMP HEADER (8 bytes)]['A' (1 byte)]
//
// Before computing the checksum, we also make sure its value in the header
// is set to 0 (mandatory).
icmp_send->icmp_cksum = 0;
memcpy(buff, icmp_send, sizeof(icmp_send));

// Now we can compute the checksum, and have to instruct our checksum()
// function that we want to include the data ('A') by setting the length
// in the second argument to 8 (ICMP header length) + 1 (data length of 'A')
icmp_send->icmp_cksum = checksum(buff, sizeof(icmp_send) + 1);

// Checksum is now ok, lets copy it again to buff. Here we copy the whole
// header but the idea is just to update the ICMP checksum field.
memcpy(buff, icmp_send, sizeof(icmp_send));

// buff contains header + data, ok to send it. Note that you don't need to
// compute the checksum of the IP packet. The system does it for you because
// your socket was created with the IPPROTO_ICMP
if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1, 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
            printf("Send err.\n");
}

此后完成代码(OT:确保在通过网络发送数据时使用hton功能,如果需要)

unsigned short checksum(void *b, int len)
{   unsigned short *buf = b;
    unsigned int sum=0;
    unsigned short result;

    for ( sum = 0; len > 1; len -= 2 )
        sum += *buf++;
    if ( len == 1 )
        sum += *(unsigned char*)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

void ping(struct sockaddr_in *addr) {
    int sd;
    const int val=255;
    struct sockaddr_in r_addr;
    sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
    if ( sd < 0 ) {
        perror("socket");
        return;
    }
    if ( setsockopt(sd, SOL_IP, IP_TTL, &val, sizeof(val)) != 0)
        perror("Set TTL option");
    if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 )
        perror("Request nonblocking I/O");
    socklen_t len=sizeof(r_addr);
    struct icmp *icmp_send, *icmp_recv;
    icmp_send->icmp_type = ICMP_ECHO;
    icmp_send->icmp_code = 0;
    icmp_send->icmp_id = getpid();
    icmp_send->icmp_seq = htons(1);
    unsigned char buff[2000];
    unsigned char*p = buff;
    p += sizeof(icmp_send);
    *p = 'A';

    icmp_send->icmp_cksum = 0;
    memcpy(buff, icmp_send, sizeof(icmp_send)) ;

    icmp_send->icmp_cksum = checksum(buff, sizeof(icmp_send) + 1);
    memcpy(buff, icmp_send, sizeof(icmp_send)) ;

    if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1, 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
                printf("Send err.\n");
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("usage: %s destination_ip\n", argv[0]);
        return 1;
    }

    struct sockaddr_in addr;
    struct in_addr dst;

    if (inet_aton(argv[1], &dst) == 0) {

        perror("inet_aton");
        printf("%s isn't a valid IP address\n", argv[1]);
        return 1;
    }


    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_addr = dst;

    ping(&addr);
    return 0;
}

希望这有帮助!