python raw socket 介紹


因為要使用 python 底層發包模塊,也就是 raw socket 發包模式,所以在此深入了解一下 python socket 通信。

涉及到的函數:

import socket
socket()
setsockopt()
sendto()
recvfrom()

因為使用的是原始套接字,所以我們不使用bind/connect函數,參照《unix 網絡編程》

bind 函數僅僅設置本地地址。就輸出而言,調用bind函數設置的是將用於從這個原始套接字發送的所有數據報的源IP地址。如果不調用bind,內核就吧源IP地址設置為外出接口的主IP地址。

connect函數僅僅設置外地地址,同樣因為原始套接字不存在端口號的概念。就輸出而言,調用connect之后我們可以把sendto調用改為write或者send調用,因為目的IP地址已經指定了。

順便說一句,connect函數也是三次握手的發生過程,參見鏈接

套接字參數

官網介紹:

socket.socket([family[, type[, proto]]])

參數說明:

family:協議簇/地址簇。

最常用的就是 socket.AF_INET 了,TCP/UDP 通信均屬於此類型。

PS:有時我們看到的協議常量名為 AF_xxx,有時又是 PF_xxx,可以理解為 address family 和 protocol family,實際使用中是沒有區別的。一般在區分協議的時候習慣使用 PF ,而在區分地址的時候習慣使用AF。可以參照這個鏈接的解釋:

Yes. AF_foo means address family foo, and PF_foo means protocol family foo. In Linux, they are always been the same values, I believe.

Traditionally, the PF_foo constants were used for socket(), but AF_foo in the struct sockaddr structure.

According to man 2 socket, even the (historical) BSD 4.x man page states that "The protocol family generally is the same as the address family", and subsequent standards use AF_* everywhere.

Thus, today, there really should be no difference between AF_foo and PF_foo.

除此之外常用的還有 AF_UNIX/AF_LOCAL ,代表UNIX域協議,屬於IPC(進程間通信)的一種方式;AF_INET6 ,IPv6 通信。

  • socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信
  • socket.AF_INET 服務器之間網絡通信
  • socket.AF_INET6 IPv6

type:socket的類型

官網給出的列表如下:

  • socket.SOCK_STREAM
  • socket.SOCK_DGRAM
  • socket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。

還有兩種就是 socket.SOCK_RDM 與 socket.SOCK_SEQPACKET,基本沒見過用

前兩種分別代表 面向流(TCP)和面向數據報(UDP)的socket通信。

(我的理解是:SOCK_RAW = 協議頭部我也自己發)

proto: 協議類型

常見的為

IPPROTO_ICMP = 1
IPPROTO_IP = 0
IPPROTO_RAW = 255
IPPROTO_TCP = 6
IPPROTO_UDP = 17

設置套接字選項

setsockopt:設置套接字選項

socket.setsockopt(level, optname, value)

具體參數查看鏈接

level:參數作用范圍,常見的包括:

SOL_SOCKET SOL應該是指的 SOck Level ,意為套接字層選項,常見的有 SO_REUSEADDR ,可以服用處於 Time_wait 狀態的端口。

IPPROTO_IP IP數據包選項,一個將要用到的是 IP_HDRINCL ,如果是TRUE,IP頭就會隨即將發送的數據一起提交,並從讀取的數據中返回。

還有 IPPROTO_TCP 等,此處不多做介紹。

SOCKET通信例子

TCP/UDP 通信

先放兩個簡單的例子

UDP Server

import socket  
  
address = ('127.0.0.1', 31500)  
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  
s.bind(address)  
  
while True:  
    data, addr = s.recvfrom(2048)  
    if not data:  
        print "client has exist"  
        break  
    print "received:", data, "from", addr  
  
s.close()

UDP Client

import socket  
  
address = ('127.0.0.1', 31500)  
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  
  
while True:  
    msg = raw_input()  
    if not msg:  
        break  
    s.sendto(msg, address)  
  
s.close()  

TCP Server

import socket  
  
address = ('127.0.0.1', 31500)  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # s = socket.socket()  
s.bind(address)  
s.listen(5)  
  
ss, addr = s.accept()  
print 'got connected from',addr  
  
ss.send('byebye')  
ra = ss.recv(512)  
print ra  
  
ss.close()  
s.close()

TCP Client

import socket  
  
address = ('127.0.0.1', 31500)  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect(address)  
  
data = s.recv(512)  
print 'the data received is',data  
  
s.send('hihi')  
  
s.close()  

RAW SOCKET 通信

接下來我們看一個raw socket通信的例子

import sys
import socket
from impacket import ImpactDecoder, ImpactPacket
 
