mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-21 16:03:32 +01:00
transport/{TCP,TLS}: optional IP_FREEBIND / IP_BINDANY bind socketops
Allows to bind to an address even if it is not actually (yet or ever) configured. Fixes #238 Rationale: https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/#whatdoesthismeanformeadeveloper
This commit is contained in:
parent
47ed599db7
commit
d35e2400b2
@ -3,9 +3,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "golang.org/x/tools/cmd/stringer"
|
||||
_ "github.com/golang/protobuf/protoc-gen-go"
|
||||
_ "github.com/alvaroloes/enumer"
|
||||
_ "golang.org/x/tools/cmd/goimports"
|
||||
_ "github.com/golang/protobuf/protoc-gen-go"
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
_ "golang.org/x/tools/cmd/goimports"
|
||||
_ "golang.org/x/tools/cmd/stringer"
|
||||
)
|
||||
|
@ -245,14 +245,16 @@ type ServeCommon struct {
|
||||
}
|
||||
|
||||
type TCPServe struct {
|
||||
ServeCommon `yaml:",inline"`
|
||||
Listen string `yaml:"listen,hostport"`
|
||||
Clients map[string]string `yaml:"clients"`
|
||||
ServeCommon `yaml:",inline"`
|
||||
Listen string `yaml:"listen,hostport"`
|
||||
ListenFreeBind bool `yaml:"listen_freebind,default=false"`
|
||||
Clients map[string]string `yaml:"clients"`
|
||||
}
|
||||
|
||||
type TLSServe struct {
|
||||
ServeCommon `yaml:",inline"`
|
||||
Listen string `yaml:"listen,hostport"`
|
||||
ListenFreeBind bool `yaml:"listen_freebind,default=false"`
|
||||
Ca string `yaml:"ca"`
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
@ -331,8 +333,9 @@ type MonitoringEnum struct {
|
||||
}
|
||||
|
||||
type PrometheusMonitoring struct {
|
||||
Type string `yaml:"type"`
|
||||
Listen string `yaml:"listen,hostport"`
|
||||
Type string `yaml:"type"`
|
||||
Listen string `yaml:"listen,hostport"`
|
||||
ListenFreeBind bool `yaml:"listen_freebind,default=false"`
|
||||
}
|
||||
|
||||
type SyslogFacility syslog.Priority
|
||||
|
@ -12,18 +12,20 @@ import (
|
||||
"github.com/zrepl/zrepl/daemon/job"
|
||||
"github.com/zrepl/zrepl/logger"
|
||||
"github.com/zrepl/zrepl/rpc/dataconn/frameconn"
|
||||
"github.com/zrepl/zrepl/util/tcpsock"
|
||||
"github.com/zrepl/zrepl/zfs"
|
||||
)
|
||||
|
||||
type prometheusJob struct {
|
||||
listen string
|
||||
listen string
|
||||
freeBind bool
|
||||
}
|
||||
|
||||
func newPrometheusJobFromConfig(in *config.PrometheusMonitoring) (*prometheusJob, error) {
|
||||
if _, _, err := net.SplitHostPort(in.Listen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &prometheusJob{in.Listen}, nil
|
||||
return &prometheusJob{in.Listen, in.ListenFreeBind}, nil
|
||||
}
|
||||
|
||||
var prom struct {
|
||||
@ -60,7 +62,7 @@ func (j *prometheusJob) Run(ctx context.Context) {
|
||||
|
||||
log := job.GetLogger(ctx)
|
||||
|
||||
l, err := net.Listen("tcp", j.listen)
|
||||
l, err := tcpsock.Listen(j.listen, j.freeBind)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("cannot listen")
|
||||
return
|
||||
|
@ -27,6 +27,11 @@ We use the following annotations for classifying changes:
|
||||
* |bugfix| Change that fixes a bug, no regressions or incompatibilities expected.
|
||||
* |docs| Change to the documentation.
|
||||
|
||||
0.2.2 (unreleased)
|
||||
------------------
|
||||
|
||||
* |feature| New option ``listen_freebind`` (tcp, tls, prometheus listener)
|
||||
|
||||
0.2.1
|
||||
-----
|
||||
|
||||
|
@ -14,6 +14,7 @@ Prometheus & Grafana
|
||||
|
||||
zrepl can expose `Prometheus metrics <https://prometheus.io/docs/instrumenting/exposition_formats/>`_ via HTTP.
|
||||
The ``listen`` attribute is a `net.Listen <https://golang.org/pkg/net/#Listen>`_ string for tcp, e.g. ``:9091`` or ``127.0.0.1:9091``.
|
||||
The ``listen_freebind`` attribute is :ref:`explained here <listen-freebind-explanation>`.
|
||||
The Prometheues monitoring job appears in the ``zrepl control`` job list and may be specified **at most once**.
|
||||
|
||||
zrepl also ships with an importable `Grafana <https://grafana.com>`_ dashboard that consumes the Prometheus metrics:
|
||||
@ -30,6 +31,7 @@ The dashboard also contains some advice on which metrics are important to monito
|
||||
monitoring:
|
||||
- type: prometheus
|
||||
listen: ':9091'
|
||||
listen_freebind: true # optional, default false
|
||||
|
||||
|
||||
|
||||
|
@ -50,12 +50,18 @@ Serve
|
||||
serve:
|
||||
type: tcp
|
||||
listen: ":8888"
|
||||
listen_freebind: true # optional, default false
|
||||
clients: {
|
||||
"192.168.122.123" : "mysql01"
|
||||
"192.168.122.123" : "mx01"
|
||||
}
|
||||
...
|
||||
|
||||
.. _listen-freebind-explanation:
|
||||
|
||||
``listen_freebind`` controls whether the socket is allowed to bind to non-local or unconfigured IP addresses (Linux ``IP_FREEBIND`` , FreeBSD ``IP_BINDANY``).
|
||||
Enable this option if you want to ``listen`` on a specific IP address that might not yet be configured when the zrepl daemon starts.
|
||||
|
||||
Connect
|
||||
~~~~~~~
|
||||
|
||||
@ -101,6 +107,7 @@ Serve
|
||||
serve:
|
||||
type: tls
|
||||
listen: ":8888"
|
||||
listen_freebind: true # optional, default false
|
||||
ca: /etc/zrepl/ca.crt
|
||||
cert: /etc/zrepl/prod.fullchain
|
||||
key: /etc/zrepl/prod.key
|
||||
@ -110,6 +117,7 @@ Serve
|
||||
|
||||
The ``ca`` field specified the certificate authority used to validate client certificates.
|
||||
The ``client_cns`` list specifies a list of accepted client common names (which are also the client identities for this transport).
|
||||
The ``listen_freebind`` field is :ref:`explained here <listen-freebind-explanation>`.
|
||||
|
||||
Connect
|
||||
~~~~~~~
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zrepl/zrepl/config"
|
||||
"github.com/zrepl/zrepl/daemon/logging"
|
||||
"github.com/zrepl/zrepl/logger"
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zrepl/zrepl/zfs"
|
||||
)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zrepl/zrepl/platformtest"
|
||||
"github.com/zrepl/zrepl/zfs"
|
||||
)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/zrepl/zrepl/config"
|
||||
"github.com/zrepl/zrepl/transport"
|
||||
"github.com/zrepl/zrepl/util/tcpsock"
|
||||
)
|
||||
|
||||
type ipMapEntry struct {
|
||||
@ -44,16 +45,12 @@ func (m *ipMap) Get(ip net.IP) (string, error) {
|
||||
}
|
||||
|
||||
func TCPListenerFactoryFromConfig(c *config.Global, in *config.TCPServe) (transport.AuthenticatedListenerFactory, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", in.Listen)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse listen address")
|
||||
}
|
||||
clientMap, err := ipMapFromConfig(in.Clients)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse client IP map")
|
||||
}
|
||||
lf := func() (transport.AuthenticatedListener, error) {
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
l, err := tcpsock.Listen(in.Listen, in.ListenFreeBind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -12,6 +11,7 @@ import (
|
||||
"github.com/zrepl/zrepl/config"
|
||||
"github.com/zrepl/zrepl/tlsconf"
|
||||
"github.com/zrepl/zrepl/transport"
|
||||
"github.com/zrepl/zrepl/util/tcpsock"
|
||||
)
|
||||
|
||||
type TLSListenerFactory struct{}
|
||||
@ -45,12 +45,11 @@ func TLSListenerFactoryFromConfig(c *config.Global, in *config.TLSServe) (transp
|
||||
}
|
||||
|
||||
lf := func() (transport.AuthenticatedListener, error) {
|
||||
l, err := net.Listen("tcp", address)
|
||||
l, err := tcpsock.Listen(address, in.ListenFreeBind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcpL := l.(*net.TCPListener)
|
||||
tl := tlsconf.NewClientAuthListener(tcpL, clientCA, serverCert, handshakeTimeout)
|
||||
tl := tlsconf.NewClientAuthListener(l, clientCA, serverCert, handshakeTimeout)
|
||||
return &tlsAuthListener{tl, clientCNs}, nil
|
||||
}
|
||||
|
||||
|
27
util/tcpsock/tcpsock.go
Normal file
27
util/tcpsock/tcpsock.go
Normal file
@ -0,0 +1,27 @@
|
||||
package tcpsock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Listen(address string, tryFreeBind bool) (*net.TCPListener, error) {
|
||||
control := func(network, address string, c syscall.RawConn) error {
|
||||
if tryFreeBind {
|
||||
if err := freeBind(network, address, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var listenConfig = net.ListenConfig{
|
||||
Control: control,
|
||||
}
|
||||
|
||||
l, err := listenConfig.Listen(context.Background(), "tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.(*net.TCPListener), nil
|
||||
}
|
25
util/tcpsock/tcpsock_freebind_freebsd.go
Normal file
25
util/tcpsock/tcpsock_freebind_freebsd.go
Normal file
@ -0,0 +1,25 @@
|
||||
// +build freebsd
|
||||
|
||||
package tcpsock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func freeBind(network, address string, c syscall.RawConn) error {
|
||||
var err, sockerr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
if network == "tcp6" {
|
||||
sockerr = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BINDANY, 1)
|
||||
} else if network == "tcp4" {
|
||||
sockerr = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BINDANY, 1)
|
||||
} else {
|
||||
sockerr = fmt.Errorf("expecting 'tcp6' or 'tcp4', got %q", network)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sockerr
|
||||
}
|
19
util/tcpsock/tcpsock_freebind_linux.go
Normal file
19
util/tcpsock/tcpsock_freebind_linux.go
Normal file
@ -0,0 +1,19 @@
|
||||
// +build linux
|
||||
|
||||
package tcpsock
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func freeBind(network, address string, c syscall.RawConn) error {
|
||||
var err, sockerr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
// apparently, this works for both IPv4 and IPv6
|
||||
sockerr = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_FREEBIND, 1)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sockerr
|
||||
}
|
12
util/tcpsock/tcpsock_freebind_unsupported.go
Normal file
12
util/tcpsock/tcpsock_freebind_unsupported.go
Normal file
@ -0,0 +1,12 @@
|
||||
// +build !linux,!freebsd
|
||||
|
||||
package tcpsock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func freeBind(network, address string, c syscall.RawConn) error {
|
||||
return fmt.Errorf("IP_FREEBIND equivalent functionality not supported on this platform")
|
||||
}
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zrepl/zrepl/util/envconst"
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user