Python ipaddress: แยก IP ส่วนตัวกับสาธารณะก่อนเปิดพอร์ต
เวลาสคริปต์รับ IP จากผู้ใช้หรือจาก webhook แล้วเอาไปเปิด socket / ยิงต่อทันที ผมพบว่าพลาดง่ายมากถ้าไม่แยกก่อนว่าเป็นที่อยู่ส่วนตัว (private) หรือสาธารณะ (global) โมดูล ipaddress ให้ทรัพย์สิน is_private และ is_global ตรง ๆ ครับ ไม่ต้องจำช่วง 10.x / 192.168.x เอง
WHY: กรองที่อยู่ก่อนแตะเครือข่าย
IP ส่วนตัวเหมือนบ้านเลขที่ในซอย — ใช้คุยในบ้านได้ แต่ไม่ควรเป็นปลายทางของปุ่ม “ส่งออกอินเทอร์เน็ต” โดยไม่รู้ตัว ส่วน IP สาธารณะเหมือนเลขที่ติดถนนใหญ่ ถ้าสคริปต์รับค่าจากฟอร์มแล้วยิงต่อได้ทุกที่ อาจเผลอสแกนเครื่องในออฟฟิศหรือยิงเข้า printer ใน LAN
import ipaddress
import unittest
def classify(raw: str) -> str:
addr = ipaddress.ip_address(raw.strip())
if addr.is_loopback:
return "loopback"
if addr.is_private:
return "private"
if addr.is_global:
return "global"
return "other"
class IpClassifyTests(unittest.TestCase):
def test_private_and_global(self) -> None:
self.assertEqual(classify("192.168.1.10"), "private")
self.assertEqual(classify("10.0.0.5"), "private")
self.assertEqual(classify("8.8.8.8"), "global")
def test_loopback(self) -> None:
self.assertEqual(classify("127.0.0.1"), "loopback")
def test_bad_input(self) -> None:
with self.assertRaises(ValueError):
classify("not-an-ip")
if __name__ == "__main__":
unittest.main()
HOW: ใช้ทรัพย์สินของออบเจ็กต์ ไม่ฮาร์ดโค้ดช่วง
ip_address รับทั้ง IPv4 และ IPv6 แล้วคืนออบเจ็กต์ที่มีธงพร้อมใช้ is_private ตาม RFC สำหรับช่วง private / unique-local ส่วน is_global บอกว่าใช้บนอินเทอร์เน็ตสาธารณะได้ อย่าเขียน startswith("192.168.") เอง เพราะพลาด IPv6 และช่วงอื่น
ตัวอย่างการใช้จริง: ก่อนเปิดพอร์ต listen ที่ผูกกับอินเทอร์เฟซภายนอก หรือก่อนที่ระบบจะ “เรียกกลับ” URL ที่ผู้ใช้ใส่ ให้ classify ก่อน แล้วปฏิเสธ private/loopback ถ้าจานงานคือบริการสาธารณะ
GOTCHA: link-local และ reserved ไม่ใช่ global
ที่อยู่บางชนิดไม่ private แต่ก็ไม่ global (เช่น link-local) ฟังก์ชันตัวอย่างจึงมีถัง other ถ้าต้องการอนุญาตเฉพาะ public egress ให้ผ่านเฉพาะ is_global และทดสอบ IPv6 ด้วย
Recap
- ใช้
ipaddress.ip_addressแทนการจำเลขช่วงเอง - แยก loopback / private / global ก่อน bind หรือยิงต่อ
- อย่าลืม IPv6 และค่าที่ parse ไม่ได้ต้อง raise ชัด ๆ