Бот для месенджера Telegram — Swatcher_Bot

Swatcher_BotКак и многие другие люди я смотрю сериалы, в основном смотрю серии по мере их выхода в русской озвучке. Мне достаточно быстро надоело отслеживать выход новых серий, но найти подходящего сайта который бы делал это за меня мне не удалось и я забил на это! А тут совсем недавно я узнал о существование Bot API для Telegram и решил попробовать свои свежее приобретенные навыки программирования на Node.JS тем более что обертка над API для Javascript уже существует(Telegram Bot API for NodeJS) и написал бота отслеживающего сериалы — Swatcher_Bot.

Условно можно разбить Swatcher_Bot на три составляющие части:
  • модуль парсинга новостных сайтов
  • модуль оповещения пользователей
  • пользовательский интерфейс

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

Swatcher_Bot можно использовать для отслеживания выхода новых серий, а так же как базу сериалов, сейчас в базе бота 7.5 тысяч сериалов. В будущем может быть добавлю возможность рассылки новостей о сериалах.

Продакшен менеджер для Node.JS проекта

pm2.20d3efРазвиваем тему настройки VPS-сервера под Node.JS проект. После того как мы установили Node.JS и MongoDB, скачали и запустили наш проект стоит задуматься о стабильности его работы, а так же отслеживанием его работы.

Для решения этой задачи подходит PM2. Вот что написано на его сайте(примерный перевод): PM2 — это продакшен(production) менеджер для Node.JS со встроенным балансировщиком нагрузки. Он позволяет вашим приложениям жить вечно, а так же перезагружать их на лету и упрощает работу с общими системными задачами администрирования. Собственно именно это нам и нужно, во первых потому что неудобно постоянно следить за работой программы и в случае возникновения критической ошибки заходить на сервер что бы ее перезапустить. А во вторых потому что приложения Node.JS используют только одно ядро ОС и если мы хотим эффективно использовать многоядерный сервер нам нужно запускать несколько инстансов нашего приложения и балансировать между ними нагрузку.

 

Устанавливается приложение так:

sudo npm install pm2 -g

После этого можно запустить приложение:

pm2 start app.js

Что бы приложения автоматически запустились после перезапуска системы стоит добавить pm2 в автозапуск этой командой:

sudo env PATH=$PATH:/usr/local/bin pm2 startup -u user

Так же PM2 поддерживает различные расширения, например автоматическое ратирование логов pm2-logrotate.
Очень удобный параметр для разработчика —watch, запуск программы с этим параметром заставляет PM2 перезапускать приложение при изменении файла.

pm2 start app.js --watch

Настройка VPS-сервера под Node JS проект

node

MongoDBШпаргалка по настройке VPS-сервера под Node JS проект. В процессе изучения Javascript и Node JS родилась у меня идея несложного бота для Telegram, который бы оповещал своих пользователей. И когда бот кое-как работал, умел оповещать, хотя и не имел интерфейса как такого, решил я его разместить на сервере. После оплаты VPS я оказался в консоли Ubuntu 14.04 от root’a.

Добавление пользователя

Что-либо делать под root’ом довольно опасно, так что первым делом создаем папку для нового пользователя и собственно пользователя:

 

mkdir /home/username
useradd -d /home/username -s /bin/bash username

Continue reading

Настройка GPG Keyserver на OpenLDAP

87898_original

PGP (англ. Pretty Good Privacy) — компьютерная программа,  позволяющая выполнять операции шифрования и цифровой подписи сообщений, файлов и другой информации, представленной в электронном виде.

 

Будем настраивать PGP key server на openldap.
Для настройки сервера ключей нам потребуются пакеты:

sudo apt-get install slapd ldap-utils

 

Если нужно сменить суффикс:

ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f change_suffix.ldif
dn: olcDatabase={1}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com

Continue reading

Пример использования libevent2 для реализации TCP-сервера

libevent2 —  это библиотека предоставляющая механизм вызова callback-функций по приходу событий. Простой пример работы в виде TCP-сервера с буферизированным вводом/выводом. Небольшая напоминалочка себе.

 

Для начала инициализируем библиотеку. libevent2 инициализируется функцией event_init():

struct event_base *ev_base;
 
/* Инициализируем библиотеку */
event_init();
/* Указываем обработчик фатальный ошибок */
event_set_fatal_callback((event_fatal_cb) event_fatal_err);
 
ev_base = event_base_new();
if (!ev_base) {
    exit(EXIT_FAILURE);
}
static void event_fatal_err(int err)
{
    exit(EXIT_FAILURE);
}

После чего подготавливаем сокет.

/* Подготавливаем сокет */
memset((void *) &server_sock, 0, sizeof(server_sock));
server_sock.sin_family = AF_INET;
server_sock.sin_addr.s_addr = INADDR_ANY;
server_sock.sin_port = htons(31245); /* номер порта */
/* bind'им сокет */
ev_listener = evconnlistener_new_bind(ev_base,
                                      accept_connection_cb, /* будет вызвана при попытки подключения*/
                                      NULL,                 /* можно передать данные в callback-функцию */
                                      (LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE),
                                      -1,
                                      (struct sockaddr *) &server_sock,
                                      sizeof(server_sock));
if(!ev_listener) {
    exit(EXIT_FAILURE);
}

И запускаем обработчик событий:

/* обработчик */
return event_base_dispatch(ev_base);

 

Теперь при попытке подключения будет вызываться функция:

static void accept_connection_cb(struct evconnlistener *listener,
                                 evutil_socket_t fd, struct sockaddr *addr, int sock_len,
                                 void *arg)
{
    struct bufferevent *ev_buf;
    struct timeval timeout;
    int ret;
    /* Создаем сокет для подключения */
    ev_buf = bufferevent_socket_new(ev_base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (ev_buf == NULL) {
        exit(EXIT_FAILURE);
    }
    /* Устанавливаем callback-функции */
    bufferevent_setcb(ev_buf,
                      socket_read_cb,     /* callback чтения*/
                      NULL,               /* callback записи*/
                      error_callback,     /* callback ошибки*/
                      NULL);              /* в callback-функции можно передать данные */
 
    /* устанавливаем таймаут */
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;
    bufferevent_set_timeouts(ev_buf, &timeout, NULL);
    /* Активируем событие */
    ret = bufferevent_enable(ev_buf, EV_READ);
    if (ret < 0) {
        bufferevent_free(ev_buf);
        exit(EXIT_FAILURE);
    }
}

Теперь напишем callback-функции чтения и обработки ошибок:

static void error_callback(struct bufferevent *bev, short error, void *ctx)
{
    if(error & BEV_EVENT_READING) {
        /* в случае ошибки чтения аварийно завершаем программу*/
        exit(EXIT_FAILURE);
    }
}
 
static void socket_read_cb(struct bufferevent *bev, void *ctx)
{
    size_t ret;
    int chunk;
    /* Читаем даные из буфера */
    ret = bufferevent_read(bev, &chunk, sizeof(chunk));
    if(ret != sizeof(chunk)) {
        exit(EXIT_FAILURE);
    }
    /* Пишем данные в буфер */
    ret = bufferevent_write(bev, &chunk, sizeof(chunk));
    if(ret != sizeof(chunk)) {
        exit(EXIT_FAILURE);
    }
}

На официальном сайте можно найти документацию, а так же книгу.

Выделение памяти в ядре Linux

Основной единицой управления памятью в ядре Linux является страница памяти. И хотя наименьшой адресуемой единицой памяти является байт, модуль управления памятью(MMU) работает только с страницами памяти.

 

В ядре существует низкоуровневые механизмы выделения страниц памяти.

 

#include <linux/gfp.h>
 
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order);
 
struct page * alloc_page(gfp_t gfp_mask);
 
void * page_address(struct page *page);
 
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
 
unsigned long __get_free_page(gfp_t gfp_mask);
 
unsigned long get_zeroed_page(unsigned int gfp_mask);

 

Первая функция возвращает указатель на на первую из 2 в степени order (1 << order)  смежных страниц памяти, в случае ошибки возвращается NULL. Всегда проверяйте результат выполнения этой(как и других) функции, ибо цена ошибки в ядре операционной системы велика. Переменная gfp_t gfp_mask содержит битовую маску параметров. В этой переменной хранятся модификаторы зон, модификаторы операций и модификаторы и флаги типов, больше можно прочитать в в файле gfp.h. Опишу лишь некоторое из составных модификаторов(флаги типов), применяемых чаще всего:

 

 #define GFP_NOWAIT
 #define GFP_ATOMIC               /* Применяется в основном в IRQ */
 #define GFP_NOIO
 #define GFP_NOFS
 #define GFP_KERNEL               /* Обычный режим выделения памяти в ядре */
 #define GFP_TEMPORARY
 #define GFP_USER
 #define GFP_HIGHUSER
 #define GFP_HIGHUSER_MOVABLE

 

Функция alloc_page выделяет одну страницу памяти. Для того что бы преобразовать struct page к адресу который можно использовать для записи и чтения стандартными для С способами
используется функция page_address.

Функция __get_free_pages так же как alloc_pages выделяет 2 в степени order страниц памяти, только она возвращает не указатель на struct page, а адрес в виртуальной памяти. Это все
равно что вызвать сначала alloc_pages, а потом page_address__get_free_pageделает тоже самое но с 1-ой страницой памяти, а вызов get_zeroed_page еще и обнуляет эту страницу.

Для освобождения выделенных страниц памяти нужно использовать функции:

 

void __free_pages(struct page *page, unsigned int order);
 
void free_pages(unsigned long addr, unsigned int order);
 
void free_page(unsigned long addr);

 

Думаю не стоит упоминать что нужно быть крайне осторожным при работе с памятью в ядре, т.к. это может привести к потере данных или зависанию ядра(что тоже приведет к потере данных, но других).

 

