pc1crypt: make decrypt/encrypt functions take void * as argument
[project/firmware-utils.git] / src / zycast.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * zycast - push images via multicast to a ZyXEL bootloader
4 *
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
8 * closed source.
9 *
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.
16 *
17 * Protocol description:
18 *
19 * UDP to multicast destination address 225.0.0.0 port 5631. Source
20 * address and port is arbitrary.
21 *
22 * Payload is split in packets prepended with a 30 byte header:
23 *
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]
31 * 8 bit flags [9]
32 * 5 byte reserved [10]
33 *
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
37 *
38 * [2] all integers are in network byte order, i.e. big endian
39 *
40 * [3] checksum = sum >> 16 + sum, where sum is the sum of all
41 * payload bytes
42 *
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
47 *
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
51 * does not exist.
52 *
53 * [6] the length of each file in case of a multi file transfer.
54 *
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.
58 *
59 * The known image types are
60 *
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)
66 *
67 * The supported set of images vary among implementations.
68 * The protocol may support other image types.
69 *
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
75 * result in a brick.
76 *
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
80
81 * [9] bitmap controlling actions taken after a complete transfer:
82 *
83 * 0x01 - set DebugFlag
84 * 0x02 - erase "rom"
85 * 0x04 - erase "rom-d"
86 *
87 * Other, unknown, values may exist in the protocol. Device
88 * support may vary.
89 *
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.
94 *
95 * Copyright (C) 2024 Bjørn Mork <bjorn@mork.no>
96 */
97
98 #include <arpa/inet.h>
99 #include <errno.h>
100 #include <fcntl.h>
101 #include <inttypes.h>
102 #include <netinet/in.h>
103 #include <signal.h>
104 #include <stdbool.h>
105 #include <stddef.h>
106 #include <stdio.h>
107 #include <stdlib.h>
108 #include <string.h>
109 #include <sys/mman.h>
110 #include <sys/socket.h>
111 #include <sys/stat.h>
112 #include <unistd.h>
113
114 /* defaulting to 10 ms interpacket delay */
115 static int pktdelay = 10000;
116 static int sockfd = -1;
117 static bool exiting;
118
119 /* All integers are stored in network order (big endian) */
120 struct zycast_t {
121 uint32_t magic;
122 uint16_t chksum;
123 uint32_t pid;
124 uint32_t plen;
125 uint32_t flen;
126 uint16_t unusedbits;
127 unsigned char type;
128 unsigned char images;
129 char cc[2];
130 unsigned char flags;
131 char reserved[5];
132 } __attribute__ ((packed));
133
134 #define HDRSIZE (sizeof(struct zycast_t))
135 #define DEST_ADDR "225.0.0.0"
136 #define DEST_PORT 5631
137 #define CHUNK 1024
138 #define MAGIC 0x7a797800 /* "zyx" */
139
140 #define BIT(nr) (1 << (nr))
141
142 enum imagetype {
143 BOOTBASE = 0,
144 ROM,
145 RAS,
146 ROMD,
147 BACKUP,
148 _MAX_IMAGETYPE
149 };
150
151 #define FLAG_SET_DEBUG BIT(0)
152 #define FLAG_ERASE_ROM BIT(1)
153 #define FLAG_ERASE_ROMD BIT(2)
154
155 static void errexit(const char *msg)
156 {
157 fprintf(stderr, "ERR: %s: %s\n", msg, errno ? strerror(errno) : "unknown");
158 exit(EXIT_FAILURE);
159 }
160
161 static void *map_input(const char *name, size_t *len)
162 {
163 struct stat stat;
164 void *mapped;
165 int fd;
166
167 fd = open(name, O_RDONLY);
168 if (fd < 0)
169 return NULL;
170 if (fstat(fd, &stat) < 0) {
171 close(fd);
172 return NULL;
173 }
174 *len = stat.st_size;
175 mapped = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
176 if (close(fd) < 0) {
177 (void) munmap(mapped, stat.st_size);
178 return NULL;
179 }
180 return mapped;
181 }
182
183 static uint16_t chksum(uint8_t *p, size_t len)
184 {
185 int i;
186 uint32_t sum = 0;
187
188 for (i = 0; i < len; i++)
189 sum += *p++;
190 return (uint16_t)((sum >> 16) + sum);
191 }
192
193 static int pushimage(void *file, struct zycast_t *phdr)
194 {
195 uint32_t count = 0;
196 uint32_t len = ntohl(phdr->flen);
197 uint32_t plen = CHUNK;
198
199 while (!exiting && len > 0) {
200 if (len < CHUNK)
201 plen = len;
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)");
209 file += plen;
210 len -= plen;
211
212 /* No need to kill the network. The target can't
213 * process packets as fast as we send them anyway.
214 */
215 usleep(pktdelay);
216 }
217 return 0;
218 }
219
220 static void sig_handler(int signo)
221 {
222 if (signo == SIGINT)
223 exiting = true;
224 }
225
226 static void usage(const char *name)
227 {
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");
237 #ifdef DO_BOOTBASE
238 fprintf(stderr, "\t-u bootloader flash new bootloader\n");
239 fprintf(stderr, "\nWARNING: bootloader upgrades are dangerous. DON'T DO IT!\n");
240 #endif
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);
245 if (sockfd >= 0)
246 close(sockfd);
247 exit(EXIT_FAILURE);
248 }
249
250 #define ADD_IMAGE(nr) \
251 do { \
252 hdr.images |= BIT(nr); \
253 file[nr] = map_input(optarg, &len[nr]); \
254 if (!file[nr]) \
255 errexit(optarg); \
256 } while (0)
257
258 int main(int argc, char **argv)
259 {
260 void *file[_MAX_IMAGETYPE] = {};
261 size_t len[_MAX_IMAGETYPE] = {};
262 struct zycast_t hdr = {
263 .magic = htonl(MAGIC),
264 .cc = {'F', 'F' },
265 .flags = FLAG_SET_DEBUG,
266 };
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),
271 };
272 int i, c;
273
274 if (signal(SIGINT, sig_handler) == SIG_ERR)
275 errexit("signal()");
276 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
277 if (sockfd < 0)
278 errexit("socket()");
279 if (connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) < 0)
280 errexit("connect()");
281
282 while ((c = getopt(argc, argv, "i:t:f:b:d:r:u:")) != -1) {
283 switch (c) {
284 case 'i':
285 if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, optarg, strlen(optarg)) < 0)
286 errexit(optarg);
287 break;
288 case 't':
289 i = strtoul(optarg, NULL, 0);
290 if (i < 1)
291 i = 1;
292 pktdelay = i * 1000;
293 break;
294 case 'f':
295 ADD_IMAGE(RAS);
296 break;
297 case 'b':
298 ADD_IMAGE(BACKUP);
299 break;
300 case 'd':
301 ADD_IMAGE(ROM);
302 break;
303 case 'r':
304 ADD_IMAGE(ROMD);
305 break;
306 case 'u':
307 #ifdef DO_BOOTBASE
308 ADD_IMAGE(BOOTBASE);
309 break;
310 #endif
311 default:
312 usage(argv[0]);
313 }
314 }
315
316 if (!hdr.images)
317 usage(argv[0]);
318
319 fprintf(stderr, "Press Ctrl+C to stop before rebooting target after upgrade\n");
320 while (!exiting) {
321 for (i = 0; i < _MAX_IMAGETYPE; i++) {
322 if (hdr.images & BIT(i)) {
323 hdr.type = BIT(i);
324 hdr.flen = htonl(len[i]);
325 pushimage(file[i], &hdr);
326 }
327 }
328 };
329
330 fprintf(stderr, "\nClosing all files\n");
331 if (sockfd >= 0)
332 close(sockfd);
333 for (i = 0; i < _MAX_IMAGETYPE; i++)
334 if (hdr.images & BIT(i))
335 munmap(file[i], len[i]);
336
337 return EXIT_SUCCESS;
338 }