До появления ботнета Mirai только особо интересующиеся знали о том, что находится внутри обычных IP камер. В большинстве случаев там стоит обычный линукс, причем частенько с дефолтным рутовым паролем, а то и вообще без него: у нас в офисе стоит такая камера, с прошивкой от декабря 2016 года и беспарольным рутовым телнетом.

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

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

Мы решили исправить эту ситуацию своей прошивкой, причем сделав ставку на Rust.

Условия работы


Нужно сделать пару пустяков: разобраться с SDK, написать код, который настраивает железо, принимает H264 кадры и отправляет их в сеть. Пара пустяков, особенно учитывая насколько легко и просто деплоиться на IP камеры и отлаживать это всё. Ну и оставшая мелочь: мы решили писать этот код на Rust.

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

Почему не подойдет Go, Erlang или какая-нибудь Java/C#? Потому что на IP камере флешка на 8 мегабайт и 128 мегабайт памяти из которых половина отнята у ядра под нужды видео. Понятно, что камеры есть разные, но всегда стараются делать по минимуму, чтобы не поднимать стоимость без нужды. На одной камере мы видели флешку на 64 мегабайта, там конечно можно развернуться, но хватает и совсем крохотных флешек.

Так, обычная картина на дешевой камере за 3000 рублей мы видим:

# free
             total       used       free     shared    buffers     cached
Mem:         60128      17376      42752          0       2708       4416
-/+ buffers/cache:      10252      49876
Swap:            0          0          0
# cat /proc/cpuinfo 
Processor	: ARM926EJ-S rev 5 (v5l)
BogoMIPS	: 218.72
Features	: swp half thumb fastmult edsp java 
CPU implementer	: 0x41
CPU architecture: 5TEJ
CPU variant	: 0x0
CPU part	: 0x926
CPU revision	: 5

Hardware	: hi3518
Revision	: 0000
Serial		: 0000000000000000

В таких условиях паршиво написанный софт начинает очень сильно страдать уже от 3-4 подключений. Золотое правило при работе с IP камерами: вообще стараться больше одного подключения (или два, по одному на каждое качество) не делать и это связано не только с узким каналом до камеры, но ещё с тем, что четвертый клиент к IP камере зачастую делает невозможным просмотр у первых трех. Забегая вперед, скажу что у нас и на 50 клиентах проблем не возникло.

Как устроена камера


Прежде чем идти дальше, расскажу немножко о том устройстве камеры, с которым мы работаем на текущем этапе.

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

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

У uboot открытые исходники, но в нём хранятся данные, специфичные для конкретной железки. Если скопировать флешку с камеры, сделанной фирмой XM на камеру, сделанную фирмой Hikvision, то есть большой шанс, что даже uboot не загрузится.

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

Но ничего страшного, всё это решаемый вопрос, двигаемся дальше.

А дальше находится ядро линукса. Было бы слишком просто, если бы можно было одно ядро собрать для всех возможных камер и потом просто модули перетыкать. Нет, так нельзя, поэтому для разных версий чипсета нужны разные ядра: где-то 2.x.y, где-то 3.x.y. Почему так? Потому что к ядру идут закрытые модули. Где-то можно исхитриться, но всё равно унифицировать всё не получится.

После этого идет обычный хозбытовой buildroot. Здесь всё как у людей.

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

Захват видео


В захвате видео очень много подготовки железа. Если почитать спецификацию onvif и мануал по SDK IP камеры, то можно увидеть много общего — софтверный интерфейс отражает общую структуру большинства железяк и она следующая: видео снимается с сенсора, немножко обрабатывается, потом засовывается в энкодеры (аппаратные конечно) и потом в софтине можно забрать из определенного места в памяти готовые H264 NAL юниты. Для базового сценария осталось только приделать управление пользователями, настройки и какой-нибудь сетевой протокол. Для полноценной камеры ещё нужна поддержка всяких механизмов массовой настройки (discovery, onvif, psia, etc..) и аналитика.

А чего там про Rust


Вот как раз стример у нас ржавый. Целая пачка unsafe кода, автогенеренного из сишного кода SDK с помощью bindgen, подпатченый биндинг к libc (постараемся залить патч в апстрим) и дальше реализация RTSP на tokio. Даже уже есть возможность посмотреть видео с камеры в обычном браузере — это недостижимая роскошь для китайских камер, которые поголовно требуют установку ActiveX.

