209 lines
7.3 KiB
JavaScript
209 lines
7.3 KiB
JavaScript
|
import React from "react"
|
||
|
|
||
|
import IPInput from "./IPInput"
|
||
|
import RFCLink from "./RFCLink"
|
||
|
|
||
|
import { Address4, Address6 } from "ip-address"
|
||
|
import { BigInteger } from "jsbn"
|
||
|
|
||
|
import styles from "./CIDRCalculator.module.css"
|
||
|
|
||
|
const IP4IN6SUBNET = '64:ff9b::/96'
|
||
|
const IPv6TYPEEXTS = {
|
||
|
'::1/128': (<RFCLink rfc={4291}>Loopback</RFCLink>),
|
||
|
'::/128': (<RFCLink rfc={4291}>Unspecified</RFCLink>),
|
||
|
'::ffff:0:0/96': (<RFCLink rfc={4291}>IPv4 Mapped</RFCLink>),
|
||
|
'64:ff9b::/96': (<RFCLink rfc={6052}>IPv4-IPv6 Translation</RFCLink>),
|
||
|
'100::/64': (<RFCLink rfc={6666}>Discard-Only Address Block</RFCLink>),
|
||
|
'2001::/32': (<RFCLink rfc={4280}>TEREDO Tunnelling</RFCLink>),
|
||
|
'2001:1::1/128': (<RFCLink rfc={7723}>Port Control Protocol Anycast</RFCLink>),
|
||
|
'2001:1::2/128': (<RFCLink rfc={8155}>Traversal Using Relays around NAT Anycast</RFCLink>),
|
||
|
'2001:2::/48': (<RFCLink rfc={5180}>Benchmarking</RFCLink>),
|
||
|
'2001:3::/32': (<RFCLink rfc={7450}>AMT</RFCLink>),
|
||
|
'2001:4:112::/48': (<RFCLink rfc={7535}>AS112-v6</RFCLink>),
|
||
|
'2001:5::/32': (<RFCLink rfc={7954}>EID Space for LISP</RFCLink>),
|
||
|
'2001:10::/28': (<RFCLink rfc={4843}>Deprecated (previously ORCHID)</RFCLink>),
|
||
|
'2001:20::/28': (<RFCLink rfc={7343}>ORCHIDv2</RFCLink>),
|
||
|
'2002::/16': (<RFCLink rfc={3056}>6to4</RFCLink>),
|
||
|
'2620:4f:8000::/48': (<RFCLink rfc={7534}>Direct Delegation AS112 Service</RFCLink>),
|
||
|
'fc00::/7': (<RFCLink rfc={4193}>Unique-Local</RFCLink>),
|
||
|
'fe80::/10': (<RFCLink rfc={4291}>Linked-Scoped Unicast</RFCLink>),
|
||
|
}
|
||
|
|
||
|
const IPv4TYPES = {
|
||
|
'0.0.0.0/8': (<RFCLink rfc={1700}>Broadcast to "this"</RFCLink>),
|
||
|
'10.0.0.0/8': (<RFCLink rfc={1918}>Private network</RFCLink>),
|
||
|
'100.64.0.0/10': (<RFCLink rfc={6598}>Carrier-grade NAT private network</RFCLink>),
|
||
|
'127.0.0.0/8': (<RFCLink rfc={990}>Loopback</RFCLink>),
|
||
|
'169.254.0.0/16': (<RFCLink rfc={3927}>Link-local</RFCLink>),
|
||
|
'172.16.0.0/12': (<RFCLink rfc={1918}>Private network</RFCLink>),
|
||
|
'192.0.0.0/24': (<RFCLink rfc={5736}>IANA IPv4 Special Purpose Address Registry</RFCLink>),
|
||
|
'192.0.2.0/24': (<RFCLink rfc={5737}>"TEST-NET" for documentation/examples</RFCLink>),
|
||
|
'192.88.99.0/24': (<RFCLink rfc={3068}>6to4 anycast relays</RFCLink>),
|
||
|
'192.168.0.0/16': (<RFCLink rfc={1918}>Private network</RFCLink>),
|
||
|
'198.18.0.0/15': (<RFCLink rfc={2544}>Testing networking equipment</RFCLink>),
|
||
|
'198.51.100.0/24': (<RFCLink rfc={5737}>"TEST-NET-2" for documentation/examples</RFCLink>),
|
||
|
'203.0.113.0/24': (<RFCLink rfc={5737}>"TEST-NET-3" for documentation/examples</RFCLink>),
|
||
|
'224.0.0.0/4': (<RFCLink rfc={1112}>Multicast</RFCLink>),
|
||
|
'240.0.0.0/4': (<RFCLink rfc={6890}>Future use</RFCLink>),
|
||
|
'255.255.255.255/32': (<RFCLink rfc={6890}>"Limited broadcast" destination</RFCLink>),
|
||
|
}
|
||
|
|
||
|
const find = (collection, predicate) => {
|
||
|
for (const key of Object.getOwnPropertyNames(collection)) {
|
||
|
const item = collection[key]
|
||
|
if (predicate(item, key)) return item
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const v6Type = (v6) => {
|
||
|
const isType = (name, type) => v6.isInSubnet(new Address6(type))
|
||
|
|
||
|
let type = v6.getType()
|
||
|
if (type !== 'Global unicast') return type
|
||
|
return find(IPv6TYPEEXTS, isType) || type
|
||
|
}
|
||
|
|
||
|
const v4Type = (v4) => {
|
||
|
const isType = (name, type) => v4.isInSubnet(new Address4(type))
|
||
|
|
||
|
return find(IPv4TYPES, isType) || 'Global unicast'
|
||
|
}
|
||
|
|
||
|
const v4BitsToMask = (subnetMask) => {
|
||
|
let n = 0
|
||
|
for (let i = 0; i < subnetMask; i++) {
|
||
|
n = (n << 1) | 1
|
||
|
}
|
||
|
n <<= 32 - subnetMask
|
||
|
const f = (b) => ((n >> b) & 0xff).toString(10)
|
||
|
return `${f(24)}.${f(16)}.${f(8)}.${f(0)}`
|
||
|
}
|
||
|
|
||
|
const highlightElement = (e) => {
|
||
|
const range = document.createRange()
|
||
|
range.selectNodeContents(e)
|
||
|
const sel = window.getSelection()
|
||
|
sel.removeAllRanges()
|
||
|
sel.addRange(range)
|
||
|
}
|
||
|
|
||
|
const highlight = (ev) => highlightElement(ev.target)
|
||
|
const highlightNext = (ev) => highlightElement(ev.target.nextElementSibling)
|
||
|
|
||
|
const toDL = (pairs) => (
|
||
|
<dl>
|
||
|
{pairs.map(pair => (
|
||
|
([
|
||
|
<dt className={styles.cidrHead} onClick={highlightNext} key={`dt:${pair[0]}`}>{pair[0]}</dt>,
|
||
|
<dd className={styles.cidrText} onClick={highlight} key={`dd:${pair[0]}`}>{pair[1]}</dd>
|
||
|
])
|
||
|
))}
|
||
|
</dl>
|
||
|
)
|
||
|
|
||
|
class CIDRCalculator extends React.Component {
|
||
|
constructor(props) {
|
||
|
super(props)
|
||
|
this.state = {
|
||
|
}
|
||
|
|
||
|
this.handleIPChange = this.handleIPChange.bind(this)
|
||
|
this.highlight = this.highlight.bind(this)
|
||
|
}
|
||
|
|
||
|
handleIPChange(value) {
|
||
|
this.setState({ ip: value })
|
||
|
}
|
||
|
|
||
|
highlight(event) {
|
||
|
const range = document.createRange()
|
||
|
range.selectNodeContents(event.target)
|
||
|
const sel = window.getSelection()
|
||
|
sel.removeAllRanges()
|
||
|
sel.addRange(range)
|
||
|
}
|
||
|
|
||
|
renderIP(ip) {
|
||
|
if (!ip) return []
|
||
|
if (ip.to4) return this.renderIPv6(ip)
|
||
|
return this.renderIPv4(ip)
|
||
|
}
|
||
|
|
||
|
renderIPv6(ip) {
|
||
|
const meaningful4in6 = ip.isInSubnet(new Address6(IP4IN6SUBNET))
|
||
|
let info = [
|
||
|
['IPv6 Address', ip.correctForm()],
|
||
|
['Type', v6Type(ip)],
|
||
|
['Prefix Size', ip.subnet],
|
||
|
['Scope', ip.getScope()],
|
||
|
[`4-in-6${meaningful4in6 ? '' : ' (probably meaningless)'}`, ip.to4().correctForm()],
|
||
|
]
|
||
|
if (ip.getBits(88, 104).toString(16) === 'fffe') {
|
||
|
const highBitsPreFix = ip.getBits(64, 88)
|
||
|
const lowBits = ip.getBits(104, 128)
|
||
|
const highBits = highBitsPreFix.xor(new BigInteger((1 << 17).toString(10)))
|
||
|
const mac = highBits.shiftLeft(new BigInteger('24')).or(lowBits)
|
||
|
const mask = new BigInteger('255')
|
||
|
info.push(['Embedded MAC Address', [...Array(6).keys()].map((n) => {
|
||
|
return mac.shiftRight(new BigInteger((8 * 5 - n * 8).toString())).and(mask).toString(16).padStart(2, '0')
|
||
|
}).join(':')])
|
||
|
}
|
||
|
|
||
|
if (ip.subnetMask <= 126) {
|
||
|
const firstAddress = ip.startAddress()
|
||
|
const lastAddress = ip.endAddress()
|
||
|
info = info.concat([
|
||
|
['First Usable Address', firstAddress.correctForm()],
|
||
|
['Last Usable Address', lastAddress.correctForm()],
|
||
|
['Usable Addresses', lastAddress.bigInteger().subtract(firstAddress.bigInteger()).toString()],
|
||
|
])
|
||
|
}
|
||
|
|
||
|
return toDL(info)
|
||
|
}
|
||
|
|
||
|
renderIPv4(ip) {
|
||
|
const netAddress = ip.startAddress()
|
||
|
const firstAddress = Address4.fromBigInteger(netAddress.bigInteger().add(BigInteger.ONE))
|
||
|
const broadcastAddress = ip.endAddress()
|
||
|
const lastAddress = Address4.fromBigInteger(broadcastAddress.bigInteger().subtract(BigInteger.ONE))
|
||
|
|
||
|
let info = [
|
||
|
['IPv4 Address', ip.correctForm()],
|
||
|
['Type', v4Type(ip)],
|
||
|
['Prefix Size', ip.subnet],
|
||
|
['Subnet Mask', v4BitsToMask(ip.subnetMask)],
|
||
|
]
|
||
|
|
||
|
if (ip.subnetMask <= 30)
|
||
|
info = info.concat([
|
||
|
['Network Address', netAddress.correctForm()],
|
||
|
['First Usable Address', firstAddress.correctForm()],
|
||
|
['Last Usable Address', lastAddress.correctForm()],
|
||
|
['Broadcast Address', broadcastAddress.correctForm()],
|
||
|
['Usable Addresses', lastAddress.bigInteger().subtract(firstAddress.bigInteger()).toString()],
|
||
|
])
|
||
|
else if (ip.subnetMask == 31)
|
||
|
info = info.concat([
|
||
|
['First Usable Address (point-to-point)', netAddress.correctForm()],
|
||
|
['Last Usable Address (point-to-point)', firstAddress.correctForm()],
|
||
|
])
|
||
|
|
||
|
return toDL(info)
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
return (
|
||
|
<div>
|
||
|
<form>
|
||
|
<label>IP Address/Subnet: <IPInput onChange={this.handleIPChange} ip={this.state.ip}></IPInput></label>
|
||
|
{this.renderIP(this.state.ip)}
|
||
|
</form>
|
||
|
</div>
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default CIDRCalculator
|