การจำลองปัญหาเครือข่ายใน Linux

สวัสดีทุกคน ฉันชื่อ Sasha ฉันเป็นผู้นำการทดสอบแบ็กเอนด์ที่ FunCorp เราก็เหมือนกับคนอื่นๆ อีกหลายรายที่ได้นำสถาปัตยกรรมที่มุ่งเน้นการบริการมาใช้ ประการหนึ่ง สิ่งนี้ทำให้งานง่ายขึ้นเพราะ... การทดสอบแต่ละบริการแยกจากกันง่ายกว่า แต่ในทางกลับกัน จำเป็นต้องทดสอบการโต้ตอบของบริการซึ่งมักเกิดขึ้นบนเครือข่าย

ในบทความนี้ ฉันจะพูดถึงยูทิลิตี้สองตัวที่สามารถใช้ตรวจสอบสถานการณ์พื้นฐานที่อธิบายการทำงานของแอปพลิเคชันเมื่อเกิดปัญหาเครือข่าย

การจำลองปัญหาเครือข่ายใน Linux

การจำลองปัญหาเครือข่าย

โดยปกติแล้ว ซอฟต์แวร์จะได้รับการทดสอบบนเซิร์ฟเวอร์ทดสอบที่มีการเชื่อมต่ออินเทอร์เน็ตที่ดี ในสภาพแวดล้อมการผลิตที่รุนแรง สิ่งต่าง ๆ อาจไม่ราบรื่นนัก ดังนั้นบางครั้งคุณจำเป็นต้องทดสอบโปรแกรมในสภาพการเชื่อมต่อที่ไม่ดี บน Linux ยูทิลิตี้นี้จะช่วยในการจำลองเงื่อนไขดังกล่าว tc.

ทีซี(คำย่อ จากการควบคุมการจราจร) ช่วยให้คุณสามารถกำหนดค่าการส่งแพ็กเก็ตเครือข่ายในระบบได้ ยูทิลิตี้นี้มีความสามารถที่ยอดเยี่ยม คุณสามารถอ่านเพิ่มเติมเกี่ยวกับสิ่งเหล่านี้ได้ ที่นี่. ฉันจะพิจารณาเพียงไม่กี่ข้อเท่านั้น: เราสนใจการจัดตารางเวลาการรับส่งข้อมูลที่เราใช้ คิวดิสก์และเนื่องจากเราจำเป็นต้องจำลองเครือข่ายที่ไม่เสถียร เราจึงใช้ qdisc แบบไม่มีคลาส เนเทม.

มาเปิดตัวเซิร์ฟเวอร์ echo บนเซิร์ฟเวอร์กัน (ฉันใช้ nmap-ncat):

ncat -l 127.0.0.1 12345 -k -c 'xargs -n1 -i echo "Response: {}"'

เพื่อที่จะแสดงรายละเอียดการประทับเวลาทั้งหมดในแต่ละขั้นตอนการโต้ตอบระหว่างไคลเอนต์และเซิร์ฟเวอร์ ฉันจึงเขียนสคริปต์ Python ธรรมดาที่ส่งคำขอ ทดสอบ ไปยังเซิร์ฟเวอร์เสียงก้องของเรา

ซอร์สโค้ดไคลเอ็นต์

#!/bin/python

import socket
import time

HOST = '127.0.0.1'
PORT = 12345
BUFFER_SIZE = 1024
MESSAGE = "Testn"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
t1 = time.time()
print "[time before connection: %.5f]" % t1
s.connect((HOST, PORT))
print "[time after connection, before sending: %.5f]" % time.time()
s.send(MESSAGE)
print "[time after sending, before receiving: %.5f]" % time.time()
data = s.recv(BUFFER_SIZE)
print "[time after receiving, before closing: %.5f]" % time.time()
s.close()
t2 = time.time()
print "[time after closing: %.5f]" % t2
print "[total duration: %.5f]" % (t2 - t1)

print data

มาเปิดใช้งานและดูปริมาณการใช้งานบนอินเทอร์เฟซ lo และพอร์ต 12345:

