remote-curl: implement stateless-connect command
Teach remote-curl the 'stateless-connect' command which is used to establish a stateless connection with servers which support protocol version 2. This allows remote-curl to act as a proxy, allowing the git client to communicate natively with a remote end, simply using remote-curl as a pass through to convert requests to http. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Brandon Williams committed
Mar 15, 2018 at 10:31 UTC
0f1dc53f45d307c8342bf547d79695b614cf9c26
2 files changed
+251
-1
remote-curl.c
+206
-1
@@ -188,7 +188,12 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push)
188
heads->version = discover_version(&reader);
189
switch (heads->version) {
190
case protocol_v2:
191
- die("support for protocol v2 not implemented yet");
191
+ /*
192
+ * Do nothing. This isn't a list of refs but rather a
193
+ * capability advertisement. Client would have run
194
+ * 'stateless-connect' so we'll dump this capability listing
195
+ * and let them request the refs themselves.
196
+ */
197
break;
198
case protocol_v1:
199
case protocol_v0:
@@ -1085,6 +1090,202 @@ static void parse_push(struct strbuf *buf)
1090
free(specs);
1091
}
1092
1093
+/*
1094
+ * Used to represent the state of a connection to an HTTP server when
1095
+ * communicating using git's wire-protocol version 2.
1096
+ */
1097
+struct proxy_state {
1098
+ char *service_name;
1099
+ char *service_url;
1100
+ struct curl_slist *headers;
1101
+ struct strbuf request_buffer;
1102
+ int in;
1103
+ int out;
1104
+ struct packet_reader reader;
1105
+ size_t pos;
1106
+ int seen_flush;
1107
+};
1108
+
1109
+static void proxy_state_init(struct proxy_state *p, const char *service_name,
1110
+ enum protocol_version version)
1111
+{
1112
+ struct strbuf buf = STRBUF_INIT;
1113
+
1114
+ memset(p, 0, sizeof(*p));
1115
+ p->service_name = xstrdup(service_name);
1116
+
1117
+ p->in = 0;
1118
+ p->out = 1;
1119
+ strbuf_init(&p->request_buffer, 0);
1120
+
1121
+ strbuf_addf(&buf, "%s%s", url.buf, p->service_name);
1122
+ p->service_url = strbuf_detach(&buf, NULL);
1123
+
1124
+ p->headers = http_copy_default_headers();
1125
+
1126
+ strbuf_addf(&buf, "Content-Type: application/x-%s-request", p->service_name);
1127
+ p->headers = curl_slist_append(p->headers, buf.buf);
1128
+ strbuf_reset(&buf);
1129
+
1130
+ strbuf_addf(&buf, "Accept: application/x-%s-result", p->service_name);
1131
+ p->headers = curl_slist_append(p->headers, buf.buf);
1132
+ strbuf_reset(&buf);
1133
+
1134
+ p->headers = curl_slist_append(p->headers, "Transfer-Encoding: chunked");
1135
+
1136
+ /* Add the Git-Protocol header */
1137
+ if (get_protocol_http_header(version, &buf))
1138
+ p->headers = curl_slist_append(p->headers, buf.buf);
1139
+
1140
+ packet_reader_init(&p->reader, p->in, NULL, 0,
1141
+ PACKET_READ_GENTLE_ON_EOF);
1142
+
1143
+ strbuf_release(&buf);
1144
+}
1145
+
1146
+static void proxy_state_clear(struct proxy_state *p)
1147
+{
1148
+ free(p->service_name);
1149
+ free(p->service_url);
1150
+ curl_slist_free_all(p->headers);
1151
+ strbuf_release(&p->request_buffer);
1152
+}
1153
+
1154
+/*
1155
+ * CURLOPT_READFUNCTION callback function.
1156
+ * Attempts to copy over a single packet-line at a time into the
1157
+ * curl provided buffer.
1158
+ */
1159
+static size_t proxy_in(char *buffer, size_t eltsize,
1160
+ size_t nmemb, void *userdata)
1161
+{
1162
+ size_t max;
1163
+ struct proxy_state *p = userdata;
1164
+ size_t avail = p->request_buffer.len - p->pos;
1165
+
1166
+
1167
+ if (eltsize != 1)
1168
+ BUG("curl read callback called with size = %"PRIuMAX" != 1",
1169
+ (uintmax_t)eltsize);
1170
+ max = nmemb;
1171
+
1172
+ if (!avail) {
1173
+ if (p->seen_flush) {
1174
+ p->seen_flush = 0;
1175
+ return 0;
1176
+ }
1177
+
1178
+ strbuf_reset(&p->request_buffer);
1179
+ switch (packet_reader_read(&p->reader)) {
1180
+ case PACKET_READ_EOF:
1181
+ die("unexpected EOF when reading from parent process");
1182
+ case PACKET_READ_NORMAL:
1183
+ packet_buf_write_len(&p->request_buffer, p->reader.line,
1184
+ p->reader.pktlen);
1185
+ break;
1186
+ case PACKET_READ_DELIM:
1187
+ packet_buf_delim(&p->request_buffer);
1188
+ break;
1189
+ case PACKET_READ_FLUSH:
1190
+ packet_buf_flush(&p->request_buffer);
1191
+ p->seen_flush = 1;
1192
+ break;
1193
+ }
1194
+ p->pos = 0;
1195
+ avail = p->request_buffer.len;
1196
+ }
1197
+
1198
+ if (max < avail)
1199
+ avail = max;
1200
+ memcpy(buffer, p->request_buffer.buf + p->pos, avail);
1201
+ p->pos += avail;
1202
+ return avail;
1203
+}
1204
+
1205
+static size_t proxy_out(char *buffer, size_t eltsize,
1206
+ size_t nmemb, void *userdata)
1207
+{
1208
+ size_t size;
1209
+ struct proxy_state *p = userdata;
1210
+
1211
+ if (eltsize != 1)
1212
+ BUG("curl read callback called with size = %"PRIuMAX" != 1",
1213
+ (uintmax_t)eltsize);
1214
+ size = nmemb;
1215
+
1216
+ write_or_die(p->out, buffer, size);
1217
+ return size;
1218
+}
1219
+
1220
+/* Issues a request to the HTTP server configured in `p` */
1221
+static int proxy_request(struct proxy_state *p)
1222
+{
1223
+ struct active_request_slot *slot;
1224
+
1225
+ slot = get_active_slot();
1226
+
1227
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
1228
+ curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
1229
+ curl_easy_setopt(slot->curl, CURLOPT_URL, p->service_url);
1230
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, p->headers);
1231
+
1232
+ /* Setup function to read request from client */
1233
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, proxy_in);
1234
+ curl_easy_setopt(slot->curl, CURLOPT_READDATA, p);
1235
+
1236
+ /* Setup function to write server response to client */
1237
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, proxy_out);
1238
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, p);
1239
+
1240
+ if (run_slot(slot, NULL) != HTTP_OK)
1241
+ return -1;
1242
+
1243
+ return 0;
1244
+}
1245
+
1246
+static int stateless_connect(const char *service_name)
1247
+{
1248
+ struct discovery *discover;
1249
+ struct proxy_state p;
1250
+
1251
+ /*
1252
+ * Run the info/refs request and see if the server supports protocol
1253
+ * v2. If and only if the server supports v2 can we successfully
1254
+ * establish a stateless connection, otherwise we need to tell the
1255
+ * client to fallback to using other transport helper functions to
1256
+ * complete their request.
1257
+ */
1258
+ discover = discover_refs(service_name, 0);
1259
+ if (discover->version != protocol_v2) {
1260
+ printf("fallback\n");
1261
+ fflush(stdout);
1262
+ return -1;
1263
+ } else {
1264
+ /* Stateless Connection established */
1265
+ printf("\n");
1266
+ fflush(stdout);
1267
+ }
1268
+
1269
+ proxy_state_init(&p, service_name, discover->version);
1270
+
1271
+ /*
1272
+ * Dump the capability listing that we got from the server earlier
1273
+ * during the info/refs request.
1274
+ */
1275
+ write_or_die(p.out, discover->buf, discover->len);
1276
+
1277
+ /* Peek the next packet line. Until we see EOF keep sending POSTs */
1278
+ while (packet_reader_peek(&p.reader) != PACKET_READ_EOF) {
1279
+ if (proxy_request(&p)) {
1280
+ /* We would have an err here */
1281
+ break;
1282
+ }
1283
+ }
1284
+
1285
+ proxy_state_clear(&p);
1286
+ return 0;
1287
+}
1288
+
1289
int cmd_main(int argc, const char **argv)
1290
{
1291
struct strbuf buf = STRBUF_INIT;
@@ -1153,12 +1354,16 @@ int cmd_main(int argc, const char **argv)
1354
fflush(stdout);
1355
1356
} else if (!strcmp(buf.buf, "capabilities")) {
1357
+ printf("stateless-connect\n");
1358
printf("fetch\n");
1359
printf("option\n");
1360
printf("push\n");
1361
printf("check-connectivity\n");
1362
printf("\n");
1363
fflush(stdout);
1364
+ } else if (skip_prefix(buf.buf, "stateless-connect ", &arg)) {
1365
+ if (!stateless_connect(arg))
1366
+ break;
1367
} else {
1368
error("remote-curl: unknown command '%s' from git", buf.buf);
1369
return 1;
t/t5702-protocol-v2.sh
+45
@@ -201,4 +201,49 @@ test_expect_success 'ref advertisment is filtered during fetch using protocol v2
201
! grep "refs/tags/three" log
202
'
203
204
+# Test protocol v2 with 'http://' transport
205
+#
206
+. "$TEST_DIRECTORY"/lib-httpd.sh
207
+start_httpd
208
+
209
+test_expect_success 'create repo to be served by http:// transport' '
210
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
211
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
212
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one
213
+'
214
+
215
+test_expect_success 'clone with http:// using protocol v2' '
216
+ test_when_finished "rm -f log" &&
217
+
218
+ GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
219
+ clone "$HTTPD_URL/smart/http_parent" http_child &&
220
+
221
+ git -C http_child log -1 --format=%s >actual &&
222
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
223
+ test_cmp expect actual &&
224
+
225
+ # Client requested to use protocol v2
226
+ grep "Git-Protocol: version=2" log &&
227
+ # Server responded using protocol v2
228
+ grep "git< version 2" log
229
+'
230
+
231
+test_expect_success 'fetch with http:// using protocol v2' '
232
+ test_when_finished "rm -f log" &&
233
+
234
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two &&
235
+
236
+ GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \
237
+ fetch &&
238
+
239
+ git -C http_child log -1 --format=%s origin/master >actual &&
240
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
241
+ test_cmp expect actual &&
242
+
243
+ # Server responded using protocol v2
244
+ grep "git< version 2" log
245
+'
246
+
247
+stop_httpd
248
+
249
test_done