עבודה עם IPv6 ב-PHP

לאחרונה קיבלנו סטטוס LIR וחסימה /29 IPv6. ואז התעורר הצורך לעקוב אחר רשתות המשנה שהוקצו. ומכיוון שהחיוב שלנו כתוב ב-PHP, היינו צריכים להתעמק קצת בנושא ולהבין שהשפה הזו לא הכי ידידותית מבחינת עבודה עם IPv6. מתחת לחיתוך נמצא הפתרון שלנו לבעיות שעולות בעבודה עם כתובות וטווחים. אולי לא הכי אלגנטי, אבל זה עושה את העבודה.

עבודה עם IPv6 ב-PHP

קצת תיאוריה

כתב ויתור. אם אתה מכיר מה זה IPv6 ועם מה זה מגיע, החלק הזה עשוי להיות משעמם עבורך. יכול להיות שלא.

אנשים שרואים את הערת IPv6 בפעם הראשונה עשויים למצוא את זה די מרתיע. אחרי אלגנטי 64.233.177.101 אנחנו פתאום מתמודדים עם 2607:f8b0:4002:c08::8b ואנחנו עלולים להתבלבל. שניהם הם רק ייצוגים קריא אנושיים של 32 ו-128 סיביות, בהתאמה. כל חבילת IP מכילה כותרת עם מטרה סטנדרטית למהדרין לכל סיביות. מבלי להעמיק במבנה הכותרות, דבר אחד שאנחנו צריכים לקחת מזה הוא שבפעולות עם כתובות IP וטווחים, בדרך כלל נוח להשתמש במתמטיקה בינארית ובפעולות סיביות. זה גם הכי נוח לאחסן אותם במסד הנתונים כ BINARY(4) עבור IPv4 ו BINARY(16) עבור IPv6.

היבט חשוב נוסף שכדאי לגעת בו הוא מסכות רשת ותווי CIDR. CIDR הוא ראשי תיבות של Classless Inter-Domain Routing (פנייה ללא מעמד). מושג זה החליף את מושג המחלקה בקביעה איזה חלק מכתובת ה-IP הוא קידומת הרשת, ואיזה חלק הוא הכתובת של ממשק הרשת בתוך רשת זו. בפועל, n הסיביות הראשונות המתאימות לקידומת יוגדרו ל-1, והשאר ל-0.

בצורה מובנת לאדם, זה כתוב בצורה ip.add.re.ss/cidr. לדוגמה, 64.233.177.0/24 מציין ש-24 הסיביות הראשונות מתייחסות לקידומת. 8 הסיביות האחרונות, הידוע גם כמספר האחרון בתווי קריא אנושי, מתייחסים לכתובת בתוך רשת המשנה. עוד כמה תרגילים. 64.233.177.101/32 и 2607:f8b0:4002:c08::8b/128 - כתובת אחת ספציפית. 2607:f8b0:4002:c08::/64 - 64 הסיביות הראשונות (4 הקבוצות הראשונות) הן הקידומת, 64 הסיביות הנותרות הן החלק המקומי. אגב, אם מישהו מתבלבל מה-"::" בערך, המעי הגס הכפול מחליף מספר שרירותי של קטעים המכילים 0. הוא יכול להופיע רק פעם אחת בהערה. במילים אחרות, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.

מה אנחנו צריכים ללמוד מכל זה? ראשית, ניתן להשיג את כתובת המשנה הראשונה והאחרונה באמצעות AND ו- OR בינאריים, תוך הכרת המסכה בצורה בינארית. שנית, גודל רשת המשנה הבא (כלומר עם CIDR) n ניתן לחשב על ידי הוספת 1 ל n-מיקום זה בייצוג בינארי. בבינארי אני מתכוון לתוצאה של שימוש בפונקציות חבילה() и inet_pton() ושימוש נוסף אופרטורים סיביים, על ידי בינארי הוא ייצוג המערכת הבינארית שניתן להשיג על ידי, למשל, base_convert ().

