18

IPv4 サブネットを表すクラスを作成しています。ネットワーク アドレスとサブネット マスクを 4 バイトのバイナリ文字列として格納しています。これは、引数に基づいてコンストラクター中に構築されます。コンストラクターに受け入れてもらいたい表現の 1 つはCIDR 表記法です。

私のビット単位の操作は少し錆びており、サブネット マスクの 10 進数の CIDR 表現を 4 バイトのバイナリ文字列に、またはその逆に変換する際に行き詰まりました。また、文字列で左/右シフトを実行できないこともわかりました。これは以前に成功したと確信していますか?


次のコードで動作するように、バイナリ文字列への変換を行うことができました。

// An example input value.
$mask = 24; // 255.255.255.0

if ($mask < 0 || $mask > 32) {
  // Invalid prefix size
  throw new RangeException('Invalid CIDR prefix size');
} else if ($mask === 0) {
  // Handle 0
  $mask = "\x00\x00\x00\x00";
} else {
  // Left-pad a 4-byte string with $mask set bits
  $mask = pack('N', (0x01 << 31) >> ($mask - 1));
}

このロジックは、次の 2 つの理由から好きではありません。

  • 0特別なケースとして扱いたくない
  • 右シフトの後に左シフトが続くのは好きではない

0特別なケースとして扱わずに正しく処理する方法で、これをより効率的に行う方法があると確信しています。


バイナリ文字列を CIDR プレフィックス サイズの 10 進数表現に戻す場合、現在、以下のコードを使用しています。セットのビットが連続していることを確認するために、他の形式で提供されたサブネット マスクを検証するときに、非常によく似た別のコード ブロックがあります。

// An example input value.
$mask = "\xff\xff\xff\x00"; // /24

// Convert the binary string to an int so bit shifts will work
$mask = current(unpack('N', $mask));

// A counter to represent the CIDR
$cidr = 0;

// Loop and check each bit
for ($i = 31; $i > 0; $i--) {
  if (($mask >> $i) & 0x01) {
    $cidr++;
  } else {
    break;
  }
}

// Return the result
return $cidr;

私はループのためにこれが好きではありません - それを行うためのよりインテリジェントなビット単位の方法があると確信しています。


これらのタスクのいずれかを行うためのよりインテリジェントな方法はありますか?

考え/提案/一般的な悪用をお願いします...


編集:

どのソリューションも PHP 4.3.10 以降で動作する必要があり、32 ビットと 64 ビットの両方のプラットフォームで動作する必要があります。PHP のすべての整数は符号付きであり、32 ビット プラットフォームではすべて>= 0x80000000が double として格納されることに注意してください (したがって、ビット単位の操作では適切に動作しません)。

4

5 に答える 5

8

2番目の問題は、(非反転数の最初の未設定ビットを見つけるのではなく)反転数の最初の設定ビットを見つけることと同じように見ることができます。これは、数値の整数log2を見つけることと同じです。

これはビット単位の世界ではかなり一般的な問題であり、速度が最適化されたアルゴリズムが多数あります。(遅い)明白なアルゴリズムを使用しています:http ://www-graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

しかし、私はあなたが本当にスピードを気にしないと思います、むしろ簡潔さを気にします、その場合あなたはこのようなことをすることができます:

$cidr = (int) (32 - log(~current(unpack('N', $mask)) & 0xffffffff, 2));

& 0xffffffff64ビット整数と互換性があるために必要です。

于 2012-08-23T23:14:28.703 に答える
6

2 番目の問題は、テキストによるアプローチで解決できます。

$mask = "\xff\xff\xff\x00";

$cidr = strspn(sprintf('%b', current(unpack('N', $mask))), 1);

sprintf()整数をバイナリテキスト表現に変換するために使用しstrspn()、最初の数をカウントします。

アップデート

64 ビット マシンでは、バイナリ テキスト表現の左側に 32 個のゼロが埋め込まれているため、コードに次のltrim()ようなパッチを適用する必要があります。

$cidr = strspn(ltrim(sprintf('%b', current(unpack('N', $mask))), 0), 1);

更新 2

最初の問題はテキストのアプローチでも解決できますが、str_split()(PHP 4.x では機能しません) を使用する必要があります。

$mask = vsprintf('%c%c%c%c', array_map('bindec', str_split(str_pad(str_repeat(1, $mask), 32, 0), 8)));

アップデート 3

私にとってうまくいくのは次のとおりです(32ビットと64ビットの両方でテスト済み):

$mask = pack('N', 0xffffffff << (32 - $mask));

その過程で、数値は float になりますが、ビット シフトを処理するのに十分な精度を保持します。

于 2012-08-24T03:30:40.067 に答える
3

しない理由:

$netmask = ( (1<<32) -1 ) << ( 32 - $cidr);

あなたは、左から右へのシフトが気に入らないと言っていましたが、左への 2 つのシフトはどうですか ;)

ip2longこの後、かに投げるlong2ip。マスクから CIDR に移行するには、次のようにします。

$mask = ip2long($mask);
$base = ( ( 1 << 32 ) - 1 );
$cidr = 32 - log( ( $mask ^ $base ) + 1 , 2);

もちろん、ストレージの種類に合わせて必要に応じて、またはpack上記を使用できます。dechexunpack

于 2012-08-28T18:43:08.277 に答える
3

なぜそれを計算するのですか?32 個のサブネット マスクの配列を作成するだけです。

$cidr2mask = array( "\x00\x00\x00\x00", "\x80\x00\x00\x00", "\xc0\x00\x00\x00", "\xe0\x00\x00\x00",
                    "\xf0\x00\x00\x00", "\xf8\x00\x00\x00", "\xfc\x00\x00\x00", "\xfe\x00\x00\x00", 
                    "\xff\x00\x00\x00", "\xff\x80\x00\x00", "\xff\xc0\x00\x00", "\xff\xe0\x00\x00", 
                    "\xff\xf0\x00\x00", "\xff\xf8\x00\x00", "\xff\xfc\x00\x00", "\xff\xfe\x00\x00", 
                    "\xff\xff\x00\x00", "\xff\xff\x80\x00", "\xff\xff\xc0\x00", "\xff\xff\xe0\x00", 
                    "\xff\xff\xf0\x00", "\xff\xff\xf8\x00", "\xff\xff\xfc\x00", "\xff\xff\xfe\x00", 
                    "\xff\xff\xff\x00", "\xff\xff\xff\x80", "\xff\xff\xff\xc0", "\xff\xff\xff\xe0", 
                    "\xff\xff\xff\xf0", "\xff\xff\xff\xf8", "\xff\xff\xff\xfc", "\xff\xff\xff\xfe");

$mask2cidr = array_flip($cidr2mask);

次に、とを使用$cidr2mask[$cidr];$mask2cidr[$mask]ます。

于 2012-09-01T11:17:17.983 に答える
1

(恥知らずな自己宣伝)

IP アドレスに対して非常によく似た処理を行う PHP ライブラリを作成しました。

IPv4 サブネットマスクを作成する方法は次のとおりです。

<?php
$mask = (~0) << (32 - $cidr);
$binary_mask = pack('N', $mask);
echo implode('.', unpack('C4', $binary_mask));

名前空間が原因で古いバージョンの PHP では動作しませんが、互換性の問題を修正するためのプル リクエストを喜んで受け入れる追加前のブランチがあります。コードは (ほぼ) 100% 単体テストでカバーされています:)

唯一の依存関係はpear Math_BigInteger パッケージです。

于 2012-09-01T21:38:13.520 に答える