mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-07-22 21:00:38 +08:00
74 lines
2.6 KiB
Go
74 lines
2.6 KiB
Go
//go:build windows && (amd64 || 386)
|
|
|
|
package bridge
|
|
|
|
import (
|
|
"encoding/binary"
|
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// SIO_ACQUIRE_PORT_RESERVATION = _WSAIOW(IOC_VENDOR, 100):
|
|
// IOC_IN | IOC_VENDOR | 100. Despite the write-only direction code, the
|
|
// reservation result is written to the WSAIoctl output buffer; using
|
|
// _WSAIORW instead is rejected with WSAEOPNOTSUPP.
|
|
const sioAcquirePortReservation uint32 = 0x80000000 | 0x18000000 | 100
|
|
|
|
// portReservation holds a runtime port block acquired from the host TCP/IP
|
|
// stack. Runtime reservation records are protocol- and family-agnostic:
|
|
// one reservation excludes the block from ephemeral auto-assignment for
|
|
// TCP and UDP sockets of both address families (and a specific reservation
|
|
// request for numbers covered by any existing record fails with
|
|
// WSAEADDRINUSE, whatever its protocol). Explicit binds inside the block
|
|
// are rejected for the reserving protocol but still allowed for others.
|
|
// Closing the socket releases the reservation.
|
|
type portReservation struct {
|
|
socket windows.Handle
|
|
startPort uint16
|
|
}
|
|
|
|
func acquirePortReservation(family, socketType, protocol int, count uint16) (*portReservation, error) {
|
|
socket, err := windows.Socket(family, socketType, protocol)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "create reservation socket")
|
|
}
|
|
// INET_PORT_RANGE { USHORT StartPort; USHORT NumberOfPorts; }.
|
|
// StartPort 0 requests a runtime (wildcard) reservation.
|
|
var in [4]byte
|
|
binary.LittleEndian.PutUint16(in[0:2], 0)
|
|
binary.LittleEndian.PutUint16(in[2:4], count)
|
|
// INET_PORT_RESERVATION_INSTANCE {
|
|
// INET_PORT_RESERVATION { USHORT StartPort; USHORT NumberOfPorts; };
|
|
// INET_PORT_RESERVATION_TOKEN { ULONG64 Token; };
|
|
// } — the ULONG64 forces 8-byte alignment, so Token sits at offset 8.
|
|
var out [16]byte
|
|
var returned uint32
|
|
err = windows.WSAIoctl(socket, sioAcquirePortReservation,
|
|
&in[0], uint32(len(in)), &out[0], uint32(len(out)), &returned, nil, 0)
|
|
if err != nil {
|
|
windows.Closesocket(socket)
|
|
return nil, E.Cause(err, "acquire port reservation")
|
|
}
|
|
// StartPort is returned in network byte order (as documented for
|
|
// INET_PORT_RANGE); NumberOfPorts is a plain host-order count.
|
|
startPort := binary.BigEndian.Uint16(out[0:2])
|
|
reservedCount := binary.LittleEndian.Uint16(out[2:4])
|
|
if startPort == 0 || reservedCount < count {
|
|
windows.Closesocket(socket)
|
|
return nil, E.New("acquire port reservation: stack returned ", reservedCount, " of ", count, " ports")
|
|
}
|
|
return &portReservation{
|
|
socket: socket,
|
|
startPort: startPort,
|
|
}, nil
|
|
}
|
|
|
|
func (r *portReservation) Close() {
|
|
if r == nil {
|
|
return
|
|
}
|
|
windows.Closesocket(r.socket)
|
|
}
|