У некоторых людей интересные истории начинаются с приёма жидкостей с содержанием алкоголя. У некоторых с чего-то покрепче… У меня, как у истинного представителя мира IT, история началась… С отключения интернета. Конечно можно было пойти простым путём для решения проблемы, и просто заплатить, но ведь это не истинный путь самурая? Много больших скриншотов
Дело в том, что отключение интернета было обнаружено не сразу. В этот день активно использовал переписку по почте и со своим знакомым. Почта работала отлично, как и шустро бегали сообщения через XMPP на серверах jabber.ru Несмотря на это, основные прелести интернета были недоступны, и, при всех доступных условиях, мысль родилась быстро.
Как вообще работает VPN?
Для понимания принципов работы VPN тоннелей конечно пришлось заплатить за интернет, что бы воспользоваться «тем самым поисковиком». Оказалось, что в основном, туннели строятся с использованием «виртуальных сетевых драйверов» — TUN и TAP. TAP эмулирует Ethernet устройство и работает на канальном уровне модели OSI, оперируя кадрами Ethernet. TUN (сетевой туннель) работает на сетевом уровне модели OSI, оперируя IP пакетами. TAP используется для создания сетевого моста, тогда как TUN для маршрутизации. Для лучшего понимания насколько это круто напомню акие существуют уровни в модели OSI:
Получается, что эмулируя сетевой уровень, мы можем предоставить работоспособность всем уровням выше, а именно: Транспортному, сеансовому, уровню представления, и прикладному уровню. Последние 2 уровня, это и есть те самые, нужные нам протоколы: HTTP, FTP, SSH, SMB, Skype, BitTorrent и сотни других! Мало того, мы обеспечим работой и протоколы других уровней: SSL, TLS; PPTP, L2TP; TCP, UDP и другие. Т.е. наша виртуальная сеть будет практически полноценной сетью, а получать данные и отправлять данные в интерфейс мы можем прямиком из клиентского приложения!
Не труЪ
Поскольку этот мини-проект не претендует на широкое применение и распространение, я взял удобный для себя инструментарий: NodeJS, node-tuntap, node-xmpp. В нормальном случае в Linux работа с TUN и TAP интерфейсом выполняется через файл устройства /dev/net/tun и /dev/net/tap.
Заранее о проблемах
Компилируемая часть node-tuntap нестабильна, и часто падает в Segfault. Будет неплохо, если кто-то пробежится дебаггером и глазами по модулю, и поймет в чём дело. Github модуля: github.com/binarysec/node-tuntap
Поехали!
Для сети я решил использовать tun интерфейс. С ним проще работать, не нужно следить за последовательностью передачи пакетов, и кому мы их отправляем. Также на этом интерфейсе можно заранее задать IP адрес, адрес шлюза и маску подсети.
Инициализация и подключение интерфейса выполняется так:
var tuntap = require('node-tuntap');
try {
var tt = tuntap({
type: 'tun',
name: 'tun2',
mtu: 1500,
addr: '192.168.123.1',
dest: '192.168.123.2',
mask: '255.255.255.192',
ethtype_comp: 'none',
persist: false,
up: true,
running: true,
});
}
catch(e) {
console.log('Tuntap creation error: ', e);
process.exit(0);
}
После запуска этого кода (конечно, от суперпользователя), получаем новый сетевой интерфейс в системе (на скриншоте он называется tun2):
Ничегосебе! Несколько строк кода, и уже целое устройство!
Удобство модуля node-tuntap также в том, что с сетевым интерфейсом можно работать как с экземпляром объекта Stream, следовательно записать данные в интерфейс можно простым tt.write(), а получать данные из потока по событию tt.on('data').
XMPP
Для тестирования сети пришлось зарегестрировать парочку дополнительных jabber аккаунтов: ethernet@jabber.ru и ethernet@xmpp.ru. Обмен пакетами будет происходить через сообщения по протоколу XMPP. Поскольку сообщения текстовые, а данные, которые мы получаем из интерфейса — бинарные (мало того представлены в виде Buffer в NodeJS), данные будут кодироватся в Base64, и по приходу раскодироватся обратно в Buffer.
В NodeJS до 6ой версии это можно было сделать таким путём:
new Buffer(data).toString('base64') //это данные, готовые для отправки
new Buffer(message, 'base64') //здесь мы имеем данные пакета после раскодировки
Последний этап: Получать данные из сетевого интерфейса и отправлять их контакту из списка, который я условно назвал gatewayContact.
Подключение к серверу jabber с помощью xmpp-client:
var Client = require('xmpp-client').Client;
var c = new Client({
jid: login, //Наш логин, в моём случае ethernet@xmpp.ru/jabber.ru
password: password //Несокрушимый пароль
}, function() {
console.log("I'm connected"); //Просто радуемся
this.addListener('message', function(from, message){
console.log('Message from ' + from + ': '+message);
});
});
Осталось соединить воедино оба блока кода, и мы получим:
/**
Ethernet over XMPP
*/
var login = 'ethernet@jabber.ru'; //Наш аккаунт
var password = 'Несокрушимый пароль'; //Пароль, говорящий сам за себя
var gatewayContact = 'ethernet@xmpp.ru'; //Контакт из списка, на котором весит такой-же клиент
var idAdress = '192.168.123.3'; //Наш IP адрес (важно, что бы у другого клиента был другой)
var interfaceId = 'tun2'; //Интерфейс в системе
//******************************************************
var tuntap = require('node-tuntap');
try {
var tt = tuntap({
type: 'tun',
name: interfaceId,
mtu: 1500,
addr: idAdress,
dest: '192.168.123.2', //Не стал делать настраиваемыми, просто потомучто
mask: '255.255.255.192',
ethtype_comp: 'none',
persist: false,
up: true,
running: true,
});
}
catch(e) {
console.log('Tuntap creation error: ', e);
process.exit(0);
}
var Client = require('xmpp-client').Client;
var c = new Client({
jid: login,
password: password
}, function() {
console.log("I'm connected");
tt.on('data', function(data){
console.log('>>> Send packet'); //Получили пакет
c.message(gatewayContact, new Buffer(data).toString('base64')); //Кодируем, и отправляем
});
this.addListener('message', function(from, message){
if(from.indexOf(gatewayContact) !== -1){ //Нам написал контакт с нашим клиентом
console.log('<<< Recived packet');
tt.write(new Buffer(message, 'base64')); //Раскодируем пакет, и пишем в интерфейс
}
});
});
PROFIT!
Я жутко извиняюсь за именование некоторых переменных, надеюсь вы найдёте в себе силы отрефакторить несколько строк.
Тестирование
- Одна из виртуалок имеет адрес 192.168.123.3, второе 192.168.123.1
- Между виртуалками находится обычная сеть за роутером, и обычный интернет. Считаем, что на результаты тестирования это не влияет.
- Скриншоты делались в несколько дублей
Для начала тестируем ping:
Смотрите, работает!
Как насчёт чего-то более приближенного к реальности? Попробуем воспользоватся протоколом HTTP.
Ставим и запускаем Lighttpd:
Тестируем:
Хо-хо!
Усложним ещё:
Загрузилась!
Немного о скорости
Средняя скорость загрузки логотипа хабра была 957 байт/сек. В интернет с такой скоростью выходить мягко говоря не комфортно, однако, я считаю, что цель достигнута.
Windows
Как вы могли заметить, вся разработка и тестирование выполнялась в Linux Ubuntu. Выбор обусловлен несколькими факторами:
- Драйвера TUN/TAP встроены в ядро
- В Linux проще работать с TUN/TAP драйверами, и уже был готовый модуль для NodeJS
- Проще настроить маршрутизацию, что-бы через наш VPN работал интернет
Несмотря на это решить проблему для Windows не очень сложно. Существует несколько реализаций TUN/TAP драйверов, самая популярная написана для проекта OpenVPN, и имеет доступную и понятную документацию. Поддержку драйвера от OpenVPN было бы неплохо внести в тот-же модуль node-tuntap.
Заключение
Конечно эта реализация VPN через XMPP это достаточно медленная. Ради теста я написал реализацию, работающую с помощью SocketIO через хост машину, в этом случае скорости были нормальные. Несмотря на это напоминаю про ответственность за действия, которые вы можете совершить не подумав, и, что весь материал статьи представлен исключительно в ознакомительных целях.
Автор: jhonyxakep