github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/port/reachability.go (about)

     1  /*
     2   * Copyright (C) 2021 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package port
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"encoding/binary"
    24  	"errors"
    25  	"fmt"
    26  	"net"
    27  	"time"
    28  
    29  	"github.com/gofrs/uuid"
    30  	"github.com/rs/zerolog/log"
    31  )
    32  
    33  const (
    34  	portFieldSize = 2
    35  	packetSize    = portFieldSize + uuid.Size
    36  	sendPackets   = 3
    37  )
    38  
    39  // ErrEmptyServerAddressList indicates there are no servers to get response from
    40  var ErrEmptyServerAddressList = errors.New("empty server address list specified")
    41  
    42  // GloballyReachable checks if UDP port is reachable from global Internet,
    43  // performing probe against asymmetric UDP echo server
    44  func GloballyReachable(ctx context.Context, port Port, echoServerAddresses []string, timeout time.Duration) (bool, error) {
    45  	count := len(echoServerAddresses)
    46  	if count == 0 {
    47  		return false, ErrEmptyServerAddressList
    48  	}
    49  
    50  	log.Debug().Msgf("Checking if port %d globally reachable via %v", port, echoServerAddresses)
    51  
    52  	// Claim port
    53  	rxAddr := &net.UDPAddr{
    54  		Port: port.Num(),
    55  	}
    56  
    57  	rxSock, err := net.ListenUDP("udp", rxAddr)
    58  	if err != nil {
    59  		return false, err
    60  	}
    61  	defer rxSock.Close()
    62  
    63  	// Prepare request
    64  	msg := make([]byte, packetSize)
    65  	binary.BigEndian.PutUint16(msg, uint16(port.Num()))
    66  
    67  	probeUUID, err := uuid.NewV4()
    68  	if err != nil {
    69  		return false, err
    70  	}
    71  	copy(msg[portFieldSize:], probeUUID[:])
    72  
    73  	// Send probes. Proceed to listen after first send success.
    74  	sendResultChan := make(chan error)
    75  	aggregatedSenderError := make(chan error)
    76  
    77  	// Collect and aggregate output of senders. Yield result after first success.
    78  	// Drain rest until all of them done.
    79  	go func() {
    80  		var (
    81  			success bool
    82  			lastErr error
    83  		)
    84  		for i := 0; i < count; i++ {
    85  			lastErr = <-sendResultChan
    86  			if lastErr == nil && !success {
    87  				success = true
    88  				aggregatedSenderError <- nil
    89  			}
    90  		}
    91  		if !success {
    92  			aggregatedSenderError <- lastErr
    93  		}
    94  	}()
    95  
    96  	// Spawn senders
    97  	for _, address := range echoServerAddresses {
    98  		go func(echoServerAddress string) {
    99  			sendResultChan <- sendProbe(ctx, echoServerAddress, msg)
   100  		}(address)
   101  	}
   102  
   103  	if err := <-aggregatedSenderError; err != nil {
   104  		return false, fmt.Errorf("every port probe send failed. last error: %w", err)
   105  	}
   106  
   107  	// Await response
   108  	ctx, cl := context.WithTimeout(ctx, timeout)
   109  	defer cl()
   110  	responseChan := make(chan struct{})
   111  
   112  	go probeReceiver(ctx, rxSock, probeUUID, responseChan)
   113  
   114  	// Either response will be received or not. Both cases are valid results.
   115  	select {
   116  	case <-responseChan:
   117  		return true, nil
   118  	case <-ctx.Done():
   119  		return false, nil
   120  	}
   121  }
   122  
   123  func sendProbe(ctx context.Context, echoServerAddress string, msg []byte) error {
   124  	dialer := net.Dialer{}
   125  	txSock, err := dialer.DialContext(ctx, "udp", echoServerAddress)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	defer txSock.Close()
   130  
   131  	for i := 0; i < sendPackets; i++ {
   132  		_, err = txSock.Write(msg)
   133  		if err != nil && i == 0 {
   134  			return err
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  // receives datagrams from socket until one with correct probe UUID received.
   141  // notifies caller via supplied channel.
   142  func probeReceiver(ctx context.Context, rxSock *net.UDPConn, probeUUID uuid.UUID, responseChan chan<- struct{}) {
   143  	buf := make([]byte, uuid.Size)
   144  	for {
   145  		n, _, err := rxSock.ReadFromUDP(buf)
   146  		if err != nil {
   147  			if n == 0 {
   148  				return
   149  			}
   150  			continue
   151  		}
   152  
   153  		if n < uuid.Size {
   154  			continue
   155  		}
   156  
   157  		if bytes.Equal(buf, probeUUID[:]) {
   158  			select {
   159  			case responseChan <- struct{}{}:
   160  				return
   161  			case <-ctx.Done():
   162  				return
   163  			}
   164  		}
   165  	}
   166  }