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 }