Расширение браузера для управления маршрутами на Микротике

0461a81238e6ead92c1fb6a298ae6355.jpg
let opened_tab = null;
let api_ip_address = null;
let api_ip_query = '';
let routes_query = '';
let domains_query = '';
let domains_regular = ',';
let exclude_minprefix = 32;
let exclude_maxprefix = 0;
let notrouted_minprefix = 32;
let notrouted_maxprefix = 0;

async function InitPage()
{
  let load_settings = null;
  await chrome.storage.local.get(["MikroTikControlPanelSettings"]).then((result) => {
    if (typeof result.MikroTikControlPanelSettings != 'undefined')
      load_settings = result.MikroTikControlPanelSettings;
  });
  
  if (load_settings !== null) {
    settings['user'] = DecodeString(load_settings['an']);
    settings['password'] = DecodeString(load_settings['ap']);
    settings['localhost'] = load_settings['localhost'];
    settings['router'] = load_settings['router'];
    settings['protocol'] = load_settings['protocol'];
    settings['defaults']['dynamic'] = load_settings['defaults']['dynamic'];
    settings['defaults']['time'] = load_settings['defaults']['time'];
    settings['defaults']['www'] = load_settings['defaults']['www'];
    settings['defaults']['what'] = load_settings['defaults']['what'];
    settings['defaults']['how'] = load_settings['defaults']['how'];
  } else {
    if ((typeof settings['user'] == 'undefined' || !settings['user']) && typeof settings['an'] != 'undefined' && settings['an'])
      settings['user'] = ae(settings['an']);
    if ((typeof settings['password'] == 'undefined' || !settings['password']) && typeof settings['ap'] != 'undefined' && settings['ap'])
      settings['password'] = ae(settings['ap']);
  }
  
  for (let subnet in settings['exclude']) {
    if (settings['exclude'][subnet] > exclude_maxprefix)
      exclude_maxprefix = settings['exclude'][subnet];
    if (settings['exclude'][subnet] < exclude_minprefix)
      exclude_minprefix = settings['exclude'][subnet];
  }
  
  for (let subnet in settings['notrouted']) {
    if (settings['notrouted'][subnet] > notrouted_maxprefix)
      notrouted_maxprefix = settings['notrouted'][subnet];
    if (settings['notrouted'][subnet] < notrouted_minprefix)
      notrouted_minprefix = settings['notrouted'][subnet];
  }
  
  document.getElementById("local-address").value = settings['localhost'];
  document.getElementById("router-address").value = settings['router'];
  document.getElementById("router-user").value = settings['user'];
  document.getElementById("router-password").value = settings['password'];
  document.getElementById("protocol-selection").value = settings['protocol'];
  document.getElementById("domain-dynamic").checked = settings['defaults']['dynamic'];
  document.getElementById("domain-time").value = settings['defaults']['time'];
  document.getElementById("domain-" + settings['defaults']['how']).checked = true;
  document.getElementById("domain-" + settings['defaults']['what']).checked = true;
  document.getElementById("top-domains").value = settings['defaults']['www'];
  
  let routes_options = '';
  let domains_options = '';
  
  for (let idx in settings['routes']) {
    routes_options += '';
    
    if (settings['routes'][idx])
      routes_query += '"list=' + settings['routes'][idx] + '",' + (routes_query ? '"#|",' : '');
  }
  
  for (let idx in settings['domains']) {
    domains_options += '';
    
    domains_regular += settings['domains'][idx] + ',';
    
    if (settings['domains'][idx])
      domains_query += '"list=' + settings['domains'][idx] + '",' + (domains_query ? '"#|",' : '');
  }
  
  document.getElementById("route-selection").innerHTML = routes_options;
  document.getElementById("domain-selection").innerHTML = domains_options;
  
  document.getElementById("settings-button").addEventListener("click", () => {
    HideViewBlock('settings-block');
  });
  
  await chrome.tabs.query({active: true}, (tabs) => {
    const tab = tabs[0];
    if (tab) {
      opened_tab = tab;
      const url = new URL(tab.url);
      const hostname = url.hostname;
      document.getElementById("current-page").innerHTML = hostname;
      
      if ((/^(\d{1,3}\.){3}\d{1,3}$/).test(hostname))
        document.getElementById("domain-button").disabled = IpIsExclude(hostname);
      
      if (hostname.indexOf('.') > 0) {
        fetch("http://api.syo.su/ipwhois?" + hostname, {
          method: "GET",
          headers: { "Accept": "application/json" }
        }).then((response) => { return response.json(); }).then((whois) => {
          document.getElementById("page-address").innerHTML = whois['ip'];
          document.getElementById("page-city").innerHTML = whois['ip2location']['city'] + ", " + whois['ip2location']['country'];
          document.getElementById("domain-button").disabled = IpIsExclude(whois['ip']);
          
          RefreshDomainInfo();
        }).catch(function() {
          SetExternalApiError();
        });
      } else {
        document.getElementById("domain-button").disabled = true;
      }
    } else {
      document.getElementById("domain-button").disabled = true;
    }
  });
  
  RefreshAddressInfo();
  
  let authuser = 'Basic ' + btoa(settings['user'] + ":" + settings['password']);
  
  fetch(settings['protocol'] + "://" + settings['router'] + "/rest/ip/firewall/address-list/print", {
    method: "POST",
    headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser },
    body: '{".query": [' + routes_query + '"address=' + settings['localhost'] + '","disabled=false"]}'
  }).then((response) => { return response.json(); }).then((data) => {
    document.getElementById("route-selection").value = (data.length ? data[0]['list'] : '');
  }).catch(function() {
    SetRouterApiError();
  });
  
  await fetch("http://api.syo.su/gethost?api.syo.su", {
    method: "GET",
    headers: { "Accept": "text/html" }
  }).then((response) => { return response.text(); }).then((ip) => {
    api_ip_address = ip;
    api_ip_query = '"dst-address=' + ip + ':443","dst-address=' + ip + ':80","#|"';
  });
  
  document.getElementById("route-button").addEventListener("click", () => {
    SetLocalhostRoute();
  });
  
  document.getElementById("domain-button").addEventListener("click", () => {
    SetDomainRoute();
  });
  
  document.getElementById("save-button").addEventListener("click", () => {
    SaveSettings();
  });
}

