В комментариях к статье “Прошиваем роутер Upvel UR-313N4G на OpenWRT” между вашим покорным слугой и уважаемым Maysoft завязался спор насчет различий в структуре образов uImage и sysupgrade прошивки OpenWRT. Я обещал Maysoft разобраться в проблеме, и вот перед вами эта статья.

Как известно, в каталоге загрузок OpenWRT доступны, по большей части, прошивки двух типов — uImage и sysupgrade, например:

openwrt-15.05-rc3-ramips-rt305x-dir-320-b1-initramfs-uImage.bin
openwrt-15.05-rc3-ramips-rt305x-dir-320-b1-squashfs-sysupgrade.bin

Официальный FAQ пишет об их различиях весьма скупо:
What is the difference between the different image formats?
a factory image is one built for the bootloader flasher or stock software flasher
a sysupgrade image (previously named trx image) is designed to be flashed from within openwrt itself
The two have the same content, but a factory image would have extra header information or whatever the platform needs. Generally speaking, the factory image is to be used with the OEM GUI or OEM flashing utilities to convert the device to OpenWrt. After that, use the sysupgrade images.

Согласно документации, содержание образов идентично, за исключением того, что в образе factory присутствуют дополнительные заголовки, чтобы этот образ можно было прошить через Web-интерфейс оригинальной прошивки.

Отлично, сравним размер прошивок:

openwrt-15.05-rc3-ramips-rt305x-dir-320-b1-initramfs-uImage.bin — 3253035 байт.

openwrt-15.05-rc3-ramips-rt305x-dir-320-b1-squashfs-sysupgrade.bin — 3407876 байт.

Ого, прошивка sysupgrade почти на 140 Кб больше uImage, а по документации они должны быть примерно одного размера, причем uImage за счет этой самой “extra header information” — немного больше.

Конечно, достаточно посмотреть в сборочные скрипты, чтобы понять, чем различаются uImage и sysupgrade-образы, но это, согласитесь, неспортивно. Сегодня мы будем анализировать прошивки “в лоб”, как будто у нас нет исходников, а уже в конце заглянем в сборочные скрипты, чтобы подтвердить наши догадки.

Основным средством анализа прошивок на данный момент является утилита binwalk, доступная под Linux. Переименуем файлы прошивок покороче, чтобы нам было удобнее, и начнем анализ.

> binwalk ./uImage.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             uImage header, header size: 64 bytes, header CRC: 0x19DE1499, created: Fri Jul  3 22:16:00 2015, image size: 3252971 bytes, Data Address: 0x80000000, Entry Point: 0x80000000, data CRC: 0x886ADE01, OS: Linux, CPU: IPS, image type: OS Kernel Image, compression type: lzma, image name: "MIPS OpenWrt Linux-3.18.17"
64            0x40            LZMA compressed data, properties: 0x6D, dictionary size: 8388608 bytes, uncompressed size: 5479932 bytes

Кажется, вся прошивка представляет собой образ uImage — в начале следует заголовок длиной 64 (0x40) байт, а вслед за нам — поток данных, сжатый алгоритмом LZMA, размером 3252971 байт. Сложим 64 и 3252971, получим 3253035 байт, то есть размер скачанного образа. Следовательно, кроме образа uImage в файле больше ничего нет. Binwalk умеет распаковывать найденные LZMA-потоки. В принципе, можно вручную отрезать от файла первые 64 байта и распаковать остаток командой lzma -d, но зачем, когда есть такой удобный инструмент?

> binwalk -e ./uImage.bin

Посмотрим, что у нас получилось
> ls -l ./_uImage.bin.extracted/
итого 8532                                                                                                                                                                          
-rw-r--r-- 1 user user 5479932 авг  8 23:10 40                                                                                                                                  
-rw-r--r-- 1 user user 3252971 авг  8 23:10 40.7z

Файл с именем 40 (смещение в исходном файле) — и есть распакованный поток. Натравим на него binwalk:

