雙向解耦TCP協議開發(二)
在上一篇部落格中已經說明了本專案的技術路線,本篇部落格就來具體說說。
一.利用虛擬化技術搭建雙機器雙網絡卡的測試環境
首先是在VMWARE裡面新增兩個ubuntu虛擬機器,這個網上資料很多,在此就不贅述。這兩個ubuntu虛擬機器可以視為是編譯機,我們主要是藉助他們來編譯核心和製作檔案系統。這兩個虛擬機器的網路配置需要都配置成橋接模式。所謂橋接模式,簡單地說,就是把傳送給虛擬網絡卡的資料直接傳送給物理網絡卡,相當於虛擬網絡卡直接連在物理網絡卡之上。
然後就需要在每個虛擬機器裡面,藉助QEMU再構建出一個linux環境。這個linux環境跑的是我們自己製作的核心映象和檔案系統。具體可以參考部落格:
http://www.cnblogs.com/senix/archive/2013/02/21/2921221.html
http://www.cnblogs.com/pengdonglin137/p/5023340.html
出於測試需要,每個QEMU的環境都需要配置兩個網絡卡,這兩個網絡卡對應的IP地址處於不同的網段,並且對應不同的MAC地址。最終搭建的測試環境如下:
如上圖所示,有兩個QEMU模擬節點N1,N2。
N1的網路配置情況:
eth0 192.168.100.119 52:54:00:12:34:12 負責傳送資料
eth1 192.168.50.119 12:14:00:12:34:90 負責接收資料
N2的網路配置情況:
eth0 192.168.100.120 52:54:00:12:34:68 負責接收資料
eth1 192.168.50.120 12:14:00:12:34:57 負責傳送資料
二. 閱讀和修改TCP/IP協議棧原始碼
核心程式碼的體量很大,從0開始閱讀的難度比較大。我選取的核心版本是linux 2.6.26, 因為這個版本的核心原始碼是跟《追蹤Linux TCP/IP程式碼執行--基於2.6核心》這本書相配套的 ,網上相關資料也多一些。下面具體分析一下我做了哪些改動。
1. 使用者態介面
大家都知道TCP/IP協議棧提供了socket()、bind()、listen()這些使用者態介面,而核心層面的改動一般是以對使用者態透明為佳。所以我並沒有新增或者修改任何一個使用者態的介面,只是輕微改動了一下bind介面的物理含義。傳統的TCP/IP協議棧是C/S模式,client和server分工明確,一個server可以對應多個client。client需要預先配置server的地址,而server並不需要預先配置client的地址,client和server並不是對等的關係。但是雙向解耦TCP實際上針對P2P模式的環境,P2P的兩端是完全對等的,都需要知道對方的地址,這樣方可建立連線和完成資料通訊。
假設主動發起連線的節點為N1,另一個節點為N2。N1、N2的網路配置情況如上面所述。對於N1而言,我們需要知道N2的哪個IP地址是負責接收資料的,所以我們直接沿用int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)介面,在serv_addr上面配置N2的接收資料IP 192.168.100.120即可。於N2而言,我們同樣需要知道N1的哪個IP地址負責接收資料,這裡我就沿用了int bind(int sockfd,struct sockaddr * my_addr,int addrlen)介面,在myaddr裡面儲存N1接收資料的IP 192.168.50.119。 當然,bind本來的含義不是這樣的,為了支援我新設的定義,在底層是需要進行修改的。bind本來的含義是保證server只會接收發送到myaddr的ip、埠的資料,不過大多數情況下myaddr.ip都是設定成IPADDR_ANY,表示server會接收所有傳送到myaddr.port的資料。我們這裡修改bind的含義,影響並不大。接下來就沿著TCP三步握手、資料傳送、四步揮手這樣的過程來一步步分析底層的改動。
2. client傳送SYN包,要求建立TCP連線
只需要N1的使用者態呼叫connect,並且將serv_addr配置成192,168.100.120,那麼核心就會自己構造SYN包傳送給192.168.100.120,這一步核心層面不需要進行修改。
3. server收到client傳送的SYN包,給client回送SYN+ACK包
server端收到SYN包之後,經過層層呼叫會進入tcp_v4_conn_request函式,下面來看看這個函式的部分程式碼:
__be32 saddr = ip_hdr(skb)->saddr;
__be32 daddr = ip_hdr(skb)->daddr;
...
ireq->loc_addr = daddr;
ireq->rmt_addr = saddr;
...
/* First, grab a route. */
if (!dst && (dst = inet_csk_route_req(sk, req)) == NULL)
return -1;
skb = tcp_make_synack(sk, dst, req);
從上面擷取的這些程式碼可以看出,server端首先會解析SYN包,得到saddr和daddr。這裡saddr為192.168.100.119 daddr為192.168.100.120。然後給ireq結構體賦值,修改loc_addr、rmt_addr的值。然後,會進入dst = inet_csk_route_req(sk, req)
這個函式很重要,是要查詢路由!查詢什麼路由呢,就是查詢從server到client的路由。也就是說,會查詢源地址(ireq->loc_addr)為192.168.100.120,目的地址(ireq->rmt_addr)為192.168.100.119的路由。這是傳統的TCP/IP協議棧所做的事情,自然是在同一個鏈路(兩個網絡卡之間)進行資料傳輸,但是跟我們的需求自然是相悖的。
那麼我們怎麼修改呢?回憶前面的bind系統呼叫,我們實際上是在sk->daddr儲存了N1的接收IP 192.168.50.119。 因此,我們是在進入inet_csk_route_req之前,修改req的loc_addr和rmt_addr:
ireq->loc_addr = 0;
ireq->rmt_addr = inet->daddr;
然後進入inet_csk_route_req。查詢完路由表之後,得到路由表項rt,這時候rt->saddr就是192.168.50.120了。我們再修改ireq->loc_addr=rt->saddr,然後進入 tcp_make_synack
這裡就會根據我們的需求製作SYN+ACK包了。這個SACK包,是由192.168.50.120發往192.168.50.119的。
4. client收到SACK包,最後回送一個ACK包
這裡有一個問題。那就是client端如何識別出SACK包是自己需要的?在現有的TCP/IP協議棧程式碼中,client在傳送完SYN包之後,會根據hash[(saddr,daddr,sport,dport)]計算出一個值來標識對應的socket。那麼在收到SYN+ACK之後,client自然也會hash[(saddr,daddr,sport,dport)]來尋找對應的socket,但是此時的saddr、daddr已經變了,所以client是無法識別這個SACK包的!
那麼怎麼修改呢?這裡我採取了一個最簡單的辦法,我就只hash[sport,dport],這樣SYN包和SACK包的hash值就一樣了,因此client就可以順利識別這個SACK包並且找到相對應的socket。client只要識別出了SACK包,接下來發送ACK包的過程也是ok的,並不需要進行修改。
5. server收到ACK包,三步握手完成,建立一個新的socket fd
這一步在核心層面並不需要修改,到此雙向解耦TCP的三步握手完成。
6. client呼叫send傳送資料
這一步自然也是不需要修改的。
7. server收到資料,回覆ACK
這一步需要類似於步驟3做一下修改,具體的修改程式碼如下(tcp_rcv_established函式):
if(inet_sk(sk)->taddr)
{
inet_sk(sk)->daddr = inet_sk(sk)->taddr;
struct flowi fl = { .oif = sk->sk_bound_dev_if,
.nl_u = { .ip4_u =
{ .daddr = inet_sk(sk)->daddr,
.saddr = 0,
.tos = RT_CONN_FLAGS(sk) } },
.proto = sk->sk_protocol,
.uli_u = { .ports =
{ .sport = inet_sk(sk)->sport,
.dport = inet_sk(sk)->dport } } };
/* If this fails, retransmit mechanism of transport layer will
* keep trying until route appears or the connection times
* itself out.
*/
//security_sk_classify_flow(sk, &fl);
if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
goto discard;
inet_sk(sk)->saddr = rt->rt_src;
//inet_sk(sk)->saddr = 2016585920;
}
//inet_sk(sk)->rcv_addr = 0;
__tcp_ack_snd_check(sk, 0);
inet_sk(sk)->daddr = old_daddr;
inet_sk(sk)->saddr = old_saddr;
如上所述,在進入_tcp_ack_snd_check之前,需要先查詢路由表,然後修改sk->saddr即可。
8. server傳送資料
server主動傳送資料,同樣需要修改介面,修改函式tcp_write_xmit,修改程式碼同上。
9. client回送ACK
這個自然也不需要修改。
10.TCP四步揮手
也不需要修改程式碼的。
三.結果展示
我在使用者態寫了一個簡單的echo 應用程式,client從stdin獲取資料,然後傳送給server。server收到資料會馬上將其回送給client。wireshark的抓包結果如下:
從抓包結果上可以看出,TCP的三步握手、資料傳輸、四步揮手都是ok的。
相關推薦
雙向解耦TCP協議開發(二)
在上一篇部落格中已經說明了本專案的技術路線,本篇部落格就來具體說說。 一.利用虛擬化技術搭建雙機器雙網絡卡的測試環境 首先是在VMWARE裡面新增兩個ubuntu虛擬機器,這個網上資料很多,在此就不贅述。這兩個ubuntu虛擬機器可以視為是編譯機,我們主要是藉助他們來編譯核
ZigBee協議棧開發(二)
1、掌握ZigBee無線模組基本工作電路(官方): 在實際做東西的時候要加一些其他的東西,讓我們更好地應用這個基本電路。 復位電路:20腳如下接(1us低電平) 程式下載電路(五根線): 2、隨心所欲的控制io的輸入輸出 40腳,但晶片背面有額外的一個接地腳。 共有21個通用
linux網路程式設計之TCP/IP基礎(二):利用ARP和ICMP協議解釋ping命令
一、MTU 乙太網和IEEE 802.3對資料幀的長度都有限制,其最大值分別是1500和1492位元組,將這個限制稱作最大傳輸單元(MTU,Maximum Transmission Unit)。如果I
(我是初學者)第一次項目開發(二)開發中遇到的問題和註意事項
持久層 數據庫 認識 碼代碼 操作 出錯 排序 文檔 項目 這周正式開始做項目練習,這才發現實際去做的時候會遇到和出現很多的問題 在這裏說一說我的體會,請指正 首先,實體類 1、實體類中有哪些屬性,類型是什麽,並根據屬性建立sql的相應表格, 2、哪些屬性需要在寫在實體
ONOS:負載均衡路由算法及應用開發(二)
lan group uil etc src reactive core 函數的調用 pty ONOS:負載均衡路由算法及應用開發(二) 本文將為大家講述應用的實現,並進行必要的代碼分析。 本應用暫時以Maven作為項目的構建工具,並采用最簡單的sin
服務器控件開發(二)
oev table 中新 我們 simple wire num sheet register 區分Control類和WebControl類: 服務器控件可以繼承自這兩個類; 探究這個兩個類的區別,以及這兩個類分別適用在哪種情況! 本文的學習過程中
android的百度地圖開發(二) 定位
頻率 update 殺死 一次 ddr animate 語義 pri des 參考:http://blog.csdn.net/mr_wzc/article/details/51590485 第一步,初始化LocationClient類 //獲取地圖控件引用
帶你從零學ReactNative開發跨平臺App開發(二)
lin lob 進行 ava img develop glob pow gist ReactNative跨平臺開發系列教程: 帶你從零學ReactNative開發跨平臺App開發(一) 上一篇教程我們一步步配置了開發RN的必備環境,這篇文章我們依然配置環境,昨天配置
微信公眾平臺開發(二)網頁授權
是否 color 平臺開發 基本 業務 自動跳轉 str gpo 點擊 微信公眾平臺OAuth2.0授權詳細步驟如下: 1. 用戶關註微信公眾賬號。2. 微信公眾賬號提供用戶請求授權頁面URL。3. 用戶點擊授權頁面URL,將向服務器發起請求4. 服務器詢問用戶是否同意授權
corethink功能模塊探索開發(二)讓這個模塊可安裝
eth title brush pre 點擊 mod 頂部 是否 per 要想讓這個模塊可安裝,只需要在opcmf.php文件中寫一些配置數據就行 隨便寫點 Equip/opencmf.php <?php // 模塊信息配置 retu
QtCreator插件開發(二)——QtCreator菜單和菜單項
QtCreator插件開發 菜單QtCreator插件開發(二)——QtCreator菜單和菜單項 一、QtCreator菜單欄簡介 1、QtCreator菜單簡介 QtCreator菜單欄如下:QtCreator默認菜單包括“文件”、“編輯”、“工具”、“窗體”、“幫助”。“構建”、“調試”、“分析”由插
星雲鏈智能合約開發(二):Mac下安裝星雲鏈
Mac下安裝星雲鏈 星雲鏈智能合約開發 Golang環境搭建 版本用最新版:1.10.2 安裝 brew install go 配置環境變量 vi ~/.bash_profile 打開.bash_profile文件,按"i"鍵可進行編輯,添加: export GOROOT=/u
【轉載】Vue 2.x 實戰之後臺管理系統開發(二)
null element asc 其他 就會 ans 目錄 asi all 2. 常見需求 01. 父子組件通信 a. 父 -> 子(父組件傳遞數據給子組件) 使用 props,具體查看文檔 - 使用 Prop 傳遞數據(cn.vuejs.org/v2/guide
loadrunner 虛擬用戶開發(二)
loadrunner loadrunner腳本 loadrunner函數 日誌 錯誤處理與日誌函數1)選中此項表示當腳本出現錯誤時繼續執行,這是全局化的控制,對所有腳本有效。 2)非關鍵資源錯誤當做警告,如果把這項取消掉,一些圖片類的非html資源錯誤,就會當做錯誤處理,而不是warning。
C#上位機開發(二)
styles 寫代碼 面向 ext size ring 入口 查詢法 命令 上一篇大致了解了一下單片機實際項目開發中上位機開發部分的內容已經VS下載與安裝,按照編程慣例,接下來就是“Hello,World!” 1、新建C#項目工程 首先選擇新建Windows窗體應
Go語言開發(二)、Go語言基礎
Go 語言 基礎 Go語言開發(二)、Go語言基礎 一、Go語言程序結構 Go語言程序基本結構如下:A、包聲明B、引入包C、函數D、變量E、語句 & 表達式F、註釋 package main //包聲明 import "fmt" //引入包 func main(){ //main函數
微信公眾號開發(二)--掃碼綁定微信賬號
stat 需要 ech 不同 指定 步驟 引導 connect amp 簡書地址:https://www.jianshu.com/p/b2884a226247 當業務系統產生消息需要通過微信推送給指定的用戶時,首頁需要將業務系統類的用戶和微信賬號建立一個關系。這裏采用的是微
基於Html5 Plus + Vue + Mui 移動App 開發(二)
spa mar oot ready sca ror extra ref 連接 基於Html5 Plus + Vue + Mui 移動App 開發(二) 界面效果: 本頁面采用Html5 Plus + Vue + Mui 開發移動界面,本頁面實現: 1、下拉刷新、上
HyperLeger Fabric開發(二)——HyperLeger Fabric入門
out 版本 comm 開發 https .gz image roc 繼續 HyperLeger Fabric開發(二)——HyperLeger Fabric入門 本文使用RHEL 7.3 workstation版本操作系統。 一、HyperLeger Fabric環境部署
一步一步學習Android TV/盒子開發(二)
TV、機頂盒開發除錯不能像手機一樣通過USB線連線除錯,可通過ADB連線除錯 連線電視 adb connect 10.74.84.199 1 2 連線後就可以開始開發除錯了! 斷開連線 // 斷開某個裝置 adb disconnect 10.74.84