babyos2(39) network(5) -- icmp echo request, reply, receive

前面为babyos2 实现了发送IP数据报,但只测试了同一个局域网内发送,而当目的IP跟发送发自己的IP不在同一局域网内时,babyos2会把IP数据报发往网关。而如何测试是否发送成功,首先想到的是ping。ping使用ICMP(Internet控制报文协议)。

ICMP经常被认为是 IP层的一个组成部分。它传递差错报文以及其他需要注意的信息。ICMP报文通常被 IP层或更高层协议( TCP或UDP)使用。一些 ICMP报文把差错报文返回给用户进程。

ICMP报文有多种类型,如请求回显,回显应答,目的不可达,源端被关闭,重定向,超时,地址掩码请求等。我们这里只关注请求回显和回显应答。

babyos2(39) network(5) -- icmp echo request, reply, receive_第1张图片

目前从用户态发起ping请求时机还不成熟(需要socket),暂时让babyos2从内核态发送ICMP报文,当收到请求时发送reply,及接收reply等。

1. ICMP 回显请求和回显应答报文格式

babyos2(39) network(5) -- icmp echo request, reply, receive_第2张图片

class icmp_echo_hdr_t {
public:
    void init(uint8 type, uint8 code, uint16 check_sum, uint16 id, uint16 seq);

public:
    uint8  m_type;
    uint8  m_code;
    uint16 m_check_sum;
    uint16 m_id;
    uint16 m_seq_no;
};
void icmp_echo_hdr_t::init(uint8 type, uint8 code, uint16 check_sum, uint16 id, uint16 seq)
{
    m_type      = type;
    m_code      = code;
    m_check_sum = check_sum;
    m_id        = id;
    m_seq_no    = seq;
}

2. ICMP echo request

bool icmp_t::echo_request(uint32 ip, uint16 id, uint16 seq, uint8* data, uint32 len)
{
    uint32 total = len + sizeof(icmp_echo_hdr_t);
    net_buf_t* buffer = os()->get_net()->alloc_net_buffer(total);
    if (buffer == NULL) {
        console()->kprintf(RED, "ICMP echo_request, alloc net buffer failed, total: %u\n", total);
        return false;
    }

    console()->kprintf(GREEN, "send an icmp echo request to ip: ");
    net_t::dump_ip_addr(ip);
    console()->kprintf(GREEN, " seq: %u\n", seq);

    icmp_echo_hdr_t hdr;
    hdr.init(ECHO_REQUEST,          /* type */
             0,                     /* code */ 
             0,                     /* check sum */
             net_t::htons(id),      /* id */
             net_t::htons(seq));    /* seq no */
    hdr.m_check_sum = net_t::check_sum((uint8 *) &hdr, sizeof(icmp_echo_hdr_t));

    buffer->m_data = (uint8 *) buffer + sizeof(net_buf_t);
    buffer->m_data_len = total;
    uint8* p = buffer->m_data;
    memcpy(p, &hdr, sizeof(icmp_echo_hdr_t));

    if (data != NULL && len != 0) {
        p += sizeof(icmp_echo_hdr_t);
        memcpy(p, data, len);
    }

    os()->get_net()->get_ip()->transmit(ip, buffer->m_data, total, ip_t::PROTO_ICMP);
    os()->get_net()->free_net_buffer(buffer);
    return true;
}

代码比较简单,初始化一个报文头,

ip表示向哪个地址发送ping请求;

id是为了当系统中有多个ping同时发起请求时,当收到回复时识别是回复的那个ping的请求,一般会传进程ID,这里我们暂时只从内核发出请求,所以传0;

seq表示序列号,一般是一个递增的数字,表示序列;

data表示发送的数据,暂时不发任何数据,len为0.

准备号头后,计算校验和,然后分配一个buffer用于存储信息,最后发出去。


2. ICMP echo receive

receive表示收到一个ICMP报文,我们首先判断它的类型,根据类型做处理

void icmp_t::receive(net_buf_t* buf, uint32 ip)
{
    uint8 type = *(uint8 *) buf->m_data;
    switch (type) {
    case ECHO_REQUEST:
        echo_request_receive(buf, ip);
        break;
    case ECHO_REPLY:
        echo_reply_receive(buf, ip);
        break;
    default:
        console()->kprintf(RED, "receive an icmp package, but not support the type %x now.\n", type);
        break;
    }
}

暂时只支持 ECHO_REQUEST跟ECHO_REPLY,分别表示收到一个ICMP echo请求跟答复。

