Universal 32bit classifier in tc(8) Linux Universal 32bit classifier in tc(8)

tc-u32 - فیلتر سراسری ۳۲ بیتی کنترل ترافیک


tc filter ... [ handle HANDLE ] u32 OPTION_LIST [ offset OFFSET ] [ hashkey HASHKEY ] [ classid CLASSID ] [ divisor uint_value ] [ order u32_value ] [ ht HANDLE ] [ sample SELECTOR [ divisor uint_value ] ] [ link HANDLE ] [ indev ifname ] [ skip_hw | skip_sw ] [ help ]

HANDLE := { u12_hex_htid:[u8_hex_hash:[u12_hex_nodeid] | 0xu32_hex_value }

OPTION_LIST := [ OPTION_LIST ] OPTION

HASHKEY := [ mask u32_hex_value ] [ at 4*int_value ]

CLASSID := { root | none | [u16_major]:u16_minor | u32_hex_value }

OFFSET := [ plus int_value ] [ at 2*int_value ] [ mask u16_hex_value ] [ shift int_value ] [ eat ]

OPTION := { match SELECTOR | action ACTION }

SELECTOR := { u32 VAL_MASK_32 | u16 VAL_MASK_16 | u8 VAL_MASK_8 | ip IP | ip6 IP6 | { tcp | udp } TCPUDP | icmp ICMP | mark VAL_MASK_32 | ether ETHER }

IP := { { src | dst } { default | any | all | ip_address [ / { prefixlen | netmask } ] } AT | { dsfield | ihl | protocol | precedence | icmp_type | icmp_code } VAL_MASK_8 | { sport | dport } VAL_MASK_16 | nofrag | firstfrag | df | mf }

IP6 := { { src | dst } { default | any | all | ip6_address [/prefixlen ] } AT | priority VAL_MASK_8 | { protocol | icmp_type | icmp_code } VAL_MASK_8 | flowlabel VAL_MASK_32 | { sport | dport } VAL_MASK_16 }

TCPUDP := { src | dst } VAL_MASK_16

ICMP := { type VAL_MASK_8 | code VAL_MASK_8 }

ETHER := { src | dst } ether_address AT

VAL_MASK_32 := u32_value u32_hex_mask [ AT ]

VAL_MASK_16 := u16_value u16_hex_mask [ AT ]

VAL_MASK_8 := u8_value u8_hex_mask [ AT ]

AT := [ at [ nexthdr+ ] int_value ]

فیلتر ۳۲ بیتی سراسری (Universal/Ugly 32bit filter) امکان تطبیق با فیلدهای بیتی دلخواه را در بسته‌ها فراهم می‌کند. از آنجا که این فیلتر همه چیز را به مقادیر، ماسک‌ها و آفست‌ها خرد می‌کند، به‌همان اندازه که قدرتمند است، استفاده از آن نیز دشوار است. خوشبختانه دستورالعمل‌های انتزاعی متعددی در دسترس هستند که تعریف قوانین را در سطحی بالاتر ممکن ساخته و بنابراین کاربر را در بسیاری از موارد از درگیر شدن با بیت‌ها و ماسک‌ها بی‌نیاز می‌کنند.

دو حالت کلی برای فراخوانی وجود دارد: حالت اول یک فیلتر جدید ایجاد می‌کند تا بسته‌ها را به مقصدهای مختلف هدایت کند. جدا از موارد بدیهی، یعنی دسته‌بندی بسته با مشخص کردن یک CLASSID یا فراخوانی یک action، می‌توان یک فیلتر را به فیلتری دیگر (یا حتی فهرستی از آن‌ها) پیوند (link) داد، که در عمل فیلترها را در یک سلسله‌مراتب درخت‌مانند سازمان‌دهی می‌کند.

به‌طور معمول هدایت بسته‌ها توسط فیلتر با استفاده از یک جدول هش (hash table) انجام می‌شود که به حالت دوم فراخوانی منجر می‌گردد: این حالت صرفاً برای برپایی این جداول هش به‌کار می‌رود. فیلترها می‌توانند یک جدول هش را انتخاب کرده و یک گزینش‌گر کلید ارائه دهند که از روی آن یک مقدار هش محاسبه شده و به‌عنوان کلید جهت جستجوی باکت (bucket) جدول که حاوی فیلترهایی برای پردازش‌های بعدی است استفاده شود. این قابلیت در صورتی که تعداد زیادی فیلتر در حال استفاده باشد سودمند است، چرا که سربار انجام عملیات هش و جستجو در جدول در این حالت ناچیز می‌شود. استفاده از جداول هش با u32 اساساً از الگوی زیر پیروی می‌کند:

(1)
ایجاد یک جدول هش جدید، تعیین اندازه آن با استفاده از پارامتر divisor و ترجیحاً تعیین یک دستگیره (handle) که بتوان جدول را با آن شناسایی کرد. اگر شناسه دستگیره مشخص نشود، هسته خود یکی را انتخاب می‌کند که بعداً باید حدس زده شود.
(2)
ایجاد فیلترهایی که با استفاده از پارامتر link به جدول ایجادشده در مرحله (1) پیوند می‌خورند و مشخص کردن داده‌های بسته‌ای که هسته برای محاسبه hashkey به‌کار خواهد گرفت.
(3)
افزودن فیلترها به باکت‌های موجود در جدول هش مرحله (1). به‌منظور بی‌نیازی از دانستن دقیق نحوه ایجاد کلید هش توسط هسته، پارامتر sample وجود دارد که داده‌های نمونه‌ای را برای محاسبه هش ارائه می‌دهد و از این طریق باکت جدولی را که فیلتر باید به آن افزوده شود تعیین می‌کند.

در واقع، حتی اگر به‌طور صریح درخواست نشود، u32 برای هر اولویت (priority) که فیلتری با آن افزوده می‌شود، یک جدول هش ایجاد می‌کند. البته اندازه این جدول ۱ است، بنابراین در عمل صرفاً یک فهرست پیوندی است.

گزینه‌ها و انتخاب‌کننده‌ها نیازمند مشخص شدن مقادیر در قالبی خاص هستند که اغلب غیربدیهی است. بنابراین پایانه‌های بخش خلاصه دستور (SYNOPSIS) نام‌هایی توصیفی دریافت کرده‌اند تا قالب مورد نیاز و/یا حداکثر مقدار عددی مجاز را نشان دهند: پیشوندهای u32، u16 و u8 نشان‌دهنده مقادیر بدون علامت چهار، دو و تک‌بایتی هستند؛ برای نمونه u16 نشان‌دهنده یک مقدار به اندازه دو بایت در بازه بین ۰ تا ۶۵۵۳۵ (0xFFFF) به‌طور فراگیر است. پیشوند int یک مقدار علامت‌دار چهار بایتی را نشان می‌دهد. بخش میانی _hex_ نشان می‌دهد که مقدار در قالب هگزادسیمال تجزیه می‌شود. در غیر این صورت، مبنای مقدار به‌طور خودکار تشخیص داده می‌شود، بدین معنی که مقادیر دارای پیشوند 0x هگزادسیمال در نظر گرفته می‌شوند، پیشوند 0 قالب اکتال (هشت‌هشتی) را نشان می‌دهد و در غیر این صورت مبنا ده‌دهی (دسیمال) خواهد بود. همچنین برخی مقادیر با قالب‌بندی ویژه وجود دارند: ip_address و netmask همانند آدرس‌های معمول IPv4 در قالب چهارگانه نقطه‌دار (dotted-quad) هستند. یک ip6_address در قالب رایج هگزادسیمالِ جداشده با دونقطه مشخص می‌شود. در نهایت، prefixlen یک مقدار عدد صحیح ده‌دهیِ بدون علامت در بازه ۰ تا طول آدرس بر حسب بیت (۳۲ برای IPv4 و ۱۲۸ برای IPv6) است.

گاهی لازم است مقادیر بر عدد مشخصی بخش‌پذیر باشند. در این حالت نامی به شکل N*val انتخاب شده است که نشان می‌دهد val باید بر N بخش‌پذیر باشد، یا به بیانی دیگر: مقدار حاصل باید مضربی از N باشد.

دستور u32 گزینه‌های زیر را می‌شناسد:

دستگیره (handle) برای ارجاع به یک فیلتر استفاده می‌شود و بنابراین باید یکتا باشد. این شناسه متشکل از شناسه جدول هش htid و مقادیر اختیاری hash (که باکت جدول هش را مشخص می‌کند) و nodeid است. تمامی این مقادیر به‌صورت اعداد هگزادسیمال بدون علامت با طول ۱۲ بیت (htid و nodeid) یا ۸ بیت (hash) تجزیه می‌شوند. روش دیگر این است که یک مقدار هگزادسیمال ۳۲ بیتی منفرد مشخص شود که بیت‌های این سه فیلد را به‌شکل متصل به هم در بر دارد. این مقدار بر خلاف خود فیلدها باید با پیشوند 0x همراه باشد.
یک آفست را تنظیم می‌کند که مشخص می‌سازد تطبیق‌های فیلترهای بعدی در کجا اعمال شوند. بنابراین این گزینه تنها زمانی کاربرد دارد که با link یا ترکیبی از ht و sample همراه شود. آفست می‌تواند به‌طور صریح با استفاده از کلیدواژه plus داده شود، یا با استفاده از at از داده‌های بسته استخراج گردد. امکان دستکاری مقدار دومی با استفاده از کلیدواژه‌های mask و/یا shift وجود دارد. به‌طور پیش‌فرض این آفست ثبت می‌شود اما به‌طور ضمنی اعمال نمی‌گردد؛ بلکه تنها برای جایگزینی عبارت nexthdr+ استفاده می‌شود. با این حال استفاده از کلیدواژه eat این رفتار را معکوس می‌کند: آفست همواره اعمال می‌شود و nexthdr+ به صفر بازمی‌گردد.
مشخص می‌کند چه داده‌ای از بسته برای محاسبه کلید هش جهت جستجوی باکت استفاده شود. هسته مقدار را متناسب با اندازه جدول هش تنظیم می‌کند. برای کارکرد این ویژگی، گزینه link باید مشخص شده باشد.
بسته‌های منطبق را به CLASSID داده‌شده دسته‌بندی می‌کند، که متشکل از اعداد ۱۶ بیتی major و minor یا یک مقدار ۳۲ بیتی واحد ترکیب‌کننده هر دو است.
یک مقدار پیمانه (modulo) را مشخص می‌کند. هنگام ایجاد جداول هش برای تعیین اندازه آن‌ها، یا برای اعلام یک sample جهت محاسبه کلیدهای جدول هش استفاده می‌شود. باید توانی از دو با توانی حداکثر تا ۸ باشد.
مقداری برای مرتب‌سازی فیلترها به‌صورت صعودی. با handle که همان کاربرد را دارد تداخل دارد.
همراه با ht استفاده می‌شود تا مشخص کند این فیلتر به کدام باکت افزوده شود. این امکان را فراهم می‌کند که از دانستن دقیق چگونگی محاسبه هش‌ها توسط هسته بی‌نیاز شویم. مقدار پیش‌فرض divisor اضافی برابر ۲۵۶ است، بنابراین برای جداول هشی با اندازه دیگر باید به صراحت داده شود.
بسته‌های منطبق را به فیلترهای موجود در یک جدول هش ارجاع (delegate) می‌دهد. آرگومان HANDLE تنها برای مشخص کردن جدول هش استفاده می‌شود، بنابراین فقط htid مجاز است داده شود و hash و nodeid باید حذف شوند. به‌طور پیش‌فرض باکت شماره ۰ استفاده خواهد شد و می‌توان آن را با گزینه hashkey بازنویسی کرد.
فیلتر کردن بر روی رابط ورودی بسته. بدیهی است که تنها برای ترافیک هدایت‌شده (forwarded) کار می‌کند.
فیلتر توسط نرم‌افزار پردازش نشود. اگر سخت‌افزار از برون‌سپاری (offload) برای این فیلتر پشتیبانی نکند، یا برون‌سپاری TC برای این رابط فعال نباشد، عملیات با شکست مواجه می‌شود.
فیلتر توسط سخت‌افزار پردازش نشود.
چاپ یک متن راهنمای مختصر درباره گزینه‌های ممکن.

اساساً تنها انتخاب‌کننده واقعی u32 است. تمامی انتخاب‌کننده‌های دیگر صرفاً نحوی در سطح بالاتر ارائه می‌دهند و در درون به u32 ترجمه می‌شوند.

تطبیق داده‌های بسته با یک مقدار مشخص. نام انتخاب‌کننده طول نمونه استخراج‌شده را مشخص می‌کند (۳۲ بیت برای u32، ۱۶ بیت برای u16 و ۸ بیت برای u8). پیش از مقایسه، نمونه استخراج‌شده با ماسک مشخص‌شده AND بیتی می‌شود. به این ترتیب بیت‌های غیرضروری را می‌توان پیش از مقایسه پاک کرد. موقعیت نمونه با آفست مشخص‌شده در AT تعیین می‌شود.
فرض می‌کند بسته با یک هدر IPv4 (ip) یا IPv6 (ip6) آغاز می‌شود. سپس IP/IP6 امکان تطبیق فیلدهای گوناگون هدر را فراهم می‌کند:
فیلدهای آدرس مبدأ (Source) یا مقصد (Destination) را با مقدار ADDR مقایسه می‌کند. کلمات رزروشده default، any و all در عمل با هر آدرسی مطابقت پیدا می‌کنند. در غیر این صورت یک آدرس IP از پروتکل مربوطه انتظار می‌رود، که می‌تواند اختیاری همراه با طول پیشوند برای تطبیق کل زیرشبکه‌ها باشد. در مورد IPv4 می‌توان یک نت‌ماسک (netmask) نیز ارائه داد.
فقط IPv4. تطبیق با فیلد DSCP/ECN هدر بسته. مترادف‌های این گزینه عبارتند از tos و precedence.
فقط IPv4. تطبیق با فیلد طول هدر اینترنت (Internet Header Length). توجه داشته باشید که واحد این مقدار کلمات ۳۲ بیتی است، بنابراین برای تطبیق بسته‌ای با هدر ۲۴ بایتی، u8_value باید برابر ۶ باشد.
تطبیق مقدار فیلد پروتکل (IPv4) یا هدر بعدی (IPv6)، مثلاً ۶ برای TCP.
یک پروتکل هدر بعدی از نوع icmp یا ipv6-icmp را فرض کرده و با مقادیر فیلد Type یا Code تطبیق می‌دهد. این کار خطرناک است، زیرا کد حداقل اندازه هدر را برای IPv4 و عدم وجود هدرهای توسعه (extension headers) را برای IPv6 فرض می‌گیرد.
تطبیق پورت‌های مبدأ یا مقصد لایه چهار. این کار نیز خطرناک است، زیرا فرض می‌کند یک پروتکل مناسب لایه چهار وجود دارد (که فیلدهای پورت مبدأ و مقصد دقیقاً در ابتدای هدر و با اندازه ۱۶ بیت باشند). همچنین حداقل اندازه هدر برای IPv4 و عدم وجود هدرهای توسعه برای IPv6 فرض می‌شود.
فقط IPv4، بررسی پرچم‌های مشخص و مقادیر آفست قطعه‌بندی (fragment offset). تطبیق در صورتی برقرار است که بسته قطعه‌بندی نشده باشد (nofrag)، اولین قطعه از یک بسته قطعه‌بندی‌شده باشد (firstfrag)، یا بیت‌های Don't Fragment (df) یا More Fragments (mf) تنظیم شده باشند.
فقط IPv6. تطبیق با فیلد Traffic Class هدر، که از زمان انتشار RFC 3168 همان هدف و معنای فیلد ToS در IPv4 را دارد: شش بیت بالا DSCP و دو بیت پایین ECN هستند.
فقط IPv6. تطبیق با مقدار فیلد Flow Label. توجه داشته باشید که خود فیلد Flow Label تنها ۲۰ بیت طول دارد، که کم‌ارزش‌ترین بیت‌ها در اینجا هستند. ۱۲ بیت باقیمانده بالایی با فیلدهای Version و Traffic Class مطابقت پیدا می‌کنند.
تطبیق با فیلدهای هدر بعدی از پروتکل TCP یا UDP. مقادیر ممکن برای TCPUDP عبارتند از:
تطبیق بر اساس مقدار فیلد پورت مبدأ.
تطبیق بر اساس مقدار فیلد پورت مقصد.
تطبیق با فیلدهای هدر بعدی از پروتکل ICMP. مقادیر ممکن برای ICMP عبارتند از:
تطبیق بر روی فیلد Type پروتکل ICMP.
تطبیق بر روی فیلد Code پروتکل ICMP.
تطبیق بر روی مقدار fwmark فایروال netfilter.
تطبیق با فیلدهای هدر اترنت (Ethernet). مقادیر ممکن برای ETHER عبارتند از:
تطبیق بر روی آدرس اترنت مبدأ یا مقصد. این کار خطرناک است: فرض می‌کند هدر اترنت در ابتدای بسته وجود دارد. اگر با رابط‌های لایه سه مانند tun یا ppp استفاده شود احتمالاً رفتارهای غیرمنتظره‌ای به دنبال خواهد داشت.

tc filter add dev eth0 parent 999:0 prio 99 protocol ip u32 \
        match ip src 192.168.8.0/24 classid 1:1

این دستور فیلتری را به qdisc مشخص‌شده با 999:0 متصل می‌کند. اولویت آن 99 است که بر ترتیب بررسی چندین فیلتر متصل به یک parent یکسان تأثیر می‌گذارد (اولویت کمتر زودتر بررسی می‌شود). این فیلتر بسته‌هایی از نوع پروتکل ip را مدیریت کرده و در صورتی که آدرس مبدأ در هدر IP درون زیرشبکه 192.168.8.0/24 باشد منطبق می‌شود (match). بسته‌های منطبق به کلاس 1:1 دسته‌بندی می‌شوند. اثر این دستور در نگاه نخست شاید غافلگیرکننده باشد:

filter parent 1: protocol ip pref 99 u32
filter parent 1: protocol ip pref 99 u32 \
        fh 800: ht divisor 1
filter parent 1: protocol ip pref 99 u32 \
        fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 \
        match c0a80800/ffffff00 at 12

بنابراین به والد 1: یک فیلتر u32 جدید اختصاص یافته است که حاوی یک جدول هش با اندازه ۱ است (همان‌طور که divisor نشان می‌دهد). شناسه جدول 800 است. خط سوم فیلتر واقعی را که در بالا افزوده شد نشان می‌دهد: در جدول 800 و باکت 0 قرار دارد، بسته‌ها را به شناسه کلاس 1:1 دسته‌بندی می‌کند و سه بایت بالایی مقدار چهار بایتی در آفست 12 را با مقدار 0xc0a808 (که برابر ۱۹۲، ۱۶۸ و ۸ است) مطابقت می‌دهد.

اکنون برای موردی پیچیده‌تر، یعنی ایجاد یک جدول هش سفارشی:

tc filter add dev eth0 prio 99 handle 1: u32 divisor 256

این دستور جدولی با اندازه ۲۵۶ و شناسه دستگیره 1: در اولویت 99 ایجاد می‌کند. اثر آن به صورت زیر است:

filter parent 1: protocol all pref 99 u32
filter parent 1: protocol all pref 99 u32 fh 1: ht divisor 256
filter parent 1: protocol all pref 99 u32 fh 800: ht divisor 1

بنابراین به همراه جدول هش درخواست‌شده (دستگیره 1:)، هسته جدول مخصوص خود را با اندازه ۱ ایجاد کرده است تا سایر فیلترهای با همان اولویت را در آن نگه دارد.

گام بعدی ایجاد فیلتری است که به جدول هش ایجادشده پیوند بخورد:

tc filter add dev eth0 parent 1: prio 1 u32 \
        link 1: hashkey mask 0x0000ff00 at 12 \
        match ip src 192.168.0.0/16

به این فیلتر اولویت کمتری نسبت به خود جدول هش داده می‌شود تا u32 پیش از پیمایش دستی جدول هش، آن را ارزیابی کند. گزینه‌های link و hashkey تعیین می‌کنند که هدایت به کدام جدول و باکت انجام شود. در این مورد، کلید هش باید از بایت دوم در آفست ۱۲ ساخته شود، که متناظر با بایت سوم از فیلد آدرس مبدأ بسته IP است. همراه با عبارت match، این کار عملاً تمام شبکه‌های کلاس C زیر 192.168.0.0/16 را به باکت‌های مختلف جدول هش نگاشت می‌کند.

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

tc filter add dev eth0 parent 1: prio 99 u32 \
        ht 1: sample u32 0x00000800 0x0000ff00 at 12 \
        match ip src 192.168.8.0/24 classid 1:1

باکت با استفاده از گزینه sample مشخص می‌شود: در این مورد، بایت دوم در آفست ۱۲ باید دقیقاً 0x08 باشد. در این حالت، شناسه باکت حاصل بدیهتاً ۸ است، اما به محض آنکه sample میزانی از داده را انتخاب کند که بتواند از divisor بیشتر شود، فرد باید از الگوریتم درونی هسته آگاه باشد تا باکت مقصد را استنتاج کند. عبارت match این فیلتر در این حالت افزونه (تکراری) است، زیرا آنتروپی کلید هش از اندازه جدول بیشتر نمی‌شود و بنابراین برخوردی رخ نخواهد داد. در غیر این صورت، وجود آن برای جلوگیری از تطبیق بسته‌های ناخواسته ضروری است.

تطبیق فیلدهای لایه‌های بالاتر چالش‌برانگیز است، زیرا طول هدر IPv4 متغیر است و IPv6 از هدرهای توسعه پشتیبانی می‌کند که بر آفست هدر لایه بالاتر تأثیر می‌گذارند. برای غلبه بر این مسئله، امکان تعیین nexthdr+ هنگام تعیین یک آفست وجود دارد؛ و برای ساده‌تر شدن امور، تطبیق‌های tcp و udp وجود دارند که به طور ضمنی از nexthdr+ استفاده می‌کنند. با این حال این آفست باید از قبل محاسبه شود، و تنها راه دستیابی به آن انجام محاسبه در فیلتری جداگانه است که سپس به فیلتری که قصد استفاده از آن را دارد پیوند می‌خورد. نمونه‌ای از این کار در زیر آمده است:

tc filter add dev eth0 parent 1:0 protocol ip handle 1: \
        u32 divisor 1
tc filter add dev eth0 parent 1:0 protocol ip \
        u32 ht 1: \
        match tcp src 22 FFFF \
        classid 1:2
tc filter add dev eth0 parent 1:0 protocol ip \
        u32 ht 800: \
        match ip protocol 6 FF \
        match u16 0 1fff at 6 \
        offset at 0 mask 0f00 shift 6 \
        link 1:

کاری که در اینجا انجام می‌شود چنین است: در نخستین فراخوانی، یک جدول هش با اندازه یک عنصر ساخته می‌شود تا مکانی برای نگهداری فیلتر پیوندخورده و یک دستگیره شناخته‌شده (1:) جهت ارجاع به آن فراهم شود. فراخوانی دوم فیلتر اصلی را اضافه می‌کند که بسته‌های با پورت مبدأ TCP برابر ۲۲ را به کلاس 1:2 هدایت می‌کند. با استفاده از ht، این فیلتر به جدول هش ایجادشده در فراخوانی نخست منتقل می‌شود. فراخوانی سوم جادوی واقعی را اجرا می‌کند: بسته‌های IPv4 با پروتکل لایه بعدی ۶ (TCP) را فقط در صورتی که نخستین قطعه باشند تطبیق می‌دهد (معمولاً TCP بیت DF را تنظیم می‌کند، اما اگر چنین نباشد و بسته قطعه‌بندی شده باشد، تنها قطعه نخست حاوی هدر TCP است)، و سپس آفست را بر مبنای فیلد IHL هدر IP تنظیم می‌کند (شیفت ۶ بیت به راست، آفست فیلد را حذف کرده و همزمان مقدار را به واحد بایت تبدیل می‌کند). در پایان، با استفاده از link، به جدول هش فراخوانی نخست که حاوی فیلتر فراخوانی دوم است ارجاع داده می‌شود.

tc(8),
cls_u32.txt در http://linux-tc-notes.sourceforge.net

25 Sep 2015 iproute2