ESM: Tidied up IP operations

This commit is contained in:
n1474335 2018-05-29 00:48:30 +01:00
commit 7f9a2eeb6b
7 changed files with 1220 additions and 0 deletions

View file

@ -0,0 +1,120 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import {fromHex} from "../lib/Hex";
/**
* Change IP format operation
*/
class ChangeIPFormat extends Operation {
/**
* ChangeIPFormat constructor
*/
constructor() {
super();
this.name = "Change IP format";
this.module = "JSBN";
this.description = "Convert an IP address from one format to another, e.g. <code>172.20.23.54</code> to <code>ac141736</code>";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Input format",
"type": "option",
"value": ["Hex", "Raw"]
},
{
"name": "Output format",
"type": "option",
"value": ["Hex", "Raw"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inFormat, outFormat] = args,
lines = input.split("\n");
let output = "",
j = 0;
for (let i = 0; i < lines.length; i++) {
if (lines[i] === "") continue;
let baIp = [];
let octets;
let decimal;
if (inFormat === outFormat) {
output += lines[i] + "\n";
continue;
}
// Convert to byte array IP from input format
switch (inFormat) {
case "Dotted Decimal":
octets = lines[i].split(".");
for (j = 0; j < octets.length; j++) {
baIp.push(parseInt(octets[j], 10));
}
break;
case "Decimal":
decimal = lines[i].toString();
baIp.push(decimal >> 24 & 255);
baIp.push(decimal >> 16 & 255);
baIp.push(decimal >> 8 & 255);
baIp.push(decimal & 255);
break;
case "Hex":
baIp = fromHex(lines[i]);
break;
default:
throw new OperationError("Unsupported input IP format");
}
let ddIp;
let decIp;
let hexIp;
// Convert byte array IP to output format
switch (outFormat) {
case "Dotted Decimal":
ddIp = "";
for (j = 0; j < baIp.length; j++) {
ddIp += baIp[j] + ".";
}
output += ddIp.slice(0, ddIp.length-1) + "\n";
break;
case "Decimal":
decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0;
output += decIp.toString() + "\n";
break;
case "Hex":
hexIp = "";
for (j = 0; j < baIp.length; j++) {
hexIp += Utils.hex(baIp[j]);
}
output += hexIp + "\n";
break;
default:
throw new OperationError("Unsupported output IP format");
}
}
return output.slice(0, output.length-1);
}
}
export default ChangeIPFormat;

View file

@ -0,0 +1,136 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
import {IP_DELIM_OPTIONS} from "../lib/Delim";
import {ipv6ToStr, genIpv6Mask, IPV4_REGEX, strToIpv6, ipv4ToStr, IPV6_REGEX, strToIpv4} from "../lib/IP";
/**
* Group IP addresses operation
*/
class GroupIPAddresses extends Operation {
/**
* GroupIPAddresses constructor
*/
constructor() {
super();
this.name = "Group IP addresses";
this.module = "JSBN";
this.description = "Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": IP_DELIM_OPTIONS
},
{
"name": "Subnet (CIDR)",
"type": "number",
"value": 24
},
{
"name": "Only show the subnets",
"type": "boolean",
"value": false,
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0]),
cidr = args[1],
onlySubnets = args[2],
ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF,
ipv6Mask = genIpv6Mask(cidr),
ips = input.split(delim),
ipv4Networks = {},
ipv6Networks = {};
let match = null,
output = "",
ip = null,
network = null,
networkStr = "",
i;
if (cidr < 0 || cidr > 127) {
throw new OperationError("CIDR must be less than 32 for IPv4 or 128 for IPv6");
}
// Parse all IPs and add to network dictionary
for (i = 0; i < ips.length; i++) {
if ((match = IPV4_REGEX.exec(ips[i]))) {
ip = strToIpv4(match[1]) >>> 0;
network = ip & ipv4Mask;
if (ipv4Networks.hasOwnProperty(network)) {
ipv4Networks[network].push(ip);
} else {
ipv4Networks[network] = [ip];
}
} else if ((match = IPV6_REGEX.exec(ips[i]))) {
ip = strToIpv6(match[1]);
network = [];
networkStr = "";
for (let j = 0; j < 8; j++) {
network.push(ip[j] & ipv6Mask[j]);
}
networkStr = ipv6ToStr(network, true);
if (ipv6Networks.hasOwnProperty(networkStr)) {
ipv6Networks[networkStr].push(ip);
} else {
ipv6Networks[networkStr] = [ip];
}
}
}
// Sort IPv4 network dictionaries and print
for (network in ipv4Networks) {
ipv4Networks[network] = ipv4Networks[network].sort();
output += ipv4ToStr(network) + "/" + cidr + "\n";
if (!onlySubnets) {
for (i = 0; i < ipv4Networks[network].length; i++) {
output += " " + ipv4ToStr(ipv4Networks[network][i]) + "\n";
}
output += "\n";
}
}
// Sort IPv6 network dictionaries and print
for (networkStr in ipv6Networks) {
//ipv6Networks[networkStr] = ipv6Networks[networkStr].sort(); TODO
output += networkStr + "/" + cidr + "\n";
if (!onlySubnets) {
for (i = 0; i < ipv6Networks[networkStr].length; i++) {
output += " " + ipv6ToStr(ipv6Networks[networkStr][i], true) + "\n";
}
output += "\n";
}
}
return output;
}
}
export default GroupIPAddresses;

