dn42regsrv/StaticRoot/free.js
2022-08-01 11:18:18 +01:00

757 lines
22 KiB
JavaScript

//////////////////////////////////////////////////////////////////////////
// DN42 IP Explorer
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// root component
Vue.component('app-root', {
template: '#app-root-template',
methods: {
}
})
//////////////////////////////////////////////////////////////////////////
// free IPv4 view
Vue.component('app-free4', {
template: '#app-free4-template',
data() {
return {
state: 'invalid',
error: '',
inetnum: null,
p4: [ ],
free4: [ ],
stats: {
alloc: 0,
addr: 0,
nets: 0
},
filter: 27,
filtered: [ ],
ftotal: 0,
asn: '4242423999',
mntner: 'FOO',
person: 'FOO',
example: '0.0.0.0/0'
}
},
methods: {
// not used
pretty4(msg, subnet, plen) {
var octets = [ ]
octets[0] = (subnet >> 24) & 0xFF
octets[1] = (subnet >> 16) & 0xFF
octets[2] = (subnet >> 8) & 0xFF
octets[3] = subnet & 0xFF
console.log(msg, ': ', octets.join("."),'/',plen)
},
// reload prefix data
reload() {
// reset current data
this.inetnum = null
this.p4.splice(0,this.p4.length)
this.state = "loading"
// IPv4 prefixes
axios
.get('/api/registry/inetnum/*')
.then(response => {
this.inetnum = response.data
this.processIPv4()
})
.catch(error => {
this.error = error
this.state = 'error'
console.log(error)
})
},
// parse an IPv4 string in to an object
parse4(p) {
p = p.replace('_','/')
// split out prefix
var s1 = p.split('/')
// and octets
var s2 = s1[0].split('.')
if (s1.length !=2 || s2.length != 4) {
console.log("Failed to IPv4 parse ", p)
return null
}
// convert to a 32bit integer
var num =
((parseInt(s2[0]) << 24) +
(parseInt(s2[1]) << 16) +
(parseInt(s2[2]) << 8) +
parseInt(s2[3]))>>>0
var plen = parseInt(s1[1])
var mask = (0xFFFFFFFF - ((2**(32 - plen))-1))>>>0
// finally return the ipv4 object
return {
plen: plen,
mask: mask,
subnet: num
}
},
// process IPv4 prefixes
processIPv4() {
var inets = [ ]
Object.values(this.inetnum).forEach(rp => {
// map the attributes in to a hash
// no need to worry about duplicated attribs
var attrib = { }
rp.Attributes.forEach(a => {
attrib[a[0]] = a[1]
})
// convert the cidr to an object
obj = this.parse4(attrib.cidr)
if (obj != null) {
if (attrib.policy != null) {
obj.policy = attrib.policy
}
inets.push(obj)
}
})
// sort the prefixes in ascending order
var sorted = inets.sort((a,b) => {
if (a.subnet == b.subnet) {
return a.plen - b.plen
}
else {
return a.subnet - b.subnet
}
})
// splice in sorted array
this.p4.splice(0, this.p4.length, ...sorted)
// update the free list
this.updateFree4()
this.updatePrefixLen({ target: { value: this.filter } })
// all done
this.state = 'ready'
},
// update ipv4 stats
stats4(subnet, plen) {
// check DN42 range 172.20.0.0/14
var masked = (subnet & 0xFFFC0000)>>>0
if (masked == 0xAC140000) {
// add em up
this.stats.nets += 1
this.stats.addr += 2**(32-plen)
}
},
// recursively check a subnet for free blocks
scanSubnets(subnet, mask, plen) {
// check for the last defined prefix
if (this.ix >= this.p4.length) {
this.stats4(subnet, plen)
this.free4.push({
subnet: subnet,
mask: mask,
plen: plen
})
return
}
var prefix = this.p4[this.ix]
// does the next prefix match this subnet ?
if ((prefix.subnet == subnet) && (prefix.plen == plen)) {
var policy = 'assigned'
if (prefix.policy != null) {
policy = prefix.policy
}
if (policy != 'open') {
// optimisation to reduce recursion by
// scanning forward for open children
this.ix += 1
while(this.ix < this.p4.length) {
prefix = this.p4[this.ix]
var masked = (prefix.subnet & mask)>>>0
if (subnet != masked) {
// no longer a child, we're done
return
}
// found a subnet that is open
if (prefix.policy == 'open') {
this.scanSubnets(prefix.subnet,
prefix.mask,
prefix.plen,
prefix.policy)
}
else {
// don't recurse closed subnets
var tmppolicy = 'assigned'
if (prefix.policy != null) {
tmppolicy = prefix.policy
}
this.ix += 1
}
}
return
}
// move on to next index
this.ix += 1
if (this.ix >= this.p4.length) {
this.stats4(subnet, plen)
this.free4.push({
subnet: subnet,
mask: mask,
plen: plen
})
return
}
prefix = this.p4[this.ix]
}
// is the next prefix a subnet of the current search ?
var masked = (prefix.subnet & mask)>>>0
if (subnet == masked) {
// split the subnet and try again
plen += 1
var bit = (2**(32 - plen))>>>0
mask = (mask | bit)>>>0
this.scanSubnets((subnet & (~bit))>>>0, mask, plen)
this.scanSubnets((subnet | bit)>>>0, mask, plen)
}
else {
// found an open block
this.stats4(subnet, plen)
this.free4.push({
subnet: subnet,
mask: mask,
plen: plen
})
}
},
// update the free blocks list
updateFree4() {
// reset stats
this.stats.alloc = this.p4.length
this.stats.addr = 0
this.stats.nets = 0
// recursively scan for free nets
this.ix = 0
this.scanSubnets(0, 0, 0, 'undefined')
},
// pick 10 random prefixes of a certain length
pick10(prefix) {
// may be called with an empty free list
if (this.free4.length == 0) {
return { total: 0, blocks: [ ] }
}
var tlist = [ ]
// filter the free list by this prefix size
this.free4.forEach(free => {
if (free.plen == prefix) {
tlist.push(free)
}
})
// if there are fewer than 10 prefixes,
// split some of the next largest blocks
if (tlist.length < 10) {
const bit = (2**(32 - prefix))>>>0
const tmp = this.pick10(prefix-1)
const blocks = tmp.blocks
while(tlist.length < 10) {
var ix = Math.floor(Math.random()*blocks.length)
const obj = blocks[ix]
// split the block
tlist.push({
subnet: (obj.subnet & (~bit))>>>0,
mask: (obj.mask | bit)>>>0,
plen: prefix
})
tlist.push({
subnet: (obj.subnet | bit)>>>0,
mask: (obj.mask | bit)>>>0,
plen: prefix
})
blocks.splice(ix, 1)
}
}
const total = tlist.length
// select 10 random prefixes
var result = [ ]
for (var i = 0; ((tlist.length > 0) && (i < 10)); i++) {
var ix = Math.floor(Math.random()*tlist.length)
result.push(tlist[ix])
tlist.splice(ix,1)
}
return { total: total, blocks: result }
},
// filter subnets based on prefix length
filterFree() {
var result = [ ]
tmp = this.pick10(this.filter)
this.ftotal = tmp.total
blocks = tmp.blocks
for(var i = 0; i < blocks.length; i++) {
const obj = blocks[i]
// push to result
var octets = [ ]
octets[0] = (obj.subnet >> 24) & 0xFF
octets[1] = (obj.subnet >> 16) & 0xFF
octets[2] = (obj.subnet >> 8) & 0xFF
octets[3] = obj.subnet & 0xFF
result.push(octets.join(".")+'/'+obj.plen)
}
this.filtered.splice(0, this.filtered.length, ...result)
},
// update function when selecting prefixes
updatePrefixLen(e) {
this.filter = e.target.value
// update buttons
var group = document.getElementById("prefixselect")
group.childNodes.forEach(button => {
if (button.nodeName == "BUTTON") {
button.classList.remove('btn-primary')
button.classList.remove('btn-secondary')
if (button.value == this.filter) {
button.classList.add('btn-primary')
}
else {
button.classList.add('btn-secondary')
}
}
})
// select available prefixes
this.filterFree()
},
// update the example templates
updateExample(e) {
this.example = e.text
}
},
mounted() {
// reload data if required
if (this.p4.length == 0) {
this.reload()
}
// always update the prefix selection
this.updatePrefixLen({ target: { value: this.filter } })
}
})
//////////////////////////////////////////////////////////////////////////
// free IPv6 view
Vue.component('app-free6', {
template: '#app-free6-template',
data() {
return {
state: 'invalid',
error: '',
inet6num: null,
p6: [ ],
stats: {
alloc: 0,
nets: 0
},
plist: [ ]
}
},
methods: {
// reload prefix data
reload() {
// reset current data
this.inet6num = null
this.p6.splice(0,this.p6.length)
this.state = "loading"
// IPv6 prefixes
axios
.get('/api/registry/inet6num/*')
.then(response => {
this.inet6num = response.data
this.processIPv6()
})
.catch(error => {
this.error = error
this.state = 'error'
console.log(error)
})
},
// parse an IPv6 string in to an object
parse6(cidr) {
cidr = cidr.replace('_','/')
// split prefix and length
var s1 = cidr.split('/')
var plen = parseInt(s1[1])
// ignore networks longer than /64 as they aren't valid
if (plen <= 64) {
// split out the quads
var quads = s1[0].split(':')
// and canonicalise '::'
var ix=quads.indexOf('')
if (ix != -1) {
while(quads.length < 8) {
quads.splice(ix, 0, '0')
}
}
// calculate 64bit network part of prefix
var num = BigInt(0)
var quad
for(var i = 0; i < 4; i++) {
num *= BigInt(65536)
quad = (quads[i] == '' ? 0 : parseInt('0x' + quads[i]))
num += BigInt(quad)
}
var plen = parseInt(s1[1])
var mask = BigInt(0xFFFFFFFFFFFFFFFF) - ((2n**BigInt(64-plen))-1n)
// return object
return {
plen: plen,
mask: mask,
subnet: num
}
}
},
// process IPv6 prefixes
processIPv6() {
var netspace = BigInt(0)
var nets = [ ]
Object.values(this.inet6num).forEach(rp => {
// extract attributes
var attrib = { }
rp.Attributes.forEach(a => {
attrib[a[0]] = a[1]
})
// turn in to object
obj = this.parse6(attrib.cidr)
if (obj != null) {
if (attrib.policy != null) {
obj.policy = attrib.policy
}
// ignore ::/0 and fd00::/8 when calculating netspace
if ((obj.plen > 8) && (obj.policy != 'open')) {
netspace += 2n**BigInt(64 - obj.plen)
}
nets.push(obj)
}
})
netspace /= 1000000n
this.stats.nets = Number(netspace)
// sort by subnet
var sorted = nets.sort((a,b) => {
if (a.subnet == b.subnet) {
return a.plen - b.plen
}
else {
if (a.subnet > b.subnet) { return 1 }
return -1
}
})
// update
this.p6.splice(0, this.p6.length, ...nets)
this.stats.alloc = this.p6.length
this.generate()
// all done
this.state = 'ready'
},
// check if a network is already allocated
check6(n) {
// fixed /48 mask
var mask = BigInt(0xFFFFFFFFFFFF0000n)
var match = null
this.p6.forEach(obj => {
// only check longer prefixes than current match
if ((match == null) || (obj.plen >= match.plen)) {
var masked
// is the new network a subnet of the test obj ?
masked = n & obj.mask
if (obj.subnet == masked) {
// new, more precise prefix found
match = obj
}
// is the test obj a subnet of this network ?
masked = obj.subnet & mask
if (n == masked) {
// immediate fail, existing subnets not allowed
return false
}
}
})
if (match.policy != 'open') {
return false
}
return true
},
// create new random prefixes
generate() {
var nlist = [ ]
// generate 10 new prefixes
for(var i = 0; i < 10; i++) {
var valid = false
var prefix = ''
while(!valid) {
// create 48bits of random address
var quads = [ ]
for(var j = 0; j < 3; j++) {
quads[j] = Math.floor(Math.random()*65536)
}
// fix the first byte to be in fd00::/8
quads[0] = 0xFD00 + (quads[0] & 0xFF)
// convert to hex
var hex = [ ]
for (j = 0; j < 3; j++) {
hex[j] = quads[j].toString(16)
}
prefix = `${hex[0]}:${hex[1]}:${hex[2]}::/48`
// convert to a BigInt
var num = BigInt(0)
for(var j = 0; j < 3; j++) {
num *= BigInt(65536)
num += BigInt(quads[j])
}
num *= 65536n
// now check that the random prefix is not a subnet of another allocation
valid = this.check6(num)
}
nlist.push(prefix)
}
// swap in the new prefix list
this.plist.splice(0, this.plist.length, ...nlist)
}
},
computed: {
freenets() {
return Math.round(72057.594 - (this.stats.nets/1000000))
}
},
mounted() {
if (this.p6.length == 0) {
this.reload()
}
}
})
//////////////////////////////////////////////////////////////////////////
// free IPv6 view
Vue.component('app-asn', {
template: '#app-asn-template',
data() {
return {
state: 'invalid',
error: '',
autnum: null,
free: [ ],
stats: {
alloc: 0,
},
alist: [ ]
}
},
methods: {
// reload prefix data
reload() {
// reset current data
this.asn = null
this.free.splice(0,this.free.length)
this.state = "loading"
// fetch ASN list
axios
.get('/api/registry/aut-num')
.then(response => {
this.autnum = response.data['aut-num']
this.processASN()
})
.catch(error => {
this.error = error
this.state = 'error'
console.log(error)
})
},
// process ASN list
processASN() {
var asn = [ ]
// extract used ASN
this.autnum.forEach(a => {
// only interested in DN42 ASN
if (a.startsWith('AS424242')) {
var num = parseInt(a.substr(8))
asn[num] = true
this.stats.alloc += 1
}
})
// now work out which ASN are free
for(var a = 0; a < 4000; a++) {
if (!asn[a]) {
var str = a.toString(10)
// zero pad
while(str.length < 4) {
str = '0' + str
}
this.free.push('AS424242' + str)
}
}
this.generate()
// all done
this.state = 'ready'
},
// create new random prefixes
generate() {
var nlist = [ ]
for(var i = 0; i < 10; i++) {
// pick a random free ASN
var rand = Math.floor(Math.random() * this.free.length)
nlist.push(this.free[rand])
}
// swap in the new prefix list
this.alist.splice(0, this.alist.length, ...nlist)
}
},
mounted() {
if (this.free.length == 0) {
this.reload()
}
}
})
//////////////////////////////////////////////////////////////////////////
// main vue application starts here
// initialise the Vue Router
const router = new VueRouter({
routes: [
{ path: '/', component: Vue.component('app-root') },
{ path: '/4', component: Vue.component('app-free4') },
{ path: '/6', component: Vue.component('app-free6') },
{ path: '/asn', component: Vue.component('app-asn') }
]
})
// and the main app instance
const vm = new Vue({
el: '#free-app',
data: {
},
router
})
//////////////////////////////////////////////////////////////////////////
// end of code