Open1

ip計算のjavascriptメモ

Yoshiaki KawazuYoshiaki Kawazu

色々試し中

//ipv4をint32にする
ip2int=i.split('.').reduce((i,o)=>(i<<8)+parseInt(o))>>>0
//ipv6チェックはこれが短い?→::1とかだと駄目か
is6=i=>i[18] // v6→":", v4→undefined  
is6=i=>i.match(/:/)
is6=i=>/:/.test(i)

使えそうな奴

//ipバージョン
ipv=i=>/:/.test(i)?6:4
//ipv4をhexにする
ip4hex=i=>i.split('.').map(o=>parseInt(o).toString(16).padStart(2,0)).join("")
//ipv6をhexにする
ip6hex=i=>i.split(/:/).map((v,i,a)=>v&&v.padStart(4,0)).map((v,i,a)=>i==a.indexOf('')?'0'.repeat(32-a.reduce((s,v)=>s+=v.length,0)):v).join("")
//ipv4/6をhexにする
ip2hex=i=>(/:/.test(i)?ip6hex:ip4hex)(i)
//ipv4/6をbinにする
ip2bin=i=>ip2hex(i).match(/../g).map(v=>parseInt(v,16).toString(2).padStart(8,0)).join("")
//cidrをprefix binにする
cidr2bin=c=>(([a,p])=>ip2bin(a).substring(0,parseInt(p)||128))(c.split('/'))
//cidrの配列を正規表現に変換
cidrs2pattern=cidrs=>Object.fromEntries(Object.entries(cidrs.reduce((m,c)=>(m[ipv(c)].push(cidr2bin(c)),m),{4:[],6:[]})).map(([k,v])=>[k,new RegExp("^("+v.sort((a,b)=>a.length-b.length).reduce((a,c)=>a.some(v=>c.startsWith(v))?a:a.concat(c),[]).join("|")+")")]))
//cidrの配列をチェック関数に変換
cidrs2matcher=cidrs=>(p=>ip=>p[ipv(ip)].test(ip2bin(ip)))(cidrs2pattern(cidrs))

使用例

isAllowIP = cidrs2matcher(["1132:f0dsa::1/12","1132:f0dsa::1/32","1132:f0dsa::1/12","192.168.10.0/24","10.0.0.0/8"])
isAllowIP("127.0.0.1") // false
isAllowIP("192.169.10.23") // false
isAllowIP("10.10.10.10") // true
isAllowIP("192.168.10.23") // true

気になるところ

パフォーマンス考えるなら Uint8Array での実装に書き換えてベンチ取った方が良いかな

//ipv4をUint8Array()にする
ip4arr=i=>i.split('.').reduce((a,o,i)=>(a[i]=parseInt(o),a),new Uint8Array(4))
//ipv6をUint8Array()にする
ip6arr=i=>new Uint8Array(ip6hex(i).match(/../g).map(v=>parseInt(v,16)))
//ipv4/6をUint8Array()にする
ip2arr=i=>(/:/.test(i)?ip6arr:ip4arr)(i)
//cidrをnetwork addressのUint8Arrayにする
cidr2net=cidr=>(([a,p])=>{p=parseInt(p)||128;a=ip2arr(a).subarray(0,(p>>3)+(p%8?1:0));a[p>>3]&=(255^(255>>(p%8)));return a})(cidr.split('/'))

CIDRリストをコンパクトに埋め込みたい

CloudFront Functinos のコードは 1kb 制限がキツい。IPチェックするにしてもIP定義リストだけでつらい。→勘違いしてた10kbか余裕だった。

CloudFront Functions の正規表現100回制限の回避

手元のJSコンソールとかに上の「使えそうな奴」の関数定義をコピペして、更に↓こんな感じで定義リストをぶち込むと2つの正規表現を含む関数をが得られます。

// CIDRの定義リスト文字列を配列にする(カンマ区切りor改行区切り、空行除去、行コメント(#or//)除去)
cidrsFrom=str=>str.split(/[\r\n]+/).flatMap(v=>v.replace(/(#|\/\/).*/g,'').split(/,/).map(v=>v.trim())).filter(v=>v)
// cidrsを実行可能なJSコードにする(JSコンソール等で実行して計算済み正規表現リテラル&関数をえるハードコーディング)
cidrs2code = cidrs => `ip => ({${Object.entries(cidrs2pattern(cidrs)).map(([v,p])=>`"${v}":${p.toString()}`).join(",")}}[ipv(ip)].test(ip2bin(ip)))`


// ↓こんな感じに実行すると
copy(cidrs2code(cidrsFrom(`
// IPv4
10.0.0.0/8     # プライベートアドレス
172.16.0.0/12  # プライベートアドレス
192.168.0.0/16 # プライベートアドレス
224.0.0.0/4    # マルチキャストアドレス
239.0.0.0/8    # マルチキャストアドレス
169.254.0.0/16 # リンクローカルユニキャストアドレス
127.0.0.1      # ローカルアドレス
// IPv6
fe80::/10 # リンクローカルアドレス
fc00::/7  # ユニークローカルアドレス
ff00::/8  # マルチキャストアドレスス
::1       # ローカルアドレス
`)))
// ↓こんな関数定義のコードがクリップボードに入る
ip => ({"4":/^(1110|00001010|101011000001|1100000010101000|1010100111111110|01111111000000000000000000000001)/,"6":/^(1111110|11111111|1111111010|00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001)/}[ipv(ip)].test(ip2bin(ip)))

CloudFrontFunctions では以下のようにリテラルをべた書きで埋めておけばパターンのコンパイル2回+マッチ1回で済む

// ここはコピペで更新する
var isAllowIP = ip => ({"4":/^(1110|00001010|101011000001|1100000010101000|1010100111111110|01111111000000000000000000000001)/,"6":/^(1111110|11111111|1111111010|00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001)/}[ipv(ip)].test(ip2bin(ip)))

// こう使う
isAllowIP("127.0.0.1") // true