1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
|
# `go-listener`
[](https://pkg.go.dev/src.agwa.name/go-listener)
`src.agwa.name/go-listener` is a Go library for creating `net.Listener`s.
Typically, server software only supports listening on TCP ports. `go-listener` makes it easy to also listen on:
* TCP ports
* UNIX domain sockets
* Pre-opened file descriptors
Additionally, `go-listener` makes it easy to support:
* The [PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)
* TLS (with several options for certificate management, including ACME)
Listeners are specified using a string syntax, which makes them easy to pass as command line arguments.
## How To Use
```go
import "src.agwa.name/go-listener"
netListener, err := listener.Open(listenerString)
if err != nil {
// Handle err
}
defer netListener.Close()
```
`listener.Open` takes a string which describes a listener per the syntax described below, and returns a `net.Listener`, which you can use by calling `Accept`, passing to `http.Serve`, etc.
## Listener Syntax
### TCP
Listen on all interfaces:
```
tcp:PORT
```
Listen on a specific IPv4 interface:
```
tcp:IPV4ADDRESS:PORT
```
Listen on a specific IPv6 interface:
```
tcp:[IPV6ADDRESS]:PORT
```
Listen on all IPv4 interfaces:
```
tcp:0.0.0.0:PORT
```
Listen on all IPv6 interfaces:
```
tcp:[::]:PORT
```
### UNIX Domain Socket
```
unix:PATH
```
### File Descriptor
Listen on a file descriptor that is already open, bound, and listening:
```
fd:NUMBER
```
### Named File Descriptor (compatible with systemd socket activation)
Listen on a named file descriptor that is already open, bound, and listening:
```
fdname:NAME
```
`NAME` must match the `FileDescriptorName` option in the systemd socket file.
### PROXY Protocol
Wrap a listener with the [PROXY Protocol version 2](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt):
```
proxy:LISTENER
```
(where `LISTENER` is one of the syntaxes specified here)
`go-listener` will transparently read the PROXY protocol header and make the true client IP address available via the `net.Conn`'s `LocalAddr` method.
### TLS
Note: TLS support must be enabled by importing `src.agwa.name/go-listener/tls` like this:
```go
import _ "src.agwa.name/go-listener/tls"
```
Wrap a listener with TLS, using the certificate/key in the given file (which must be absolute path):
```
tls:/PATH/TO/CERTIFICATE_FILE:LISTENER
```
Wrap a listener with TLS, using the certificate/key named `SERVER_NAME.pem` in the given directory (which must be an absolute path and end with a slash):
```
tls:/PATH/TO/CERTIFICATE_DIRECTORY/:LISTENER
```
Wrap a listener with TLS and automatically obtain certificates for each hostname using ACME (requires the hostname to be publicly-accessible on port 443):
```
tls:HOSTNAME,HOSTNAME,...:LISTENER
```
#### Certificate Files
When you specify a certificate file or directory, certificates must be PEM-encoded and contain the following blocks:
* Exactly one `PRIVATE KEY`, containing the private key in PKCS#8 format.
* At least one `CERTIFICATE`, comprising the certificate chain, leaf certificate first and root certificate omitted.
* Up to one `OCSP RESPONSE`, containing a stapled OCSP response.
* Any number of `SIGNED CERTIFICATE TIMESTAMP`, containing stapled SCTs.
Certificate files are automatically reloaded when they change.
#### ACME Configuration
When you obtain certificates automatically, the following environment variables can be used to configure the ACME client:
| Environment Variable | Description | Default |
| ---------------------- | ----------- | ------- |
| `AUTOCERT_ACME_SERVER` | The directory URL of the certificate authority's ACME server | [`autocert.DefaultACMEDirectory`](https://pkg.go.dev/golang.org/x/crypto/acme/autocert#DefaultACMEDirectory) |
| `AUTOCERT_EMAIL` | Contact email address for your ACME account, used by certificate authority to notify you of certificate problems (highly recommended) | (none) |
| `AUTOCERT_EAB_KID` | Key ID of the External Account Binding to use with ACME | (none) |
| `AUTOCERT_EAB_KEY` | base64url-encoded HMAC-SHA256 key of the External Account Binding to use with ACME | (none) |
| `AUTOCERT_CACHE_DIR` | The directory where issued certificates are stored | When root, `/var/lib/autocert-cache`; otherwise, `autocert-cache` under [`$XDG_DATA_HOME`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) |
## Example
Here's how to use `go-listener` with `http.Server`:
```go
package main
import (
"flag"
"log"
"net/http"
"src.agwa.name/go-listener"
_ "src.agwa.name/go-listener/tls"
)
func main() {
var listenerString string
flag.StringVar(&listenerString, "listen", "", "Socket to listen on")
flag.Parse()
netListener, err := listener.Open(listenerString)
if err != nil {
log.Fatal(err)
}
defer netListener.Close()
log.Fatal(http.Serve(netListener, nil))
}
```
Listen on localhost, port 80:
```
httpd -listen tcp:127.0.0.1:80
```
Listen on IPv6 localhost, port 80:
```
httpd -listen tcp:[::1]:80
```
Listen on file descriptor 3:
```
httpd -listen fd:3
```
Listen on port 443, all interfaces, with TLS, using certificates in `/var/certs`:
```
httpd -listen tls:/var/certs/:tcp:443
```
Listen on port 443, all interfaces, with TLS, with automatic certificates for `www.example.com` and `example.com`:
```
httpd -listen tls:www.example.com,example.com:tcp:443
```
Listen on UNIX domain docket `/run/example.sock` with the PROXY protocol:
```
httpd -listen proxy:unix:/run/example.sock
```
Listen on UNIX domain socket `/run/example.sock` with TLS and the PROXY protocol, with certificate in `/etc/ssl/example.com.pem`:
```
httpd -listen tls:/etc/ssl/example.com.pem:proxy:unix:/run/example.sock
```
(Details: `go-listener` will listen on `/run/example.sock`. When a connection is accepted, `go-listener` will first read the PROXY protocol header to get the true client IP address, which will be made available through the `net.Conn`'s `LocalAddr` method. It will then do a TLS handshake using the private key and certificate in `/etc/ssl/example.com.pem`.)
### Socket Activation
Here's how to use systemd socket activation to run httpd as an unprivileged user listening on port 80 (which is a privileged port):
In `/etc/systemd/system/httpd.socket` put:
```
[Socket]
ListenStream=80
[Install]
WantedBy=sockets.target
```
In `/etc/systemd/system/httpd.service` put:
```
[Service]
ExecStart=/path/to/httpd -listen fd:3
DynamicUser=yes
[Install]
WantedBy=multi-user.target
```
You can also name the socket using the `FileDescriptorName` option in the `httpd.socket` file, and refer to it using the `fdname` listener type (instead of `fd:3`).
You don't have to use systemd; the `fd` listener type can be used with any process supervisor which supports listening on a file descriptor, dropping privileges, and passing the listening file descriptor to the daemon.
|