github.com/astaguna/popon-core@v0.0.0-20231019235610-96e42d76a5ff/psiphon/UDPConn.go (about) 1 /* 2 * Copyright (c) 2018, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package psiphon 21 22 import ( 23 "context" 24 "math/rand" 25 "net" 26 "strconv" 27 "syscall" 28 29 "github.com/astaguna/popon-core/psiphon/common/errors" 30 ) 31 32 // NewUDPConn resolves addr and configures a new *net.UDPConn. The UDP socket 33 // is created using options in DialConfig, including DeviceBinder. The 34 // returned UDPAddr uses DialConfig options IPv6Synthesizer and 35 // ResolvedIPCallback. 36 // 37 // The UDP conn is not dialed; it is intended for use with WriteTo using the 38 // returned UDPAddr, not Write. 39 // 40 // The returned conn is not a common.Closer; the caller is expected to wrap 41 // this conn with another higher-level conn that provides that interface. 42 func NewUDPConn( 43 ctx context.Context, addr string, config *DialConfig) (net.PacketConn, *net.UDPAddr, error) { 44 45 host, strPort, err := net.SplitHostPort(addr) 46 if err != nil { 47 return nil, nil, errors.Trace(err) 48 } 49 port, err := strconv.Atoi(strPort) 50 if err != nil { 51 return nil, nil, errors.Trace(err) 52 } 53 54 if port <= 0 || port >= 65536 { 55 return nil, nil, errors.Tracef("invalid destination port: %d", port) 56 } 57 58 if config.ResolveIP == nil { 59 // Fail even if we don't need a resolver for this dial: this is a code 60 // misconfiguration. 61 return nil, nil, errors.TraceNew("missing resolver") 62 } 63 ipAddrs, err := config.ResolveIP(ctx, host) 64 if err != nil { 65 return nil, nil, errors.Trace(err) 66 } 67 if len(ipAddrs) < 1 { 68 return nil, nil, errors.TraceNew("no IP address") 69 } 70 71 ipAddr := ipAddrs[rand.Intn(len(ipAddrs))] 72 73 if config.IPv6Synthesizer != nil { 74 if ipAddr.To4() != nil { 75 synthesizedIPAddress := config.IPv6Synthesizer.IPv6Synthesize(ipAddr.String()) 76 if synthesizedIPAddress != "" { 77 synthesizedAddr := net.ParseIP(synthesizedIPAddress) 78 if synthesizedAddr != nil { 79 ipAddr = synthesizedAddr 80 } 81 } 82 } 83 } 84 85 listen := &net.ListenConfig{ 86 Control: func(_, _ string, c syscall.RawConn) error { 87 var controlErr error 88 err := c.Control(func(fd uintptr) { 89 90 socketFD := int(fd) 91 92 setAdditionalSocketOptions(socketFD) 93 94 if config.BPFProgramInstructions != nil { 95 err := setSocketBPF(config.BPFProgramInstructions, socketFD) 96 if err != nil { 97 controlErr = errors.Tracef("setSocketBPF failed: %s", err) 98 return 99 } 100 } 101 102 if config.DeviceBinder != nil { 103 _, err := config.DeviceBinder.BindToDevice(socketFD) 104 if err != nil { 105 controlErr = errors.Tracef("BindToDevice failed: %s", err) 106 return 107 } 108 } 109 }) 110 if controlErr != nil { 111 return errors.Trace(controlErr) 112 } 113 return errors.Trace(err) 114 }, 115 } 116 117 network := "udp4" 118 if ipAddr.To4() == nil { 119 network = "udp6" 120 } 121 122 // It's necessary to create an "unconnected" UDP socket, for use with 123 // WriteTo, as required by quic-go. As documented in net.ListenUDP: with 124 // an unspecified IP address, the resulting conn "listens on all 125 // available IP addresses of the local system except multicast IP 126 // addresses". 127 // 128 // Limitation: these UDP sockets are not necessarily closed when a device 129 // changes active network (e.g., WiFi to mobile). It's possible that a 130 // QUIC connection does not immediately close on a network change, and 131 // instead outbound packets are sent from a different active interface. 132 // As quic-go does not yet support connection migration, these packets 133 // will be dropped by the server. This situation is mitigated by use of 134 // DeviceBinder; by network change event detection, which initiates new 135 // tunnel connections; and by timeouts/keep-alives. 136 137 conn, err := listen.ListenPacket(ctx, network, "") 138 if err != nil { 139 return nil, nil, errors.Trace(err) 140 } 141 142 udpConn, ok := conn.(*net.UDPConn) 143 if !ok { 144 return nil, nil, errors.Tracef("unexpected conn type: %T", conn) 145 } 146 147 if config.ResolvedIPCallback != nil { 148 config.ResolvedIPCallback(ipAddr.String()) 149 } 150 151 return udpConn, &net.UDPAddr{IP: ipAddr, Port: port}, nil 152 }