1 // SPDX-License-Identifier: GPL-2.0-only
3 * zycast - push images via multicast to a ZyXEL bootloader
5 * Many ZyXEL devices supports image manipulation using a multicast
6 * based protocol. The protocol is not documented publicly, and
7 * both the bootloader embedded part and the official clients are
10 * This client is based on the following description of the protocol.
11 * which is reverse engineered from bootloader binaries. It is likely
12 * to be both incomplete and inaccurate, as it only covers the
13 * observed implementation on a limited set of devices. No client
14 * implementation or network packets were available for the protocol
15 * reverse engineering.
17 * Protocol description:
19 * UDP to multicast destination address 225.0.0.0 port 5631. Source
20 * address and port is arbitrary.
22 * Payload is split in packets prepended with a 30 byte header:
24 * 4 byte signature: 'z', 'y', 'x', 0x0 [1]
25 * 16 bit checksum [2][3]
26 * 32 bit packet id [2][4]
27 * 32 bit packet length [2][5]
28 * 32 bit file length [2][6]
29 * 32 bit image bitmap [2][7]
30 * 2 byte ascii country code [8]
32 * 5 byte reserved [10]
34 * [1] the terminating null is not actually checked by the observed
35 * implementations, but is assumed to be safest in case the
36 * signature is treated as a string
38 * [2] all integers are in network byte order, i.e. big endian
40 * [3] checksum = sum >> 16 + sum, where sum is the sum of all
43 * [4] starts at 0 and is incremented by 1 for each packet. Used both
44 * to ensure sequential, loss free, unidirectional transport, and to
45 * allow the transfer to start at any point. The sequence must be
46 * repeated until the transfer is complete
48 * [5] Testing indicates that some implementations expect 1024 byte
49 * packets. Smaller size results in a corrupt download, and larger
50 * size causes the download to hang - waiting for packet ids which
53 * [6] the length of each file in case of a multi file transfer.
55 * [7] the lower 8 bits is a bitmap of all image types included in the
56 * transfer. Bits 8 - 16 contains the image type for this packet.
57 * The purpose of the upper 16 bits is unknown.
59 * The known image types are
61 * 0x01 - "bootbase" (often "Bootloader" partition)
62 * 0x02 - "rom" (often "data" partition)
63 * 0x04 - "ras" (often "Kernel" partition)
64 * 0x08 - "romd" (often "rom-d" partition)
65 * 0x10 - "backup" (often "Kernel2" partition)
67 * The supported set of images vary among implementations.
68 * The protocol may support other image types.
70 * WARNING: The flash offset of each supported image type is hard
71 * coded in the bootloader server implementation. There is no
72 * relation to the bootloader configuration, and no way to verify
73 * that those values are correct without decompiling that
74 * implementations. Device specific bugs are likely, and may
77 * [8] two upper case ascii characters, like 'D','E'. The purpose
78 * is unknown, but ZyXEL devices are often configured with this
79 * as one of their device specific variables
81 * [9] bitmap controlling actions taken after a complete transfer:
83 * 0x01 - set DebugFlag
85 * 0x04 - erase "rom-d"
87 * Other, unknown, values may exist in the protocol. Device
90 * [10] these bytes are not used by the observed implementations.
91 * The purpose is therefore unknown. There is a risk
92 * they are interpreted by other devices, resulting in
93 * unexpected and potentially harmful behaviour.
95 * Copyright (C) 2024 Bjørn Mork <bjorn@mork.no>
98 #include <arpa/inet.h>
101 #include <inttypes.h>
102 #include <netinet/in.h>
109 #include <sys/mman.h>
110 #include <sys/socket.h>
111 #include <sys/stat.h>
114 /* defaulting to 10 ms interpacket delay */
115 static int pktdelay
= 10000;
116 static int sockfd
= -1;
119 /* All integers are stored in network order (big endian) */
128 unsigned char images
;
132 } __attribute__ ((packed
));
134 #define HDRSIZE (sizeof(struct zycast_t))
135 #define DEST_ADDR "225.0.0.0"
136 #define DEST_PORT 5631
138 #define MAGIC 0x7a797800 /* "zyx" */
140 #define BIT(nr) (1 << (nr))
151 #define FLAG_SET_DEBUG BIT(0)
152 #define FLAG_ERASE_ROM BIT(1)
153 #define FLAG_ERASE_ROMD BIT(2)
155 static void errexit(const char *msg
)
157 fprintf(stderr
, "ERR: %s: %s\n", msg
, errno
? strerror(errno
) : "unknown");
161 static void *map_input(const char *name
, size_t *len
)
167 fd
= open(name
, O_RDONLY
);
170 if (fstat(fd
, &stat
) < 0) {
175 mapped
= mmap(NULL
, stat
.st_size
, PROT_READ
, MAP_SHARED
, fd
, 0);
177 (void) munmap(mapped
, stat
.st_size
);
183 static uint16_t chksum(uint8_t *p
, size_t len
)
188 for (i
= 0; i
< len
; i
++)
190 return (uint16_t)((sum
>> 16) + sum
);
193 static int pushimage(void *file
, struct zycast_t
*phdr
)
196 uint32_t len
= ntohl(phdr
->flen
);
197 uint32_t plen
= CHUNK
;
199 while (!exiting
&& len
> 0) {
202 phdr
->plen
= htonl(plen
);
203 phdr
->pid
= htonl(count
++);
204 phdr
->chksum
= htons(chksum(file
, plen
));
205 if (send(sockfd
, phdr
, HDRSIZE
, MSG_MORE
| MSG_DONTROUTE
) < 0)
206 errexit("send(phdr)");
207 if (send(sockfd
, file
, plen
, MSG_DONTROUTE
) < 0)
208 errexit("send(payload)");
212 /* No need to kill the network. The target can't
213 * process packets as fast as we send them anyway.
220 static void sig_handler(int signo
)
226 static void usage(const char *name
)
228 fprintf(stderr
, "Usage:\n");
229 fprintf(stderr
, " %s [options]\n", name
);
230 fprintf(stderr
, "Options:\n");
231 fprintf(stderr
, "\t-i interface outgoing interface for multicast packets\n");
232 fprintf(stderr
, "\t-t delay interpacket delay in milliseconds\n");
233 fprintf(stderr
, "\t-f rasimage primary firmware image\n");
234 fprintf(stderr
, "\t-b backupimage secondary firmware image (if supported)\n");
235 fprintf(stderr
, "\t-d rom data for the \"rom\" or \"data\" partition\n");
236 fprintf(stderr
, "\t-r romd data for the \"rom-d\" partition\n");
238 fprintf(stderr
, "\t-u bootloader flash new bootloader\n");
239 fprintf(stderr
, "\nWARNING: bootloader upgrades are dangerous. DON'T DO IT!\n");
241 fprintf(stderr
, "\nNOTE: some bootloaders will flash a rasimage to both primary and\n");
242 fprintf(stderr
, "secondary firmware partitions\n");
243 fprintf(stderr
, "\nExample:\n");
244 fprintf(stderr
, " %s -i eth1 -t 20 -f openwrt-initramfs.bin\n\n", name
);
250 #define ADD_IMAGE(nr) \
252 hdr.images |= BIT(nr); \
253 file[nr] = map_input(optarg, &len[nr]); \
258 int main(int argc
, char **argv
)
260 void *file
[_MAX_IMAGETYPE
] = {};
261 size_t len
[_MAX_IMAGETYPE
] = {};
262 struct zycast_t hdr
= {
263 .magic
= htonl(MAGIC
),
265 .flags
= FLAG_SET_DEBUG
,
267 const struct sockaddr_in dest
= {
268 .sin_family
= AF_INET
,
269 .sin_addr
.s_addr
= inet_addr(DEST_ADDR
),
270 .sin_port
= htons(DEST_PORT
),
274 if (signal(SIGINT
, sig_handler
) == SIG_ERR
)
276 sockfd
= socket(AF_INET
, SOCK_DGRAM
, 0);
279 if (connect(sockfd
, (struct sockaddr
*)&dest
, sizeof(dest
)) < 0)
280 errexit("connect()");
282 while ((c
= getopt(argc
, argv
, "i:t:f:b:d:r:u:")) != -1) {
285 if (setsockopt(sockfd
, SOL_SOCKET
, SO_BINDTODEVICE
, optarg
, strlen(optarg
)) < 0)
289 i
= strtoul(optarg
, NULL
, 0);
319 fprintf(stderr
, "Press Ctrl+C to stop before rebooting target after upgrade\n");
321 for (i
= 0; i
< _MAX_IMAGETYPE
; i
++) {
322 if (hdr
.images
& BIT(i
)) {
324 hdr
.flen
= htonl(len
[i
]);
325 pushimage(file
[i
], &hdr
);
330 fprintf(stderr
, "\nClosing all files\n");
333 for (i
= 0; i
< _MAX_IMAGETYPE
; i
++)
334 if (hdr
.images
& BIT(i
))
335 munmap(file
[i
], len
[i
]);