UNPKG

@webwriter/network

Version:

Visualization of network topologies. Can represent different kinds of networks.

548 lines (506 loc) 21.9 kB
import { msg } from '@lit/localize'; import { ComputerNetwork } from '../../..'; import { Ipv4Address } from '../../adressing/Ipv4Address'; import { AddressingHelper } from '../../utils/AdressingHelper'; import { AlertHelper } from '../../utils/AlertHelper'; import { GraphNode } from '../GraphNode'; import { Router } from '../physicalNodes/Connector'; import { PhysicalNode } from '../physicalNodes/PhysicalNode'; import { LogicalNode } from './LogicalNode'; export class Net extends LogicalNode { bitmask: number; networkAddress: Ipv4Address; netmask: string; binaryNetmask: string; parent?: string; //this is updated on drag-and-drop gateways: Map<string, number> = new Map(); //(routerId, portIndex) currentDefaultGateway: [string, number]; constructor( color: string, netAd: string, netmask: string, bitmask: number, database: Map<string, string>, id?: string ) { super(color); if (id != null && id != undefined && id != '') { this.id = id; Net.counter = +id.charAt(id.length - 1); } else { this.id = 'net' + Net.counter; } Net.counter++; this.cssClass.push('net-node'); //if bitmask is not null, calculate equivalent netmask if (bitmask != null) { this.bitmask = bitmask; this.binaryNetmask = ''.padStart(bitmask, '1').padEnd(32, '0'); let derivedDecimalMask: number[] = AddressingHelper.binaryToDecimalOctets(this.binaryNetmask); //if the input netmask is valid and doesn't match our bitmask this.netmask = derivedDecimalMask.join('.'); if (netmask != null && derivedDecimalMask.join('.') != netmask) { AlertHelper.toastAlert( 'warning', 'exclamation-diamond', '', msg('The netmask you entered') + ' <strong>' + netmask + "</strong> " + msg("doesn't match the bitmask") + " <strong>" + this.bitmask + '</strong>. ' + msg('Derived netmask is') + ': ' + this.netmask ); } } else if (netmask != null) { //if bitmask is null, netmask!=null --> calculate bitmask from netmask this.netmask = netmask; this.binaryNetmask = AddressingHelper.decimalStringWithDotToBinary(netmask); this.bitmask = (this.binaryNetmask.match(new RegExp('1', 'g')) || []).length; } let networkId = Ipv4Address.validateAddress(netAd, database, this.bitmask); if (networkId == null) { if (id == null || id == undefined || id == '') { AlertHelper.toastAlert('danger', 'exclamation-diamond', msg('Provided network ID is not valid.'), ''); } this.cssClass.push('unconfigured-net'); return; } this.networkAddress = networkId != null ? networkId : null; this.name = this.networkAddress != null && this.bitmask != undefined ? this.networkAddress.address + ' /' + this.bitmask : ''; Ipv4Address.addAddressToDatabase(this.networkAddress, database, this.id, this.bitmask); } /** * Filter for valid bitmask and netmask * @param color * @param netAd * @param netmask * @param bitmask * @param database * @returns */ static createNet( color: string, netAd: string, netmask: string, bitmask: number, database: Map<string, string>, network: ComputerNetwork ): Net { let bitmaskValid: boolean = !( bitmask == null || bitmask == undefined || Number.isNaN(bitmask) || bitmask < 0 || bitmask > 32 ); let netmaskValid: boolean = !( netmask == null || netmask == undefined || netmask == '' || !AddressingHelper.validateNetMask(netmask) ); if (!bitmaskValid && !netmaskValid) { if (network.subnettingMode == 'NET_BASED') { AlertHelper.toastAlert( 'danger', 'exclamation-diamond', msg('Net-based Mode for CIDR/Subnetting activated:'), msg('Cannot create a net without both bitmask and netmask!') ); return null; } let unconfiguredNet = new Net( color, netAd, netmaskValid ? netmask : null, bitmaskValid ? bitmask : null, database ); unconfiguredNet.cssClass.push('unconfigured-net'); return unconfiguredNet; } return new Net(color, netAd, netmaskValid ? netmask : null, bitmaskValid ? bitmask : null, database); } static setMode(mode: SubnettingMode, network: ComputerNetwork): void { network.subnettingMode = mode; } /** * Calculate the network CIDR when a host is dragged into the network * * @param net A network * @param ip The Ipv4 Address of a new host * @param database Ipv4 database */ static calculateCIDRGivenNewHost( net: Net, ip: Ipv4Address, database: Map<string, string>, network: ComputerNetwork ): void { if (network.subnettingMode != 'HOST_BASED' || ip.matchesNetworkCidr(net)) { return; } let count: number; let candidateId: string; if (!net.cssClass.includes('unconfigured-net')) { let oldPrefix = net.networkAddress.binaryOctets.join('').slice(0, net.bitmask); let newPrefix = AddressingHelper.getPrefix([oldPrefix, ip.binaryOctets.join('')]); count = newPrefix.length; candidateId = newPrefix.padEnd(32, '0'); } else { count = 30; candidateId = ip.binaryOctets.join('').slice(0, count).padEnd(32, '0'); } this.testPossibleNetAddresses(count, candidateId, net, database); } private static testPossibleNetAddresses( count: number, candidateId: string, net: Net, database: Map<string, string>, network: ComputerNetwork ): void { if (network.subnettingMode != 'HOST_BASED') return; if (net.networkAddress != null && net.networkAddress != undefined) { Ipv4Address.removeAddressFromDatabase(net.networkAddress, database, net.bitmask); } let candidateIdFormatted = AddressingHelper.binaryToDecimalOctets(candidateId).join('.'); let candidateAddress = Ipv4Address.validateAddress(candidateIdFormatted, database, count); while (count > 0 && candidateAddress == null) { candidateId = AddressingHelper.replaceAt(candidateId, count, '0'); candidateIdFormatted = AddressingHelper.binaryToDecimalOctets(candidateId).join('.'); candidateAddress = Ipv4Address.validateAddress(candidateIdFormatted, database, count); count--; } //if no net address is available if (candidateAddress == null) { AlertHelper.toastAlert( 'warning', 'exclamation-triangle', msg('Hosts-based mode:'), msg('No valid network address can be assigned to this net.') ); net.setNetInfo(null, null, null, null, true, ''); return; } else { net.setNetInfo( candidateAddress, count, AddressingHelper.binaryToDecimalOctets(''.padStart(count, '1').padEnd(32, '0')).join('.'), ''.padStart(count, '1').padEnd(32, '0'), false ); Ipv4Address.addAddressToDatabase(net.networkAddress, database, net.id, net.bitmask); } } /** * Calculate the network CIDR when a net is dragged into the net * * @param supernet A network * @param subnet A network that is dragged into the supernet * @param database Ipv4 database */ static calculateCIDRGivenNewSubnet( supernet: Net, subnet: Net, database: Map<string, string>, network: ComputerNetwork ): void { if ( network.subnettingMode != 'HOST_BASED' || supernet.isSupernetOf(subnet) || subnet.cssClass.includes('unconfigured-net') ) return; let count: number; let candidateId: string; if (!supernet.cssClass.includes('unconfigured-net')) { let oldSupernetPrefix = supernet.networkAddress.binaryOctets.join('').slice(0, supernet.bitmask); let subnetPrefix = subnet.networkAddress.binaryOctets.join('').slice(0, subnet.bitmask); let newPrefix = AddressingHelper.getPrefix([oldSupernetPrefix, subnetPrefix]); count = newPrefix.length; candidateId = newPrefix.padEnd(32, '0'); } else { let count = subnet.bitmask - 1; let subnetPrefix = subnet.networkAddress.binaryOctets.join('').slice(0, subnet.bitmask); candidateId = AddressingHelper.replaceAt(subnetPrefix, count, '0').padEnd(32, '0'); } Net.testPossibleNetAddresses(count, candidateId, supernet, database); } validateNetLocally(hosts: GraphNode[], gateways: Router[], network: ComputerNetwork, noAlert: boolean): boolean { if (this.cssClass.includes('unconfigured-net')) return false; let allCorrect: boolean = true; let unmatchedPairs: Map<string, [string, string]> = new Map(); //("host", [type of host, name]) let shouldContains: Map<string, string> = new Map(); //(ip, name) hosts.forEach((host) => { if (host instanceof PhysicalNode && host.layer > 2) { host.portData.forEach((data) => { if (!data.get('IPv4').matchesNetworkCidr(this)) { unmatchedPairs.set(data.get('IPv4').address, ['host', host.name]); allCorrect = false; } }); } else if (host instanceof Net) { if (!this.isSupernetOf(host) && !host.cssClass.includes('unconfigured-net')) { unmatchedPairs.set(host.name, ['net', host.name]); allCorrect = false; } } }); gateways.forEach((gateway) => { let port = this.gateways.get(gateway.id); let ip4 = gateway.portData.get(port).get('IPv4'); console.log(this, gateway, port, ip4); if (!ip4.matchesNetworkCidr(this)) { unmatchedPairs.set(ip4.address, ['gateway', gateway.name]); allCorrect = false; } }); network._graph .$('.host-node') .orphans() .forEach((host) => { host.data().portData.forEach((data) => { let ip4 = data.get('IPv4'); if (ip4.matchesNetworkCidr(this) && ip4.address != '127.0.0.1') { shouldContains.set(ip4.address, host.data('name')); allCorrect = false; } }); }); if (allCorrect) return true; if (noAlert && !allCorrect) return false; let alert: string = ''; if (unmatchedPairs.size != 0) { alert += 'should not contain: <ul>'; unmatchedPairs.forEach(([type, name], node) => { switch (type) { case 'host': alert += `<li>${msg("Host")} ` + name + ': ' + node + '</li>'; break; case 'net': alert += `<li>${msg("Net")} ` + node + '</li>'; break; case 'gateway': alert += `<li>${msg("Gateway")} ` + name + ': ' + node + '</li>'; break; } }); alert += '</ul>'; } if (shouldContains.size != 0) { alert += `${msg("should contain")}: <ul>`; shouldContains.forEach((name, ip) => { alert += `<li>${msg("Host")} ` + name + ` ${msg("with address")} ` + ip + '</li>'; }); alert += '</ul>'; } AlertHelper.toastAlert('warning', 'exclamation-triangle', `${msg("Network")} ` + this.name, alert); return false; } isSupernetOf(subnet: Net): boolean { if ( subnet == null || this.cssClass.includes('unconfigured-net') || subnet.cssClass.includes('unconfigured-net') ) { return false; } if (this.bitmask >= subnet.bitmask) return false; return ( this.networkAddress.binaryOctets.join('').slice(0, this.bitmask) == subnet.networkAddress.binaryOctets.join('').slice(0, this.bitmask) ); } private setNetInfo( networkAddress: Ipv4Address, bitmask: number, netmask: string, binaryNetmask: string, unconfig: boolean, name?: string ) { this.bitmask = bitmask; this.networkAddress = networkAddress; this.netmask = netmask; this.binaryNetmask = binaryNetmask; this.name = name != null ? (this.name = name) : this.networkAddress.address + ' /' + this.bitmask; if (!unconfig) { while (this.cssClass.includes('unconfigured-net')) { this.cssClass.splice(this.cssClass.indexOf('unconfigured-net'), 1); } } else { this.cssClass.push('unconfigured-net'); } } handleChangesOnNewNetInfo( newNetId: string, newnetmask: string, newBitmask: number, network: ComputerNetwork ): boolean { let bitmaskValid: boolean = !( newBitmask == null || newBitmask == undefined || Number.isNaN(newBitmask) || newBitmask < 0 || newBitmask > 32 ); let netmaskValid: boolean = !( newnetmask == null || newnetmask == undefined || newnetmask == '' || !AddressingHelper.validateNetMask(newnetmask) ); let networkToFree: [string, number] = !this.cssClass.includes('unconfigured-net') ? [this.networkAddress.address, this.bitmask] : null; if (!bitmaskValid && !netmaskValid) return false; //if bitmask valid, calculate equivalent net mask if (bitmaskValid) { let derivedDecimalMask: number[] = AddressingHelper.binaryToDecimalOctets( ''.padStart(newBitmask, '1').padEnd(32, '0') ); //if the input netmask is valid and doesn't match our bitmask if (netmaskValid && derivedDecimalMask.join('.') != newnetmask) { AlertHelper.toastAlert( 'warning', 'exclamation-diamond', '', `${msg("The netmask you entered")} <strong>` + newnetmask + `</strong> ${msg("doesn't match the bitmask")} <strong>` + newBitmask + `</strong>. ${msg("Derived netmask is")}: ` + derivedDecimalMask.join('.') ); } newnetmask = derivedDecimalMask.join('.'); } else if (netmaskValid) { //if bitmask not valid --> calculate bitmask from netmask newBitmask = (AddressingHelper.decimalStringWithDotToBinary(newnetmask).match(new RegExp('1', 'g')) || []) .length; } if ( this.networkAddress != null && newNetId == this.networkAddress.address && (this.bitmask == undefined || this.bitmask == null || this.bitmask >= newBitmask) ) { network.ipv4Database.delete( AddressingHelper.getBroadcastAddress(this.networkAddress.address, this.bitmask) ); this.setNetInfo( this.networkAddress, newBitmask, newnetmask, AddressingHelper.decimalStringWithDotToBinary(newnetmask), false ); network.ipv4Database.set( AddressingHelper.getBroadcastAddress(this.networkAddress.address, this.bitmask), null ); AlertHelper.toastAlert('success', 'check2-circle', msg('Your changes have been saved.'), ''); return true; } let networkId = Ipv4Address.validateAddress(newNetId, network.ipv4Database, newBitmask); if (networkId == null) return false; switch (network.subnettingMode) { case 'HOST_BASED': if ( this.bitmask >= newBitmask && this.networkAddress.binaryOctets.join('').slice(0, newBitmask) == networkId.binaryOctets.join('').slice(0, newBitmask) ) { this.setNetInfo( networkId, newBitmask, newnetmask, AddressingHelper.decimalStringWithDotToBinary(newnetmask), false ); } else { AlertHelper.toastAlert( 'danger', 'exclamation-triangle', msg('Host-based mode on:'), msg("New network doesn't extend old network!") ); return false; } break; case 'NET_BASED': this.setNetInfo( networkId, newBitmask, newnetmask, AddressingHelper.decimalStringWithDotToBinary(newnetmask), false ); network._graph .$('#' + this.id) .children() .forEach((node) => { let nodeData = node.data(); if (nodeData instanceof PhysicalNode && nodeData.layer > 2) { nodeData.portData.forEach((data) => { let ip4 = data.get('IPv4'); if (ip4 != null && !ip4.matchesNetworkCidr(this)) { Ipv4Address.removeAddressFromDatabase(ip4, network.ipv4Database); let newIpv4 = Ipv4Address.generateNewIpGivenNet(network.ipv4Database, ip4, this); data.set('IPv4', newIpv4); Ipv4Address.addAddressToDatabase(newIpv4, network.ipv4Database, nodeData.id); } }); } else if (nodeData instanceof Net) { //if the subnet doesn't match supernet's CIDR if (!this.isSupernetOf(nodeData)) { network.ipv4Database.delete(nodeData.networkAddress.address); //delete the subnet from database network.ipv4Database.delete( AddressingHelper.getBroadcastAddress( nodeData.networkAddress.address, nodeData.bitmask ) ); nodeData.networkAddress = null; //delete the subnet Address node.toggleClass('unconfigured-net', true); nodeData.cssClass.push('unconfigured-net'); nodeData.name = ''; } } }); this.gateways.forEach((port, id) => { let gateway = network._graph.$('#' + id); let ip4 = gateway.data('portData').get(port).get('IPv4'); if (ip4 != null && !ip4.matchesNetworkCidr(this)) { Ipv4Address.removeAddressFromDatabase(ip4, network.ipv4Database); let newIp4 = Ipv4Address.generateNewIpGivenNet(network.ipv4Database, ip4, this); gateway.data('portData').get(port).set('IPv4', newIp4); Ipv4Address.addAddressToDatabase(newIp4, network.ipv4Database, gateway.id()); } }); break; case 'MANUAL': this.setNetInfo( networkId, newBitmask, newnetmask, AddressingHelper.decimalStringWithDotToBinary(newnetmask), false ); break; } if (networkToFree != null) { network.ipv4Database.delete(AddressingHelper.getBroadcastAddress(networkToFree[0], networkToFree[1])); network.ipv4Database.delete(networkToFree[0]); } AlertHelper.toastAlert('success', 'check2-circle', msg('Your changes have been saved.'), ''); return true; } } export type SubnettingMode = 'NET_BASED' | 'HOST_BASED' | 'MANUAL';