View file

@ -0,0 +1,81 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {ipv4CidrRange, ipv4HyphenatedRange, ipv6CidrRange, ipv6HyphenatedRange} from "../lib/IP";
/**
* Parse IP range operation
*/
class ParseIPRange extends Operation {
/**
* ParseIPRange constructor
*/
constructor() {
super();
this.name = "Parse IP range";
this.module = "JSBN";
this.description = "Given a CIDR range (e.g. <code>10.0.0.0/24</code>) or a hyphenated range (e.g. <code>10.0.0.0 - 10.0.1.0</code>), this operation provides network information and enumerates all IP addresses in the range.<br><br>IPv6 is supported but will not be enumerated.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Include network info",
"type": "boolean",
"value": true
},
{
"name": "Enumerate IP addresses",
"type": "boolean",
"value": true
},
{
"name": "Allow large queries",
"type": "boolean",
"value": false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [
includeNetworkInfo,
enumerateAddresses,
allowLargeList
] = args;
// Check what type of input we are looking at
const ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/,
ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/,
ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i,
ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i;
let match;
if ((match = ipv4CidrRegex.exec(input))) {
return ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList);
} else if ((match = ipv4RangeRegex.exec(input))) {
return ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList);
} else if ((match = ipv6CidrRegex.exec(input))) {
return ipv6CidrRange(match, includeNetworkInfo);
} else if ((match = ipv6RangeRegex.exec(input))) {
return ipv6HyphenatedRange(match, includeNetworkInfo);
} else {
throw new OperationError("Invalid input.\n\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported.");
}
}
}
export default ParseIPRange;

View file

@ -0,0 +1,129 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
import {fromHex, toHex} from "../lib/Hex";
import {ipv4ToStr, protocolLookup} from "../lib/IP";
import TCPIPChecksum from "./TCPIPChecksum";
/**
* Parse IPv4 header operation
*/
class ParseIPv4Header extends Operation {
/**
* ParseIPv4Header constructor
*/
constructor() {
super();
this.name = "Parse IPv4 header";
this.module = "JSBN";
this.description = "Given an IPv4 header, this operations parses and displays each field in an easily readable format.";
this.inputType = "string";
this.outputType = "html";
this.args = [
{
"name": "Input format",
"type": "option",
"value": ["Hex", "Raw"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const format = args[0];
let output;
if (format === "Hex") {
input = fromHex(input);
} else if (format === "Raw") {
input = Utils.strToByteArray(input);
} else {
throw new OperationError("Unrecognised input format.");
}
let ihl = input[0] & 0x0f;
const dscp = (input[1] >>> 2) & 0x3f,
ecn = input[1] & 0x03,
length = input[2] << 8 | input[3],
identification = input[4] << 8 | input[5],
flags = (input[6] >>> 5) & 0x07,
fragOffset = (input[6] & 0x1f) << 8 | input[7],
ttl = input[8],
protocol = input[9],
checksum = input[10] << 8 | input[11],
srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15],
dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19],
checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20));
let version = (input[0] >>> 4) & 0x0f,
options = [];
// Version
if (version !== 4) {
version = version + " (Error: for IPv4 headers, this should always be set to 4)";
}
// IHL
if (ihl < 5) {
ihl = ihl + " (Error: this should always be at least 5)";
} else if (ihl > 5) {
// sort out options...
const optionsLen = ihl * 4 - 20;
options = input.slice(20, optionsLen + 20);
}
// Protocol
const protocolInfo = protocolLookup[protocol] || {keyword: "", protocol: ""};
// Checksum
const correctChecksum = (new TCPIPChecksum).run(checksumHeader),
givenChecksum = Utils.hex(checksum);
let checksumResult;
if (correctChecksum === givenChecksum) {
checksumResult = givenChecksum + " (correct)";
} else {
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
}
output = `<table class='table table-hover table-condensed table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
<tr><td>Version</td><td>${version}</td></tr>
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
<tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr>
<tr><td>Explicit Congestion Notification (ECN)</td><td>${ecn}</td></tr>
<tr><td>Total length</td><td>${length} bytes
IP header: ${ihl * 4} bytes
Data: ${length - ihl * 4} bytes</td></tr>
<tr><td>Identification</td><td>0x${Utils.hex(identification)} (${identification})</td></tr>
<tr><td>Flags</td><td>0x${Utils.hex(flags, 2)}
Reserved bit:${flags >> 2} (must be 0)
Don't fragment:${flags >> 1 & 1}
More fragments:${flags & 1}</td></tr>
<tr><td>Fragment offset</td><td>${fragOffset}</td></tr>
<tr><td>Time-To-Live</td><td>${ttl}</td></tr>
<tr><td>Protocol</td><td>${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})</td></tr>
<tr><td>Header checksum</td><td>${checksumResult}</td></tr>
<tr><td>Source IP address</td><td>${ipv4ToStr(srcIP)}</td></tr>
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>`;
if (ihl > 5) {
output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;
}
return output + "</table>";
}
}
export default ParseIPv4Header;

