مقدمه‌ای بر ماژول ipaddress

نویسنده:

پیتر مودی

نویسنده:

Nick Coghlan

ایجاد اشیاء نشانی/شبکه/رابط

از آن‌جا که ipaddress ماژولی برای بررسی و دست‌کاری آدرس‌های IP است، اولین کاری که می‌خواهید انجام دهید ایجاد چند شیء است. می‌توانید از ipaddress برای ایجاد شیءها از رشته‌ها و اعداد صحیح استفاده کنید.

یادداشتی درباره نسخه‌های IP

برای خوانندگانی که به‌ویژه با آدرس‌دهی IP آشنا نیستند، مهم است بدانند که پروتکل اینترنت (IP) در حال حاضر در حال گذار از نسخه 4 این پروتکل به نسخه 6 است. این گذار عمدتاً به این دلیل رخ می‌دهد که نسخه 4 پروتکل، آدرس‌های کافی برای پاسخ‌گویی به نیازهای کل جهان را فراهم نمی‌کند، به‌ویژه با توجه به افزایش تعداد دستگاه‌هایی که اتصال مستقیم به اینترنت دارند.

توضیح جزئیات تفاوت‌های میان دو نسخه از پروتکل خارج از دامنه این مقدمه است، اما شما باید دست‌کم از وجود این دو نسخه آگاه باشید، و گاهی لازم است استفاده از یکی از این دو نسخه را تحمیل کنید.

آدرس‌های میزبان IP

نشانی‌ها، که اغلب با عنوان «نشانی‌های میزبان» شناخته می‌شوند، پایه‌ای‌ترین واحد هنگام کار با نشانی‌دهی IP هستند. ساده‌ترین راه برای ایجاد نشانی‌ها، استفاده از تابع کارخانه‌ایٔ ipaddress.ip_address() است، که به‌طور خودکار بر اساس مقدار ورودی تعیین می‌کند که یک نشانی IPv4 ایجاد شود یا IPv6:

>>> ipaddress.ip_address('192.0.2.1')
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address('2001:DB8::1')
IPv6Address('2001:db8::1')

نشانی‌ها همچنین می‌توانند مستقیماً از اعداد صحیح ساخته شوند. مقادیری که در ۳۲ بیت جای می‌گیرند، نشانی‌های IPv4 فرض می‌شوند:

>>> ipaddress.ip_address(3221225985)
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address(42540766411282592856903984951653826561)
IPv6Address('2001:db8::1')

برای وادار کردن به استفاده از نشانی‌های IPv4 یا IPv6، می‌توان کلاس‌های مربوطه را مستقیماً فراخوانی کرد. این کار به‌ویژه برای وادار کردن به ایجاد نشانی‌های IPv6 برای اعداد صحیح کوچک مفید است:

>>> ipaddress.ip_address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv4Address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv6Address(1)
IPv6Address('::1')

تعریف شبکه‌ها

نشانی‌های میزبان معمولاً با یکدیگر در شبکه‌های IP گروه‌بندی می‌شوند، بنابراین ipaddress راهی برای ایجاد، بررسی و دستکاری تعریف‌های شبکه فراهم می‌کند. اشیاء شبکه‌ی IP از رشته‌هایی ساخته می‌شوند که بازه‌ی نشانی‌های میزبانی که بخشی از آن شبکه هستند را تعریف می‌کنند. ساده‌ترین شکل برای این اطلاعات، یک جفت «نشانی شبکه/پیشوند شبکه» است، که در آن پیشوند تعداد بیت‌های پیشرویی را تعریف می‌کند که برای تعیین اینکه آیا یک نشانی بخشی از شبکه است یا خیر مقایسه می‌شوند و نشانی شبکه مقدار مورد انتظار آن بیت‌ها را تعریف می‌کند.

در مورد آدرس‌ها، یک تابع کارخانه‌ای ارائه شده است که نسخه صحیح IP را به‌طور خودکار تعیین می‌کند:

>>> ipaddress.ip_network('192.0.2.0/24')
IPv4Network('192.0.2.0/24')
>>> ipaddress.ip_network('2001:db8::0/96')
IPv6Network('2001:db8::/96')

اشیاء شبکه نمی‌توانند هیچ بیت میزبان تنظیم‌شده‌ای داشته باشند. اثر عملی این موضوع آن است که 192.0.2.1/24 یک شبکه را توصیف نمی‌کند. چنین تعاریفی به‌عنوان اشیاء رابط شناخته می‌شوند، زیرا نمادگذاری آی‌پی روی شبکه (ip-on-a-network) معمولاً برای توصیف رابط‌های شبکه‌ی یک کامپیوتر در یک شبکه‌ی مشخص به کار می‌رود و در بخش بعدی بیشتر توضیح داده شده‌اند.