Структура очень непривычна после эрланга: ведь тут нет процессов и сообщений, есть каналы, а с ними всё становится немножко по-другому. Как я уже выше писал, современно написанный код с правильной организацией дает возможность раздавать видео не 2-3 клиентам, а более 50 без какой-либо просадки производительности.

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

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

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


  1. miolini
    04.08.2017 18:15
    +1

    Молодцы!


    1. erlyvideo Автор
      04.08.2017 18:16
      +6

      ща, статью откорректирую и на ICO =)


      1. Das_original
        05.08.2017 18:36
        -5

        image
        К сожалению качеством лучше не нашел, но всё же!


  1. varanio
    04.08.2017 18:22
    +4

    Интересно было бы почитать о проблемах, возникающих при программировании на расте


    1. erlyvideo Автор
      04.08.2017 18:24
      +5

      хорошо, постараюсь написать. Они, конечно, есть.


  1. datacompboy
    04.08.2017 20:10
    +4

    Мяса, мяса давай! А то сплошные байки из склепа. Так-то все понятно, но со стороны — информации ноль.


    1. erlyvideo Автор
      04.08.2017 20:36
      +2

      сделаем, сделаем.


  1. datacompboy
    04.08.2017 20:13
    +1

    Сделать опенсорц как хик не хотите ли?


    1. erlyvideo Автор
      04.08.2017 20:36

      а где у них опенсорсная прошивка?


      1. datacompboy
        04.08.2017 21:44

        Ух, обещали но так и нет.
        Тогда как sigrand :)


        1. erlyvideo Автор
          04.08.2017 21:53

          да и у сигранда негусто https://github.com/sigrand


          1. datacompboy
            04.08.2017 22:26

            Сигранд тут живёт: git://sigrand.ru/sigticam.git


            1. erlyvideo Автор
              04.08.2017 23:09
              +1

              ты прям озадачил. Надо подумать, не будет ли здесь проблем от третьих сторон.


  1. apro
    04.08.2017 20:45
    +1

    Было бы интересно про опыт использования tokio в более подробном изложении.


    1. erlyvideo Автор
      04.08.2017 21:05
      +1

      хорошо. Там есть некоторые ньюансы, связанные с tokio_io::codec и его обходом.


  1. tgz
    04.08.2017 21:55
    +2

    Rust очень хорош, вот прямо очень. ЛУчший язык придуманный за последние 10 лет.


    1. khim
      05.08.2017 16:59
      +2

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


      1. tgz
        05.08.2017 21:52
        -1

        Назовите тогда лучший.


        1. ozkriff
          05.08.2017 23:08
          +4

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


          1. tgz
            06.08.2017 08:35
            -1

            Это как то мешает делать выбор и сравнивать? Есть куча параметров независящих от контекста и требований.


            1. ozkriff
              06.08.2017 08:59
              +5

              "Лучший" — это же значит что я в любой ситуации этот язык предпочту всем остальным, так?


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


              1. tgz
                06.08.2017 11:15
                -8

                Люди когда придумывают язык, они знают о конкретных ваших ситуациях? Как же они без этого всего решения выбирают?
                P.S. так как этот пидорский сайт не дает оставлять комментарии тогда, когдя я хочу, то дискуссию я пожалуй продолжать не буду.


  1. WFrag
    05.08.2017 00:03
    +10

    Кому интересен Rust на маленьких микроконтроллерах (типа STM32), есть неплохой блог (на английском): http://blog.japaric.io


    1. erlyvideo Автор
      05.08.2017 00:15

      это bare metal, без линукса?


      1. WFrag
        05.08.2017 00:31
        +3

        Да, это bare metal.


    1. datacompboy
      05.08.2017 00:30

      Блог — очень плохой способ структурировать редкую информацию, вроде описанной… Хорошо для апдейтов, плохо для впитывания.


      1. WFrag
        05.08.2017 00:37
        +2

        Согласен. Но для тех, кто в такие темы погружается набегами, в качестве хобби, любая информация полезна. Я просто недавно искал замену C++ в для своего маленького проекта на STM32 и без этого блога я бы самостоятельно Rust на голом STM32 не осилил бы.


      1. grossws
        05.08.2017 05:21
        +2

        У Jorge Aparicio есть ещё пара вполне структурированных мини-книжек на тему bare metal: https://japaric.github.io/discovery/ для совсем начинающих и https://japaric.github.io/copper/ для продолжающих.


  1. 9660
    05.08.2017 06:44
    -1

    Очень интересно. Но очень мало :)

    Единственный момент который меня озадачил

    В большинстве случаев там стоит обычный линукс, причем частенько с дефолтным рутовым паролем,

    Впервые слышу про дефолтовый рутовый пароль у линукса.


    1. d1f
      05.08.2017 06:59
      +1

      > дефолтовый рутовый пароль

      у данного типа устройства он дефолтный, один на всех.

      Ну и про «обычный линукс» это сильное преувеличение.


      1. erlyvideo Автор
        05.08.2017 10:24
        +1

        не, реально это достаточно обычный билдрутовый линукс.

        Просто в отличие от достаточно сильно развитого openwrt в котором есть пакеты, тут всё сильно попроще, потому что почти вся система readonly.


    1. erlyvideo Автор
      05.08.2017 10:25
      +1

      если перед вами ssh (чаще telnet) к IP камере, то в 80-95% случаев вам поможет один из примерно 40 известных паролей.

      Очень сложно организационно сменить пароль на камере при её установке.


  1. codrem
    06.08.2017 15:53
    +2

    Есть ли шанс заиметь вашу ржавчину чтоб поставить самому? Уж очень печальная там родная прошивка. Приходиться танцы с бубном устраивать чтоб получить вменяемый rtsp/h264


    1. erlyvideo Автор
      06.08.2017 16:21
      +4

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

      Начните с фотографии процессора на камере. Если там что-то типа hi3518 или 3516, то шанс есть, но нужны все буквы. Так например 3516c и 3516a несовместимы вплоть до загрузчика.


    1. erlyvideo Автор
      07.08.2017 00:09

      наверное лучше даже по-другому: у вас есть оригинальная прошивка от вашей камеры?


      1. codrem
        07.08.2017 15:00

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


        1. erlyvideo Автор
          07.08.2017 21:06

          если найдете — попробуем помочь


          1. codrem
            07.08.2017 23:15

            Сравнил cpuinfo — один в один.
            прошивка текущая — V4.02.R12.00006510.10010.140700.00000 / HI3518E_50H10L_S39
            Вот такое нашел когда-то на просторах интернета. + есть бинари с прошивкой за июнь 2016 и сентябрь 2014
            Только что-то я перемудрил в прошлый раз с их CMS и теперь не могу по телнету на камеру попасть.

            И для интересующихся вот много ссылок на всякое разное по этим китайцам.


            1. erlyvideo Автор
              08.08.2017 00:10

              ага, ссылка хорошая.


  1. alex-pat
    08.08.2017 00:03

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


    1. erlyvideo Автор
      08.08.2017 00:08

      пока 900кб


  1. Tuxman
    09.08.2017 22:42

    Три года назад работал в компании, предоставляющей набор охранной системы для частников — это набор беспроводных датчиков и базовой станции, которая общается с «центром» через GSM/3G и имеет бекапную батарейку (грабители обычно обесточивают дом, обрезают телефонную линию, разбивают панель ввода пинкода, думая, что он посылает сигнал тревоги). Я разрабатывал для них IP камеру (прошивку на основе hi3518 и Ambarella A5s/S2Lm) и серверную часть. SDK обычно предоставляет высокоуровневый RTSP/RTP стриминг, но я брал с уровня NAL h264 фреймов и запихивал в пропраетный протокол. Общение камеры с клаудом по типу dropcam, основан на TLS соединении (каждая камера имеет свой сертификат с UUID, подписанный единым CA, что авторизует каждую камеру, и сертификат сервера может быть проверен тоже с помощью CA паблика вшитого в камеру), и протокол на основе protobuf. Серверная часть C++ Boost::ASIO позволяет держать десятки тысяч одновременно подключенных камер на один сервер (стриминг происходит по активации со стороны приложения или по срабатыванию датчиков в доме).

    Интересная тема с просмотром live и записанном видео в браузере и в мобильных клиентах. На Android и iOS мы можем просто скармливать NAL фреймы фреймворку и сама OS будет проигрывать видео, т.е. транспорт полностью может быть пропраетным. С браузерами не всё так просто, либо flash, либо html video tag. Хотя есть вариант плагинов, типа VLC для браузеров, чтобы проигрывать RTSP/RTP потоки, или какой-нибудь ActiveX — но это уже сильно устарело.

    HTML5 video отлично проигрывает HLS и MPEG-DASH во всех современных браузерах, но live video запаздывает на величину 2х-3х сегментов, которые скажем по 2 сек, значит на 4-6 секунд. Мы же хотим видеть live именно live с минимальной задержкой (как в RTSP/RTP). К сожалению, я выбрал вариант Flash и с сервера лил файлы FLV просто потоком, который сразу же проигрывался. FLV формат супер прост, пихаем h264 NAL и AAC фреймы и всё. На мобильных клиентах мы написали простой FLV парсер и скармливали NAL и AAC фреймы системе.


    1. ValdikSS
      10.08.2017 00:12

      С браузерами не всё так просто, либо flash, либо html video tag.
      Вы смотрели в сторону Media Source Extensions? Мне кажется, он отлично подходит для вашего случая.
      На википедии такое примечание даже:
      Unreal HTML5 player uses MSE for low latency (sub-second) live playback of streams sent via WebSockets by Unreal Media Server


      1. Tuxman
        10.08.2017 00:20

        Нет, тогда MSE прошло мимо нашего радара. Да и сам html5 video tag был немного в диковинку, точнее список видео/аудио кодеков и контейнеров, которые поддерживаются тем или иным браузером. Как минимум мобильные браузеры не могли тогда играть html5 video, не знаю как сейчас. Кстати, AAC аудио кодек тоже немного не для нас, мы всё делали в Speex (потом стал opus). Speex хорошо жмёт голос, а не музыку, предоставляя лучший битрейт. FLV может содержать Speex. Ничего такого из html5 video не могло содержать speex.
        P.S. Сейчас я мало знаю про техническую сторону того проекта, уже почти два года в другой компании.


        1. ValdikSS
          10.08.2017 00:22

          Теперь уже OPUS в браузерах поддерживается почти повсеместно, как и webm с VP8/9. YouTube по умолчанию отдает webm VP9 + OPUS разными файлами, объединяя их через Media Source Extensions.