.net viewmodel list数据_0021 virtio-net简易驱动

上篇讲了virtio-blk简易驱动,这篇来实验virtio-net驱动。喜欢自己鼓捣协议栈的,有virtio-net驱动之后,就可以开始了。同样,有了网络之后,内核就可以跟外界交互了,可以实验各种简易网络服务,比如memcache,httpserver等。

virtio-net需要2个或以上virtio队列,一个接收队列,一个发送队列,可能还有一个控制队列。

接收队列用于接收网卡收到的包。因为是被动接收的,设置队列的时候只需要告诉virtio内存地址跟大小即可。

static void virtio_net_refill_rx_queue(void)
{
    struct addr_size phys[1];
    struct packet *p;

    while ((in_rx < BUF_PACKETS / 2) && !STAILQ_EMPTY(&free_list)) {
        /* peek */
        p = STAILQ_FIRST(&free_list);
        /* remove */
        STAILQ_REMOVE_HEAD(&free_list, next);

        phys[0].vp_addr = p->pdata;
        phys[0].vp_size = MAX_PACK_SIZE;
        phys[0].vp_flags = VRING_DESC_F_WRITE;
        
        virtio_to_queue(to_virtio_dev_t(&pci_vn), RX_Q, phys, 1, p);
        in_rx++;
    }

    if (in_rx == 0 && STAILQ_EMPTY(&free_list)) {
        pci_w(("warning: rx queue underflow!"));
        virtio_net_stats.ets_fifoUnder++;
    }
}

virtio_net_refill_rx_queue用来设置接收队列,有个阈值,BUF_PACKETS / 2,当接收队列可用描述符数量少于阈值的时候,就填充进去。

填充就一项即可,接收包内存地址p->pdata,大小为MAX_PACK_SIZE,设置只写属性,即告诉virtio这个是输出的。

static void virtio_net_check_queues(void)
{
    struct packet *p;
    size_t len;

    /* Put the received packets into the recv list */
    while (virtio_from_queue(to_virtio_dev_t(&pci_vn), RX_Q, (void **)&p, &len)
           == 0) {
        pci_d("virtio_from_RX_queue:%lx,len:%xn", p, len);
        p->len = len;
        pci_d("vhdr:%lx,phdr:%lx,vdata:%lx,pdata:%lx,len:%dn",
              p->vhdr, p->phdr, p->vdata, p->pdata, p->len);
        dump_mem(p->vdata,len);
    }
}

virtio_net_check_queues从接收队列接收包数据,然后打印内存。运行之后发现会有几个包收到。

