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 }