...

суббота, 17 июня 2017 г.

Что может пойти не так на сайте метапоисковика и что с этим делать? Часть 2

Метапоисковик — это не так просто, как кажется. Почему мы не можем подгружать сразу все туры? Почему так часто меняется цена? Кто виноват, когда тур “ушёл”, и как выкрутиться перед клиентом?

Об этих и других проблемах и багах сайта Travelata.ru я рассказал в первой части. Продолжаем публичную порку самих же себя.

Отельная страница


Когда открывается серп, направляется запрос в кеш, который хранится от 15 минут до нескольких часов. В момент открытия отельной страницы, если на отель из кеша пришло менее 3 туров, к ТО отправляется новый запрос на поиск по названию отеля. И могут приехать новые цены. Актуальные.

Почему вообще приходится так часто отправлять запросы к ТО?

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

Вторая причина — туры просто быстро раскупают.

image

Самые большие скачки цен на динамический пакетах (не путать с динамическим ценообразованием из абзаца выше). Динамические пакеты — это туры “отель, билет и иногда трансфер”. Туроператор собирает их самостоятельно, подключаясь к GDS в момент стороннего запроса. Схема такова: мы стучимся к ТО, ТО обращается к GDS, забирая стоимость авиабилета (это всегда регулярный рейс) и одновременно к своей базе отелей и трансферов. И только потом складывается финальная цена. Такие туры могут оказаться выгоднее готовых чартерных пакетов, если у ТО есть договорённость с авиакомпанией или отелем и спецтарифы.

Всё это очень сложно донести до пользователя, отсюда много негатива. Клиенты думают, что мы сами специально накручиваем цены, но у нас нет маркапов. Все цены практически идентичны туроператорским, а зарабатываем мы на комиссии от ТО, как и обычное турагентство.

Какие тут могут возникать проблемы?

  • Не приехал тур.

У того же Букинга есть прямые контакты с отельерами и база с ценами. А наша сборка выдачи больше напоминает метапоиск. Мы отправляем поставщикам цен асинхронные запросы и зависим от того, что отдаст каждый из них. Если ТО тормозят, то и инвентаря не будет. Поэтому клиентам выдача иногда кажется непредсказуемой — в 12 дня увидел одни предложения, через 3 часа приехали другие. Ну или просто желаемый тур уже купили :)
  • Мало информации об отеле.

Также на странице отеля клиенты рассчитывают найти всю информацию об отеле. То есть вообще всю. Например, многим важно наличие бесплатного вайфая в номере. Но сегодня в отеле может быть бесплатный интернет, а завтра за него начнут брать плату — отслеживать такие вещи слишком энергозатратно. Для таких специфических вопросов мы подключили возможность написать менеджеру в мессенджерах. Это гораздо удобнее живых чатов на сайте. Вся переписка сохраняется в телефоне, и к ней можно вернуться в любой момент.

Мы не контентный проект. Но все равно стараемся наполнять карточки отелей по максимуму. Подключили отзывы с TopHotels — клиенты говорят, очень удобно.

image

Чекаут


Пользователь выбрал тур, происходит финальная актуализация цены. Мы отправляем запрос ТО на этот тур, проверяем места в отеле и на рейс и приносим всю дополнительную информацию по перелетам и доплатам.

Что может пойти не так?

1. Подгрузился топливный сбор, и цена выросла

Топливный сбор — это такое эфемерное понятие. Кто-то называет его уловкой туроператоров, но бОльшая часть туристов вообще не понимают, что это и для чего он нужен. Тем не менее, он есть и чаще всего на популярные направления. Составляет от $40 до $80 в зависимости от авиакомпании. Нет строгого правила в какую страну и по какому ТО есть топливо, а куда нет. По каждому туру расчёт индивидуален. Но вот у Библио-Глобуса топливного сбора нет ни на одном направлении. Возможно он уже зашит в цену и с ней и передаётся к нам.

2. Топливный сбор не подтянулся, но он есть

Чтобы получить данные по топливу, нужно прокинуть запрос в личный кабинет туроператора на конкретный тур. Иногда ТО может не отдать критерии, и цена не подъезжает. Бывают случаи, когда в ЛК что-то поменяли, и парсер отвалился. Подключаемся, фиксим.

Почему нельзя добавить топливный сбор сразу, в цены на серпе?

Теоретически это возможно. Но конкуренты тоже показывают цены на туры без учёта топлива. Есть риск, что люди увидят более высокие цены и уйдут от нас. И в целом будет складываться впечатление дорогих туров. Обязательно попробуем выводить топливо сразу, но в качестве эксперимента.

3. Не прошла актуализация по туру (неверная цена и информация о перелёте)

ТО может не ответить на запрос, и вся информация не актуализируется. То есть если вы не видите данных о перелётах (время вылета, аэропорт), то скорее всего цена тоже не обновилась. В таком случае вернитесь на страницу отеля и сузьте запрос. Например, если вас интересует тур на 7 ночей, а в поиске выставлен фильтр на 6-10 ночей, проставьте точно на 7 ночей и запустите поиск. Если не сработает, вернитесь к этому туру через несколько минут.

В среднем уровень успешных ответов на актуализацию составляет 92%. Когда падает до 90%, разбираемся с ТО и отключаем его, пока не пофиксят баги.

На регулярные рейсы запрос может быть более долгим. Если за 1 минуту ТО не ответил, то актуализации не происходит.

Что говорим клиенту?

При любой ошибке, если оплата от клиента прошла, менеджер связывается с ним и сообщает, что цена на тур изменилась по не зависящим от нас причинам. Менеджер предлагает переподбор, и если клиент отказывается, деньги на карте размораживаются. Если турист негативит, дарим скидку на следующий тур. В 70% случаев клиенты успокаиваются и соглашаются.

Оплата


Мы не можем бронировать туры без оплаты. Это гарантия, что клиент серьёзно настроен на покупку. К тому же после аннуляции брони по турам вступают штрафные санкции, и ТО перекладывают их на нас. При этом мы позволяем забронировать тур с минимальной информацией — обязательны только поля со звёздочками, остальное можно заполнить потом. Это удобно, если тур горящий, и может “уйти” в любой момент.

image

При этом мы не списываем деньги с карты клиента, а замораживаем их. Если произойдёт какая-либо ошибка, деньги моментально размораживаются и можно забронировать другой тур.

1. На этапе оплаты могут произойти проблемы с банком. На стороне банка, естественно.

Мы используем систему 3D Secure — это когда по SMS отправляется код подтверждения оплаты. Однако не все банки отправляют SMS для 3DS-аутентификации и не ко всем картам подключена 3DS. Отменить эту систему мы не можем, она позволяет избежать фрода.

Когда мы холдируем деньги, от большинства банков приходит SMS клиенту о том, что деньги списаны. Некоторые банки присылают SMS о том, что деньги заморожены. А бывают и такие случаи, когда приходит смс о заморозке и за ней — и списании. Это редкий случай, но клиенты пугаются, что с них 2 раза сняли деньги. Приходится успокаивать.

2. Иногда возникает ошибка в процессе оплаты. В таком случае мы просим пользователя оплатить тур другой картой, позвонить в банк или попробовать оплатить тур позднее.

После оплаты


1. Оплаченный тур может не подтвердить туроператор

Есть туры на моментальном бронировании — это значит, что за туроператором закреплена определённая квота по номерам и билетам на самолёт, причём уже проплаченная. Другие туры подтверждаются реже — обычно у ТО есть договорённость с этими отелями и авиакомпаниями, но сами места на проплачены. Но это не гарантирует наличия мест на определённые даты. Поэтому туроператору приходится обращаться к партнёрам за подтверждением.

Туры под запрос на Травелате помечены значком. Можно эти туры отфильтровать и не показывать в поисковой выдаче. Туры под запрос подтверждаются примерно в 60% случаев, не под запрос — в 90%.

image

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

Если тур не подтверждают, а на переподбор клиент не согласился, мы размораживаем деньги. Обычно это процесс моментальный, но могут произойти проблемы на стороне банка. Тогда обращайтесь в банк.

2. Рейс перенесли

Здесь ничего не поделаешь. Это особенность чартерных рейсов, которая чётко прописана в договоре. У нас на чекауте есть услуга конкретизации рейса, которая фиксирует номер чартерного рейса. Без этой услуги туриста могут пересадить на любой рейс с вероятностью 20-30%. С конкретизацией рейс зафиксирован, он практически приравнивется к “регулярке”. Хотя и может задержаться, как и любой другой перелёт.

Как справиться с недоверием?

Новый клиент очень аккуратен, он боится платить, боится неправильно заполнить паспортные данные. Что уж говорить об оплате… В таких случаях опять же поможет чат в мессенджерах.

Поставщик и посредник — кто кого вытягивает?

Технические возможности ТО постепенно улучшаются. К примеру, 5 лет назад ни у кого не было API, приходилось парсить данные из личных кабинетов. Сейчас API есть почти у всех, даже не самых крупных ТО.

Сильно выросла онлайн нагрузка на сервера ТО. Раньше у них на сайтах сидели только турагенты и искали точечные запросы. Сейчас в онлайн с ними работают такие игроки как мы, от нас уходит несколько миллионов запросов в день.

Наши отношения с туроператорами — это взаимовыгодное сотрудничество. Для них IT — не основная специализация. Около 95% всех туров до сих пор продаются в офлайне.

Пускай ТО создают хороший продукт. А мы своим фидбеком поможем им развиваться в онлайне. Если у них возникли какие-то проблемы, мы просто отключим выдачу по одной из стран или полностью отключим на время. Часто мы находим баги у ТО быстрее, чем они. Поэтому все в выигрыше.