virtio_from_RX_queue:ffffffff8005da48,len:107
vhdr:ffffffff8005c1d6,phdr:5c1d6,vdata:ffffffff800555f6,pdata:555f6,len:263
addr:ffffffff800555f6:
~800555f6 - 00 00 00 00 00 00 00 00 00 00 33 33 00 00 00 fb ..........33....
~80055606 - be 13 e8 8e 9a ce 86 dd 60 02 53 8d 00 c7 11 ff ........`.S.....
~80055616 - fe 80 00 00 00 00 00 00 bc 13 e8 ff fe 8e 9a ce ................
~80055626 - ff 02 00 00 00 00 00 00 00 00 00 00 00 00 00 fb ................
~80055636 - 14 e9 14 e9 00 c7 6b 84 00 00 84 00 00 00 00 04 ......k.........
~80055646 - 00 00 00 00 04 68 61 32 33 0b 5f 75 64 69 73 6b .....ha23._udisk
~80055656 - 73 2d 73 73 68 04 5f 74 63 70 05 6c 6f 63 61 6c s-ssh._tcp.local
~80055666 - 00 00 10 80 01 00 00 11 94 00 01 00 04 68 61 32 .............ha2
~80055676 - 33 c0 22 00 1c 80 01 00 00 00 78 00 10 fe 80 00 3.".......x.....
~80055686 - 00 00 00 00 00 bc 13 e8 ff fe 8e 9a ce 01 65 01 ..............e.
~80055696 - 63 01 61 01 39 01 65 01 38 01 65 01 66 01 66 01 c.a.9.e.8.e.f.f.
~800556a6 - 66 01 38 01 65 01 33 01 31 01 63 01 62 01 30 01 f.8.e.3.1.c.b.0.
~800556b6 - 30 01 30 01 30 01 30 01 30 01 30 01 30 01 30 01 0.0.0.0.0.0.0.0.
~800556c6 - 30 01 30 01 30 01 30 01 38 01 65 01 66 03 69 70 0.0.0.0.8.e.f.ip
~800556d6 - 36 04 61 72 70 61 00 00 0c 80 01 00 00 00 78 00 6.arpa........x.
~800556e6 - 02 c0 34 c0 0c 00 21 80 01 00 00 00 78 00 08 00 ..4.........x...
~800556f6 - 00 00 00 00 16 c0 34          ......4

前面10个字节属于virtio-net的头,后面是以太网的数据。

目的mac:33:33:00:00:00:fb

源mac:be:13:e8:8e:9a:ce

协议类型:86 dd(ipv6)

运行ifconfig,可以看到tap0

tap0      Link encap:Ethernet  HWaddr be:13:e8:8e:9a:ce
          inet6 addr: fe80::bc13:e8ff:fe8e:9ace/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:19 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:2942 (2.9 KB)

源mac跟tap0的mac对应。

ipv6包第一个字节是协议版本,0x60,高4位0x6表示协议版本6(ipv6),低4位是其他内容。

对于大端跟小端,在低4位还是高4位不一样。对应x86,协议号在高4位。

52cf00466e7c5a250f5c6cefd8dce4e1.png

可以看到payload Length为16位,0x00 0xc7

整个包长度为0x107,virtio-net头10字节,ethnet协议头14字节(mac地址2*6,协议类型2字节),ipv6头40字节。加起来刚好。

0x107 = 0xc7 + 10 + 14+ 40

可以看到源ip地址为:fe 80 00 00 00 00 00 00 bc 13 e8 ff fe 8e 9a ce

跟ifconfig tap0的ipv6地址对应:inet6 addr: fe80::bc13:e8ff:fe8e:9ace

目的ip,ff 02 00 00 00 00 00 00 00 00 00 00 00 00 00 fb

FF开头的为组播地址,相当于ipv4的广播。

因此自动收到的几个包是ipv6的组播包。

Next Header为0x11,表示是UDP包。

027fa86120d5d6081b0c5afa86f6d0d8.png

可以看到端口号为14e9 14e9,即5353,为DNS端口。00 c7为包长度,同ipv6头里面的payload Length,payload Length包括ipv6扩展头跟payload长度,这里没有扩展头所以一致。6b 84为校验和。

现在我们来写个ipv6 udp包的校验和检测,看看我们自己写的校验和跟包里面的是否一致。因为接下来我们要进行发包,为了测试,就发ipv6 udp包,需要计算校验和。

u32 check_sum(u32 sum,void *buf, int len)
{
    int num = 0;
    uchar *p = (uchar *)buf;
    if ((NULL == p) || (0==len)) return sum;
    while (len > 1) {
        sum += ((ushort)p[num]<<8&0xff00)|((ushort)p[num+1]&0xff);
        len -= 2;
        num += 2;
    }

    if (len>0) {
        sum += ((ushort)p[num]<<8)&0xffff;
        num++;
    }

    while (sum >> 16) {
        sum = (sum >> 16) + (sum & 0xffff);
    }
    return sum;
}

check_sum为校验和计算函数,16位进行累加,换算成本机字节序再计算,如何和有溢出,超过16位,溢出部分加到低位。最后取反。

计算的时候需要添加伪头部,即源ip地址,目的ip地址,以及协议号。

        u16 *pw = (u16 *) p->vdata+5;

        pci_d("%lx,%lx,%lxn", pw[5], pw[6], pw[7]);
        uchar *pc = (uchar *) p->vdata+10;
        pci_d("To:%x:%x:%x:%x:%x:%x,From:%x:%x:%x:%x:%x:%x: len:%x,type:%xn",
              pc[0], pc[1], pc[2], pc[3], pc[4], pc[5], pc[6], pc[7], pc[8],
              pc[9], pc[10], pc[11],pw[9],pw[6]);
        if (pw[6]==0xdd86)pci_d("ipv6n");
        if (pc[20]==0x11)pci_d("udpn");
        if (pw[6]==0xdd86 && pc[20]==0x11){
            ushort oldsum = pw[30];
            pw[30]=0;
            u32 sum = 0;
            sum+=len-64;
            sum = check_sum(sum,&pw[11],32);
            sum += 0x11;
            ushort calsum = (ushort)check_sum(sum,&pw[27],len-64);
            pci_d("oldsum:%x,calsum:%xn",oldsum,~calsum&0xffff);
        }

pw,pc指向以太网包头。

计算sum时,先添加udp长度,为virtio-net输出的len,减去64字节(10字节为virtio头,14字节为以太网包头,40字节为ipv6包头),然后计算源ip地址跟目的ip地址,2个16字节,然后加上0x11,UDP协议号。再计算UDP内容部分。

打印出来为:

virtio_from_RX_queue:ffffffff8005da48,len:107
vhdr:ffffffff8005c1d6,phdr:5c1d6,vdata:ffffffff800555f6,pdata:555f6,len:263
addr:ffffffff800555f6:
~800555f6 - 00 00 00 00 00 00 00 00 00 00 33 33 00 00 00 fb ..........33....
~80055606 - 96 c9 06 8f 74 a9 86 dd 60 0e c2 65 00 c7 11 ff ....t...`..e....
~80055616 - fe 80 00 00 00 00 00 00 94 c9 06 ff fe 8f 74 a9 ..............t.
~80055626 - ff 02 00 00 00 00 00 00 00 00 00 00 00 00 00 fb ................
~80055636 - 14 e9 14 e9 00 c7 bd 23 00 00 84 00 00 00 00 04 .......#........
~80055646 - 00 00 00 00 04 68 61 32 33 0b 5f 75 64 69 73 6b .....ha23._udisk
~80055656 - 73 2d 73 73 68 04 5f 74 63 70 05 6c 6f 63 61 6c s-ssh._tcp.local
~80055666 - 00 00 10 80 01 00 00 11 94 00 01 00 04 68 61 32 .............ha2
~80055676 - 33 c0 22 00 1c 80 01 00 00 00 78 00 10 fe 80 00 3.".......x.....
~80055686 - 00 00 00 00 00 94 c9 06 ff fe 8f 74 a9 01 39 01 ...........t..9.
~80055696 - 61 01 34 01 37 01 66 01 38 01 65 01 66 01 66 01 a.4.7.f.8.e.f.f.
~800556a6 - 66 01 36 01 30 01 39 01 63 01 34 01 39 01 30 01 f.6.0.9.c.4.9.0.
~800556b6 - 30 01 30 01 30 01 30 01 30 01 30 01 30 01 30 01 0.0.0.0.0.0.0.0.
~800556c6 - 30 01 30 01 30 01 30 01 38 01 65 01 66 03 69 70 0.0.0.0.8.e.f.ip
~800556d6 - 36 04 61 72 70 61 00 00 0c 80 01 00 00 00 78 00 6.arpa........x.
~800556e6 - 02 c0 34 c0 0c 00 21 80 01 00 00 00 78 00 08 00 ..4.........x...
~800556f6 - 00 00 00 00 16 c0 34          ......4
flags:0,gso_type:0,hdr_len:0,gso_size:0,csum_start:0,csum_offset:0
0,fb00000033330000,dd86a9748f06c996,ff11c70065c20e60,80fe,a9748ffeff06c994,2ff
a974,dd86,e60
To:33:33:0:0:0:fb,From:96:c9:6:8f:74:a9: len:c700,type:dd86
ipv6
udp
oldsum:23bd,calsum:bd23

