github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/layer4/caddyfile.go (about)

     1  package layer4
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strconv"
     7  
     8  	"github.com/caddyserver/caddy/v2"
     9  	"github.com/caddyserver/caddy/v2/caddyconfig"
    10  	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
    11  	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
    12  )
    13  
    14  func init() {
    15  	httpcaddyfile.RegisterGlobalOption("layer4", parseLayer4)
    16  }
    17  
    18  // parseLayer4 sets up the App from Caddyfile tokens. Syntax:
    19  //
    20  //	{
    21  //		layer4 {
    22  //			# srv0
    23  //			<addresses...> {
    24  //				...
    25  //			}
    26  //			# srv1
    27  //			<addresses...> {
    28  //				...
    29  //			}
    30  //		}
    31  //	}
    32  func parseLayer4(d *caddyfile.Dispenser, existingVal any) (any, error) {
    33  	app := &App{Servers: make(map[string]*Server)}
    34  
    35  	// Multiple global layer4 blocks are combined
    36  	if existingVal != nil {
    37  		appConfig, ok := existingVal.(httpcaddyfile.App)
    38  		if !ok {
    39  			return nil, d.Errf("existing %T config of unexpected type: %T", *app, existingVal)
    40  		}
    41  		err := json.Unmarshal(appConfig.Value, app)
    42  		if err != nil {
    43  			return nil, d.Errf("parsing existing %T config: %v", *app, err)
    44  		}
    45  	}
    46  
    47  	d.Next() // consume wrapper name
    48  
    49  	// No same-line options are supported
    50  	if d.CountRemainingArgs() > 0 {
    51  		return nil, d.ArgErr()
    52  	}
    53  
    54  	i := len(app.Servers)
    55  	for nesting := d.Nesting(); d.NextBlock(nesting); {
    56  		server := &Server{}
    57  		var inst interface{} = server
    58  		unm, ok := inst.(caddyfile.Unmarshaler)
    59  		if !ok {
    60  			return nil, d.Errf("%T is not a Caddyfile unmarshaler", inst)
    61  		}
    62  		if err := unm.UnmarshalCaddyfile(d); err != nil {
    63  			return nil, err
    64  		}
    65  		app.Servers["srv"+strconv.Itoa(i)] = server
    66  		i++
    67  	}
    68  
    69  	return httpcaddyfile.App{
    70  		Name:  "layer4",
    71  		Value: caddyconfig.JSON(app, nil),
    72  	}, nil
    73  }
    74  
    75  // ParseCaddyfileNestedRoutes parses the Caddyfile tokens for nested named matcher sets, handlers and matching timeout,
    76  // composes a list of route configurations, and adjusts the matching timeout.
    77  func ParseCaddyfileNestedRoutes(d *caddyfile.Dispenser, routes *RouteList, matchingTimeout *caddy.Duration) error {
    78  	var hasMatchingTimeout bool
    79  	matcherSetTokensByName, routeTokens := make(map[string][]caddyfile.Token), make([]caddyfile.Token, 0)
    80  	for nesting := d.Nesting(); d.NextBlock(nesting); {
    81  		optionName := d.Val()
    82  		if len(optionName) > 1 && optionName[0] == '@' {
    83  			if _, exists := matcherSetTokensByName[optionName]; exists {
    84  				return d.Errf("duplicate matcher set '%s'", d.Val())
    85  			}
    86  			matcherSetTokensByName[optionName] = append(matcherSetTokensByName[optionName], d.NextSegment()...)
    87  		} else if optionName == "matching_timeout" {
    88  			if hasMatchingTimeout {
    89  				return d.Errf("duplicate option '%s'", optionName)
    90  			}
    91  			if d.CountRemainingArgs() > 1 || !d.NextArg() {
    92  				return d.ArgErr()
    93  			}
    94  			dur, err := caddy.ParseDuration(d.Val())
    95  			if err != nil {
    96  				return d.Errf("parsing option '%s' duration: %v", optionName, err)
    97  			}
    98  			*matchingTimeout, hasMatchingTimeout = caddy.Duration(dur), true
    99  		} else if optionName == "route" {
   100  			routeTokens = append(routeTokens, d.NextSegment()...)
   101  		} else {
   102  			return d.ArgErr()
   103  		}
   104  	}
   105  
   106  	matcherSetsByName := make(map[string]caddy.ModuleMap)
   107  	for matcherSetName, tokens := range matcherSetTokensByName {
   108  		dd := caddyfile.NewDispenser(tokens)
   109  		dd.Next() // consume wrapper name
   110  		if !dd.NextArg() && !dd.NextBlock(dd.Nesting()) {
   111  			return dd.ArgErr()
   112  		}
   113  
   114  		dd.Reset() // reset dispenser after argument/block checks above
   115  		dd.Next()  // consume wrapper name again
   116  		matcherSet, err := ParseCaddyfileNestedMatcherSet(dd)
   117  		if err != nil {
   118  			return err
   119  		}
   120  		matcherSetsByName[matcherSetName] = matcherSet
   121  	}
   122  
   123  	dd := caddyfile.NewDispenser(routeTokens)
   124  	for dd.Next() { // consume route wrapper name
   125  		route := Route{}
   126  
   127  		if dd.CountRemainingArgs() > 0 {
   128  			for dd.NextArg() {
   129  				matcherSetName := dd.Val()
   130  				matcherSet, exists := matcherSetsByName[matcherSetName]
   131  				if !exists {
   132  					return dd.Errf("undefined matcher set '%s'", matcherSetName)
   133  				}
   134  				route.MatcherSetsRaw = append(route.MatcherSetsRaw, matcherSet)
   135  			}
   136  		}
   137  
   138  		if err := ParseCaddyfileNestedHandlers(dd, &route.HandlersRaw); err != nil {
   139  			return err
   140  		}
   141  		*routes = append(*routes, &route)
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  // ParseCaddyfileNestedHandlers parses the Caddyfile tokens for nested handlers,
   148  // and composes a list of their raw json configurations.
   149  func ParseCaddyfileNestedHandlers(d *caddyfile.Dispenser, handlersRaw *[]json.RawMessage) error {
   150  	for nesting := d.Nesting(); d.NextBlock(nesting); {
   151  		handlerName := d.Val()
   152  
   153  		unm, err := caddyfile.UnmarshalModule(d, "layer4.handlers."+handlerName)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		nh, ok := unm.(NextHandler)
   158  		if !ok {
   159  			return d.Errf("handler module '%s' is not a layer4 connection handler", handlerName)
   160  		}
   161  		handlerConfig := caddyconfig.JSON(nh, nil)
   162  
   163  		handlerConfig, err = SetModuleNameInline("handler", handlerName, handlerConfig)
   164  		if err != nil {
   165  			return err
   166  		}
   167  		*handlersRaw = append(*handlersRaw, handlerConfig)
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  // ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested matcher set,
   174  // and returns its raw module map value.
   175  func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
   176  	matcherMap := make(map[string]ConnMatcher)
   177  
   178  	tokensByMatcherName := make(map[string][]caddyfile.Token)
   179  	for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
   180  		matcherName := d.Val()
   181  		if _, exists := tokensByMatcherName[matcherName]; exists {
   182  			return nil, d.Errf("duplicate matcher module '%s'", matcherName)
   183  		}
   184  		tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
   185  	}
   186  
   187  	for matcherName, tokens := range tokensByMatcherName {
   188  		dd := caddyfile.NewDispenser(tokens)
   189  		dd.Next() // consume wrapper name
   190  
   191  		unm, err := caddyfile.UnmarshalModule(dd, "layer4.matchers."+matcherName)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		cm, ok := unm.(ConnMatcher)
   196  		if !ok {
   197  			return nil, d.Errf("matcher module '%s' is not a layer4 connection matcher", matcherName)
   198  		}
   199  		matcherMap[matcherName] = cm
   200  	}
   201  
   202  	matcherSet := make(caddy.ModuleMap)
   203  	for name, matcher := range matcherMap {
   204  		jsonBytes, err := json.Marshal(matcher)
   205  		if err != nil {
   206  			return nil, d.Errf("marshaling %T matcher: %v", matcher, err)
   207  		}
   208  		matcherSet[name] = jsonBytes
   209  	}
   210  
   211  	return matcherSet, nil
   212  }
   213  
   214  // SetModuleNameInline sets the string value of moduleNameKey to moduleName in raw,
   215  // where raw must be a JSON encoding of a map, and returns the modified raw.
   216  // In fact, it is a reverse function for caddy.getModuleNameInline.
   217  func SetModuleNameInline(moduleNameKey, moduleName string, raw json.RawMessage) (json.RawMessage, error) {
   218  	// temporarily unmarshal json into a map of string to any
   219  	var tmp map[string]any
   220  	err := json.Unmarshal(raw, &tmp)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	// add an inline key with the module name
   226  	tmp[moduleNameKey] = moduleName
   227  
   228  	// re-marshal the map into json
   229  	result, err := json.Marshal(tmp)
   230  	if err != nil {
   231  		return nil, fmt.Errorf("re-encoding module '%s' configuration: %v", moduleName, err)
   232  	}
   233  
   234  	return result, nil
   235  }