binwalk ./_uImage.bin.extracted/40

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
2812692       0x2AEB14        Linux kernel version "3.18.17 (buildbot@builder1) (gcc version 4.8.3 (OpenWrt/Linaro c version 4.8.3 (OpenWrt/Linaro GCC 4.8-2014.04 r46018) ) #2 Fr"
2932132       0x2CBDA4        LZMA compressed data, properties: 0x5D, dictionary size: 16777216 bytes, missing uncompressed size
2936592       0x2CCF10        xz compressed data
3400392       0x33E2C8        LZMA compressed data, properties: 0x6D, dictionary size: 1048576 bytes, uncompressed size: -1 bytes

А здесь у нас что-то, на первый взгляд, непонятное — binwalk обнаружил ядро Linux по смещению 0x2AEB14 и три сжатых потока данных, следующих за ядром. Дело в том, что binwalk для анализа использует эвристики и то, что получается у него на выходе — не истина в последней инстанции, а информация к размышлению.
Здравый смысл подсказывает, что ядро должно начинаться со смещения 0, а сжатый поток — быть один и содержать initramfs — начальную файловую систему, загружаемую в RAM. О том же говорит и документация на ядро:
What is initramfs?
— All 2.6 Linux kernels contain a gzipped «cpio» format archive, which is extracted into rootfs when the kernel boots up. After extracting, the kernel checks to see if rootfs contains a file «init», and if so it executes it as PID 1.

и далее
Populating initramfs:
— The 2.6 kernel build process always creates a gzipped cpio format initramfs archive and links it into the resulting kernel binary. By default, this archive is empty (consuming 134 bytes on x86).

Здесь же упоминается и формат потока — CPIO.

Посмотрим, что binwalk сможет извлечь из нашего образа:

> binwalk -e ./_uImage.bin.extracted/40
ls -l ./_uImage.bin.extracted/_40.extracted/
итого 14028
-rw-r--r-- 1 user user 2547808 авг  8 23:25 2CBDA4.7z
-rw-r--r-- 1 user user 2543340 авг  8 23:25 2CCF10.tar
-rw-r--r-- 1 user user 7186944 авг  8 23:25 33E2C8
-rw-r--r-- 1 user user 2079540 авг  8 23:25 33E2C8.7z

Итак, успешно распаковался только поток по смещению 33E2C8. Если мы все правильно делаем, то это должен быть CPIO-контейнер с файловой системой:

> binwalk _uImage.bin.extracted/_40.extracted/33E2C8

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             ASCII cpio archive (SVR4 with no CRC), file name: "dev", file name length: "0x00000004", file size: "0x00000000"
116           0x74            ASCII cpio archive (SVR4 with no CRC), file name: "dev/console", file name length: "0x0000000C", file size: "0x00000000"
240           0xF0            ASCII cpio archive (SVR4 with no CRC), file name: "lib", file name length: "0x00000004", file size: "0x00000000"
356           0x164           ASCII cpio archive (SVR4 with no CRC), file name: "lib/netifd", file name length: "0x0000000B", file size: "0x00000000"
480           0x1E0           ASCII cpio archive (SVR4 with no CRC), file name: "lib/netifd/netifd-wireless.sh", file name length: "0x0000001E", file size: "0x00001638"

***********Куча файлов***********

7186416       0x6DA7F0        ASCII cpio archive (SVR4 with no CRC), file name: "dev/urandom", file name length: "0x0000000C", file size: "0x00000000"
7186540       0x6DA86C        ASCII cpio archive (SVR4 with no CRC), file name: "dev/pts", file name length: "0x00000008", file size: "0x00000000"
7186660       0x6DA8E4        ASCII cpio archive (SVR4 with no CRC), file name: "TRAILER!!!", file name length: "0x0000000B", file size: "0x00000000"

В конце архива мы видим файл с именем TRAILER!!!.. который. согласно документации, является меткой конца архива.

Значит, структура прошивки initramfs-uImage такова:



Теперь возьмемся за образ squashfs-sysupgrade. Из названия следует, что в образе содержится (кроме ядра) файловая система squashfs. Посмотрим, так ли это:

> binwalk -e ./sysupgrade.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             uImage header, header size: 64 bytes, header CRC: 0x66CC90D2, created: Mon Jul  6 21:54:35 2015, image size: 1142606 bytes, Data Address: 0x80000000, Entry Point: 0x80000000, data CRC: 0x91B77696, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "MIPS OpenWrt Linux-3.18.17"
64            0x40            LZMA compressed data, properties: 0x6D, dictionary size: 8388608 bytes, uncompressed size: 3396940 bytes
1142670       0x116F8E        Squashfs filesystem, little endian, version 4.0, compression:lzma (non-standard type definition), size: 2221946 bytes,  1132 inodes, blocksize: 262144 bytes, created: Mon Jul  6 21:54:02 2015


Возьмемся за арифметику: 64 + 1142606 (image size) = 1142670, как раз по этому смещению начинается образ squashfs, а заканчивается он по смещению 1142670 + 2221946 = 3364616. Размер образа, между тем, 3407876 байт, значит, у нас есть еще 3407876 — 3364616 = 43260 байт неидентифицированной информации. Посмотрим, что там, hexdump’ом
> hexdump -s 3364616 ./sysupgrade.bin 
0335708 ffff ffff ffff ffff ffff ffff ffff ffff
*
0335ff8 ffff ffff ffff ffff adde dec0 ffff ffff
0336008 ffff ffff ffff ffff ffff ffff ffff ffff
*
0337ff8 ffff ffff ffff ffff adde dec0 ffff ffff
0338008 ffff ffff ffff ffff ffff ffff ffff ffff
*
033fff8 ffff ffff ffff ffff adde dec0          
0340004

Тут явно какая-то заглушка. Вернемся к ней позднее.

Посмотрим, что у нас в каталоге с распакованным образом:

> ls -l _sysupgrade.bin.extracted/
итого 8820
-rw-r--r-- 1 user user 2221946 авг  8 23:40 116F8E.squashfs
-rw-r--r-- 1 user user 3396940 авг  8 23:40 40
-rw-r--r-- 1 user user 3407812 авг  8 23:40 40.7z

Распакуем LZMA-поток по смещению 40:

binwalk -e _sysupgrade.bin.extracted/40

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
2812644       0x2AEAE4        Linux kernel version "3.18.17 (buildbot@builder1) (gcc version 4.8.3 (OpenWrt/Linaro c version 4.8.3 (OpenWrt/Linaro GCC 4.8-2014.04 r46018) ) #1 Fr"
2932068       0x2CBD64        LZMA compressed data, properties: 0x5D, dictionary size: 16777216 bytes, missing uncompressed size
2936444       0x2CCE7C        xz compressed data
3396424       0x33D348        ASCII cpio archive (SVR4 with no CRC), file name: "dev", file name length: "0x00000004", file size: "0x00000000"
3396540       0x33D3BC        ASCII cpio archive (SVR4 with no CRC), file name: "dev/console", file name length: "0x0000000C", file size: "0x00000000"
3396664       0x33D438        ASCII cpio archive (SVR4 with no CRC), file name: "root", file name length: "0x00000005", file size: "0x00000000"
3396780       0x33D4AC        ASCII cpio archive (SVR4 with no CRC), file name: "TRAILER!!!", file name length: "0x0000000B", file size: "0x00000000"

Здесь у нас ядро Linux и небольшой initramfs-образ с четырьмя файлами. Остальное, видимо, переместилось в образ squashfs:

> unsquashfs -i ./_sysupgrade.bin.extracted/116F8E.squashfs 
Parallel unsquashfs: Using 4 processors
1033 inodes (1034 blocks) to write

squashfs-root
squashfs-root/bin
squashfs-root/bin/ash
squashfs-root/bin/board_detect
squashfs-root/bin/busybox
***********Куча файлов***********

squashfs-root/www/luci-static/resources/load.svg
squashfs-root/www/luci-static/resources/wifirate.svg
squashfs-root/www/luci-static/resources/wireless.svg
squashfs-root/www/luci-static/resources/xhr.js

created 848 files
created 99 directories
created 184 symlinks
created 0 devices
created 0 fifos

Действительно, основная файловая система содержится в образе squashfs.

Теперь разберемся с заглушкой в конце образа. Есть подозрение, что она как-то относится к ФС JFFS2, потому что при прошивке образа sysupgrade и последующей загрузке в dmesg появляются строки:

[   29.550000] jffs2_scan_eraseblock(): End of filesystem marker found at 0x0
[   29.580000] jffs2_build_filesystem(): unlocking the mtd device... done.
[   29.590000] jffs2_build_filesystem(): erasing all blocks after the end marker...

