П'ятничний формат: VPN через... Jabber?

У деяких людей цікаві історії починаються з прийому рідин із вмістом алкоголю. У деяких з чогось міцнішого… У мене, як у справжнього представника світу IT, історія почалася… З відключення інтернету. Звичайно, можна було піти простим шляхом для вирішення проблеми, і просто заплатити, але адже це не істинний шлях самурая? Багато великих скріншотів


Справа в тому, що відключення інтернету було виявлено не відразу. В цей день активно використовував листування поштою та зі своїм знайомим. Пошта працювала відмінно, як і спритно бігали повідомлення через XMPP на серверах jabber.ru Незважаючи на це, основні принади інтернету були недоступні, і, при всіх доступних умовах, думка народилася швидко.

Як взагалі працює VPN?
Для розуміння принципів роботи VPN тунелів звичайно довелося заплатити за інтернет, що б скористатися «тим самим пошукачем». Виявилося, що в основному, тунелі будуються з використанням «віртуальних мережевих драйверів» — TUN і TAP (https://ru.wikipedia.org/wiki/TUN/TAP). TAP емулює Ethernet пристрій працює на канальному рівні моделі OSI, оперуючи кадрами Ethernet. TUN (мережевий тунель) працює на мережевому рівні моделі OSI, оперуючи IP пакетами. TAP використовується для створення мережевого моста, тоді як TUN для маршрутизації. Для кращого розуміння наскільки це круто нагадаю акі існують рівні моделі OSI:

Пристрій моделі OSIimage


Виходить, що емулюючи мережевий рівень, ми можемо надати працездатність всіх рівнях вище, а саме: Транспортного, сеансовому, рівня подання, і прикладного рівня. Останні 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. Вибір обумовлений декількома факторами:
  1. Драйвера TUN/TAP вбудовані в ядро
  2. Linux простіше працювати з TUN/TAP драйверами, і вже був готовий модуль для NodeJS
  3. Простіше налаштувати маршрутизацію, що-б через наш VPN працював інтернет


Незважаючи на це вирішити проблему для Windows не дуже складно. Існує декілька реалізацій TUN/TAP драйверів, найпопулярніша написана для проекту OpenVPN, і має доступну і зрозумілу документацію. Підтримку драйвера від OpenVPN було б непогано внести в той-же модуль node-tuntap.

Висновок
Звичайно ця реалізація VPN через XMPP це досить повільна. Заради тесту я написав реалізацію, працюючу з допомогою SocketIO через хост машину, в цьому випадку швидкості були нормальні. Незважаючи на це нагадую про відповідальність за дії, які ви можете зробити, не подумавши, і, що весь матеріал статті представлена виключно в ознайомлювальних цілях.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.