github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/layer4/routes.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 layer4 16 17 import ( 18 "encoding/json" 19 "errors" 20 "fmt" 21 "os" 22 "time" 23 24 "github.com/caddyserver/caddy/v2" 25 "go.uber.org/zap" 26 ) 27 28 // Route represents a collection of handlers that are gated by 29 // matching logic. A route is invoked if its matchers match 30 // the byte stream. In an equivalent "if...then" statement, 31 // matchers are like the "if" clause and handlers are the "then" 32 // clause: if the matchers match, then the handlers will be 33 // executed. 34 type Route struct { 35 // Matchers define the conditions upon which to execute the handlers. 36 // All matchers within the same set must match, and at least one set 37 // must match; in other words, matchers are AND'ed together within a 38 // set, but multiple sets are OR'ed together. No matchers match all. 39 MatcherSetsRaw []caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=layer4.matchers"` 40 41 // Handlers define the behavior for handling the stream. They are 42 // executed in sequential order if the route's matchers match. 43 HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=layer4.handlers inline_key=handler"` 44 45 matcherSets MatcherSets 46 middleware []Middleware 47 } 48 49 var ErrMatchingTimeout = errors.New("aborted matching according to timeout") 50 51 // Provision sets up a route. 52 func (r *Route) Provision(ctx caddy.Context) error { 53 // matchers 54 matchersIface, err := ctx.LoadModule(r, "MatcherSetsRaw") 55 if err != nil { 56 return fmt.Errorf("loading matcher modules: %v", err) 57 } 58 err = r.matcherSets.FromInterface(matchersIface) 59 if err != nil { 60 return err 61 } 62 63 // handlers 64 mods, err := ctx.LoadModule(r, "HandlersRaw") 65 if err != nil { 66 return err 67 } 68 var handlers Handlers 69 for _, mod := range mods.([]interface{}) { 70 handler := mod.(NextHandler) 71 handlers = append(handlers, handler) 72 } 73 for _, midhandler := range handlers { 74 r.middleware = append(r.middleware, wrapHandler(midhandler)) 75 } 76 return nil 77 } 78 79 // RouteList is a list of connection routes that can create 80 // a middleware chain. Routes are evaluated in sequential 81 // order: for the first route, the matchers will be evaluated, 82 // and if matched, the handlers invoked; and so on for the 83 // second route, etc. 84 type RouteList []*Route 85 86 // Provision sets up all the routes. 87 func (routes RouteList) Provision(ctx caddy.Context) error { 88 for i, r := range routes { 89 err := r.Provision(ctx) 90 if err != nil { 91 return fmt.Errorf("route %d: %v", i, err) 92 } 93 } 94 return nil 95 } 96 97 const ( 98 // routes that need more data to determine the match 99 routeNeedsMore = iota 100 // routes definitely not matched 101 routeNotMatched 102 routeMatched 103 ) 104 105 // Compile prepares a middleware chain from the route list. 106 // This should only be done once: after all the routes have 107 // been provisioned, and before the server loop begins. 108 func (routes RouteList) Compile(logger *zap.Logger, matchingTimeout time.Duration, next Handler) Handler { 109 return HandlerFunc(func(cx *Connection) error { 110 deadline := time.Now().Add(matchingTimeout) 111 112 var ( 113 lastMatchedRouteIdx = -1 114 lastNeedsMoreIdx = -1 115 routesStatus = make(map[int]int) 116 matcherNeedMore bool 117 ) 118 // this loop should only be done if there are matchers that can't determine the match, 119 // i.e. some of the matchers returned false, ErrConsumedAllPrefetchedBytes. The index which 120 // the loop begins depends upon if there is a matched route. 121 loop: 122 // timeout matching to protect against malicious or very slow clients 123 err := cx.Conn.SetReadDeadline(deadline) 124 if err != nil { 125 return err 126 } 127 for { 128 // only read more because matchers require more (no matcher in the simplest case). 129 // can happen if this routes list is embedded in another 130 if matcherNeedMore { 131 err = cx.prefetch() 132 if err != nil { 133 logFunc := logger.Error 134 if errors.Is(err, os.ErrDeadlineExceeded) { 135 err = ErrMatchingTimeout 136 logFunc = logger.Warn 137 } 138 logFunc("matching connection", zap.String("remote", cx.RemoteAddr().String()), zap.Error(err)) 139 return nil // return nil so the error does not get logged again 140 } 141 } 142 143 for i, route := range routes { 144 if i <= lastMatchedRouteIdx { 145 continue 146 } 147 148 // If the route is definitely not matched, skip it 149 if s, ok := routesStatus[i]; ok && s == routeNotMatched && i <= lastNeedsMoreIdx { 150 continue 151 } 152 // now the matcher is after a matched route and current route needs more data to determine if more data is needed. 153 // note a matcher is skipped if the one after it can determine it is matched 154 155 // A route must match at least one of the matcher sets 156 matched, err := route.matcherSets.AnyMatch(cx) 157 if errors.Is(err, ErrConsumedAllPrefetchedBytes) { 158 lastNeedsMoreIdx = i 159 routesStatus[i] = routeNeedsMore 160 // the first time a matcher requires more data, exit the loop to force a prefetch 161 if !matcherNeedMore { 162 break 163 } 164 continue // ignore and try next route 165 } 166 if err != nil { 167 logger.Error("matching connection", zap.String("remote", cx.RemoteAddr().String()), zap.Error(err)) 168 return nil 169 } 170 if matched { 171 routesStatus[i] = routeMatched 172 lastMatchedRouteIdx = i 173 lastNeedsMoreIdx = i 174 // remove deadline after we matched 175 err = cx.Conn.SetReadDeadline(time.Time{}) 176 if err != nil { 177 return err 178 } 179 180 isTerminal := true 181 lastHandler := HandlerFunc(func(conn *Connection) error { 182 // Catch potentially wrapped connection to use it as input for the next round of route matching. 183 // This is for example required for matchers after a tls handler. 184 cx = conn 185 // If this handler is called all handlers before where not terminal 186 isTerminal = false 187 return nil 188 }) 189 // compile the route handler stack with lastHandler being called last 190 handler := wrapHandler(forwardNextHandler{})(lastHandler) 191 for i := len(route.middleware) - 1; i >= 0; i-- { 192 handler = route.middleware[i](handler) 193 } 194 err = handler.Handle(cx) 195 if err != nil { 196 return err 197 } 198 199 // If handler is terminal we stop routing, 200 // otherwise we try the next handler. 201 if isTerminal { 202 return nil 203 } 204 } else { 205 routesStatus[i] = routeNotMatched 206 } 207 } 208 // end of match 209 if lastMatchedRouteIdx == len(routes)-1 { 210 // next is called because if the last handler is terminal, it's already returned 211 return next.Handle(cx) 212 } 213 var indetermined int 214 for i, s := range routesStatus { 215 if i > lastMatchedRouteIdx && s == routeNeedsMore { 216 indetermined++ 217 } 218 } 219 // some of the matchers can't reach a conclusion 220 if indetermined > 0 { 221 matcherNeedMore = true 222 goto loop 223 } 224 return next.Handle(cx) 225 } 226 }) 227 }