Now supports socket activation
This commit is contained in:
@@ -3,10 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/coreos/go-systemd/activation"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/thequux/qddns/common"
|
"github.com/thequux/qddns/common"
|
||||||
"github.com/thequux/qddns/db"
|
"github.com/thequux/qddns/db"
|
||||||
_ "go.uber.org/zap"
|
"github.com/thequux/qddns/multilistener"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -96,7 +97,16 @@ func main() {
|
|||||||
r.POST("/update/:domain", Update)
|
r.POST("/update/:domain", Update)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if _, err = net.ResolveTCPAddr("tcp", *listen); err == nil {
|
if listeners, err := activation.Listeners(); err == nil && len(listeners) > 0 {
|
||||||
|
// Socket activation
|
||||||
|
var listener net.Listener
|
||||||
|
if len(listeners) > 1 {
|
||||||
|
listener, _ = multilistener.New(listeners...)
|
||||||
|
} else {
|
||||||
|
listener = listeners[0]
|
||||||
|
}
|
||||||
|
err = r.RunListener(listener)
|
||||||
|
} else if _, err = net.ResolveTCPAddr("tcp", *listen); err == nil {
|
||||||
err = r.Run(*listen)
|
err = r.Run(*listen)
|
||||||
} else {
|
} else {
|
||||||
// Probably a UNIX address
|
// Probably a UNIX address
|
||||||
|
|||||||
49
flake.nix
49
flake.nix
@@ -1,29 +1,28 @@
|
|||||||
{
|
{
|
||||||
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
|
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
|
||||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||||
outputs = {self, nixpkgs, flake-utils, ...}: flake-utils.lib.eachDefaultSystem (system:
|
outputs = {self, nixpkgs, flake-utils, ...}:
|
||||||
let pkgs = nixpkgs.legacyPackages.${system};
|
{
|
||||||
in rec {
|
nixosModules.qddns = import ./nix/qddns-module.nix self;
|
||||||
defaultPackage = pkgs.buildGoModule {
|
} //
|
||||||
pname = "qddns";
|
(flake-utils.lib.eachDefaultSystem (system:
|
||||||
version = "1.0";
|
let pkgs = nixpkgs.legacyPackages.${system};
|
||||||
src = ./.;
|
in rec {
|
||||||
vendorSha256 = "sha256-JsN+NFYOQskocFKcjklQmXQTEKgnoZd6R/v/335X2CI=";
|
defaultPackage = pkgs.buildGoModule {
|
||||||
|
pname = "qddns";
|
||||||
preferLocalBuild = true;
|
version = "1.0";
|
||||||
};
|
src = ./.;
|
||||||
apps.qddns-server = {
|
vendorSha256 = "sha256-J/r7K5gDVNmd+hGH7tFBOMPcuPzo0PWAqvyX19EnkLc=";
|
||||||
type = "app";
|
|
||||||
program = "${defaultPackage}/bin/qddns-server";
|
preferLocalBuild = true;
|
||||||
};
|
};
|
||||||
apps.qddns-client = {
|
apps.qddns-admin = {type = "app"; program = "${defaultPackage}/bin/qddns-admin";};
|
||||||
type = "app";
|
apps.qddns-client = {type = "app"; program = "${defaultPackage}/bin/qddns-client";};
|
||||||
program = "${defaultPackage}/bin/qddns-client";
|
apps.qddns-server = {type = "app"; program = "${defaultPackage}/bin/qddns-server";};
|
||||||
};
|
devShells.default = pkgs.mkShell {
|
||||||
devShells.default = pkgs.mkShell {
|
nativeBuildInputs = [
|
||||||
nativeBuildInputs = [
|
pkgs.go
|
||||||
pkgs.go
|
];
|
||||||
];
|
};
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -6,10 +6,10 @@ require (
|
|||||||
github.com/alecthomas/kong v0.6.1
|
github.com/alecthomas/kong v0.6.1
|
||||||
github.com/gin-gonic/gin v1.8.1
|
github.com/gin-gonic/gin v1.8.1
|
||||||
github.com/jackc/pgx/v4 v4.16.1
|
github.com/jackc/pgx/v4 v4.16.1
|
||||||
go.uber.org/zap v1.13.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -9,6 +9,8 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
|
|||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|||||||
21
multilistener/LICENSE
Normal file
21
multilistener/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Daniel Garcia
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
125
multilistener/listener.go
Normal file
125
multilistener/listener.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package multilistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listener implements a net.Listener interface but multiplexes connections
|
||||||
|
// from multiple listeners.
|
||||||
|
type Listener struct {
|
||||||
|
listeners []net.Listener
|
||||||
|
closing chan struct{}
|
||||||
|
conns chan acceptResults
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResults struct {
|
||||||
|
conn net.Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Listener = &Listener{}
|
||||||
|
|
||||||
|
// New creates an instance of a Listener using the given listeners. You must
|
||||||
|
// pass at least one listener. The new listener object listens for new connection
|
||||||
|
// on all the given listeners.
|
||||||
|
func New(listeners ...net.Listener) (*Listener, error) {
|
||||||
|
|
||||||
|
if len(listeners) == 0 {
|
||||||
|
return nil, fmt.Errorf("multilistener requires at least 1 listener")
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &Listener{
|
||||||
|
listeners: listeners,
|
||||||
|
closing: make(chan struct{}),
|
||||||
|
conns: make(chan acceptResults),
|
||||||
|
}
|
||||||
|
for i := range n.listeners {
|
||||||
|
go n.loop(n.listeners[i])
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the address of the first listener the multi-listener is using.
|
||||||
|
// The address of other listeners are not available.
|
||||||
|
func (l *Listener) Addr() net.Addr {
|
||||||
|
return l.listeners[0].Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will close the multi-listener by iterating over its listeners and calling
|
||||||
|
// Close() on each one. If an error is encountered, it is returned. If multiple
|
||||||
|
// errors are encountered they are returned in a MutiError. Close will also shut down
|
||||||
|
// the background goroutines that are calling Accept() on the underlying listeners.
|
||||||
|
//
|
||||||
|
// Calling Close() more than once will cause it to panic.
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
close(l.closing)
|
||||||
|
var errors []error
|
||||||
|
for i := range l.listeners {
|
||||||
|
err := l.listeners[i].Close()
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch len(errors) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return errors[0]
|
||||||
|
}
|
||||||
|
return &MultiError{Errors: errors}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiError is a wrapper around a slice of errors that implements the error interface.
|
||||||
|
type MultiError struct {
|
||||||
|
Errors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error concats the Error() messages of the underlying errors
|
||||||
|
func (m *MultiError) Error() string {
|
||||||
|
if len(m.Errors) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s := "errors: "
|
||||||
|
for _, e := range m.Errors {
|
||||||
|
s += e.Error() + ", "
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop continually accepts connections from the given listener. It forwards the result
|
||||||
|
// of the .Accept() method to a channel on the listener. When a user of the Listener object
|
||||||
|
// calls Accept(), it receives a value from that channel. Closing the listener will cause
|
||||||
|
// this loop to exit.
|
||||||
|
func (l *Listener) loop(listener net.Listener) {
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
r := acceptResults{
|
||||||
|
conn: conn,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case l.conns <- r:
|
||||||
|
case <-l.closing:
|
||||||
|
if r.err == nil {
|
||||||
|
r.conn.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept will wait for a result from calling Accept from the underlying listeners.
|
||||||
|
// It will return an error if the multi-listener is closed.
|
||||||
|
func (l *Listener) Accept() (net.Conn, error) {
|
||||||
|
select {
|
||||||
|
case acceptResult, ok := <-l.conns:
|
||||||
|
if ok {
|
||||||
|
return acceptResult.conn, acceptResult.err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("closed conn channel")
|
||||||
|
case <-l.closing:
|
||||||
|
return nil, fmt.Errorf("listener is closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
54
multilistener/listener_test.go
Normal file
54
multilistener/listener_test.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package multilistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
|
||||||
|
_, err := New()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error when creating listener with no underlying listeners")
|
||||||
|
}
|
||||||
|
l1, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not create listener")
|
||||||
|
}
|
||||||
|
l2, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not create listener")
|
||||||
|
}
|
||||||
|
|
||||||
|
ml, err := New(l1, l2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected ok, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c1, err := net.Dial(l1.Addr().Network(), l1.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected to dial to l1: %s", err)
|
||||||
|
}
|
||||||
|
if n, err := c1.Write([]byte("a")); n != 1 || err != nil {
|
||||||
|
t.Fatalf("expected n = 1, err = nil, got %d, %s", n, err)
|
||||||
|
}
|
||||||
|
c1_ml, err := ml.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected to connect to c1: %s", err)
|
||||||
|
}
|
||||||
|
buf := make([]byte, 100)
|
||||||
|
if n, err := c1_ml.Read(buf); n != 1 || err != nil {
|
||||||
|
t.Fatalf("expected 1 byte got %d, %s", n, err)
|
||||||
|
}
|
||||||
|
if buf[0] != 'a' {
|
||||||
|
t.Fatalf("expected a, got %c", buf[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ml.Close(); err != nil {
|
||||||
|
t.Fatalf("expected no error closing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ml.Accept(); err == nil {
|
||||||
|
t.Fatalf("expected error after closing")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user