mirror of
https://github.com/caddyserver/caddy.git
synced 2025-10-19 15:53:17 +00:00
Add support for indexed named sockets to handle duplicate names
This commit is contained in:
parent
35658131f9
commit
a819b7309b
2 changed files with 143 additions and 13 deletions
55
listeners.go
55
listeners.go
|
@ -38,6 +38,10 @@ import (
|
|||
"github.com/caddyserver/caddy/v2/internal"
|
||||
)
|
||||
|
||||
// listenFdsStart is the first file descriptor number for systemd socket activation.
|
||||
// File descriptors 0, 1, 2 are reserved for stdin, stdout, stderr.
|
||||
const listenFdsStart = 3
|
||||
|
||||
// NetworkAddress represents one or more network addresses.
|
||||
// It contains the individual components for a parsed network
|
||||
// address of the form accepted by ParseNetworkAddress().
|
||||
|
@ -308,8 +312,12 @@ func IsFdNetwork(netw string) bool {
|
|||
// getFdByName returns the file descriptor number for the given
|
||||
// socket name from systemd's LISTEN_FDNAMES environment variable.
|
||||
// Socket names are provided by systemd via socket activation.
|
||||
func getFdByName(name string) (int, error) {
|
||||
if name == "" {
|
||||
//
|
||||
// The name can optionally include an index to handle multiple sockets
|
||||
// with the same name: "web:0" for first, "web:1" for second, etc.
|
||||
// If no index is specified, defaults to index 0 (first occurrence).
|
||||
func getFdByName(nameWithIndex string) (int, error) {
|
||||
if nameWithIndex == "" {
|
||||
return 0, fmt.Errorf("socket name cannot be empty")
|
||||
}
|
||||
|
||||
|
@ -318,18 +326,45 @@ func getFdByName(name string) (int, error) {
|
|||
return 0, fmt.Errorf("LISTEN_FDNAMES environment variable not set")
|
||||
}
|
||||
|
||||
// Parse the socket names
|
||||
names := strings.Split(fdNamesStr, ":")
|
||||
// Parse name and optional index
|
||||
parts := strings.Split(nameWithIndex, ":")
|
||||
if len(parts) > 2 {
|
||||
return 0, fmt.Errorf("invalid socket name format '%s': too many colons", nameWithIndex)
|
||||
}
|
||||
|
||||
// Find the index of the requested name
|
||||
for i, fdName := range names {
|
||||
if fdName == name {
|
||||
// File descriptors start at 3 (after stdin=0, stdout=1, stderr=2)
|
||||
return 3 + i, nil
|
||||
name := parts[0]
|
||||
targetIndex := 0
|
||||
|
||||
if len(parts) > 1 {
|
||||
var err error
|
||||
targetIndex, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid socket index '%s': %v", parts[1], err)
|
||||
}
|
||||
if targetIndex < 0 {
|
||||
return 0, fmt.Errorf("socket index cannot be negative: %d", targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("socket name '%s' not found in LISTEN_FDNAMES", name)
|
||||
// Parse the socket names
|
||||
names := strings.Split(fdNamesStr, ":")
|
||||
|
||||
// Find the Nth occurrence of the requested name
|
||||
matchCount := 0
|
||||
for i, fdName := range names {
|
||||
if fdName == name {
|
||||
if matchCount == targetIndex {
|
||||
return listenFdsStart + i, nil
|
||||
}
|
||||
matchCount++
|
||||
}
|
||||
}
|
||||
|
||||
if matchCount == 0 {
|
||||
return 0, fmt.Errorf("socket name '%s' not found in LISTEN_FDNAMES", name)
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("socket name '%s' found %d times, but index %d requested", name, matchCount, targetIndex)
|
||||
}
|
||||
|
||||
// ParseNetworkAddress parses addr into its individual
|
||||
|
|
|
@ -682,23 +682,65 @@ func TestGetFdByName(t *testing.T) {
|
|||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "multiple sockets - first",
|
||||
name: "multiple different sockets - first",
|
||||
fdNames: "http:https:dns",
|
||||
socketName: "http",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "multiple sockets - second",
|
||||
name: "multiple different sockets - second",
|
||||
fdNames: "http:https:dns",
|
||||
socketName: "https",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "multiple sockets - third",
|
||||
name: "multiple different sockets - third",
|
||||
fdNames: "http:https:dns",
|
||||
socketName: "dns",
|
||||
expectedFd: 5,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - first occurrence (no index)",
|
||||
fdNames: "web:web:api",
|
||||
socketName: "web",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - first occurrence (explicit index 0)",
|
||||
fdNames: "web:web:api",
|
||||
socketName: "web:0",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - second occurrence (index 1)",
|
||||
fdNames: "web:web:api",
|
||||
socketName: "web:1",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - first api",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "api:0",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - second api",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "api:1",
|
||||
expectedFd: 6,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - first web",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "web:0",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - second web",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "web:1",
|
||||
expectedFd: 5,
|
||||
},
|
||||
{
|
||||
name: "socket not found",
|
||||
fdNames: "http:https",
|
||||
|
@ -717,6 +759,30 @@ func TestGetFdByName(t *testing.T) {
|
|||
socketName: "http",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "index out of range",
|
||||
fdNames: "web:web",
|
||||
socketName: "web:2",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "negative index",
|
||||
fdNames: "web",
|
||||
socketName: "web:-1",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid index format",
|
||||
fdNames: "web",
|
||||
socketName: "web:abc",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "too many colons",
|
||||
fdNames: "web",
|
||||
socketName: "web:0:extra",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
@ -788,6 +854,20 @@ func TestParseNetworkAddressFdName(t *testing.T) {
|
|||
Host: "5",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/http:0",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/https:0",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "4",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdgramname/http",
|
||||
expectAddr: NetworkAddress{
|
||||
|
@ -802,6 +882,13 @@ func TestParseNetworkAddressFdName(t *testing.T) {
|
|||
Host: "4",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdgramname/http:0",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/nonexistent",
|
||||
expectErr: true,
|
||||
|
@ -810,6 +897,14 @@ func TestParseNetworkAddressFdName(t *testing.T) {
|
|||
input: "fdgramname/nonexistent",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fdname/http:99",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fdname/invalid:abc",
|
||||
expectErr: true,
|
||||
},
|
||||
// Test that old fd/N syntax still works
|
||||
{
|
||||
input: "fd/7",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue