До появления ботнета Mirai только особо интересующиеся знали о том, что находится внутри обычных IP камер. В большинстве случаев там стоит обычный линукс, причем частенько с дефолтным рутовым паролем, а то и вообще без него: у нас в офисе стоит такая камера, с прошивкой от декабря 2016 года и беспарольным рутовым телнетом.
Но что же дальше, какой софт запущен на этом линуксе? Есть несколько классных статей datacompboy про поиск бага которого нет, есть ещё разрозненная информация, но в целом ситуация такая: на IP-камере стоит специально пропатченное ядро, которое дает доступ программе через специальную библиотеку к железу, выдающему сжатые видеокадры.
Грустная реальность в том, что очень часто этот софт написан далеко не лучшим образом. Достаточно сказать, что большинство камер, которые висят на улице очень страдают из-за большого расстояния до сервера, потому что авторы их прошивки освоили мастерство потерь данных по TCP.
Мы решили исправить эту ситуацию своей прошивкой, причем сделав ставку на Rust.
Нужно сделать пару пустяков: разобраться с SDK, написать код, который настраивает железо, принимает H264 кадры и отправляет их в сеть. Пара пустяков, особенно учитывая насколько легко и просто деплоиться на IP камеры и отлаживать это всё. Ну и оставшая мелочь: мы решили писать этот код на Rust.
Rust в качестве эксперимента был выбран за его удивительное свойство: compile time гарантию целостности памяти вместе с отсутствием рантайма. Это означает, что мы можем ожидать возможность контроля за выделением памяти, что очень важно, учитывая стесненность по ресурсам.
Почему не подойдет Go, Erlang или какая-нибудь Java/C#? Потому что на IP камере флешка на 8 мегабайт и 128 мегабайт памяти из которых половина отнята у ядра под нужды видео. Понятно, что камеры есть разные, но всегда стараются делать по минимуму, чтобы не поднимать стоимость без нужды. На одной камере мы видели флешку на 64 мегабайта, там конечно можно развернуться, но хватает и совсем крохотных флешек.
Так, обычная картина на дешевой камере за 3000 рублей мы видим:
В таких условиях паршиво написанный софт начинает очень сильно страдать уже от 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..) и аналитика.
Вот как раз стример у нас ржавый. Целая пачка unsafe кода, автогенеренного из сишного кода SDK с помощью bindgen, подпатченый биндинг к libc (постараемся залить патч в апстрим) и дальше реализация RTSP на tokio. Даже уже есть возможность посмотреть видео с камеры в обычном браузере — это недостижимая роскошь для китайских камер, которые поголовно требуют установку ActiveX.
Структура очень непривычна после эрланга: ведь тут нет процессов и сообщений, есть каналы, а с ними всё становится немножко по-другому. Как я уже выше писал, современно написанный код с правильной организацией дает возможность раздавать видео не 2-3 клиентам, а более 50 без какой-либо просадки производительности.
Важный момент: за время разработки пока не случилось ни единого сегфолта. Пока есть стойкое ощущение, что Rust заставляет писать так, как в принципе пишут хорошие поседевшие сишники, повидавшие всякого нехорошего. Так что пока всё нравится.
В течение августа есть планы закончить работу по базовому сценарию, так что есть вопрос к аудитории, который идет в опросе. Ну и задавайте вопросы, которые возникли.
Но что же дальше, какой софт запущен на этом линуксе? Есть несколько классных статей 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.36% что умеет IP камера и как устроен SDK?132
82.13% как делается такой embedded на Rust?239
Проголосовал 291 пользователь. Воздержались 47 пользователей.