היסטוריהלפנייה ללא מעמד קדמה הפרדה מעמדית. באותן שנים רחוקות, איש לא תיאר לעצמו שיהיו כל כך הרבה רשתות משנה; הן הופצו מימין ומשמאל בגושים גדולים: מחלקה A - הקידומת הייתה 8 הסיביות הראשונות (כלומר המספר הראשון), עם סיביות מובילה של 0; מחלקה B - ראשונים 16 (שני המספרים הראשונים), סיביות מובילות 10; class C - 24 הסיביות הראשונות, סיביות מובילות 110. סיביות מובילות אלו ציינו את הטווחים שבהם הונפקה הכתובת של מחלקה מסוימת: 0.0.0.0 - 127.255.255.255 לכיתה א', 128.0.0.0 - 191.255.255.255 - class B, 192.0.0.0 - 223.255.255.255 - class C. כשהאינטרנט התפשט על פני כדור הארץ, הרגולטורים הבינו שהם פספסו את המטרה, ובתחילת שנות ה-90 הם פיתחו קונספט חסר מעמדות שאיפשר לא להיות קשורים לחלקים המובילים. אפשר למצוא קצת יותר פרטים, למשל, גדול ויודע כל.

בואו נעבור לתרגול

בפועל, ניישם את שלוש המשימות הסבירות ביותר, כפי שנראה לי,:

  1. השגת הכתובת הראשונה והאחרונה של הטווח;
  2. קבל את טווח המידות הבא (CIDR);
  3. בודקים אם כתובת שייכת לטווח.

היישום יהיה עבור IPv6, אך במידת הצורך, ניתן להתאים את ההיגיון בקלות. יש לי כמה רעיונות מכאן, אבל יישם את זה קצת אחרת. כמו כן, הדוגמאות אינן בודקות שגיאות קלט. אז בוא נלך.

כפי שכבר ציינתי, ניתן לקבוע את הכתובת הראשונה והאחרונה של טווח באמצעות פעולות סיביות, תוך ידיעת תחילת הטווח ומסיכת המשנה הבינארית. בהתאם לכך, הדבר הראשון שעלינו לעשות הוא להפוך את ה-CIDR למסכה בינארית. לשם כך, נאסוף את ייצוג ההקסדה שלו ונארוז אותו לייצוג בינארי.

function cidrToMask ($cidr) {
    $mask = str_repeat('f', ceil($cidr / 4));
    $mask .= dechex(4 * ($cidr % 4));
    $mask = str_pad($mask, 32, '0');
    return pack('H*', $mask);
}

Вызов pack('H*', $mask) אורז את ייצוג הhex באותו אופן כמו inet_pton(). ההבדל היחיד הוא כשמתקשרים חבילה() כל האפסים חייבים להיות במקומות שלהם, ולא אמורים להיות נקודתיים בערך, בניגוד לערך הניתן לקריאה על ידי אדם.

השלב הבא הוא לחשב את ההתחלה והסוף של הטווח. וכאן נוצרים ניואנסים. פעולות Bitwise מוגבלות על ידי קיבולת הסיביות של המעבד. בהתאם לכך, ב-32-bit CubieTruck שלי, שאני משתמש בו לפעמים לכל מיני פינוקים לטסטים, לא ניתן יהיה לעבד את כל 128 הביטים של הכתובת בפעולה אחת. עם זאת, שום דבר לא מונע מאיתנו לחלק אותו לקבוצות של 32 סיביות (למקרה, מי יודע על אילו מעבדים נרוץ).

function getRangeBoundary ($ip, $cidr, $which, $ipIsBin = false, $returnBin = false) {
    $mask = cidrToMask($cidr);
    if (!$ipIsBin) {
        $ip = inet_pton($ip);
    }
    $ipParts   = str_split($ip, 4);
    $maskParts = str_split($mask, 4);
    $rangeParts  = [];
    for ($i = 0; $i < count($ipParts); $i++) {
        if ($which == 'start') {
            /* Побитовый & адреса и маски оставит только биты префикса. */
            $rangeParts[$i] = $ipParts[$i] & $maskParts[$i];
        } else {
            /* Побитовый | с обратной маской (~) оставит биты префикса и установит все биты локальной части в 1. */
            $rangeParts[$i] = $ipParts[$i] | ~$maskParts[$i];
        }
    }
    $rangeBoundary = implode($rangeParts);
    if ($returnBin) {
        return $rangeBoundary;
    } else {
        return inet_ntop($rangeBoundary);
    }
}

לשימוש עתידי, אנו נספק את היכולת לשדר IP ולקבל את התוצאה בצורה בינארית וקריאה לאדם. פָּרָמֶטֶר $אשר כאן הוא מציין אם ברצוננו לקבל את ההתחלה או הסוף של הטווח (ערכים 'הַתחָלָה' או 'סוֹף' בהתאמה).