[user@host ~]# python client.py
[time before connection: 1578652979.44837]
[time after connection, before sending: 1578652979.44889]
[time after sending, before receiving: 1578652979.44894]
[time after receiving, before closing: 1578652979.45922]
[time after closing: 1578652979.45928]
[total duration: 0.01091]
Response: Test

การถ่ายโอนข้อมูลการจราจร

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
10:42:59.448601 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [S], seq 3383332866, win 43690, options [mss 65495,sackOK,TS val 606325685 ecr 0,nop,wscale 7], length 0
10:42:59.448612 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [S.], seq 2584700178, ack 3383332867, win 43690, options [mss 65495,sackOK,TS val 606325685 ecr 606325685,nop,wscale 7], length 0
10:42:59.448622 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 606325685 ecr 606325685], length 0
10:42:59.448923 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 606325685 ecr 606325685], length 5
10:42:59.448930 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [.], ack 6, win 342, options [nop,nop,TS val 606325685 ecr 606325685], length 0
10:42:59.459118 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 606325696 ecr 606325685], length 14
10:42:59.459213 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 15, win 342, options [nop,nop,TS val 606325696 ecr 606325696], length 0
10:42:59.459268 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 606325696 ecr 606325696], length 0
10:42:59.460184 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 606325697 ecr 606325696], length 0
10:42:59.460196 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 606325697 ecr 606325697], length 0

ทุกอย่างเป็นไปตามมาตรฐาน: การจับมือสามทาง, PSH/ACK และ ACK สองครั้ง - นี่คือการแลกเปลี่ยนคำขอและการตอบกลับระหว่างไคลเอนต์และเซิร์ฟเวอร์ และ FIN/ACK และ ACK สองครั้ง - การเชื่อมต่อเสร็จสมบูรณ์

ความล่าช้าของแพ็คเก็ต

ตอนนี้เรามาตั้งค่าการหน่วงเวลาเป็น 500 มิลลิวินาที:

tc qdisc add dev lo root netem delay 500ms

เราเปิดตัวไคลเอนต์และเห็นว่าสคริปต์ทำงานเป็นเวลา 2 วินาที:

[user@host ~]# ./client.py
[time before connection: 1578662612.71044]
[time after connection, before sending: 1578662613.71059]
[time after sending, before receiving: 1578662613.71065]
[time after receiving, before closing: 1578662614.72011]
[time after closing: 1578662614.72019]
[total duration: 2.00974]
Response: Test

มีอะไรอยู่ในการจราจร? มาดูกัน:

การถ่ายโอนข้อมูลการจราจร

13:23:33.210520 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [S], seq 1720950927, win 43690, options [mss 65495,sackOK,TS val 615958947 ecr 0,nop,wscale 7], length 0
13:23:33.710554 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [S.], seq 1801168125, ack 1720950928, win 43690, options [mss 65495,sackOK,TS val 615959447 ecr 615958947,nop,wscale 7], length 0
13:23:34.210590 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 615959947 ecr 615959447], length 0
13:23:34.210657 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 615959947 ecr 615959447], length 5
13:23:34.710680 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [.], ack 6, win 342, options [nop,nop,TS val 615960447 ecr 615959947], length 0
13:23:34.719371 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 615960456 ecr 615959947], length 14
13:23:35.220106 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [.], ack 15, win 342, options [nop,nop,TS val 615960957 ecr 615960456], length 0
13:23:35.220188 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 615960957 ecr 615960456], length 0
13:23:35.720994 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 615961457 ecr 615960957], length 0
13:23:36.221025 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 615961957 ecr 615961457], length 0

คุณจะเห็นว่าความล่าช้าที่คาดไว้ครึ่งวินาทีปรากฏขึ้นในการโต้ตอบระหว่างไคลเอนต์และเซิร์ฟเวอร์ ระบบจะทำงานน่าสนใจยิ่งขึ้นหากความล่าช้ามากขึ้น: เคอร์เนลเริ่มส่งแพ็กเก็ต TCP บางส่วนอีกครั้ง ลองเปลี่ยนการหน่วงเวลาเป็น 1 วินาทีแล้วดูปริมาณการใช้ข้อมูล (ฉันจะไม่แสดงผลลัพธ์ของลูกค้า ซึ่งคาดว่าจะใช้เวลาทั้งหมด 4 วินาที):

