tcp/ip协议学习 第三章 IP协议
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