github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4tee/tee.go (about) 1 // Copyright 2020 Matthew Holt 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package l4tee 16 17 import ( 18 "encoding/json" 19 "io" 20 "net" 21 22 "github.com/caddyserver/caddy/v2" 23 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" 24 "go.uber.org/zap" 25 26 "github.com/mholt/caddy-l4/layer4" 27 ) 28 29 func init() { 30 caddy.RegisterModule(&Handler{}) 31 } 32 33 // Handler is a layer4 handler that replicates a connection so 34 // that a branch of handlers can concurrently handle it. Reads 35 // happen in lock-step with all concurrent branches to 36 // avoid buffering: if one of the branches (including the main 37 // handler chain) stops reading from the connection, it will 38 // block all branches. 39 type Handler struct { 40 // Handlers is the list of handlers that constitute this 41 // concurrent branch. Any handlers that do connection 42 // matching (which involves recording and rewinding the 43 // stream) are unsafe to tee, so do all connection 44 // matching before teeing. 45 HandlersRaw []json.RawMessage `json:"branch,omitempty" caddy:"namespace=layer4.handlers inline_key=handler"` 46 47 compiledChain layer4.Handler 48 logger *zap.Logger 49 } 50 51 // CaddyModule returns the Caddy module information. 52 func (*Handler) CaddyModule() caddy.ModuleInfo { 53 return caddy.ModuleInfo{ 54 ID: "layer4.handlers.tee", 55 New: func() caddy.Module { return new(Handler) }, 56 } 57 } 58 59 // Provision sets up the handler. 60 func (t *Handler) Provision(ctx caddy.Context) error { 61 t.logger = ctx.Logger(t) 62 63 // set up the handler chain 64 mods, err := ctx.LoadModule(t, "HandlersRaw") 65 if err != nil { 66 return err 67 } 68 handlers := make(layer4.Handlers, 0) 69 for _, mod := range mods.([]interface{}) { 70 handlers = append(handlers, mod.(layer4.NextHandler)) 71 } 72 t.compiledChain = handlers.Compile() 73 74 return nil 75 } 76 77 // Handle handles the connection. 78 func (t *Handler) Handle(cx *layer4.Connection, next layer4.Handler) error { 79 // what is read by the next handler will also be 80 // read by the branch handlers; this is done by 81 // writing conn's reads into a pipe, and having 82 // the branch read from the pipe 83 pr, pw := io.Pipe() 84 85 // this is the conn we pass to the next handler; 86 // anything read by it will be teed into the pipe 87 // (it also needs a pointer to the pipe, so it can 88 // close the pipe when the connection closes, 89 // otherwise we'll leak the goroutine, yikes!) 90 nextc := *cx 91 nextc.Conn = nextConn{ 92 Conn: cx, 93 Reader: io.TeeReader(cx, pw), 94 pipe: pw, 95 } 96 97 // this is the conn we pass to the branch 98 branchc := *cx 99 branchc.Conn = teeConn{ 100 Conn: cx, 101 Reader: pr, 102 } 103 104 // run the branch concurrently 105 go func() { 106 err := t.compiledChain.Handle(&branchc) 107 if err != nil { 108 t.logger.Error("handling connection in branch", zap.String("remote", cx.RemoteAddr().String()), zap.Error(err)) 109 } 110 }() 111 112 return next.Handle(&nextc) 113 } 114 115 // UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax: 116 // 117 // tee { 118 // <handler> 119 // <handler> [<args>] 120 // } 121 func (t *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { 122 d.Next() // consume wrapper name 123 124 // No same-line options are supported 125 if d.CountRemainingArgs() > 0 { 126 return d.ArgErr() 127 } 128 129 if err := layer4.ParseCaddyfileNestedHandlers(d, &t.HandlersRaw); err != nil { 130 return err 131 } 132 133 return nil 134 } 135 136 // teeConn is a connection wrapper that reads 137 // from a different reader. 138 type teeConn struct { 139 net.Conn 140 io.Reader 141 } 142 143 func (tc teeConn) Read(p []byte) (int, error) { 144 return tc.Reader.Read(p) 145 } 146 147 // nextConn is a connection wrapper that reads from 148 // a different reader, and when the reader returns 149 // EOF, the associated pipe is closed. 150 type nextConn struct { 151 net.Conn 152 io.Reader 153 pipe *io.PipeWriter 154 } 155 156 func (nc nextConn) Read(p []byte) (n int, err error) { 157 n, err = nc.Reader.Read(p) 158 if err == io.EOF { 159 _ = nc.pipe.Close() 160 } 161 return 162 } 163 164 // Interface guards 165 var ( 166 _ caddyfile.Unmarshaler = (*Handler)(nil) 167 _ layer4.NextHandler = (*Handler)(nil) 168 )