github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/nat/behavior/race.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 behavior 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "time" 25 26 "github.com/mysteriumnetwork/node/nat" 27 ) 28 29 // ErrEmptyAddressList indicates there are no servers to get response from 30 var ErrEmptyAddressList = errors.New("empty STUN server list specified") 31 32 type discoverResult struct { 33 res nat.NATType 34 err error 35 } 36 37 // RacingDiscoverNATBehavior implements concurrent NAT discovery against multiple STUN servers in parallel. 38 // First successful response is returned, other probing sessions are cancelled. 39 func RacingDiscoverNATBehavior(ctx context.Context, addresses []string, timeout time.Duration) (nat.NATType, error) { 40 count := len(addresses) 41 42 ctx1, cl := context.WithCancel(ctx) 43 defer cl() 44 45 results := make(chan discoverResult) 46 47 for _, address := range addresses { 48 go func(address string) { 49 res, err := DiscoverNATBehavior(ctx1, address, timeout) 50 resPair := discoverResult{res, err} 51 select { 52 case results <- resPair: 53 case <-ctx1.Done(): 54 } 55 }(address) 56 } 57 58 lastError := ErrEmptyAddressList 59 for i := 0; i < count; i++ { 60 select { 61 case res := <-results: 62 if res.err == nil { 63 return res.res, nil 64 } 65 lastError = res.err 66 case <-ctx1.Done(): 67 return "", ctx1.Err() 68 } 69 } 70 71 return "", fmt.Errorf("concurrent NAT probing failed. last error: %w", lastError) 72 }