Repository: caddyserver/caddy (language: go)

Issue:
caddyhttp: allow custom network for HTTP/3
Caddy has great support for supporting custom networks via the `RegisterNetwork` hook:

https://github.com/caddyserver/caddy/blob/cee4441cb1d485b38d728168a315cda5641d84fb/listeners.go#L682-L686

This allows us to serve http on a custom network with the caddyhttp module. Unfortunately, for http3, this only applies to the networks that are support by default in caddy. In caddyhttp, we switch over the network of the http1.1 listener to figure out the corresponding network for the http3 listener. This switch statement does not consider custom networks that were registered:
https://github.com/caddyserver/caddy/blob/cee4441cb1d485b38d728168a315cda5641d84fb/modules/caddyhttp/server.go#L519-L532

Instead of hard-coding this mapping, we could also introduce a hook system like we already have for `RegisterNetwork`.
The API could look something like this:

```go
package caddyhttp

// RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3
// network. This should be called during init() and will panic if the network
// type is standard or reserved, or if it is already registered. EXPERIMENTAL
// and subject to change.
func RegisterNetworkHTTP3(originalNetwork, h3Network string) {}
```

If this is something you are interested in, I would be happy to contribute code.

















Discussion / hints:
Seems reasonable, but I'd like to better understand why you're writing custom networks. What are you implementing?
> Seems reasonable

Cool, if you agree with the function signature, I will create a patch to implement the functionality.

>  but I'd like to better understand why you're writing custom networks. What are you implementing?

I'm building a plugin to enable Caddy to be connected to a [SCION](https://scion-architecture.net/) network.

Currently, we are using Caddy to serve an API that is access via HTTP via IP. It acts as a reverse proxy and takes care of authN/authZ.

The idea is to also expose this API on the SCION network. Currently, the [networking library](https://github.com/scionproto/scion/tree/master/pkg/snet) only supports UDP and no TCP. Given QUIC is UDP based, I'm planning to access the appliance via HTTP/3 over SCION. So, what my plugin will do is register a new SCION network type to the listeners. We need the mapping for the listener that is used in HTTP/3, because that will also need to be a custom SCION network.

By writing the plugin, we can leverage the excellent work of Caddy and expose applications to the SCION network without changing the applications themselves (by means of the reverse proxy). It also allows us to share the same authN/authZ logic between the regular IP world and the SCION world.


Is `RegisterNetwork()` unsuitable because of its return value? Do you need something that's more Packet-oriented instead of Listener-oriented?
@mholt `RegisterNetwork` is perfectly fine for registering the network. It does exactly what we need.

However, for HTTP/3 we map the initial network to a different network. (Because HTTP/1.1 and HTTP/2 work over TCP and HTTP/3 works over UDP). There is currently no way to define how the original listener is mapped.

I guess an alternative would be to be explicit in the configuration what listener should be used for HTTP/3, instead of doing the implicit mapping in `serveHTTP3`. But this would require additional configuration, and would probably break backwards compatibility.

I see, thanks. So what if we made that `switch` in the `serveHTTP3()` method reading from a map instead? A map that can have entries added to it. (I assume you still want to leave the default mapping, right? And just add another?)
@mholt that is essentially what I'm proposing.
Maybe it was unclear from the initial comment.

The `RegisterNetworkHTTP3` function will allow plugins to register the custom mapping in that map.

Okay, seems like both of you are on board. I will create a PR soon.
@oncilla Ok, gotcha. Yeah, I suppose that is a good start then. Thanks!

We'll mark it as experimental since I don't know how HTTP3-specific it actually is (future-proofing). But definitely open to a PR.

Fix patch:
diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go
index db370db6751..4bbd23b6820 100644
--- a/modules/caddyhttp/server.go
+++ b/modules/caddyhttp/server.go
@@ -520,17 +520,7 @@ func (s *Server) findLastRouteWithHostMatcher() int {
 // not already done, and then uses that server to serve HTTP/3 over
 // the listener, with Server s as the handler.
 func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error {
-	switch addr.Network {
-	case "unix":
-		addr.Network = "unixgram"
-	case "tcp4":
-		addr.Network = "udp4"
-	case "tcp6":
-		addr.Network = "udp6"
-	default:
-		addr.Network = "udp" // TODO: Maybe a better default is to not enable HTTP/3 if we do not know the network?
-	}
-
+	addr.Network = getHTTP3Network(addr.Network)
 	lnAny, err := addr.Listen(s.ctx, 0, net.ListenConfig{})
 	if err != nil {
 		return err
@@ -918,3 +908,30 @@ const (
 	// For tracking the real client IP (affected by trusted_proxy)
 	ClientIPVarKey string = "client_ip"
 )
+
+var networkTypesHTTP3 = map[string]string{
+	"unix": "unixgram",
+	"tcp4": "udp4",
+	"tcp6": "udp6",
+}
+
+// RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3
+// network. This should be called during init() and will panic if the network
+// type is standard, reserved, or already registered.
+//
+// EXPERIMENTAL: Subject to change.
+func RegisterNetworkHTTP3(originalNetwork, h3Network string) {
+	if _, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)]; ok {
+		panic("network type " + originalNetwork + " is already registered")
+	}
+	networkTypesHTTP3[originalNetwork] = h3Network
+}
+
+func getHTTP3Network(originalNetwork string) string {
+	h3Network, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)]
+	if !ok {
+		// TODO: Maybe a better default is to not enable HTTP/3 if we do not know the network?
+		return "udp"
+	}
+	return h3Network
+}


Test patch: