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  }