github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/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/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 30 ) 31 32 // NewUDPConn resolves addr and configures a new UDP conn. The UDP socket is 33 // created using options in DialConfig, including DeviceBinder. The returned 34 // UDPAddr uses DialConfig options IPv6Synthesizer and ResolvedIPCallback. 35 // 36 // The UDP conn is not dialed; it is intended for use with WriteTo using the 37 // returned UDPAddr, not Write. 38 // 39 // The returned conn is not a Closer; the caller is expected to wrap this conn 40 // with another higher-level conn that provides that interface. 41 func NewUDPConn( 42 ctx context.Context, addr string, config *DialConfig) (net.PacketConn, *net.UDPAddr, error) { 43 44 host, strPort, err := net.SplitHostPort(addr) 45 if err != nil { 46 return nil, nil, errors.Trace(err) 47 } 48 port, err := strconv.Atoi(strPort) 49 if err != nil { 50 return nil, nil, errors.Trace(err) 51 } 52 53 if port <= 0 || port >= 65536 { 54 return nil, nil, errors.Tracef("invalid destination port: %d", port) 55 } 56 57 if config.ResolveIP == nil { 58 // Fail even if we don't need a resolver for this dial: this is a code 59 // misconfiguration. 60 return nil, nil, errors.TraceNew("missing resolver") 61 } 62 ipAddrs, err := config.ResolveIP(ctx, host) 63 if err != nil { 64 return nil, nil, errors.Trace(err) 65 } 66 if len(ipAddrs) < 1 { 67 return nil, nil, errors.TraceNew("no IP address") 68 } 69 70 ipAddr := ipAddrs[rand.Intn(len(ipAddrs))] 71 72 if config.IPv6Synthesizer != nil { 73 if ipAddr.To4() != nil { 74 synthesizedIPAddress := config.IPv6Synthesizer.IPv6Synthesize(ipAddr.String()) 75 if synthesizedIPAddress != "" { 76 synthesizedAddr := net.ParseIP(synthesizedIPAddress) 77 if synthesizedAddr != nil { 78 ipAddr = synthesizedAddr 79 } 80 } 81 } 82 } 83 84 var domain int 85 if ipAddr != nil && ipAddr.To4() != nil { 86 domain = syscall.AF_INET 87 } else if ipAddr != nil && ipAddr.To16() != nil { 88 domain = syscall.AF_INET6 89 } else { 90 return nil, nil, errors.Tracef("invalid IP address: %s", ipAddr.String()) 91 } 92 93 conn, err := newUDPConn(domain, config) 94 if err != nil { 95 return nil, nil, errors.Trace(err) 96 } 97 98 if config.ResolvedIPCallback != nil { 99 config.ResolvedIPCallback(ipAddr.String()) 100 } 101 102 return conn, &net.UDPAddr{IP: ipAddr, Port: port}, nil 103 }