tc qdisc change dev lo root netem delay 1s

การถ่ายโอนข้อมูลการจราจร

13:29:07.709981 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [S], seq 283338334, win 43690, options [mss 65495,sackOK,TS val 616292946 ecr 0,nop,wscale 7], length 0
13:29:08.710018 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [S.], seq 3514208179, ack 283338335, win 43690, options [mss 65495,sackOK,TS val 616293946 ecr 616292946,nop,wscale 7], length 0
13:29:08.711094 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [S], seq 283338334, win 43690, options [mss 65495,sackOK,TS val 616293948 ecr 0,nop,wscale 7], length 0
13:29:09.710048 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 616294946 ecr 616293946], length 0
13:29:09.710152 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 616294947 ecr 616293946], length 5
13:29:09.711120 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [S.], seq 3514208179, ack 283338335, win 43690, options [mss 65495,sackOK,TS val 616294948 ecr 616292946,nop,wscale 7], length 0
13:29:10.710173 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [.], ack 6, win 342, options [nop,nop,TS val 616295947 ecr 616294947], length 0
13:29:10.711140 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 616295948 ecr 616293946], length 0
13:29:10.714782 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 616295951 ecr 616294947], length 14
13:29:11.714819 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 15, win 342, options [nop,nop,TS val 616296951 ecr 616295951], length 0
13:29:11.714893 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 616296951 ecr 616295951], length 0
13:29:12.715562 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 616297952 ecr 616296951], length 0
13:29:13.715596 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 616298952 ecr 616297952], length 0

จะเห็นได้ว่าไคลเอนต์ส่งแพ็กเก็ต SYN สองครั้ง และเซิร์ฟเวอร์ส่ง SYN/ACK สองครั้ง

นอกเหนือจากค่าคงที่แล้ว การหน่วงเวลายังสามารถตั้งค่าเป็นค่าเบี่ยงเบน ฟังก์ชันการกระจาย และความสัมพันธ์ (ด้วยค่าสำหรับแพ็กเก็ตก่อนหน้า) ทำได้ดังนี้:

tc qdisc change dev lo root netem delay 500ms 400ms 50 distribution normal

ที่นี่เราได้ตั้งค่าความล่าช้าระหว่าง 100 ถึง 900 มิลลิวินาที ค่าจะถูกเลือกตามการแจกแจงแบบปกติและจะมีความสัมพันธ์ 50% กับค่าความล่าช้าสำหรับแพ็กเก็ตก่อนหน้า

คุณอาจสังเกตเห็นว่าในคำสั่งแรกที่ฉันใช้ เพิ่มแล้ว เปลี่ยนแปลง. ความหมายของคำสั่งเหล่านี้ชัดเจน ดังนั้นฉันจะเสริมว่ายังมีคำสั่งมากกว่านี้อีก เดลซึ่งสามารถใช้เพื่อลบการกำหนดค่าได้

การสูญเสียแพ็คเก็ต

ตอนนี้เรามาลองทำการสูญหายของแพ็กเก็ตกันดีกว่า ดังที่เห็นได้จากเอกสารประกอบ สิ่งนี้สามารถทำได้สามวิธี: การสูญเสียแพ็กเก็ตแบบสุ่มโดยมีความน่าจะเป็นอยู่บ้าง โดยใช้ลูกโซ่มาร์คอฟ 2, 3 หรือ 4 สถานะเพื่อคำนวณการสูญเสียแพ็กเก็ต หรือใช้แบบจำลองเอลเลียต-กิลเบิร์ต ในบทความฉันจะพิจารณาวิธีแรก (ง่ายและชัดเจนที่สุด) และคุณสามารถอ่านเกี่ยวกับวิธีอื่นได้ ที่นี่.

มาทำให้แพ็กเก็ตสูญเสีย 50% โดยมีความสัมพันธ์กัน 25%:

