summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile10
-rw-r--r--opentracker.c30
-rw-r--r--ot_batch.c158
-rw-r--r--ot_batch.h48
-rw-r--r--ot_http.c53
-rw-r--r--ot_http.h7
-rw-r--r--proxy.c14
-rw-r--r--tests/runner.c1
-rw-r--r--tests/t_batch.c348
-rw-r--r--tests/test.h1
-rw-r--r--trackerlogic.c3
11 files changed, 591 insertions, 82 deletions
diff --git a/Makefile b/Makefile
index 00a9f93..c7105d4 100644
--- a/Makefile
+++ b/Makefile
@@ -68,9 +68,9 @@ CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread
68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread 68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread
69 69
70BINARY =opentracker 70BINARY =opentracker
71HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h 71HEADERS=trackerlogic.h scan_urlencoded_query.h ot_batch.h ot_buf.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
72SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_buf.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c 72SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_batch.c ot_buf.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
73SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c 73SOURCES_proxy=proxy.c ot_batch.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
74 74
75OBJECTS = $(SOURCES:%.c=%.o) 75OBJECTS = $(SOURCES:%.c=%.o)
76OBJECTS_debug = $(SOURCES:%.c=%.debug.o) 76OBJECTS_debug = $(SOURCES:%.c=%.debug.o)
@@ -119,8 +119,8 @@ $(OBJECTS_proxy_debug): $(HEADERS)
119# has to. tests/stubs.c supplies the few symbols those modules reference but 119# has to. tests/stubs.c supplies the few symbols those modules reference but
120# the tests never reach. 120# the tests never reach.
121TESTBIN=tests/runner 121TESTBIN=tests/runner
122TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c 122TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c
123TEST_UNITS=scan_urlencoded_query.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c 123TEST_UNITS=scan_urlencoded_query.c ot_batch.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c
124TEST_HEADERS=tests/test.h $(HEADERS) 124TEST_HEADERS=tests/test.h $(HEADERS)
125# Tests reach code that sits behind feature flags, so the units under test are 125# Tests reach code that sits behind feature flags, so the units under test are
126# built with those features enabled. A test binary is not a production binary. 126# built with those features enabled. A test binary is not a production binary.
diff --git a/opentracker.c b/opentracker.c
index 96a265a..50fff29 100644
--- a/opentracker.c
+++ b/opentracker.c
@@ -23,7 +23,6 @@
23 23
24/* Libowfat */ 24/* Libowfat */
25#include "io.h" 25#include "io.h"
26#include "iob.h"
27 26
28/* Opentracker */ 27/* Opentracker */
29#include "ot_accesslist.h" 28#include "ot_accesslist.h"
@@ -120,9 +119,8 @@ static ssize_t header_complete(char *request, ssize_t byte_count) {
120static void handle_dead(const int64_t sock) { 119static void handle_dead(const int64_t sock) {
121 struct http_data *cookie = io_getcookie(sock); 120 struct http_data *cookie = io_getcookie(sock);
122 if (cookie) { 121 if (cookie) {
123 size_t i; 122 if (cookie->batch)
124 for (i = 0; i < cookie->batches; ++i) 123 batch_reset(cookie->batch);
125 iob_reset(cookie->batch + i);
126 free(cookie->batch); 124 free(cookie->batch);
127 buf_reset(&cookie->request); 125 buf_reset(&cookie->request);
128 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) 126 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))
@@ -178,33 +176,23 @@ static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
178 176
179static void handle_write(const int64_t sock) { 177static void handle_write(const int64_t sock) {
180 struct http_data *cookie = io_getcookie(sock); 178 struct http_data *cookie = io_getcookie(sock);
181 size_t i;
182 int chunked = 0; 179 int chunked = 0;
183 180
184 /* Look for the first io_batch still containing bytes to write */
185 if (cookie) { 181 if (cookie) {
186 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) 182 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)
187 chunked = 1; 183 chunked = 1;
188 184
189 for (i = 0; i < cookie->batches; ++i) { 185 if (cookie->batch) {
190 if (cookie->batch[i].bytesleft) { 186 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
191 int64_t res = iob_send(sock, cookie->batch + i); 187 handle_dead(sock);
192 188 return;
193 if (res == -3) {
194 handle_dead(sock);
195 return;
196 }
197
198 if (!cookie->batch[i].bytesleft)
199 continue;
200
201 if (res == -1 || res > 0 || i < cookie->batches - 1)
202 return;
203 } 189 }
190 if (batch_pending(cookie->batch))
191 return;
204 } 192 }
205 } 193 }
206 194
207 /* In a chunked transfer after all batches accumulated have been sent, wait for the next one */ 195 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
208 if (chunked) 196 if (chunked)
209 io_dontwantwrite(sock); 197 io_dontwantwrite(sock);
210 else 198 else
diff --git a/ot_batch.c b/ot_batch.c
new file mode 100644
index 0000000..84eb4a8
--- /dev/null
+++ b/ot_batch.c
@@ -0,0 +1,158 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <limits.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <sys/uio.h>
13#include <unistd.h>
14
15/* Opentracker */
16#include "ot_batch.h"
17
18#ifndef IOV_MAX
19#define IOV_MAX 1024
20#endif
21
22#define OT_BATCH_MIN_ENTRIES 16
23
24static int batch_room_for(ot_batch *batch, size_t wanted) {
25 size_t space = batch->space ? batch->space : OT_BATCH_MIN_ENTRIES;
26 struct iovec *grown;
27
28 if (batch->count + wanted <= batch->space)
29 return 0;
30
31 /* During a chunked transfer entries are appended while earlier ones are
32 still going out, so the front of the array frees up as we go. Slide the
33 rest down before asking for more. */
34 if (batch->first && batch->count - batch->first + wanted <= batch->space) {
35 memmove(batch->entries, batch->entries + batch->first, (batch->count - batch->first) * sizeof(struct iovec));
36 batch->count -= batch->first;
37 batch->first = 0;
38 return 0;
39 }
40
41 while (space < batch->count + wanted)
42 space *= 2;
43
44 if (!(grown = realloc(batch->entries, space * sizeof(struct iovec))))
45 return -1;
46
47 batch->entries = grown;
48 batch->space = space;
49 return 0;
50}
51
52int batch_add(ot_batch *batch, void *buf, size_t len) {
53 if (batch_room_for(batch, 1)) {
54 free(buf);
55 return -1;
56 }
57
58 batch->entries[batch->count].iov_base = buf;
59 batch->entries[batch->count].iov_len = len;
60 ++batch->count;
61 return 0;
62}
63
64int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count) {
65 if (batch_room_for(batch, count)) {
66 while (count--)
67 free(entries[count].iov_base);
68 return -1;
69 }
70
71 memcpy(batch->entries + batch->count, entries, count * sizeof(struct iovec));
72 batch->count += count;
73 return 0;
74}
75
76/* Drop everything the kernel accepted, freeing each buffer as it goes */
77static void batch_consume(ot_batch *batch, size_t written) {
78 while (written) {
79 struct iovec *entry = batch->entries + batch->first;
80 size_t left = entry->iov_len - batch->offset;
81
82 if (written < left) {
83 batch->offset += written;
84 return;
85 }
86
87 free(entry->iov_base);
88 written -= left;
89 batch->offset = 0;
90 ++batch->first;
91 }
92
93 /* Nothing is outstanding, so the array can start over from the front */
94 if (batch->first == batch->count)
95 batch->first = batch->count = 0;
96}
97
98/* A peer that has gone away must come back as an error rather than as a
99 signal that kills the tracker, so ask the kernel not to raise one where it
100 can be asked. macOS cannot, and relies on SIGPIPE being blocked. */
101static ssize_t batch_writev(int s, struct iovec *entries, int count) {
102#ifdef MSG_NOSIGNAL
103 struct msghdr msg;
104
105 memset(&msg, 0, sizeof(msg));
106 msg.msg_iov = entries;
107 msg.msg_iovlen = count;
108 return sendmsg(s, &msg, MSG_NOSIGNAL);
109#else
110 return writev(s, entries, count);
111#endif
112}
113
114ssize_t batch_send(int s, ot_batch *batch) {
115 size_t entries = batch->count - batch->first;
116 ssize_t written;
117
118 if (!batch_pending(batch))
119 return BATCH_DONE;
120
121 if (entries > IOV_MAX)
122 entries = IOV_MAX;
123
124 /* The first entry may be half way out already */
125 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base + batch->offset;
126 batch->entries[batch->first].iov_len -= batch->offset;
127 written = batch_writev(s, batch->entries + batch->first, (int)entries);
128 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base - batch->offset;
129 batch->entries[batch->first].iov_len += batch->offset;
130
131 if (written < 0)
132 return (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) ? BATCH_AGAIN : BATCH_FAILED;
133 if (!written)
134 return BATCH_FAILED;
135
136 batch_consume(batch, (size_t)written);
137 return written;
138}
139
140void batch_reset(ot_batch *batch) {
141 while (batch->first < batch->count)
142 free(batch->entries[batch->first++].iov_base);
143
144 free(batch->entries);
145 batch->entries = NULL;
146 batch->count = batch->first = batch->offset = batch->space = 0;
147}
148
149/* For the few constants the chunked encoding needs, which the batch frees
150 along with everything else */
151int batch_add_copy(ot_batch *batch, const void *buf, size_t len) {
152 void *copy = malloc(len);
153
154 if (!copy)
155 return -1;
156 memcpy(copy, buf, len);
157 return batch_add(batch, copy, len);
158}
diff --git a/ot_batch.h b/ot_batch.h
new file mode 100644
index 0000000..c855671
--- /dev/null
+++ b/ot_batch.h
@@ -0,0 +1,48 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_BATCH_H__
7#define OT_BATCH_H__
8
9#include <stddef.h>
10#include <sys/types.h>
11#include <sys/uio.h>
12
13/* A queue of buffers on their way to one socket. The batch owns every buffer
14 handed to it and frees each one as soon as it has left, so a client that
15 reads slowly costs memory only for what it has not received yet.
16
17 A zeroed ot_batch is ready to use. */
18typedef struct {
19 struct iovec *entries;
20 size_t space;
21 size_t count;
22 size_t first; /* entries before this one are sent and freed */
23 size_t offset; /* bytes of entries[first] already sent */
24} ot_batch;
25
26static inline int batch_pending(const ot_batch *batch) { return batch->first != batch->count; }
27
28/* What batch_send reports instead of a byte count */
29enum { BATCH_DONE = 0, BATCH_AGAIN = -1, BATCH_FAILED = -2 };
30
31/* Queue buf and take ownership of it. Returns 0, or -1 having freed buf. */
32int batch_add(ot_batch *batch, void *buf, size_t len);
33
34/* Write as much as the socket takes. Returns the number of bytes written, or
35 BATCH_DONE, BATCH_AGAIN or BATCH_FAILED. */
36ssize_t batch_send(int s, ot_batch *batch);
37
38/* Queue every entry of a vector, taking ownership of each buffer but not of
39 the vector itself. Returns 0, or -1 having freed all of them. */
40int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count);
41
42/* Queue a copy of buf, which the batch then owns. Returns 0, or -1. */
43int batch_add_copy(ot_batch *batch, const void *buf, size_t len);
44
45/* Free whatever has not left yet, along with the batch's own array */
46void batch_reset(ot_batch *batch);
47
48#endif
diff --git a/ot_http.c b/ot_http.c
index 4c4121b..4675efb 100644
--- a/ot_http.c
+++ b/ot_http.c
@@ -15,7 +15,7 @@
15#include <unistd.h> 15#include <unistd.h>
16 16
17/* Libowfat */ 17/* Libowfat */
18#include "iob.h" 18#include "io.h"
19 19
20/* Opentracker */ 20/* Opentracker */
21#include "ot_accesslist.h" 21#include "ot_accesslist.h"
@@ -29,14 +29,7 @@
29#include "scan_urlencoded_query.h" 29#include "scan_urlencoded_query.h"
30#include "trackerlogic.h" 30#include "trackerlogic.h"
31 31
32#ifdef WANT_NO_AUTO_FREE
33#define OT_IOB_INIT(B) (bzero(B, sizeof(io_batch)), 0)
34#else
35#define OT_IOB_INIT(B) iob_init_autofree(B, 0)
36#endif
37
38#define OT_MAXMULTISCRAPE_COUNT 64 32#define OT_MAXMULTISCRAPE_COUNT 64
39#define OT_BATCH_LIMIT (1024 * 1024 * 16)
40extern const char *g_redirecturl; 33extern const char *g_redirecturl;
41 34
42const char *g_stats_path; 35const char *g_stats_path;
@@ -90,20 +83,17 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
90 83
91 memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size); 84 memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size);
92 if (!cookie->batch) { 85 if (!cookie->batch) {
93 cookie->batch = malloc(sizeof(io_batch)); 86 cookie->batch = calloc(1, sizeof(ot_batch));
94 if (!cookie->batch || OT_IOB_INIT(cookie->batch) == -1) { 87 if (!cookie->batch) {
95 free(cookie->batch);
96 free(outbuf); 88 free(outbuf);
97 buf_reset(&cookie->request); 89 buf_reset(&cookie->request);
98 free(cookie); 90 free(cookie);
99 io_close(sock); 91 io_close(sock);
100 return; 92 return;
101 } 93 }
102
103 cookie->batches = 1;
104 } 94 }
105 95
106 iob_addbuf_free(cookie->batch, outbuf, ws->reply_size - written_size); 96 batch_add(cookie->batch, outbuf, ws->reply_size - written_size);
107 97
108 /* writeable short data sockets just have a tcp timeout */ 98 /* writeable short data sockets just have a tcp timeout */
109 if (!ws->keep_alive) { 99 if (!ws->keep_alive) {
@@ -146,10 +136,9 @@ ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code)
146 136
147ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { 137ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) {
148 struct http_data *cookie = io_getcookie(sock); 138 struct http_data *cookie = io_getcookie(sock);
149 io_batch *current;
150 char *header; 139 char *header;
151 const char *encoding = ""; 140 const char *encoding = "";
152 size_t i, header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector); 141 size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector);
153 tai6464 t; 142 tai6464 t;
154 143
155 /* No cookie? Bad socket. Leave. */ 144 /* No cookie? Bad socket. Leave. */
@@ -199,42 +188,24 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t
199 } 188 }
200 189
201 if (!cookie->batch) { 190 if (!cookie->batch) {
202 cookie->batch = malloc(sizeof(io_batch)); 191 cookie->batch = calloc(1, sizeof(ot_batch));
203 if (!cookie->batch || OT_IOB_INIT(cookie->batch) == -1) { 192 if (!cookie->batch) {
204 free(cookie->batch);
205 free(header); 193 free(header);
206 iovec_free(&iovec_entries, &iovector); 194 iovec_free(&iovec_entries, &iovector);
207 HTTPERROR_500; 195 HTTPERROR_500;
208 } 196 }
209 cookie->batches = 1;
210 } 197 }
211 current = cookie->batch + cookie->batches - 1;
212 iob_addbuf_free(current, header, header_size);
213
214 /* Split huge iovectors into separate io_batches */
215 for (i = 0; i < (size_t)iovec_entries; ++i) {
216 /* If the current batch's limit is reached, try to reallocate a new batch to work on */
217 if (current->bytesleft > OT_BATCH_LIMIT) {
218 io_batch *new_batch = realloc(cookie->batch, (cookie->batches + 1) * sizeof(io_batch));
219 198
220 if (new_batch) { 199 batch_add(cookie->batch, header, header_size);
221 cookie->batch = new_batch; 200 batch_add_vector(cookie->batch, iovector, iovec_entries);
222 if (OT_IOB_INIT(cookie->batch + cookie->batches) != -1)
223 ++cookie->batches;
224 }
225 /* realloc may have moved the batches, so current is stale either way */
226 current = cookie->batch + cookie->batches - 1;
227 }
228 iob_addbuf_free(current, iovector[i].iov_base, iovector[i].iov_len);
229 }
230 free(iovector); 201 free(iovector);
202
231 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) 203 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)
232 iob_addbuf(current, "\r\n", 2); 204 batch_add_copy(cookie->batch, "\r\n", 2);
233 } 205 }
234 206
235 if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) { 207 if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) {
236 current = cookie->batch + cookie->batches - 1; 208 batch_add_copy(cookie->batch, "0\r\n\r\n", 5);
237 iob_addbuf(current, "0\r\n\r\n", 5);
238 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; 209 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
239 } 210 }
240 211
diff --git a/ot_http.h b/ot_http.h
index abad83a..003b545 100644
--- a/ot_http.h
+++ b/ot_http.h
@@ -10,9 +10,7 @@
10#include <sys/types.h> 10#include <sys/types.h>
11#include <sys/uio.h> 11#include <sys/uio.h>
12 12
13/* Libowfat */ 13#include "ot_batch.h"
14#include "iob.h"
15
16#include "ot_buf.h" 14#include "ot_buf.h"
17#include "trackerlogic.h" 15#include "trackerlogic.h"
18 16
@@ -27,8 +25,7 @@ typedef enum {
27 25
28struct http_data { 26struct http_data {
29 ot_buf request; 27 ot_buf request;
30 io_batch *batch; 28 ot_batch *batch;
31 size_t batches;
32 ot_ip6 ip; 29 ot_ip6 ip;
33 STRUCT_HTTP_FLAG flag; 30 STRUCT_HTTP_FLAG flag;
34}; 31};
diff --git a/proxy.c b/proxy.c
index 219d926..63b4c4f 100644
--- a/proxy.c
+++ b/proxy.c
@@ -16,9 +16,9 @@
16 16
17/* Libowfat */ 17/* Libowfat */
18#include "io.h" 18#include "io.h"
19#include "iob.h"
20 19
21/* Opentracker */ 20/* Opentracker */
21#include "ot_batch.h"
22#include "ot_ip.h" 22#include "ot_ip.h"
23#include "ot_mutex.h" 23#include "ot_mutex.h"
24#include "ot_random.h" 24#include "ot_random.h"
@@ -228,7 +228,7 @@ typedef struct {
228 size_t indata_length; /* Length of unprocessed data */ 228 size_t indata_length; /* Length of unprocessed data */
229 uint32_t tracker_id; /* How the other end greeted */ 229 uint32_t tracker_id; /* How the other end greeted */
230 int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */ 230 int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */
231 io_batch outdata; /* The iobatch containing our sync data */ 231 ot_batch outdata; /* The batch containing our sync data */
232 232
233 size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */ 233 size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */
234 uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */ 234 uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */
@@ -243,7 +243,7 @@ void reset_info_block(proxy_peer *peer) {
243 peer->tracker_id = 0; 243 peer->tracker_id = 0;
244 peer->fd = -1; 244 peer->fd = -1;
245 peer->packet_tcount = 0; 245 peer->packet_tcount = 0;
246 iob_reset(&peer->outdata); 246 batch_reset(&peer->outdata);
247 PROXYPEER_SETDISCONNECTED(peer->state); 247 PROXYPEER_SETDISCONNECTED(peer->state);
248} 248}
249 249
@@ -424,11 +424,11 @@ static void handle_write(int64_t peersocket) {
424 } 424 }
425 break; 425 break;
426 case FLAG_CONNECTED: 426 case FLAG_CONNECTED:
427 switch (iob_send(peersocket, &peer->outdata)) { 427 switch (batch_send(peersocket, &peer->outdata)) {
428 case 0: /* all data sent */ 428 case BATCH_DONE:
429 io_dontwantwrite(peersocket); 429 io_dontwantwrite(peersocket);
430 break; 430 break;
431 case -3: /* an error occured */ 431 case BATCH_FAILED:
432 io_close(peersocket); 432 io_close(peersocket);
433 reset_info_block(peer); 433 reset_info_block(peer);
434 break; 434 break;
@@ -728,7 +728,7 @@ static void *streamsync_worker(void *args) {
728 void *tmp = malloc(mem); 728 void *tmp = malloc(mem);
729 if (tmp) { 729 if (tmp) {
730 memcpy(tmp, ptr, mem); 730 memcpy(tmp, ptr, mem);
731 iob_addbuf_free(&g_connections[i].outdata, tmp, mem); 731 batch_add(&g_connections[i].outdata, tmp, mem);
732 io_wantwrite(g_connections[i].fd); 732 io_wantwrite(g_connections[i].fd);
733 } 733 }
734 } 734 }
diff --git a/tests/runner.c b/tests/runner.c
index 972a56a..148b928 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -24,6 +24,7 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle
24 {"ot_ip", test_ip}, 24 {"ot_ip", test_ip},
25 {"ot_socket", test_socket}, 25 {"ot_socket", test_socket},
26 {"ot_mutex", test_mutex}, 26 {"ot_mutex", test_mutex},
27 {"ot_batch", test_batch},
27 {"ot_accesslist", test_accesslist}, 28 {"ot_accesslist", test_accesslist},
28 {NULL, NULL}}; 29 {NULL, NULL}};
29 30
diff --git a/tests/t_batch.c b/tests/t_batch.c
new file mode 100644
index 0000000..02743f1
--- /dev/null
+++ b/tests/t_batch.c
@@ -0,0 +1,348 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <fcntl.h>
8#include <poll.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14/* Opentracker */
15#include "ot_batch.h"
16#include "test.h"
17
18/* The batch writes to a real socket pair, because what is worth pinning down
19 is what it does when the kernel takes only part of what it was offered. */
20
21static char *dup_of(const char *text) {
22 size_t len = strlen(text);
23 char *copy = malloc(len);
24
25 memcpy(copy, text, len);
26 return copy;
27}
28
29static void drain(int s, char *into, size_t len) {
30 size_t got = 0;
31
32 while (got < len) {
33 ssize_t r = read(s, into + got, len - got);
34
35 if (r <= 0)
36 break;
37 got += (size_t)r;
38 }
39 into[got] = 0;
40}
41
42static void case_queue_accounting(void) {
43 ot_batch batch;
44
45 memset(&batch, 0, sizeof(batch));
46
47 test_case("a zeroed batch is empty and sends nothing");
48 CHECK_INT(batch_pending(&batch), 0);
49 CHECK_INT(batch_send(-1, &batch), BATCH_DONE);
50
51 test_case("anything queued makes it pending");
52 CHECK_INT(batch_add(&batch, dup_of("hello "), 6), 0);
53 CHECK_INT(batch_add_copy(&batch, "world", 5), 0);
54 CHECK_INT(batch_pending(&batch), 1);
55 CHECK_INT(batch.count, 2);
56
57 batch_reset(&batch);
58 test_case("and a reset empties it");
59 CHECK_INT(batch_pending(&batch), 0);
60 CHECK_PTR(batch.entries, NULL);
61}
62
63static void case_send_in_one_go(void) {
64 ot_batch batch;
65 int sv[2];
66 char got[32];
67
68 memset(&batch, 0, sizeof(batch));
69 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
70
71 test_case("several buffers arrive as one stream, in order");
72 CHECK_INT(batch_add(&batch, dup_of("opentracker"), 11), 0);
73 CHECK_INT(batch_add_copy(&batch, " says ", 6), 0);
74 CHECK_INT(batch_add(&batch, dup_of("hello"), 5), 0);
75 CHECK_INT(batch_send(sv[0], &batch), 22);
76 CHECK_INT(batch_pending(&batch), 0);
77
78 drain(sv[1], got, 22);
79 CHECK_STR(got, "opentracker says hello");
80
81 test_case("and an empty batch reports it has nothing left");
82 CHECK_INT(batch_send(sv[0], &batch), BATCH_DONE);
83
84 batch_reset(&batch);
85 close(sv[0]);
86 close(sv[1]);
87}
88
89/* Send everything the batch holds, reading the other end as we go so the
90 socket never stays full, and return what arrived. */
91static size_t pump(int writer, int reader, ot_batch *batch, char *into, size_t room) {
92 size_t got = 0;
93
94 while (batch_pending(batch) || got < room) {
95 ssize_t r;
96
97 if (batch_pending(batch)) {
98 ssize_t res = batch_send(writer, batch);
99
100 if (res == BATCH_FAILED)
101 break;
102 if (res > 0)
103 continue;
104 }
105
106 r = read(reader, into + got, room - got);
107 if (r <= 0)
108 break;
109 got += (size_t)r;
110 }
111 return got;
112}
113
114static void case_partial_write_resumes(void) {
115 const size_t size = 200000;
116 ot_batch batch;
117 int sv[2], sndbuf = 4096;
118 char *sent, *got;
119 size_t i, arrived;
120
121 memset(&batch, 0, sizeof(batch));
122 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
123 setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
124 fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK);
125
126 sent = malloc(size);
127 got = malloc(size);
128 for (i = 0; i < size; ++i)
129 sent[i] = (char)(i % 251);
130
131 test_case("a buffer the kernel takes in pieces arrives whole and in order");
132 CHECK_INT(batch_add(&batch, memcpy(malloc(size), sent, size), size), 0);
133 arrived = pump(sv[0], sv[1], &batch, got, size);
134 CHECK_INT(arrived, size);
135 CHECK_INT(batch_pending(&batch), 0);
136 CHECK_MEM(got, sent, size);
137
138 free(sent);
139 free(got);
140 batch_reset(&batch);
141 close(sv[0]);
142 close(sv[1]);
143}
144
145/* writev refuses more than IOV_MAX entries at once, so a long batch has to be
146 sent across several calls. */
147static void case_more_entries_than_one_writev(void) {
148 const size_t entries = 3000, each = 4;
149 ot_batch batch;
150 int sv[2], sndbuf = 4096;
151 char *sent, *got;
152 size_t i, arrived, refused = 0;
153
154 memset(&batch, 0, sizeof(batch));
155 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
156 setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
157 fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK);
158
159 sent = malloc(entries * each);
160 got = malloc(entries * each);
161
162 test_case("a batch of more entries than writev accepts still arrives whole");
163 for (i = 0; i < entries; ++i) {
164 char piece[4];
165
166 piece[0] = (char)(i), piece[1] = (char)(i >> 8), piece[2] = 'x', piece[3] = 'y';
167 memcpy(sent + i * each, piece, each);
168 if (batch_add(&batch, memcpy(malloc(each), piece, each), each))
169 ++refused;
170 }
171 CHECK_INT(refused, 0);
172 CHECK_INT(batch.count, entries);
173
174 arrived = pump(sv[0], sv[1], &batch, got, entries * each);
175 CHECK_INT(arrived, entries * each);
176 CHECK_MEM(got, sent, entries * each);
177
178 free(sent);
179 free(got);
180 batch_reset(&batch);
181 close(sv[0]);
182 close(sv[1]);
183}
184
185static void case_reset_frees_the_unsent(void) {
186 ot_batch batch;
187 int sv[2];
188
189 memset(&batch, 0, sizeof(batch));
190 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
191
192 test_case("a batch torn down mid flight keeps no bytes outstanding");
193 CHECK_INT(batch_add(&batch, dup_of("first"), 5), 0);
194 CHECK_INT(batch_add(&batch, dup_of("second"), 6), 0);
195 CHECK_INT(batch_send(sv[0], &batch), 11);
196 CHECK_INT(batch_add(&batch, dup_of("never sent"), 10), 0);
197 CHECK_INT(batch_pending(&batch), 1);
198
199 /* Whether the unsent buffer is freed is for the sanitizer to say */
200 batch_reset(&batch);
201 CHECK_INT(batch_pending(&batch), 0);
202 CHECK_INT(batch.count, 0);
203
204 close(sv[0]);
205 close(sv[1]);
206}
207
208static void case_closed_socket_is_fatal(void) {
209 ot_batch batch;
210 int sv[2];
211
212 memset(&batch, 0, sizeof(batch));
213 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
214 close(sv[1]);
215
216 test_case("writing to a socket whose peer is gone reports a dead connection");
217 CHECK_INT(batch_add(&batch, dup_of("nobody is listening"), 19), 0);
218 CHECK_INT(batch_send(sv[0], &batch), BATCH_FAILED);
219
220 batch_reset(&batch);
221 close(sv[0]);
222}
223
224static void case_vector_is_taken_whole(void) {
225 ot_batch batch;
226 struct iovec *vector = malloc(3 * sizeof(struct iovec));
227 int sv[2];
228 char got[32];
229
230 memset(&batch, 0, sizeof(batch));
231 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
232
233 test_case("a whole vector is queued in one go, in order");
234 vector[0].iov_base = dup_of("one "), vector[0].iov_len = 4;
235 vector[1].iov_base = dup_of("two "), vector[1].iov_len = 4;
236 vector[2].iov_base = dup_of("three"), vector[2].iov_len = 5;
237 CHECK_INT(batch_add_vector(&batch, vector, 3), 0);
238 free(vector);
239 CHECK_INT(batch.count, 3);
240 CHECK_INT(batch_send(sv[0], &batch), 13);
241
242 drain(sv[1], got, 13);
243 CHECK_STR(got, "one two three");
244
245 batch_reset(&batch);
246 close(sv[0]);
247 close(sv[1]);
248}
249
250/* The entry array starts small and doubles, so crossing that boundary one
251 buffer at a time is worth its own case. */
252static void case_growing_one_entry_at_a_time(void) {
253 const size_t entries = 100;
254 ot_batch batch;
255 int sv[2];
256 char sent[400], got[401];
257 size_t i, refused = 0;
258
259 memset(&batch, 0, sizeof(batch));
260 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
261
262 test_case("a batch grown one buffer at a time keeps them all, in order");
263 for (i = 0; i < entries; ++i) {
264 char piece[4];
265
266 piece[0] = (char)('a' + i % 26), piece[1] = (char)('0' + i % 10);
267 piece[2] = (char)('A' + i % 26), piece[3] = '.';
268 memcpy(sent + i * 4, piece, 4);
269 if (batch_add(&batch, memcpy(malloc(4), piece, 4), 4))
270 ++refused;
271 }
272 CHECK_INT(refused, 0);
273 CHECK_INT(batch.count, entries);
274 CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(entries * 4));
275
276 drain(sv[1], got, entries * 4);
277 CHECK_MEM(got, sent, entries * 4);
278
279 batch_reset(&batch);
280 close(sv[0]);
281 close(sv[1]);
282}
283
284/* Appending while earlier entries are still on their way is what a chunked
285 transfer does. Rather than depend on when a kernel decides to block, this
286 takes entries off the front the way a completed send would and checks that
287 the array reuses the space instead of growing. */
288static void case_append_while_sending(void) {
289 const size_t per_round = 12, each = 8;
290 ot_batch batch;
291 int sv[2];
292 char expected[16], got[17];
293 size_t i, round, refused = 0, space_after_first = 0;
294
295 memset(&batch, 0, sizeof(batch));
296 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
297
298 test_case("an array whose front has gone out is reused, not grown");
299 for (round = 0; round < 8; ++round) {
300 for (i = 0; i < per_round; ++i) {
301 /* Distinct per entry, so a move from the wrong index shows up */
302 char *piece = malloc(each);
303
304 memset(piece, (int)('a' + (round * per_round + i) % 26), each);
305 piece[0] = (char)('0' + i);
306 piece[1] = (char)('0' + round);
307 if (batch_add(&batch, piece, each))
308 ++refused;
309 }
310
311 /* All but the last entry have left and been freed */
312 while (batch.count - batch.first > 1)
313 free(batch.entries[batch.first++].iov_base);
314
315 if (!round)
316 space_after_first = batch.space;
317 }
318 CHECK_INT(refused, 0);
319 CHECK_INT(batch.space, space_after_first);
320
321 test_case("and what is still queued survives the move, in order");
322 CHECK_INT(batch.count - batch.first, 1);
323 CHECK_INT(batch_add(&batch, memset(malloc(each), 'Z', each), each), 0);
324 CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(2 * each));
325
326 memset(expected, (int)('a' + (7 * per_round + per_round - 1) % 26), each);
327 expected[0] = (char)('0' + per_round - 1);
328 expected[1] = '7';
329 memset(expected + each, 'Z', each);
330 drain(sv[1], got, 2 * each);
331 CHECK_MEM(got, expected, 2 * each);
332
333 batch_reset(&batch);
334 close(sv[0]);
335 close(sv[1]);
336}
337
338void test_batch(void) {
339 case_queue_accounting();
340 case_send_in_one_go();
341 case_vector_is_taken_whole();
342 case_growing_one_entry_at_a_time();
343 case_append_while_sending();
344 case_partial_write_resumes();
345 case_more_entries_than_one_writev();
346 case_reset_frees_the_unsent();
347 case_closed_socket_is_fatal();
348}
diff --git a/tests/test.h b/tests/test.h
index 9677524..fd574c2 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -87,5 +87,6 @@ void test_buf(void);
87void test_ip(void); 87void test_ip(void);
88void test_socket(void); 88void test_socket(void);
89void test_mutex(void); 89void test_mutex(void);
90void test_batch(void);
90 91
91#endif 92#endif
diff --git a/trackerlogic.c b/trackerlogic.c
index 0fa7bfb..74fea6d 100644
--- a/trackerlogic.c
+++ b/trackerlogic.c
@@ -10,9 +10,6 @@
10#include <stdlib.h> 10#include <stdlib.h>
11#include <string.h> 11#include <string.h>
12 12
13/* Libowfat */
14#include "io.h"
15#include "iob.h"
16 13
17/* Opentracker */ 14/* Opentracker */
18#include "ot_accesslist.h" 15#include "ot_accesslist.h"