Server types other than HTTP

Caddy 0.9 will allow you to plug in almost anything, including server types. This has huge implications: Caddy will soon be able to serve more than just HTTP.

Why, or who cares? As I was musing earlier, imagine the joy of using an SSH server that:

  • is not vulnerable to most memory mistakes like C programs are,
  • can be deployed to any platform, including Windows and Android,
  • is statically-compiled (no dependencies),
  • is configured with the ease of the Caddyfile, consistent with your web server
  • can be restarted with zero downtime

To clarify, I’m not going to write this. I’m too busy. But there is already a pure Go implementation of SSH and “all” you’d have to do is wrap that library in a way that plugs into Caddy. Such an SSH server would be used with a Caddyfile – with its own directives and plugins.

The really cool part is that a single Caddy binary could potentially serve both SSH and HTTP (if you wanted to, or you could have a process for each). Both can be seamlessly upgraded or reloaded, and both could even benefit from the API (slated for version 0.10).

Is this ringing through clearly? Do you like this? What other server types would you want to see for Caddy?

5 Likes

As one that spends a lot of time in SSH, this would be nice. Be sure to allow for moving the port around, disallowing remote root access, no password access (only certs), and allowing a choice of IPv4, IPv6, or both. Whatever is needed for optional port knocking. I’ll stop now. Thanks!

DNS. I’d love to have a Caddy-esque DNS server.

There is CoreDNS, a fork of Caddy.

1 Like

@abiosoft I’m in a car right now, and restructuring my DNS setup over a mobile SSH connection is a bad idea, but… Gonna give this a go sometime, thanks :smiley:

(I still vote Caddy gets DNS support!)

1 Like

Mail!

I would like to see Caddy provide a mail server - both IMAP, POP3 and SMTP would be great.

It is something I’ve thought of spending time on - but as with so many things, time is allocated differently.

3 Likes

Do you think it’s in the scope of Caddy to host a full email stack? Hosting the complete stack requires SMTP/IMAP, persistence for mails (e.g. databases), user management and integration with other systems, webmail, …

SMTP isn’t any problem, I have a working MTA which should be pluggable into Caddy. But only an MTA isn’t really a working thing, and everyone would be using his own mail handlers etc, to do something useful with the received message.

1 Like

Caddy the web server? No. Caddy the library? Yes. With Caddy 0.9, the web server is a plugin just like any other server type.

4 Likes

I’ll usually resort to Citadel for email (works dandy), but the Ubuntu package maintainer must be testing on a Raspberry Pi. A full golang email stack would be splendid.

All we need after that is a golang kernel and gnu.

:cherries::cherries::cherries:
:seven::seven::seven:
:cherries::cherries::cherries:

1 Like

A simple SMTP server like DenBeke suggested would be quite useful.

Sometimes server-side software just doesn’t get its own email sending right, especially with forum software.

Using an external application or external provider for SMTP works well with this software, and having caddy provide that directly would package it quite neatly as solution. :smiley:

So I’m porting CoreDNS back to caddy as a server plugin. Just looking at the Server interface this is probably not suffficient. Seeing the commented out UDP code above. I think the following Server interface makes more sense. Most tcp-only server will make the packetConn ones a noop

type Server interface {
	// Listen starts listening by creating a new listener
	// and returning it. It does not start accepting
	// connections.
	Listen() (net.Listener, error)
        ListenPacket() (net.PacketConn, error)

	// Serve starts serving using the provided listener.
	// Serve must start the server loop nearly immediately,
	// or at least not return any errors before the server
	// loop begins. Serve blocks indefinitely, or in other
	// words, until the server is stopped.
	Serve(net.Listener) error
        Serve(net.PacketConn) error
}

Of course having to methods called Serve won’t work, improvements welcome. From the looks of it I can strip CoreDNS of most stuff and make this a plugin relative easy.

2 Likes

Currently I use
func (s *Server) Serve(ln net.Listener, pc net.PacketConn) error {

for the Serve function (Listen() could return the same - also what I’m doing now). Downside of that is the empty argument you’ll need to give when doing a TCP only server.

What about two interfaces, one is a TCPServer and another is a UDPServer? Caddy could probably do a type assertion then, instead of implementing no-ops.

I know I suggested this in email but wasn’t really clear on your take on it. What about wrapping the net.Listener instead, with this kind of thing (is this the “commented out UDP code” you were referring to?):

type UDPListener struct {
	*net.UDPConn
}

func (u UDPListener) Accept() (net.Conn, error) {
	return u.UDPConn, nil
}

func (u UDPListener) Close() error {
	return u.UDPConn.Close()
}

func (u UDPListener) Addr() net.Addr {
	return u.UDPConn.LocalAddr()
}

(Here I use UDPConn but perhaps PacketConn could be used in its place. UDPConn is just an implementation of PacketConn I think.)

I know it’s a little weird to call Accept() but this way there are simply Servers, which is much simpler IMO. Will this not work for CoreDNS? I want to confirm before I introduce more complexity into such a critical part of the Caddy core.

Thanks for working on this BTW!! I think this will be really awesome when complete. :+1:

I’m not sure about the Accept call. IOW: I need to fiddle with a bit. Will probably open a PR for the change I need (or make sense to me)

1 Like

What does:

var _ net.Listener = UDPListener{}

actually do?

Hmmm, doing UDP and TCP separately is weird. I parse all zones on startup, this means I have to do it twice I find some way of sharing this between two separate server instances.

How about using a slice of net.Listeners and making the UDP one a listener as well (as you proposed), I think that would work. I can start a single server with 2 listeners (or more).

That ensures UDPListener implements the net.Listener interface at compile-time. Not sure how helpful it is yet, but, thought I’d try it.

Hmm, that’s a possibility. But wait, is this to solve this problem:

If so, there is one place where all your different servers (UDP and TCP) are together: in the MakeServers() function. You should be able to parse zones once and just set a field in both servers to share that. For example, the HTTP server iterates all the site configs that were built in the context, and then creates servers by grouping them. You could do something similar, where your context has the information necessary to create both TCP and UDP servers, so for each server you make you just copy the state into it or set a pointer to it or something.

Does that make sense? If it helps, maybe DM me here or on Slack and I’ll be happy to look at some code.

But if it doesn’t work out, I’m not opposed to making the proposed change ^, I just need to look to make sure it won’t break other parts like restarts. :sweat_smile:

Ah, nice. You learn something every day. :slight_smile:

Hmm, that’s a possibility. But wait, is this to solve this problem:

If so, there is one place where all your different servers (UDP and TCP) are together: in the MakeServers() function. You should be able to parse zones once and just set a field in both servers to share that. For example, the HTTP server iterates all the site configs that were built in the context, and then creates servers by grouping them. You could do something similar, where your context has the information necessary to create both TCP and UDP servers, so for each server you make you just copy the state into it or set a pointer to it or something.

Does that make sense? If it helps, maybe DM me here or on Slack and I’ll be happy to look at some code.

But if it doesn’t work out, I’m not opposed to making the proposed change ^, I just need to look to make sure it won’t break other parts like restarts. :sweat_smile:
[/quote]
No that makes perfect sense. I hacking in this branch https://github.com/miekg/coredns/tree/back-to-caddy. This was just the first step, that prompted me to prod you with questions :slight_smile:

1 Like

That’s great!
Any plugins planned (like ssh…)?

Following here to be up to date :wink: