depot/web/lukegbcom/lib/NetworkingTools/CIDRCalculator.js

208 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