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  }