Мини осуществления Тор ("Кинк только анонимные сети")


Цель моего проекта-создать мини - "анонимной" сети (например, Тор) (command-line only), используя Node.js и рамок (гнездо.Ио / Аксиос / экспресс). Сеть где я могу скачать файлы (HTML и т. д) анонимно. У меня Центральный server и узлов (clients). На старте каждый клиент подключается к центральному серверу (используя сокет.Ио) и сервер добавляет его в список hosts. Каждую минуту клиент просит сервер для последнего списка живых узлов.

Я пришел сюда, потому что я сделал все возможное, и я не вижу больше багов / ошибок / недоработок. Кроме того, я не силен в безопасности и только на моем пути к изучению глубокой.

У меня есть несколько вопросов и я буду признательна, если вы посоветуете, как улучшить мой код:

  1. Что можно улучшить и как?
  2. Что насчет безопасности?
  3. Мой узел-цепь не сейчас, что хорошо. Как я могу сделать его больше (Сейчас мне нужно установить 1-минутный таймер и запуск узлов в разное время, но это плохо; в том, что если какой-то узел помрет в середине, мой код будет разрушена, так что я ищу в реальном времени длительный (не менее 3 узлов), услуги)
  4. Сейчас я получаю ответ в виде HTML в моей консоли. Можно ли сделать так, что если узлу localhost:4500 отправляет запрос я вижу ответ как сайт на localhost:4500?

Может быть, мой central server следует создать graf of clients где каждый client (узел) будет 3 (строгий) соседних узлов, но как реализовать это? (визуализации изображения ниже!)

Clients graf

Мой код полностью рабочий и вы можете увидеть его ниже с некоторыми правилами, что мой проект.


URL-адрес (файл) запрос скачать

  1. Если узел инициирует этот запрос сам, он должен иметь в виду id признать отвечу позже

  2. Сетевой узел инициирования запроса отправляет сообщение всем известные соседи

  3. Впервые, когда получили это сообщение, узел сети решает, загружать ли файл (вероятность х%, мой сейчас 50%)

    1. Если файл не загружается, узел сети посылает сообщение всем своим соседям, если сосед IP совпадает с IP-адресом узла передачи сообщения

    2. Когда файл будет загружен, а file тип сообщения инициируется

    3. В обоих случаях сообщение id и экспедиционного узла сети IP должны быть сохранены

  4. Прием второго сообщения с тем же id (или собственной инициативе) будут проигнорированы

  5. Узел инициирования запроса должен учитывать вероятность того, что ответ не гарантируется в связи с неустойчивым характером сети

Вернуть содержимое файла

  1. Сетевой узел, который инициировал запрос, отправляет сообщение на соседа, от которого они downloaded то же самое сообщение с тем же id

  2. Если предыдущий шаг не удается, сообщение будет отправлено всем соседям

  3. Впервые, когда получили это сообщение, узел сети решает, если файл был собственный (id parameter)

    1. Если нет, сетевой узел отправляет сообщение на соседа, который получил запрос с тем же id

    2. Если предыдущий шаг не удалось (или не сообщали о таких сосед), сообщение будет отправлено всем соседям, если сосед IP совпадает отправителя файла в вопрос с IP-адресом

    3. Если файл был запрошен узел, тело запроса анализируется статус и, если содержимое файла

  4. Прием второго сообщения с тем же ID (или собственной инициативе) будут проигнорированы


install гнездо.Ио, экспресс, Аксиос, запрос, тело-парсер, событие-излучатель

Для тестирования я использую почтальон и вот пример запроса:

localhost:4500/download?id=763145322224615&url=https://google.com&port=4500

Я посылаю download запрос на узел с port 4500, id это случайное число, которое запомнится этот узел, чтобы признать ответ позже. url это файл для загрузки.

Клиента (не менее двух клиентов, чтобы получить его работу!)

node client.js 4500
node client.js 4501

Сервер

