From: Jonathan Tan <hidden> Date: 2018-12-03 23:37:46
Some of us have been working on a design to improve the scalability of
Git servers by allowing them to offload part of the packfile response to
CDNs in this way: returning HTTP(S) URIs in fetch responses in addition
to packfiles.
This can reduce the load on individual Git servers and improves
proximity (by having data served from closer to the user).
I have included here a design document (patch 2) and a rough
implementation of the server (patch 5). Currently, the implementation
only allows replacing single blobs with URIs, but the protocol
improvement is designed in such a way as to allow independent
improvement of Git server implementations.
There is a potential issue: a server which produces both the URIs and
the packfile at roughly the same time (like the implementation in this
patch set) will not have sideband access until it has concluded sending
the URIs. Among other things, this means that the server cannot send
keepalive packets until quite late in the response. One solution to this
might be to add a feature that allows the server to use a sideband
throughout the whole response - and this has other benefits too like
allowing servers to inform the client throughout the whole fetch, not
just at the end.
Jonathan Tan (5):
Documentation: order protocol v2 sections
Documentation: add Packfile URIs design doc
upload-pack: refactor reading of pack-objects out
upload-pack: refactor writing of "packfile" line
upload-pack: send part of packfile response as uri
Documentation/technical/packfile-uri.txt | 83 +++++++++++++
Documentation/technical/protocol-v2.txt | 22 ++--
builtin/pack-objects.c | 48 ++++++++
fetch-pack.c | 9 ++
t/t5702-protocol-v2.sh | 25 ++++
upload-pack.c | 150 ++++++++++++++++-------
6 files changed, 285 insertions(+), 52 deletions(-)
create mode 100644 Documentation/technical/packfile-uri.txt
--
2.19.0.271.gfe8321ec05.dirty
From: Jonathan Tan <hidden> Date: 2018-12-03 23:37:48
The git command line expects Git servers to follow a specific order of
sections when transmitting protocol v2 responses, but this is not
explicit in the documentation. Make the order explicit.
Signed-off-by: Jonathan Tan <redacted>
---
Documentation/technical/protocol-v2.txt | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
@@ -309,11 +309,11 @@ the 'wanted-refs' section in the server's response as explained below. The response of `fetch` is broken into a number of sections separated by delimiter packets (0001), with each section beginning with its section-header.+header. Most sections are sent only when the packfile is sent.- output = *section- section = (acknowledgments | shallow-info | wanted-refs | packfile)- (flush-pkt | delim-pkt)+ output = acknowledgements flush-pkt |+ [acknowledgments delim-pkt] [shallow-info delim-pkt]+ [wanted-refs delim-pkt] packfile flush-pkt acknowledgments = PKT-LINE("acknowledgments" LF) (nak | *ack)
@@ -335,9 +335,10 @@ header. *PKT-LINE(%x01-03 *%x00-ff) acknowledgments section- * If the client determines that it is finished with negotiations- by sending a "done" line, the acknowledgments sections MUST be- omitted from the server's response.+ * If the client determines that it is finished with negotiations by+ sending a "done" line (thus requiring the server to send a packfile),+ the acknowledgments sections MUST be omitted from the server's+ response. * Always begins with the section header "acknowledgments"
@@ -388,9 +389,6 @@ header. which the client has not indicated was shallow as a part of its request.- * This section is only included if a packfile section is also- included in the response.- wanted-refs section * This section is only included if the client has requested a ref using a 'want-ref' line and if a packfile section is also
@@ -0,0 +1,83 @@+Packfile URIs+=============++This feature allows servers to serve part of their packfile response as URIs.+This allows server designs that improve scalability in bandwidth and CPU usage+(for example, by serving some data through a CDN), and (in the future) provides+some measure of resumability to clients.++This feature is available only in protocol version 2.++Protocol+--------++The server advertises `packfile-uris`.++If the client replies with the following arguments:++ * packfile-uris+ * thin-pack+ * ofs-delta++when the server sends the packfile, it MAY send a `packfile-uris` section+directly before the `packfile` section (right after `wanted-refs` if it is+sent) containing HTTP(S) URIs. See protocol-v2.txt for the documentation of+this section.++Clients then should understand that the returned packfile could be incomplete,+and that it needs to download all the given URIs before the fetch or clone is+complete. Each URI should point to a Git packfile (which may be a thin pack and+which may contain offset deltas).++Server design+-------------++The server can be trivially made compatible with the proposed protocol by+having it advertise `packfile-uris`, tolerating the client sending+`packfile-uris`, and never sending any `packfile-uris` section. But we should+include some sort of non-trivial implementation in the Minimum Viable Product,+at least so that we can test the client.++This is the implementation: a feature, marked experimental, that allows the+server to be configured by one or more `uploadpack.blobPackfileUri=<sha1>+<uri>` entries. Whenever the list of objects to be sent is assembled, a blob+with the given sha1 can be replaced by the given URI. This allows, for example,+servers to delegate serving of large blobs to CDNs.++Client design+-------------++While fetching, the client needs to remember the list of URIs and cannot+declare that the fetch is complete until all URIs have been downloaded as+packfiles.++The division of work (initial fetch + additional URIs) introduces convenient+points for resumption of an interrupted clone - such resumption can be done+after the Minimum Viable Product (see "Future work").++The client can inhibit this feature (i.e. refrain from sending the+`packfile-urls` parameter) by passing --no-packfile-urls to `git fetch`.++Future work+-----------++The protocol design allows some evolution of the server and client without any+need for protocol changes, so only a small-scoped design is included here to+form the MVP. For example, the following can be done:++ * On the server, a long-running process that takes in entire requests and+ outputs a list of URIs and the corresponding inclusion and exclusion sets of+ objects. This allows, e.g., signed URIs to be used and packfiles for common+ requests to be cached.+ * On the client, resumption of clone. If a clone is interrupted, information+ could be recorded in the repository's config and a "clone-resume" command+ can resume the clone in progress. (Resumption of subsequent fetches is more+ difficult because that must deal with the user wanting to use the repository+ even after the fetch was interrupted.)++There are some possible features that will require a change in protocol:++ * Additional HTTP headers (e.g. authentication)+ * Byte range support+ * Different file formats referenced by URIs (e.g. raw object)+
From: Jonathan Tan <hidden> Date: 2018-12-03 23:37:53
Subsequent patches will change how the output of pack-objects is
processed, so extract that processing into its own function.
Currently, at most 1 character can be buffered (in the "buffered" local
variable). One of those patches will require a larger buffer, so replace
that "buffered" local variable with a buffer array.
Signed-off-by: Jonathan Tan <redacted>
---
upload-pack.c | 80 +++++++++++++++++++++++++++++----------------------
1 file changed, 46 insertions(+), 34 deletions(-)
@@ -101,14 +101,51 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)return0;}+structoutput_state{+charbuffer[8193];+intused;+};++staticintread_pack_objects_stdout(intoutfd,structoutput_state*os)+{+/* Data ready; we keep the last byte to ourselves+*incasewedetectbrokenrev-list,sothatwe+*canleavethestreamcorrupted.Thisis+*unfortunate--unpack-objectswouldhappily+*acceptavalidpackdatawithtrailinggarbage,+*soappendinggarbageafterwepassallthe+*packdataisnotgoodenoughtosignal+*breakagetodownstream.+*/+ssize_treadsz;++readsz=xread(outfd,os->buffer+os->used,+sizeof(os->buffer)-os->used);+if(readsz<0){+returnreadsz;+}+os->used+=readsz;++if(os->used>1){+send_client_data(1,os->buffer,os->used-1);+os->buffer[0]=os->buffer[os->used-1];+os->used=1;+}else{+send_client_data(1,os->buffer,os->used);+os->used=0;+}++returnreadsz;+}+staticvoidcreate_pack_file(conststructobject_array*have_obj,conststructobject_array*want_obj){structchild_processpack_objects=CHILD_PROCESS_INIT;-chardata[8193],progress[128];+structoutput_stateoutput_state={0};+charprogress[128];charabort_msg[]="aborting due to possible repository ""corruption on the remote side.";-intbuffered=-1;ssize_tsz;inti;FILE*pipe_fd;
@@ -235,39 +272,15 @@ static void create_pack_file(const struct object_array *have_obj,continue;}if(0<=pu&&(pfd[pu].revents&(POLLIN|POLLHUP))){-/* Data ready; we keep the last byte to ourselves-*incasewedetectbrokenrev-list,sothatwe-*canleavethestreamcorrupted.Thisis-*unfortunate--unpack-objectswouldhappily-*acceptavalidpackdatawithtrailinggarbage,-*soappendinggarbageafterwepassallthe-*packdataisnotgoodenoughtosignal-*breakagetodownstream.-*/-char*cp=data;-ssize_toutsz=0;-if(0<=buffered){-*cp++=buffered;-outsz++;-}-sz=xread(pack_objects.out,cp,-sizeof(data)-outsz);-if(0<sz)-;-elseif(sz==0){+intresult=read_pack_objects_stdout(pack_objects.out,+&output_state);++if(result==0){close(pack_objects.out);pack_objects.out=-1;-}-else+}elseif(result<0){gotofail;-sz+=outsz;-if(1<sz){-buffered=data[sz-1]&0xFF;-sz--;}-else-buffered=-1;-send_client_data(1,data,sz);}/*
@@ -292,9 +305,8 @@ static void create_pack_file(const struct object_array *have_obj,}/* flush the data */-if(0<=buffered){-data[0]=buffered;-send_client_data(1,data,1);+if(output_state.used>0){+send_client_data(1,output_state.buffer,output_state.used);fprintf(stderr,"flushed.\n");}if(use_sideband)
From: Jonathan Tan <hidden> Date: 2018-12-03 23:37:55
A subsequent patch allows pack-objects to output additional information
(in addition to the packfile that it currently outputs). This means that
we must hold off on writing the "packfile" section header to the client
before we process the output of pack-objects, so move the writing of
the "packfile" section header to read_pack_objects_stdout().
Unfortunately, this also means that we cannot send keepalive packets
until pack-objects starts sending out the packfile, since the sideband
is only established when the "packfile" section header is sent.
Signed-off-by: Jonathan Tan <redacted>
---
upload-pack.c | 47 ++++++++++++++++++++++++++++++++++++-----------
1 file changed, 36 insertions(+), 11 deletions(-)
@@ -104,9 +104,12 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)structoutput_state{charbuffer[8193];intused;+unsignedpackfile_started:1;+structstrbufprogress_buf;};-staticintread_pack_objects_stdout(intoutfd,structoutput_state*os)+staticintread_pack_objects_stdout(intoutfd,structoutput_state*os,+intuse_protocol_v2){/* Data ready; we keep the last byte to ourselves*incasewedetectbrokenrev-list,sothatwe
From: Jonathan Tan <hidden> Date: 2018-12-03 23:38:02
This is a partial implementation of upload-pack sending part of its
packfile response as URIs.
The client is not fully implemented - it knows to ignore the
"packfile-uris" section, but because it does not actually fetch those
URIs, the returned packfile is incomplete. A test is included to show
that the appropriate URI is indeed transmitted, and that the returned
packfile is lacking exactly the expected object.
Signed-off-by: Jonathan Tan <redacted>
---
builtin/pack-objects.c | 48 ++++++++++++++++++++++++++++++++++++++++++
fetch-pack.c | 9 ++++++++
t/t5702-protocol-v2.sh | 25 ++++++++++++++++++++++
upload-pack.c | 37 ++++++++++++++++++++++++++++----
4 files changed, 115 insertions(+), 4 deletions(-)
@@ -831,6 +840,23 @@ static off_t write_reused_pack(struct hashfile *f)returnreuse_packfile_offset-sizeof(structpack_header);}+staticvoidwrite_excluded_by_configs(void)+{+structoidset_iteriter;+conststructobject_id*oid;++oidset_iter_init(&excluded_by_config,&iter);+while((oid=oidset_iter_next(&iter))){+structconfigured_exclusion*ex=+oidmap_get(&configured_exclusions,oid);++if(!ex)+BUG("configured exclusion wasn't configured");+write_in_full(1,ex->uri,strlen(ex->uri));+write_in_full(1,"\n",1);+}+}+staticconstcharno_split_warning[]=N_("disabling bitmap writing, packs are split due to pack.packSizeLimit");
@@ -1124,6 +1150,12 @@ static int want_object_in_pack(const struct object_id *oid,}}+if(exclude_configured_blobs&&+oidmap_get(&configured_exclusions,oid)){+oidset_insert(&excluded_by_config,oid);+return0;+}+return1;}
@@ -2728,6 +2760,19 @@ static int git_pack_config(const char *k, const char *v, void *cb)pack_idx_opts.version);return0;}+if(!strcmp(k,"uploadpack.blobpackfileuri")){+structconfigured_exclusion*ex=xmalloc(sizeof(*ex));+constchar*end;++if(parse_oid_hex(v,&ex->e.oid,&end)||*end!=' ')+die(_("value of uploadpack.blobpackfileuri must be "+"of the form '<sha-1> <uri>' (got '%s')"),v);+if(oidmap_get(&configured_exclusions,&ex->e.oid))+die(_("object already configured in another "+"uploadpack.blobpackfileuri (got '%s')"),v);+ex->uri=xstrdup(end+1);+oidmap_put(&configured_exclusions,ex);+}returngit_default_config(k,v,cb);}
@@ -3314,6 +3359,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)N_("do not pack objects in promisor packfiles")),OPT_BOOL(0,"delta-islands",&use_delta_islands,N_("respect islands during delta compression")),+OPT_BOOL(0,"exclude-configured-blobs",&exclude_configured_blobs,+N_("respect uploadpack.blobpackfileuri")),OPT_END(),};
@@ -588,6 +588,31 @@ test_expect_success 'when server does not send "ready", expect FLUSH' 'test_i18ngrep"expected no other sections to be sent after no .ready."err'+test_expect_success'part of packfile response provided as URI''+rm-rf"$HTTPD_DOCUMENT_ROOT_PATH/http_parent"http_childlog&&++gitinit"$HTTPD_DOCUMENT_ROOT_PATH/http_parent"&&+echomy-blob>"$HTTPD_DOCUMENT_ROOT_PATH/http_parent/my-blob"&&+git-C"$HTTPD_DOCUMENT_ROOT_PATH/http_parent/"addmy-blob&&+git-C"$HTTPD_DOCUMENT_ROOT_PATH/http_parent/"commit-mx&&++git-C"$HTTPD_DOCUMENT_ROOT_PATH/http_parent/"hash-objectmy-blob>h&&+git-C"$HTTPD_DOCUMENT_ROOT_PATH/http_parent/"config\+"uploadpack.blobpackfileuri"\+"$(cath) https://example.com/a-uri"&&++# NEEDSWORK: "git clone" fails here because it ignores the URI provided+# instead of fetching it.+test_must_failenvGIT_TRACE_PACKET="$(pwd)/log"\+git-cprotocol.version=2clone\+"$HTTPD_URL/smart/http_parent"http_child2>err&&+# Although "git clone" fails, we can still check that the server+# provided the URI we requested and that the error message pinpoints+# the object that is missing.+grep"clone< uri https://example.com/a-uri"log&&+test_i18ngrep"did not receive expected object $(cath)"err+'+ stop_httpd test_done
From: Stefan Beller <hidden> Date: 2018-12-04 00:01:32
On Mon, Dec 3, 2018 at 3:37 PM Jonathan Tan [off-list ref] wrote:
There is a potential issue: a server which produces both the URIs and
the packfile at roughly the same time (like the implementation in this
patch set) will not have sideband access until it has concluded sending
the URIs. Among other things, this means that the server cannot send
keepalive packets until quite late in the response. One solution to this
might be to add a feature that allows the server to use a sideband
throughout the whole response - and this has other benefits too like
allowing servers to inform the client throughout the whole fetch, not
just at the end.
While side band sounds like the right thing to do, we could also
sending (NULL)-URIs within this feature.
@@ -313,7 +313,8 @@ header. Most sections are sent only when the packfile is sent. output = acknowledgements flush-pkt | [acknowledgments delim-pkt] [shallow-info delim-pkt]- [wanted-refs delim-pkt] packfile flush-pkt+ [wanted-refs delim-pkt] [packfile-uris delim-pkt]+ packfile flush-pkt
While this is an RFC and incomplete, we'd need to remember to
add packfile-uris to the capabilities list above, stating that it requires
thin-pack and ofs-delta to be sent, and what to expect from it.
The mention of --no-packfile-urls in the Client design above
seems to imply we'd want to turn it on by default, which I thought
was not the usual stance how we introduce new things.
An odd way of disabling it would be --no-thin-pack, hoping the
client side implementation abides by the implied requirements.
@@ -331,6 +332,9 @@ header. Most sections are sent only when the packfile is sent. *PKT-LINE(wanted-ref LF) wanted-ref = obj-id SP refname+ packfile-uris = PKT-LINE("packfile-uris" LF) *packfile-uri+ packfile-uri = PKT-LINE("uri" SP *%x20-ff LF)
Is the *%x20-ff a fancy way of saying obj-id?
While the server is configured with pairs of (oid URL),
we would not need to send the exact oid to the client
as that is what the client can figure out on its own by reading
the downloaded pack.
Instead we could send an integrity hash (i.e. the packfile
downloaded from "uri" is expected to hash to $oid here)
Thanks,
Stefan
From: Stefan Beller <hidden> Date: 2018-12-04 00:31:01
On Mon, Dec 3, 2018 at 3:37 PM Jonathan Tan [off-list ref] wrote:
Subsequent patches will change how the output of pack-objects is
processed, so extract that processing into its own function.
Currently, at most 1 character can be buffered (in the "buffered" local
variable). One of those patches will require a larger buffer, so replace
that "buffered" local variable with a buffer array.
This buffering sounds oddly similar to the pkt reader which can buffer
at most one pkt, the difference being that we'd buffer bytes
instead of pkts.
@@ -0,0 +1,83 @@+Packfile URIs+=============++This feature allows servers to serve part of their packfile response as URIs.+This allows server designs that improve scalability in bandwidth and CPU usage+(for example, by serving some data through a CDN), and (in the future) provides+some measure of resumability to clients.++This feature is available only in protocol version 2.++Protocol+--------++The server advertises `packfile-uris`.++If the client replies with the following arguments:++ * packfile-uris+ * thin-pack+ * ofs-delta++when the server sends the packfile, it MAY send a `packfile-uris` section+directly before the `packfile` section (right after `wanted-refs` if it is+sent) containing HTTP(S) URIs. See protocol-v2.txt for the documentation of+this section.++Clients then should understand that the returned packfile could be incomplete,+and that it needs to download all the given URIs before the fetch or clone is+complete. Each URI should point to a Git packfile (which may be a thin pack and+which may contain offset deltas).
Some thoughts here:
First, I'd like to see a section (and a bit in the implementation)
requiring HTTPS if the original protocol is secure (SSH or HTTPS).
Allowing the server to downgrade to HTTP, even by accident, would be a
security problem.
Second, this feature likely should be opt-in for SSH. One issue I've
seen repeatedly is that people don't want to use HTTPS to fetch things
when they're using SSH for Git. Many people in corporate environments
have proxies that break HTTP for non-browser use cases[0], and using SSH
is the only way that they can make a functional Git connection.
Third, I think the server needs to be required to both support Range
headers and never change the content of a URI, so that we can have
resumable clone implicit in this design. There are some places in the
world where connections are poor and fetching even the initial packfile
at once might be a problem. (I've seen such questions on Stack
Overflow, for example.)
Having said that, I think overall this is a good idea and I'm glad to
see a proposal for it.
[0] For example, a naughty-word filter may corrupt or block certain byte
sequences that occur incidentally in the pack stream.
--
brian m. carlson: Houston, Texas, US
OpenPGP: https://keybase.io/bk2204
From: Jonathan Tan <hidden> Date: 2018-12-04 19:29:10
Some thoughts here:
First, I'd like to see a section (and a bit in the implementation)
requiring HTTPS if the original protocol is secure (SSH or HTTPS).
Allowing the server to downgrade to HTTP, even by accident, would be a
security problem.
Second, this feature likely should be opt-in for SSH. One issue I've
seen repeatedly is that people don't want to use HTTPS to fetch things
when they're using SSH for Git. Many people in corporate environments
have proxies that break HTTP for non-browser use cases[0], and using SSH
is the only way that they can make a functional Git connection.
Good points about SSH support and the client needing to control which
protocols the server will send URIs for. I'll include a line in the
client request in which the client can specify which protocols it is OK
with.
Third, I think the server needs to be required to both support Range
headers and never change the content of a URI, so that we can have
resumable clone implicit in this design. There are some places in the
world where connections are poor and fetching even the initial packfile
at once might be a problem. (I've seen such questions on Stack
Overflow, for example.)
Good points. I'll add these in the next revision.
Having said that, I think overall this is a good idea and I'm glad to
see a proposal for it.
From: Stefan Beller <hidden> Date: 2018-12-04 20:09:53
On Mon, Dec 3, 2018 at 3:38 PM Jonathan Tan [off-list ref] wrote:
This is a partial implementation of upload-pack sending part of its
packfile response as URIs.
It does so by implementing a new flag `--exclude-configured-blobs`
in pack-objects, which would change the output of pack-objects to
output a list of URLs (of the excluded blobs) followed by the
pack to be asked for.
This design seems easy to implement as then upload-pack
can just parse the output and only needs to insert
"packfile" and "packfile-uris\n" at the appropriate places
of the stream, otherwise it just passes through the information
obtained from pack-objects.
The design as-is would make for hard documentation of
pack-objects (its output is not just a pack anymore when that
flag is given, but a highly optimized byte stream).
Initially I did not anticipate this to be one of the major design problems
as I assumed we'd want to use this pack feature over broadly (e.g.
eventually by offloading most of the objects into a base pack that
is just always included as the likelihood for any object in there is
very high on initial clone), but it makes total sense to only
output the URIs that we actually need.
An alternative that comes very close to the current situation
would be to either pass a file path or file descriptor (that upload-pack
listens to in parallel) to pack-objects as an argument of the new flag.
Then we would not need to splice the protocol sections into the single
output stream, but we could announce the sections, then flush
the URIs and then flush the pack afterwards.
I looked at this quickly, but that would need either extensions in
run-command.c for setting up the new fd for us, as there we already
have OS specific code for these setups, or we'd have to duplicate
some of the logic here, which doesn't enthuse me either.
So maybe we'd create a temp file via mkstemp and pass
the file name to pack-objects for writing the URIs and then
we'd just need to stream that file?
+ # NEEDSWORK: "git clone" fails here because it ignores the URI provided
+ # instead of fetching it.
+ test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" \
+ git -c protocol.version=2 clone \
+ "$HTTPD_URL/smart/http_parent" http_child 2>err &&
+ # Although "git clone" fails, we can still check that the server
+ # provided the URI we requested and that the error message pinpoints
+ # the object that is missing.
+ grep "clone< uri https://example.com/a-uri" log &&
+ test_i18ngrep "did not receive expected object $(cat h)" err
From: Christian Couder <hidden> Date: 2019-02-19 13:22:45
On Tue, Dec 4, 2018 at 8:31 PM Jonathan Tan [off-list ref] wrote:
quoted
Some thoughts here:
First, I'd like to see a section (and a bit in the implementation)
requiring HTTPS if the original protocol is secure (SSH or HTTPS).
Allowing the server to downgrade to HTTP, even by accident, would be a
security problem.
Second, this feature likely should be opt-in for SSH. One issue I've
seen repeatedly is that people don't want to use HTTPS to fetch things
when they're using SSH for Git. Many people in corporate environments
have proxies that break HTTP for non-browser use cases[0], and using SSH
is the only way that they can make a functional Git connection.
Good points about SSH support and the client needing to control which
protocols the server will send URIs for. I'll include a line in the
client request in which the client can specify which protocols it is OK
with.
What if a client is ok to fetch from some servers but not others (for
example github.com and gitlab.com but nothing else)?
Or what if a client is ok to fetch using SSH from some servers and
HTTPS from other servers but nothing else?
I also wonder in general how this would interact with promisor/partial
clone remotes.
When we discussed promisor/partial clone remotes in the thread
following this email:
https://public-inbox.org/git/20181016174304.GA221682@aiede.svl.corp.google.com/
it looked like you were ok with having many promisor remotes, which I
think could fill the same use cases especially related to large
objects.
As clients would configure promisor remotes explicitly, there would be
no issues about which protocol and servers are allowed or not.
If the issue is that you want the server to decide which promisor
remotes would be used without the client having to do anything, maybe
that could be something added on top of the possibility to have many
promisor remotes.
@@ -0,0 +1,83 @@+Packfile URIs+=============++This feature allows servers to serve part of their packfile response as URIs.+This allows server designs that improve scalability in bandwidth and CPU usage+(for example, by serving some data through a CDN), and (in the future) provides+some measure of resumability to clients.++This feature is available only in protocol version 2.++Protocol+--------++The server advertises `packfile-uris`.++If the client replies with the following arguments:++ * packfile-uris+ * thin-pack+ * ofs-delta++when the server sends the packfile, it MAY send a `packfile-uris` section+directly before the `packfile` section (right after `wanted-refs` if it is+sent) containing HTTP(S) URIs. See protocol-v2.txt for the documentation of+this section.++Clients then should understand that the returned packfile could be incomplete,+and that it needs to download all the given URIs before the fetch or clone is+complete. Each URI should point to a Git packfile (which may be a thin pack and+which may contain offset deltas).
First, I'd like to see a section (and a bit in the implementation)
requiring HTTPS if the original protocol is secure (SSH or HTTPS).
Allowing the server to downgrade to HTTP, even by accident, would be a
security problem.
Maybe I've misunderstood the design (I'm writing some other follow-up
E-Mails in this thread which might clarify things for me), but I don't
see why.
We get the ref advertisement from the server. We don't need to trust the
CDN server or the transport layer. We just download whatever we get from
there, validate the packfile with SHA-1 (and in the future SHA-256). It
doesn't matter if the CDN transport is insecure.
You can do this offline with git today, you don't need to trust me to
trust that my copy of git.git I give you on a sketchy USB stick is
genuine. Just unpack it, then compare the SHA-1s you get with:
git ls-remote https://github.com/git/git.git
So this is a case similar to Debian's where they distribute packages
over http, but manifests over https: https://whydoesaptnotusehttps.com
Second, this feature likely should be opt-in for SSH. One issue I've
seen repeatedly is that people don't want to use HTTPS to fetch things
when they're using SSH for Git. Many people in corporate environments
have proxies that break HTTP for non-browser use cases[0], and using SSH
is the only way that they can make a functional Git connection.
Yeah, there should definitely be accommodations for such clients, per my
reading clients can always ignore the CDN and proceed with a normal
negotiation. Isn't that enough, or is something extra needed?
Third, I think the server needs to be required to both support Range
headers and never change the content of a URI, so that we can have
resumable clone implicit in this design. There are some places in the
world where connections are poor and fetching even the initial packfile
at once might be a problem. (I've seen such questions on Stack
Overflow, for example.)
I think this should be a MAY not a MUST in RFC 2119 terms. There's still
many users who might want to offload things to a very dumb CDN, such as
Debian where they don't control their own mirrors, but might want to
offload a 1GB packfile download to some random university's Debian
mirror.
Such a download (over http) will work most of the time. If it's not
resumable it still sucks less than no CDN at all, and client can always
fall back if the CDN breaks, which they should be doing anyway in case
of other sorts of issues.
Having said that, I think overall this is a good idea and I'm glad to
see a proposal for it.
[0] For example, a naughty-word filter may corrupt or block certain byte
sequences that occur incidentally in the pack stream.
On Tue, Dec 04 2018, Jonathan Tan wrote:
I meant to follow-up after Git Merge, but didn't remember until this
thread was bumped.
But some things I'd like to clarify / am concerned about...
+when the server sends the packfile, it MAY send a `packfile-uris` section
+directly before the `packfile` section (right after `wanted-refs` if it is
+sent) containing HTTP(S) URIs. See protocol-v2.txt for the documentation of
+this section.
+
+Clients then should understand that the returned packfile could be incomplete,
+and that it needs to download all the given URIs before the fetch or clone is
+complete. Each URI should point to a Git packfile (which may be a thin pack and
+which may contain offset deltas).
[...]
+This is the implementation: a feature, marked experimental, that allows the
+server to be configured by one or more `uploadpack.blobPackfileUri=<sha1>
+<uri>` entries. Whenever the list of objects to be sent is assembled, a blob
+with the given sha1 can be replaced by the given URI. This allows, for example,
+servers to delegate serving of large blobs to CDNs.
Okey, so the server advertisement is not just "<urls>" but <oid><url>
pairs. More on this later...
+While fetching, the client needs to remember the list of URIs and cannot
+declare that the fetch is complete until all URIs have been downloaded as
+packfiles.
And this. I don't quite understand this well enough, but maybe it helps
if I talk about what I'd expect out of CDN offloading. It comes down to
three things:
* The server should be able to point to some "seed" packfiles *without*
necessarily knowing what OIDs are in it, or have to tell the client.
* The client should be able to just blindly get this data ("I guess
this is where most of it is"), unpack it, see what OIDs it has, and
*then* without initiating a new connection continue a want/have
dialog.
This effectively "bootstraps" a "clone" mid way into an arbitrary
"fetch".
* There should be no requirement that a client successfully downloads
the advertised CDNs, for fault handling (also discussed in
https://public-inbox.org/git/87lg2b6gg0.fsf@evledraar.gmail.com/)
More concretely, I'd like to have a setup where a server can just dumbly
point to some URL that probably has most of the data, without having any
idea what OIDs are in it. So that e.g. some machine entirely
disconnected from the server (and with just a regular clone) can
continually generating an up-to-date-enough packfile.
I don't see how this is compatible with the server needing to send a
bunch of "<oid> <url>" lines, or why a client "cannot declare that the
fetch is complete until all URIs have been downloaded as
packfiles". Can't it fall back on the normal dialog?
Other thoughts:
* If there isn't such a close coordination between git server & CDN, is
there a case for having pack *.idx files on the CDN, so clients can
inspect them to see if they'd like to download the full referenced
pack?
* Without the server needing to know enough about the packs to
advertise "<oid> <url>" is there a way to e.g. advertise 4x packs to
clients:
big.pack, last-month.pack, last-week.pack, last-day.pack
Or some other optimistic negotiation where clients, even ones just
doing regular fetches, can seek to get more up-to-date with one of
the more recent packs before doing the first fetch in 3 days?
In the past I'd toyed with creating a similar "not quite CDN" setup
using git-bundle.
From: Jonathan Tan <hidden> Date: 2019-02-19 20:10:28
quoted
Good points about SSH support and the client needing to control which
protocols the server will send URIs for. I'll include a line in the
client request in which the client can specify which protocols it is OK
with.
What if a client is ok to fetch from some servers but not others (for
example github.com and gitlab.com but nothing else)?
Or what if a client is ok to fetch using SSH from some servers and
HTTPS from other servers but nothing else?
The objects received from the various CDNs are still rehashed by the
client (so they are identified with the correct name), and if the client
is fetching from a server, presumably it can trust the URLs it receives
(just like it trusts ref names, and so on). Do you know of a specific
case in which a client wants to fetch from some servers but not others?
(In any case, if this happens, the client can just disable the CDN
support.)
I also wonder in general how this would interact with promisor/partial
clone remotes.
When we discussed promisor/partial clone remotes in the thread
following this email:
https://public-inbox.org/git/20181016174304.GA221682@aiede.svl.corp.google.com/
it looked like you were ok with having many promisor remotes, which I
think could fill the same use cases especially related to large
objects.
As clients would configure promisor remotes explicitly, there would be
no issues about which protocol and servers are allowed or not.
If the issue is that you want the server to decide which promisor
remotes would be used without the client having to do anything, maybe
that could be something added on top of the possibility to have many
promisor remotes.
It's true that there is a slight overlap with respect to large objects,
but this protocol can also handle large sets of objects being offloaded
to CDN, not only single ones. (The included implementation only handles
single objects, as a minimum viable product, but it is conceivable that
the server implementation is later expanded to allow offloading of sets
of objects.)
And this protocol is meant to be able to use CDNs to help serve objects,
whether single objects or sets of objects. In the case of promisor
remotes, the thing we fetch from has to be a Git server. (We could use
dumb HTTP from a CDN, but that defeats the purpose in at least one way -
with dumb HTTP, we have to fetch objects individually, but with URL
support, we can fetch objects as sets too.)
From: Jonathan Tan <hidden> Date: 2019-02-19 22:06:13
quoted
+when the server sends the packfile, it MAY send a `packfile-uris` section
+directly before the `packfile` section (right after `wanted-refs` if it is
+sent) containing HTTP(S) URIs. See protocol-v2.txt for the documentation of
+this section.
+
+Clients then should understand that the returned packfile could be incomplete,
+and that it needs to download all the given URIs before the fetch or clone is
+complete. Each URI should point to a Git packfile (which may be a thin pack and
+which may contain offset deltas).
[...]
+This is the implementation: a feature, marked experimental, that allows the
+server to be configured by one or more `uploadpack.blobPackfileUri=<sha1>
+<uri>` entries. Whenever the list of objects to be sent is assembled, a blob
+with the given sha1 can be replaced by the given URI. This allows, for example,
+servers to delegate serving of large blobs to CDNs.
Okey, so the server advertisement is not just "<urls>" but <oid><url>
pairs. More on this later...
Actually, the server advertisement is just "<urls>". (The OID is there
to tell the server which object to omit if it sends the URL.) But I see
that the rest of your comments still stand.
More concretely, I'd like to have a setup where a server can just dumbly
point to some URL that probably has most of the data, without having any
idea what OIDs are in it. So that e.g. some machine entirely
disconnected from the server (and with just a regular clone) can
continually generating an up-to-date-enough packfile.
Thanks for the concrete use case. Server ignorance would work in this
case, since the client can concisely communicate to the server what
objects it obtained from the CDN (in this case, through "have" lines),
but it does not seem to work in the general case (e.g. offloading large
blobs; or the CDN serving a pack suitable for a shallow clone -
containing all objects referenced by the last few commits, whether
changed in that commit or not).
In this case, maybe the batch job can also inform the server which
commit the CDN is prepared to serve.
I don't see how this is compatible with the server needing to send a
bunch of "<oid> <url>" lines, or why a client "cannot declare that the
fetch is complete until all URIs have been downloaded as
packfiles". Can't it fall back on the normal dialog?
As stated above, the server advertisement is just "<url>", but you're
right that the server still needs to know their corresponding OIDs (or
have some knowledge like "this pack contains all objects in between this
commit and that commit").
I was thinking that there is no normal dialog to be had with this
protocol, since (as above) in the general case, the client cannot
concisely communicate what objects it obtained from the CDN.
Other thoughts:
* If there isn't such a close coordination between git server & CDN, is
there a case for having pack *.idx files on the CDN, so clients can
inspect them to see if they'd like to download the full referenced
pack?
I'm not sure if I understand this fully, but off the top of my head, the
.idx file doesn't contain relations between objects, so I don't think
the client has enough information to decide if it wants to download the
corresponding packfile.
* Without the server needing to know enough about the packs to
advertise "<oid> <url>" is there a way to e.g. advertise 4x packs to
clients:
big.pack, last-month.pack, last-week.pack, last-day.pack
Or some other optimistic negotiation where clients, even ones just
doing regular fetches, can seek to get more up-to-date with one of
the more recent packs before doing the first fetch in 3 days?
In the past I'd toyed with creating a similar "not quite CDN" setup
using git-bundle.
I think such optimistic downloading of packs during a regular fetch
would only work in a partial clone where "holes" are tolerated. (That
does bring up the possibility of having a fetch mode in which we
download potentially incomplete packfiles into a partial repo and then
"completing" the repo through a not-yet-implemented process, but I
haven't thought through this.)
From: brian m. carlson <hidden> Date: 2019-02-21 01:09:22
On Tue, Feb 19, 2019 at 02:44:31PM +0100, Ævar Arnfjörð Bjarmason wrote:
On Tue, Dec 04 2018, brian m. carlson wrote:
quoted
First, I'd like to see a section (and a bit in the implementation)
requiring HTTPS if the original protocol is secure (SSH or HTTPS).
Allowing the server to downgrade to HTTP, even by accident, would be a
security problem.
Maybe I've misunderstood the design (I'm writing some other follow-up
E-Mails in this thread which might clarify things for me), but I don't
see why.
We get the ref advertisement from the server. We don't need to trust the
CDN server or the transport layer. We just download whatever we get from
there, validate the packfile with SHA-1 (and in the future SHA-256). It
doesn't matter if the CDN transport is insecure.
You can do this offline with git today, you don't need to trust me to
trust that my copy of git.git I give you on a sketchy USB stick is
genuine. Just unpack it, then compare the SHA-1s you get with:
git ls-remote https://github.com/git/git.git
So this is a case similar to Debian's where they distribute packages
over http, but manifests over https: https://whydoesaptnotusehttps.com
This assumes that integrity of the data is the only reason you'd want to
use HTTPS. There's also confidentiality. Perhaps a user is downloading
data that will help them circumvent the Great Firewall of China. A
downgrade to HTTP could result in a long prison sentence.
Furthermore, some ISPs tamper with headers to allow tracking, and some
environments (e.g. schools and libraries) perform opportunistic
filtering on HTTP connections to filter certain content (and a lot of
this filtering is really simplistic).
Moreover, Google is planning on using this and filters in place of Git
LFS for large objects. I expect that if this approach becomes viable, it
may actually grow authentication functionality, or, depending on how the
series uses the existing code, it may already have it. In such a case,
we should not allow authentication to go over a plaintext connection
when the user thinks that the connection they're using is encrypted
(since they used an SSH or HTTPS URL to clone or fetch).
Downgrades from HTTPS to HTTP are generally considered CVE-worthy. We
need to make sure that we refuse to allow a downgrade on the client
side, even if the server ignores our request for a secure protocol.
quoted
Second, this feature likely should be opt-in for SSH. One issue I've
seen repeatedly is that people don't want to use HTTPS to fetch things
when they're using SSH for Git. Many people in corporate environments
have proxies that break HTTP for non-browser use cases[0], and using SSH
is the only way that they can make a functional Git connection.
Yeah, there should definitely be accommodations for such clients, per my
reading clients can always ignore the CDN and proceed with a normal
negotiation. Isn't that enough, or is something extra needed?
I think at least a config option and a command line flag are needed to
be able to turn CDN usage off. There needs to be an easy way for people
in broken environments to circumvent the breakage.
--
brian m. carlson: Houston, Texas, US
OpenPGP: https://keybase.io/bk2204
On Tue, Feb 19, 2019 at 02:44:31PM +0100, Ævar Arnfjörð Bjarmason wrote:
quoted
On Tue, Dec 04 2018, brian m. carlson wrote:
quoted
First, I'd like to see a section (and a bit in the implementation)
requiring HTTPS if the original protocol is secure (SSH or HTTPS).
Allowing the server to downgrade to HTTP, even by accident, would be a
security problem.
Maybe I've misunderstood the design (I'm writing some other follow-up
E-Mails in this thread which might clarify things for me), but I don't
see why.
We get the ref advertisement from the server. We don't need to trust the
CDN server or the transport layer. We just download whatever we get from
there, validate the packfile with SHA-1 (and in the future SHA-256). It
doesn't matter if the CDN transport is insecure.
You can do this offline with git today, you don't need to trust me to
trust that my copy of git.git I give you on a sketchy USB stick is
genuine. Just unpack it, then compare the SHA-1s you get with:
git ls-remote https://github.com/git/git.git
So this is a case similar to Debian's where they distribute packages
over http, but manifests over https: https://whydoesaptnotusehttps.com
This assumes that integrity of the data is the only reason you'd want to
use HTTPS. There's also confidentiality. Perhaps a user is downloading
data that will help them circumvent the Great Firewall of China. A
downgrade to HTTP could result in a long prison sentence.
Furthermore, some ISPs tamper with headers to allow tracking, and some
environments (e.g. schools and libraries) perform opportunistic
filtering on HTTP connections to filter certain content (and a lot of
this filtering is really simplistic).
Moreover, Google is planning on using this and filters in place of Git
LFS for large objects. I expect that if this approach becomes viable, it
may actually grow authentication functionality, or, depending on how the
series uses the existing code, it may already have it. In such a case,
we should not allow authentication to go over a plaintext connection
when the user thinks that the connection they're using is encrypted
(since they used an SSH or HTTPS URL to clone or fetch).
Downgrades from HTTPS to HTTP are generally considered CVE-worthy. We
need to make sure that we refuse to allow a downgrade on the client
side, even if the server ignores our request for a secure protocol.
All good points, I definitely agree we shouldn't do downgrading by
default for the reasons you've outlined, and e.g. make this an opt-in.
I'm just mindful that git's used as infrastructure in a lot of unusual
case, e.g. something like what apt's doing (after carefully weighing
http v.s. https for their use-case).
So I think providing some optional escape hatch is still a good idea.
quoted
quoted
Second, this feature likely should be opt-in for SSH. One issue I've
seen repeatedly is that people don't want to use HTTPS to fetch things
when they're using SSH for Git. Many people in corporate environments
have proxies that break HTTP for non-browser use cases[0], and using SSH
is the only way that they can make a functional Git connection.
Yeah, there should definitely be accommodations for such clients, per my
reading clients can always ignore the CDN and proceed with a normal
negotiation. Isn't that enough, or is something extra needed?
I think at least a config option and a command line flag are needed to
be able to turn CDN usage off. There needs to be an easy way for people
in broken environments to circumvent the breakage.
Yeah, but let's try hard to make it Just Work. I.e. if in the middle of
the dialog the CDN connection is broken can we retry then, and if that
fails just continue with negotiation against the server?
As opposed to erroring by default, and the user needing to retry with
some config option...
From: Christian Couder <hidden> Date: 2019-02-22 11:35:15
On Tue, Feb 19, 2019 at 9:10 PM Jonathan Tan [off-list ref] wrote:
quoted
quoted
Good points about SSH support and the client needing to control which
protocols the server will send URIs for. I'll include a line in the
client request in which the client can specify which protocols it is OK
with.
What if a client is ok to fetch from some servers but not others (for
example github.com and gitlab.com but nothing else)?
Or what if a client is ok to fetch using SSH from some servers and
HTTPS from other servers but nothing else?
The objects received from the various CDNs are still rehashed by the
client (so they are identified with the correct name), and if the client
is fetching from a server, presumably it can trust the URLs it receives
(just like it trusts ref names, and so on). Do you know of a specific
case in which a client wants to fetch from some servers but not others?
For example I think the Great Firewall of China lets people in China
use GitHub.com but not Google.com. So if people start configuring
their repos on GitHub so that they send packs that contain Google.com
CDN URLs (or actually anything that the Firewall blocks), it might
create many problems for users in China if they don't have a way to
opt out of receiving packs with those kind of URLs.
(In any case, if this happens, the client can just disable the CDN
support.)
Would this mean that people in China will not be able to use the
feature at all, because too many of their clones could be blocked? Or
that they will have to create forks to mirror any interesting repo and
reconfigure those forks to work well from China?
quoted
I also wonder in general how this would interact with promisor/partial
clone remotes.
When we discussed promisor/partial clone remotes in the thread
following this email:
https://public-inbox.org/git/20181016174304.GA221682@aiede.svl.corp.google.com/
it looked like you were ok with having many promisor remotes, which I
think could fill the same use cases especially related to large
objects.
As clients would configure promisor remotes explicitly, there would be
no issues about which protocol and servers are allowed or not.
If the issue is that you want the server to decide which promisor
remotes would be used without the client having to do anything, maybe
that could be something added on top of the possibility to have many
promisor remotes.
It's true that there is a slight overlap with respect to large objects,
but this protocol can also handle large sets of objects being offloaded
to CDN, not only single ones.
Isn't partial clone also designed to handle large sets of objects?
(The included implementation only handles
single objects, as a minimum viable product, but it is conceivable that
the server implementation is later expanded to allow offloading of sets
of objects.)
And this protocol is meant to be able to use CDNs to help serve objects,
whether single objects or sets of objects. In the case of promisor
remotes, the thing we fetch from has to be a Git server.
When we discussed the plan for many promisor remotes, Jonathan Nieder
(in the email linked above) suggested:
2. Simplifying the protocol for fetching missing objects so that it
can be satisfied by a lighter weight object storage system than
a full Git server. The ODB helpers introduced in this series are
meant to speak such a simpler protocol since they are only used
for one-off requests of a collection of missing objects instead of
needing to understand refs, Git's negotiation, etc.
and I agreed with that point.
Is there something that you don't like in many promisor remotes?
(We could use
dumb HTTP from a CDN, but that defeats the purpose in at least one way -
with dumb HTTP, we have to fetch objects individually, but with URL
support, we can fetch objects as sets too.)