Комментарии (0)

    Let's block ads! (Why?)

    Повседневный C++: изолируем API в стиле C

    Лучше один раз услышать чем семь раз прочитать

    [Перевод] Как добиться того, чтобы обучение в играх не раздражало

    Security Week 24: 95 фиксов от Microsoft, роутер сливает данные светодиодами, для MacOS появился рансомвар-сервис

    Тестируем возможности ARKit. Создаем игру с дополненной реальностью

    image


    На WWDC 2017 Apple анонсировала ARKit — SDK для работы с дополненной реальностью. Благодаря ему порог вхождения в эту технологию стал значительно ниже. Можно ожидать появления большого количества качественных игр и приложений.


    Если вы смотрели Keynote, то, вероятно, вы уже в восторге от увиденного. Игровой мир, который инженеры Apple смогли развернуть на обычном столе при помощи ARKit, не может оставить равнодушными даже самых искушенных геймеров. Это был не просто прототип, а хорошо работающая технология, над которой действительно потрудились. В этом легко убедиться, запустив несколько демо или попробовав самим привнести что-либо виртуальное в наш мир.


    Вынужден расстроить счастливых обладателей iPhone 6 и ниже. На данных девайсах все эти прелести жизни будут недоступны. Для использования всех ключевых функций ARKit необходим процессор А9 и выше. Apple, конечно, даст урезанный доступ к функциональности, но это уже совсем не то.


    Дополненная реальность


    Дополненная реальность (augmented reality, AR) — это виртуальная среда, которая накладывается на реальный мир для придания ему большей выразительности, информативности или просто ради развлечения. Термин, предположительно, был предложен исследователем компании Boeing Томасом Коделлом еще в 1990 году. Уже тогда начали появляться первые примеры устройств с применением данной технологии. Впервые дополненная реальность была реализована на электронных шлемах летчиков для вывода информации о полете и радаре.


    Хочется спросить, чем же все занимались почти 20 лет и почему масштабное развитие эта технология получила лишь сейчас. Все предельно просто. Появление хороших камер в телефонах, сенсоров и развитие технологий компьютерного зрения сделали это возможным.


    Что же можно сделать полезного и чего ждать в ближайшее время на полках AppStore? На самом деле все ограничивается лишь фантазией разработчиков. Можно с уверенностью назвать несколько отраслей, где AR произведет революцию с выходом нового фреймворка от Apple:


    • Игровая индустрия;
    • Архитектура;
    • Киноиндустрия.

    Возможности ARKit


    ARKit — не волшебная палочка Гарри Поттера, а инструмент, который умеет грамотно обрабатывать большое количество данных, полученных от устройства. Благодаря камере и датчикам движения фреймворк отслеживает движение, находит поверхности и определяет освещенность. После анализа данных мы получаем конкретное представление об окружающем мире в виде точек пересечения, координат поверхностей и положении камеры в пространстве.


    Основой задачей ARKit является слежение за окружающим миром (World Tracking) для создания виртуальной модели реального мира. Фреймворк распознает особенности видеокадров, отслеживает изменения их положения и сравнивает эту информацию с данными от датчиков движения. Результатом является виртуальная модель реального мира. Отдельная возможность — распознавание плоских горизонтальных поверхностей. ARKit находит плоскости и сообщает об их расположении и размерах.


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


    Основой ARKit являются ARSCNView и ARSKView. Они служат для отображения live видео и рендеринга 3D и 2D изображений. Как все уже догадались, это наследники от SCNView и SKView. Следовательно, ARKit не привносит каких-то невероятных особенностей в отображении данных. Это все те же движки для работы с 2D и 3D графикой, с которыми уже все знакомы. Поэтому порог вхождения в данную технологию будет достаточно низким. Apple знаменита любовью к своим технологиям и продуктам, но несмотря на это разработчики ARKit сделали поддержку Unity и Unreal Engine. Это положительно скажется на количестве качественных приложений, которые появятся в ближайшее время.


    ARSCNView и ARSKView содержат в себе сердце ARKit — ARSession. Именно этот класс содержит в себе все необходимое для работы с дополненной реальностью. Для запуска ARSession необходимо передать конфигурацию работы сессии.


    Тип конфигурации определяет стиль и качество работы AR, которое может быть достигнуто:


    • На девайсах с процессором A9 и новее можно использовать ARWorldTrackingSessionConfiguration. Именно эта конфигурация дает возможность воспользоваться всей мощью нового фреймворка. Для вас будет создана модель окружающего мира в виртуальной реальности и предоставлена информация о плоскостях в поле видимости камеры. Это поможет расположить виртуальные объекты с максимальной точностью.
    • На остальных девайсах, поддерживающих ARKit, будет доступна лишь ARSessionConfiguration. Базовый класс предоставляет только информацию о движении устройства в пространстве, но не строит виртуальных моделей. Это не даст необходимого эффекта и не позволит насладиться всем качеством новой технологии. Вам будет недоступна возможность фиксации виртуальных объектов относительно объектов реального мира.

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


    override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            // Create a session configuration
            let configuration = ARWorldTrackingSessionConfiguration()
    
            // Run the view's session
            sceneView.session.run(configuration)
    }
    

    Важно помнить, что ARKit потребляет довольно много энергии для расчетов. Если View с контентом не отображается в данный момент на экране, то имеет смысл приостановить сессию на это время, используя session.pause().


    После запуска сессии можно начинать работать с виртуальным контентом. Если хотите, чтобы ARKit распознавал плоскости, не забудьте установить значение planeDetection у конфигурации в значение horizontal. Изначально распознавание горизонтальных поверхностей выключено. Будем надеяться, что в будущем появится возможность находить и вертикальные поверхности, но пока только горизонтальные.


    Способ получения информации об окружающей среде зависит от того, какой вид отображения данных вы будете использовать ARSCNView, ARSKView или Metal. Единицей информации, которую предоставляет ARKit, является ARAnchor. Если у вас включено распознавание поверхностей, то вы столкнетесь с сабклассом ARPlaneAnchor. Он содержит в себе информацию о найденных плоскостях. Благодаря данным якорям есть возможность ориентироваться в пространстве. В случае использования Metal вам придется вручную заниматься рендерингом. Тогда можете подписаться на обновления, используя делегат ARSessionDelegate у класса ARSession, и получать якоря от сессии. Если используете один из Apple движков для рендеринга объектов, тогда есть возможность воспользоваться более удобными делегатами ARSCNViewDelegate или ARSKViewDelegate.


    На первый взгляд все довольно просто. Почти всю сложную работу делает ARSession. Давайте попробуем сделать тестовое приложение.


    Тестируем возможности ARKit


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


    Для создания тестового приложения мы воспользуемся ARSCNView для создания и рендеринга 3D моделей. Наша игра будет состоять из 2 этапов. Сначала мы будем расставлять мишени по комнате, а после пытаться как можно быстрее попасть по ним всем. Игра довольна примитивна, но продемонстрирует простоту создания игр с дополненной реальностью.


    Начнем с того, что растянем на весь ViewController ARSCNView и создадим IBOutlet. Далее будем работать с ней, как с обычной SCNView. Произведем первоначальную настройку. Сделаем контроллер делегатом контактов физического мира и выведем статистику. Настроим запуск и паузу сессии при появлении и скрытии контроллера.


    override func viewDidLoad() {
            super.viewDidLoad()
    
            sceneView.scene.physicsWorld.contactDelegate = self
    
            // Show statistics such as fps and timing information
            sceneView.showsStatistics = true
    }
    
    override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            // Create a session configuration
            let configuration = ARSessionConfiguration.isSupported ? 
    ARWorldTrackingSessionConfiguration() : ARSessionConfiguration()
    
            // Run the view's session
            sceneView.session.run(configuration)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
    
            // Pause the view's session
            sceneView.session.pause()
    }
    

    Выбор конфигурации сессии зависит от модели устройства, на котором запустили приложение. Крайне важно сделать эту проверку. Иначе, в случае неверной конфигурации, сессия пришлет ошибку и игра не запустится вообще.


    ARKit настолько прост, что мы больше не будем использовать никакие его настройки. Единственное, что еще понадобится — это расположение камеры в пространстве виртуального мира. Остальное — дело техники и немного SceneKit.


    Мы не будем здесь описывать обработку нажатий или подсчет очков. Это не так важно, и вы можете это увидеть сами в ДЕМО, представленном в конце статьи.


    Наша игра содержит две модели объектов: шарик, которым мы будем стрелять, и летающие логотипы Touch Instinct. Для добавления этих моделей на экран, необходимо создать их, используя SCNNode.


    Что понадобится, чтобы создать физический объект:


    • задать фигуру определенного размера;
    • создать фигуру с физическими свойствами для контролирования контактов с другими объектам;
    • создать физическое тело для описания поведения объекта при соприкосновении;
    • задать текстуры.

    Пример реализации классов патрона в виде шара и логотипа в виде куба с нужными текстурами.


    class ARBullet: SCNNode {
    
        override init() {
            super.init()
    
            let arKitBox = SCNSphere(radius: 0.025)
            self.geometry = arKitBox
            let shape = SCNPhysicsShape(geometry: arKitBox, options: nil)
            self.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
            self.physicsBody?.isAffectedByGravity = false
    
            self.physicsBody?.categoryBitMask = CollisionCategory.arBullets.rawValue
            self.physicsBody?.contactTestBitMask = CollisionCategory.logos.rawValue
    
            // add texture
            let material = SCNMaterial()
            material.diffuse.contents = UIImage(named: "art.scnassets/ARKit_logo.png")
            self.geometry?.materials  = [material]
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    

    class Logo: SCNNode {
    
        override init() {
            super.init()
    
            let logo = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
            self.geometry = logo
            let shape = SCNPhysicsShape(geometry: logo, options: nil)
    
            self.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
            self.physicsBody?.isAffectedByGravity = false
    
            self.physicsBody?.categoryBitMask = CollisionCategory.logos.rawValue
            self.physicsBody?.contactTestBitMask = CollisionCategory.arBullets.rawValue
    
            // add texture
            let material = SCNMaterial()
            material.diffuse.contents = UIImage(named: "art.scnassets/logo-mobile.png")
            self.geometry?.materials  = Array(repeating: material, count: 6)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    

    Хочется обратить внимание на CollisionCategory. Это структура используется для определения типа объекта при контакте.


    struct CollisionCategory: OptionSet {
        let rawValue: Int
    
        static let arBullets  = CollisionCategory(rawValue: 1 << 0)
        static let logos = CollisionCategory(rawValue: 1 << 1)
    }
    

    Это стандартная тактика для определения контакта. Свойство categoryBitMask задает маску конкретного объекта, а contactTestBitMask настраивает все контакты, которые нам будут интересны и о которых мы хотим получать уведомления.


    Раз мы заговорили про обработку контактов, давайте посмотрим, как это выглядит в контроллере. Во viewDidLoad мы уже подписались на события контактов физического мира. Осталось реализовать одну функцию.


    extension ViewController: SCNPhysicsContactDelegate {
    
        func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
            guard let nodeABitMask = contact.nodeA.physicsBody?.categoryBitMask,
                let nodeBBitMask = contact.nodeB.physicsBody?.categoryBitMask,
                nodeABitMask & nodeBBitMask == CollisionCategory.logos.rawValue & CollisionCategory.arBullets.rawValue else {
                        return
            }
    
            contact.nodeB.removeFromParentNode()
            logoCount -= 1
    
            if logoCount == 0 {
                DispatchQueue.main.async {
                    self.stopGame()
                }
            }
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
                contact.nodeA.removeFromParentNode()
            })
        }
    
    }
    

    Самое интересное — это первая проверка того, что произошло именно соударение патрона и мишени в виде куба. Она выполняется на основе битовой маски. Это очень удобно и избавляет от большого количества других проверок.


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


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


    private func addLogo() {
            guard let currentFrame = sceneView.session.currentFrame else {
                return
            }
    
            let logo = Logo()
            sceneView.scene.rootNode.addChildNode(logo)
    
            var translation = matrix_identity_float4x4
            translation.columns.3.z = -1
            logo.simdTransform = matrix_multiply(currentFrame.camera.transform, translation)
    
            logoCount += 1
            if logoCount == ViewController.logoMaxCount {
                startGame()
            }
    }
    

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


    private func shoot() {
            let arBullet = ARBullet()
    
            let (direction, position) = cameraVector
            arBullet.position = position
            arBullet.physicsBody?.applyForce(direction, asImpulse: true)
            sceneView.scene.rootNode.addChildNode(arBullet)
    }
    

    Вот так всего за пару десятков строк мы создали простую игру.


    Будущее наступит в сентябре


    Как видите, Apple потрудились на славу. Благодаря новому фреймворку ARKit создание приложений с дополненной реальностью — так же просто, как сделать приложение с несколькими контроллерами. При этом вам не нужно беспокоиться о красивых декорациях. Эта технология точно изменит наше представление о мобильных приложениях.


    Скачивайте новый Xcode 9, и создавайте приложения, которые добавят в наш мир виртуальной магии. Будущее уже здесь. Ну или будет здесь ближе к сентябрю, после очередной презентации Apple.


    Демо проект


    Скачивайте в репозитории Touch Instinct

    Комментарии (0)

      Let's block ads! (Why?)

      [Перевод] Сокращаем использование Redux кода с помощью React Apollo

      Неглубокое погружение или восстановление данных с жесткого диска после затопления офиса

      пятница, 16 июня 2017 г.

      Как пропатчить K̶D̶E̶ TCP-стек под FreeBSD

      Когда стоит вопрос выбора между проприетарным и открытым программным обеспечением, часто в пользу последнего приводят следующий аргумент: в случае необходимости всегда можно взять исходники и поправить их под себя, или исправить ошибку прямо сейчас, а не дожидаясь месяцами реакции от вендора. На самом деле этот аргумент весьма умозрительный — ну право же, кто в здравом уме возьмется за оптимизацию sql-планировщика, когда проще исправить сам sql запрос. Равно как и вряд-ли кто-то начнет искать и исправлять проблему в драйвере, когда быстрее и проще просто сменить железку. Баг-репорт отписать и то не всякий возьмется… Тем не менее, бывают случаи, когда именно наличие открытого кода позволяет избежать потенциальных убытков в случае возникновения непредвиденных проблем. Об одном из таких я и хочу сейчас расказать.

      Этот вечер пятницы не предвещал никаких проблем. Впереди были очередные выходные, планов особых не было, предполагалось спокойно отдохнуть ;) Но реальные события оказались куда интереснее предполагаемых…

      Первый звоночек зазвенел в субботу поздним вечером. Я уже спал, но пришлось подниматься и, чертыхаясь, идти разбираться, почему слег один из важных серверов. Их в кластере стояло 3+3 штук, и каждый тянет всю нагрузку своей тройки, так что выпадение одного ничем сервису не грозило. Но все же было крайне неприятно осознавать тот факт, что сервера, доселе спокойно принимающие суммарный входящий трафик в 10+К http-запросов в секунду, и имевшие (как казалось) ещё несколько-кратный запас по производительности, вдруг оказались не такими уж и устойчивыми. Что же, пока ребилдился raid1 и постгрес догонял репликацию, было время посмотреть на остальные сервера.

      Стоит заранее объяснить, как устроен данный кластер. Сервера стоят в разных местах, два в Европе и четыре в USA. Они разбиты на тройки, каждая обслуживает свою группу IP (т.е. для каждой тройки один сервер в Европе и остальные два в USA). Трафик распределяется средствами anycast — на всех серверах тройки прописаны одни и те же IP, и поднята BGP-сессия с роутером. Если один сервер ложится, то его роутер перестает анонсировать его сеть в Internet и трафик автоматически уходит на оставшиеся сервера.

      Смотреть особо было не на что. По данным мониторилки непосредственно перед падением был сильный всплеск входящего и исходящего трафика на оба европейских сервера (один из них и слег), причем если бэндвич вырос просто в два раза, то кол-во пакетов в секунду стало бо ́льшим уже раз в десять, причем в обе стороны. Т.е. пакеты были мелкие, и их было много (под 200к в секунду). На highload сервисах трафик просто-так сам по себе не меняется, а тут ещё и в таких размерах… Очень похоже на DDOS, не так ли? Не сказать что я сильно удивился, DDOS-ов разных видов мне пришлось повидать немало, и пока что, если сетевое оборудование у провайдеров позволяло доставлять трафик без потерь на сервера, их все удавалось успешно блокировать. Удивляло, правда, то, что всплеск трафика был только на европейских серверах, ведь если ботнет распределенный, то и трафик должен быть тоже распределен на весь кластер.

      После ввода сервера в строй я запустил `top`, `nload` и стал мониторить загрузку. Ждать пришлось недолго, трафик скоро опять поднялся в два раза и ssh сессия начала ощутимо лагать. Налицо потери пакетов, `mtr -ni 0.1 8.8.8.8` данную гипотезу сразу и подтвердил, а `top -SH` указал, что дело именно в ядре ОС — обработчику входящих сетевых пакетов не хватает CPU. Что же, теперь понятно, почему завис сервер — потери пакетов ему смерти подобны:

      У FreeBSD есть одна весьма неприятная особенность в сетевом стеке — он плохо масштабируется относительно кол-ва TCP-сессий :(. Увеличение кол-ва TCP-сессий в несколько раз приводит к непропорционально большему потреблению CPU. Пока сессий немного, то проблем нет, но начиная с нескольких десятков тысяч активных TCP-сессий обработчик входящих пакетов начинает испытывать нехватку CPU и ему приходится дропать пакеты. А это приводит к цепной реакции — из-за потерь пакетов активные TCP-сессии начинают медленно обслуживаться, их кол-во начинает немедленно расти, а с ним растет и нехватка CPU и уровень потерь пакетов.

      Пока сервер окончательно не завис, срочно тушу BGP-сессию, и параллельно запускаю проверку потерь пакетов на том сервере, что принял на себя европейский трафик. Он имеет несколько более мощное железо — есть шансы, что в Штатах ничего плохого не случится. Но с проблемным сервером надо срочно решать. Первым делом выключаю keep-alive — TCP-сессии начнут завершаться раньше и в сумме их будет уже меньше. Тюнинг настроек сетевой карты занял не один десяток минут, проверяя наличие потерь пакетов каждый раз кратковременным поднятием BGP-сессии — пришлось оставить polling режим, но активировать idlepoll — теперь одно ядро процессора было занято исключительно сетевой картой, но зато потери пакетов прекратились.

      Оставались еще непонятные моменты — например, кол-во TCP-сессий во время атаки и в обычном рабочем режиме практически не отличалось. Но вот что было совершенно непонятно, так это почему на штатовских серверах этой атаки не было видно вообще! Во время отключения европейских серверов на штатовские приходил только актуальный рабочий трафик, но не было никакого дополнительного! Хотя после возвращения трафика в Европу он какое-то время держался на рабочем уровне, а потом начинался очередной всплеск.

      Время было за час ночи, потери пакетов, кажется, удалось прекратить, а с этими сетевыми странностями можно разбираться и на свежую голову. С такими мыслями я отправился обратно спать, хотя выспаться мне в ту ночь было не суждено. Через пару часов меня опять разбудили — в этот раз лежали уже оба европейских сервера ;(. Что добавило очередную странность в копилку — ведь время уже было позднее, и пик трафика был давно позади. Хотя, как для DDOS-атаки так вполне нормально, ведь большинство специалистов спит и заниматься атакой есть мало кому ). Оба сервера были вскоре запущены, но последующий мониторинг ситуации ничего нового не дал — атака в тот день больше не повторялась.

      В воскресенье пришлось немного поработать ). Отдельный скрипт уже мониторил кол-во TCP-сессий и временно снимал трафик (т.е. переводил его в Штаты) в случае повышенной нагрузки, что уменьшало полученный ущерб. Пока что штатовские сервера работали без проблем, но все же надо было разобраться с этим трафиком и научиться его блокировать. В http-логах никаких аномалий не было, netstat и подобные утилиты тоже ничего подозрительного не показывали. Но раз мы видим повышение трафика на сетевой карте, то с этим уже можно работать, а на помощь придет верный tcpdump )

      Пролистывать тонны дампов сетевых пакетов бывает непросто, но в этот раз долго искать не пришлось — среди обычного HTTP/HTTPS обмена было видно аномально много пустых TCP-пакетов, т.е. легальных пакетов с корректными IP и TCP заголовками, но без данных. При выключеном keep-alive пустых пакетов и так немало — три пустых на установление соединения, потом два пакета обмена данными, и потом опять пустые пакеты закрывают соединения. Ну и для HTTPS у нас ещё есть пакеты с данными для установки TLS-сессии. Но вот сейчас в дампе регулярно видны интенсивные обмены пустыми пакетами:
      13:48:20.229921 IP 103.248.114.6.49467 > 88.208.9.69.80: Flags [.], ack 1, win 0, length 0
      13:48:20.229925 IP 88.208.9.69.80 > 103.248.114.6.49467: Flags [.], ack 4294966738, win 8400, length 0
      13:48:20.229927 IP 103.248.114.6.49467 > 88.208.9.69.80: Flags [.], ack 1, win 0, length 0
      13:48:20.229931 IP 88.208.9.69.80 > 103.248.114.6.49467: Flags [.], ack 4294966738, win 8400, length 0
      13:48:20.229933 IP 103.248.114.6.49467 > 88.208.9.69.80: Flags [.], ack 1, win 0, length 0
      13:48:20.229937 IP 88.208.9.69.80 > 103.248.114.6.49467: Flags [.], ack 4294966738, win 8400, length 0
      13:48:20.229939 IP 103.248.114.6.49467 > 88.208.9.69.80: Flags [.], ack 1, win 0, length 0

      Выборочная проверка (`tcpdump -nc 1000 host 103.248.114.6 and tcp port 49467`) отдельных TCP-сессий показала, что таки да, по некоторым сессиям происходит очень интенсивный обмен пустыми TCP-пакетами. Причем почти все эти сессии были родом из Индии! Было и немного Саудовской Аравии с Кувейтом. Сложно сказать, что это за такой хитрый ботнет, да пока и не до этого. Пишу второй несложный скрипт, который каждую секунду запускает tcpdump на 30к пакетов и ищет среди них сессии, в которых кол-во последовательных обменов пустыми пакетами превышает указанный лимит, найденные IP немедленно блокируются. Результат не заставил себя ждать — при блокировке только пяти IP трафик тут же падает в два раза. Каждую минуту блокировался ещё один-два новых IP. Победа! ))

      В понедельник с коллегами обсудили эту проблему, оказалось что все не так радужно (. Во первых, интенсивность блокировки новых IP нарастала — уже не в пик трафика скорость блокировки доходила до нескольких десятков штук в минуту. Во вторых, затронутыми оказались не только эти сервера, но и много других. Что характерно, все в Европе и все на FreeBSD. Стало ясно, что это никакая не DDOS-атака. Вот только что же это?…

      Пока суть да дело, а заблокированные IP надо освободить. Вместо блокировки теперь дропались сами TCP-сессии (во FreeBSD для этого есть утилита tcpdrop). Это так же эффективно удерживало нагрузку под контролем. Заодно и keep-alive можно включить.

      Пришлось опять брать в руки tcpdump и смотреть трафик дальше. Не буду детально описывать те часы, которые были потрачены на поиск аномалий и закономерностей, рассказ и так уже весьма затянулся ). TCP-сессии были разные. Были и полностью пустые:

      dump1
      06:07:58.753852 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [S], seq 3258188889, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
      06:07:58.753868 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [S.], seq 2165986257, ack 3258188890, win 8192, options [mss 1452,nop,wscale 6,sackOK,eol], length 0
      06:07:58.906312 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [S], seq 3258188889, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
      06:07:58.906327 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [S.], seq 2165986257, ack 3258188890, win 8192, options [mss 1452,nop,wscale 6,sackOK,eol], length 0
      06:07:59.059091 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [S], seq 3258188889, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
      06:07:59.059103 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [S.], seq 2165986257, ack 3258188890, win 8192, options [mss 1452,nop,wscale 6,sackOK,eol], length 0
      06:07:59.112677 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, length 0
      06:07:59.161950 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, options [nop,nop,sack 1 {0:1}], length 0
      06:07:59.269749 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, length 0
      06:07:59.313826 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, options [nop,nop,sack 1 {0:1}], length 0
      06:08:09.313764 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [.], ack 1, win 136, length 0
      06:08:09.569443 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, length 0
      06:08:09.678113 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [F.], seq 1, ack 1, win 260, length 0
      06:08:09.678132 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [.], ack 2, win 136, length 0
      06:08:09.678206 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [F.], seq 1, ack 2, win 136, length 0
      06:08:09.720977 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, length 0
      06:08:09.872479 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, length 0
      06:08:09.932997 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 2, win 260, length 0
      06:08:10.024179 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 1, win 260, length 0
      06:08:20.023725 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [.], ack 1, win 8712, length 0
      06:08:20.279407 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 2, win 0, length 0
      06:08:20.279412 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [.], ack 1, win 8712, length 0
      06:08:20.430575 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 2, win 0, length 0
      06:08:20.430581 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [.], ack 1, win 8712, length 0
      06:08:20.534901 IP 122.167.126.199.56698 > 88.208.9.8.80: Flags [.], ack 2, win 0, length 0
      06:08:20.534908 IP 88.208.9.8.80 > 122.167.126.199.56698: Flags [.], ack 1, win 8712, length 0


      а были и с обменом данными, которые потом переходили в цикл обмена пустыми пакетами:
      dump2
      06:18:39.046506 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [S], seq 1608423399, win 14600, options [mss 1400,sackOK,TS val 2790685 ecr 0,nop,wscale 6], length 0
      06:18:39.046525 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [S.], seq 3258835787, ack 1608423400, win 8192, options [mss 1400,nop,wscale 6,sackOK,TS val 2982841058 ecr 2790685], length 0
      06:18:39.228192 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 2790704 ecr 2982841058], length 0
      06:18:39.234683 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [P.], seq 1:512, ack 1, win 229, options [nop,nop,TS val 2790704 ecr 2982841058], length 511
      06:18:39.235039 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [P.], seq 1:358, ack 512, win 130, options [nop,nop,TS val 2982841246 ecr 2790704], length 357
      06:18:39.379057 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 2790704 ecr 2982841058], length 0
      06:18:39.385527 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [P.], seq 1:512, ack 1, win 229, options [nop,nop,TS val 2790704 ecr 2982841058], length 511
      06:18:39.408290 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 358, win 274, options [nop,nop,TS val 2790722 ecr 2982841246], length 0
      06:18:39.408304 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 130, options [nop,nop,TS val 2982841420 ecr 2790722], length 0
      06:18:39.408305 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [F.], seq 512, ack 358, win 274, options [nop,nop,TS val 2790722 ecr 2982841246], length 0
      06:18:39.408312 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 513, win 130, options [nop,nop,TS val 2982841420 ecr 2790722], length 0
      06:18:39.408319 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [F.], seq 358, ack 513, win 130, options [nop,nop,TS val 2982841420 ecr 2790722], length 0
      06:18:39.536434 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [P.], seq 1:512, ack 1, win 229, options [nop,nop,TS val 2790704 ecr 2982841058], length 511
      06:18:39.536442 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [F.], seq 358, ack 513, win 130, options [nop,nop,TS val 2982841548 ecr 2790722], length 0
      06:18:39.580158 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790739 ecr 2982841420], length 0
      06:18:39.580167 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790739 ecr 2982841420], length 0
      06:18:39.687698 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [P.], seq 1:512, ack 1, win 229, options [nop,nop,TS val 2790704 ecr 2982841058], length 511
      06:18:39.688031 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [P.], seq 1:358, ack 512, win 138, options [nop,nop,TS val 2982841058 ecr 2790704], length 357
      06:18:39.712200 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790752 ecr 2982841420], length 0
      06:18:39.712204 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 138, options [nop,nop,TS val 2982841083 ecr 2790704], length 0
      06:18:39.882468 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790769 ecr 2982841420], length 0
      06:18:39.882476 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 138, options [nop,nop,TS val 2982841253 ecr 2790704], length 0
      06:18:39.884164 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790769 ecr 2982841420], length 0
      06:18:39.884170 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 138, options [nop,nop,TS val 2982841255 ecr 2790704], length 0
      06:18:39.917773 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [P.], seq 1:358, ack 512, win 138, options [nop,nop,TS val 2982841289 ecr 2790704], length 357
      06:18:40.033516 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790769 ecr 2982841420], length 0
      06:18:40.033525 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 138, options [nop,nop,TS val 2982841404 ecr 2790704], length 0
      06:18:40.035244 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790769 ecr 2982841420], length 0
      06:18:40.035248 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 138, options [nop,nop,TS val 2982841406 ecr 2790704], length 0
      06:18:40.082506 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790789 ecr 2982841420], length 0
      06:18:40.082513 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 138, options [nop,nop,TS val 2982841453 ecr 2790704], length 0
      06:18:40.132575 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790794 ecr 2982841420], length 0
      06:18:40.132583 IP 88.208.9.8.80 > 106.193.154.239.1223: Flags [.], ack 512, win 138, options [nop,nop,TS val 2982841503 ecr 2790704], length 0
      06:18:40.142588 IP 106.193.154.239.1223 > 88.208.9.8.80: Flags [.], ack 359, win 274, options [nop,nop,TS val 2790795 ecr 2982841420], length 0

      Но зацепка все же была. Перед уходом в цикл обмена пустыми пакетами от удаленной стороны приходил FIN пакет (пакет с флагом FIN сигнализирует, что данных больше не будет и сессию надо закрывать), иногда и не один, а бывало и RST пакет (пакет с флагом RST указывает, что сессия уже закрыта и больше не валидна). Что интересно, несмотря на наличие FIN и RST пакетов, потом бывало что на сервер приходили и пакеты с данными. Либо где-то настолько криво реализован TCP-стек, что маловероятно, либо где-то происходит грубое вмешательство в TCP-сессии, а вот это уже вполне вероятно (особенно этим любят баловаться мобильные операторы, не буду показывать пальцем). Вторую версию также подтверждал тот факт, что проверка по http-логу найденных зловредных TCP-сессий показала, что практически во всех них был задействован мобильный браузер, причем как Android, так и iOS.

      Логично было предположить, FIN или RST пакет переводил TCP-сессию в закрытое состояние, в котором TCP-стек просто подтверждал получение пакетов. Было интересно, какое конкретно из TCP-состояний

      tcp_fsm.h
      #define TCP_NSTATES     11
      
      #define TCPS_CLOSED             0       /* closed */
      #define TCPS_LISTEN             1       /* listening for connection */
      #define TCPS_SYN_SENT           2       /* active, have sent syn */
      #define TCPS_SYN_RECEIVED       3       /* have sent and received syn */
      /* states < TCPS_ESTABLISHED are those where connections not established */
      #define TCPS_ESTABLISHED        4       /* established */
      #define TCPS_CLOSE_WAIT         5       /* rcvd fin, waiting for close */
      /* states > TCPS_CLOSE_WAIT are those where user has closed */
      #define TCPS_FIN_WAIT_1         6       /* have closed, sent fin */
      #define TCPS_CLOSING            7       /* closed xchd FIN; await FIN ACK */
      #define TCPS_LAST_ACK           8       /* had fin and close; await FIN ACK */
      /* states > TCPS_CLOSE_WAIT && < TCPS_FIN_WAIT_2 await ACK of FIN */
      #define TCPS_FIN_WAIT_2         9       /* have closed, fin is acked */
      #define TCPS_TIME_WAIT          10      /* in 2*msl quiet wait after close */
      
      
      так себя ведет, и перед вызовом `tcpdrop` я добавил поиск удаляемой TCP-сесии в выводе `netstat -an`. Результат был немного обескураживающим — они все были в состоянии ESTABLISHED! Это уже было сильно похоже на баг — не может закрытая TCP-сессия перейти обратно в состояние ESTABLISHED, не предусмотрен такой вариант. Я немедленно начал проверять исходники и ядра и был обескуражен второй раз:
      tp->t_state = TCPS_ESTABLISHED
      
      в коде вызывается ровно два раза, и оба раза непосредственно перед этим проверяется текущее значение t_state — в одном случае оно равно TCPS_SYN_SENT (сервер отослал SYN пакет и получил подтверждение), а во втором это TCPS_SYN_RECEIVED (сервер получил SYN, отправил SYN/ACK и получил подтверждающий ACK). Вывод из это следует вполне конкретный — FIN и RST пакеты сервером игнорировались, и никакого бага в TCP-стеке нет (по крайней мере, бага с неправильным переходом из одного состояния в другое).

      Все же было непонятно, зачем серверу отвечать на каждый полученный TCP пакет. Обычно в этом нет необходимости, и TCP-стек работает по другому — он принимает несколько пакетов, а потом отсылает одним пакетом подтверждение для всех сразу — так экономнее ). Пролить свет на ситуацию помогло внимательное изучение содержимого пакетов, в частности 32-х битных счетчиков TCP — sequence и acknowledgement. Поведение tcpdump по умолчанию — показывать разницу seq/ack между пакетами вместо абсолютных значений в данном случае сыграло дурную службу :).

      Посмотрим внимательно на абсолютные значения:
      16:03:21.931367 IP (tos 0x28, ttl 47, id 44771, offset 0, flags [DF], proto TCP (6), length 60)
      46.153.19.182.54645 > 88.208.9.111.80: Flags [S], cksum 0x181c (correct), seq 3834615051, win 65535, options [mss 1460,sackOK,TS val 932840 ecr 0,nop,wscale 6], length 0
      16:03:21.931387 IP (tos 0x0, ttl 64, id 1432, offset 0, flags [DF], proto TCP (6), length 60)
      88.208.9.111.80 > 46.153.19.182.54645: Flags [S.], cksum 0xa4bc (incorrect -> 0xf9a4), seq 1594895211, ack 3834615052, win 8192, options [mss 1460,nop,wscale 6,sackOK,TS val 2509954639 ecr 932840], length 0
      16:03:22.049434 IP (tos 0x28, ttl 47, id 44772, offset 0, flags [DF], proto TCP (6), length 52)
      46.153.19.182.54645 > 88.208.9.111.80: Flags [.], cksum 0x430b (correct), seq 3834615052, ack 1594895212, win 1369, options [nop,nop,TS val 932852 ecr 2509954639], length 0
      16:03:22.053697 IP (tos 0x28, ttl 47, id 44773, offset 0, flags [DF], proto TCP (6), length 40)
      46.153.19.182.54645 > 88.208.9.111.80: Flags [R], cksum 0x93ba (correct), seq 211128292, win 1369, length 0
      16:03:22.059913 IP (tos 0x28, ttl 48, id 0, offset 0, flags [DF], proto TCP (6), length 40)
      46.153.19.182.54645 > 88.208.9.111.80: Flags [R.], cksum 0xa03f (correct), seq 0, ack 1594897965, win 0, length 0
      16:03:22.060700 IP (tos 0x28, ttl 47, id 44774, offset 0, flags [DF], proto TCP (6), length 52)
      46.153.19.182.54645 > 88.208.9.111.80: Flags [.], cksum 0x3a48 (correct), seq 3834615953, ack 1594896512, win 1410, options [nop,nop,TS val 932853 ecr 2509954639], length 0
      16:03:22.060706 IP (tos 0x0, ttl 64, id 3974, offset 0, flags [DF], proto TCP (6), length 52)
      88.208.9.111.80 > 46.153.19.182.54645: Flags [.], cksum 0xa4b4 (incorrect -> 0x475c), seq 1594895212, ack 3834615052, win 135, options [nop,nop,TS val 2509954768 ecr 932852], length 0

      Первый пакет содержит seq 3834615051, в ответ от сервера ушел пакет seq 1594895211, ack 3834615052 (в out-ack ушел номер in-seq + 1)
      Потом пришло пару RST пакетов, они нам не интересны.
      А вот следующий пакет нам интересен — в нем записаны номера seq 3834615953, ack 1594896512. Оба эти номера существенно больше чем initial seq/ack, а это означает, что удаленная сторона уже отослала 3834615953-3834615052=901 байт и даже успела получить 1594896512-1594895212=1300 байт. Разумеется, этих пакетов с данными мы не видим и не увидим — этот обмен был с MiTM системой. Но сервер то этого не знает. Он видит пакет с seq 3834615953, а следовательно, что ему не пришло 901 байт данных, и отсылает обратно пакет с последними валидными seq/ack номерами, ему известными — seq 1594895212, ack 3834615052. Удаленная сторона получает этот пакет, и в свою очередь рапортует, что у неё все отлично, 1300 байт данных получены успешно. Вот у нас и зацикливание.

      Также становится понятным, почему штатовские сервера не видели этого трафика — он на самом деле был, но во много раз меньше — во столько же раз, во сколько пинг из Индии в Штаты больше, чем пинг из Индии в Европу.

      Осталось, собственно, найти, как исправить этот баг. Опять берем исходники, интересующий нас код находится в файле tcp_input.c. Это было не особо сложно — первичной обработкой TCP-пакета занимается функция tcp_input() — в самом конце, если пакет проходит все проверки и TCP-соединение находится в состоянии ESTABLISHED — пакет передается на обработку в функцию tcp_do_segment(). Надо просто добавить ещё одну проверку — если ack-счетчик от удаленной стороны показывает, что она получила данные, которые сервер не отсылал — пакет надо игнорировать. Обрывать сразу соединение нельзя — иначе мы откроем злоумышленникам простой способ обрывать чужие TCP-соединения ). Тестирование патча показало, что в TCP-трафике также присутствуют пакеты с нулевым значением ack — их игнорировать уже не надо. Итоговый патч занял три строчки (без учета комментариев):

      +      if(SEQ_GT(th->th_ack, tp->snd_max) && th->th_ack != 0) {
      +               goto dropunlock;
      +       }
      
      

      PR (problem report) разработчикам FreeBSD отправлен в тот же день: http://ift.tt/2rEIEuf

      P.S. Как с этой проблемой обстоят дела в Linux и Windows? А там все нормально, такие пакеты игнорируются (тестировал Windows 10 и Linux 3.10).

      Комментарии (0)

        Let's block ads! (Why?)

        [Перевод] Туториал по AsyncDisplayKit 2.0 (Texture): автоматическая компоновка


        Добро пожаловать во вторую часть серии статей по AsyncDisplayKit (Texture)!


        Система компоновки AsyncDisplayKit позволяет писать невероятно быстрый, декларативный код.


        Помимо быстрой настройки, она автоматически адаптируется к устройству, на котором запущено приложение. Допустим, вы пытаетесь создать узел, который можно использовать во view controller приложения или в качестве popover в приложении для iPad. Если его layout будет правильно создан, вы сможете перенести узел в эту новую среду, не беспокоясь об изменении базового кода макета!



        В этом туториале AsyncDisplayKit 2.0 вы вернетесь к классу CardNode из первой части и узнаете о спецификациях layout, которые были использованы для его создания. Вы увидите, как легко составлять спецификации макета, чтобы получить желаемый результат.


        Проблема с Auto Layout


        Я слышу, как вы кричите: «Что не так с Auto Layout ?!» В Auto Layout каждый создаваемый вами коснтрейнт представляется в виде уравнения в системе уравнений. Это означает, что каждый добавленный констрейнт увеличивает время расчета констрейнтов экспоненциально. Такой расчет всегда выполняется в основном потоке.


        Одна из целей дизайна ASDK – как можно лучше придерживаться API UIKit. К сожалению, Auto Layout – непрозрачная система, в которой нет способа выполнять расчет констрейнтов в другом потоке.


        Давайте начнем


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


        Примечание: Прежде чем приступить к работе с туториалом по AsyncDisplayKit 2.0, ознакомьтесь с первой частью.

        Знакомство с ASLayoutSpec


        Прежде чем начать, расскажу вам немного предыстории.


        Спецификации макета – это обобщение системы макетов, о которой вкратце рассказано в Building Paper Event. Идея состоит в том, чтобы унифицировать вычисление и применение размеров и положений узла и подузлов, и иметь возможность их переиспользовать.


        В ASDK 1.9.X вы могли создавать асинхронные макеты, но код макета был похож на pre-auto layout в UIKit. Размер подузлов узла можно было вычислить в методе -calculateSizeThatFits :. Эти размеры могли быть закэшированны, а затем добавлены позже в -layout. А позиции узлов все еще должны были рассчитываться с использованием старой-доброй математики – никто не любит возиться с математикой.



        Спецификация макета


        В ASDK 2.0 подклассы ASDisplayNode могут реализовать -layoutSpecThatFits :. Объект класса ASLayoutSpec определяет размер и положение всех подузлов. При этом спецификация макета также определяет размер упомянутого родительского узла.


        Узел вернет объект спецификации макета из -layoutSpecThatFits :. Этот объект определит размер узла, а также размеры и позиции всех его подузлов рекурсивно.


        Аргумент ThatFits является объектом класса ASSizeRange. Он имеет два свойства типа CGSize (min и max), которые определяют наименьшие и наибольшие размеры узла.


        ASDK предоставляет множество различных видов спецификаций. Вот некоторые из них:


        • ASStackLayoutSpec: Позволяет определить вертикальный или горизонтальный стек дочерних элементов. Свойство justifyContent определяет расстояние между дочерними элементами в направлении стека, а alignItems определяет их расстояние вдоль противоположной оси. Эта спецификация настроена аналогично UIKit UIStackView.
        • ASOverlayLayoutSpec: Позволяет растянуть один элемент макета над другим. Объект, который накладывается на него, должен иметь intrinsic content size, чтобы это сработало.
        • ASRelativeLayoutSpec: Помещает элемент в относительную позицию внутри его доступного пространства. Подумайте о девяти секциях девяти нарезанных изображений. Вы можете поместить объект в одну из этих секций.
        • ASInsetLayoutSpec: Позволяет сделать отступы вокруг существующего объкта. Вы хотите классические «iOS 16 пиксельные отступы» вокруг вашей ячейки? Нет проблем!

        ASLayoutElement протокол


        Спецификации макета управляют расположением одного или нескольких дочерних элементов. Элемент может быть узлом, таким как ASTextNode или ASImageNode. В дополнение к узлам, дочерний шаблон спецификации макета также может быть другой спецификацией макета.


        Эй, как это возможно?


        Дочерние элементы спецификации макета должны соответствовать протоколу ASLayoutElement. ASLayoutSpec и ASDisplayNode соответствуют ASLayoutElement, поэтому оба типа и их подклассы могут быть дочерними элементами.



        Эта простая концепция оказывается невероятно мощной. Одной из наиболее важных характеристик макета является ASStackLayoutSpec. Возможность разместить изображение и текст – это одно, а вот разместить изображение и другой стек – совсем другое!


        Вы совершенно правы. Пришло время дуэли! Я имею в виду, писать код ...



        Размещение изображения животного


        Итак, вы на работе, и ваш дизайнер отправил вам скриншот того, что он хочет для нового приложения «Энциклопедия для животных», над которым вы работаете.



        Первое, что нужно сделать, это разбить экран на соответствующие спецификации макета, чтобы понять общий макет. Иногда это может ввести в ступор, но помните, что мощность макета зависит от того, насколько легко они могут быть скомпонованы. Начните с простого.


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


        Разархивируйте стартовый проект и откройте RainforestStarter.xcworkspace. Перейдите в CardNode.m к методу -layoutSpecThatFits :. Сейчас он просто возвращает пустой объект ASLayoutSpec.


        Если вы скомпилируите и запустите проект, то увидите следующее:



        Хорошо, это только начало. Как насчет того, чтобы сперва показать изображение животного?


        По умолчанию узел сетевого изображения не имеет содержимого и, следовательно, не имеет собственного размера. Посмотрев на скриншот, можно сказать, что изображение животного должно занимать полную ширину экрана и 2/3 высоты.


        Для этого замените существующий оператор return следующим:


        //1
        CGFloat ratio = constrainedSize.min.height/constrainedSize.min.width;
        
        //2
        ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec 
                                                    ratioLayoutSpecWithRatio:ratio 
                                                                       child:self.animalImageNode];
        //3
        return imageRatioSpec;
        

        Рассмотрим каждый пронумерованный комментарий по очереди:


        1. Calculate Ratio: Во-первых, вы определяете соотношение, которое хотите применить к своему изображению. Соотношения определяются по высоте / ширине. Здесь вы указываете, что высота этого изображения должна составлять 2/3 минимальной высоты ячейки, что является высотой экрана.
        2. Create Ratio Layout Spec: Затем вы создаете новый ASRatioLayoutSpec с помощью рассчитанного отношения и дочернего элемента animalImageNode.
        3. Return a Spec: Возвращенный imageRatioSpec определяет высоту и ширину ячейки.

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



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


        Примечание: constrainedSize, переданный в ячейку узла таблицы, состоит из минимального (0, 0) и максимального значения (tableNodeWidth, INF). Поэтому вам необходимо использовать preferredFrameSize для определения высоты изображения. preferredFrameSize был установлен в AnimalPagerController в части 1.

        Добавление градиента


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


        ASOverlayLayoutSpec – это просто спецификация для работы.


        Сначала добавьте следующую строчку после инициализации imageRatioSpec:


        ASOverlayLayoutSpec *gradientOverlaySpec = [ASOverlayLayoutSpec
                                                      overlayLayoutSpecWithChild:imageRatioSpec 
                                                                         overlay:self.gradientNode];
        

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


        Замените текущий return следующим.


        return gradientOverlaySpec;
        

        Скомпилируйте и запустите, чтобы увидеть градиент, растянутый по каждому объекту imageNode.


        Градиент для каждой птицы – отлично!



        Добавление текста с именем животного


        Единственное, что осталось сделать – это добавить название животного.


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


        1. Название должно располагаться поверх градиента.
        2. Название должно быть в нижнем левом углу изображения животного.
        3. Отступ – 16 поинтов слева и 8 снизу.

        Вы уже знаете, как разместить текст сверху. Пришло время вырваться из проверенной и истинной спецификации оверлея.


        Добавьте следующую строчку сразу после gradientOverlaySpec


        ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec
                                                  overlayLayoutSpecWithChild:gradientOverlaySpec 
                                                                     overlay:self.animalNameTextNode];
        

        Кроме того, вам нужно изменить оператор return на следующий:


        return nameOverlaySpec;
        

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



        Неплохо. Вам просто нужно переместить этот текст в нижний угол.


        Стоит упомянуть об общем случае, с которым вы столкнетесь. У вас есть текст на птице, поэтому естественное стремление – обернуть OverlaySpec в другие спецификации, чтобы поместить его там, где хотите. Как правило, нужно сделать шаг назад и подумать о том, что вы пытаетесь выразить.


        В этом случае вы используете nameOverlaySpec, чтобы расширить что-нибудь еще поверх существующего содержимого.


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


        Знакомство с ASRelativeLayoutSpec


        На самом деле вам нужен ASRelativeLayoutSpec.


        ASRelativeLayoutSpec принимает дочерний объект ASLayoutElement, рассматривает пространство, в котором оно доступно, и затем помещает этот дочерний элемент в соответствии с вашими инструкциями.


        При определении относительной спецификации, можно установить ее свойства verticalPosition и horizontalPosition.


        Эти два свойства могут быть следующими:


        • ASRelativeLayoutSpecPositionStart
        • ASRelativeLayoutSpecPositionCenter
        • ASRelativeLayoutSpecPositionEnd

        Комбинация позволяет разместить объект в одном из углов, краев или в центре доступного пространства.


        В качестве упражнения: как бы вы поместили эту лягушку на правом краю свободного пространства?



        Если вы сказали: «Установите verticalPosition в ASRelativeLayoutSpecPositionCenter и horizontalPosition в ASRelativeLayoutSpecPositionEnd», то вы правы!


        Теперь, когда вы попрактиковались, следующая строка должна иметь немного больше смысла. Добавьте эту строчку прямо перед nameOverlaySpec:


        ASRelativeLayoutSpec *relativeSpec = [ASRelativeLayoutSpec    
              relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart       
                                              verticalPosition:ASRelativeLayoutSpecPositionEnd 
                                                  sizingOption:ASRelativeLayoutSpecSizingOptionDefault 
                                                         child:self.animalNameTextNode];
        

        Так вы установите horizontalPosition дочернего элемента для старта и verticalPosition для завершения. На лягушачьем языке это выглядело бы примерно так:



        Теперь, когда у вас установлена ​​относительная спецификация, измените определение nameOverlaySpec на следующее:


        ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec 
                                                   overlayLayoutSpecWithChild:gradientOverlaySpec 
                                                                      overlay:relativeSpec];
        

        Скомпилируйте и запустите, чтобы посмотреть, что у вас получилось:



        Хорошо! Только есть еще одна вещь, которую нужно сделать на этой половине ячейки.


        Знакомство с ASInsetLayoutSpec


        Последнее, что вам нужно сделать, это поместить название животного на 16 поинтов слева и на 8 поинтов вниз. Для этого у вас есть ASInsetLayoutSpec.


        Чтобы добавить небольшой отступ вокруг любого из ваших объектов, просто оберните объект в спецификацию вставки и предоставьте UIEdgeInsets определить, какой отступ вам нужен.


        Добавьте следующую строчку после nameOverlaySpec:


        ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec 
                                       insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) 
                                                           child:nameOverlaySpec];
        

        Затем еще раз измените оператор return, чтобы вернуть самую внешнюю спецификацию.


        return nameInsetSpec;
        

        Скомпилируйте и запустите.




        Вы не хотите, чтобы вставка была применена ко всей области, которую охватывает оверлей, так как она включает в себя изображение вашего животного.


        То, что вы на самом деле хотите, это применить вставку к пространству relativeSpec. Чтобы исправить это, сначала удалите текущее определение nameInsetSpec.


        Затем добавьте следующую новую и улучшенную версию прямо перед определением nameOverlaySpec:


        ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec 
                    insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:relativeSpec];
        

        Теперь вам нужен nameOverlaySpec, чтобы наложить новую вставку, не relativeSpec. Замените определение nameOverlaySpec на:


        ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec
                            overlayLayoutSpecWithChild:gradientOverlaySpec overlay:nameInsetSpec];
        

        Наконец, вернитесь к:


        return nameOverlaySpec;
        

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



        Половина работы выполнена!


        Нижняя половина


        Вторая половина немного легче. Это просто описание животного со вставкой вокруг него… и вы уже знаете, как это сделать.


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


        ASInsetLayoutSpec *descriptionTextInsetSpec = [ASInsetLayoutSpec        
                                    insetLayoutSpecWithInsets:UIEdgeInsetsMake(16.0, 28.0, 12.0, 28.0) 
                                                        child:self.animalDescriptionTextNode];
        

        Если бы вы вернули эту вставку, а затем скомпилировали и запустили приложение, то увидели бы следующее:



        Это именно то, что вы хотели. Теперь, когда мы разобрались с обеими половинками, объединить их вместе совсем несложно.


        Внутренние размеры контента


        Вам не нужно беспокоиться о том, чтобы у текста был размер контента для заполнения пространства. Это потому, что ASTextNode имеет свой внутренний размер содержимого, основанный на его тексте и атрибутах.


        Следующие узлы не имеют дефолтного размера:


        • Подклассы ASDisplayNode
        • ASNetworkImageNode и ASMultiplexImageNode
        • ASVideoNode и ASVideoPlayerNode

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


        Знакомство с ASStackLayoutSpec


        Это идеальное время для использования спецификации макета стека. Вы можете воспринимать это как спецификацию макета, эквивалентную UIStackView, исключая ее автоматическую обратную совместимость, которая довольно изящна.


        Стеки могут быть определены как вертикальные, так и горизонтальные. И как все, спецификации макета могут принимать либо узлы, либо другие разметки в качестве дочерних элементов.


        Чтобы настроить этот стек, добавьте эти три строки после определения вставки описания:


        ASStackLayoutSpec *verticalStackSpec = [[ASStackLayoutSpec alloc] init];
        verticalStackSpec.direction = ASStackLayoutDirectionVertical;
        verticalStackSpec.children = @[nameOverlaySpec, descriptionTextInsetSpec];
        

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


        И снова верните новую спецификацию макета.


        return verticalStackSpec;
        

        Скомпилируйте и запустите. Почти готово!



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

        Вложенность стеков, каждый из которых имеет свои собственные настройки justifyContent и alignItems, означает, что они могут быть невероятно выразительными, а также невероятно разочаровывающими. Не забудьте посмотреть flex box froggy game и Async Display Kit docs для более глубокого изучения.


        Знакомство с ASBackgroundLayoutSpec


        Помните спецификацию оверлея? Одно из правил заключается в том, что предмет, который накладывается на нее, должен иметь свой собственный размер.


        Элемент сзади определяет размер, а элемент спереди просто растягивается над ним.


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


        В этом случае вам нужно использовать спецификацию макета фона, чтобы растянуть изображение размытого животного.


        Для этого добавьте следующую строчку:


        ASBackgroundLayoutSpec *backgroundLayoutSpec = [ASBackgroundLayoutSpec 
                                              backgroundLayoutSpecWithChild:verticalStackSpec    
                                                                 background:self.backgroundImageNode];
        

        И замените оператор return


        return backgroundLayoutSpec;
        

        Скомпилируйте и запустите, чтобы посмотреть, что получилось:



        Завершенный проект доступен по ссылке. Повторюсь, что также есть версия для Swift.


        Как только вы разберетесь с этими концепциями, начните изучать документацию. Это был просто маленький обзор того, на что способна система компоновки.


        Мы надеемся, что вам понравился этот туториал AsyncDisplayKit 2.0, и если у вас есть вопросы – не стесняйтесь оставлять их в комментариях!


        P.S. Отдельное спасибо BeataSunshine и evgen за помощь в переводе статьи.

        Комментарии (0)

          Let's block ads! (Why?)

          Почему подключаться под атакой к сервису нейтрализации DDoS уже слишком поздно

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

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

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

          Лирическое отступление: если вы собираетесь ехать в отпуск в тропическую страну на другом континенте, то нелишне, помимо общепринятой туристической страховки, озаботиться и вопросом прививок от наиболее опасных и распространённых заболеваний.

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

          Каковы плюсы подключения к сети фильтрации трафика до атаки?


          Первое и самое важное — это знание о поведении пользователей. Наблюдая трафик целевого ресурса до стресса, мы хорошо представляем себе, каково нормальное поведение пользователей ресурса и какая статистика посещаемости является для ресурса естественной. При нахождении под атакой происходит постепенная (или быстрая, всё зависит от действий атакующих) деградация сервиса, в результате которой поведение нормальных пользователей ресурса существенно меняется. В некоторых случаях действия легитимных пользователей начинают походить на атаку и ухудшают её последствия, как пример — циклическое обновление одной и той же страницы пользователем до получения требуемой страницы в браузере.

          Чем сильнее деградация сервиса под атакой — тем сложнее отличить легитимных пользователей от вредоносных ботов. Если мы не видели поведение пользователей в преддверии атаки с нормальным и доказано легитимным поведением — мы не знаем заранее, по каким признакам возможно отличить настоящего пользователя от бота-злоумышленника, отправляющего мусорные запросы на сервер. Помимо этого, объём трафика, сопровождающий нормальный рабочий процесс сервиса, был бы также известен.

          В случае, если на начало атаки сеть или фильтрационное оборудование не были включены в процесс её нейтрализации, то в процессе для автоматики и обучающихся моделей все пользователи, нажимающие «Обновить страницу», почти неотличимы от трафика атаки, соответственно, вероятность их блокировки высока. Зная предыдущее поведение пользователей ресурса, можно обезопасить и их, и себя от возникновения подобной ситуации.

          Точно так же и с объёмом трафика и статистикой посещаемости в нормальной ситуации — в случае отсутствия такой информации на момент атаки будет очень сложно с уверенностью утверждать, вернулся ли трафик в норму после нейтрализации атаки из-за того, что под блокировку могли попасть настоящие пользователи, просто обладающие чуть отличающимся от медианного поведением во время деградации сервиса под атакой.

          Конечно, подобные проблемы встречаются не всегда, но становятся особенно острыми при некоторых сценариях атаки. В зависимости от сложности атакующего бота и хитрости злоумышленника, действительно неприятной может стать атака в корень сервиса — в этом случае при деградации сервиса поведение ботов выглядит почти неотличимо от поведения легитимных пользователей, у которых просто «ничего не открывается». В случае атак другого типа, как правило, остаются значимые отличия в легитимном и вредоносном поведении с точки зрения потоков данных.

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

          Каковы реальные минусы и возможные последствия при подключении под атакой?


          «С больной головы на здоровую» — это про нейтрализацию DDoS. И как ни странно, но даже давно присутствующие на рынке компании с профессиональными сотрудниками и тысячами потребителей по всей стране или даже по всему миру далеко не всегда задумываются о нейтрализации DDoS до момента начала атаки. Нам бы хотелось изменить данную ситуацию с помощью донесения адекватной информации до аудитории, но не будем строить иллюзий — пока гром не грянет, мужик не перекрестится.

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

          Второе — если вас атакуют, то, скорее всего, злоумышленник знает уже не только ваше доменное имя, но и IP-адрес. Вдобавок, обычно IP-адрес атакуемого ресурса принадлежит хостеру, поэтому мы априори считаем, что хостер также известен. То есть злоумышленник владеет достаточной информацией для модификации вектора атаки.

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

          Конечно, отработка false positive — не единственная трудность. Подключение под атакой не устраняет проблему частичной или полной недоступности, и если атака уже началась и успела причинить некоторый вред, то помимо работ по подключению ресурса к системе защиты нужен «контроль повреждений», то есть работа над последствиями атаки на стороне потребителя и заказчика.

          Бывает, что атака «хорошо пошла» и принесла значительный ущерб. Буквально это значит следующее — помимо забивания канала связи, через который далее так или иначе будет необходимо организовывать связность уже с облачным провайдером фильтрации трафика, пока выделенных каналов нет, может «умереть» (отказать в обслуживании) пограничный маршрутизатор или балансировщик. Плохо может быть и другим инфраструктурным составляющим, например, нередко в разрыв соединения с внешним миром смотрит межсетевой экран (файрвол), являющийся нежным устройством и не любящий распределённых атак на отказ в обслуживании. Остановка нормальной работы файрвола грозит дырой в сетевом периметре. Да, это лечится перезагрузкой отказавшего оборудования, но до него ещё необходимо добраться и дождаться возвращения его в консистентное состояние. Всё это время трафик пропускается без фильтрации.

          Говоря об атаках на прикладной уровень (L7), то здесь и с каналом всё может быть хорошо, и связность есть, но веб-приложение всё равно болеет, находится в депрессии и может, например, отвалиться от базы, либо упасть вместе с ней. А возвращение его в нормальное консистентное состояние может занимать от нескольких часов до нескольких суток, в зависимости от существующих архитектурных сложностей.

          К сетям фильтрации трафика существует два наиболее распространенных способа подключения — с использованием DNS либо BGP. Рассмотрим два этих сценария по отдельности.

          DNS


          Предположим, что атака ведётся только по доменному имени.
          Типичный сценарий подключения сайта под атакой по DNS выглядит следующим образом: владелец сетевого ресурса, представленного доменным именем, в A-записи DNS которого указан текущий IP-адрес атакуемого веб-сервера, обращается к нам за помощью. После соблюдения всех формальностей Qrator Labs выделяет клиенту специальный IP-адрес, на который он заменяет свой текущий адрес.

          Как правило, в этот момент приходится учитывать возможный высокий TTL на изменение записи DNS, который может составлять от нескольких часов до суток максимум (предел по RFC: 2147483647 секунд) — в течение этого времени старая А-запись будет существовать в кэше DNS-рекурсоров. Поэтому если вы заранее осознаете, что атака вероятна, необходимо иметь низкий TTL для A-записи DNS.

          Но в некоторых случаях даже это может не спасти. Ведь злоумышленники во время атаки проверяют её результативность, и видя, что атака не даёт результата, смышлёный злодей быстро переключится на атаку в тот IP-адрес, который он «помнит», минуя сеть фильтрации.

          В таком случае необходим новый IP-адрес, неизвестный злоумышленникам и никак не компрометирующий защищаемое доменное имя, желательно в отличном от старого блоке адресов, а идеально — у другого поставщика услуг. Ведь злоумышленники всегда могут пойти дальше и переключить вектор DDoS-атаки в блок адресов (префикс) поставщика, имея достаточные мощности. Ну а к нам — милости просим. Двигаемся дальше.

          BGP


          В случае с подключением по протоколу BGP всё выглядит несколько иначе.
          Если атакуемый сервис обладает собственными блоками адресов (префиксами) и хочет перевести всю инфраструктуру под защиту целиком, анонсируя собственные префиксы через поставщика услуг нейтрализации DDoS — как выглядит данный процесс?

          Автономная система под атакой добавляется в наш AS-SET, для того чтобы иметь возможность анонсировать собственные префиксы, после чего начинаются пресловутые сутки (24 часа) на получение всеми нашими аплинками данной информации и обновления префикс-листов. Естественно, в экстренном случае мы стараемся пойти навстречу такому клиенту, форсируя данный процесс, но это возможно не в каждом случае и делается вручную. В свете вышесказанного, время — ключевой и основной стресс-фактор, ведь защитить ресурс требуется безотлагательно, «обезболить мгновенно».

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

          Лирическое отступление: ваш поставщик услуг связности покупает IP-транзит, как правило, у больших и надежных операторов связи, не ниже региональных Tier-1 операторов. Они фильтруют префиксы, поступающие к ним от клиентов, на основании некоторого списка (prefix-list), который они берут из открытых баз данных (RIPE, RADB и других). Штатно одни поставщики IP-транзита сервиса по защите от DDoS обновляют эти фильтры циклично раз в сутки, другие делают это только по запросу. У грамотного поставщика услуг нейтрализации DDoS точки присутствия расположены по всему миру, мгновенно не накатишь.

          Наиболее сложным случаем подключения под атакой является обращение владельца сложной инфраструктуры, обладающей самым разным оборудованием: маршрутизаторами, межсетевыми экранами, балансировщиками нагрузки и прочими чудесами современных технологий. В подобных сценариях даже штатное подключение к поставщику услуг нейтрализации DDoS — сложный процесс, подходить к которому стоит взвешенно и планомерно. Даже как-то стыдно упоминать, что в случае атаки на такой сервис экстренное его подключение к сети фильтрации съедает кучу усилий и не оставляет времени на тщательное планирование, ведь под атакой каждая минута на счету. Пока данные проблемы не решены, сервис имеет проблемы с доступностью у пользователей и деградирует.

          Нередко DDoS-атака комбинируется в некоем порядке с хакерской атакой — на взлом и проникновение, либо до атаки на отказ в обслуживании, либо после. Здесь задействованы риски уже совершенно другого порядка: утечка пользовательских данных, получение полного доступа к системе злоумышленником. Данные проблемы необходимо решать перпендикулярно с нейтрализацией DDoS, что в худшем случае ведёт к ещё более продолжительной недоступности у пользователей, а в лучшем — к увеличенной задержке у тех, кто пытается запросить необходимую страницу.


          Постскриптум.
          Коллеги, доводим до вашего сведения следующую важную новость: http://ift.tt/2ta0B68

          Инициатива о внедрении механизма автоматической защиты от возникновения «утечек маршрутов» (route leaks), непосредственное участие в создании которой принимали инженеры Qrator Labs, успешно прошла этап «принятия» (adoption call) и перешла в рабочую группу по Interdomain Routing (IDR).

          Следующий этап — доработка документа в рамках IDR и, в дальнейшем, проверка руководящей группой (IESG www.ietf.org/iesg). В случае успешного прохождения этих этапов черновик станет новым сетевым стандартом RFC (http://ift.tt/1z7er9G).

          Авторы: Александр Азимов, Евгений Богомазов, Рэнди Буш (Randy Bush, Internet Initiative Japan), Котикалапуди Шрирам (Kotikalapudi Sriram, US NIST) и Кейур Пател (Keyur Patel, Arrcus Inc.) осознают, что в индустрии есть острый спрос на предлагаемые изменения. Однако спешка в данном процессе также неприемлема, и авторы приложат максимум усилий, чтобы сделать предлагаемый стандарт удобным как для транснациональных операторов, так и небольших домашних сетей.

          Мы благодарим технических специалистов, выразившим свою поддержку в рамках adoption call. При этом отдельная благодарность тем, кто не просто выразил свое мнение, но и прислал свои замечания. Мы постараемся учесть их при последующих изменениях данного черновика.

          Мы также хотим донести до сведения заинтересованных инженеров, что у вас всё ещё есть возможность выразить собственные пожелания к внесению дополнений и уточнений через рассылку IETF (draft-ymbk-idr-bgp-open-policy@ietf.org) или на через сайт инициатив Qrator Labs.

          Важно также отметить, что к моменту принятия финального решения у стандарта должно быть две рабочих реализации. Одна из них уже доступна — это наша разработка на базе раутингового демона Bird, доступная на Github: http://ift.tt/2ta38wW. Мы приглашаем вендоров и open source сообщество присоединиться к данному процессу.

          Комментарии (0)

            Let's block ads! (Why?)

            [Перевод - recovery mode ] Самое простое руководство по иконографике

            Вышел Firefox 54, который наконец получил поддержку многопроцессного режима

            У вас есть право на анонимность. Часть 3. Правоприменительная борьба с инструментами анонимности

            От b2b-приложений к массовому сервису по всему миру

            image


            Привет, Хабр! Меня зовут Евгений Лисовский, я руковожу проектом MAPS.ME — это международный проект, интересный и очень амбициозный. Наша задача — конкурировать с Google, Apple и несколькими компаниями второго эшелона. Сегодня я кратко расскажу о своём трудовом пути, чтобы затем подробнее остановиться на самых интересных этапах.


            До института я подрабатывал, собирал компьютеры на заказ. А свой компьютер у меня появился в 1995 году, в 13 лет — спасибо родителям, это было реально очень круто. В институте я начал сам изучать PHP, MySQL, соорудил собственный движок для интернет-магазина. Сделал три сайта, брал по 300 долларов за каждый. С 2004 года я работал в международной компании Radmin.com, которая создаёт b2b-продукт для удалённого управления компьютерами, там я провёл шесть лет. Потом ломанулся в стартапы: KupiBonus, детские товары BabyBoom. Там было интересно! Важны не только успешные проекты, но и фейлы: надо хорошо анализировать, почему они происходили. Потом был Litres. Очень интересный бизнес — продавать электронные книги при уровне пиратства 96 %. В 2015 году мы заработали 15 млн долларов, с 2011 года выручка выросла в 18 раз. Затем я вместе с партнерами запустил собственный проект MoikaMoika.ru. Не то чтобы основатели сразу стали миллионерами, но это интересный опыт. Проект жив и развивается. А дальше про всё это — более подробно.


            Институт


            Я писал на С++ программную часть дипломной работы, а мой партнёр делал аппаратную часть. По сути, печатную плату. Прибор был очень простой: снимал ЭКГ с трёх отведений — правой руки, левой руки, левой ноги — и формировал электрокардиограмму. Она снималась простым аналого-цифровым преобразователем, плюс частотный модулятор на звуковой частоте, и передавалась через звуковую плату компьютера. Поскольку звуковая карта есть во всех компьютерах, то это самый простой способ ввести данные в цифровом виде. Потом я программно снимал огибающую, которая и представляет собой ЭКГ. По высоте зубцов и расстоянию между ними можно определить разные типы заболеваний, отметить определённые паттерны, сравнить их с базой и сделать вывод о диагнозе.


            image


            Это было ещё в 2004 году. Тогда я понимал, что наш прибор может стать массовым продуктом, но был я слишком зелен и юн. Главная моя проблема в прошлом — неуверенность в своих силах. Мне казалось, что мир такой большой, вокруг такие взрослые дяди, они зарабатывают деньги, а я ещё такой глупый, маленький и ничего не умею. Это ключевая сложность. Крайне важно перебороть страх. Когда вы делаете что-то новое, вам всегда кажется, что задача неподъёмна. А потом проходит месяц-два, вы оборачиваетесь и понимаете: блин, чего я вообще боялся? Всё нормально, в этом нет ничего сложного. На протяжении всей своей карьеры я боролся со страхом. А ещё очень важно уметь признавать свои ошибки и исправлять их.


            Первая компания


            После института я начал искать работу веб-программистом, программистом на PHP, MySQL или сисадмином. Для последней должности навыков не хватало, я даже собеседовался в Лабораторию Касперского, но это оказалось очень сложно. Да и с PHP-программированием было не особо. Я пришёл, а мне говорят: вот блокнот, напиши нам плагинатор. И ты думаешь: зачем блокнот, когда есть DreamWeaver, в котором все теги подсвечиваются.


            Потом меня пригласили в софтверную международную компанию Radmin интернет-маркетологом. У меня был свободный английский. Погоняли всякие IQ-тесты, позадавали вопросы, я на всё ответил, и мой будущий руководитель говорит: «Я вижу, что ты нормальный парень, но слишком молодой. Если сразу во всё быстро вникнешь и за шесть дней вольёшься в работу, то мы тебя берём. Ты маркетингом занимался?» Я думаю: сайты делал, SEO делал — ну конечно, маркетинг — это моё вообще, любимое. Он: «Google Adwords знаешь?» А я в контекстной рекламе — вообще ни в зуб ногой. Но мне деньги были очень нужны, у меня только сын родился. Говорю: «Ну конечно!»


            Так я и устроился на работу. Мне повезло. Этот опыт дал мне, инженеру, огромный профит. Мне стало очень легко общаться с разработчиками. Знаете, есть классическое противостояние между разработчиками и маркетологами. Приходит маркетолог, говорит: сделай мне такую красивую красную кнопку, чтобы всё хорошо работало. А инженеры отвечают: да иди ты куда подальше… И всё, начинается обособление. А когда ты приходишь и говоришь: чувак, всё нормально, я тоже кодил на С++, то IT-директор сразу выдыхает с облегчением. И становится гораздо проще общаться. Ведь практически все современные IT-проекты — это симбиоз разработки и маркетинга продукта, они неотделимы друг от друга. И когда их искусственно разделяют, то получается не очень хорошая ситуация.


            В этой компании я работал с контекстной рекламой ещё в 2004 году, когда в России было очень мало интернет-маркетологов. Очень интересный опыт. Я ездил по разным международным конференциям, где уже тогда обсуждалось тестирование лендинговых страниц. В США и Европе А/В-тестирование, оптимизация конверсий и прочие методики были отработаны на высшем уровне. До нас это докатывалось с задержкой примерно в пять лет.


            Я не могу сказать, что сделал много за время работы в Radmin. Уйдя в стартап KupiBonus — купонный проект, — я за полгода сделал больше, чем за шесть лет в предыдущей компании. Возможно, динамика жизни в интернете была немного ниже или я боролся с собой: мне казалось, что пора уходить, но я оставался, потому что это хорошая надёжная компания. Я думал: приду на позицию интернет-маркетолога, дорасту до маркетинг-директора — и вот оно, счастье. На самом деле счастье — это достижение целей в создании каких-либо продуктов.


            В кризисный 2009 год мне удалось перепозиционировать продукт. Я объездил всю Россию, все федеральные округи, провёл большую работу с продажниками, которые потом распространили нашу систему оптимизации бизнес-процессов по всей стране. И в итоге в кризисный год получилось поднять выручку в России на 30 %.


            Какие выводы я сделал для себя, поработав в софтверной компании? Я хотел поменять команду. Я понял, что когда веб-мастер сидит и при мне в буквальном смысле спит, то это беда. Я пытался искать какие-то системы управления проектами, проанализировал кучу разных вариантов. Думал, что если внедрю нормальную систему, то всё сразу заиграет яркими красками. Но на самом деле проблема была в людях. Надо изначально нанимать правильных людей.


            Первые стартапы


            Мне захотелось создать свою команду с нуля. Уйдя в стартап, я пришёл в готовую команду. Это тоже оказалось проблемой, потому что людей нанимал не я. У нас не было взаимного доверия, приходилось налаживать отношения, а это достаточно сложно. В купонном бизнесе мой рабочий день начинался с шоколадки и чашки кофе в восемь утра, а заканчивался в одиннадцать вечера. Был даже период (конец 2010 года), когда мы работали по 15 часов в сутки. Это очень динамичный бизнес. Топ-менеджмент у нас была сильный, а ребята в моей команде — слабоваты. В итоге многое приходилось делать самому.


            Мне говорили: Женя, ты хреновый менеджер, взял всё на себя, ты должен делегировать. И я понимал, что это так. Я не пытался обмануть себя, работал над собой, но всё равно продолжал делать сам, потому что не мог зафакапить проект.


            Было очень приятно, когда президент компании, француз, бывший (до 1998 года) владелец OZON.ru подходил и говорил: Евгений, маркетинг — это ключевое. Это так. Я не мог заснуть ночью, пока не придумывал, как бы выполнить план. А план у нас был конский. Я пришёл из компании с бюджетом в 100 тыс. долларов в год в купонный бизнес с бюджетом 200 тыс. евро в месяц. И нам крайне не хватало лидов.


            Затем был проект BabyBoom — классический ретейл. Мой совет: не работайте с неподходящими людьми, увольняйте слабых, нанимайте сильных. Например, в BabyBoom и Litres я собирал команды с нуля и зачастую делал выбор в пользу, может, не самого опытного человека, но готового быстро обучаться. В маркетинге нет ничего суперсложного, всё можно освоить за месяц, если нормально подойти к процессу. В итоге гораздо проще выучить людей, чем нанять очень опытных и дорогих. Также у меня был пунктик, чтобы это была именно моя команда.


            Но тут есть одна проблема. Boss Cap — Friend Cap. Это то, чему я научился в BabyBoom, стартапе по продаже детских товаров. Сначала мы подняли 1 млн долларов инвестиций, начали увеличивать оборот, выросли в пять раз. Но логистика в детских товарах очень сложная: часто встречаются крупногабаритные товары. Хотя средний чек достаточно большой, маржинальность доходила до 30 %, однако крупногабаритные товары просто убивали бизнес-модель. Логистика сжирала всю маржу. Добавьте сюда далеко не полную информацию об остатках на складе. К сожалению, у нас не получилось закрыть второй раунд инвестиций из-за слишком большого количества рисков. В итоге проект развалился, и я пошёл в Litres.


            Growth Hacking


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


            Что может привести к успеху? Интеллект, эрудиция, социальная адаптация, помноженные на предпринимательские способности. Человек может быть интеллектуалом, но не особо эрудитом, но при этом иметь высокие способности в предпринимательстве и социальной адаптации. Социальная адаптация — это умение общаться, договариваться, строить взаимоотношения с людьми. Очень полезный навык. Постарайтесь развивать его в себе.


            image


            Команда — это основа успешности проекта. Ключевая компетенция руководителя — подбор сотрудников. Я сам искал людей, не прибегая к услугам кадровиков. Я тратил много времени, но оно того стоит. Как обычно я выбирал работников? Я смотрел в их добрые глаза и рассказывал о проекте. Представьте: 2011 год, электронные книги — кто их покупает? Это как продавать мороженое эскимосам. Я рассказывал, что в США электронные книги занимают уже 20 % всего книжного рынка, и сейчас мы будем круто расти. Если у собеседника расширяются глаза, он наливается румянцем — значит, правильный человек, ему это интересно.


            Если ты будешь заниматься тем, что тебе нравится, то добьёшься большого результата, и это увидят все: руководство, акционеры. Работайте как собственник. Это очень интересная концепция. Я всегда действовал так, будто это мои деньги, и я их трачу. Я не останусь в проекте, который мне не нравится, но я всегда работаю так, будто это мой собственный проект. Это заметят, поверьте. Но не следуйте принципу «я наёмный работник и выше головы прыгать не буду, потому что больше мне не заплатят». Это неправильный подход, обязательно нужна самоотдача.


            Представьте себе, например, строителя по найму. Он строит дома тяп-ляп и мечтает о собственном доме, когда будут деньги. И если он 20 лет работал по найму и строил как придётся, как вы думаете, сможет ли он собственный дом построить хорошо? Конечно же, нет. Невозможно стать предпринимателем, не работая по найму как предприниматель.


            image


            Будьте как Йода. Это прописные истины правильного руководителя в IT. Конечно, должны быть достижения. Если ты приходишь руководить командой, то очень тяжело завоевать авторитет. Нужна открытость. Если ребята понимают, что ты делал, знают о твоих достижениях, то у них не будет вопросов. Если есть достижения, тогда легко вольёшься. Также надо быть лидером и разбираться в IT. Тут мне очень хорошо помогает технический бэкграунд.


            Во время работы приходится очень много анализировать. Если человек не умеет пользоваться Excel — скорее всего, у него несистемное мышление, а это значит, что мы не сработаемся. Я не требовал от людей технического образования, главное, чтобы кандидаты понимали базовые принципы. Один пример. В BabyBoom я нанял парня на партнёрские программы, заниматься женскими форумами. Я ему говорю: есть сотни сайтов, давай выберем самые крупные, отранжируем. Сделай мне табличку — и посмотрим, как будем дальше работать. Он пошёл думать, с утра приносит табличку. А в ячейках с рейтингом записаны не цифры 4/5, а «четыре из пяти баллов». Я говорю: а как ты автофильтр включишь? Это же просто символы, а не цифры. Но он меня даже не понял.


            Моя ошибка: я недостаточно подробно его расспрашивал на собеседовании. После этого я всем кандидатам давал такое задание: «Представьте, что вы — менеджер партнерской программы в Litres. Существует 10 000 сайтов о книгах. Ваша задача — запартнёриться со всеми этими сайтами. Как вы будете действовать?» Мне важно оценить, как человек понимает бизнес-процессы. Мыслит ли он структурно: «Занесу в табличку, отсортирую по убыванию трафика, возьму топ-20, которые, скорее всего, будут генерировать 80 % трафика, и начну в первую очередь с ними работать». По сути, Excel и Google Docs Spreadsheets — это своеобразные CRM. У меня всё на них сделано и отработано на куче стартапов, никакая система управления проектами не нужна.


            Создавая у себя в голове структуру команды и нанимая людей, я всегда давал им понять, где они могут оказаться. Например, кандидату, который пришёл на партнёрские программы, я сказал: «У меня есть план структуры компании на два года вперёд, и здесь есть позиция — руководитель партнёрских программ. Я ничего не гарантирую, но если ты будешь активно расти и развиваться, ты сможешь быть там». В итоге он действительно занял эту должность. И мне это очень приятно. Я люблю, когда сотрудники растут. Также я открыто объясняю, что карьера — это не безоблачный полёт, когда всё ровно и гладко, каждый может забуксовать. Но либо человек движется вперёд, либо мы расстаемся. Есть два состояния: падение и рост. Без вариантов.


            О продукте


            В 2011 году в Litres мы сделали мобильное приложение. Так себе, просто читалка. Ни закладок, ни заметок, ни синхронизации позиций — ничего, примитивная читалка с каталогом за деньги.


            Понадобилось это как-то продвигать. У меня не было опыта в мобильном маркетинге, и я договорился о взаимном консалтинге с компанией Alawar Entertainment, с подразделением мобильных игр. Я консультировал их по классическому вебу, а они меня — по мобильному сегменту. Так я набрался знаний, и мы начали продвигать свою читалку. В 2012 году выручка выросла на 120 %. Тогда в России мало кто занимался мобильным маркетингом так же активно. Мы просто скупали кучу трафика. Как только в AdMob, впоследствии Facebook, появилась реклама, мы были одними из первых в России, кто начал её активно скупать.


            Если бы мы в 2011 году решили сначала сделать суперклассный продукт с кучей возможностей и запустили бы его на год позже, то мы бы прозевали решающий 2012 год. Когда я пришёл в Litres, то один из моих знакомых вёл сделку с Dream Industries. Проект Bookmate. И в конце 2011 года эти ребята подняли 30 млн долларов. А у меня бюджет на маркетинг в 2012 году был 9 млн рублей. Как вы думаете, какие у меня появились мысли? А я проработал в Litres только месяц. Прибегаю к генеральному директору: «Серега, что делать?» — «Ну, работать».


            Как мы работали? Как лошадям закрывают глаза шорами, чтобы они не видели, что происходит вокруг, так и мы просто понеслись вперёд. Прошёл год, оборачиваемся — а что-то и не видать никого сзади. Time to Market — вовремя сделанный продукт и хороший маркетинг решили судьбу проекта.


            image


            Само ничего не взлетит. Многие думают, что достаточно сделать превосходный продукт, выложить в App Store или Google Play, и оно попрёт как на дрожжах. Это уже давно не работает. Сейчас необходим системный отлаженный маркетинг, привлечение трафика с кучи разных каналов, анализ конверсий и когорт, внутренняя аналитика. Не надейтесь, что кнопочка «Рассказать друзьям» даст вам мощнейший виральный эффект.


            Системный маркетинг


            image


            Здесь отображён базовый подход к маркетингу. Есть показы, клики, установки и т. д. Допустим, вы покупаете трафик на Facebook. Думаете, что если поставить CPI 50 центов, то Facebook принесёт вам кучу установок. Такого не будет, потому что Facebook берёт на себя все риски за плохой креатив, за плохой лендинг, за плохие описания, низкий рейтинг и т. д. Естественно, что при этом он будет завышать планку. Покажу на примере контекстной рекламы. Проводя собеседования на должности в маркетинге, я задавал простой вопрос: «Объясни, как работает контекстная реклама?» Кандидат говорит: «Вот, есть система ранжирования…» — «А зачем она нужна?» — «Чтобы рекламодатели нормально друг с другом, чтобы была честная сортировка…» Всё не так.


            Представьте, что я — коммерческий директор Яндекс.Директа. Моя задача — увеличить выручку на тысячу показов с контекста. Каким KPI можно это выразить? И CPM, выручка на тысячу показов. Как его максимизировать? Алгоритм ранжирования в Яндекс.Директе — это формула, максимизирующая выручку на тысячу показов, и ничего более. Это не система, которая позволяет уравнять в правах рекламодателей. Это система, максимизирующая выручку. Там есть несколько факторов: CTR, CPC. Если у тебя высокий CTR, то на сторону рекламодателя идёт больше трафика, а значит, больше денег.


            Есть ещё один — качество лендинговой страницы. Зачем он нужен? Здесь кандидаты вообще терялись и отвечали: «Чтобы людям было хорошо». На самом деле — для увеличения выручки lifetime value. Надо понимать, что тому, кто продаёт вам трафик, нужны деньги. И этот фактор позволяет контролировать уровень доверия к контекстной рекламе. Если пользователь по объявлению перейдёт на лендинг, где окажется совершенно другая информация, то он потеряет доверие к контекстной рекламе и в долгосрочной перспективе будет реже на неё кликать.


            Напоследок приведу пример того, что мы делали в купонных системах. Это из разряда growth hacks, как можно хакнуть систему. Тогда на рынке была компания Darberry, потом её купил «Групон». У них было размещение на Mail.Ru Group за 300 тыс. рублей в день. Очень дорого, как мне тогда казалось. Такими бюджетами я управлял впервые. CTR был достаточно низкий, а стоимость за лид получалась 86 рублей, хотя по модели она была около 50. Дороговато, ещё и женская аудитория. Что делать? У меня был сотрудник, который отвечал за макеты баннеров. Он в шесть вечера часов заявляет, что ему пора бежать на учёбу. А мне до одиннадцати надо отправить макет. Я говорю: «У меня кампания стартует, а ты уходишь». — «Ну, сорян». Потом я его уволил, но в тот вечер пришлось сесть и подумать, как быть.


            Я просто открыл Paint и нарисовал баннер, написал на белом фоне «–90 %», максимально растянув на весь экран, без окантовки. Не знаю, как тогда это пропустили. Позже, кстати, запретили делать баннеры без окантовки и сделали заливку градиентом. Но тогда это прокатило. Я отправил. Есть книга Стива Круга «Don’t make me think». Надо всё упрощать — чем проще, тем лучше. Благодаря этому примитивному баннеру CTR вырос более чем в восемь раз. Естественно, конверсия в регистрацию упала, но на выходе я всё равно получил стоимость за лид в 34 рубля.


            Правда, потом к нам пришла юридическая служба Mail.Ru Group и сказала, что у вас тут написано «–90 %», а на сайте такой акции нет. Действительно, не было. Потом мы начали подбирать акции под такие большие размещения.


            Свой стартап


            В 2014 году мы с партнёрами решили запустить стартап MoikaMoika. Это запись на мойку без очереди. И звучит неплохо, и рынок приличный. Действительно, есть такая проблема — очереди на мойку. Здесь существует определённая сезонность, но мы рассчитали бизнес-модель, всё прикинули. В 2014 году начали разработку, в феврале 2015-го выпустили на iOS и Android и взялись за маркетинг. Я придумывал всевозможные креативы. Потом, кстати, их блокировали, но какое-то время они работали.


            image


            Вот пример того, что если поработать над креативом, над CTR, эффективностью, если сделать биндинг по CPI, то результат будет гораздо хуже.


            Начальный капитал стартапа — примерно по 300 тыс. с каждого из основателей. Пример расчёта бизнес-модели MoikaMoika с когортами, конверсиями, примерным подходом:


            image


            MAPS.ME


            Что касается MAPS.ME, то сегодня проект где-то на шестом месте в мире по размеру аудитории, при этом мы не пытаемся быть Google Maps, у нас своя ниша и свои пользователи. Сейчас мы — карты для путешественников: лёгкие, быстрые и надёжные, созданные для работы без интернета. В этом сегменте мы лидеры, поскольку основная масса навигаторов «старой гвардии» вообще не рассчитана на пешеходную навигацию, а туристов, передвигающихся без автомобиля, — около 70 %. По оценкам Google, 40 % путешественников предпочитают использовать офлайн-карты.


            image


            Именно за этот сегмент рынка мы боремся. В год в мире порядка 1,3 млрд путешественников, нам их вполне достаточно :) Однако перед нами стоит задача бороться также за ежедневное использование MAPS.ME, для чего мы активно работаем над функционалом, который позволит лучше удерживать аудиторию. К примеру, в декабре 2016 года мы запустили пробки по 38 странам мира, что позволило поднять уровень дневной аудитории среди автомобилистов и конвертировать пользователей из туристического сценария использования в ежедневный. При этом мы внедряем в весь добавляемый функционал простоту и надёжность: пробки эффективно сжимаются и потребляют в четыре раза меньше мобильного трафика, чем приложения конкурентов, что позволяет использовать нашу навигацию с пробками даже в роуминге. Также весь функционал — поиск, авто-, вело- и пешая навигация, закладки, заметки — работает без интернета. Это не значит, что наши пользователи вообще обходятся без него: по нашей статистике, более 80 % подключаются к интернету в течение дня, что позволяет нам зарабатывать на партнёрских программах с туристическими гигантами вроде Booking.com, с которыми мы за полгода вошли в топ-10 партнёров по объёму бронирований. Интеграция Booking.com для нас — это не только возможность заработать, но и востребованный функционал поиска отелей на карте. Он весьма удобен, если вам важно видеть, где находится отель и как далеко он расположен от транспортных узлов и основных достопримечательностей.


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


            Если коротко говорить о нашей долгосрочной стратегии, то в рамках туристического сегмента стоит задача расширить применение MAPS.ME, превратить его из инструмента «добраться из точки А в точку В» в полноценный помощник путешественника. Поэтому мы планируем добавлять функционал, востребованный среди туристов: UGC (user-generated content) — рейтинги, отзывы, чтобы туристу было легче выбрать ресторан или любое другое заведение; поиск интересных мест; покупку билетов в музеи и на различные местные активности. Мы убеждены, что карта — это инструмент базовой регулярной потребности, вокруг которого можно надстраивать дополнительный комплементарный функционал, расширяющий сценарии туристического использования. При этом востребованность офлайн-карт будет только расти, особенно в развивающихся странах, где мобильный интернет стоит дорого и имеет плохое покрытие.


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


            У нас сильная команда разработчиков и маркетологов и отлаженная система планирования бизнес-процессов и управления ими. Наш девиз: хорошему продукту — хороший маркетинг. Это позволяет достигать максимальной синергии для проактивного развития проекта и добиваться высоких результатов.



            Полезные материалы


            Я выложил все свои наработки в открытый доступ бесплатно для любого использования (некоммерческого/коммерческого): www.lisovskiy.ru/edu. Там есть шаблоны примеров расчёта бизнес-моделей в табличках, презентации. Есть подробный обучающий курс: в 2016 году я постарался собрать воедино весь свой практический опыт. Надеюсь, кому-то это пригодится и поможет в запуске новых проектов и развитии существующих.

            Комментарии (0)

              Let's block ads! (Why?)