המשימה הבאה (וגם הפרקטית ביותר עבור החברה שלנו) היא חישוב הטווח הבא. עבור המשימה הזו, שום דבר טוב יותר עלה בראש מאשר להרחיב את הכתובת למחרוזת בינארית ולהוסיף 1 במיקום הרצוי, ואז לכווץ הכל בחזרה. כדי למנוע ממצאים להופיע בכל מקום, החלטתי לפצל את הכתובת לפי בתים במהלך הפירוק וההרכבה.

function getNextBlock ($ipStart, $cidr, $ipIsBin = false, $returnBin = false) {
    if (!$ipIsBin) {
        $ipStart = inet_pton($ipStart);
    }
    $ipParts = str_split($ipStart, 1);
    $ipBin   = '';
    foreach ($ipParts as $ipPart) {
        $ipBin .= str_pad(base_convert(unpack('H*', $ipPart)[1], 16, 2), 8, '0', STR_PAD_LEFT);
    }
    /* Добавляем 1 в нужном разряде двоичного представления строки "влоб" :) */
    $i = $cidr - 1;
    while ($i >= 0) {
        if ($ipBin[$i] == '0') {
            $ipBin[$i] = '1';
            break;
        } else {
            $ipBin[$i] = '0';
        }
        $i--;
    }
    $ipBinParts = str_split($ipBin, 8);
    foreach ($ipBinParts as $key => $ipBinPart) {
        $ipParts[$key] = pack('H*', str_pad(base_convert($ipBinPart, 2, 16), 2, '0', STR_PAD_LEFT));
    }
    $nextIp = implode($ipParts);
    if ($returnBin) {
        return $nextIp;
    } else {
        return inet_ntop($nextIp);
    }
}

בפלט אנו מקבלים את הקידומת של טווח הגדלים הבא שצוין ב $cidr. עם פונקציה זו אנו מקצים בלוקים של כתובות ללקוחות שלנו.

לבסוף, בודקים אם הכתובת שייכת לטווח. לדוגמה, הקצנו בלוק אחד /48 להפצת בלוקים ללקוחות /64, וצריך לוודא שבעת ההקצאה לא נצא מהגוש המוקצה (בפועל זה לא יקרה בקרוב, אבל עדיין יש אפשרות). הכל פשוט כאן. אנחנו מקבלים את ההתחלה והסוף של הטווח בצורה בינארית ובודקים האם הכתובת נמצאת בגבולות.

function ipInRange ($ip, $rangeStart, $cidr) {
    $start = getRangeBoundary($rangeStart, $cidr, 'start',false, true);
    $end = getRangeBoundary($rangeStart, $cidr, 'end',false, true);
    $ipBin = inet_pton($ip);
    return ($ipBin >= $start && $ipBin <= $end);
}

אני מקווה שזה היה מועיל. אילו פונקציות נוספות לעבודה עם כתובות עשויות להיות שימושיות לדעתך? כל תוספת, הערה וסקירות קוד יתקבלו בברכה בתגובות.

אם אתה כבר לקוח שלנו או רק חושב להיות כזה, לרגל פרסום מאמר זה אנו מציעים לך לקבל בלוק /64 בחינם לחלוטין עבור כל שירותי ה-VPS או השרת הייעודי במרכז הנתונים Equinix Tier IV, הולנד על פי בקשה למחלקת המכירות על ידי מתן קישור למאמר זה בכרטיס. המבצע בתוקף עד מרץ 2020.

כמה מודעות 🙂

תודה שנשארת איתנו. האם אתה אוהב את המאמרים שלנו? רוצים לראות עוד תוכן מעניין? תמכו בנו על ידי ביצוע הזמנה או המלצה לחברים, Cloud VPS למפתחים החל מ-$4.99, אנלוגי ייחודי של שרתים ברמת הכניסה, שהומצא על ידינו עבורכם: כל האמת על VPS (KVM) E5-2697 v3 (6 ליבות) 10GB DDR4 480GB SSD 1Gbps החל מ-$19 או איך לשתף שרת? (זמין עם RAID1 ו-RAID10, עד 24 ליבות ועד 40GB DDR4).

Dell R730xd זול פי 2 במרכז הנתונים Equinix Tier IV באמסטרדם? רק כאן 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV החל מ-$199 בהולנד! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - החל מ-$99! לקרוא על כיצד לבנות תשתיות קורפ. מחלקה עם שימוש בשרתי Dell R730xd E5-2650 v4 בשווי 9000 יורו עבור אגורה?

מקור: www.habr.com

קנה אירוח אמין לאתרים עם הגנת DDoS, שרתי VPS VDS 🔥 קנה אחסון אתרים אמין עם הגנת DDoS, שרתי VPS VDS | ProHoster