派猴子來的救兵html
關於IP的RFC文檔在此!python
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四類地址劃分的意義,除了在路由選擇的匹配中會配合子網劃分用到.ubuntu
之前本科的時候, IP配置好以後, 學校內網是能夠訪問的. 若是想訪問外網, 須要撥號.但撥號以後內網就不方便上了.小程序
學校網站有個小程序, 能夠傻瓜式的修改系統路由. 撥號以後內網外網一塊兒上.網絡
當時不明白這是個什麼東西, 其實如今也想不太明白怎麼回事, 也沒條件回學校試驗下了. 但至少如今也算大概明白路由表以及路由選擇了.併發
以前爲了學習, 拿公司的網絡實驗了一把. osx下面的雙網卡路由配置socket
用程序發送raw IP包的時候, Total Length 和 Checksum 會由內核自動生成. 至於爲啥,可能看到tcpip協議詳解下(實現)的時候就知道了吧.tcp
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