NewDERPMap constructs a DERPMap from a set of STUN addresses and optionally a remote URL to fetch a mapping from e.g. https://controlplane.tailscale.com/derpmap/default. nolint:revive
(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []string, remoteURL, localPath string, disableSTUN bool)
| 58 | // |
| 59 | //nolint:revive |
| 60 | func NewDERPMap(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []string, remoteURL, localPath string, disableSTUN bool) (*tailcfg.DERPMap, error) { |
| 61 | if remoteURL != "" && localPath != "" { |
| 62 | return nil, xerrors.New("a remote URL or local path must be specified, not both") |
| 63 | } |
| 64 | if disableSTUN { |
| 65 | stunAddrs = nil |
| 66 | } |
| 67 | |
| 68 | // stunAddrs only applies when a default region is set. Each STUN node gets |
| 69 | // it's own region ID because netcheck will only try a single STUN server in |
| 70 | // each region before canceling the region's STUN check. |
| 71 | addRegions := []*tailcfg.DERPRegion{} |
| 72 | if region != nil { |
| 73 | addRegions = append(addRegions, region) |
| 74 | stunRegions, err := STUNRegions(region.RegionID, stunAddrs) |
| 75 | if err != nil { |
| 76 | return nil, xerrors.Errorf("create stun regions: %w", err) |
| 77 | } |
| 78 | addRegions = append(addRegions, stunRegions...) |
| 79 | } |
| 80 | |
| 81 | derpMap := &tailcfg.DERPMap{ |
| 82 | Regions: map[int]*tailcfg.DERPRegion{}, |
| 83 | } |
| 84 | if remoteURL != "" { |
| 85 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, remoteURL, nil) |
| 86 | if err != nil { |
| 87 | return nil, xerrors.Errorf("create request: %w", err) |
| 88 | } |
| 89 | res, err := http.DefaultClient.Do(req) |
| 90 | if err != nil { |
| 91 | return nil, xerrors.Errorf("get derpmap: %w", err) |
| 92 | } |
| 93 | defer res.Body.Close() |
| 94 | err = json.NewDecoder(res.Body).Decode(&derpMap) |
| 95 | if err != nil { |
| 96 | return nil, xerrors.Errorf("fetch derpmap: %w", err) |
| 97 | } |
| 98 | } |
| 99 | if localPath != "" { |
| 100 | content, err := os.ReadFile(localPath) |
| 101 | if err != nil { |
| 102 | return nil, xerrors.Errorf("read derpmap from %q: %w", localPath, err) |
| 103 | } |
| 104 | err = json.Unmarshal(content, &derpMap) |
| 105 | if err != nil { |
| 106 | return nil, xerrors.Errorf("unmarshal derpmap: %w", err) |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | // Add our custom regions to the DERP map. |
| 111 | if len(addRegions) > 0 { |
| 112 | for _, region := range addRegions { |
| 113 | _, conflicts := derpMap.Regions[region.RegionID] |
| 114 | if conflicts { |
| 115 | return nil, xerrors.Errorf("a default region ID %d (%s - %q) conflicts with a remote region from %q", region.RegionID, region.RegionCode, region.RegionName, remoteURL) |
| 116 | } |
| 117 | derpMap.Regions[region.RegionID] = region |