@@ -22,13 +22,16 @@ import (
2222 "log"
2323 "net"
2424 "net/http"
25+ "os"
2526 "path"
2627 "runtime"
28+ "strconv"
2729 "strings"
2830 "sync"
2931 "sync/atomic"
3032 "time"
3133
34+ "github.com/caddyserver/certmagic/internal/filedescriptor"
3235 "github.com/libdns/libdns"
3336 "github.com/mholt/acmez/v3"
3437 "github.com/mholt/acmez/v3/acme"
@@ -708,6 +711,7 @@ func getSolverInfo(address string) *solverInfo {
708711// which is useful for our challenge servers, where we assume
709712// that whatever is already listening can solve the challenges.
710713func robustTryListen (addr string ) (net.Listener , error ) {
714+
711715 var listenErr error
712716 for i := 0 ; i < 2 ; i ++ {
713717 // doesn't hurt to sleep briefly before the second
@@ -718,17 +722,56 @@ func robustTryListen(addr string) (net.Listener, error) {
718722
719723 // if we can bind the socket right away, great!
720724 var ln net.Listener
721- ln , listenErr = net .Listen ("tcp" , addr )
722- if listenErr == nil {
725+
726+ // First, try to parse the address as a file descriptor. When testing
727+ // with Caddy, the address contains a port number (even though that
728+ // doesn't really make sense for a file descriptor), so remove that too.
729+ fd , err := strconv .ParseUint (strings .Split (strings .TrimPrefix (addr , "fd/" ), ":" )[0 ], 0 , strconv .IntSize )
730+ if err == nil {
731+ // os.NewFile takes ownership of the file descriptor, so if
732+ // something else is using the file descriptor, this would be bad
733+ // since it means that closing the os.File would also close the
734+ // underlying file descriptor out from under the other user. So
735+ // instead we'll duplicate the file descriptor here, then we can
736+ // safely do whatever we want with it.
737+ fd , listenErr := filedescriptor .Dup (int (fd ))
738+ if listenErr != nil {
739+ continue
740+ }
741+
742+ // net.FileListener internally duplicates the file descriptor, so we
743+ // can unconditionally close the "file" after creating the listener.
744+ file := os .NewFile (uintptr (fd ), addr )
745+ defer file .Close ()
746+
747+ // net.FileListener will return an error if the file descriptor is
748+ // not a stream socket, so we don't need to check ourselves. But the
749+ // socket could be a Unix socket, so we still need to check for that
750+ // and raise an error if so.
751+ ln , listenErr = net .FileListener (file )
752+ if listenErr == nil {
753+ return ln , nil
754+ }
755+
756+ if ln .Addr ().Network () != "tcp" {
757+ ln .Close ()
758+ return nil , fmt .Errorf ("file descriptor %d is not a TCP socket" , fd )
759+ }
760+
723761 return ln , nil
724- }
762+ } else {
763+ ln , listenErr = net .Listen ("tcp" , addr )
764+ if listenErr == nil {
765+ return ln , nil
766+ }
725767
726- // if it failed just because the socket is already in use, we
727- // have no choice but to assume that whatever is using the socket
728- // can answer the challenge already, so we ignore the error
729- connectErr := dialTCPSocket (addr )
730- if connectErr == nil {
731- return nil , nil
768+ // if it failed just because the socket is already in use, we
769+ // have no choice but to assume that whatever is using the socket
770+ // can answer the challenge already, so we ignore the error
771+ connectErr := dialTCPSocket (addr )
772+ if connectErr == nil {
773+ return nil , nil
774+ }
732775 }
733776
734777 // Hmm, we couldn't connect to the socket, so something else must
0 commit comments