@@ -41,6 +41,8 @@ import (
41
41
"github.com/bufbuild/buf/private/pkg/netrc"
42
42
"github.com/bufbuild/buf/private/pkg/stringutil"
43
43
"github.com/bufbuild/buf/private/pkg/verbose"
44
+ "github.com/quic-go/quic-go"
45
+ "github.com/quic-go/quic-go/http3"
44
46
"github.com/spf13/pflag"
45
47
"go.uber.org/multierr"
46
48
"golang.org/x/net/http2"
@@ -60,6 +62,7 @@ const (
60
62
protocolFlagName = "protocol"
61
63
unixSocketFlagName = "unix-socket"
62
64
http2PriorKnowledgeFlagName = "http2-prior-knowledge"
65
+ http3FlagName = "http3"
63
66
64
67
// TLS flags
65
68
keyFlagName = "key"
@@ -207,6 +210,7 @@ type flags struct {
207
210
Protocol string
208
211
UnixSocket string
209
212
HTTP2PriorKnowledge bool
213
+ HTTP3 bool
210
214
211
215
// TLS
212
216
Key , Cert , CACert , ServerName string
@@ -324,6 +328,16 @@ choose either HTTP 1.1 or HTTP/2 for URLs with an https scheme. With this flag s
324
328
HTTP/2 is always used, even over plain-text.` ,
325
329
)
326
330
331
+ flagSet .BoolVar (
332
+ & f .HTTP3 ,
333
+ http3FlagName ,
334
+ false ,
335
+ `This flag can be used to indicate that HTTP/3 should be used. Without this, HTTP 1.1
336
+ will be used with URLs with an http scheme, and protocol negotiation will be used to
337
+ choose either HTTP 1.1 or HTTP/2 for URLs with an https scheme. With this flag set,
338
+ HTTP/3 is always used.` ,
339
+ )
340
+
327
341
flagSet .BoolVar (
328
342
& f .NoKeepAlive ,
329
343
noKeepAliveFlagName ,
@@ -544,6 +558,19 @@ func (f *flags) validate(hasURL, isSecure bool) error {
544
558
return fmt .Errorf ("grpc protocol cannot be used with plain-text URLs (http) unless --%s flag is set" , http2PriorKnowledgeFlagName )
545
559
}
546
560
561
+ if ! isSecure && f .HTTP3 {
562
+ return fmt .Errorf ("--%s cannot be used with plain-text URLs (http)" , http3FlagName )
563
+ }
564
+
565
+ if f .UnixSocket != "" && f .HTTP3 {
566
+ return fmt .Errorf ("--%s cannot be used with --%s" , unixSocketFlagName , http3FlagName )
567
+ }
568
+
569
+ // NOTE: This can be removed once trailer support lands for quic-go: https://github.com/quic-go/quic-go/issues/2266
570
+ if f .Protocol == connect .ProtocolGRPC && f .HTTP3 {
571
+ return fmt .Errorf ("--%s cannot be used with --%s=%s" , http3FlagName , protocolFlagName , connect .ProtocolGRPC )
572
+ }
573
+
547
574
if f .Netrc && f .NetrcFile != "" {
548
575
return fmt .Errorf ("--%s and --%s flags are mutually exclusive; they may not both be specified" , netrcFlagName , netrcFileFlagName )
549
576
}
@@ -691,6 +718,18 @@ func (f *flags) determineCredentials(
691
718
return basicAuth (username , password ), nil
692
719
}
693
720
721
+ func (f * flags ) getTLSConfig (authority string , printer verbose.Printer ) (* tls.Config , error ) {
722
+ return bufcurl .MakeVerboseTLSConfig (& bufcurl.TLSSettings {
723
+ KeyFile : f .Key ,
724
+ CertFile : f .Cert ,
725
+ CACertFile : f .CACert ,
726
+ ServerName : f .ServerName ,
727
+ Insecure : f .Insecure ,
728
+ HTTP2PriorKnowledge : f .HTTP2PriorKnowledge ,
729
+ HTTP3 : f .HTTP3 ,
730
+ }, authority , printer )
731
+ }
732
+
694
733
func promptForPassword (ctx context.Context , container app.Container , prompt string ) (string , error ) {
695
734
// NB: The comments below and the mechanism of handling I/O async was
696
735
// copied from the "registry login" command.
@@ -916,7 +955,11 @@ func run(ctx context.Context, container appext.Container, f *flags) (err error)
916
955
// This shouldn't be possible since we check in flags.validate, but just in case
917
956
return nil , errors .New ("URL positional argument is missing" )
918
957
}
919
- return makeHTTPClient (f , isSecure , bufcurl .GetAuthority (host , requestHeaders ), container .VerbosePrinter ())
958
+ roundTripper , err := makeHTTPRoundTripper (f , isSecure , bufcurl .GetAuthority (host , requestHeaders ), container .VerbosePrinter ())
959
+ if err != nil {
960
+ return nil , err
961
+ }
962
+ return bufcurl .NewVerboseHTTPClient (roundTripper , container .VerbosePrinter ()), nil
920
963
})
921
964
922
965
output := container .Stdout ()
@@ -1025,7 +1068,10 @@ func run(ctx context.Context, container appext.Container, f *flags) (err error)
1025
1068
}
1026
1069
}
1027
1070
1028
- func makeHTTPClient (f * flags , isSecure bool , authority string , printer verbose.Printer ) (connect.HTTPClient , error ) {
1071
+ func makeHTTPRoundTripper (f * flags , isSecure bool , authority string , printer verbose.Printer ) (http.RoundTripper , error ) {
1072
+ if f .HTTP3 {
1073
+ return makeHTTP3RoundTripper (f , authority , printer )
1074
+ }
1029
1075
var dialer net.Dialer
1030
1076
if f .ConnectTimeoutSeconds != 0 {
1031
1077
dialer .Timeout = secondsToDuration (f .ConnectTimeoutSeconds )
@@ -1035,6 +1081,7 @@ func makeHTTPClient(f *flags, isSecure bool, authority string, printer verbose.P
1035
1081
} else {
1036
1082
dialer .KeepAlive = secondsToDuration (f .KeepAliveTimeSeconds )
1037
1083
}
1084
+
1038
1085
var dialFunc func (ctx context.Context , network , address string ) (net.Conn , error )
1039
1086
if f .UnixSocket != "" {
1040
1087
dialFunc = func (ctx context.Context , network , addr string ) (net.Conn , error ) {
@@ -1055,14 +1102,7 @@ func makeHTTPClient(f *flags, isSecure bool, authority string, printer verbose.P
1055
1102
1056
1103
var dialTLSFunc func (ctx context.Context , network , address string ) (net.Conn , error )
1057
1104
if isSecure {
1058
- tlsConfig , err := bufcurl .MakeVerboseTLSConfig (& bufcurl.TLSSettings {
1059
- KeyFile : f .Key ,
1060
- CertFile : f .Cert ,
1061
- CACertFile : f .CACert ,
1062
- ServerName : f .ServerName ,
1063
- Insecure : f .Insecure ,
1064
- HTTP2PriorKnowledge : f .HTTP2PriorKnowledge ,
1065
- }, authority , printer )
1105
+ tlsConfig , err := f .getTLSConfig (authority , printer )
1066
1106
if err != nil {
1067
1107
return nil , err
1068
1108
}
@@ -1104,7 +1144,53 @@ func makeHTTPClient(f *flags, isSecure bool, authority string, printer verbose.P
1104
1144
MaxIdleConns : 1 ,
1105
1145
}
1106
1146
}
1107
- return bufcurl .NewVerboseHTTPClient (transport , printer ), nil
1147
+ return transport , nil
1148
+ }
1149
+
1150
+ func makeHTTP3RoundTripper (f * flags , authority string , printer verbose.Printer ) (http.RoundTripper , error ) {
1151
+ quicCfg := & quic.Config {
1152
+ KeepAlivePeriod : - 1 ,
1153
+ }
1154
+ if f .ConnectTimeoutSeconds != 0 {
1155
+ quicCfg .HandshakeIdleTimeout = secondsToDuration (f .ConnectTimeoutSeconds )
1156
+ }
1157
+ if ! f .NoKeepAlive {
1158
+ quicCfg .KeepAlivePeriod = secondsToDuration (f .KeepAliveTimeSeconds )
1159
+ }
1160
+
1161
+ tlsConfig , err := f .getTLSConfig (authority , printer )
1162
+ if err != nil {
1163
+ return nil , err
1164
+ }
1165
+
1166
+ udpConn , err := net .ListenUDP ("udp" , nil )
1167
+ if err != nil {
1168
+ return nil , err
1169
+ }
1170
+ transport := & quic.Transport {Conn : udpConn }
1171
+ roundTripper := & http3.RoundTripper {
1172
+ TLSClientConfig : tlsConfig ,
1173
+ QUICConfig : quicCfg ,
1174
+ Dial : func (ctx context.Context , addr string , tlsCfg * tls.Config , cfg * quic.Config ) (quic.EarlyConnection , error ) {
1175
+ printer .Printf ("* Dialing (udp) %s..." , addr )
1176
+ udpAddr , err := net .ResolveUDPAddr ("udp" , addr )
1177
+ if err != nil {
1178
+ return nil , err
1179
+ }
1180
+ printer .Printf ("* ALPN: offering %s" , strings .Join (tlsCfg .NextProtos , "," ))
1181
+ conn , err := transport .DialEarly (ctx , udpAddr , tlsCfg , cfg )
1182
+ if err != nil {
1183
+ return nil , err
1184
+ }
1185
+ printer .Printf ("* Connected to %s" , conn .RemoteAddr ().String ())
1186
+ return conn , err
1187
+ },
1188
+ EnableDatagrams : false ,
1189
+ AdditionalSettings : map [uint64 ]uint64 {},
1190
+ MaxResponseHeaderBytes : 0 ,
1191
+ DisableCompression : false ,
1192
+ }
1193
+ return roundTripper , nil
1108
1194
}
1109
1195
1110
1196
func secondsToDuration (secs float64 ) time.Duration {
0 commit comments