Initial revision
[people/sha0/gpxe.git] / contrib / mini-slamd / mini-slamd.c
1 /*
2  * mini-slamd
3  * (c) 2002 Eric Biederman
4  */
5
6 #include <string.h>
7 #include <errno.h>
8 #include <stdio.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <sys/types.h>
12 #include <sys/poll.h>
13 #include <sys/socket.h>
14 #include <sys/stat.h>
15 #include <netinet/ip.h>
16 #include <netinet/in.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #include <arpa/inet.h>
20
21 /*
22  * To specify the default interface for multicast packets use:
23  * route add -net 224.0.0.0 netmask 240.0.0.0 dev eth1
24  * This server is stupid and does not override the default.
25  */
26
27 /* Sever states.
28  *
29  * Waiting for clients.
30  * Sending data to clients.
31  * Pinging clients for data.
32  *
33  */
34 #define SLAM_PORT 10000
35 #define SLAM_MULTICAST_IP ((239<<24)|(255<<16)|(1<<8)|(1<<0))
36 #define SLAM_MULTICAST_PORT 10000
37 #define SLAM_MULTICAST_TTL 1
38 #define SLAM_MULTICAST_LOOPBACK 1
39 #define SLAM_MAX_CLIENTS 10
40
41 #define SLAM_PING_TIMEOUT       100 /* ms */
42
43 /*** Packets Formats ***
44  * Data Packet:
45  *   transaction
46  *   total bytes
47  *   block size
48  *   packet #
49  *   data
50  *
51  * Status Request Packet
52  *   transaction
53  *   total bytes
54  *   block packets
55  *
56  * Status Packet
57  *   received packets
58  *   requested packets
59  *   received packets
60  *   requested packets
61  *   ...
62  *   received packets
63  *   requested packtes
64  *   0
65  */
66
67 #define MAX_HDR (7 + 7 + 7) /* transaction, total size, block size */
68 #define MIN_HDR (1 + 1 + 1) /* transaction, total size, block size */
69
70 #define MAX_DATA_HDR (MAX_HDR + 7) /* header, packet # */
71 #define MIN_DATA_HDR (MAX_HDR + 1) /* header, packet # */
72
73 /* ETH_MAX_MTU 1500 - sizeof(iphdr) 20  - sizeof(udphdr) 8 = 1472 */
74 #define SLAM_MAX_NACK           (1500 - (20 + 8))
75 /* ETH_MAX_MTU 1500 - sizeof(iphdr) 20  - sizeof(udphdr) 8 - MAX_HDR = 1451 */
76 #define SLAM_BLOCK_SIZE         (1500 - (20 + 8 + MAX_HDR))
77
78
79 /* Define how many debug messages you want 
80  * 1 - sparse but useful
81  * 2 - everything
82  */
83 #ifndef DEBUG
84 #define DEBUG 0
85 #endif
86
87 static int slam_encode(
88         unsigned char **ptr, unsigned char *end, unsigned long value)
89 {
90         unsigned char *data = *ptr;
91         int bytes;
92         bytes = sizeof(value);
93         while ((bytes > 0) && ((0xff & (value >> ((bytes -1)<<3))) == 0)) {
94                 bytes--;
95         }
96         if (bytes <= 0) {
97                 bytes = 1;
98         }
99         if (data + bytes >= end) {
100                 return -1;
101         }
102         if ((0xe0 & (value >> ((bytes -1)<<3))) == 0) {
103                 /* packed together */
104                 *data = (bytes << 5) | (value >> ((bytes -1)<<3));
105         } else {
106                 bytes++;
107                 *data = (bytes << 5);
108         }
109         bytes--;
110         data++;
111         while(bytes) {
112                 *(data++) = 0xff & (value >> ((bytes -1)<<3));
113                 bytes--;
114         }
115         *ptr = data;
116         return 0;
117 }
118
119 static unsigned long slam_decode(unsigned char **ptr, unsigned char *end, int *err)
120 {
121         unsigned long value;
122         unsigned bytes;
123         if (*ptr >= end) {
124                 *err = -1;
125         }
126         bytes = ((**ptr) >> 5) & 7;
127         if ((bytes == 0) || (bytes > sizeof(unsigned long))) {
128                 *err = -1;
129                 return 0;
130         }
131         if ((*ptr) + bytes >= end) {
132                 *err =  -1;
133         }
134         value = (**ptr) & 0x1f;
135         bytes--;
136         (*ptr)++;
137         while(bytes) {
138                 value <<= 8;
139                 value |= **ptr;
140                 (*ptr)++;
141                 bytes--;
142         }
143         return value;
144 }
145
146
147 static struct sockaddr_in client[SLAM_MAX_CLIENTS];
148 static int clients;
149
150
151 void del_client(struct sockaddr_in *old)
152 {
153         int i;
154         for(i = 0; i < clients; i++) {
155                 if ((client[i].sin_family == old->sin_family) &&
156                         (client[i].sin_addr.s_addr == old->sin_addr.s_addr) &&
157                         (client[i].sin_port == old->sin_port)) {
158                         memmove(&client[i], &client[i+1],
159                                 (clients - (i+1))*sizeof(client[0]));
160                         clients--;
161                 }
162         }
163 }
164
165 void add_client(struct sockaddr_in *new)
166 {
167         del_client(new);
168         if (clients >= SLAM_MAX_CLIENTS)
169                 return;
170         memcpy(&client[clients], new, sizeof(*new));
171         clients++;
172 }
173
174 void push_client(struct sockaddr_in *new)
175 {
176         del_client(new);
177         if (clients >= SLAM_MAX_CLIENTS) {
178                 clients--;
179         }
180         memmove(&client[1], &client[0], clients*sizeof(*new));
181         memcpy(&client[0], new, sizeof(*new));
182         clients++;
183 }
184
185
186 void next_client(struct sockaddr_in *next)
187 {
188         /* Find the next client we want to ping next */
189         if (!clients) {
190                 next->sin_family = AF_UNSPEC;
191                 return;
192         }
193         /* Return the first client */
194         memcpy(next, &client[0], sizeof(*next));
195 }
196
197 int main(int argc, char **argv)
198 {
199         char *filename;
200         uint8_t nack_packet[SLAM_MAX_NACK];
201         int nack_len;
202         uint8_t request_packet[MAX_HDR];
203         int request_len;
204         uint8_t data_packet[MAX_DATA_HDR +  SLAM_BLOCK_SIZE];
205         int data_len;
206         uint8_t *ptr, *end;
207         struct sockaddr_in master_client;
208         struct sockaddr_in sa_src;
209         struct sockaddr_in sa_mcast;
210         uint8_t mcast_ttl;
211         uint8_t mcast_loop;
212         int sockfd, filefd;
213         int result;
214         struct pollfd fds[1];
215         int state;
216 #define STATE_PINGING      1
217 #define STATE_WAITING      2
218 #define STATE_RECEIVING    3
219 #define STATE_TRANSMITTING 4
220         off_t size;
221         struct stat st;
222         uint64_t transaction;
223         unsigned long packet;
224         unsigned long packet_count;
225         unsigned slam_port, slam_multicast_port;
226         struct in_addr slam_multicast_ip;
227
228         slam_port = SLAM_PORT;
229         slam_multicast_port = SLAM_MULTICAST_PORT;
230         slam_multicast_ip.s_addr = htonl(SLAM_MULTICAST_IP);
231         
232         if (argc != 2) {
233                 fprintf(stderr, "Bad argument count\n");
234                 fprintf(stderr, "Usage: mini-slamd filename\n");
235                 exit(EXIT_FAILURE);
236         }
237         filename = argv[1];
238         filefd = -1;
239         size = 0;
240         transaction = 0;
241
242         /* Setup the udp socket */
243         sockfd = socket(PF_INET, SOCK_DGRAM, 0);
244         if (sockfd < 0) {
245                 fprintf(stderr, "Cannot create socket\n");
246                 exit(EXIT_FAILURE);
247         }
248         memset(&sa_src, 0, sizeof(sa_src));
249         sa_src.sin_family = AF_INET;
250         sa_src.sin_port = htons(slam_port);
251         sa_src.sin_addr.s_addr = INADDR_ANY;
252
253         result = bind(sockfd, &sa_src, sizeof(sa_src));
254         if (result < 0) { 
255                 fprintf(stderr, "Cannot bind socket to port %d\n", 
256                         ntohs(sa_src.sin_port));
257                 exit(EXIT_FAILURE);
258         }
259
260         /* Setup the multicast transmission address */
261         memset(&sa_mcast, 0, sizeof(sa_mcast));
262         sa_mcast.sin_family = AF_INET;
263         sa_mcast.sin_port = htons(slam_multicast_port);
264         sa_mcast.sin_addr.s_addr = slam_multicast_ip.s_addr;
265         if (!IN_MULTICAST(ntohl(sa_mcast.sin_addr.s_addr))) {
266                 fprintf(stderr, "Not a multicast ip\n");
267                 exit(EXIT_FAILURE);
268         }
269
270         /* Set the multicast ttl */
271         mcast_ttl = SLAM_MULTICAST_TTL;
272         setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL,
273                 &mcast_ttl, sizeof(mcast_ttl));
274
275         /* Set the multicast loopback status */
276         mcast_loop = SLAM_MULTICAST_LOOPBACK;
277         setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &mcast_loop, sizeof(mcast_loop));
278
279
280         state = STATE_WAITING;
281         packet = 0;
282         packet_count = 0;
283         fds[0].fd = sockfd;
284         fds[0].events = POLLIN;
285         fds[0].revents = 0;
286         for(;;) {
287                 switch(state) {
288                 case STATE_PINGING:
289                         state = STATE_WAITING;
290                         next_client(&master_client);
291                         if (master_client.sin_family == AF_UNSPEC) {
292                                 break;
293                         }
294 #if DEBUG
295                         printf("Pinging %s:%d\n", 
296                                 inet_ntoa(master_client.sin_addr),
297                                 ntohs(master_client.sin_port));
298                         fflush(stdout);
299 #endif
300
301                         /* Prepare the request packet, it is all header */
302                         ptr = request_packet;
303                         end = &request_packet[sizeof(request_packet) -1];
304                         slam_encode(&ptr, end, transaction);
305                         slam_encode(&ptr, end, size);
306                         slam_encode(&ptr, end, SLAM_BLOCK_SIZE);
307                         request_len = ptr - request_packet;
308
309                         result = sendto(sockfd, request_packet, request_len, 0,
310                                 &master_client, sizeof(master_client));
311                         /* Forget the client I just asked, when the reply
312                          * comes in we will remember it again.
313                          */
314                         del_client(&master_client);
315                         break;
316                 case STATE_WAITING:
317                 {
318                         int timeout;
319                         int from_len;
320                         timeout = -1;
321                         if (master_client.sin_family != AF_UNSPEC) {
322                                 timeout = SLAM_PING_TIMEOUT;
323                         }
324                         result = poll(fds, sizeof(fds)/sizeof(fds[0]), timeout);
325                         if (result == 0) {
326                                 /* On a timeout try the next client */
327                                 state = STATE_PINGING;
328                                 break;
329                         }
330                         if (result > 0) {
331                                 from_len = sizeof(master_client);
332                                 result = recvfrom(sockfd, 
333                                         nack_packet,    sizeof(nack_packet), 0,
334                                         &master_client, &from_len);
335                                 if (result < 0)
336                                         break;
337                                 nack_len = result;
338 #if DEBUG
339                                 printf("Received Nack from %s:%d\n",
340                                         inet_ntoa(master_client.sin_addr),
341                                         ntohs(master_client.sin_port));
342                                 fflush(stdout);
343 #endif
344 #if DEBUG
345                                 {
346                                         ptr = nack_packet;
347                                         end = ptr + result;
348                                         packet = 0;
349                                         result = 0;
350                                         while(ptr < end) {
351                                                 packet += slam_decode(&ptr, end, &result);
352                                                 if (result < 0) break;
353                                                 packet_count = slam_decode(&ptr, end, &result);
354                                                 if (result < 0) break;
355                                                 printf("%d-%d ",
356                                                         packet, packet + packet_count -1);
357                                         }
358                                         printf("\n");
359                                         fflush(stdout);
360                                 }
361 #endif
362                                 /* Forget this client temporarily.
363                                  * If the packet appears good they will be
364                                  * readded.
365                                  */
366                                 del_client(&master_client);
367                                 ptr = nack_packet;
368                                 end = ptr + nack_len;
369                                 result = 0;
370                                 packet = slam_decode(&ptr, end, &result);
371                                 if (result < 0)
372                                         break;
373                                 packet_count = slam_decode(&ptr, end, &result);
374                                 if (result < 0)
375                                         break;
376                                 /* We appear to have a good packet, keep
377                                  * this client.
378                                  */
379                                 push_client(&master_client);
380
381                                 /* Reopen the file to transmit */
382                                 if (filefd != -1) {
383                                         close(filefd);
384                                 }
385                                 filefd = open(filename, O_RDONLY);
386                                 if (filefd < 0) {
387                                         fprintf(stderr, "Cannot open %s: %s\n",
388                                                 filename, strerror(errno));
389                                         break;
390                                 }
391                                 size = lseek(filefd, 0, SEEK_END);
392                                 if (size < 0) {
393                                         fprintf(stderr, "Seek failed on %s: %s\n",
394                                                 filename, strerror(errno));
395                                         break;
396                                 }
397                                 result = fstat(filefd, &st);
398                                 if (result < 0) {
399                                         fprintf(stderr, "Stat failed on %s: %s\n",
400                                                 filename, strerror(errno));
401                                         break;
402                                 }
403                                 transaction = st.st_mtime;
404                                 
405                                 state = STATE_TRANSMITTING;
406                                 break;
407                         }
408                         break;
409                 }
410                 case STATE_RECEIVING:
411                         /* Now clear the queue of received packets */
412                 {
413                         struct sockaddr_in from;
414                         int from_len;
415                         uint8_t dummy_packet[SLAM_MAX_NACK];
416                         state = STATE_TRANSMITTING;
417                         result = poll(fds, sizeof(fds)/sizeof(fds[0]), 0);
418                         if (result < 1)
419                                 break;
420                         from_len = sizeof(from);
421                         result = recvfrom(sockfd, 
422                                 dummy_packet, sizeof(dummy_packet), 0,
423                                 &from, &from_len);
424                         if (result <= 0)
425                                 break;
426 #if DEBUG                               
427                         printf("Received Nack from %s:%d\n",
428                                 inet_ntoa(from.sin_addr),
429                                 ntohs(from.sin_port));
430                         fflush(stdout);
431 #endif
432                         /* Receive packets until I don't get any more */
433                         state = STATE_RECEIVING;
434                         /* Process a  packet */
435                         if (dummy_packet[0] == '\0') {
436                                 /* If the first byte is null it is a disconnect
437                                  * packet.  
438                                  */
439                                 del_client(&from);
440                         }
441                         else {
442                                 /* Otherwise attempt to add the client. */
443                                 add_client(&from);
444                         }
445                         break;
446                 }
447                 case STATE_TRANSMITTING:
448                 {
449                         off_t off;
450                         off_t offset;
451                         ssize_t bytes;
452                         uint8_t *ptr2, *end2;
453
454                         /* After I transmit a packet check for packets to receive. */
455                         state = STATE_RECEIVING;
456
457                         /* Find the packet to transmit */
458                         offset = packet * SLAM_BLOCK_SIZE;
459
460                         /* Seek to the desired packet */
461                         off = lseek(filefd, offset, SEEK_SET);
462                         if ((off < 0) || (off != offset)) {
463                                 fprintf(stderr, "Seek failed on %s:%s\n",
464                                         filename, strerror(errno));
465                                 break;
466                         }
467                         /* Encode the packet header */
468                         ptr2 = data_packet;
469                         end2 = data_packet + sizeof(data_packet);
470                         slam_encode(&ptr2, end2, transaction);
471                         slam_encode(&ptr2, end2, size);
472                         slam_encode(&ptr2, end2, SLAM_BLOCK_SIZE);
473                         slam_encode(&ptr2, end2, packet);
474                         data_len = ptr2 - data_packet;
475                         
476                         /* Read in the data */
477                         bytes = read(filefd, &data_packet[data_len], 
478                                 SLAM_BLOCK_SIZE);
479                         if (bytes <= 0) {
480                                 fprintf(stderr, "Read failed on %s:%s\n",
481                                         filename, strerror(errno));
482                                 break;
483                         }
484                         data_len += bytes;
485                         /* Write out the data */
486                         result = sendto(sockfd, data_packet, data_len, 0,
487                                 &sa_mcast, sizeof(sa_mcast));
488                         if (result != data_len) {
489                                 fprintf(stderr, "Send failed %s\n",
490                                         strerror(errno));
491                                 break;
492                         }
493 #if DEBUG > 1
494                         printf("Transmitted: %d\n", packet);
495                         fflush(stdout);
496 #endif
497                         /* Compute the next packet */
498                         packet++;
499                         packet_count--;
500                         if (packet_count == 0) {
501                                 packet += slam_decode(&ptr, end, &result);
502                                 if (result >= 0)
503                                         packet_count = slam_decode(&ptr, end, &result);
504                                 if (result < 0) {
505                                         /* When a transmission is done close the file,
506                                          * so it may be updated.  And then ping then start
507                                          * pinging clients to get the transmission started
508                                          * again.
509                                          */
510                                         state = STATE_PINGING;
511                                         close(filefd);
512                                         filefd = -1;
513                                         break;
514                                 }
515                         }
516                         break;
517                 }
518                 }
519         }
520         return EXIT_SUCCESS;
521 }