ترِیسروت با Rust: پشت پردهی ردیابی مسیر شبکه
خلاصهٔ کاملتر
traceroute ابزاریه که بهمون میگه یه پکت اینترنتی از کدوم روترها رد میشه تا به مقصد برسه. خروجیش یه لیست از آدرسهای IPه که هر کدوم یه «هاپ» (hop) یا گرهی میانی در مسیرن. اما این ابزار چطور این اطلاعات رو به دست میاره؟ جواب تو یه فیلد سادهی هدر IP پنهانه: TTL یا Time To Live.
هر پکت IP یه عدد TTL داره که معمولاً از ۶۴ شروع میشه. هر روتری که پکت رو فوروارد میکنه، این عدد رو یه واحد کم میکنه. وقتی TTL به صفر رسید، روتر پکت رو دراپ میکنه و یه پیام ICMP با عنوان «Time Exceeded» به فرستنده میفرسته. این پیام حاوی آدرس IP همون روتره. traceroute از این رفتار استفاده میکنه: اول یه پکت با TTL=1 میفرسته، اولین روتر جواب میده. بعد TTL=2، روتر دوم جواب میده، و همینطور ادامه میده تا به مقصد برسه.
پکتهایی که traceroute میفرسته از نوع UDP هستن؛ چون هدف اینه که پکت «بمیره» قبل از رسیدن به مقصد. پورت مقصد هم ۳۳۴۳۴ هست که به طور سنتی برای traceroute استفاده میشه و معمولاً هیچ سرویسی روی اون گوش نمیده. این یعنی وقتی پکت به مقصد نهایی برسه، سرور با یه ICMP «Port Unreachable» جواب میده که به traceroute میگه به مقصد رسیدیم.
برای دریافت پیامهای ICMP، باید از یه raw socket استفاده کنیم، چون سیستمعامل به طور پیشفرض این پیامها رو به اپلیکیشنهای معمولی نمیرسونه. به همین دلیل traceroute به دسترسی root یا sudo نیاز داره.
در پیادهسازی Rust، ساختار اصلی اینه که دو سوکت داریم: یکی UDP برای فرستادن پکت با TTL دلخواه، یکی raw ICMP برای گوش دادن به پاسخها. پکت دریافتشده یه IP header خامه که بایتهای ۱۲ تا ۱۵ آدرس فرستندهی پیام ICMP رو دارن، یعنی همون روتری که پکت ما رو دراپ کرده.
let send_sock = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
send_sock.set_ttl_v4(ttl)?;
let recv_sock = Socket::new(
Domain::IPV4,
Type::from(libc::SOCK_RAW),
Some(Protocol::ICMPV4),
)?;
recv_sock.set_read_timeout(Some(Duration::from_secs(2)))?;
let dest = SockAddr::from(SocketAddrV4::new(target, 33434));
send_sock.send_to(&[0u8; 32], &dest)?;بعد از دریافت پاسخ، برای خوندن آدرس IP روتر کافیه بایتهای ۱۲ تا ۱۵ هدر IP رو بخونیم:
if buf.len() >= 20 {
let ip = Ipv4Addr::new(buf[12], buf[13], buf[14], buf[15]);
Ok(Some(ip))
}ICMP یا Internet Control Message Protocol یه پروتکل گزارش خطاست، نه انتقال داده. پیام «Time Exceeded» نوع ۱۱ ICMP هست و پیام «Port Unreachable» نوع ۳. با بررسی بایت بیستم پکت دریافتی میشه فهمید کدوم نوع ICMP رسیده و کِی باید حلقه رو متوقف کرد. بایت ۲۰ به بعد پیام ICMP شروع میشه و اولین بایتش نوع پیامه.
حلقهی اصلی برنامه TTL رو از ۱ تا ۱۵ افزایش میده و برای هر هاپ IP مربوطه رو چاپ میکنه. وقتی IP دریافتی با آدرس مقصد برابر شد، یعنی رسیدیم و میشه حلقه رو شکست:
for ttl in 1..=15 {
let hop = probe(target, ttl)?;
match hop {
Some(ip) => println!("{:>2} {}", ttl, ip),
None => println!("{:>2} *", ttl),
}
if hop == Some(target) { break; }
}ستارههایی که تو خروجی traceroute میبینیم (مثل * * *) به این معناست که یه روتر پیام ICMP نفرستاده یا فایروال اون رو بلاک کرده. بعضی روترها به خاطر سیاستهای امنیتی یا بار پردازشی این پیامها رو نادیده میگیرن.
نکات کلیدی:
- traceroute با دستکاری فیلد TTL در هدر IP کار میکنه؛ هر بار یه پکت طراحیشده برای مردن میفرسته
- روترها وقتی TTL رو به صفر میرسونن، یه پیام ICMP «Time Exceeded» با آدرس خودشون برمیگردونن
- پکتهای UDP به پورت ۳۳۴۳۴ فرستاده میشن؛ رسیدن به مقصد با پیام ICMP «Port Unreachable» مشخص میشه
- خوندن پیامهای ICMP نیاز به raw socket داره و به همین دلیل sudo لازمه
- ستاره در خروجی یعنی اون هاپ پیام ICMP نفرستاده یا فایروال بلاکش کرده
- traceroute از TCP هم پشتیبانی میکنه (با -T) برای شبکههایی که UDP رو بلاک میکنن