字节序换成网络字节序,就都是23bd了。

接下来来实践发包。

libs/net/udp.c

void udp_set_data(struct udp_packet *p,void *data, size_t len)
{
    memcpy(p->data, data, len);
    p->udp.len = htons(len + sizeof(p->udp));
    p->ip.ip_len = htons(len +sizeof(p->udp) + (p->ip.ip_hl<<2));
    ip_set_checksum(&p->ip);
    ip_set_udp_checksum(&p->ip);
}
void udp6_set_data(struct udp6_packet *p,void *data, size_t len)
{
    memcpy(p->data, data, len);
    p->udp.len = htons(len + sizeof(p->udp));
    p->ip6.payload_len = p->udp.len;
    ip_set_udp6_checksum(&p->ip6);
}

udp_set_data, udp6_set_data分别为ipv4跟ipv6的udp包设置数据,然后计算包长度,设置校验和。

校验和相关函数在libs/net/ip.c

__used static void init_udp_packet(char *destip,char *destmac)
{
    struct udp_packet *p = ptest_udp;
    eth_set_hdr(&p->eth, destmac, pci_vn._config.mac,ETH_IP_PROTO);
    char myip[4]={192,168,122,98};
    ip_init_hdr(&p->ip,destip,myip,0x11);
    p->udp.source = htons(4444);
    p->udp.dest = htons(4444);
    udp_set_data(p,"Hello Yaos!", 12);
    //printk("ip.len:%dn",ntohs(p->ip.ip_len));
    dump_mem(p,ntohs(p->ip.ip_len)+sizeof(p->eth));

}
__used static void init_udp6_packet(char *destip,char *destmac)
{
    struct udp6_packet *p = ptest_udp6;
    eth_set_hdr(&p->eth, destmac, pci_vn._config.mac,ETH_P_IPV6);
    char myip[16]={0,0,0,0,0,0,0,0,0,0,0xff,0xff,192,168,122,98};
    ip6_init_hdr(&p->ip6,destip,myip,0x11);
    p->udp.source = htons(4444);
    p->udp.dest = htons(4444);
    udp6_set_data(p,"Hello Yaos! From IPV6", 22);
    //printk("ip.len:%dn",ntohs(p->ip.ip_len));
    dump_mem(p,ntohs(p->udp.len)+sizeof(p->ip6)+sizeof(p->eth));

}

