摘要:分析解決lvs fullnat模式下少量的請求記錄client IP不是用戶真實的IP地址問題.
摘要
分析解決lvs fullnat模式下少量的請求記錄client IP不是用戶真實的IP地址問題.
原創(chuàng)文章:來自分析lvs fullnat模式下后端服務器獲取真實IP地址異常問題
問題背景
lvs fullnat模式下觀察后端服務器realserver http/https業(yè)務運行系統(tǒng)日志,有時候可以發(fā)現(xiàn)有少量的請求記錄的client IP不是用戶真實的IP地址(存在但出現(xiàn)的概率很小,增加了問題排查的難度),而是屬于lvs主機私有的IP地址。關于fullnat的簡介可以參考http://www.baokaijian.com/?tag=fullnat,這里借用一下文中的圖片。
問題分析
大多數(shù)TCP正常建立連接過程

realserver linux系統(tǒng)屬于用戶自屬管理默認配置MTU=1500、MSS=1460,在TCP建連時可以通過syn+ack報文通告給client,而client端可屬于移動端或PC端MTU、MSS的差異性較大,通過syn報文通告給realserver端,從而建立的TCP連接取兩者之小值做為鏈路的MSS。中間的LVS只是做連接與報文轉發(fā)的負載均衡。
fullnat 模式下獲取真實IP地址的原理:
開源的lvs fullnat代碼與patch可查閱lvs官方網(wǎng)址http://kb.linuxvirtualserver.org/wiki/IPVS_FULLNAT_and_SYNPROXY
lvs fullnat應用模式通過TCP三次握手的ack,ack+data報文將client端的真實IP地址插入到tcp options中從而帶給后端的realserver。而后端realserver toa內核模塊中通過hook了tcp_v4_syn_recv_sock()函數(shù),然后調用get_toa_data()從tcp_options中取得客戶端的真實IP地址,具體的調用過程下邊分析。
非標準的tcp三次握手情況分析
先來看以下幾個典型的報文序列是否可以建立三次握手:
1. 亂序的情況(第三個ack和第四個ack+data報文亂序)

直接使用ack+data報文(或含有push標志)不存在bare ack報文

3.直接使用fin+ack+data報文( 或含有push標志)

來點內核TCP代碼中深度分析:
看一下建立TCP連接時兩次經過的tcp_v4_hnd_req函數(shù)流程.
syn報文到來時的處理流程:
tcp_v4_rcv() -->tcp_v4_do_rcv()-->tcp_v4_hnd_req()
查找代表客戶端連接的sock結構,如果沒有找到則返回代表服務器的sock結構, 然后在tcp_rcv_state_process()函數(shù)中調用tcp_v4_conn_request()創(chuàng)建req_sock請求結構并放入半連接隊列。
握手階段收到第三個ack報文或其他攜帶ACK標志的報文處理,tcp_v4_hnd_req()函數(shù)將返回代表連接請求的sock結構.
tcp_v4_rcv() ->tcp_v4_do_rcv()-->tcp_v4_hnd_req()-->tcp_check_req()-->tcp_v4_syn_recv_sock()
tcp_v4_syn_recv_sock()函數(shù)中將對報文序列號判斷,并完成握手,最后將連接請求從半連接請求移到全連接隊列,等待應用層調用accept。
特別注意在tcp_check_req()函數(shù)中檢查了clinet端到來報文的序列號
/* RFC793 page 36: "If the connection is in any non-synchronized state ...
* and the incoming segment acknowledges something not yet
* sent (the segment carries an unacceptable ACK) ...
* a reset is sent."
*
* Invalid ACK: reset will be sent by listening socket
*/if((flg & TCP_FLAG_ACK) &&// 正常情況下 收包的ack_seq = 初始發(fā)送的seq + 1(syn)
(TCP_SKB_CB(skb)->ack_seq !=? ? //此報序列號不等于發(fā)送初始序列號? ? ? tcp_rsk(req)->snt_isn +1+ tcp_s_data_size(tcp_sk(sk))))returnsk;
結論: 完成握手最后一個clinet端到來報文ack序號必須是client 端tcp連接ISN初始序號+1(沒有檢查seq序列號),所以只要符合此規(guī)則的都可以通過,然后調用調用tcp_v4_syn_recv_sock()從而調用toa模塊取client IP tcp options。再解釋明白點: 非bare ack亂序報文即帶有數(shù)據(jù)的ack報文,也可以通過。注:至于seq序列號有效性以及影響能不能真正完成連接建立,這是在后續(xù)的tcp_ack()中判斷了。
接著tcp_v4_syn_recv_sock()函數(shù)中創(chuàng)建代表與client端連接的sock結構(設置此條TCP連接狀態(tài)是TCP_SYN_RECV),然后在tcp_rcv_state_process()函數(shù)中case TCP_SYN_RECV代碼段中通過tcp_set_state(sk, TCP_ESTABLISHED); 改狀態(tài)為ESTABLISHED連接狀態(tài)。
經過在toa模塊中探點調試發(fā)現(xiàn):
從realserver端看到大量使用FIN+ACK+PUSH的含數(shù)據(jù)報文完成三次握手,這時也不能插入toa options(用戶業(yè)務很多用http/https這種post請求推數(shù)據(jù),realserver收到后會回復response,但若是反向代理的proxy收到后會FIN掉client,同時向后端realserver再發(fā)RST報文)
上述圖中因亂序或直接ack+data報文沒超但達到了1444字節(jié)(假如toa需要16字節(jié)); 使用ack+data報文完成了三次握手, 使lvs層不能插入用戶的IP:PORT
改進建議
官方lvs fullnat代碼:
http://kb.linuxvirtualserver.org/wiki/IPVS_FULLNAT_and_SYNPROXY
Lvs-fullnat-synproxy.tar.gz 包中包含fullnat 和toa的patch.
1、 lvs代碼tcp_fnat_in_handler()函數(shù)中去掉!tcph->fin的條件判斷,可以解決FIN+PUSH+ACK+數(shù)據(jù)報文中不能攜帶client端ip與端口的缺陷
2、 后端RS建立監(jiān)聽時直接使用 setsockopt, 將MSS改?。ㄈ鐃oa需要16字節(jié)則可減小到1444字節(jié)), 從而client到來的報文可以有插入clientip:port的空間
3、 (第2條的替代方法但會增加lvs處理的復雜性:
TCP握手階段lvs收到后端RS發(fā)送的syn+ack報文時,將其中的MSS改小(如16字節(jié)),傳給client, 從而client到來的報文就有了插入clientip:port的空間。
另一種方法雖然client發(fā)送來的報文已經達到了client與RS協(xié)商的MSS(如1460), 但若lvs與RS之間設置的MTU(如9000)要大于client與LVS之間的值1460, 并且tcp options有足夠的空間可以強制在tcp options中插入clientip:port)
4、以上修改后最后還是不行(可能存在非標準的client tcp處理),建議客戶端程序(特別是手機APP)建立socket時使用 setsockopt 將MSS改小適當?shù)臄?shù)值(發(fā)現(xiàn)存在有部分終端出現(xiàn)通告給其MSS是1444,但仍然發(fā)送1460的報文)
備注:
1. tcp options滿載40字節(jié),如果在握手時用戶有插入自己的數(shù)據(jù)占滿則lvs判斷沒有空間再插入用戶IP了。
發(fā)現(xiàn)手機終端的MSS值多種多樣,這里列里幾個值, 1412 1120 1452 1400 1394 1260 1460