Files

100 lines
2.6 KiB
Go

package dialer
import (
"context"
"net"
"sync"
"syscall"
"go.uber.org/atomic"
)
// DefaultDialer is the package-level default Dialer.
// It is used by DialContext and ListenPacket.
var DefaultDialer = &Dialer{}
// RegisterSockOpt registers a socket option on the DefaultDialer.
func RegisterSockOpt(opt SocketOption) {
DefaultDialer.RegisterSockOpt(opt)
}
// Reset removes all registered socket options from the DefaultDialer.
func Reset() {
DefaultDialer.Reset()
}
// DialContext dials using the DefaultDialer.
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return DefaultDialer.DialContext(ctx, network, address)
}
// ListenPacket listens using the DefaultDialer.
func ListenPacket(network, address string) (net.PacketConn, error) {
return DefaultDialer.ListenPacket(network, address)
}
// Dialer applies registered SocketOptions to all dials/listens.
type Dialer struct {
optsMu sync.Mutex
atomicOpts atomic.Value
}
// New creates a new Dialer with the given initial socket options.
func New(opts ...SocketOption) *Dialer {
d := &Dialer{}
for _, opt := range opts {
d.RegisterSockOpt(opt)
}
return d
}
// RegisterSockOpt registers a socket option on the Dialer.
func (d *Dialer) RegisterSockOpt(opt SocketOption) {
d.optsMu.Lock()
opts, _ := d.atomicOpts.Load().([]SocketOption)
d.atomicOpts.Store(append(opts, opt))
d.optsMu.Unlock()
}
// Reset removes all registered socket options from the Dialer.
func (d *Dialer) Reset() {
d.optsMu.Lock()
d.atomicOpts.Store([]SocketOption(nil))
d.optsMu.Unlock()
}
func (d *Dialer) applySockOpts(network string, address string, c syscall.RawConn) error {
opts, _ := d.atomicOpts.Load().([]SocketOption)
if len(opts) == 0 {
return nil
}
// Skip non-global-unicast IPs (e.g. loopback, link-local).
if host, _, err := net.SplitHostPort(address); err == nil {
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
return nil
}
}
for _, opt := range opts {
if err := opt.Apply(network, address, c); err != nil {
return err
}
}
return nil
}
// DialContext behaves like net.Dialer.DialContext, applying registered SocketOptions.
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
nd := &net.Dialer{
Control: d.applySockOpts,
}
return nd.DialContext(ctx, network, address)
}
// ListenPacket behaves like net.ListenConfig.ListenPacket, applying registered SocketOptions.
func (d *Dialer) ListenPacket(network, address string) (net.PacketConn, error) {
lc := &net.ListenConfig{
Control: d.applySockOpts,
}
return lc.ListenPacket(context.Background(), network, address)
}