а при прошивке и загрузке образа uImage этих строк нет. Поиск в “ванильном“ ядре по этим строкам ничего не дает, а вот в дереве исходных кодов OpenWRT такие строки есть:

--- a/fs/jffs2/build.c
+++ b/fs/jffs2/build.c
@@ -114,6 +114,16 @@ static int jffs2_build_filesystem(struct
 	dbg_fsbuild("scanned flash completely\n");
 	jffs2_dbg_dump_block_lists_nolock(c);
 
+	if (c->flags & (1 << 7)) {
+		printk("%s(): unlocking the mtd device... ", __func__);
+		mtd_unlock(c->mtd, 0, c->mtd->size);
+		printk("done.\n");
+
+		printk("%s(): erasing all blocks after the end marker... ", __func__);
+		jffs2_erase_pending_blocks(c, -1);
+		printk("done.\n");
+	}
+
 	dbg_fsbuild("pass 1 starting\n");
 	c->flags |= JFFS2_SB_FLAG_BUILDING;
 	/* Now scan the directory tree, increasing nlink according to every dirent found. */
--- a/fs/jffs2/scan.c
+++ b/fs/jffs2/scan.c
@@ -148,8 +148,14 @@ int jffs2_scan_medium(struct jffs2_sb_in
 		/* reset summary info for next eraseblock scan */
 		jffs2_sum_reset_collected(s);
 
-		ret = jffs2_scan_eraseblock(c, jeb, buf_size?flashbuf:(flashbuf+jeb->offset),
-						buf_size, s);
+		if (c->flags & (1 << 7)) {
+			if (mtd_block_isbad(c->mtd, jeb->offset))
+				ret = BLK_STATE_BADBLOCK;
+			else
+				ret = BLK_STATE_ALLFF;
+		} else
+			ret = jffs2_scan_eraseblock(c, jeb, buf_size?flashbuf:(flashbuf+jeb->offset),
+							buf_size, s);
 
 		if (ret < 0)
 			goto out;
@@ -561,6 +567,17 @@ full_scan:
 			return err;
 	}
 
+	if ((buf[0] == 0xde) &&
+		(buf[1] == 0xad) &&
+		(buf[2] == 0xc0) &&
+		(buf[3] == 0xde)) {
+		/* end of filesystem. erase everything after this point */
+		printk("%s(): End of filesystem marker found at 0x%x\n", __func__, jeb->offset);
+		c->flags |= (1 << 7);
+
+		return BLK_STATE_ALLFF;````
+	}
+
 	/* We temporarily use 'ofs' as a pointer into the buffer/jeb */
 	ofs = 0;
 	max_ofs = EMPTY_SCAN_SIZE(c->sector_size);

Ага, вот и наша заглушка 0xDEADCODE. Если драйвер JFFS2 находит эту метку, то он считает ее концом предыдущей файловой системы и стирает все, начиная с нулевого байта метки и заканчивая концом накопителя. Таким образом сама метка также затирается.
После этого драйвер создает на этом месте новый экземпляр JFFS2.
Итак, получается следующая структура образа:



Таким образом:
  1. uImage содержит минимально необходимый функционал для запуска OpenWRT и за счет этого его структуру можно легко изменить так, чтобы она проходила проверки на корректность в Web-интерфейсах оригинальных прошивок.
  2. Sysupgrade устроен сложнее и использует Linux-специфичные инструменты — SquashFS и JFFS2.
  3. Оба типа образов не содержат начального загрузчика (и не должны)
  4. При прошивке через начальный загрузчик (через UART или аварийное восстановление) можно сразу шить sysuprade.
  5. При прошивке через mtd_write, если эта утилита доступна через telnet из официальной прошивки, также можно сразу шить sysupgrade.


В заключении заглянем сборочные скрипты OpenWRT, чтобы убедиться в своих выводах. Начнем с файла /target/linux/ramips/Makefile. Но если вы думаете, что это обычный GNU Makefile, то это не так. Вот как описывают улучшенный Makefile сами разработчики:
Looking at one of the package makefiles, you'd hardly recognize it as a makefile. Through what can only be described as blatant disregard and abuse of the traditional make format, the makefile has been transformed into an object oriented template which simplifies the entire ordeal.

Кажется, сборка образа запускается в строке 564:

Image/Build/Profile/DIR-320-B1=$(call BuildFirmware/Default8M/$(1),$(1),dir-320-b1,DIR-320-B1)

Цель BuildFirmware/Default8M описана в строках 195 и 196:

BuildFirmware/Default8M/squashfs=$(call BuildFirmware/OF,$(1),$(2),$(3),$(ralink_default_fw_size_8M),$(4))
BuildFirmware/Default8M/initramfs=$(call BuildFirmware/OF/initramfs,$(1),$(2),$(3),$(4))

Она состоит из двух позиций: squashfs и initramfs. Цели BuildFirmware/OF и BuildFirmware/OF/initramfs описаны в том же файле чуть выше

149    # $(1), Rootfs type, e.g. squashfs
150    # $(2), lowercase board name
151    # $(3), DTS filename without .dts extension
152    # $(4), maximum size of sysupgrade image
153    # $(5), uImage header's ih_name field
154    define BuildFirmware/OF
155            $(call MkImageLzmaDtb,$(2),$(3),$(5))
156            $(call MkImageSysupgrade/$(1),$(1),$(2),$(4),$(6))
157    endef

и

169    # $(1), squashfs/initramfs
170    # $(2), lowercase board name
171    # $(3), DTS filename without .dts extension
172    # $(4), ih_name field of uImage header
173    define BuildFirmware/OF/initramfs
174            $(call MkImageLzmaDtb,$(2),$(3),$(4),-initramfs)
175            $(CP) $(KDIR)/vmlinux-$(2)-initramfs.uImage $(call imgname,$(1),$(2))-uImage.bin
176    endef

По имени цели MkImageLzmaDtb можно догадаться, что она создает образ uImage по описанию из DTS-файла. Для цели BuildFirmware/OF/initramfs к этому образу добавляется раздел initramfs с основной файловой системой, после чего образ копируется в выходной каталог. Для цели BuildFirmware/OF исходный образ обрабатывается функцией MkImageSysupgrade, которая с помощью команды “cat” прицепляет к нему раздел squashfs, а затем, взывает функцию prepare_generic_squashfs, определенную в файле image.mk.

# pad to 4k, 8k, 16k, 64k, 128k, 256k and add jffs2 end-of-filesystem mark
110	   define prepare_generic_squashfs
111	           $(STAGING_DIR_HOST)/bin/padjffs2 $(1) 4 8 16 64 128 256
112	   endef

А утилита padjffs2, написанная на C, записывает отметку 0xDEADCODE в конец файла образа:

static unsigned char eof_mark[4] = {0xde, 0xad, 0xc0, 0xde};
***
static unsigned char *pad = eof_mark;
***
   /* write out the JFFS end-of-filesystem marker */
	                t = write(fd, pad, pad_len);
	                if (t != pad_len) {
	                        ERRS("Unable to write to %s", name);
	                        goto close;
	                }

В общем, мир OpenWRT прекрасен и удивителен, особенно доставляют комментарии типа:
# The real magic happens inside these templates

или
/* vodoo from original driver */

В заключение хочется сказать, что при сборке OpenWRT из исходных кодов нужно обращать внимание на сборочную среду, например, в этом репозитории на моем Debian 8+Sid не собирается даже набор инструментов для кросс-компиляции. А вот на Debian 7 все это богатство собирается просто изумительно, если не править начальную конфигурацию. Если же ее поправить, может оказаться, что некоторые Web-источники, из которых сборочный скрипт скачивает исходные коды, уже “протухли”, надо искать новые источники, исправлять скрипт и так далее.

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


  1. 4aba
    16.08.2015 22:08

    Спасибо, а я всегда думал что sysupgrade для обновления OpenWRT, а factory для перепрошивки, и с опаской шью самосборные прошивки на wt3020, потому как factory даёт ошибку а sysupgrade нет


  1. Wedmer
    16.08.2015 23:01
    +2

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


  1. MaxFactor
    16.08.2015 23:04

    Всегда использовал DD-WRT — очень стабильна и не подводила, подскажите насколько сильна разница с OpenWRT?


    1. Wedmer
      16.08.2015 23:26

      Разница в чём? Если в надёжности, то OpenWrt крайне надёжен. На счёт dd-wrt ничего не знаю.


    1. alexyr
      17.08.2015 08:03

      OpenWRT открыта и есть пакетный менеджер. Oчень удобно добавлять желаемый функционал. Наверняка есть ещё отличия…


    1. 4aba
      17.08.2015 09:18

      Отличия в удобстве. Есть правда кое какие моменты которые в OpenWRT нужно подумать а в dd-wrt работают из коробки (например VPN), но это компенсируется удобством в настройке что с консоли что с веба, простота сборки прошивки с выбором пакетов в прошивки ( что даёт возможность записать пакеты которые не устанавливаются из-за недостатка места, благодаря оптимизации ( или сжатию, не суть) и докучи всунуть в прошивку например логин-пароль инета. и при factory reset получить сразу рабочий инет.
      + использование места на встроенной памяти ( в dd-wrt такого не видел, когда пользовался)


      1. MaxFactor
        17.08.2015 11:14

        В dd-wrt тоже это есть. Но вот добавления в прошивку нужно функционала нету точно, а пакетный менеджер тоже есть.


        1. reji
          17.08.2015 15:20
          +1

          Несовсем, dd-wrt открыт(opensource) лишь частично, вы можете пересобрать прошивку, используя их SDK. Но это нетривиальный процесс с почти отсутствующей документацией. Однажды пройдя этот квест, все профиты, полученные этим трудом, оказались ниже удобства OpenWRT.

          P.S. Добавлял coovachilli + кастомные страницы веб интерфейса. Удалось, но я вам настоятельно советую не повторять этот квест.


    1. klirichek
      17.08.2015 10:50
      +3

      Если аналогию с десктопами — это примерно как ubuntu vs gentoo.
      В openwrt всё достаточно чётко структурировано по пакетам/папкам — тут ядро, тут тулзы, тут вебморда и т.д. Причём разный дополнительный софт (например, tor, transmission-daemon и т.д. фактически является частью системы и ставится единообразно. Разве что скачивается из отдельных реп; но в дереве конфига потом уже без разницы). Разработка идёт непрерывно; новые фичи и обновлённые пакеты появляются в транке практически сразу. И да — я говорю про сборку из исходников. Она достаточно беспроблемна.
      dd-wrt принято ставить из бинарей. Исходники можно _попробовать_ собрать, но геморроя с этим гораздо больше, чем в openwrt. Сами исходники организованы как попало (например, какие-нибудь специфические модули ядра могут лежать в произвольной папке и ссылаться на другие исходники прямыми связями). Дополнительного софта в базовой сборке вообще не предполагается. Собрать модуль ядра, чтобы запустить какую-нубидь специфическую железку (3g модем или принтер) — совсем нетривиальная задача. Но зато есть и плюсы — монолитная система (скачал блоб, прошил — и работает). Есть проприетарные модули для разных чипов, аналогов которых нет в openwrt (например, если производитель железа даёт собственный скомпилированный блоб — ddwrt его заюзает и включит в прошивку; openwrt не будет).
      Ну и — дополнительные софт для ddwrt всё же можно поставить через optware. Но у этого подхода есть минусы (например, увеличенное потребление памяти из-за дубля системных библиотек; невозможно так просто собрать что-то низкоуровневое, что нужно запускать как модуль ядра).


      1. Wedmer
        17.08.2015 16:50

        Я бы не сказал, что сборка транка беспроблемна. Но сообщество старается, чтобы она была таковой.


    1. kt315
      17.08.2015 12:49
      +2

      Если сравнивать эти две прошивки, то dd-wrt больше тяготеет к proprietary ПО и больше прошивка, чем полноценная ОС. Касательно расширяемости, то она просто лучше стока и не более того.
      А вот OpenWRT — это как полноценная ОС как для широко распространненного, так и для специфичного железа (роутеры, raspberry pi, и т. д.), со всеми возможностями полноценной ОС. Очень легко собрать прошивку для своего роутера со своим блэкджеком и… ну вы поняли :)


      1. Wedmer
        17.08.2015 16:52

        OpenWrt можно собрать и для микроволновки, если архитектура позволит.