နားလည်ထားရမယ့် အချက်
နောက်ဆုံး project ဟာ protocol တစ်ခုအကြောင်း မဟုတ်ပါဘူး၊ နည်းစနစ် တစ်ခုအကြောင်းပါ။ အရင်က သင်ခဲ့တာ အားလုံးကို အစဉ်လိုက် မေးခွန်းစာရင်း တစ်ခုအဖြစ် ပြောင်းလိုက်ပြီး၊ program ရဲ့ တကယ့် တာဝန်က အဲဒီ မေးခွန်းတွေကို အောက်ကနေ အပေါ်သို့ မေးပြီး ပထမဆုံး "မဟုတ်ဘူး" မှာ ရပ်လိုက်ဖို့ပါပဲ။
အစီအစဉ် ကိုယ်တိုင်က အဓိက အယူအဆပါ။ CHECKS ဟာ host ကိုယ်တိုင်ရဲ့ configuration ကနေ အပေါ်ကို တက်သွားပါတယ် — ဒီစက်မှာ သုံးလို့ရတဲ့ address ရှိရဲ့လား၊ သူ့ gateway က သူ့ကိုယ်ပိုင် subnet ထဲမှာ ရှိရဲ့လား၊ gateway က ပြန်ဖြေရဲ့လား၊ နာမည်က resolve ဖြစ်ရဲ့လား၊ အဲဒီ address ဆီ packet တစ်ခု တကယ် ဘယ်လမ်းကနေ သွားမလဲ၊ ပြီးမှသာ connection ပြီးမြောက်ရဲ့လား ဆိုပြီးပါ။ တစ်ခုခု ကျသွားတာနဲ့ နောက်က check အားလုံးကို run မလုပ်တော့ဘဲ SKIP လို့ မှတ်လိုက်ပါတယ်၊ ဘာလို့လဲဆိုတော့ သူတို့ရဲ့ ရလဒ်တွေက အဓိပ္ပာယ် ကင်းမဲ့နေမှာမို့ပါ — mask မှားနေတဲ့ host တစ်ခုမှာ DNS timeout ဖြစ်တာက DNS အကြောင်း ဘာမှ မပြောပြပါဘူး။
အရင်က concept သုံးခုက ရောဂါရှာပေးနေတာပါ။ Subnet math က tool ထဲက အထက်ရှားဆုံး check ကို မောင်းနှင်ပေးတယ် — gw not in net ဆိုတာက mask မှားနေတာကို ဖမ်းမိပါတယ်၊ ဒါဟာ "network ကျနေတယ်" နဲ့ အတူဆုံး ပုံပေါက်ပြီး တကယ်တော့ လုံးဝ local ဖြစ်နေတဲ့ ပြဿနာပါ။ Scenario 1 မှာ host က /26 ဖြစ်လို့ သူ့ subnet က 192.168.1.63 မှာ ရပ်ပြီး၊ .100 က gateway ဟာ switch ဘယ်လောက် ကောင်းကောင်း ဘယ်တော့မှ မရောက်နိုင်ပါဘူး။ Routing decision — target က on-link လား off-link လား ဆိုတာလည်း တူညီတဲ့ နှိုင်းယှဉ်မှုပါပဲ၊ ပြီးတော့ gateway ပါဝင်ရဲ့လား ဆိုတာကို ပြောပြပါတယ်။ Transport layer ကတော့ troubleshooting မှာ အသုံးအဝင်ဆုံး ခွဲခြားချက်ကို ပေးပါတယ် — RST ဆိုတာ host က ပြန်ဖြေပေမယ့် ဘာမှ နားမထောင်နေဘူးလို့ ဆိုလိုပြီး၊ တိတ်ဆိတ်နေတာက တစ်ခုခုက SYN ကို ချပစ်လိုက်တာပါ။ Scenario 2 မှာ DNS က resolve ဖြစ်ပြီး SYN က RST ပြန်ရလို့၊ နာမည်လည်း အဆင်ပြေ၊ route လည်း အဆင်ပြေ၊ ပြဿနာက သေနေတဲ့ service တစ်ခုပါ။
BOTTOM-UP DIAGNOSTIC LADDER
---------------------------
OBSERVATIONS CHECK LADDER FIRST FAILURE
------------ ------------ -------------
host ip + mask ----> [L3] host address & mask
| PASS
v
gateway ip ----> [L3] gateway inside subnet --FAIL--> wrong mask
| PASS
v
arp / ping ----> [L2] gateway reachable --FAIL--> cable / VLAN
| PASS
v
resolver ----> [L7] DNS resolution --FAIL--> name problem
| PASS
v
routing table ----> [L3] on-link or via gateway
|
v
connect() ----> [L4] TCP connect --FAIL--> RST = no
| PASS listener
v drop = firewall
ALL LAYERS OKလက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
ဒါဟာ on-call runbook တစ်ခုကို code အဖြစ် ရေးလိုက်တဲ့ ပုံသဏ္ဌာန်ပါ။ တန်ဖိုးက computer တစ်လုံး ဒီ check တွေ လုပ်နိုင်လို့ မဟုတ်ပါဘူး — လက်နဲ့လည်း လုပ်လို့ရပါတယ် — အစီအစဉ်က အသေဖြစ်နေပြီး ရပ်ရမယ့် စည်းမျဉ်းကို အတည်ပြု ကျင့်သုံးပေးလို့၊ ညနက် ၃ နာရီမှာ မောပန်းနေတဲ့ engineer တစ်ယောက် စိတ်ဝင်စားစရာ ကောင်းတဲ့ layer ဆီ ခုန်မကျော်မိအောင် ထိန်းပေးလို့ပါ။
ဒါက တားဆီးပေးတဲ့ ချို့ယွင်းမှုက incident response မှာ အဖြစ်အများဆုံးပါ — အပေါ်ကနေ စတာပါ။ တစ်ယောက်ယောက်က "app က database ကို မရောက်ဘူး" လို့ တိုင်လိုက်တာနဲ့၊ app ကို၊ connection string ကို၊ database ကိုယ်တိုင်ရဲ့ log ကို ကြည့်ချင်စိတ် ပေါက်ပါတယ်။ နာရီအတော်ကြာမှ host က DHCP scope မှားတစ်ခုကနေ /26 ရထားတာလို့ ဒါမှမဟုတ် deploy တစ်ခုမှာ security group ကို တင်းကျပ်လိုက်တာလို့ ပေါ်လာပါတယ်။ အောက်ကနေ အပေါ် တက်တဲ့ ladder က နှစ်ခုစလုံးကို စက္ကန့်ပိုင်းအတွင်း ရှာပေးပြီး၊ ပိုအရေးကြီးတာက တခြားလူတစ်ယောက်ကို လက်ဆင့်ကမ်းလို့ရတဲ့ အထောက်အထား ထုတ်ပေးပါတယ် — "L2 နဲ့ L3 pass, DNS က 10.20.9.14 ဆီ resolve ဖြစ်တယ်, SYN က RST ပြန်ရတယ်" ဆိုတဲ့ စကားကြောင်းက database team ချက်ချင်း လုပ်ဆောင်လို့ရပေမယ့် "အလုပ်မလုပ်ဘူး" ဆိုတာက မရပါဘူး။
Scenario table ကလည်း တန်ဖိုးရှိတဲ့ ဒုတိယ အလေ့အထ တစ်ခုကို ပြပါတယ် — အမြဲတမ်း known-good baseline တစ်ခု ထားပါ။ Scenario 3 ရှိနေတာက သန့်ရှင်းတဲ့ run တစ်ခု ဘယ်လိုပုံစံလဲ သိစေဖို့ပါ။ အဲဒါ မရှိရင် မရင်းနှီးတဲ့ PASS တစ်ကြောင်းဟာ FAIL တစ်ကြောင်းလိုပဲ သံသယ ဖြစ်စရာ ကောင်းနေမှာပါ။ Production မှာ team တွေက subnet တိုင်းမှာ jump host ဒါမှမဟုတ် canary တစ်ခုစီ ထားကြတာ ဒီအတွက်ပါပဲ — ရောဂါရှာခြင်းရဲ့ တစ်ဝက်ဟာ နှိုင်းယှဉ်စရာ အလုပ်လုပ်နေတဲ့ အရာတစ်ခု ရှိထားခြင်းပါ။
အတူတူ စမ်းရေးကြည့်မယ်
import ipaddress
import textwrap
PASS, FAIL, INFO = "PASS", "FAIL", "INFO"
# Hardcoded observations. In a real tool these come from `ip addr`, `ip route`,
# a resolver call and a connect() attempt -- here they are frozen so the
# diagnosis is reproducible.
SCENARIOS = [
{
"name": "laptop-a (freshly re-imaged)",
"host_ip": "192.168.1.50", "mask": "255.255.255.192",
"gateway": "192.168.1.100",
"gateway_reachable": False,
"target_name": "api.example.com", "target_port": 443,
"dns_result": "93.184.216.34",
"tcp_result": "timeout",
},
{
"name": "web-tier-03 (deploy just failed)",
"host_ip": "10.20.5.31", "mask": "255.255.255.0",
"gateway": "10.20.5.1",
"gateway_reachable": True,
"target_name": "db.internal", "target_port": 5432,
"dns_result": "10.20.9.14",
"tcp_result": "refused",
},
{
"name": "workstation-7 (baseline, known good)",
"host_ip": "172.16.4.9", "mask": "255.255.255.0",
"gateway": "172.16.4.1",
"gateway_reachable": True,
"target_name": "cache.internal", "target_port": 6379,
"dns_result": "172.16.4.60",
"tcp_result": "open",
},
]
def check_host_address(obs, ctx):
iface = ipaddress.ip_interface("%s/%s" % (obs["host_ip"], obs["mask"]))
ctx["network"] = iface.network
if iface.ip in ipaddress.ip_network("169.254.0.0/16"):
return FAIL, "link-local address -- DHCP never answered", \
"Fix DHCP or assign a static address."
return PASS, "%s in %s" % (iface.ip, iface.network), None
def check_gateway_local(obs, ctx):
gw = ipaddress.ip_address(obs["gateway"])
net = ctx["network"]
if gw not in net:
return FAIL, "gateway %s is OUTSIDE %s" % (gw, net), \
("The mask is wrong. /%d only spans %s-%s, so the host can never "
"ARP for its own gateway." % (net.prefixlen, net[0], net[-1]))
return PASS, "gateway %s is inside %s" % (gw, net), None
def check_gateway_reachable(obs, ctx):
if not obs["gateway_reachable"]:
return FAIL, "no reply from gateway %s" % obs["gateway"], \
"Layer 1/2 problem: cable, VLAN, or a down switch port."
return PASS, "gateway %s replies" % obs["gateway"], None
def check_dns(obs, ctx):
if not obs["dns_result"]:
return FAIL, "%s did not resolve" % obs["target_name"], \
"Name problem, not a reachability problem. Check the resolver."
ctx["target_ip"] = ipaddress.ip_address(obs["dns_result"])
return PASS, "%s -> %s" % (obs["target_name"], obs["dns_result"]), None
def check_route(obs, ctx):
target, net = ctx["target_ip"], ctx["network"]
if target in net:
return INFO, "%s is on-link, delivered directly" % target, None
return INFO, "%s is off-link, sent via %s" % (target, obs["gateway"]), None
def check_tcp(obs, ctx):
result = obs["tcp_result"]
if result == "open":
return PASS, "handshake completed on port %d" % obs["target_port"], None
if result == "refused":
return FAIL, "RST on port %d" % obs["target_port"], \
("The host is reachable and answered -- nothing is listening on "
"that port. A dead service, not a network fault.")
return FAIL, "no SYN-ACK on port %d" % obs["target_port"], \
"Silent drop: a firewall or ACL is discarding the SYN."
CHECKS = [
("L3", "Host address and mask", check_host_address),
("L3", "Gateway within subnet", check_gateway_local),
("L2", "Gateway reachable", check_gateway_reachable),
("L7", "DNS resolution", check_dns),
("L3", "Routing decision", check_route),
("L4", "TCP connect", check_tcp),
]
def diagnose(obs):
print("SCENARIO: %s" % obs["name"])
print("-" * 70)
print(" target %s:%d" % (obs["target_name"], obs["target_port"]))
print()
ctx = {}
root_cause = None
for layer, label, fn in CHECKS:
if root_cause is not None:
print(" %-3s %-24s %-6s %s" % (layer, label, "SKIP", "not reached"))
continue
status, detail, advice = fn(obs, ctx)
print(" %-3s %-24s %-6s %s" % (layer, label, status, detail))
if status == FAIL:
root_cause = (layer, label, advice)
print()
if root_cause is None:
print(" VERDICT: healthy -- every layer checks out.")
else:
layer, label, advice = root_cause
print(" VERDICT: first failure at %s -- %s" % (layer, label))
for i, line in enumerate(textwrap.wrap(advice, 60)):
print(" %s %s" % ("CAUSE :" if i == 0 else " ", line))
print()
print("LAYERED NETWORK DIAGNOSTIC REPORT")
print("=" * 70)
print()
for scenario in SCENARIOS:
diagnose(scenario)
LAYERED NETWORK DIAGNOSTIC REPORT
======================================================================
SCENARIO: laptop-a (freshly re-imaged)
----------------------------------------------------------------------
target api.example.com:443
L3 Host address and mask PASS 192.168.1.50 in 192.168.1.0/26
L3 Gateway within subnet FAIL gateway 192.168.1.100 is OUTSIDE 192.168.1.0/26
L2 Gateway reachable SKIP not reached
L7 DNS resolution SKIP not reached
L3 Routing decision SKIP not reached
L4 TCP connect SKIP not reached
VERDICT: first failure at L3 -- Gateway within subnet
CAUSE : The mask is wrong. /26 only spans 192.168.1.0-192.168.1.63,
so the host can never ARP for its own gateway.
SCENARIO: web-tier-03 (deploy just failed)
----------------------------------------------------------------------
target db.internal:5432
L3 Host address and mask PASS 10.20.5.31 in 10.20.5.0/24
L3 Gateway within subnet PASS gateway 10.20.5.1 is inside 10.20.5.0/24
L2 Gateway reachable PASS gateway 10.20.5.1 replies
L7 DNS resolution PASS db.internal -> 10.20.9.14
L3 Routing decision INFO 10.20.9.14 is off-link, sent via 10.20.5.1
L4 TCP connect FAIL RST on port 5432
VERDICT: first failure at L4 -- TCP connect
CAUSE : The host is reachable and answered -- nothing is listening
on that port. A dead service, not a network fault.
SCENARIO: workstation-7 (baseline, known good)
----------------------------------------------------------------------
target cache.internal:6379
L3 Host address and mask PASS 172.16.4.9 in 172.16.4.0/24
L3 Gateway within subnet PASS gateway 172.16.4.1 is inside 172.16.4.0/24
L2 Gateway reachable PASS gateway 172.16.4.1 replies
L7 DNS resolution PASS cache.internal -> 172.16.4.60
L3 Routing decision INFO 172.16.4.60 is on-link, delivered directly
L4 TCP connect PASS handshake completed on port 6379
VERDICT: healthy -- every layer checks out.
၅ မိနစ် စမ်းကြည့်
Scenario လေးခုမြောက် တစ်ခု ထည့်ပါ — host က link-local address 169.254.10.7 ကို /16 mask နဲ့ ရထားပြီး (DHCP က ဘယ်တော့မှ မဖြေခဲ့တဲ့ အခြေအနေ)၊ ကျန်တဲ့ observation တွေက ကောင်းနေတယ် ဆိုပါစို့။ Report က ပထမဆုံး check မှာပဲ ရပ်ပြီး ကျန်တာ ငါးခုလုံးကို SKIP လုပ်ရပါမယ်။ ပြီးရင် check_dns() ကို dns_result = None ဖြစ်တဲ့ scenario တစ်ခုနဲ့ စမ်းပြီး၊ DNS ကျသွားတဲ့ အခါ TCP check က တကယ်ပဲ run မဖြစ်တော့ဘူးဆိုတာ အတည်ပြုပါ — အဲဒါက ဒီ tool ရဲ့ အသက်ပါပဲ။
သတိလေးတစ်ချက်
Check တစ်ခု ကျပြီးနောက် ကျန်တာ အားလုံးကို ဆက် run ပြီး ကျတဲ့ စာရင်းအရှည်ကြီး ထုတ်ပေးတာ။ နောက်ပိုင်း "ကျမှု" တွေဟာ အကြောင်းရင်း မဟုတ်ဘဲ အကျိုးဆက်တွေ ဖြစ်နေပါတယ် — mask မှားနေတဲ့ host တစ်ခုမှာ DNS လည်း ကျမယ်၊ TCP လည်း ကျမယ်၊ ပြီးတော့ report က layer 3 ပြဿနာကို DNS အပေါ် အပြစ်တင်လိုက်ပါလိမ့်မယ်။ ပထမဆုံး FAIL မှာ ရပ်ပြီး ကျန်တာကို SKIP လို့ ရှင်းရှင်းလင်းလင်း မှတ်ပါ။
Gateway နဲ့ host ကို octet သုံးလုံး မျက်စိနဲ့ ကြည့်ပြီး နှိုင်းယှဉ်တာ။ "192.168.1.50 နဲ့ 192.168.1.100 က ထင်ရှားတဲ့ subnet တစ်ခုတည်းပဲ" ဆိုတာ /24 အတွက် မှန်ပြီး /26 အတွက် လုံးဝ မှားပါတယ်။ Dotted-quad prefix နဲ့ မဟုတ်ဘဲ တကယ့် mask ကနေ ရလာတဲ့ network (ip_interface(...).network) နဲ့ containment test ကို အမြဲ လုပ်ပါ — ဒီ bug မျိုးက mask ကို /24 ကနေ တခြားတစ်ခုကို ပြောင်းလိုက်တဲ့ အချိန်ကျမှ ပေါ်လာလေ့ရှိပါတယ်။
RFC 1122 -- Requirements for Internet Hosts: Communication Layers — Computer Networking