tc qdisc add dev lo root netem loss 50% 25%

แต่น่าเสียดายที่ tcpdump จะไม่สามารถแสดงให้เราเห็นการสูญหายของแพ็กเก็ตได้อย่างชัดเจนเราจะถือว่าใช้งานได้จริงเท่านั้น และเวลาทำงานที่เพิ่มขึ้นและไม่เสถียรของสคริปต์จะช่วยเราตรวจสอบสิ่งนี้ ลูกค้า.py (สามารถทำได้ทันทีหรืออาจใช้เวลา 20 วินาที) รวมถึงจำนวนแพ็กเก็ตที่ส่งซ้ำเพิ่มขึ้น:

[user@host ~]# netstat -s | grep retransmited; sleep 10; netstat -s | grep retransmited
    17147 segments retransmited
    17185 segments retransmited

เพิ่มเสียงรบกวนให้กับแพ็คเก็ต

นอกจากการสูญเสียแพ็กเก็ตแล้ว คุณยังสามารถจำลองความเสียหายของแพ็กเก็ตได้ โดยสัญญาณรบกวนจะปรากฏที่ตำแหน่งแพ็กเก็ตแบบสุ่ม มาสร้างความเสียหายให้กับแพ็กเก็ตด้วยความน่าจะเป็น 50% และไม่มีความสัมพันธ์กัน:

tc qdisc change dev lo root netem corrupt 50%

เรารันสคริปต์ไคลเอนต์ (ไม่มีอะไรน่าสนใจ แต่ใช้เวลา 2 วินาทีจึงจะเสร็จสมบูรณ์) ดูปริมาณการใช้งาน:

การถ่ายโอนข้อมูลการจราจร

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
10:20:54.812434 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [S], seq 2023663770, win 43690, options [mss 65495,sackOK,TS val 1037001049 ecr 0,nop,wscale 7], length 0
10:20:54.812449 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [S.], seq 2104268044, ack 2023663771, win 43690, options [mss 65495,sackOK,TS val 1037001049 ecr 1037001049,nop,wscale 7], length 0
10:20:54.812458 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 1037001049 ecr 1037001049], length 0
10:20:54.812509 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1037001049 ecr 1037001049], length 5
10:20:55.013093 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1037001250 ecr 1037001049], length 5
10:20:55.013122 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [.], ack 6, win 342, options [nop,nop,TS val 1037001250 ecr 1037001250], length 0
10:20:55.014681 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 1037001251 ecr 1037001250], length 14
10:20:55.014745 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 15, win 340, options [nop,nop,TS val 1037001251 ecr 1037001251], length 0
10:20:55.014823 IP 127.0.0.1.43666 > 127.0.0.5.12345: Flags [F.], seq 2023663776, ack 2104268059, win 342, options [nop,nop,TS val 1037001251 ecr 1037001251], length 0
10:20:55.214088 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [P.], seq 1:15, ack 6, win 342, options [nop,unknown-65 0x0a3dcf62eb3d,[bad opt]>
10:20:55.416087 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 1037001653 ecr 1037001251], length 0
10:20:55.416804 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 1037001653 ecr 1037001653], length 0
10:20:55.416818 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 16, win 343, options [nop,nop,TS val 1037001653 ecr 1037001653], length 0
10:20:56.147086 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 1037002384 ecr 1037001653], length 0
10:20:56.147101 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 1037002384 ecr 1037001653], length 0

