Writing a TCP/UDP server type for Caddy


Finally I got around to start looking at this.

I’ve started a basic server type (echo) just to first wrap my head around the mechanics of adding a new server type on my Github page based on the wiki page and using CoreDNS as an example.

I’ve struggled a bit to get my head around the caddy.Context and how to write some main entry point to see how it works. The code compiles, however when I run the app I get no server types plugged in.

Anyways, there’s still alot of work to be done and I’ll be adding changes with time.

Any comments on how I’ve implemented this are welcome!

1 Like

Hey @pieterlouw - nice start on that!

A couple tips:

  • You don’t need a separate main() function. Just plug it into Caddy: https://github.com/mholt/caddy/blob/75ccc05d840923a1dc731c569973471c5ce4654a/caddy/caddymain/run.go#L19-L20 - then run caddy -type=tcpecho. This means you can eliminate main.go and the tcpechomain folder from your repo. (CoreDNS started as a fork before Caddy had pluggable server types, so now there’s no need for your own main function or package. I still would like to help CoreDNS simplify in that sense.)

  • The configuration file is still called a Caddyfile (not “Echofile”) - remember, Caddy serves many types, not just HTTP. Caddy !== HTTP. You can write a Caddyfile that serves TCP (or any other server type).

  • I think you accidentally committed your binary files to the repo.

Anyway, see if these tips help get rid of that pesky “no server types plugged in” error. Again, you shouldn’t need a separate repo or main() function. Just add a line to Caddy’s run.go file and you should be up and running.

1 Like

Thanks for the tips @matt

Can one leverage http server type directives,or should they be implemented per server type?

Directives should almost always be implemented per server type, but it depends on the directive. For example, the tls directive can work with any server type, but the server type has to plug into it, instead of the other way around. I suspect this won’t be very common though.

Hi @matt,

Okay, I’ve cleaned up the repo a bit (removed main code) and renamed it from tcpecho to echo as I want to add UDP as well.

I have also managed to get it to run within Caddy - Achievement unlocked! :slight_smile: Thanks for your help.

I have enough now to start implementing something more than echo now, thanks for your help


Great! Keep me posted about the progress of this TCP/UDP server. I will be happy to help you get it ready to ship and put on the Caddy website.

Lots of people are hankering for a TCP/UDP proxy / load balancer for Caddy. Let’s make it happen, if you’re willing! :slight_smile:

1 Like

I would like it very much to get it up and running and make it happen.The plan is to add more TCP/UDP server types.

Do you think I should have a server type for each function i.e echo, proxy, ssh etc. or one server type that have directives/middleware for each function?

This is a good question… the answer is, I’m not entirely sure. I imagine an SSH server would be its own kind, but I would be surprised if echo and proxy were different types. Why’s that?

Can one server type listen on more than one port? I ask as I don’t see a reason why one would want to echo and proxy on the same port.

They are very similar but I don’t see yet why they should be one server type.

Also,if they get combined I wonder what the server type will be called

Yes, definitely. The HTTP server often listens on both 80 and 443.

Different server types should do different things. I was thinking they should differ by protocol, as you configure an SSH server differently from an HTTP server, for example.

Ah yes, you’re right, I’m seeing now how echo and proxy should be together as they are the same on protocol level - straight TCP and UDP, although they do different things, but I’ll see how I can combine them.

That said, TCP and UDP are different protocols which opens up the question if they should not be separate… ( am I over analyzing this a bit?)

They can, but not necessarily always, do different things. For example, HTTP, HTTPS, and QUIC are all somewhat different, but they kinda do the same thing. In fact, QUIC uses UDP whereas HTTP and (usually) HTTPS use TCP. Anyway, it’s not well defined. Definitely a fuzzy area.

I don’t know the answer to that. What you could do is draft up your design plans: what the Caddyfile would look like for your server type - what the keys for each block would be (the HTTP server uses site addresses as keys), what the directives would be and what they would do, etc. If there’s a lot of overlap between TCP and UDP, then maybe they should be the same server type. Feel free to start a new thread about that, I think that’d be very useful.

Good idea, let me go through some iterations and see what emerges.

Hi @matt
After some tinkering I think this should be one server type, serving both TCP and UDP, and echoing and proxying can be specified per server block in the Caddyfile as follow:

localhost:8080 {

localhost:12017 {
    proxy localhost:22017

The first server block will listen on port 8080 and echo any traffic back to caller

The second server block will listen on port 12017 and forward traffic to address localhost:22017

Rule:A server block can only echo or proxy, not both.

The server type will simply be called net

This is till very much work in progress. but the echoing and proxying work when started with caddy -type=net. The listening addresses can be configured in the Caddyfile, however proxying is fixed at but will soon be configurable.

Here’s the repo: caddy-net

Interesting! Good progress so far.

Couple questions:

  1. What value does a hostname have for a TCP/UDP server? Does TCP even use hostnames for listening? Maybe you could get away with just a port.

  2. If echo or proxy (but not both) can be specified, what about this instead:

echo {
    # I guess this assumes there's no config for echo
proxy :12017 :22017 {
    # proxy config

It’s a little unorthodox, but… just an idea.

Good questions,

  1. You’re right,it’s not necessary to use hostnames for a TCP server,however there may be more than one IP address on a server and would want to bind to a specific interface maybe.

  2. I like the layout you propose for config. I’m not sure what config there might be for echo,so this works good.

Let me work on this and UDP and SSL then I think it will be very close to being able to be used.

This is a good point; I suspect most users will leave the host empty, however. Might be best to do docs that don’t show a hostname or show an IP address that would make sense to bind to.

Anyway, I’m excited to see what you come up with. The (auto) TLS stuff can be a little tricky… so let me know if you need some guidance on that.

I’m going to split these replies off into their own new topic, if that’s alright with you.

1 Like

I’m done with the echo and proxy parts of both TCP and UDP.

The final step is adding TLS.

As hostnames don’t really feature in TCP implementations , do you think it will be okay to have a tls section in the Caddyfile where the hostname can be specified?

Also, will it be possible to test TLS , especially the auto TSL, on my development machine?

1 Like

Interesting. I’m actually wondering how I can use the onstartup, shutdown directives in CoreDNS, without forking the code into the CoreDNS repo.

To test auto TLS I did the following:

  • create a domain name in a zone you own
  • point that name to you the IP for you internet
  • forward the ports on your router to your development machine

It is somewhat annoying to setup, but this allowed me to let let’s encrypt give me certs.

1 Like