به‌طور پیش‌فرض، تلاش برای ایجاد یک شیء شبکه با بیت‌های میزبانِ تنظیم‌شده، باعث پرتاب ValueError می‌شود. برای درخواست اینکه بیت‌های اضافی درعوض به صفر تبدیل شوند، می‌توان پرچم strict=False را به سازنده ارسال کرد:

>>> ipaddress.ip_network('192.0.2.1/24')
Traceback (most recent call last):
   ...
ValueError: 192.0.2.1/24 has host bits set
>>> ipaddress.ip_network('192.0.2.1/24', strict=False)
IPv4Network('192.0.2.0/24')

اگرچه شکل رشته‌ای انعطاف‌پذیری بسیار بیشتری فراهم می‌کند، شبکه‌ها را می‌توان درست مانند آدرس‌های میزبان، با اعداد صحیح نیز تعریف کرد. در این حالت، شبکه تنها شامل آدرس شناسایی‌شده توسط آن عدد صحیح در نظر گرفته می‌شود، بنابراین پیشوند شبکه، کل آدرس شبکه را شامل می‌شود:

>>> ipaddress.ip_network(3221225984)
IPv4Network('192.0.2.0/32')
>>> ipaddress.ip_network(42540766411282592856903984951653826560)
IPv6Network('2001:db8::/128')

همانند آدرس‌ها، می‌توان با فراخوانی مستقیم سازنده‌ی کلاس به‌جای استفاده از تابع کارخانه (factory function)، ایجاد یک نوع خاص از شبکه را اجباری کرد.

رابط‌های میزبان

همان‌طور که درست در بالا اشاره شد، اگر نیاز دارید نشانی‌ای در یک شبکه خاص را توصیف کنید، نه کلاس‌های نشانی و نه کلاس‌های شبکه کافی نیستند. مهندسان شبکه و افرادی که ابزارهایی برای فایروال‌ها و مسیریاب‌ها می‌نویسند، معمولاً از نمادگذاری مانند 192.0.2.1/24 به‌عنوان خلاصه‌ای برای «میزبان 192.0.2.1 در شبکه 192.0.2.0/24» استفاده می‌کنند. بر همین اساس، ipaddress مجموعه‌ای از کلاس‌های ترکیبی را فراهم می‌کند که یک نشانی را با یک شبکه خاص مرتبط می‌کنند. رابط ایجاد آن‌ها کاملاً مشابه رابط تعریف اشیاء شبکه است، با این تفاوت که بخش نشانی محدود به نشانی شبکه نیست.

>>> ipaddress.ip_interface('192.0.2.1/24')
IPv4Interface('192.0.2.1/24')
>>> ipaddress.ip_interface('2001:db8::1/96')
IPv6Interface('2001:db8::1/96')

ورودی‌های عدد صحیح پذیرفته می‌شوند (همان‌طور که در شبکه‌ها نیز چنین است)، و می‌توان با فراخوانی مستقیم سازنده مربوطه، استفاده از نسخه خاصی از IP را الزامی کرد.

بررسی اشیاء آدرس/شبکه/رابط

شما زحمت ایجاد یک شیء IPv(4|6)(Address|Network|Interface) را به خود داده‌اید، بنابراین احتمالاً می‌خواهید اطلاعاتی درباره‌ی آن کسب کنید. ipaddress تلاش می‌کند انجام این کار را آسان و شهودی کند.

استخراج نسخه IP:

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr6 = ipaddress.ip_address('2001:db8::1')
>>> addr6.version
6
>>> addr4.version
4

به دست آوردن شبکه از یک رابط:

>>> host4 = ipaddress.ip_interface('192.0.2.1/24')
>>> host4.network
IPv4Network('192.0.2.0/24')
>>> host6 = ipaddress.ip_interface('2001:db8::1/96')
>>> host6.network
IPv6Network('2001:db8::/96')

یافتن تعداد آدرس‌های منفرد موجود در یک شبکه:

>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.num_addresses
256
>>> net6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.num_addresses
4294967296

پیمایش آدرس‌های «قابل‌استفاده» در یک شبکه:

>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> for x in net4.hosts():
...     print(x)
192.0.2.1
192.0.2.2
192.0.2.3
192.0.2.4
...
192.0.2.252
192.0.2.253
192.0.2.254

به‌دست آوردن netmask (یعنی بیت‌های تنظیم‌شده متناظر با پیشوند شبکه) یا hostmask (هر بیتی که بخشی از netmask نیست):

>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.netmask
IPv4Address('255.255.255.0')
>>> net4.hostmask
IPv4Address('0.0.0.255')
>>> net6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.netmask
IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff::')
>>> net6.hostmask
IPv6Address('::ffff:ffff')

بسط یا فشرده‌سازی نشانی:

>>> addr6.exploded
'2001:0db8:0000:0000:0000:0000:0000:0001'
>>> addr6.compressed
'2001:db8::1'
>>> net6.exploded
'2001:0db8:0000:0000:0000:0000:0000:0000/96'
>>> net6.compressed
'2001:db8::/96'

اگرچه IPv4 از بسط (explosion) یا فشرده‌سازی پشتیبانی نمی‌کند، اشیاء مرتبط همچنان ویژگی‌های مربوطه را فراهم می‌کنند تا کد مستقل از نسخه بتواند به‌راحتی اطمینان حاصل کند که مختصرترین یا پرجزئیات‌ترین فرم برای آدرس‌های IPv6 استفاده می‌شود و در عین حال آدرس‌های IPv4 به‌درستی مدیریت می‌شوند.

شبکه‌ها به‌عنوان فهرست‌هایی از نشانی‌ها

گاهی مفید است که با شبکه‌ها مانند فهرست‌ها رفتار کنید. این بدان معناست که می‌توانید آن‌ها را به این شکل اندیس‌گذاری کنید:

>>> net4[1]
IPv4Address('192.0.2.1')
>>> net4[-1]
IPv4Address('192.0.2.255')
>>> net6[1]
IPv6Address('2001:db8::1')
>>> net6[-1]
IPv6Address('2001:db8::ffff:ffff')

این همچنین به این معناست که اشیاء شبکه‌ای را می‌توان با سینتکس آزمون عضویت در فهرست به این صورت استفاده کرد:

if address in network:
    # do something

آزمون دربرگیری (containment testing) به‌صورت کارآمد بر اساس پیشوند شبکه انجام می‌شود:

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr4 in ipaddress.ip_network('192.0.2.0/24')
True
>>> addr4 in ipaddress.ip_network('192.0.3.0/24')
False

مقایسه‌ها

ipaddress روش‌های ساده‌ای ارائه می‌دهد که امید است شهودی باشند، برای مقایسه‌ی اشیاء در مواردی که منطقی باشد:

>>> ipaddress.ip_address('192.0.2.1') < ipaddress.ip_address('192.0.2.2')
True

اگر سعی کنید اشیایی با نسخه‌ها یا انواع مختلف را مقایسه کنید، استثنای TypeError پرتاب می‌شود.

استفاده از آدرس‌های IP با سایر ماژول‌ها

ماژول‌های دیگری که از آدرس‌های IP استفاده می‌کنند (مانند socket) معمولاً اشیاء این ماژول را مستقیماً نمی‌پذیرند. در عوض، باید به یک عدد صحیح یا رشته‌ای تبدیل شوند که ماژول دیگر بپذیرد:

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> str(addr4)
'192.0.2.1'
>>> int(addr4)
3221225985

دریافت جزئیات بیشتر در صورت شکست در ایجاد نمونه

هنگام ایجاد اشیای نشانی/شبکه/رابط با استفاده از توابع کارخانه‌ای مستقل از نسخه، هر خطایی به‌صورت ValueError با پیام خطای عام گزارش می‌شود که صرفاً می‌گوید مقدار داده‌شده به‌عنوان شیئی از آن نوع شناسایی نشد. دلیل نبود خطای مشخص این است که برای ارائه جزئیات بیشتر درباره‌ی علت رد شدن مقدار، لازم است بدانیم که آیا مقدار باید IPv4 باشد یا IPv6.

برای پشتیبانی از موارد استفاده‌ای که دسترسی به این جزئیات اضافی در آن‌ها مفید است، سازنده‌های هر کلاس در واقع استثناهای زیرکلاس ValueError، یعنی ipaddress.AddressValueError و ipaddress.NetmaskValueError را پرتاب می‌کنند تا دقیقاً مشخص کنند کدام بخش از تعریف به‌درستی تجزیه نشد.

پیام‌های خطا هنگامی که مستقیماً از سازنده‌های کلاس استفاده می‌کنید، به‌طور قابل‌توجهی جزئی‌تر هستند. برای مثال:

>>> ipaddress.ip_address("192.168.0.256")
Traceback (most recent call last):
  ...
ValueError: '192.168.0.256' does not appear to be an IPv4 or IPv6 address
>>> ipaddress.IPv4Address("192.168.0.256")
Traceback (most recent call last):
  ...
ipaddress.AddressValueError: Octet 256 (> 255) not permitted in '192.168.0.256'

>>> ipaddress.ip_network("192.168.0.1/64")
Traceback (most recent call last):
  ...
ValueError: '192.168.0.1/64' does not appear to be an IPv4 or IPv6 network
>>> ipaddress.IPv4Network("192.168.0.1/64")
Traceback (most recent call last):
  ...
ipaddress.NetmaskValueError: '64' is not a valid netmask

با این حال، کلاس والد هر دو استثنای مختص ماژول، ValueError است، بنابراین اگر نگران نوع خاصی از خطا نیستید، همچنان می‌توانید کدی مانند زیر بنویسید:

try:
    network = ipaddress.IPv4Network(address)
except ValueError:
    print('address/netmask is invalid for IPv4:', address)