init_udp_packet 和 init_udp6_packet这2个函数分别初始化一个ipv4的udp包跟一个ipv6的udp包。

启动一个定时器,在定时器里面发包:

__used static void vn_timeout(u64 nowmsec,void *param)
{
    set_timeout_nsec(5000000000, vn_timeout,param);

    pci_d("vntimeout:now msec:%dn", nowmsec/1000000UL);
    
    init_udp6_packet(host_ipv6,host_mac);
    virtio_net_send_packet(ptest_udp6,ntohs(ptest_udp6->udp.len)+sizeof(ptest_udp6->eth)
        +sizeof(ptest_udp6->ip6));
    char toip4[4]={192,168,122,1};
    init_udp_packet(toip4,host_mac);
    struct udp_packet *p = ptest_udp;
    virtio_net_send_packet(p,ntohs(p->ip.ip_len)+sizeof(p->eth));
}

这里指定了ip地址:192.168.122.98,现在还没有通过DHCP获取IP,也不支持ARP。

就通过手动指定ARP的方式:

arp -s 192.168.122.98 52:54:0:12:34:56

test/client为ipv4的udp发送包,在内核运行之后,可以运行client发包测试。

./test/client 192.168.122.98 4444

运行过arp -s 192.168.122.98 52:54:0:12:34:56之后

To:52:54:0:12:34:56,From:b6:ac:3:b6:32:e9: len:a38,type:8,prot:40,port:5c11,len:2a
addr:ffffffff80057284:
~80057284 - 52 54 00 12 34 56 b6 ac 03 b6 32 e9 08 00 45 00 RT..4V....2...E.
~80057294 - 00 2a 38 0a 40 00 40 11 8d 04 c0 a8 7a 01 c0 a8 .*8.@.@.....z...
~800572a4 - 7a 62 c8 9a 11 5c 00 16 bb 11 68 65 6c 6c 6f 20 zb.......hello.
~800572b4 - 69 27 6d 20 68 65 72 65         i'm.here
oldip:48d,newip:48d,oldudp:11bb,newudp:11bb

如果没有运行arp,结果为:

To:ff:ff:ff:ff:ff:ff,From:b6:ac:3:b6:32:e9: len:406,type:608,prot:0,port:0,len:800

这个是ARP包。

内核往外发包,可以通过tcpdump来查看

tcpdump -i tap0 -XX -xx -nn -c4

以上命令抓取tap0接口的4个包,注意要等内核运行之后再抓包。内核运行指挥tap0才会启动。

tcpdump -i tap0 -XX -xx -nn -c4
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tap0, link-type EN10MB (Ethernet), capture size 262144 bytes
23:58:54.572160 IP6 ::ffff:192.168.122.98.4444 > fe80::ec60:d1ff:feb3:91bc.4444: UDP, 
length 22
        0x0000:  ee60 d1b3 91bc 5254 0012 3456 86dd 6000  .`....RT..4V..`.
        0x0010:  0000 001e 11ff 0000 0000 0000 0000 0000  ................
        0x0020:  ffff c0a8 7a62 fe80 0000 0000 0000 ec60  ....zb.........`
        0x0030:  d1ff feb3 91bc 115c 115c 001e ea36 4865  ...........6He
        0x0040:  6c6c 6f20 5961 6f73 2120 4672 6f6d 2049  llo.Yaos!.From.I
        0x0050:  5056 3600                                PV6.
23:58:54.572173 IP 192.168.122.98.4444 > 192.168.122.1.4444: UDP, length 12
        0x0000:  ee60 d1b3 91bc 5254 0012 3456 0800 4500  .`....RT..4V..E.
        0x0010:  0028 0000 4000 ff11 0610 c0a8 7a62 c0a8  .(..@.......zb..
        0x0020:  7a01 115c 115c 0014 5992 4865 6c6c 6f20  z.....Y.Hello.
        0x0030:  5961 6f73 2100                           Yaos!.
23:58:59.582392 IP6 ::ffff:192.168.122.98.4444 > fe80::ec60:d1ff:feb3:91bc.4444: UDP,
 length 22
        0x0000:  ee60 d1b3 91bc 5254 0012 3456 86dd 6000  .`....RT..4V..`.
        0x0010:  0000 001e 11ff 0000 0000 0000 0000 0000  ................
        0x0020:  ffff c0a8 7a62 fe80 0000 0000 0000 ec60  ....zb.........`
        0x0030:  d1ff feb3 91bc 115c 115c 001e ea36 4865  ...........6He
        0x0040:  6c6c 6f20 5961 6f73 2120 4672 6f6d 2049  llo.Yaos!.From.I
        0x0050:  5056 3600                                PV6.
23:58:59.582408 IP 192.168.122.98.4444 > 192.168.122.1.4444: UDP, length 12
        0x0000:  ee60 d1b3 91bc 5254 0012 3456 0800 4500  .`....RT..4V..E.
        0x0010:  0028 0000 4000 ff11 0610 c0a8 7a62 c0a8  .(..@.......zb..
        0x0020:  7a01 115c 115c 0014 5992 4865 6c6c 6f20  z.....Y.Hello.
        0x0030:  5961 6f73 2100                           Yaos!.

test/server为udp 服务端测试程序,可以运行./test/server 192.168.122.1 4444进行测试收包。

./test/server 192.168.122.1 4444
create socket.
bind address to socket.
receive from 192.168.122.98: buffer:Hello Yaos!
receive from 192.168.122.98: buffer:Hello Yaos!
receive from 192.168.122.98: buffer:Hello Yaos!

gcc -o server udpserver.c

gcc -o client udpclient.c

进行编译。

运行本例:

git clone https://github.com/saneee/x86_64_kernel.git
cd 0021
make qemu

测试收包用test/udpclient.c,发包用test/udpserver.c,或者用tcpdump

BUG:

arch/x86_64/entry64.S: movq $init_per_cpu__init_stack+4096,%rax

初始化的时候固定了4K栈,修改INIT_STACK_SIZE不会改变初始化栈大小,需要修正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值