node server.js

Сервер

"use strict";

const app = require("express")();
const http = require("http").Server(app);
const io = require("socket.io")(http);

var hosts = [];

app.get("/hosts", function(req, res) {
  res.send(hosts);
});

http.listen(3000, function() {
  console.log("listening on *:3000");
});

io.on("connection", function(socket) {
  const address = socket.handshake.address;
  const ipAddress = address.substring(address.lastIndexOf(':') + 1, address.length);
  const port = socket.handshake.query.port;
  hosts.push({ip: ipAddress, port: port});

  console.log(`A machine with ${ipAddress}:${port} successfully connected!`);
  socket.join("hosts");
  console.log(`A machine successfully joined!`);
  socket.emit("hosts", hosts);
  // socket.broadcast.to("hosts").emit("hosts", hosts);

  socket.on("disconnect", function() {
    console.log(`A machine with ${ipAddress}:${port} successfully disconnected!`);
    hosts = hosts.filter(item => item.ip !== ipAddress || item.port !== port);
    // socket.broadcast.to("hosts").emit("hosts", hosts);
  });
});

Клиент

"use strict";

const io = require('socket.io-client');
const app = require('express')();
const http = require('http')
const server = http.Server(app);
const request = require('request');
const axios = require('axios');
const bodyParser = require('body-parser')
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {};
const event = new MyEmitter();

app.use(bodyParser.urlencoded({ extended: true}));
app.use(bodyParser.json())

const states = {"REQUEST": 1, "SEND": 2, "DOWNLOAD": 3, "FINISH": 4};
const errorCodes = {"NOT_ACCEPTABLE": "NOT ACCEPTABLE", "OK": "OK"};
const args = process.argv;

const myIpAddress = "127.0.0.1"
const myPort = args[2] || 4500;

const serverAddress = "127.0.0.1";
const serverPort = 3000;
const socket = io(`http://${serverAddress}:${serverPort}/`, {
  query: `port=${myPort}`
});

const laziness = 0.5;

var hosts = [];
var requests = {};

server.listen(myPort, myIpAddress, () => {
  console.log(`Listening ${myIpAddress}:${myPort}`);
});

socket.on("hosts", data => {
  //console.log("Received data: ", data)
  hosts = data;
});

app.get("/download", (req, res) => {
  const id = req.query.id;
  const url = req.query.url;
  const ip = req.connection.remoteAddress;
  const port = req.query.port;

  if (id == null || url == null || ip == null || port == null) {
    res.sendStatus(404);
    return;
  }
  if (requests[id] != null) {
    res.status(200).send(errorCodes.NOT_ACCEPTABLE);
    return;
  }
  console.log(`Downloading URL: ${url} with ID: ${id}`);

  requests[id] = {
    ip: ip,
    port: port,
    url: url,
    host: false,
    state: states.REQUEST
  }

  event.emit('check', id);
  res.sendStatus(200);
});

app.get('/check', (req, res) => {
  let id = req.query.id;

  res.sendStatus(requests[id] != null && requests[id].state != states.REQUEST ? 200 : 204);
});

app.post('/file', (req, res) => {
  const id = req.query.id;
  console.log('File request');

  if (requests[id] == null || requests[id].state === states.FINISH) {
    res.status(200).send(errorCodes.NOT_ACCEPTABLE);
    return;
  }

  if (requests[id].host) {
    requests[id].state = states.FINISH;
    console.log('StatusCode: ', req.body.status);
    console.log('Mime-type: ', req.body['mime-type']);
    console.log("Content: ", Buffer.from(req.body.content, "base64").toString("ascii"));
  } else {
    console.log('Sending back to host');
    event.emit("sendBackToHost", id, req.body);
  }

  res.sendStatus(200);
});