Описанные функции полезны когда нужно выделить одну или две смежные страницы памяти, в остальных же случаях лучше воспользоваться более высокоуровневыми функциями.

Reverse Engineering для начинающих

reverse-engineeringЛистал тут Hacker News и наткнулся на пост про бесплатную книгу по реверс инжинирингу для начинающих, приятным сюрпризом оказалось то, что автор русскоязычный и книгу можно читать на русском.

Вот ссылка на github этой книги, а вот ссылка на сайт автора этой книги.

 

Пост про книгу только увидел и естественно что всю я ее не читал, но по оглавлению и тем 10 страницам что я успел прочитал, могу сделать вывод что книга стоящая, а стоящие книги по программированию и всему что  с ним связано на русском языке появляются не так часто и зачастую стоят немалых денег, а тут бесплатно! Автор заслуживает уважения и поощрения. Что меня особенно впечатлило, так это то что автор рассматривает примеры не только для x86 архитектуры процессоров, но и для процессоров ARM.

Test-Driven Development

 

 tdd-circle-of-life

Что такое Test-Driven Development?

Test-Driven Development — это техника постепенной разработки программного обеспечения, основанная на утверждении, что разработка ПО начинается с написания тестов. TDD практикует выражение желаемого поведения кода через тесты. Автоматизация этого процесса является ключом к TDD. Шаг TDD — это написание теста с последующим написанием кода, удовлетворяющего требованиям теста. Каждое изменение кода приводит к запуску тестов, проверяющих как новый так и старый функционал программного обеспечения.

Для TDD важно чтобы тесты всегда шли раньше кода, тесты описывают желаемое поведение системы, являясь при этом еще и документацией, а так же написание тестов помогает создавать модульный код.  Рабочий цикл описан картинкой вверху поста:

  1. написать провальный тест(состояние кода — красное)
  2. написать код проходящий новый тест и не ломающий старые(другие уже существующие) тесты(состояние кода — зеленое)
  3. отрефакторить код(состояние кода — желтое)
  4. повторить

 

Наличие тестов развязывает разработчику руки, позволяя со спокойной душой заниматься рефакторингом. Разработка через тестирование достаточно успешно применяется во многих областяхязыках программирования, кроме встраиваемого программирования. Тут это техника распространена очень плохо, хотя польза от нее именно в этой области может быть существенна. Понимание этого пришло в процессе разработки и выпуска девайсов, мне как разработчику хотелось бы иметь удобный способ проверки последних изменений, а так же быстрый фидбек о влиянии последних изменений на другие части системы. В данный момент читаю книгу Test Driven Development for Embedded C, надеюсь что скоро ее дочитаю  и сделаю заметку.

Иcпользуем CryptoApi ядра Linux для шифрования

TuxПоиск хороших примеров работы с cryptoapi ядра Linux, ни к чему хорошему не привел. Потому потратив вечерок на изучение исходников ядра решил закрепить материал, может кому-то пригодится.

В сети достаточно примеров по использованию Crypto API для хеширования данных и совсем немного(в основном на stackoverflow) по шифрованию данных, хороший, рабочий пример пришлось собирать по кусочкам. Итак начнем.

Первое что нам понадобится это включить заголовочные файлы:

#include <linux/crypto.h>
#include <linux/scatterlist.h>

А файл crypto.h я бы настоятельно рекомендовал открыть и заглядывать в случае возникновения вопросов. Теперь мы можем инициализировать необходимые нам структуры:

struct crypto_blkcipher * blk;
struct blkcipher_desc desc;
struct scatterlist sg[2];

Основная структура здесь это crypto_blkcipher которая является контекстом шифратора, в то время как blkcipher_desc — всего лишь обертка надcrypto_blkcipher , в чем мы убедимся дальше. Структура scatterlist используется для работы с буферами данных. Далее мы инициализируем crypto_blkcipherнужным нам алгоритмом, в данном случае алгоритм шифрования AES в режимеXTS.

blk = crypto_alloc_blkcipher("xts(aes)", 0, 0);
if ( IS_ERR(blk) )
{
	printk(KERN_ALERT "Failed to initialize AES cipher. n");
	return -1;
}

После инициализации контекста можно установить ключ и вектор инициализации.

crypto_blkcipher_setkey(blk, key, key_len);

crypto_blkcipher_set_iv(blk, iv, iv_len);

Входной и выходной буфер функция шифрования принимает в формате struct scatterlist, которые мы сейчас и инициализируем:

sg_init_one(&sg[0],src,src_len);

sg_init_one(&sg[1],dst,dst_len);

Дело осталось за малым, инициализировать дескриптор и зашифровать данные:

desc.tfm = blk;
desc.flags = 0;

if(crypto_blkcipher_encrypt(&desc, &sg[1], &sg[0], src_len)) {
    printk(KERN_ALERT "Failed to encrypt. n");
}

 

Готовый для сборки пример можно найти на github.