pax_global_header00006660000000000000000000000064131466104270014516gustar00rootroot0000000000000052 comment=aec3508d994dbfdb2df9c7514038929b1fd3adac golang-blitiri-go-systemd-0.0+git20170821.0.aec3508/000077500000000000000000000000001314661042700211625ustar00rootroot00000000000000golang-blitiri-go-systemd-0.0+git20170821.0.aec3508/LICENSE000066400000000000000000000022041314661042700221650ustar00rootroot00000000000000 Licensed under the MIT licence, which is reproduced below (from https://opensource.org/licenses/MIT). ----- Copyright (c) 2016 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. golang-blitiri-go-systemd-0.0+git20170821.0.aec3508/README.md000066400000000000000000000020761314661042700224460ustar00rootroot00000000000000 # blitiri.com.ar/go/systemd [![GoDoc](https://godoc.org/blitiri.com.ar/go/systemd?status.svg)](https://godoc.org/blitiri.com.ar/go/systemd) [systemd](https://godoc.org/blitiri.com.ar/go/systemd) is a Go package implementing a way to get network listeners from systemd, similar to C's `sd_listen_fds()` and `sd_listen_fds_with_names()` ([man](https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html)). Supports named file descriptors, which is useful if your daemon needs to be able to tell the different ports apart (e.g. http vs https). It is used by daemons such as [chasquid](https://blitiri.com.ar/p/chasquid/) to listen on privileged ports without needing to run as root. ## Example ```go listeners, err := systemd.Listeners() for _, l := range listeners["service.socket"] { go serve(l) } ``` ## Status The API should be considered stable. Branch v1 will only have backwards-compatible changes made to it. There are no plans for v2 at the moment. ## Contact If you have any questions, comments or patches please send them to albertito@blitiri.com.ar. golang-blitiri-go-systemd-0.0+git20170821.0.aec3508/example_test.go000066400000000000000000000007711314661042700242100ustar00rootroot00000000000000package systemd_test import ( "fmt" "net" "blitiri.com.ar/go/systemd" ) func serve(l net.Listener) { // Serve over the listener. } func Example() { listeners, err := systemd.Listeners() if err != nil { fmt.Printf("error getting listeners: %v", err) return } // Iterate over the listeners of a particular systemd socket. // The name comes from the FileDescriptorName option, defaults to the name // of the socket unit. for _, l := range listeners["service.socket"] { go serve(l) } } golang-blitiri-go-systemd-0.0+git20170821.0.aec3508/systemd.go000066400000000000000000000121441314661042700232030ustar00rootroot00000000000000// Package systemd implements a way to get network listeners from systemd, // similar to C's sd_listen_fds(3) and sd_listen_fds_with_names(3). package systemd // import "blitiri.com.ar/go/systemd" import ( "errors" "fmt" "net" "os" "strconv" "strings" "sync" "syscall" ) var ( // Error to return when $LISTEN_PID does not refer to us. ErrPIDMismatch = errors.New("$LISTEN_PID != our PID") // First FD for listeners. // It's 3 by definition, but using a variable simplifies testing. firstFD = 3 ) // Keep a single global map of listeners, to avoid repeatedly parsing which // can be problematic (see parse). var listeners map[string][]net.Listener var parseError error var mutex sync.Mutex // parse listeners, updating the global state. // This function messes with file descriptors and environment, so it is not // idempotent and must be called only once. For the callers' convenience, we // save the listeners map globally and reuse it on the user-visible functions. func parse() { mutex.Lock() defer mutex.Unlock() if listeners != nil { return } pidStr := os.Getenv("LISTEN_PID") nfdsStr := os.Getenv("LISTEN_FDS") fdNamesStr := os.Getenv("LISTEN_FDNAMES") fdNames := strings.Split(fdNamesStr, ":") // Nothing to do if the variables are not set. if pidStr == "" || nfdsStr == "" { return } pid, err := strconv.Atoi(pidStr) if err != nil { parseError = fmt.Errorf( "error converting $LISTEN_PID=%q: %v", pidStr, err) return } else if pid != os.Getpid() { parseError = ErrPIDMismatch return } nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) if err != nil { parseError = fmt.Errorf( "error reading $LISTEN_FDS=%q: %v", nfdsStr, err) return } // We should have as many names as we have descriptors. // Note that if we have no descriptors, fdNames will be [""] (due to how // strings.Split works), so we consider that special case. if nfds > 0 && (fdNamesStr == "" || len(fdNames) != nfds) { parseError = fmt.Errorf( "Incorrect LISTEN_FDNAMES, have you set FileDescriptorName?") return } listeners = map[string][]net.Listener{} for i := 0; i < nfds; i++ { fd := firstFD + i // We don't want childs to inherit these file descriptors. syscall.CloseOnExec(fd) name := fdNames[i] sysName := fmt.Sprintf("[systemd-fd-%d-%v]", fd, name) lis, err := net.FileListener(os.NewFile(uintptr(fd), sysName)) if err != nil { parseError = fmt.Errorf( "Error making listener out of fd %d: %v", fd, err) return } listeners[name] = append(listeners[name], lis) } // Remove them from the environment, to prevent accidental reuse (by // us or children processes). os.Unsetenv("LISTEN_PID") os.Unsetenv("LISTEN_FDS") os.Unsetenv("LISTEN_FDNAMES") } // Listeners returns a map of listeners for the file descriptors passed by // systemd via environment variables. // // It returns a map of the form (file descriptor name -> slice of listeners). // // The file descriptor name comes from the "FileDescriptorName=" option in the // systemd socket unit. Multiple socket units can have the same name, hence // the slice of listeners for each name. // // Ideally you should not need to call this more than once. If you do, the // same listeners will be returned, as repeated calls to this function will // return the same results: the parsing is done only once, and the results are // saved and reused. // // See sd_listen_fds(3) and sd_listen_fds_with_names(3) for more details on // how the passing works. func Listeners() (map[string][]net.Listener, error) { parse() return listeners, parseError } // OneListener returns a listener for the first systemd socket with the given // name. If there are none, the listener and error will both be nil. An error // will be returned only if there were issues parsing the file descriptors. // // This function can be convenient for simple callers where you know there's // only one file descriptor being passed with the given name. // // This is a convenience function built on top of Listeners(). func OneListener(name string) (net.Listener, error) { parse() if parseError != nil { return nil, parseError } lis := listeners[name] if len(lis) < 1 { return nil, nil } return lis[0], nil } // Listen returns a listener for the given address, similar to net.Listen. // // If the address begins with "&" it is interpreted as a systemd socket being // passed. For example, using "&http" would mean we expect a systemd socket // passed to us, named with "FileDescriptorName=http" in its unit. // // Otherwise, it uses net.Listen to create a new listener with the given net // and local address. // // This function can be convenient for simple callers where you get the // address from a user, and want to let them specify either "use systemd" or a // normal address without too much additional complexity. // // This is a convenience function built on top of Listeners(). func Listen(netw, laddr string) (net.Listener, error) { if strings.HasPrefix(laddr, "&") { name := laddr[1:] lis, err := OneListener(name) if lis == nil && err == nil { err = fmt.Errorf("systemd socket %q not found", name) } return lis, err } else { return net.Listen(netw, laddr) } } golang-blitiri-go-systemd-0.0+git20170821.0.aec3508/systemd_test.go000066400000000000000000000125571314661042700242520ustar00rootroot00000000000000package systemd import ( "math/rand" "net" "os" "strconv" "strings" "testing" ) func setenv(pid, fds string, names ...string) { os.Setenv("LISTEN_PID", pid) os.Setenv("LISTEN_FDS", fds) os.Setenv("LISTEN_FDNAMES", strings.Join(names, ":")) listeners = nil parseError = nil } func TestEmptyEnvironment(t *testing.T) { cases := []struct{ pid, fds string }{ {"", ""}, {"123", ""}, {"", "4"}, } for _, c := range cases { setenv(c.pid, c.fds) if ls, err := Listeners(); ls != nil || err != nil { t.Logf("Case: LISTEN_PID=%q LISTEN_FDS=%q", c.pid, c.fds) t.Errorf("Unexpected result: %v // %v", ls, err) } } } func TestBadEnvironment(t *testing.T) { // Create a listener so we have something to reference. l := newListener(t) firstFD = listenerFd(t, l) ourPID := strconv.Itoa(os.Getpid()) cases := []struct { pid, fds string names []string }{ {"a", "1", []string{"name"}}, // Invalid PID. {ourPID, "a", []string{"name"}}, // Invalid number of fds. {"1", "1", []string{"name"}}, // PID != ourselves. {ourPID, "1", []string{"name1", "name2"}}, // Too many names. {ourPID, "1", []string{}}, // Not enough names. } for _, c := range cases { setenv(c.pid, c.fds, c.names...) if ls, err := Listeners(); err == nil { t.Logf("Case: LISTEN_PID=%q LISTEN_FDS=%q LISTEN_FDNAMES=%q", c.pid, c.fds, c.names) t.Errorf("Unexpected result: %v // %v", ls, err) } if ls, err := OneListener("name"); err == nil { t.Errorf("Unexpected result: %v // %v", ls, err) } } } func TestWrongPID(t *testing.T) { // Find a pid != us. 1 should always work in practice. pid := 1 for pid == os.Getpid() { pid = rand.Int() } setenv(strconv.Itoa(pid), "4") if _, err := Listeners(); err != ErrPIDMismatch { t.Errorf("Did not fail with PID mismatch: %v", err) } if _, err := OneListener("name"); err != ErrPIDMismatch { t.Errorf("Did not fail with PID mismatch: %v", err) } } func TestNoFDs(t *testing.T) { setenv(strconv.Itoa(os.Getpid()), "0") if ls, err := Listeners(); len(ls) != 0 || err != nil { t.Errorf("Got a non-empty result: %v // %v", ls, err) } } // newListener creates a TCP listener. func newListener(t *testing.T) *net.TCPListener { addr := &net.TCPAddr{ Port: 0, } l, err := net.ListenTCP("tcp", addr) if err != nil { t.Fatalf("Could not create TCP listener: %v", err) } return l } // listenerFd returns a file descriptor for the listener. // Note it is a NEW file descriptor, not the original one. func listenerFd(t *testing.T, l *net.TCPListener) int { f, err := l.File() if err != nil { t.Fatalf("Could not get TCP listener file: %v", err) } return int(f.Fd()) } func sameAddr(a, b net.Addr) bool { return a.Network() == b.Network() && a.String() == b.String() } func TestOneSocket(t *testing.T) { l := newListener(t) firstFD = listenerFd(t, l) setenv(strconv.Itoa(os.Getpid()), "1", "name") lsMap, err := Listeners() if err != nil || len(lsMap) != 1 { t.Fatalf("Got an invalid result: %v // %v", lsMap, err) } ls := lsMap["name"] if !sameAddr(ls[0].Addr(), l.Addr()) { t.Errorf("Listener 0 address mismatch, expected %#v, got %#v", l.Addr(), ls[0].Addr()) } oneL, err := OneListener("name") if err != nil { t.Errorf("OneListener error: %v", err) } if !sameAddr(oneL.Addr(), l.Addr()) { t.Errorf("OneListener address mismatch, expected %#v, got %#v", l.Addr(), ls[0].Addr()) } if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { t.Errorf("Failed to reset the environment") } } func TestManySockets(t *testing.T) { // Create two contiguous listeners. // The test environment does not guarantee us that they are contiguous, so // keep going until they are. var l0, l1 *net.TCPListener var f0, f1 int = -1, -3 for f0+1 != f1 { // We have to be careful with the order of these operations, because // listenerFd will create *new* file descriptors. l0 = newListener(t) l1 = newListener(t) f0 = listenerFd(t, l0) f1 = listenerFd(t, l1) t.Logf("Looping for FDs: %d %d", f0, f1) } firstFD = f0 setenv(strconv.Itoa(os.Getpid()), "2", "name1", "name2") lsMap, err := Listeners() if err != nil || len(lsMap) != 2 { t.Fatalf("Got an invalid result: %v // %v", lsMap, err) } ls := []net.Listener{ lsMap["name1"][0], lsMap["name2"][0], } if !sameAddr(ls[0].Addr(), l0.Addr()) { t.Errorf("Listener 0 address mismatch, expected %#v, got %#v", l0.Addr(), ls[0].Addr()) } if !sameAddr(ls[1].Addr(), l1.Addr()) { t.Errorf("Listener 1 address mismatch, expected %#v, got %#v", l1.Addr(), ls[1].Addr()) } oneL, _ := OneListener("name1") if !sameAddr(oneL.Addr(), l0.Addr()) { t.Errorf("Listener 0 address mismatch, expected %#v, got %#v", oneL.Addr(), ls[0].Addr()) } if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" || os.Getenv("LISTEN_FDNAMES") != "" { t.Errorf("Failed to reset the environment") } } func TestListen(t *testing.T) { orig := newListener(t) firstFD = listenerFd(t, orig) setenv(strconv.Itoa(os.Getpid()), "1", "name") l, err := Listen("tcp", "&name") if err != nil { t.Errorf("Listen failed: %v", err) } if !sameAddr(l.Addr(), orig.Addr()) { t.Errorf("Listener 0 address mismatch, expected %#v, got %#v", l.Addr(), orig.Addr()) } l, err = Listen("tcp", ":0") if err != nil { t.Errorf("Listen failed: %v", err) } t.Logf("listener created at %v", l.Addr()) }