16 "github.com/xtaci/kcp-go/v5"
17 "github.com/xtaci/smux"
18 "www.bamsoftware.com/git/champa.git/noise"
19 "www.bamsoftware.com/git/champa.git/turbotunnel"
22 // smux streams will be closed after this much time without receiving data.
23 const idleTimeout
= 2 * time
.Minute
25 // readKeyFromFile reads a key from a named file.
26 func readKeyFromFile(filename
string) ([]byte, error
) {
27 f
, err
:= os
.Open(filename
)
32 return noise
.ReadKey(f
)
35 func handle(local
*net
.TCPConn
, sess
*smux
.Session
, conv
uint32) error
{
36 stream
, err
:= sess
.OpenStream()
38 return fmt
.Errorf("session %08x opening stream: %v", conv
, err
)
41 log
.Printf("end stream %08x:%d", conv
, stream
.ID())
44 log
.Printf("begin stream %08x:%d", conv
, stream
.ID())
50 _
, err
:= io
.Copy(stream
, local
)
52 // smux Stream.Write may return io.EOF.
55 if err
!= nil && err
!= io
.ErrClosedPipe
{
56 log
.Printf("stream %08x:%d copy stream←local: %v", conv
, stream
.ID(), err
)
63 _
, err
:= io
.Copy(local
, stream
)
65 // smux Stream.WriteTo may return io.EOF.
68 if err
!= nil && err
!= io
.ErrClosedPipe
{
69 log
.Printf("stream %08x:%d copy local←stream: %v", conv
, stream
.ID(), err
)
78 func run(serverURL
, cacheURL
*url
.URL
, front
, localAddr
string, pubkey
[]byte) error
{
79 ln
, err
:= net
.Listen("tcp", localAddr
)
85 http
.DefaultTransport
.(*http
.Transport
).MaxConnsPerHost
= 20
87 var poll PollFunc
= func(ctx context
.Context
, p
[]byte) (io
.ReadCloser
, error
) {
88 return exchangeAMP(ctx
, serverURL
, cacheURL
, front
, p
)
90 pconn
:= NewPollingPacketConn(turbotunnel
.DummyAddr
{}, poll
)
93 // Open a KCP conn on the PacketConn.
94 conn
, err
:= kcp
.NewConn2(turbotunnel
.DummyAddr
{}, nil, 0, 0, pconn
)
96 return fmt
.Errorf("opening KCP conn: %v", err
)
99 log
.Printf("end session %08x", conn
.GetConv())
102 log
.Printf("begin session %08x", conn
.GetConv())
103 // Permit coalescing the payloads of consecutive sends.
104 conn
.SetStreamMode(true)
105 // Disable the dynamic congestion window (limit only by the maximum of
106 // local and remote static windows).
108 0, // default nodelay
109 0, // default interval
111 1, // nc=1 => congestion window off
113 // ACK received data immediately; this is good in our polling model.
114 conn
.SetACKNoDelay(true)
115 conn
.SetWindowSize(1024, 1024) // Default is 32, 32.
116 // TODO: We could optimize a call to conn.SetMtu here, based on a
117 // maximum URL length we want to send (such as the 8000 bytes
118 // recommended at https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1).
119 // The idea is that if we can slightly reduce the MTU from its default
120 // to permit one more packet per request, we should do it.
121 // E.g. 1400*5 = 7000, but 1320*6 = 7920.
123 // Put a Noise channel on top of the KCP conn.
124 rw
, err
:= noise
.NewClient(conn
, pubkey
)
129 // Start a smux session on the Noise channel.
130 smuxConfig
:= smux
.DefaultConfig()
131 smuxConfig
.Version
= 2
132 smuxConfig
.KeepAliveTimeout
= idleTimeout
133 smuxConfig
.MaxReceiveBuffer
= 4 * 1024 * 1024 // default is 4 * 1024 * 1024
134 smuxConfig
.MaxStreamBuffer
= 1 * 1024 * 1024 // default is 65536
135 sess
, err
:= smux
.Client(rw
, smuxConfig
)
137 return fmt
.Errorf("opening smux session: %v", err
)
142 local
, err
:= ln
.Accept()
144 if err
, ok
:= err
.(net
.Error
); ok
&& err
.Temporary() {
151 err
:= handle(local
.(*net
.TCPConn
), sess
, conn
.GetConv())
153 log
.Printf("handle: %v", err
)
162 var pubkeyFilename
string
163 var pubkeyString
string
165 flag
.Usage
= func() {
166 fmt
.Fprintf(flag
.CommandLine
.Output(), `Usage:
167 %[1]s [-cache CACHEURL] [-front DOMAIN] SERVERURL LOCALADDR
170 %[1]s -cache https://amp.cache.example/ -front amp.cache.example https://server.example/champa/ 127.0.0.1:7000
175 flag
.StringVar(&cache
, "cache", "", "URL of AMP cache (try https://cdn.ampproject.org/)")
176 flag
.StringVar(&front
, "front", "", "domain to domain-front HTTPS requests with (try www.google.com)")
177 flag
.StringVar(&pubkeyString
, "pubkey", "", fmt
.Sprintf("server public key (%d hex digits)", noise
.KeyLen
*2))
178 flag
.StringVar(&pubkeyFilename
, "pubkey-file", "", "read server public key from file")
181 log
.SetFlags(log
.LstdFlags | log
.LUTC
)
183 if flag
.NArg() != 2 {
187 serverURL
, err
:= url
.Parse(flag
.Arg(0))
189 fmt
.Fprintf(os
.Stderr
, "cannot parse server URL: %v\n", err
)
192 localAddr
:= flag
.Arg(1)
194 var cacheURL
*url
.URL
196 cacheURL
, err
= url
.Parse(cache
)
198 fmt
.Fprintf(os
.Stderr
, "cannot parse AMP cache URL: %v\n", err
)
204 if pubkeyFilename
!= "" && pubkeyString
!= "" {
205 fmt
.Fprintf(os
.Stderr
, "only one of -pubkey and -pubkey-file may be used\n")
207 } else if pubkeyFilename
!= "" {
209 pubkey
, err
= readKeyFromFile(pubkeyFilename
)
211 fmt
.Fprintf(os
.Stderr
, "cannot read pubkey from file: %v\n", err
)
214 } else if pubkeyString
!= "" {
216 pubkey
, err
= noise
.DecodeKey(pubkeyString
)
218 fmt
.Fprintf(os
.Stderr
, "pubkey format error: %v\n", err
)
222 fmt
.Fprintf(os
.Stderr
, "the -pubkey or -pubkey-file option is required\n")
226 err
= run(serverURL
, cacheURL
, front
, localAddr
, pubkey
)