จะเห็นได้ว่าบางแพ็กเก็ตถูกส่งซ้ำๆ และมีหนึ่งแพ็กเก็ตที่มีข้อมูลเมตาเสียหาย: ตัวเลือก [nop, ไม่ทราบ-65 0x0a3dcf62eb3d, [เลือกไม่ถูกต้อง]>. แต่สิ่งสำคัญคือในที่สุดทุกอย่างก็ทำงานได้อย่างถูกต้อง - TCP รับมือกับงานของมันได้

การทำสำเนาแพ็คเก็ต

คุณสามารถทำอะไรได้อีก เนเทม? ตัวอย่างเช่น จำลองสถานการณ์ย้อนกลับของการสูญหายของแพ็กเก็ต - การทำสำเนาแพ็กเก็ต คำสั่งนี้ยังรับอาร์กิวเมนต์ 2 รายการด้วย: ความน่าจะเป็นและความสัมพันธ์

tc qdisc change dev lo root netem duplicate 50% 25%

การเปลี่ยนลำดับของแพ็คเกจ

คุณสามารถผสมถุงได้สองวิธี

ในตอนแรก บางแพ็กเก็ตจะถูกส่งทันที ส่วนแพ็กเก็ตที่เหลือจะมีความล่าช้าตามที่ระบุ ตัวอย่างจากเอกสารประกอบ:

tc qdisc change dev lo root netem delay 10ms reorder 25% 50%

ด้วยความน่าจะเป็น 25% (และความสัมพันธ์ 50%) แพ็กเก็ตจะถูกส่งทันที ส่วนที่เหลือจะถูกส่งโดยมีความล่าช้า 10 มิลลิวินาที

วิธีที่สองคือเมื่อทุกแพ็กเก็ต N ถูกส่งทันทีด้วยความน่าจะเป็นที่กำหนด (และสหสัมพันธ์) และแพ็กเก็ตที่เหลือด้วยความล่าช้าที่กำหนด ตัวอย่างจากเอกสารประกอบ:

tc qdisc change dev lo root netem delay 10ms reorder 25% 50% gap 5

ทุก ๆ แพ็คเกจที่ห้ามีโอกาส 25% ที่จะถูกส่งโดยไม่ชักช้า

การเปลี่ยนแบนด์วิธ

โดยปกติแล้วทุกที่ที่พวกเขาอ้างถึง ทีบีเอฟแต่ด้วยความช่วยเหลือ เนเทม คุณยังสามารถเปลี่ยนแบนด์วิธของอินเทอร์เฟซได้:

tc qdisc change dev lo root netem rate 56kbit

ทีมนี้จะเดินป่ารอบๆ localhost เจ็บปวดพอ ๆ กับการท่องอินเทอร์เน็ตผ่านโมเด็มแบบเรียกผ่านสายโทรศัพท์ นอกเหนือจากการตั้งค่าบิตเรตแล้ว คุณยังสามารถจำลองโมเดลโปรโตคอลเลเยอร์ลิงก์ได้: ตั้งค่าโอเวอร์เฮดสำหรับแพ็กเก็ต ขนาดเซลล์ และโอเวอร์เฮดสำหรับเซลล์ ตัวอย่างเช่น สามารถจำลองสิ่งนี้ได้ ATM และบิตเรต 56 kbit/วินาที:

tc qdisc change dev lo root netem rate 56kbit 0 48 5

การจำลองการหมดเวลาการเชื่อมต่อ

จุดสำคัญอีกประการหนึ่งในแผนการทดสอบเมื่อยอมรับซอฟต์แวร์คือการหมดเวลา นี่เป็นสิ่งสำคัญเนื่องจากในระบบแบบกระจาย เมื่อบริการใดบริการหนึ่งถูกปิดใช้งาน บริการอื่น ๆ จะต้องถอยกลับไปยังบริการอื่นทันเวลาหรือส่งคืนข้อผิดพลาดไปยังไคลเอนต์ และไม่ว่าในกรณีใด บริการเหล่านั้นควรหยุดทำงาน รอการตอบสนองหรือการเชื่อมต่อ ที่จะจัดตั้งขึ้น

มีหลายวิธีในการทำเช่นนี้: ตัวอย่างเช่น ใช้โปรแกรมจำลองที่ไม่ตอบสนอง หรือเชื่อมต่อกับกระบวนการโดยใช้ดีบักเกอร์ วางเบรกพอยต์ในตำแหน่งที่ถูกต้องและหยุดกระบวนการ (นี่อาจเป็นวิธีในทางที่ผิดที่สุด) แต่สิ่งหนึ่งที่ชัดเจนที่สุดคือพอร์ตไฟร์วอลล์หรือโฮสต์ มันจะช่วยเราในเรื่องนี้ iptables.

สำหรับการสาธิต เราจะใช้ไฟร์วอลล์พอร์ต 12345 และเรียกใช้สคริปต์ไคลเอนต์ของเรา คุณสามารถไฟร์วอลล์แพ็กเก็ตขาออกไปยังพอร์ตนี้ที่ผู้ส่งหรือแพ็กเก็ตขาเข้าที่ผู้รับ ในตัวอย่างของฉัน แพ็กเก็ตขาเข้าจะถูกไฟร์วอลล์ (เราใช้ chain INPUT และตัวเลือก --dport). แพ็กเก็ตดังกล่าวสามารถ DROP, REJECT หรือ REJECT ด้วยแฟล็ก TCP RST หรือโฮสต์ ICMP ไม่สามารถเข้าถึงได้ (อันที่จริงแล้ว พฤติกรรมเริ่มต้นคือ icmp-พอร์ตไม่สามารถเข้าถึงได้และยังมีโอกาสส่งตอบกลับอีกด้วย icmp-net-ไม่สามารถเข้าถึงได้, icmp-proto-ไม่สามารถเข้าถึงได้, ห้ามใช้ icmp-net и ห้ามโฮสต์ icmp).

DROP

หากมีกฎที่มี DROP แพ็กเก็ตก็จะ "หายไป"

iptables -A INPUT -p tcp --dport 12345 -j DROP

เราเปิดตัวไคลเอนต์และเห็นว่ามันค้างในขั้นตอนการเชื่อมต่อกับเซิร์ฟเวอร์ ลองดูที่การจราจร:
การถ่ายโอนข้อมูลการจราจร

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
08:28:20.213506 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203046450 ecr 0,nop,wscale 7], length 0
08:28:21.215086 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203047452 ecr 0,nop,wscale 7], length 0
08:28:23.219092 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203049456 ecr 0,nop,wscale 7], length 0
08:28:27.227087 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203053464 ecr 0,nop,wscale 7], length 0
08:28:35.235102 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203061472 ecr 0,nop,wscale 7], length 0

