Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

activation: allow more control over socket-activated file descriptors #441

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions activation/filesmethod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package activation

// Method decides what happens to the file descriptors that are passed in by systemd.
type Method int

const (
// ConsumeFiles is the default, and removes the original file descriptors passed in by
// systemd. This means that new file descriptors created by the program may use the
// file descriptors indices.
ConsumeFiles Method = iota

// ReserveFiles stores placeholder file descriptors, which point to /dev/null. This
// stops new file descriptors consuming the indices.
ReserveFiles

// CloneFiles duplicates and leaves the original file descriptors in tack. This
// stops new file descriptors consuming the indices like [ReserveFiles] but
// consider the possible secuirty risk of leaving the sockets exposed.
ConserveFiles
)
68 changes: 68 additions & 0 deletions activation/filesmethod_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !windows
// +build !windows

package activation

import (
"os"
"syscall"
)

type ErrorDevNullSetup struct {
fd int
err error
}

func (e ErrorDevNullSetup) Error() string {
return "setting up %d fd to /dev/null: " + e.err.Error()
}

func (e ErrorDevNullSetup) Unwrap() error {
return e.err
}

func (m Method) Apply(f *os.File) error {
saveFd := int(f.Fd()) // get the idx before being closed.

switch m {
case ConsumeFiles:
return f.Close()
case ReserveFiles:
devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0755)
if err != nil {
return ErrorDevNullSetup{err: err, fd: saveFd}
}

nullFd := int(devNull.Fd())

// "If oldfd equals newfd, then dup3() fails with the error EINVAL."
if saveFd == nullFd {
syscall.CloseOnExec(nullFd)
} else {
// "makes newfd be the copy of oldfd, closing newfd first if necessary"
if err := syscall.Dup3(nullFd, saveFd, syscall.O_CLOEXEC); err != nil {
devNull.Close() // on an error tidy up.

return ErrorDevNullSetup{err: err, fd: saveFd}
}
}
case ConserveFiles:
// no action
}

return nil
}
21 changes: 21 additions & 0 deletions activation/filesmethod_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package activation

import "os"

func (m Method) Apply(f *os.File) error {
return nil
}
30 changes: 20 additions & 10 deletions activation/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,29 @@ import (
// The order of the file descriptors is preserved in the returned slice.
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
func Listeners() ([]net.Listener, error) {
files := Files(true)
func Listeners(opts ...option) ([]net.Listener, error) {
o := Options(opts...)

files := Files(o.unsetEnv)
listeners := make([]net.Listener, len(files))

for i, f := range files {
if pc, err := net.FileListener(f); err == nil {
listeners[i] = pc
f.Close()

if err := o.method.Apply(f); err != nil {

}
}
}
return listeners, nil
}

// ListenersWithNames maps a listener name to a set of net.Listener instances.
func ListenersWithNames() (map[string][]net.Listener, error) {
files := Files(true)
func ListenersWithNames(opts ...option) (map[string][]net.Listener, error) {
o := Options(opts...)

files := Files(o.unsetEnv)
listeners := map[string][]net.Listener{}

for _, f := range files {
Expand All @@ -51,7 +58,10 @@ func ListenersWithNames() (map[string][]net.Listener, error) {
} else {
listeners[f.Name()] = append(current, pc)
}
f.Close()

if err := o.method.Apply(f); err != nil {

}
}
}
return listeners, nil
Expand All @@ -60,8 +70,8 @@ func ListenersWithNames() (map[string][]net.Listener, error) {
// TLSListeners returns a slice containing a net.listener for each matching TCP socket type
// passed to this process.
// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig.
func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {
listeners, err := Listeners()
func TLSListeners(tlsConfig *tls.Config, opts ...option) ([]net.Listener, error) {
listeners, err := Listeners(opts...)

if listeners == nil || err != nil {
return nil, err
Expand All @@ -81,8 +91,8 @@ func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {

// TLSListenersWithNames maps a listener name to a net.Listener with
// the associated TLS configuration.
func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) {
listeners, err := ListenersWithNames()
func TLSListenersWithNames(tlsConfig *tls.Config, opts ...option) (map[string][]net.Listener, error) {
listeners, err := ListenersWithNames(opts...)

if listeners == nil || err != nil {
return nil, err
Expand Down
40 changes: 40 additions & 0 deletions activation/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package activation

type options struct {
unsetEnv bool
method Method
}

type option func(*options)

// UnsetEnv controls if the LISTEN_PID, LISTEN_FDS & LISTEN_FDNAMES environment
// variables are unset.
//
// This is useful to avoid clashes in fd usage and to avoid leaking environment
// flags to child processes.
func UnsetEnv(f bool) option {
return func(o *options) {
o.unsetEnv = f
}
}

// UseMethod chooses the [Method] applied to the file descriptor passed in by
// systemd socket activation.
func UseMethod(m Method) option {
return func(o *options) {
o.method = m
}
}

// Options takes some [option]s and produces a stuct containing the flags and settings.
func Options(opts ...option) *options {
o := &options{
unsetEnv: true,
}

for _, opt := range opts {
opt(o)
}

return o
}
9 changes: 7 additions & 2 deletions activation/packetconns.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ import (
// The order of the file descriptors is preserved in the returned slice.
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
func PacketConns() ([]net.PacketConn, error) {
func PacketConns(opts ...option) ([]net.PacketConn, error) {
o := Options(opts...)

files := Files(true)
conns := make([]net.PacketConn, len(files))

for i, f := range files {
if pc, err := net.FilePacketConn(f); err == nil {
conns[i] = pc
f.Close()

if err := o.method.Apply(f); err != nil {

}
}
}
return conns, nil
Expand Down
Loading