function HideViewBlock(block_name)
{
  let info_block = document.getElementById(block_name);
  info_block.hidden = !info_block.hidden;
}

function SetRouterApiError()
{
  document.getElementById("error-messages").innerHTML += "Error connecting to router API
Check router address, username, password and protocol in settings"; document.getElementById("route-button").disabled = true; document.getElementById("domain-button").disabled = true; } function SetExternalApiError() { document.getElementById("error-messages").innerHTML += "Error connecting to external API
Check availability http://api.syo.su"; document.getElementById("domain-button").disabled = true; } function RefreshAddressInfo() { fetch("http://api.syo.su/myip", { method: "GET", headers: { "Accept": "text/html" } }).then((response) => { return response.text(); }).then((data) => { document.getElementById("current-address").innerHTML = data; fetch("http://api.syo.su/ipwhois?" + data, { method: "GET", headers: { "Accept": "application/json" } }).then((response) => { return response.json(); }).then((whois) => { document.getElementById("address-city").innerHTML = whois['ip2location']['city'] + ", " + whois['ip2location']['country']; document.getElementById("address-provider").innerHTML = whois['ip2location']['provider']; }).catch(function() { SetExternalApiError(); }); }).catch(function() { SetExternalApiError(); }); } async function SetLocalhostRoute() { let localhost = document.getElementById("local-address").value.replaceAll(' ', ''); let addrlist = document.getElementById("route-selection").value; let address = document.getElementById("page-address").innerHTML; let domain = document.getElementById("current-page").innerHTML; let router = document.getElementById("router-address").value.replaceAll(' ', ''); let username = document.getElementById("router-user").value; let userpass = document.getElementById("router-password").value; let protocol = document.getElementById("protocol-selection").value; let authuser = 'Basic ' + btoa(username + ":" + userpass); let is_exclude = (!address || address == '-' || IpIsExclude(address)); let disable_from = 0; let current_list = await fetch(protocol + "://" + router + "/rest/ip/firewall/address-list/print", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".query": [' + routes_query + '"address=' + localhost + '"]}' }).then((response) => { return response.json(); }).then((data) => { return data; }); let current_conn_query = api_ip_query; if (!is_exclude) current_conn_query += '"dst-address=' + address + ':443","dst-address=' + address + ':80","#|"' + (api_ip_query ? '"#|"' : ""); let current_conn = await fetch(protocol + "://" + router + "/rest/ip/firewall/connection/print", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".query": [' + current_conn_query + ']}' }).then((response) => { return response.json(); }).then((data) => { return data; }); let gateway_address = null; if (current_conn.length) gateway_address = current_conn[0]['reply-dst-address'].split(':')[0]; if (addrlist) { disable_from = 1; let fetch_method = null; let fetch_url = null; let sendbody = '"address":"' + localhost + '","disabled":"false","dynamic":"false","list":"' + addrlist + '"'; if (current_list.length) { fetch_method = "PATCH"; fetch_url = protocol + "://" + router + "/rest/ip/firewall/address-list/" + current_list[0]['.id']; let comment = (typeof current_list[0]['comment'] != 'undefined' ? ',"comment":"' + current_list[0]['comment'] + '"' : ""); sendbody = '".id":"' + current_list[0]['.id'] + '",' + sendbody + comment; } else { fetch_method = "POST"; fetch_url = protocol + "://" + router + "/rest/ip/firewall/address-list/add"; } await fetch(fetch_url, { method: fetch_method, headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{' + sendbody + '}' }).then((response) => { return response; }); } for (let i = disable_from; i < current_list.length; i++) if (current_list[i]['disabled'] == 'false') await fetch(protocol + "://" + router + "/rest/ip/firewall/address-list/" + current_list[i]['.id'], { method: "PATCH", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".id":"' + current_list[i]['.id'] + '","address":"' + localhost + '","disabled":"true","dynamic":"' + current_list[i]['dynamic'] + '","list":"' + current_list[i]['list'] + '"}' }).then((response) => { return response; }); for (let i = 0; i < current_conn.length; i++) { if (localhost == current_conn[i]['src-address'].split(':')[0]) { let check_address = await fetch(protocol + "://" + router + "/rest/ip/firewall/connection/" + current_conn[i]['.id'], { method: "DELETE", headers: { "Accept": "application/json", "Authorization": authuser } }).then((response) => { return response; }); } }; setTimeout(RefreshAddressInfo, 1000); if (!is_exclude) setTimeout(function() { chrome.tabs.reload(opened_tab.id); }, 1000); } function RefreshDomainInfo() { let domain = document.getElementById("current-page").innerHTML; let address = document.getElementById("page-address").innerHTML; let router = document.getElementById("router-address").value.replaceAll(' ', ''); let username = document.getElementById("router-user").value; let userpass = document.getElementById("router-password").value; let protocol = document.getElementById("protocol-selection").value; let authuser = 'Basic ' + btoa(username + ":" + userpass); fetch(protocol + "://" + router + "/rest/ip/firewall/address-list/print", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".query": [' + domains_query + '"comment=' + domain + '","address=' + address + '","#&","address=' + domain + '","#|","disabled=false","timeout","#!","dynamic=true","#&!"]}' }).then((response) => { return response.json(); }).then((data) => { if (data.length) { document.getElementById("domain-time").value = (data[0]['dynamic'] == 'true' ? data[0]['timeout'] : settings['defaults']['time']); document.getElementById("domain-dynamic").checked = (data[0]['dynamic'] == 'true'); document.getElementById("domain-selection").value = data[0]['list']; } else { document.getElementById("domain-time").value = settings['defaults']['time']; document.getElementById("domain-dynamic").checked = settings['defaults']['dynamic']; document.getElementById("domain-selection").value = ""; } }); } async function SetDomainRoute() { let domain = document.getElementById("current-page").innerHTML; let address = document.getElementById("page-address").innerHTML; let addrlist = document.getElementById("domain-selection").value; let dynamic = document.getElementById("domain-dynamic").checked; let dyntime = document.getElementById("domain-time").value.replaceAll(' ', ''); let router = document.getElementById("router-address").value.replaceAll(' ', ''); let username = document.getElementById("router-user").value; let userpass = document.getElementById("router-password").value; let protocol = document.getElementById("protocol-selection").value; let top_domains_template = ',' + document.getElementById("top-domains").value.replaceAll(' ', '').toLowerCase() + ','; let as_address = document.getElementById("domain-address").checked; let add_www = document.getElementById("domain-www").checked; let add_all = document.getElementById("domain-all").checked; let authuser = 'Basic ' + btoa(username + ":" + userpass); let is_exclude = (!address || address == '-' || IpIsExclude(address)); if (is_exclude) return 0; let domains = new Array(); let addresses = new Array(); let error_messages = new Array(); if (domain != address) { let domain_struct = domain.split('.'); let steps_count = 1; if (add_www) { if (top_domains_template.indexOf(',' + domain_struct[0] + ',') >= 0) steps_count = 2; } else if (add_all) { steps_count = domain_struct.length - 1; } let domain_next = domain; for (let i = 0; i < steps_count; i++) { domains[domain_next] = new Array(); if (as_address) { let hosts = await fetch("http://api.syo.su/gethosts?" + domain_next, { method: "GET", headers: { "Accept": "text/html" } }).then((response) => { return response.text().split('
'); }).catch(function() { return null; }); if (hosts !== null) { for (let i = 0; i < hosts.length; i++) if (!addresses.includes(hosts[i])) { addresses.push(hosts[i]); domains[domain_next].push(hosts[i]); } } else error_messages.push("Error API access"); } else { domains[domain_next].push(domain_next); } domain_next = domain_next.substr(domain_struct[i].length + 1); } } let current_query = ''; let current_addr_query = ''; for (let dom in domains) { current_query += '"comment=' + dom + '","address=' + dom + '","#|",' + (current_query ? '"#|",' : ''); current_addr_query += '"comment=' + dom + '",' + (current_addr_query ? '"#|",' : ''); } let current_list = await fetch(protocol + "://" + router + "/rest/ip/firewall/address-list/print", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".query": [' + domains_query + current_query + '"disabled=false","timeout","#!","dynamic=true","#&!"]}' }).then((response) => { return response.json(); }).then((data) => { return data; }); let current_addrs = new Array(); let current_num = null; if (current_list.length) current_num = 0; if (addrlist) { for (let dom in domains) { for (let i = 0; i < domains[dom].length; i++) { let set_address = domains[dom][i]; let sendbody = '"address":"' + set_address + '","list":"' + addrlist + '","disabled":"false"'; if (as_address) sendbody += ',"comment":"' + dom + '"'; if (dynamic) sendbody += ',"dynamic":"true","timeout":"' + dyntime + '"'; else sendbody += ',"dynamic":"false"'; let fetch_method = null; let fetch_url = null; if (current_num !== null && current_num < current_list.length) { fetch_method = "PATCH"; fetch_url = protocol + "://" + router + "/rest/ip/firewall/address-list/" + current_list[current_num]['.id']; sendbody = '".id":"' + current_list[current_num]['.id'] + '",' + sendbody; current_num++; } else { fetch_method = "POST"; fetch_url = protocol + "://" + router + "/rest/ip/firewall/address-list/add"; } let check_address = await fetch(fetch_url, { method: fetch_method, headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{' + sendbody + '}' }).then((response) => { return response.json(); }).then((answer) => { return answer; }); } } } else { current_addrs = await fetch(protocol + "://" + router + "/rest/ip/firewall/address-list/print", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".query": [' + domains_query + current_addr_query + '"disabled=false"]}' }).then((response) => { return response.json(); }).then((data) => { return data; }); } if (current_num !== null) for (let i = current_num; i < current_list.length; i++) { let check_address = await fetch(protocol + "://" + router + "/rest/ip/firewall/address-list/" + current_list[i]['.id'], { method: "DELETE", headers: { "Accept": "application/json", "Authorization": authuser } }).then((response) => { return response; }); } if (addrlist) current_addrs = await fetch(protocol + "://" + router + "/rest/ip/firewall/address-list/print", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".query": [' + domains_query + current_addr_query + '"disabled=false"]}' }).then((response) => { return response.json(); }).then((data) => { return data; }); let current_conn_query = ''; for (let i = 0; i < current_addrs.length; i++) { current_conn_query += '"dst-address=' + current_addrs[i]['address'] + ':443","dst-address=' + current_addrs[i]['address'] + ':80","#|",' + (current_conn_query ? '"#|",' : ''); } let current_conn = await fetch(protocol + "://" + router + "/rest/ip/firewall/connection/print", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authorization": authuser }, body: '{".query": [' + current_conn_query + ']}' }).then((response) => { return response.json(); }).then((data) => { return data; }); for (let i = 0; i < current_conn.length; i++) { let check_address = await fetch(protocol + "://" + router + "/rest/ip/firewall/connection/" + current_conn[i]['.id'], { method: "DELETE", headers: { "Accept": "application/json", "Authorization": authuser } }).then((response) => { return response; }); } setTimeout(function() { chrome.tabs.reload(opened_tab.id); }, 1000); } function SaveSettings() { let localhost = document.getElementById("local-address").value; let dynamic = document.getElementById("domain-dynamic").checked; let dyntime = document.getElementById("domain-time").value; let router = document.getElementById("router-address").value; let username = document.getElementById("router-user").value; let userpass = document.getElementById("router-password").value; let protocol = document.getElementById("protocol-selection").value; let top_domains_template = document.getElementById("top-domains").value; let as_address = document.getElementById("domain-address").checked; let add_www = document.getElementById("domain-www").checked; let add_all = document.getElementById("domain-all").checked; let save_settings = { localhost: localhost, router: router, an: CodeString(username), ap: CodeString(userpass), protocol: protocol, defaults: { dynamic: dynamic, time: dyntime, www: top_domains_template, what: (add_www ? "www" : (add_all ? "all" : "top")), how: (as_address ? "address" : "name") } }; chrome.storage.local.set({"MikroTikControlPanelSettings": save_settings }).then(() => { document.getElementById("settings-info").innerHTML = "Settings saved"; }); } const delay = (delayInms) => { return new Promise(resolve => setTimeout(resolve, delayInms)); }; function IpIsExclude(ip_str) { let ip = ParseIp(ip_str); if (ip === null) return false; return IpInArray(ip, settings['exclude'], exclude_minprefix, exclude_maxprefix) || IpInArray(ip, settings['notrouted'], notrouted_minprefix, notrouted_maxprefix); } function IpInArray(ip, arr, prefmin, prefmax) { let submask = new Uint32Array([0xffffffff << (32 - prefmax)])[0]; let network = ip & submask; for (let prefix = prefmax; prefix >= prefmin; prefix--) { let subnet = ((network >>> 24) & 0xff).toString() + '.' + ((network >>> 16) & 0xff).toString() + '.' + ((network >>> 8) & 0xff).toString() + '.' + (network & 0xff).toString(); if (typeof arr[subnet] != 'undefined' && arr[subnet] <= prefix) return true; submask = submask << 1; network = network & submask; } return false; } function ParseIp(ip_str, ip_format = 10) { let ip_octets = ip_str.split('.'); ip_octets[0] = parseInt(ip_octets[0], ip_format); ip_octets[1] = parseInt(ip_octets[1], ip_format); ip_octets[2] = parseInt(ip_octets[2], ip_format); ip_octets[3] = parseInt(ip_octets[3], ip_format); if (isNaN(ip_octets[0]) || isNaN(ip_octets[1]) || isNaN(ip_octets[2]) || isNaN(ip_octets[3]) || ip_octets[0] > 255 || ip_octets[1] > 255 || ip_octets[2] > 255 || ip_octets[3] > 255 || ip_octets[0] < 0 || ip_octets[1] < 0 || ip_octets[2] < 0 || ip_octets[3] < 0) return null; return new Uint32Array([((ip_octets[0] << 24) + (ip_octets[1] << 16) + (ip_octets[2] << 8) + ip_octets[3])])[0]; } function ae(ai) { let f = ""; for (let c = 0; c < ai.length; c++) f += String.fromCharCode(ai.charCodeAt(c) ^ (1 + (ai.length - c) % 32)); return f; } function StringToHex(str) { let hex = ''; for (let i = 0; i < str.length; i++) hex += str.charCodeAt(i).toString(16).padStart(2, '0'); return hex; } function HexToString(hex) { let str = ''; for (let i = 0; i < hex.length; i += 2) str += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16)); return str; } function CodeString(str) { return StringToHex(ae(btoa(str))); } function DecodeString(str) { return atob(ae(HexToString(str))); } InitPage();

© Habrahabr.ru