mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-13 00:00:43 +00:00
* chore(go): use lowercase module import path * ci: scope checks for Go module changes
199 lines
4.9 KiB
Go
199 lines
4.9 KiB
Go
package netstd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"syscall"
|
|
|
|
"github.com/easytier/easytier/easytier-go/platform"
|
|
)
|
|
|
|
// SocketFactory implements platform.SocketFactory with the Go standard
|
|
// library and the socket controls required by EasyTier hole punching.
|
|
type SocketFactory struct{}
|
|
|
|
func (SocketFactory) ConnectTCP(
|
|
ctx context.Context,
|
|
options platform.TCPConnectOptions,
|
|
) (net.Conn, error) {
|
|
if options.Purpose == platform.TCPConnectFake {
|
|
return nil, fmt.Errorf("FakeTCP is not supported by netstd")
|
|
}
|
|
if err := validateTCPBindOptions(options.Bind); err != nil {
|
|
return nil, err
|
|
}
|
|
dialer := net.Dialer{
|
|
LocalAddr: options.Bind.LocalAddr,
|
|
Control: tcpControl(options.Bind),
|
|
}
|
|
connection, err := dialer.DialContext(ctx, "tcp", options.RemoteAddr.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if options.Purpose == platform.TCPConnectSTUNProbe {
|
|
if err := connection.(*net.TCPConn).SetLinger(0); err != nil {
|
|
_ = connection.Close()
|
|
return nil, fmt.Errorf("set TCP STUN probe linger: %w", err)
|
|
}
|
|
}
|
|
return connection, nil
|
|
}
|
|
|
|
func (SocketFactory) BindUDP(
|
|
_ context.Context,
|
|
options platform.UDPBindOptions,
|
|
) (net.PacketConn, error) {
|
|
if options.Context.SocketMark != nil || options.Context.NetNS != nil ||
|
|
options.BindDevice != nil || options.ReuseAddr || options.ReusePort {
|
|
return nil, fmt.Errorf("non-default UDP bind policy is not supported by netstd")
|
|
}
|
|
return net.ListenUDP(udpNetwork(options), options.LocalAddr)
|
|
}
|
|
|
|
func (SocketFactory) ListenTCP(
|
|
ctx context.Context,
|
|
options platform.TCPListenOptions,
|
|
) (net.Listener, error) {
|
|
if err := validateTCPBindOptions(options.Bind); err != nil {
|
|
return nil, err
|
|
}
|
|
config := net.ListenConfig{Control: tcpControl(options.Bind)}
|
|
return config.Listen(ctx, "tcp", options.Bind.LocalAddr.String())
|
|
}
|
|
|
|
func udpNetwork(options platform.UDPBindOptions) string {
|
|
if options.LocalAddr != nil && options.LocalAddr.IP.To4() != nil {
|
|
return "udp4"
|
|
}
|
|
if options.OnlyV6 {
|
|
return "udp6"
|
|
}
|
|
if options.LocalAddr != nil && len(options.LocalAddr.IP) != 0 {
|
|
return "udp"
|
|
}
|
|
if options.Context.IPVersion == platform.IPVersionV4 {
|
|
return "udp4"
|
|
}
|
|
return "udp"
|
|
}
|
|
|
|
func validateTCPBindOptions(options platform.TCPBindOptions) error {
|
|
if options.Context.SocketMark != nil || options.Context.NetNS != nil ||
|
|
options.BindDevice != nil {
|
|
return fmt.Errorf("non-default TCP bind policy is not supported by netstd")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func tcpControl(
|
|
options platform.TCPBindOptions,
|
|
) func(string, string, syscall.RawConn) error {
|
|
return func(network, _ string, connection syscall.RawConn) error {
|
|
var socketErr error
|
|
if err := connection.Control(func(descriptor uintptr) {
|
|
socketErr = applyTCPSocketOptions(descriptor, network, options)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return socketErr
|
|
}
|
|
}
|
|
|
|
func boolInt(value bool) int {
|
|
if value {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type DNSResolver struct {
|
|
Resolver *net.Resolver
|
|
}
|
|
|
|
func (resolver DNSResolver) configured() *net.Resolver {
|
|
if resolver.Resolver != nil {
|
|
return resolver.Resolver
|
|
}
|
|
return net.DefaultResolver
|
|
}
|
|
|
|
func (resolver DNSResolver) LookupIP(
|
|
ctx context.Context,
|
|
query platform.DNSQuery,
|
|
) ([]netip.Addr, error) {
|
|
if address, err := netip.ParseAddr(query.Host); err == nil {
|
|
return []netip.Addr{address.Unmap()}, nil
|
|
}
|
|
network := "ip"
|
|
switch query.IPVersion {
|
|
case 4:
|
|
network = "ip4"
|
|
case 6:
|
|
network = "ip6"
|
|
}
|
|
addresses, err := resolver.configured().LookupNetIP(ctx, network, query.Host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for index := range addresses {
|
|
addresses[index] = addresses[index].Unmap()
|
|
}
|
|
return addresses, nil
|
|
}
|
|
|
|
func (resolver DNSResolver) LookupTXT(
|
|
ctx context.Context,
|
|
query platform.DNSQuery,
|
|
) (string, error) {
|
|
records, err := resolver.configured().LookupTXT(ctx, query.Host)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(records) == 0 {
|
|
return "", fmt.Errorf("DNS TXT query for %q returned no records", query.Host)
|
|
}
|
|
return records[0], nil
|
|
}
|
|
|
|
func (resolver DNSResolver) LookupSRV(
|
|
ctx context.Context,
|
|
query platform.DNSQuery,
|
|
) ([]*net.SRV, error) {
|
|
_, records, err := resolver.configured().LookupSRV(ctx, "", "", query.Host)
|
|
return records, err
|
|
}
|
|
|
|
type ConnectorEnvironment struct{}
|
|
|
|
func (ConnectorEnvironment) LocalAddrForRemote(
|
|
ctx context.Context,
|
|
remote *net.UDPAddr,
|
|
socketContext platform.SocketContext,
|
|
) (net.Addr, error) {
|
|
if socketContext.SocketMark != nil || socketContext.NetNS != nil {
|
|
return nil, fmt.Errorf("non-default connector context is not supported by netstd")
|
|
}
|
|
network := "udp"
|
|
if remote.IP.To4() != nil {
|
|
network = "udp4"
|
|
} else if remote.IP.To16() != nil {
|
|
network = "udp6"
|
|
}
|
|
connection, err := (&net.Dialer{}).DialContext(ctx, network, remote.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer connection.Close()
|
|
return connection.LocalAddr(), nil
|
|
}
|
|
|
|
func Services() platform.Services {
|
|
return platform.Services{
|
|
Sockets: SocketFactory{},
|
|
DNS: DNSResolver{},
|
|
Environment: ConnectorEnvironment{},
|
|
}
|
|
}
|