1package internet
2
3import (
4	"context"
5	"runtime"
6	"syscall"
7
8	"github.com/pires/go-proxyproto"
9
10	"github.com/v2fly/v2ray-core/v4/common/net"
11	"github.com/v2fly/v2ray-core/v4/common/session"
12)
13
14var (
15	effectiveListener = DefaultListener{}
16)
17
18type controller func(network, address string, fd uintptr) error
19
20type DefaultListener struct {
21	controllers []controller
22}
23
24func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []controller) func(network, address string, c syscall.RawConn) error {
25	return func(network, address string, c syscall.RawConn) error {
26		return c.Control(func(fd uintptr) {
27			if sockopt != nil {
28				if err := applyInboundSocketOptions(network, fd, sockopt); err != nil {
29					newError("failed to apply socket options to incoming connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
30				}
31			}
32
33			setReusePort(fd) // nolint: staticcheck
34
35			for _, controller := range controllers {
36				if err := controller(network, address, fd); err != nil {
37					newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx))
38				}
39			}
40		})
41	}
42}
43
44func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.Listener, error) {
45	var lc net.ListenConfig
46	var l net.Listener
47	var err error
48	var network, address string
49	switch addr := addr.(type) {
50	case *net.TCPAddr:
51		network = addr.Network()
52		address = addr.String()
53		lc.Control = getControlFunc(ctx, sockopt, dl.controllers)
54	case *net.UnixAddr:
55		lc.Control = nil
56		network = addr.Network()
57		address = addr.Name
58		if (runtime.GOOS == "linux" || runtime.GOOS == "android") && address[0] == '@' {
59			// linux abstract unix domain socket is lockfree
60			if len(address) > 1 && address[1] == '@' {
61				// but may need padding to work with haproxy
62				fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
63				copy(fullAddr, address[1:])
64				address = string(fullAddr)
65			}
66		} else {
67			// normal unix domain socket needs lock
68			locker := &FileLocker{
69				path: address + ".lock",
70			}
71			err := locker.Acquire()
72			if err != nil {
73				return nil, err
74			}
75			ctx = context.WithValue(ctx, address, locker) // nolint: golint,staticcheck
76		}
77	}
78
79	l, err = lc.Listen(ctx, network, address)
80	if sockopt != nil && sockopt.AcceptProxyProtocol {
81		policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil }
82		l = &proxyproto.Listener{Listener: l, Policy: policyFunc}
83	}
84	return l, err
85}
86
87func (dl *DefaultListener) ListenPacket(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.PacketConn, error) {
88	var lc net.ListenConfig
89
90	lc.Control = getControlFunc(ctx, sockopt, dl.controllers)
91
92	return lc.ListenPacket(ctx, addr.Network(), addr.String())
93}
94
95// RegisterListenerController adds a controller to the effective system listener.
96// The controller can be used to operate on file descriptors before they are put into use.
97//
98// v2ray:api:beta
99func RegisterListenerController(controller func(network, address string, fd uintptr) error) error {
100	if controller == nil {
101		return newError("nil listener controller")
102	}
103
104	effectiveListener.controllers = append(effectiveListener.controllers, controller)
105	return nil
106}
107