View file

@ -0,0 +1,192 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
import {strToIpv6, ipv6ToStr, ipv4ToStr, IPV6_REGEX} from "../lib/IP";
import BigInteger from "jsbn";
/**
* Parse IPv6 address operation
*/
class ParseIPv6Address extends Operation {
/**
* ParseIPv6Address constructor
*/
constructor() {
super();
this.name = "Parse IPv6 address";
this.module = "JSBN";
this.description = "Displays the longhand and shorthand versions of a valid IPv6 address.<br><br>Recognises all reserved ranges and parses encapsulated or tunnelled addresses including Teredo and 6to4.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let match,
output = "";
if ((match = IPV6_REGEX.exec(input))) {
const ipv6 = strToIpv6(match[1]),
longhand = ipv6ToStr(ipv6),
shorthand = ipv6ToStr(ipv6, true);
output += "Longhand: " + longhand + "\nShorthand: " + shorthand + "\n";
// Detect reserved addresses
if (shorthand === "::") {
// Unspecified address
output += "\nUnspecified address corresponding to 0.0.0.0/32 in IPv4.";
output += "\nUnspecified address range: ::/128";
} else if (shorthand === "::1") {
// Loopback address
output += "\nLoopback address to the local host corresponding to 127.0.0.1/8 in IPv4.";
output += "\nLoopback addresses range: ::1/128";
} else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 &&
ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) {
// IPv4-mapped IPv6 address
output += "\nIPv4-mapped IPv6 address detected. IPv6 clients will be handled natively by default, and IPv4 clients appear as IPv6 clients at their IPv4-mapped IPv6 address.";
output += "\nMapped IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]);
output += "\nIPv4-mapped IPv6 addresses range: ::ffff:0:0/96";
} else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 &&
ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) {
// IPv4-translated address
output += "\nIPv4-translated address detected. Used by Stateless IP/ICMP Translation (SIIT). See RFCs 6145 and 6052 for more details.";
output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]);
output += "\nIPv4-translated addresses range: ::ffff:0:0:0/96";
} else if (ipv6[0] === 0x100) {
// Discard prefix per RFC 6666
output += "\nDiscard prefix detected. This is used when forwarding traffic to a sinkhole router to mitigate the effects of a denial-of-service attack. See RFC 6666 for more details.";
output += "\nDiscard range: 100::/64";
} else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 &&
ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) {
// IPv4/IPv6 translation per RFC 6052
output += "\n'Well-Known' prefix for IPv4/IPv6 translation detected. See RFC 6052 for more details.";
output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]);
output += "\n'Well-Known' prefix range: 64:ff9b::/96";
} else if (ipv6[0] === 0x2001 && ipv6[1] === 0) {
// Teredo tunneling
output += "\nTeredo tunneling IPv6 address detected\n";
const serverIpv4 = (ipv6[2] << 16) + ipv6[3],
udpPort = (~ipv6[5]) & 0xffff,
clientIpv4 = ~((ipv6[6] << 16) + ipv6[7]),
flagCone = (ipv6[4] >>> 15) & 1,
flagR = (ipv6[4] >>> 14) & 1,
flagRandom1 = (ipv6[4] >>> 10) & 15,
flagUg = (ipv6[4] >>> 8) & 3,
flagRandom2 = ipv6[4] & 255;
output += "\nServer IPv4 address: " + ipv4ToStr(serverIpv4) +
"\nClient IPv4 address: " + ipv4ToStr(clientIpv4) +
"\nClient UDP port: " + udpPort +
"\nFlags:" +
"\n\tCone: " + flagCone;
if (flagCone) {
output += " (Client is behind a cone NAT)";
} else {
output += " (Client is not behind a cone NAT)";
}
output += "\n\tR: " + flagR;
if (flagR) {
output += " Error: This flag should be set to 0. See RFC 5991 and RFC 4380.";
}
output += "\n\tRandom1: " + Utils.bin(flagRandom1, 4) +
"\n\tUG: " + Utils.bin(flagUg, 2);
if (flagUg) {
output += " Error: This flag should be set to 00. See RFC 4380.";
}
output += "\n\tRandom2: " + Utils.bin(flagRandom2, 8);
if (!flagR && !flagUg && flagRandom1 && flagRandom2) {
output += "\n\nThis is a valid Teredo address which complies with RFC 4380 and RFC 5991.";
} else if (!flagR && !flagUg) {
output += "\n\nThis is a valid Teredo address which complies with RFC 4380, however it does not comply with RFC 5991 (Teredo Security Updates) as there are no randomised bits in the flag field.";
} else {
output += "\n\nThis is an invalid Teredo address.";
}
output += "\n\nTeredo prefix range: 2001::/32";
} else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) {
// Benchmarking
output += "\nAssigned to the Benchmarking Methodology Working Group (BMWG) for benchmarking IPv6. Corresponds to 198.18.0.0/15 for benchmarking IPv4. See RFC 5180 for more details.";
output += "\nBMWG range: 2001:2::/48";
} else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) {
// ORCHIDv1
output += "\nDeprecated, previously ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers).\nORCHIDv1 range: 2001:10::/28\nORCHIDv2 now uses 2001:20::/28.";
} else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) {
// ORCHIDv2
output += "\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers).\nThese are non-routed IPv6 addresses used for Cryptographic Hash Identifiers.";
output += "\nORCHIDv2 range: 2001:20::/28";
} else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) {
// Documentation
output += "\nThis is a documentation IPv6 address. This range should be used whenever an example IPv6 address is given or to model networking scenarios. Corresponds to 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4.";
output += "\nDocumentation range: 2001:db8::/32";
} else if (ipv6[0] === 0x2002) {
// 6to4
output += "\n6to4 transition IPv6 address detected. See RFC 3056 for more details." +
"\n6to4 prefix range: 2002::/16";
const v4Addr = ipv4ToStr((ipv6[1] << 16) + ipv6[2]),
slaId = ipv6[3],
interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16),
interfaceId = new BigInteger(interfaceIdStr, 16);
output += "\n\nEncapsulated IPv4 address: " + v4Addr +
"\nSLA ID: " + slaId +
"\nInterface ID (base 16): " + interfaceIdStr +
"\nInterface ID (base 10): " + interfaceId.toString();
} else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) {
// Unique local address
output += "\nThis is a unique local address comparable to the IPv4 private addresses 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. See RFC 4193 for more details.";
output += "\nUnique local addresses range: fc00::/7";
} else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) {
// Link-local address
output += "\nThis is a link-local address comparable to the auto-configuration addresses 169.254.0.0/16 in IPv4.";
output += "\nLink-local addresses range: fe80::/10";
} else if (ipv6[0] >= 0xff00) {
// Multicast
output += "\nThis is a reserved multicast address.";
output += "\nMulticast addresses range: ff00::/8";
}
// Detect possible EUI-64 addresses
if ((ipv6[5] & 0xff === 0xff) && (ipv6[6] >>> 8 === 0xfe)) {
output += "\n\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets.";
const intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" +
Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[5] & 0xff) + ":" +
Utils.hex(ipv6[6] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" +
Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff),
mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" +
Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" +
Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff);
output += "\nInterface identifier: " + intIdent +
"\nMAC address: " + mac;
}
} else {
throw new OperationError("Invalid IPv6 address");
}
return output;
}
}
export default ParseIPv6Address;