event.on('check', (id) => {
  axios.get(`http://${requests[id].ip}:${requests[id].port}/check?id=${id}`)
    .then(res => {
      if (res.status === 204) {
        requests[id].url = encodeUrl(requests[id].url);
        requests[id].host = true;
        event.emit('sendNext', id);
      } else if (res.status === 200)
        event.emit('downloadOrSend', id);
    })
    .catch(err => {
      console.error('Error with checking', err);
    })
});

event.on('downloadOrSend', (id) => {
  if (isDownloadState()) {
    axios.get(decodeUrl(requests[id].url))
      .then(res => {
        const body = {
          content: Buffer.from(res.data).toString('base64'),
          status: res.status,
          "mime-type": res.headers["content-type"]
        };

        requests[id].state = states.DOWNLOAD;
        event.emit('sendBackToHost', id, body)
      })
      .catch(err => {
        console.error('Error while downloading file', err.response);
      })
    return;
  }
  event.emit('sendNext', id);
})

event.on('sendBackToHost', (id, body) => {
  axios.post(`http://${requests[id].ip}:${requests[id].port}/file?id=${id}`, body)
    .catch(err => {
      console.error("Error while sending back to host", err.response);
      event.emit('sendFileBackToAll', id, body);
    });
});

event.on('sendNext', (id) => {
  requests[id].state = states.SEND;
  for (let h of hosts) {
    if (h.ip == requests[id].ip && h.port == requests[id].port) continue;
    if (h.ip == myIpAddress && h.port == myPort) continue;

    axios.get(`http://${h.ip}:${h.port}/download?id=${id}&url=${requests[id].url}&port=${myPort}`)
      .catch(err => {
        console.error('Error while sending requests to neighbours', err.response);
      });
  }
});

event.on('sendFileBackToAll', (id, body) => {
  for (let h of hosts) {
    if (h.ip == myIpAddress && h.port == myPort) continue;

    axios.post(`http://${h.ip}:${h.port}/file?id=${id}`, body)
      .catch(err => {
        console.error("Error while sending back to all hosts", err.response);
      });
  }
});

function isDownloadState() {
  return Math.random() >= laziness;
}

function encodeUrl(url) {
  return (decodeURIComponent(url) === url) ? encodeURIComponent(url) : url;
}

function decodeUrl(url) {
  return (decodeURIComponent(url) === url) ? url : decodeURIComponent(url);
}

setInterval(() => {
  axios.get(`http://${serverAddress}:${serverPort}/hosts`)
    .then(res => {
      hosts = res.data;
    })
    .catch(err => {
      console.error("Error while fetching data from server", err.response);
    })
}, 60000)


Комментарии
1 ответ

Я не знаю JavaScript, но некоторые вещи мне стало интересно. Заранее прошу прощения, если я проглядел очевидное. Большинство вещей, которые я указываю здесь вы можете узнать о Список сайтов. Это большой ресурс.


  • Я не могу сказать, если вы проверяете вход, это определенно хорошая идея. Например URL-адреса, которые передаются.

  • Вы не должны принимать чистый HTML, это было бы хорошо, чтобы санировать сначала.

  • Центральный сервер хранит список известных хостов, но как сделать, чтобы клиенты, подключающиеся к серверу централизованной знаю, они не говорят на 'вредоносный' один? Вы можете противостоять этому с помощью публичных/частных ключей(взгляните на libsodium), так что вы могли бы, например, центрального сервера входа в файле hosts\список. То есть центральный хозяева открытый ключ жестко закодированы в клиентов, так что они могут проверить файл 'доверять'.

  • Все данные передаются в открытом виде, как я могу сказать, так явное улучшение будет постоянно расти на открытых ключей и всех узлов генерирует пару ключей, где открытые ключи, принадлежащие к каждому из узлов, перечисленных в файле hosts/список. Затем всякий раз, когда узел передает данные другому узлу, эти данные также могут быть зашифрованы (это очень похоже на подход, ТОР, если я правильно помню).

1
ответ дан 4 апреля 2018 в 07:04 Источник Поделиться