2019-06-30 17:07:58 -05:00
|
|
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2019-06-14 12:58:28 -05:00
|
|
|
package caddy
|
2019-03-26 16:45:51 -05:00
|
|
|
|
|
|
|
import (
|
2022-03-22 18:47:57 -05:00
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"errors"
|
2019-03-26 16:45:51 -05:00
|
|
|
"fmt"
|
|
|
|
"net"
|
2022-01-19 14:26:44 -05:00
|
|
|
"os"
|
2019-07-08 17:46:38 -05:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2019-03-26 20:42:52 -05:00
|
|
|
"sync"
|
2019-03-26 16:45:51 -05:00
|
|
|
"sync/atomic"
|
2021-02-16 15:55:49 -05:00
|
|
|
"syscall"
|
2019-04-02 15:58:24 -05:00
|
|
|
"time"
|
2022-03-22 18:47:57 -05:00
|
|
|
|
|
|
|
"github.com/lucas-clemente/quic-go"
|
|
|
|
"github.com/lucas-clemente/quic-go/http3"
|
2019-03-26 16:45:51 -05:00
|
|
|
)
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// Listen is like net.Listen, except Caddy's listeners can overlap
|
|
|
|
// each other: multiple listeners may be created on the same socket
|
|
|
|
// at the same time. This is useful because during config changes,
|
|
|
|
// the new config is started while the old config is still running.
|
|
|
|
// When Caddy listeners are closed, the closing logic is virtualized
|
|
|
|
// so the underlying socket isn't actually closed until all uses of
|
|
|
|
// the socket have been finished. Always be sure to close listeners
|
|
|
|
// when you are done with them, just like normal listeners.
|
2019-03-31 21:41:29 -05:00
|
|
|
func Listen(network, addr string) (net.Listener, error) {
|
2022-08-03 12:04:51 -05:00
|
|
|
lnKey := listenerKey(network, addr)
|
2019-03-26 20:42:52 -05:00
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
|
|
|
ln, err := net.Listen(network, addr)
|
|
|
|
if err != nil {
|
2022-01-19 14:26:44 -05:00
|
|
|
// https://github.com/caddyserver/caddy/pull/4534
|
|
|
|
if isUnixNetwork(network) && isListenBindAddressAlreadyInUseError(err) {
|
|
|
|
return nil, fmt.Errorf("%w: this can happen if Caddy was forcefully killed", err)
|
|
|
|
}
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &sharedListener{Listener: ln, key: lnKey}, nil
|
|
|
|
})
|
2019-03-26 16:45:51 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-03-26 20:42:52 -05:00
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener)}, nil
|
2019-03-26 16:45:51 -05:00
|
|
|
}
|
|
|
|
|
2019-09-10 09:03:37 -05:00
|
|
|
// ListenPacket returns a net.PacketConn suitable for use in a Caddy module.
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// It is like Listen except for PacketConns.
|
2019-09-10 09:03:37 -05:00
|
|
|
// Always be sure to close the PacketConn when you are done.
|
|
|
|
func ListenPacket(network, addr string) (net.PacketConn, error) {
|
2022-08-03 12:04:51 -05:00
|
|
|
lnKey := listenerKey(network, addr)
|
2019-09-10 09:03:37 -05:00
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
|
|
|
pc, err := net.ListenPacket(network, addr)
|
|
|
|
if err != nil {
|
2022-01-19 14:26:44 -05:00
|
|
|
// https://github.com/caddyserver/caddy/pull/4534
|
|
|
|
if isUnixNetwork(network) && isListenBindAddressAlreadyInUseError(err) {
|
|
|
|
return nil, fmt.Errorf("%w: this can happen if Caddy was forcefully killed", err)
|
|
|
|
}
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
|
|
|
|
})
|
2019-09-10 09:03:37 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
|
2019-09-10 09:03:37 -05:00
|
|
|
}
|
|
|
|
|
2022-03-22 18:47:57 -05:00
|
|
|
// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
|
|
|
|
// Note that the context passed to Accept is currently ignored, so using
|
|
|
|
// a context other than context.Background is meaningless.
|
|
|
|
func ListenQUIC(addr string, tlsConf *tls.Config) (quic.EarlyListener, error) {
|
2022-08-03 12:04:51 -05:00
|
|
|
lnKey := listenerKey("udp", addr)
|
2022-03-22 18:47:57 -05:00
|
|
|
|
|
|
|
sharedEl, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
|
|
|
el, err := quic.ListenAddrEarly(addr, http3.ConfigureTLSConfig(tlsConf), &quic.Config{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &sharedQuicListener{EarlyListener: el, key: lnKey}, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &fakeCloseQuicListener{
|
|
|
|
sharedQuicListener: sharedEl.(*sharedQuicListener),
|
|
|
|
context: ctx, contextCancel: cancel,
|
|
|
|
}, err
|
|
|
|
}
|
|
|
|
|
2022-08-03 12:04:51 -05:00
|
|
|
func listenerKey(network, addr string) string {
|
|
|
|
return network + "/" + addr
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListenerUsage returns the current usage count of the given listener address.
|
|
|
|
func ListenerUsage(network, addr string) int {
|
|
|
|
count, _ := listenerPool.References(listenerKey(network, addr))
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// fakeCloseListener is a private wrapper over a listener that
|
|
|
|
// is shared. The state of fakeCloseListener is not shared.
|
|
|
|
// This allows one user of a socket to "close" the listener
|
|
|
|
// while in reality the socket stays open for other users of
|
|
|
|
// the listener. In this way, servers become hot-swappable
|
|
|
|
// while the listener remains running. Listeners should be
|
|
|
|
// re-wrapped in a new fakeCloseListener each time the listener
|
|
|
|
// is reused. This type is atomic and values must not be copied.
|
2019-03-26 16:45:51 -05:00
|
|
|
type fakeCloseListener struct {
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
closed int32 // accessed atomically; belongs to this struct only
|
|
|
|
*sharedListener // embedded, so we also become a net.Listener
|
2019-03-26 16:45:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
|
2019-04-02 15:58:24 -05:00
|
|
|
// if the listener is already "closed", return error
|
2019-03-26 16:45:51 -05:00
|
|
|
if atomic.LoadInt32(&fcl.closed) == 1 {
|
2022-03-22 18:47:57 -05:00
|
|
|
return nil, fakeClosedErr(fcl)
|
2019-03-26 16:45:51 -05:00
|
|
|
}
|
2019-04-02 15:58:24 -05:00
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// call underlying accept
|
|
|
|
conn, err := fcl.sharedListener.Accept()
|
2019-04-02 15:58:24 -05:00
|
|
|
if err == nil {
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// since Accept() returned an error, it may be because our reference to
|
|
|
|
// the listener (this fakeCloseListener) may have been closed, i.e. the
|
|
|
|
// server is shutting down; in that case, we need to clear the deadline
|
|
|
|
// that we set when Close() was called, and return a non-temporary and
|
|
|
|
// non-timeout error value to the caller, masking the "true" error, so
|
|
|
|
// that server loops / goroutines won't retry, linger, and leak
|
core: Synchronize calls to SetDeadline within fakeCloseListener
First evidenced in #2658, listener deadlines would sometimes be set
after clearing them, resulting in endless i/o timeout errors, which
leave all requests hanging. This bug is fixed by synchronizing the
calls to SetDeadline: when Close() is called, the deadline is first
set to a time in the past, and the lock is released only after the
deadline is set, so when the other servers break out of their Accept()
calls, they will clear the deadline *after* it was set. Before, the
clearing could sometimes come before the set, which meant that it was
left in a timeout state indefinitely.
This may not yet be a perfect solution -- ideally, the setting and
clearing of the deadline would happen exactly once per underlying
listener, not once per fakeCloseListener, but in rigorous testing with
these changes (comprising tens of thousands of config reloads), I was
able to verify that no race condition is manifest.
2019-11-04 14:10:03 -05:00
|
|
|
if atomic.LoadInt32(&fcl.closed) == 1 {
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// we dereference the sharedListener explicitly even though it's embedded
|
|
|
|
// so that it's clear in the code that side-effects are shared with other
|
|
|
|
// users of this listener, not just our own reference to it; we also don't
|
|
|
|
// do anything with the error because all we could do is log it, but we
|
|
|
|
// expliclty assign it to nothing so we don't forget it's there if needed
|
|
|
|
_ = fcl.sharedListener.clearDeadline()
|
|
|
|
|
2019-04-02 15:58:24 -05:00
|
|
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
2022-03-22 18:47:57 -05:00
|
|
|
return nil, fakeClosedErr(fcl)
|
2019-04-02 15:58:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
2019-03-26 16:45:51 -05:00
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// Close stops accepting new connections without closing the
|
|
|
|
// underlying listener. The underlying listener is only closed
|
|
|
|
// if the caller is the last known user of the socket.
|
2019-03-26 16:45:51 -05:00
|
|
|
func (fcl *fakeCloseListener) Close() error {
|
2019-04-02 15:58:24 -05:00
|
|
|
if atomic.CompareAndSwapInt32(&fcl.closed, 0, 1) {
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// There are two ways I know of to get an Accept()
|
|
|
|
// function to return to the server loop that called
|
|
|
|
// it: close the listener, or set a deadline in the
|
|
|
|
// past. Obviously, we can't close the socket yet
|
|
|
|
// since others may be using it (hence this whole
|
|
|
|
// file). But we can set the deadline in the past,
|
|
|
|
// and this is kind of cheating, but it works, and
|
|
|
|
// it apparently even works on Windows.
|
|
|
|
_ = fcl.sharedListener.setDeadline()
|
2022-01-11 01:27:39 -05:00
|
|
|
_, _ = listenerPool.Delete(fcl.sharedListener.key)
|
2019-04-02 15:58:24 -05:00
|
|
|
}
|
2019-03-26 16:45:51 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-22 18:47:57 -05:00
|
|
|
type fakeCloseQuicListener struct {
|
|
|
|
closed int32 // accessed atomically; belongs to this struct only
|
|
|
|
*sharedQuicListener // embedded, so we also become a quic.EarlyListener
|
|
|
|
context context.Context
|
|
|
|
contextCancel context.CancelFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
// Currently Accept ignores the passed context, however a situation where
|
|
|
|
// someone would need a hotswappable QUIC-only (not http3, since it uses context.Background here)
|
|
|
|
// server on which Accept would be called with non-empty contexts
|
|
|
|
// (mind that the default net listeners' Accept doesn't take a context argument)
|
|
|
|
// sounds way too rare for us to sacrifice efficiency here.
|
2022-05-12 00:25:17 -05:00
|
|
|
func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnection, error) {
|
2022-03-22 18:47:57 -05:00
|
|
|
conn, err := fcql.sharedQuicListener.Accept(fcql.context)
|
|
|
|
if err == nil {
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the listener is "closed", return a fake closed error instead
|
|
|
|
if atomic.LoadInt32(&fcql.closed) == 1 && errors.Is(err, context.Canceled) {
|
|
|
|
return nil, fakeClosedErr(fcql)
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fcql *fakeCloseQuicListener) Close() error {
|
|
|
|
if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
|
|
|
|
fcql.contextCancel()
|
|
|
|
_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// fakeClosedErr returns an error value that is not temporary
|
|
|
|
// nor a timeout, suitable for making the caller think the
|
|
|
|
// listener is actually closed
|
2022-03-22 18:47:57 -05:00
|
|
|
func fakeClosedErr(l interface{ Addr() net.Addr }) error {
|
2019-04-02 15:58:24 -05:00
|
|
|
return &net.OpError{
|
|
|
|
Op: "accept",
|
2022-03-22 18:47:57 -05:00
|
|
|
Net: l.Addr().Network(),
|
|
|
|
Addr: l.Addr(),
|
2019-04-25 14:54:48 -05:00
|
|
|
Err: errFakeClosed,
|
2019-04-02 15:58:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// ErrFakeClosed is the underlying error value returned by
|
|
|
|
// fakeCloseListener.Accept() after Close() has been called,
|
|
|
|
// indicating that it is pretending to be closed so that the
|
|
|
|
// server using it can terminate, while the underlying
|
|
|
|
// socket is actually left open.
|
|
|
|
var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
|
|
|
|
|
|
|
|
// fakeClosePacketConn is like fakeCloseListener, but for PacketConns.
|
2019-09-10 09:03:37 -05:00
|
|
|
type fakeClosePacketConn struct {
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
closed int32 // accessed atomically; belongs to this struct only
|
|
|
|
*sharedPacketConn // embedded, so we also become a net.PacketConn
|
2019-09-10 09:03:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (fcpc *fakeClosePacketConn) Close() error {
|
|
|
|
if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) {
|
2022-01-11 01:27:39 -05:00
|
|
|
_, _ = listenerPool.Delete(fcpc.sharedPacketConn.key)
|
2019-09-10 09:03:37 -05:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-16 15:55:49 -05:00
|
|
|
// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
|
|
|
|
func (fcpc fakeClosePacketConn) SetReadBuffer(bytes int) error {
|
|
|
|
if conn, ok := fcpc.PacketConn.(interface{ SetReadBuffer(int) error }); ok {
|
|
|
|
return conn.SetReadBuffer(bytes)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("SetReadBuffer() not implemented for %T", fcpc.PacketConn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Supports QUIC implementation: https://github.com/caddyserver/caddy/issues/3998
|
|
|
|
func (fcpc fakeClosePacketConn) SyscallConn() (syscall.RawConn, error) {
|
|
|
|
if conn, ok := fcpc.PacketConn.(interface {
|
|
|
|
SyscallConn() (syscall.RawConn, error)
|
|
|
|
}); ok {
|
|
|
|
return conn.SyscallConn()
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("SyscallConn() not implemented for %T", fcpc.PacketConn)
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// sharedListener is a wrapper over an underlying listener. The listener
|
|
|
|
// and the other fields on the struct are shared state that is synchronized,
|
|
|
|
// so sharedListener structs must never be copied (always use a pointer).
|
|
|
|
type sharedListener struct {
|
|
|
|
net.Listener
|
|
|
|
key string // uniquely identifies this listener
|
|
|
|
deadline bool // whether a deadline is currently set
|
core: Synchronize calls to SetDeadline within fakeCloseListener
First evidenced in #2658, listener deadlines would sometimes be set
after clearing them, resulting in endless i/o timeout errors, which
leave all requests hanging. This bug is fixed by synchronizing the
calls to SetDeadline: when Close() is called, the deadline is first
set to a time in the past, and the lock is released only after the
deadline is set, so when the other servers break out of their Accept()
calls, they will clear the deadline *after* it was set. Before, the
clearing could sometimes come before the set, which meant that it was
left in a timeout state indefinitely.
This may not yet be a perfect solution -- ideally, the setting and
clearing of the deadline would happen exactly once per underlying
listener, not once per fakeCloseListener, but in rigorous testing with
these changes (comprising tens of thousands of config reloads), I was
able to verify that no race condition is manifest.
2019-11-04 14:10:03 -05:00
|
|
|
deadlineMu sync.Mutex
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sl *sharedListener) clearDeadline() error {
|
|
|
|
var err error
|
|
|
|
sl.deadlineMu.Lock()
|
|
|
|
if sl.deadline {
|
|
|
|
switch ln := sl.Listener.(type) {
|
|
|
|
case *net.TCPListener:
|
|
|
|
err = ln.SetDeadline(time.Time{})
|
|
|
|
case *net.UnixListener:
|
|
|
|
err = ln.SetDeadline(time.Time{})
|
|
|
|
}
|
|
|
|
sl.deadline = false
|
|
|
|
}
|
|
|
|
sl.deadlineMu.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sl *sharedListener) setDeadline() error {
|
|
|
|
timeInPast := time.Now().Add(-1 * time.Minute)
|
|
|
|
var err error
|
|
|
|
sl.deadlineMu.Lock()
|
|
|
|
if !sl.deadline {
|
|
|
|
switch ln := sl.Listener.(type) {
|
|
|
|
case *net.TCPListener:
|
|
|
|
err = ln.SetDeadline(timeInPast)
|
|
|
|
case *net.UnixListener:
|
|
|
|
err = ln.SetDeadline(timeInPast)
|
|
|
|
}
|
|
|
|
sl.deadline = true
|
|
|
|
}
|
|
|
|
sl.deadlineMu.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destruct is called by the UsagePool when the listener is
|
|
|
|
// finally not being used anymore. It closes the socket.
|
|
|
|
func (sl *sharedListener) Destruct() error {
|
|
|
|
return sl.Listener.Close()
|
|
|
|
}
|
|
|
|
|
2022-03-22 18:47:57 -05:00
|
|
|
// sharedQuicListener is like sharedListener, but for quic.EarlyListeners.
|
|
|
|
type sharedQuicListener struct {
|
|
|
|
quic.EarlyListener
|
|
|
|
key string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destruct closes the underlying QUIC listener.
|
|
|
|
func (sql *sharedQuicListener) Destruct() error {
|
|
|
|
return sql.EarlyListener.Close()
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// sharedPacketConn is like sharedListener, but for net.PacketConns.
|
|
|
|
type sharedPacketConn struct {
|
|
|
|
net.PacketConn
|
|
|
|
key string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destruct closes the underlying socket.
|
|
|
|
func (spc *sharedPacketConn) Destruct() error {
|
|
|
|
return spc.PacketConn.Close()
|
2019-04-02 16:31:02 -05:00
|
|
|
}
|
|
|
|
|
2020-04-07 09:33:45 -05:00
|
|
|
// NetworkAddress contains the individual components
|
2019-11-11 17:33:38 -05:00
|
|
|
// for a parsed network address of the form accepted
|
|
|
|
// by ParseNetworkAddress(). Network should be a
|
|
|
|
// network value accepted by Go's net package. Port
|
|
|
|
// ranges are given by [StartPort, EndPort].
|
2020-04-07 09:33:45 -05:00
|
|
|
type NetworkAddress struct {
|
2019-11-11 17:33:38 -05:00
|
|
|
Network string
|
|
|
|
Host string
|
|
|
|
StartPort uint
|
|
|
|
EndPort uint
|
|
|
|
}
|
|
|
|
|
2020-04-07 09:33:45 -05:00
|
|
|
// IsUnixNetwork returns true if na.Network is
|
2019-12-06 13:45:50 -05:00
|
|
|
// unix, unixgram, or unixpacket.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) IsUnixNetwork() bool {
|
|
|
|
return isUnixNetwork(na.Network)
|
2019-12-06 13:45:50 -05:00
|
|
|
}
|
|
|
|
|
2019-11-11 17:33:38 -05:00
|
|
|
// JoinHostPort is like net.JoinHostPort, but where the port
|
|
|
|
// is StartPort + offset.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
|
|
|
if na.IsUnixNetwork() {
|
|
|
|
return na.Host
|
2019-12-06 13:45:50 -05:00
|
|
|
}
|
2020-04-07 09:33:45 -05:00
|
|
|
return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
|
2019-11-11 17:33:38 -05:00
|
|
|
}
|
2019-07-08 17:46:38 -05:00
|
|
|
|
2022-08-03 12:04:51 -05:00
|
|
|
func (na NetworkAddress) Expand() []NetworkAddress {
|
|
|
|
size := na.PortRangeSize()
|
|
|
|
addrs := make([]NetworkAddress, size)
|
|
|
|
for portOffset := uint(0); portOffset < size; portOffset++ {
|
|
|
|
na2 := na
|
|
|
|
na2.StartPort, na2.EndPort = na.StartPort+portOffset, na.StartPort+portOffset
|
|
|
|
addrs[portOffset] = na2
|
|
|
|
}
|
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
2019-11-11 17:33:38 -05:00
|
|
|
// PortRangeSize returns how many ports are in
|
|
|
|
// pa's port range. Port ranges are inclusive,
|
|
|
|
// so the size is the difference of start and
|
|
|
|
// end ports plus one.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) PortRangeSize() uint {
|
2022-08-03 12:04:51 -05:00
|
|
|
if na.EndPort < na.StartPort {
|
|
|
|
return 0
|
|
|
|
}
|
2020-04-07 09:33:45 -05:00
|
|
|
return (na.EndPort - na.StartPort) + 1
|
2019-11-11 17:33:38 -05:00
|
|
|
}
|
|
|
|
|
2020-04-10 18:31:38 -05:00
|
|
|
func (na NetworkAddress) isLoopback() bool {
|
|
|
|
if na.IsUnixNetwork() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if na.Host == "localhost" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if ip := net.ParseIP(na.Host); ip != nil {
|
|
|
|
return ip.IsLoopback()
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-16 12:41:32 -05:00
|
|
|
func (na NetworkAddress) isWildcardInterface() bool {
|
|
|
|
if na.Host == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if ip := net.ParseIP(na.Host); ip != nil {
|
|
|
|
return ip.IsUnspecified()
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-10 18:31:38 -05:00
|
|
|
func (na NetworkAddress) port() string {
|
|
|
|
if na.StartPort == na.EndPort {
|
|
|
|
return strconv.FormatUint(uint64(na.StartPort), 10)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%d-%d", na.StartPort, na.EndPort)
|
|
|
|
}
|
|
|
|
|
2022-07-25 18:28:20 -05:00
|
|
|
// String reconstructs the address string for human display.
|
|
|
|
// The output can be parsed by ParseNetworkAddress(). If the
|
|
|
|
// address is a unix socket, any non-zero port will be dropped.
|
2020-04-07 09:33:45 -05:00
|
|
|
func (na NetworkAddress) String() string {
|
2022-07-25 18:28:20 -05:00
|
|
|
if na.Network == "tcp" && (na.Host != "" || na.port() != "") {
|
|
|
|
na.Network = "" // omit default network value for brevity
|
|
|
|
}
|
2020-04-10 18:31:38 -05:00
|
|
|
return JoinNetworkAddress(na.Network, na.Host, na.port())
|
2019-11-11 17:33:38 -05:00
|
|
|
}
|
|
|
|
|
2019-12-06 13:45:50 -05:00
|
|
|
func isUnixNetwork(netw string) bool {
|
|
|
|
return netw == "unix" || netw == "unixgram" || netw == "unixpacket"
|
|
|
|
}
|
|
|
|
|
2022-01-19 14:26:44 -05:00
|
|
|
func isListenBindAddressAlreadyInUseError(err error) bool {
|
|
|
|
switch networkOperationError := err.(type) {
|
|
|
|
case *net.OpError:
|
|
|
|
switch syscallError := networkOperationError.Err.(type) {
|
|
|
|
case *os.SyscallError:
|
|
|
|
if syscallError.Syscall == "bind" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-11-11 17:33:38 -05:00
|
|
|
// ParseNetworkAddress parses addr into its individual
|
|
|
|
// components. The input string is expected to be of
|
|
|
|
// the form "network/host:port-range" where any part is
|
|
|
|
// optional. The default network, if unspecified, is tcp.
|
|
|
|
// Port ranges are inclusive.
|
|
|
|
//
|
|
|
|
// Network addresses are distinct from URLs and do not
|
|
|
|
// use URL syntax.
|
2020-04-07 09:33:45 -05:00
|
|
|
func ParseNetworkAddress(addr string) (NetworkAddress, error) {
|
2019-07-08 17:46:38 -05:00
|
|
|
var host, port string
|
2019-11-11 17:33:38 -05:00
|
|
|
network, host, port, err := SplitNetworkAddress(addr)
|
2019-07-08 17:46:38 -05:00
|
|
|
if err != nil {
|
2020-04-07 09:33:45 -05:00
|
|
|
return NetworkAddress{}, err
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
2022-07-25 18:28:20 -05:00
|
|
|
if network == "" {
|
|
|
|
network = "tcp"
|
|
|
|
}
|
2019-12-06 13:45:50 -05:00
|
|
|
if isUnixNetwork(network) {
|
2020-04-07 09:33:45 -05:00
|
|
|
return NetworkAddress{
|
2019-11-11 17:33:38 -05:00
|
|
|
Network: network,
|
|
|
|
Host: host,
|
|
|
|
}, nil
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
2019-11-11 17:33:38 -05:00
|
|
|
var start, end uint64
|
2022-07-25 18:28:20 -05:00
|
|
|
if port != "" {
|
|
|
|
ports := strings.SplitN(port, "-", 2)
|
|
|
|
if len(ports) == 1 {
|
|
|
|
ports = append(ports, ports[0])
|
|
|
|
}
|
|
|
|
start, err = strconv.ParseUint(ports[0], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return NetworkAddress{}, fmt.Errorf("invalid start port: %v", err)
|
|
|
|
}
|
|
|
|
end, err = strconv.ParseUint(ports[1], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return NetworkAddress{}, fmt.Errorf("invalid end port: %v", err)
|
|
|
|
}
|
|
|
|
if end < start {
|
|
|
|
return NetworkAddress{}, fmt.Errorf("end port must not be less than start port")
|
|
|
|
}
|
|
|
|
if (end - start) > maxPortSpan {
|
|
|
|
return NetworkAddress{}, fmt.Errorf("port range exceeds %d ports", maxPortSpan)
|
|
|
|
}
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
2020-04-07 09:33:45 -05:00
|
|
|
return NetworkAddress{
|
2019-11-11 17:33:38 -05:00
|
|
|
Network: network,
|
|
|
|
Host: host,
|
|
|
|
StartPort: uint(start),
|
|
|
|
EndPort: uint(end),
|
|
|
|
}, nil
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
|
|
|
|
2019-09-05 14:14:39 -05:00
|
|
|
// SplitNetworkAddress splits a into its network, host, and port components.
|
2019-11-11 17:33:38 -05:00
|
|
|
// Note that port may be a port range (:X-Y), or omitted for unix sockets.
|
2019-09-05 14:14:39 -05:00
|
|
|
func SplitNetworkAddress(a string) (network, host, port string, err error) {
|
2019-07-08 17:46:38 -05:00
|
|
|
if idx := strings.Index(a, "/"); idx >= 0 {
|
|
|
|
network = strings.ToLower(strings.TrimSpace(a[:idx]))
|
|
|
|
a = a[idx+1:]
|
|
|
|
}
|
2019-12-06 14:00:04 -05:00
|
|
|
if isUnixNetwork(network) {
|
2019-07-08 17:46:38 -05:00
|
|
|
host = a
|
|
|
|
return
|
|
|
|
}
|
|
|
|
host, port, err = net.SplitHostPort(a)
|
2022-07-25 18:28:20 -05:00
|
|
|
if err == nil || a == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// in general, if there was an error, it was likely "missing port",
|
|
|
|
// so try adding a bogus port to take advantage of standard library's
|
|
|
|
// robust parser, then strip the artificial port before returning
|
|
|
|
// (don't overwrite original error though; might still be relevant)
|
|
|
|
var err2 error
|
|
|
|
host, port, err2 = net.SplitHostPort(a + ":0")
|
|
|
|
if err2 == nil {
|
|
|
|
err = nil
|
|
|
|
port = ""
|
|
|
|
}
|
2019-07-08 17:46:38 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-05 14:14:39 -05:00
|
|
|
// JoinNetworkAddress combines network, host, and port into a single
|
2019-12-06 14:00:04 -05:00
|
|
|
// address string of the form accepted by ParseNetworkAddress(). For
|
|
|
|
// unix sockets, the network should be "unix" (or "unixgram" or
|
|
|
|
// "unixpacket") and the path to the socket should be given as the
|
2019-11-11 17:33:38 -05:00
|
|
|
// host parameter.
|
2019-09-05 14:14:39 -05:00
|
|
|
func JoinNetworkAddress(network, host, port string) string {
|
2019-07-08 17:46:38 -05:00
|
|
|
var a string
|
|
|
|
if network != "" {
|
|
|
|
a = network + "/"
|
|
|
|
}
|
2020-04-10 18:31:38 -05:00
|
|
|
if (host != "" && port == "") || isUnixNetwork(network) {
|
2019-10-11 15:25:39 -05:00
|
|
|
a += host
|
|
|
|
} else if port != "" {
|
|
|
|
a += net.JoinHostPort(host, port)
|
2019-07-08 17:46:38 -05:00
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
2019-11-11 17:33:38 -05:00
|
|
|
|
2020-03-15 22:26:17 -05:00
|
|
|
// ListenerWrapper is a type that wraps a listener
|
|
|
|
// so it can modify the input listener's methods.
|
|
|
|
// Modules that implement this interface are found
|
|
|
|
// in the caddy.listeners namespace. Usually, to
|
|
|
|
// wrap a listener, you will define your own struct
|
|
|
|
// type that embeds the input listener, then
|
|
|
|
// implement your own methods that you want to wrap,
|
|
|
|
// calling the underlying listener's methods where
|
|
|
|
// appropriate.
|
|
|
|
type ListenerWrapper interface {
|
|
|
|
WrapListener(net.Listener) net.Listener
|
|
|
|
}
|
|
|
|
|
core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.
This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.
Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.
Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.
Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-11 01:24:58 -05:00
|
|
|
// listenerPool stores and allows reuse of active listeners.
|
|
|
|
var listenerPool = NewUsagePool()
|
2019-11-11 17:33:38 -05:00
|
|
|
|
|
|
|
const maxPortSpan = 65535
|
2021-02-16 15:55:49 -05:00
|
|
|
|
|
|
|
// Interface guards (see https://github.com/caddyserver/caddy/issues/3998)
|
|
|
|
var (
|
|
|
|
_ (interface{ SetReadBuffer(int) error }) = (*fakeClosePacketConn)(nil)
|
|
|
|
_ (interface {
|
|
|
|
SyscallConn() (syscall.RawConn, error)
|
|
|
|
}) = (*fakeClosePacketConn)(nil)
|
|
|
|
)
|