จะเห็นได้ว่าไคลเอนต์ส่งแพ็กเก็ต SYN พร้อมการหมดเวลาที่เพิ่มขึ้นแบบทวีคูณ ดังนั้นเราจึงพบข้อบกพร่องเล็กๆ น้อยๆ ในไคลเอนต์: คุณต้องใช้วิธีนี้ ตั้งเวลาหมด()เพื่อจำกัดเวลาที่ไคลเอนต์จะพยายามเชื่อมต่อกับเซิร์ฟเวอร์

เราจะลบกฎออกทันที:

iptables -D INPUT -p tcp --dport 12345 -j DROP

คุณสามารถลบกฎทั้งหมดพร้อมกันได้:

iptables -F

หากคุณใช้ Docker และจำเป็นต้องไฟร์วอลล์การรับส่งข้อมูลทั้งหมดที่ไปที่คอนเทนเนอร์ คุณสามารถทำได้ดังนี้:

iptables -I DOCKER-USER -p tcp -d CONTAINER_IP -j DROP

ปฏิเสธ

ตอนนี้เรามาเพิ่มกฎที่คล้ายกัน แต่ด้วย REJECT:

iptables -A INPUT -p tcp --dport 12345 -j REJECT

ไคลเอนต์ออกหลังจากผ่านไปหนึ่งวินาทีโดยมีข้อผิดพลาด [ข้อผิดพลาด 111] การเชื่อมต่อถูกปฏิเสธ. ลองดูที่การรับส่งข้อมูล ICMP:

[user@host ~]# tcpdump -i lo -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
08:45:32.871414 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 tcp port 12345 unreachable, length 68
08:45:33.873097 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 tcp port 12345 unreachable, length 68

จะเห็นได้ว่าลูกค้าได้รับสองครั้ง พอร์ตไม่สามารถเข้าถึงได้ แล้วจบลงด้วยข้อผิดพลาด

ปฏิเสธด้วยการรีเซ็ต tcp

มาลองเพิ่มตัวเลือกกัน --ปฏิเสธด้วย tcp-reset:

iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with tcp-reset

ในกรณีนี้ ไคลเอ็นต์ออกทันทีโดยมีข้อผิดพลาด เนื่องจากคำขอแรกได้รับแพ็กเก็ต RST:

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
09:02:52.766175 IP 127.0.0.1.60658 > 127.0.0.1.12345: Flags [S], seq 1889460883, win 43690, options [mss 65495,sackOK,TS val 1205119003 ecr 0,nop,wscale 7], length 0
09:02:52.766184 IP 127.0.0.1.12345 > 127.0.0.1.60658: Flags [R.], seq 0, ack 1889460884, win 0, length 0

ปฏิเสธด้วย icmp-host-unreachable

ลองใช้ตัวเลือกอื่นสำหรับการใช้ REJECT:

iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with icmp-host-unreachable

ไคลเอนต์ออกหลังจากผ่านไปหนึ่งวินาทีโดยมีข้อผิดพลาด [ข้อผิดพลาด 113] ไม่มีเส้นทางไปยังโฮสต์เราเห็นในการรับส่งข้อมูล ICMP ไม่สามารถเข้าถึงโฮสต์ ICMP 127.0.0.1 ได้.

คุณยังสามารถลองใช้พารามิเตอร์ REJECT อื่นๆ ได้ และฉันจะเน้นไปที่สิ่งเหล่านี้ :)

กำลังจำลองการหมดเวลาของคำขอ

อีกสถานการณ์หนึ่งคือเมื่อไคลเอนต์สามารถเชื่อมต่อกับเซิร์ฟเวอร์ แต่ไม่สามารถส่งคำขอได้ จะกรองแพ็กเก็ตได้อย่างไรเพื่อให้การกรองไม่เริ่มทันที? หากคุณดูการรับส่งข้อมูลของการสื่อสารใด ๆ ระหว่างไคลเอนต์และเซิร์ฟเวอร์ คุณจะสังเกตเห็นว่าเมื่อสร้างการเชื่อมต่อจะใช้เฉพาะแฟล็ก SYN และ ACK เท่านั้น แต่เมื่อทำการแลกเปลี่ยนข้อมูล แพ็กเก็ตคำขอสุดท้ายจะมีแฟล็ก PSH มันจะติดตั้งโดยอัตโนมัติเพื่อหลีกเลี่ยงการบัฟเฟอร์ คุณสามารถใช้ข้อมูลนี้เพื่อสร้างตัวกรอง โดยจะอนุญาตแพ็กเก็ตทั้งหมด ยกเว้นแพ็กเก็ตที่มีแฟล็ก PSH ดังนั้นการเชื่อมต่อจะถูกสร้างขึ้น แต่ไคลเอนต์จะไม่สามารถส่งข้อมูลไปยังเซิร์ฟเวอร์ได้

DROP

สำหรับ DROP คำสั่งจะมีลักษณะดังนี้:

iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j DROP

เปิดตัวไคลเอนต์และดูปริมาณการใช้งาน:

การถ่ายโอนข้อมูลการจราจร

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
10:02:47.549498 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [S], seq 2166014137, win 43690, options [mss 65495,sackOK,TS val 1208713786 ecr 0,nop,wscale 7], length 0
10:02:47.549510 IP 127.0.0.1.12345 > 127.0.0.1.49594: Flags [S.], seq 2341799088, ack 2166014138, win 43690, options [mss 65495,sackOK,TS val 1208713786 ecr 1208713786,nop,wscale 7], length 0
10:02:47.549520 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 1208713786 ecr 1208713786], length 0
10:02:47.549568 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1208713786 ecr 1208713786], length 5
10:02:47.750084 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1208713987 ecr 1208713786], length 5
10:02:47.951088 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1208714188 ecr 1208713786], length 5
10:02:48.354089 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1208714591 ecr 1208713786], length 5

เราเห็นว่ามีการสร้างการเชื่อมต่อแล้วและไคลเอนต์ไม่สามารถส่งข้อมูลไปยังเซิร์ฟเวอร์ได้

ปฏิเสธ

ในกรณีนี้พฤติกรรมจะเหมือนเดิม: ลูกค้าจะไม่สามารถส่งคำขอได้ แต่จะได้รับ ICMP 127.0.0.1 พอร์ต tcp 12345 ไม่สามารถเข้าถึงได้ และเพิ่มเวลาระหว่างการส่งคำขอซ้ำแบบทวีคูณ คำสั่งมีลักษณะดังนี้:

iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT

ปฏิเสธด้วยการรีเซ็ต tcp

คำสั่งมีลักษณะดังนี้:

iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT --reject-with tcp-reset

เรารู้อยู่แล้วว่าเมื่อใช้ --ปฏิเสธด้วย tcp-reset ไคลเอนต์จะได้รับแพ็กเก็ต RST เป็นการตอบกลับ ดังนั้นจึงสามารถคาดการณ์พฤติกรรมได้: การรับแพ็กเก็ต RST ในขณะที่สร้างการเชื่อมต่อหมายความว่าซ็อกเก็ตถูกปิดโดยไม่คาดคิดในอีกด้านหนึ่ง ซึ่งหมายความว่าไคลเอนต์ควรได้รับ การเชื่อมต่อรีเซ็ตโดยเพียร์. มารันสคริปต์ของเราและตรวจสอบสิ่งนี้กัน และนี่คือลักษณะของการจราจร:

การถ่ายโอนข้อมูลการจราจร

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
10:22:14.186269 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flags [S], seq 2615137531, win 43690, options [mss 65495,sackOK,TS val 1209880423 ecr 0,nop,wscale 7], length 0
10:22:14.186284 IP 127.0.0.1.12345 > 127.0.0.1.52536: Flags [S.], seq 3999904809, ack 2615137532, win 43690, options [mss 65495,sackOK,TS val 1209880423 ecr 1209880423,nop,wscale 7], length 0
10:22:14.186293 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 1209880423 ecr 1209880423], length 0
10:22:14.186338 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1209880423 ecr 1209880423], length 5
10:22:14.186344 IP 127.0.0.1.12345 > 127.0.0.1.52536: Flags [R], seq 3999904810, win 0, length 0

ปฏิเสธด้วย icmp-host-unreachable

ฉันคิดว่าทุกคนเห็นได้ชัดว่าคำสั่งจะเป็นอย่างไร :) พฤติกรรมของลูกค้าในกรณีนี้จะแตกต่างไปเล็กน้อยจากการปฏิเสธแบบง่าย: ไคลเอนต์จะไม่เพิ่มการหมดเวลาระหว่างความพยายามในการส่งแพ็กเก็ตอีกครั้ง

[user@host ~]# tcpdump -i lo -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
10:29:56.149202 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.349107 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.549117 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.750125 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.951130 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:57.152107 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:57.353115 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65

เอาท์พุต

ไม่จำเป็นต้องเขียนโปรแกรมจำลองเพื่อทดสอบการโต้ตอบของบริการกับไคลเอนต์หรือเซิร์ฟเวอร์ที่หยุดทำงาน บางครั้งการใช้ยูทิลิตี้มาตรฐานที่พบใน Linux ก็เพียงพอแล้ว

ยูทิลิตี้ที่กล่าวถึงในบทความมีความสามารถมากกว่าที่อธิบายไว้ ดังนั้นคุณจึงมีตัวเลือกในการใช้งานของคุณเอง โดยส่วนตัวแล้ว ฉันมีเรื่องที่เขียนมามากพอเสมอ (อันที่จริงก็น้อยกว่านั้นด้วยซ้ำ) หากคุณใช้โปรแกรมอรรถประโยชน์เหล่านี้หรือคล้ายกันในการทดสอบในบริษัทของคุณ โปรดเขียนว่าอย่างไร ถ้าไม่เช่นนั้น ฉันหวังว่าซอฟต์แวร์ของคุณจะดีกว่านี้หากคุณตัดสินใจทดสอบในสภาวะที่เกิดปัญหาเครือข่ายโดยใช้วิธีการที่แนะนำ

ที่มา: will.com

เพิ่มความคิดเห็น