udp-transfer

a file transfer program written in C using UDP


udp-transfer / receiver.c

receiver.c (5.5K) raw | download
1#define _POSIX_C_SOURCE 200809L
2#include <sys/socket.h>
3#include <netinet/in.h>
4#include <arpa/inet.h>
5#include <poll.h>
6#include <fcntl.h>
7#include <unistd.h>
8#include <errno.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14
15#define PAYLOAD 1024
16#define HEADER 7
17#define DATA 1
18#define ACK 2
19#define END 3
20#define IDLE_TIMEOUT 10000
21#define LINGER 6000
22
23static int64_t
24now_ms(void)
25{
26 struct timespec ts;
27 if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) return -1;
28 return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
29}
30
31static int
32wait_packet(int fd, int64_t deadline)
33{
34 struct pollfd pfd = {fd, POLLIN, 0};
35 for (;;) {
36 int64_t now = now_ms();
37 int result;
38 if (now < 0) return -1;
39 if (now >= deadline) return 0;
40 result = poll(&pfd, 1, (int)(deadline - now));
41 if (result < 0 && errno == EINTR) continue;
42 if (result <= 0) return result;
43 if (pfd.revents & POLLIN) return 1;
44 errno = EIO;
45 return -1;
46 }
47}
48
49static int
50write_all(int fd, const unsigned char *buf, size_t len)
51{
52 while (len > 0) {
53 ssize_t n = write(fd, buf, len);
54 if (n < 0 && errno == EINTR) continue;
55 if (n <= 0) return -1;
56 buf += n;
57 len -= (size_t)n;
58 }
59 return 0;
60}
61
62static int
63send_ack(int fd, const struct sockaddr_in *peer, uint32_t seq)
64{
65 unsigned char packet[HEADER] = {ACK, 0, 0, 0, 0, 0, 0};
66 uint32_t wire_seq = htonl(seq);
67 ssize_t n;
68 memcpy(packet + 1, &wire_seq, sizeof(wire_seq));
69 do {
70 n = sendto(fd, packet, sizeof(packet), 0,
71 (const struct sockaddr *)peer, sizeof(*peer));
72 } while (n < 0 && errno == EINTR);
73 if (n < 0) return -1;
74 if (n != HEADER) { errno = EIO; return -1; }
75 return 0;
76}
77
78int
79main(int argc, char **argv)
80{
81 struct sockaddr_in address = {0}, peer = {0};
82 uint32_t expected = 0;
83 int fd, output = -1, have_peer = 0, finished = 0, status = 1;
84 int64_t deadline;
85 char *end;
86 long port;
87
88 if (argc != 3) {
89 fprintf(stderr, "usage: %s port output\n", argv[0]);
90 return 1;
91 }
92 errno = 0;
93 port = strtol(argv[1], &end, 10);
94 if (errno || !*argv[1] || *end || port < 1 || port > 65535) {
95 fprintf(stderr, "invalid port: %s\n", argv[1]);
96 return 1;
97 }
98 fd = socket(AF_INET, SOCK_DGRAM, 0);
99 if (fd < 0) { perror("socket"); return 1; }
100 address.sin_family = AF_INET;
101 address.sin_addr.s_addr = htonl(INADDR_ANY);
102 address.sin_port = htons((unsigned short)port);
103 if (bind(fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
104 perror("bind");
105 close(fd);
106 return 1;
107 }
108 output = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
109 if (output < 0) { perror(argv[2]); close(fd); return 1; }
110 deadline = now_ms();
111 if (deadline < 0) { perror("clock_gettime"); goto done; }
112 deadline += IDLE_TIMEOUT;
113 for (;;) {
114 unsigned char packet[HEADER + PAYLOAD + 1];
115 struct sockaddr_in source;
116 socklen_t source_len = sizeof(source);
117 uint32_t seq;
118 uint16_t len;
119 ssize_t n;
120 int result = wait_packet(fd, deadline);
121 if (result < 0) { perror("receive"); break; }
122 if (result == 0) {
123 if (finished) status = 0;
124 else fprintf(stderr, "transfer timed out\n");
125 break;
126 }
127 do {
128 n = recvfrom(fd, packet, sizeof(packet), 0,
129 (struct sockaddr *)&source, &source_len);
130 } while (n < 0 && errno == EINTR);
131 if (n < 0) { perror("recvfrom"); break; }
132 if (n < HEADER || n > HEADER + PAYLOAD ||
133 source_len != sizeof(source) || source.sin_family != AF_INET)
134 continue;
135 memcpy(&seq, packet + 1, sizeof(seq));
136 memcpy(&len, packet + 5, sizeof(len));
137 seq = ntohl(seq);
138 len = ntohs(len);
139 if (n != HEADER + len ||
140 !((packet[0] == DATA && len > 0 && len <= PAYLOAD && seq < UINT32_MAX) ||
141 (packet[0] == END && len == 0)))
142 continue;
143 if (have_peer && (source.sin_addr.s_addr != peer.sin_addr.s_addr ||
144 source.sin_port != peer.sin_port))
145 continue;
146 if (!have_peer) {
147 if (seq != 0) continue;
148 peer = source;
149 have_peer = 1;
150 }
151 if (finished) {
152 if (packet[0] != END || seq != expected) continue;
153 } else if (packet[0] == DATA) {
154 if (seq == expected) {
155 if (write_all(output, packet + HEADER, len) < 0) {
156 perror("write");
157 break;
158 }
159 ++expected;
160 } else if (expected == 0 || seq != expected - 1) {
161 continue;
162 }
163 } else {
164 if (seq != expected) continue;
165 result = close(output);
166 output = -1;
167 if (result < 0) { perror("close output"); break; }
168 finished = 1;
169 /* Keep the socket briefly to acknowledge a retransmitted END. */
170 deadline = now_ms();
171 if (deadline < 0) { perror("clock_gettime"); break; }
172 deadline += LINGER;
173 }
174 if (send_ack(fd, &peer, seq) < 0) { perror("sendto"); break; }
175 if (!finished) {
176 deadline = now_ms();
177 if (deadline < 0) { perror("clock_gettime"); break; }
178 deadline += IDLE_TIMEOUT;
179 }
180 }
181done:
182 if (output >= 0 && close(output) < 0) { perror("close output"); status = 1; }
183 close(fd);
184 return status;
185}