def main():
 
    if len(sys.argv) < 3:
        print "Use: %s <src ip> <dst ip>" % sys.argv[0]
        print "Use: %s <src ip> <dst ip> <cnt>" % sys.argv[0]
        sys.exit(1)
    elif len(sys.argv) == 3:
        src = sys.argv[1]
        dst = sys.argv[2]
        cnt = 1
    elif len(sys.argv) ==4:
        src = sys.argv[1]
        dst = sys.argv[2]
        cnt = sys.argv[3]
    else:
        print "Input error!"
        sys.exit(1)
#print src, dst
    ip = ImpactPacket.IP()
    ip.set_ip_src(src)
    ip.set_ip_dst(dst)
 
    tcp = ImpactPacket.TCP()
    tcp.set_th_sport(55968)
    tcp.set_th_dport(80)
    tcp.set_th_seq(1)
    tcp.set_th_ack(1)
    tcp.set_th_flags(0x18)
    tcp.set_th_win(64)
 
    tcp.contains( ImpactPacket.Data("GET /att/DIYLife/41264/528 HTTP/1.1\r\nHost: 192.168.111.1\r\nAccept-Encoding: identity\r\n\r\n"))
 
    ip.contains(tcp)
 
    # Open a raw socket. Special permissions are usually required.
    s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
    s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)  // 此選項設置使用我們自己構造的IP頭部
    seq_id = 0
    while cnt >= 1:
        # Calculate its checksum.
        seq_id = seq_id + 1
        tcp.set_th_seq(seq_id)
        tcp.calculate_checksum()
 
        # Send it to the target host.
        s.sendto(ip.get_packet(), (dst,80))
        cnt= cnt -1
 
if __name__ == '__main__':
    main()

看完這幾個例子之后,說一下我的看法。

Socket 的作用就是封裝了各種不同的底層協議,為我們提供一個統一的操作接口。使用socket通信的時候,我們只需要根據協議類型來初始化相應的socket,然后將我們需要寫入的數據傳入該socket即可。

因此,在初始化之后,socket為我們做了這么幾件事情:

  1. 對於面向流的連接如TCP,可以幫助我們自動完成三次握手(connect函數)和四次揮手(close函數)的過程
  2. 在我們每次發送數據的時候,將我們要發送的數據根據默認或者你設置的選項包裹好包頭,將其交給網卡的發送緩沖區
  3. 接受數據的時候,幫助我們去掉包頭

由於不同協議都可以使用同樣的接口進行發送和接受數據,因此,區分不同包頭的過程都是在socket()函數中完成的。

總結

包結構圖

創建四層以上的套接字

直接使用 socket.socket(socket.AF_INET,socket.SOCK_STREAM/socket.SOCK_DGRAM , socket.IPPROTO_TCP) 即可,proto 可以自動推斷(等價於IPPROTO_IP),也可以直接簡寫為s = socket.socket()

意味着我們需要填充的內容僅僅是包結構圖中的 [ 數據 ] 部分的內容

創建三層套接字

  1. 自行填充TCP頭/UDP頭,IP頭部交給內核填充

意味着我們需要填充的是包結構圖中的 [ TCP包頭 | 數據 ]

此時由於四層協議頭部需要由我們自己填充,就有一個問題:如果是四層以上套接字的話,我們是不用告訴socket協議名的,程序會自動根據你的端口號來區分應用層協議。但是如果你填充四層協議頭的話,socket就必須提前知道是什么協議,用來填充IP頭部的協議字段,也就是說協議字段不能為IPPROTO_IP。

因此,我們就需要傳入 socket 函數的第三個參數。例如我們要自己構造TCP包,可以用 socket.socket(socket.AF_INET,socket.SOCK_RAW , socket.IPPROTO_TCP )

  1. 自行填充 四層協議頭部和IP頭部(限定是IP協議)

意味着我們需要填充的是包結構圖中的 [ IP包頭 | TCP包頭 | 數據 ] 的內容。

這個和上面那個差不多,只不過我們可以修改IP頭部,一種方式是:

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)  # 設置 IP 頭部自己發送

另外一種方式是:

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)

這兩種方式應該都是僅僅限於發送IP協議,所以 Ethernet 頭部的協議字段不用我們填充~

創建二層套接字

方式1:

socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))

自行填充 以太網包頭

意味着我們需要填充的是上圖中的 [ MAC包頭 | IP包頭 | TCP包頭 | 數據 ] 的內容。

方式2:

socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))

使用SOCK_RAW發送的數據必須包含鏈路層的協議頭,接受得到的數據包,包含鏈路層協議頭。而使用SOCK_DGRAM則都不含鏈路層的協議頭。

也即是說,需要填充的是上圖中的 [ IP包頭 | TCP包頭 | 數據 ] 的內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM