suika/cmd/soju/main.go
chris f42c6694ff Set a higher timeout for proxyproto listeners
go-proxyproto added support for a read timeout in 0.6.0[1] and
defaulted it to 200ms. After this time if no data is read on
the socket, it is closed.

This is _really_ low if the underlying connection is a TLS
one as no data pops out the other end until the handshake is
done. It effectively limits you to TLS connections within
a 50ms RTT of your bouncer with clients that are fast enough
at responding.

It appears that HexChat on Arch is somehow slow enough at
TLS connections thant it consistently takes longer than
200ms even over localhost, meaning it outright can't connect
to soju any longer.

To make this a lot less painful, have soju pass in a read
timeout of 5 seconds. This feels like a reasonable tradeoff
between keeping (possibly malicious) connections open and
accepting the realities of network connections.

[1]: https://github.com/pires/go-proxyproto/issues/65

git-svn-id: file:///srv/svn/repo/suika/trunk@592 f0ae65fe-ee39-954e-97ec-027ff2717ef4
2021-09-19 15:00:40 +00:00

259 lines
6.1 KiB
Go

package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"sync/atomic"
"syscall"
"time"
"github.com/pires/go-proxyproto"
"git.sr.ht/~emersion/soju"
"git.sr.ht/~emersion/soju/config"
)
// TCP keep-alive interval for downstream TCP connections
const downstreamKeepAlive = 1 * time.Hour
type stringSliceFlag []string
func (v *stringSliceFlag) String() string {
return fmt.Sprint([]string(*v))
}
func (v *stringSliceFlag) Set(s string) error {
*v = append(*v, s)
return nil
}
func main() {
var listen []string
var configPath string
var debug bool
flag.Var((*stringSliceFlag)(&listen), "listen", "listening address")
flag.StringVar(&configPath, "config", "", "path to configuration file")
flag.BoolVar(&debug, "debug", false, "enable debug logging")
flag.Parse()
var cfg *config.Server
if configPath != "" {
var err error
cfg, err = config.Load(configPath)
if err != nil {
log.Fatalf("failed to load config file: %v", err)
}
} else {
cfg = config.Defaults()
}
cfg.Listen = append(cfg.Listen, listen...)
if len(cfg.Listen) == 0 {
cfg.Listen = []string{":6697"}
}
db, err := soju.OpenSqliteDB(cfg.SQLDriver, cfg.SQLSource)
if err != nil {
log.Fatalf("failed to open database: %v", err)
}
var tlsCfg *tls.Config
var tlsCert atomic.Value
if cfg.TLS != nil {
cert, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath)
if err != nil {
log.Fatalf("failed to load TLS certificate and key: %v", err)
}
tlsCert.Store(&cert)
tlsCfg = &tls.Config{
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return tlsCert.Load().(*tls.Certificate), nil
},
}
}
srv := soju.NewServer(db)
// TODO: load from config/DB
srv.Hostname = cfg.Hostname
srv.LogPath = cfg.LogPath
srv.HTTPOrigins = cfg.HTTPOrigins
srv.AcceptProxyIPs = cfg.AcceptProxyIPs
srv.Debug = debug
for _, listen := range cfg.Listen {
listenURI := listen
if !strings.Contains(listenURI, ":/") {
// This is a raw domain name, make it an URL with an empty scheme
listenURI = "//" + listenURI
}
u, err := url.Parse(listenURI)
if err != nil {
log.Fatalf("failed to parse listen URI %q: %v", listen, err)
}
switch u.Scheme {
case "ircs", "":
if tlsCfg == nil {
log.Fatalf("failed to listen on %q: missing TLS configuration", listen)
}
host := u.Host
if _, _, err := net.SplitHostPort(host); err != nil {
host = host + ":6697"
}
ircsTLSCfg := tlsCfg.Clone()
ircsTLSCfg.NextProtos = []string{"irc"}
lc := net.ListenConfig{
KeepAlive: downstreamKeepAlive,
}
l, err := lc.Listen(context.Background(), "tcp", host)
if err != nil {
log.Fatalf("failed to start TLS listener on %q: %v", listen, err)
}
ln := tls.NewListener(l, ircsTLSCfg)
ln = proxyProtoListener(ln, srv)
go func() {
if err := srv.Serve(ln); err != nil {
log.Printf("serving %q: %v", listen, err)
}
}()
case "irc+insecure":
host := u.Host
if _, _, err := net.SplitHostPort(host); err != nil {
host = host + ":6667"
}
lc := net.ListenConfig{
KeepAlive: downstreamKeepAlive,
}
ln, err := lc.Listen(context.Background(), "tcp", host)
if err != nil {
log.Fatalf("failed to start listener on %q: %v", listen, err)
}
ln = proxyProtoListener(ln, srv)
go func() {
if err := srv.Serve(ln); err != nil {
log.Printf("serving %q: %v", listen, err)
}
}()
case "unix":
ln, err := net.Listen("unix", u.Path)
if err != nil {
log.Fatalf("failed to start listener on %q: %v", listen, err)
}
ln = proxyProtoListener(ln, srv)
go func() {
if err := srv.Serve(ln); err != nil {
log.Printf("serving %q: %v", listen, err)
}
}()
case "wss":
if tlsCfg == nil {
log.Fatalf("failed to listen on %q: missing TLS configuration", listen)
}
addr := u.Host
if _, _, err := net.SplitHostPort(addr); err != nil {
addr = addr + ":https"
}
httpSrv := http.Server{
Addr: addr,
TLSConfig: tlsCfg,
Handler: srv,
}
go func() {
if err := httpSrv.ListenAndServeTLS("", ""); err != nil {
log.Fatalf("serving %q: %v", listen, err)
}
}()
case "ws+insecure":
addr := u.Host
if _, _, err := net.SplitHostPort(addr); err != nil {
addr = addr + ":http"
}
httpSrv := http.Server{
Addr: addr,
Handler: srv,
}
go func() {
if err := httpSrv.ListenAndServe(); err != nil {
log.Fatalf("serving %q: %v", listen, err)
}
}()
case "ident":
if srv.Identd == nil {
srv.Identd = soju.NewIdentd()
}
host := u.Host
if _, _, err := net.SplitHostPort(host); err != nil {
host = host + ":113"
}
ln, err := net.Listen("tcp", host)
if err != nil {
log.Fatalf("failed to start listener on %q: %v", listen, err)
}
ln = proxyProtoListener(ln, srv)
go func() {
if err := srv.Identd.Serve(ln); err != nil {
log.Printf("serving %q: %v", listen, err)
}
}()
default:
log.Fatalf("failed to listen on %q: unsupported scheme", listen)
}
log.Printf("server listening on %q", listen)
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
if err := srv.Start(); err != nil {
log.Fatal(err)
}
for sig := range sigCh {
switch sig {
case syscall.SIGHUP:
if cfg.TLS != nil {
log.Print("reloading TLS certificate")
cert, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath)
if err != nil {
log.Printf("failed to reload TLS certificate and key: %v", err)
break
}
tlsCert.Store(&cert)
}
case syscall.SIGINT, syscall.SIGTERM:
log.Print("shutting down server")
srv.Shutdown()
return
}
}
}
func proxyProtoListener(ln net.Listener, srv *soju.Server) net.Listener {
return &proxyproto.Listener{
Listener: ln,
Policy: func(upstream net.Addr) (proxyproto.Policy, error) {
tcpAddr, ok := upstream.(*net.TCPAddr)
if !ok {
return proxyproto.IGNORE, nil
}
if srv.AcceptProxyIPs.Contains(tcpAddr.IP) {
return proxyproto.USE, nil
}
return proxyproto.IGNORE, nil
},
ReadHeaderTimeout: 5 * time.Second,
}
}