关于IP的RFC文档在此!

IP的头文件还是先贴一下, 总是记不住.

   0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

网络和子网划分

不太明白ABCD四类地址划分的意义,除了在路由选择的匹配中会配合子网划分用到.

路由表, 路由选择

以前本科的时候, IP配置好之后, 学校内网是可以访问的. 如果想访问外网, 需要拨号.但拨号之后内网就不方便上了.

学校网站有个小程序, 可以傻瓜式的修改系统路由. 拨号之后内网外网一起上.

当时不明白这是个什么东西, 其实现在也想不太明白怎么回事, 也没条件回学校试验下了. 但至少现在也算大概明白路由表以及路由选择了.

之前为了学习, 拿公司的网络实验了一把. osx下面的双网卡路由配置

来段程序吧

python

用程序发送raw IP包的时候, Total Length 和 Checksum 会由内核自动生成. 至于为啥,可能看到tcpip协议详解下(实现)的时候就知道了吧.

Checksum 算法在模拟后面协议的时候就要自己实现了.ICMP、IGMP、UDP和TCP都采用相同的检验和算法.

有个不同点是: 首部检验和字段是根据IP头计算的检验和码。它不对body进行计算。 ICMP、 IGMP、UDP和TCP的checksum覆盖首部和数据。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''发送一个裸的IP包, 20字节的IP头, 后面跟一个随便写的字符串.
还不知道IP包的ID应该根据什么生成, 就随便写了一个54321
IP头里面:
    IP包总长度属性和checksum属性都是内核自动生成的.
    协议是用的socket.IPPROTO_TCP,也就是6.但没什么用,IP包里面就随便的字符串,不是按TCP协议来的.
'''

import socket
from struct import pack
import sys


def main():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
    except socket.error as msg:
        print 'Socket could not be created. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        return

    packet = ''

    source_ip = '127.0.0.1'
    dest_ip = '127.0.0.1'

    # ip header fields
    ip_ver = 4
    ip_ihl = 5
    ip_tos = 0
    ip_tot_len = 0  # kernel will fill the correct total length
    ip_id = 54321
    ip_frag_off = 0
    ip_ttl = 32
    ip_proto = socket.IPPROTO_TCP  # no use in this case
    ip_checksum = 0    # kernel will fill the correct checksum

    ip_saddr = socket.inet_aton(source_ip)
    ip_daddr = socket.inet_aton(dest_ip)

    ip_ihl_ver = (ip_ver << 4) + ip_ihl

    # the ! in the pack format string means network order
    ip_header = pack(
        '!BBHHHBBH4s4s',
        ip_ihl_ver,
        ip_tos,
        ip_tot_len,
        ip_id,
        ip_frag_off,
        ip_ttl,
        ip_proto,
        ip_checksum,
        ip_saddr,
        ip_daddr)

    user_data = sys.argv[1] if sys.argv[1:] else '0123456789'

    packet = ip_header + user_data

    # the port specified has no effect
    r = s.sendto(packet, (dest_ip, 0))

    # result is the length of packet sent out
    print r

if __name__ == '__main__':
    main()
    ip_check = 1231    # kernel will fill the correct checksum

抓包. 运行环境, ubuntu12.04

% sudo tcpdump -i lo -x -vvv -t -c 1                                                                                                ✭
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
IP (tos 0x0, ttl 32, id 54321, offset 0, flags [none], proto TCP (6), length 30)
    localhost.12337 > localhost.12851:  tcp 10 [bad hdr length 0 - too short, < 20]
	0x0000:  4500 001e d431 0000 2006 c8a6 7f00 0001
	0x0010:  7f00 0001 3031 3233 3435 3637 3839
1 packet captured
2 packets received by filter
0 packets dropped by kernel

tcp 10 [bad hdr length 0 - too short, < 20] 是说IP body里面的字节数太少, 只有10, 小于TCP header应有的20字节.

  • 1字节, 45, 4代表Version, 5代表Header Length, 单位是4Byte.

  • 2字节, 00, OTS(Type of Service), 代表此IP包的最小时延,最大吞吐量,最高可靠性, 最小费用等特征. 比如用于FTP的控制的IP包, 应该是最小时延, 用于FTP的数据的IP包, 则应该是最大吞吐量.书中提到:

    现在大多数的TCP/IP实现都不支持TOS特性,但是自4.3BSD Reno以后的新版系统都对它 进行了设置。另外,新的路由协议如 OSPF和IS-IS都能根据这些字段的值进行路由决策。

  • 3,4字节, 001e, Total Length. 0x1e = 30; 发送的01234567890+20字节的IP Header.
    因为一些数据链路(如以太网)需要填充一些数据以达到最小长度。尽管以太网的最小帧长为 46字节,但是IP数据可能会更短,像我们就是30。如果没有总长度字段,那么IP层就不知道46字节中有多少是IP数据报的内容。所以IP包总长度这个字段是一定需要的.

  • 5,6字节, Identification. 0xd431=54321.

  • 7,8字节, 0000.

  • 9字节, 20. TTL. 路由转发此IP包的时候就把这个数值减1, 减到0的时候就直接丢弃, 并发送ICMP通知主机. trouceroute即利用了这个.

  • 10字节, 06, 代表TCP协议.

  • 11,12字节 c8a6. Checksum.

  • 13-16字节, 7f00 0001. Source Address. 代表127.0.0.1

  • 17-20字节, 7f00 0001. Destination Address. 代表127.0.0.1

Checksum是内核帮我们算好的, 但以后的TCP等就要自己算了, 这里先看一下算法.

首先把检验和字段置为 0。然后,对首部中每个 16 bit 进行二进制反码求和(整个首部看成是由一串 16 bit的字组成)

def checksum(ip_header):
    ip_header += chr(0x00)*(len(ip_header)%2)
    r = 0
    while ip_header:
        # 因为ip_header已经是网络序了, 所以这里用!H. 否则需要用H
        r += unpack('H',ip_header[:2])[0]
        ip_header = ip_header[2:]

    if r > 0xffff:
        r = r&0xffff + (r>>16);

    return ~r