void icmp_t::echo_request_receive(net_buf_t* buf, uint32 ip)
{
    icmp_echo_hdr_t* hdr = (icmp_echo_hdr_t *) buf->get_data();
    uint16 check_sum = net_t::check_sum(buf->get_data(), buf->get_data_len());
    if (check_sum != 0) {
        console()->kprintf(RED, "receive an icmp echo request, but checksum is error: %x.\n", check_sum);
        return;
    }

    console()->kprintf(GREEN, "receive an icmp echo request from ip: ");
    net_t::dump_ip_addr(ip);
    console()->kprintf(GREEN, " seq: %u\n", net_t::ntohs(hdr->m_seq_no));
    echo_reply(ip, net_t::ntohs(hdr->m_id), net_t::ntohs(hdr->m_seq_no), NULL, 0);
}

void icmp_t::echo_reply_receive(net_buf_t* buf, uint32 ip)
{
    icmp_echo_hdr_t* hdr = (icmp_echo_hdr_t *) buf->get_data();
    uint16 check_sum = net_t::check_sum((uint8 *) hdr, sizeof(icmp_echo_hdr_t));
    if (check_sum != 0) {
        console()->kprintf(RED, "receive an icmp echo reply, but checksum is error: %x.\n", check_sum);
        return;
    }

    console()->kprintf(WHITE, "receive an icmp echo reply from ip: ");
    net_t::dump_ip_addr(ip);
    console()->kprintf(WHITE, " seq: %u\n", net_t::ntohs(hdr->m_seq_no));
}

当收到一个请求时,需要答复,当收到一个答复时只简单打印一些信息表示收到答复。

3.ICMP echo reply

bool icmp_t::echo_reply(uint32 ip, uint16 id, uint16 seq, uint8* data, uint32 len)
{
    uint32 total = len + sizeof(icmp_echo_hdr_t);
    net_buf_t* buffer = os()->get_net()->alloc_net_buffer(total);
    if (buffer == NULL) {
        console()->kprintf(RED, "ICMP echo_reply, alloc net buffer failed, total: %u\n", total);
        return false;
    }

    console()->kprintf(GREEN, "send an icmp echo reply to ip: ");
    net_t::dump_ip_addr(ip);
    console()->kprintf(GREEN, " seq: %u\n", seq);

    icmp_echo_hdr_t hdr;
    hdr.init(ECHO_REPLY,            /* type */
             0,                     /* code */ 
             0,                     /* check sum */
             net_t::htons(id),      /* id */
             net_t::htons(seq));    /* seq no */
    hdr.m_check_sum = net_t::check_sum((uint8 *) &hdr, sizeof(icmp_echo_hdr_t));

    buffer->m_data = (uint8 *) buffer + sizeof(net_buf_t);
    buffer->m_data_len = total;
    uint8* p = buffer->m_data;
    memcpy(p, &hdr, sizeof(icmp_echo_hdr_t));

    if (data != NULL && len != 0) {
        p += sizeof(icmp_echo_hdr_t);
        memcpy(p, data, len);
    }

    os()->get_net()->get_ip()->transmit(ip, buffer->m_data, total, ip_t::PROTO_ICMP);
    os()->get_net()->free_net_buffer(buffer);
    return true;
}

答复跟请求类似,代码比较简单,不做过多解释。

4.测试

babyos2(39) network(5) -- icmp echo request, reply, receive_第3张图片

上图是启动两个虚拟机,其中一个向另一个发送ICMP echo请求,可以发现104向105发送请求,105收到请求并作出答复,104收到答复。

babyos2(39) network(5) -- icmp echo request, reply, receive_第4张图片

这是wireshark抓包的结果。

下一步是测试ping baidu,因为babyos2暂时不支持DNS,所以先在linux上ping baid.com得到它的IP,再让babyos2向这个IP发送ICMP echo请求:

babyos2(39) network(5) -- icmp echo request, reply, receive_第5张图片

babyos2(39) network(5) -- icmp echo request, reply, receive_第6张图片

可以发现向111.13.101.208发送ICMP echo request,因为该IP不在同一局域网内,将数据包发送到了网关,IP 182.168.1.1,然后收到了来自111.13.101.208的ICMP echo reply。

babyos2(39) network(5) -- icmp echo request, reply, receive_第7张图片

从抓到的包来看,确实收到了来自baidu的回显答复。

-----------------------------------------------------------------------------------------------------

到目前为止babyos2具有了简单的往某个IP发送数据报的能力,此时实现socket IF_INET的时机已基本成熟,前面实现过通过socket进行进程间通信,通过网络与之相似但要复杂一些,特别是有连接的SOCK_STREAM 需要实现TCP。但无连接的SOCK_DGRAM 跟SOCK_RAW已经能够实现。

后面的计划:

1)socket SOCK_RAW, 测试方式是用户进程ping,通过SOCK_RAW发送ICMP echo请求

2)socket SOCK_DGRAM, 需要先支持UDP

3)socket SOCK_STREAM,需要支持TCP



你可能感兴趣的:(babyos2,babyos2)