github.com/noisysockets/noisysockets@v0.21.2-0.20240515114641-7f467e651c90/network.go (about) 1 // SPDX-License-Identifier: MPL-2.0 2 /* 3 * Copyright (C) 2024 The Noisy Sockets Authors. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * Portions of this file are based on code originally from wireguard-go, 10 * 11 * Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved. 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining a copy of 14 * this software and associated documentation files (the "Software"), to deal in 15 * the Software without restriction, including without limitation the rights to 16 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 17 * of the Software, and to permit persons to whom the Software is furnished to do 18 * so, subject to the following conditions: 19 * 20 * The above copyright notice and this permission notice shall be included in all 21 * copies or substantial portions of the Software. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 * SOFTWARE. 30 */ 31 32 package noisysockets 33 34 import ( 35 "fmt" 36 "log/slog" 37 "net/netip" 38 "strconv" 39 "strings" 40 41 "context" 42 "regexp" 43 "time" 44 45 stdnet "net" 46 47 miekgdns "github.com/miekg/dns" 48 "github.com/noisysockets/netstack/pkg/tcpip" 49 "github.com/noisysockets/netstack/pkg/tcpip/adapters/gonet" 50 "github.com/noisysockets/netstack/pkg/tcpip/network/ipv4" 51 "github.com/noisysockets/netstack/pkg/tcpip/network/ipv6" 52 "github.com/noisysockets/netstack/pkg/tcpip/stack" 53 "github.com/noisysockets/netstack/pkg/tcpip/transport/icmp" 54 "github.com/noisysockets/netstack/pkg/tcpip/transport/tcp" 55 "github.com/noisysockets/netstack/pkg/tcpip/transport/udp" 56 "github.com/noisysockets/noisysockets/config" 57 latestconfig "github.com/noisysockets/noisysockets/config/v1alpha2" 58 "github.com/noisysockets/noisysockets/internal/conn" 59 "github.com/noisysockets/noisysockets/internal/dns" 60 "github.com/noisysockets/noisysockets/internal/dns/addrselect" 61 "github.com/noisysockets/noisysockets/internal/transport" 62 "github.com/noisysockets/noisysockets/internal/util" 63 "github.com/noisysockets/noisysockets/network" 64 "github.com/noisysockets/noisysockets/types" 65 ) 66 67 var ( 68 ErrCanceled = fmt.Errorf("operation was canceled") 69 ErrTimeout = fmt.Errorf("i/o timeout") 70 ErrNumericPort = fmt.Errorf("port must be numeric") 71 ErrNoSuitableAddress = fmt.Errorf("no suitable address found") 72 ErrMissingAddress = fmt.Errorf("missing address") 73 ErrUnknownPeer = fmt.Errorf("unknown peer") 74 ) 75 76 var protoSplitter = regexp.MustCompile(`^(tcp|udp)(4|6)?$`) 77 78 type NoisySocketsNetwork struct { 79 logger *slog.Logger 80 peers *peerList 81 rt *routingTable 82 transport *transport.Transport 83 stack *stack.Stack 84 hostname string 85 interfaceAddrs []netip.Addr 86 domain string 87 resolver dns.Resolver 88 } 89 90 // OpenNetwork creates a new network using the provided configuration. 91 // The returned network is a userspace WireGuard peer that exposes 92 // Dial() and Listen() methods compatible with the net package. 93 func OpenNetwork(logger *slog.Logger, conf *latestconfig.Config) (network.Network, error) { 94 var privateKey types.NoisePrivateKey 95 if err := privateKey.UnmarshalText([]byte(conf.PrivateKey)); err != nil { 96 return nil, fmt.Errorf("failed to parse private key: %w", err) 97 } 98 99 publicKey := privateKey.Public() 100 101 logger = logger.With(slog.String("id", publicKey.DisplayString())) 102 103 net := &NoisySocketsNetwork{ 104 logger: logger, 105 peers: newPeerList(), 106 rt: newRoutingTable(logger), 107 stack: stack.New(stack.Options{ 108 NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, 109 TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6}, 110 HandleLocal: true, 111 }), 112 } 113 114 net.domain = config.DefaultDomain 115 if conf.DNS != nil && conf.DNS.Domain != "" { 116 net.domain = miekgdns.Fqdn(conf.DNS.Domain) 117 } 118 119 // Parse local addresses. 120 var err error 121 net.interfaceAddrs, err = util.ParseAddrList(conf.IPs) 122 if err != nil { 123 return nil, fmt.Errorf("could not parse local addresses: %w", err) 124 } 125 126 // Add the local node to the list of peers. 127 net.hostname = conf.Name 128 if net.hostname != "" { 129 p := newPeer(nil, net.hostname, publicKey) 130 p.AddAddresses(net.interfaceAddrs...) 131 net.peers.add(p) 132 } 133 134 if conf.DNS != nil && len(conf.DNS.Nameservers) > 0 { 135 nameservers, err := util.ParseAddrPortList(conf.DNS.Nameservers) 136 if err != nil { 137 return nil, fmt.Errorf("could not parse nameserver addresses: %w", err) 138 } 139 140 net.resolver = dns.NewUDPResolver(net, nameservers) 141 } 142 143 sourceSink, err := newSourceSink(logger, net.rt, net.stack, net.interfaceAddrs) 144 if err != nil { 145 return nil, fmt.Errorf("could not create source sink: %w", err) 146 } 147 148 net.transport = transport.NewTransport(logger, sourceSink, conn.NewStdNetBind()) 149 150 net.transport.SetPrivateKey(privateKey) 151 152 if err := net.transport.UpdatePort(conf.ListenPort); err != nil { 153 return nil, fmt.Errorf("failed to update port: %w", err) 154 } 155 156 // Add peers. 157 for _, peerConf := range conf.Peers { 158 if err := net.AddPeer(peerConf); err != nil { 159 return nil, fmt.Errorf("could not add peer %s: %w", peerConf.Name, err) 160 } 161 } 162 163 // Add routes. 164 for _, routeConf := range conf.Routes { 165 destination, err := netip.ParsePrefix(routeConf.Destination) 166 if err != nil { 167 return nil, fmt.Errorf("could not parse route destination: %w", err) 168 } 169 170 if err := net.AddRoute(destination, routeConf.Via); err != nil { 171 return nil, fmt.Errorf("could not add route: %w", err) 172 } 173 } 174 175 logger.Debug("Bringing transport up") 176 177 if err := net.transport.Up(); err != nil { 178 return nil, fmt.Errorf("failed to bring transport up: %w", err) 179 } 180 181 return net, nil 182 } 183 184 func (net *NoisySocketsNetwork) Close() error { 185 net.stack.Close() 186 187 if err := net.transport.Close(); err != nil { 188 return fmt.Errorf("failed to close transport: %w", err) 189 } 190 191 return nil 192 } 193 194 func (net *NoisySocketsNetwork) Hostname() (string, error) { 195 return net.hostname, nil 196 } 197 198 func (net *NoisySocketsNetwork) InterfaceAddrs() ([]stdnet.Addr, error) { 199 var addrs []stdnet.Addr 200 for _, addr := range net.interfaceAddrs { 201 addrs = append(addrs, &stdnet.IPAddr{ 202 IP: stdnet.IP(addr.AsSlice()), 203 }) 204 } 205 206 return addrs, nil 207 } 208 209 func (net *NoisySocketsNetwork) LookupHost(host string) ([]string, error) { 210 logger := net.logger.With(slog.String("host", host)) 211 212 logger.Debug("Looking up host") 213 214 var addrs []netip.Addr 215 216 // Host is an IP address. 217 if addr, err := netip.ParseAddr(host); err == nil { 218 addrs = append(addrs, addr) 219 220 logger.Debug("Host is an IP address") 221 222 goto LOOKUP_HOST_DONE 223 } 224 225 // Fully qualify the host name (if not already). 226 host = miekgdns.Fqdn(host) 227 228 { 229 // Check if the host name is a peer name (strip the default search domain suffix if present). 230 peerName := strings.TrimSuffix(strings.TrimSuffix(host, net.domain), ".") 231 232 // Host is the name of a peer. 233 if p, ok := net.peers.getByName(peerName); ok { 234 addrs = p.Addresses() 235 236 logger.Debug("Host is the name of a peer") 237 238 goto LOOKUP_HOST_DONE 239 } 240 } 241 242 // Host is a DNS name. 243 if net.resolver != nil { 244 logger.Debug("Resolving host, using DNS") 245 246 var err error 247 addrs, err = net.resolver.LookupHost(host) 248 if err != nil { 249 return nil, err 250 } 251 252 if len(addrs) >= 0 { 253 logger.Debug("Host is a DNS name") 254 } 255 } 256 257 LOOKUP_HOST_DONE: 258 if len(addrs) == 0 { 259 return nil, &stdnet.DNSError{Err: "no such host", Name: host} 260 } 261 262 addrselect.SortByRFC6724(net, addrs) 263 264 addrsStrings := make([]string, 0, len(addrs)) 265 for _, addr := range addrs { 266 addrsStrings = append(addrsStrings, addr.String()) 267 } 268 269 return addrsStrings, nil 270 } 271 272 func (net *NoisySocketsNetwork) Dial(network, address string) (stdnet.Conn, error) { 273 return net.DialContext(context.Background(), network, address) 274 } 275 276 func (net *NoisySocketsNetwork) DialContext(ctx context.Context, network, address string) (stdnet.Conn, error) { 277 net.logger.Debug("Dialing", slog.String("network", network), slog.String("address", address)) 278 279 acceptV4, acceptV6 := true, true 280 matches := protoSplitter.FindStringSubmatch(network) 281 if matches == nil { 282 return nil, &stdnet.OpError{Op: "dial", Err: stdnet.UnknownNetworkError(network)} 283 } else if len(matches[2]) != 0 { 284 acceptV4 = matches[2][0] == '4' 285 acceptV6 = !acceptV4 286 } 287 288 host, sport, err := stdnet.SplitHostPort(address) 289 if err != nil { 290 return nil, &stdnet.OpError{Op: "dial", Err: err} 291 } 292 293 port, err := strconv.Atoi(sport) 294 if err != nil || port < 0 || port > 65535 { 295 return nil, &stdnet.OpError{Op: "dial", Err: ErrNumericPort} 296 } 297 298 allAddr, err := net.LookupHost(host) 299 if err != nil { 300 return nil, &stdnet.OpError{Op: "dial", Err: err} 301 } 302 303 var addrs []netip.AddrPort 304 for _, addr := range allAddr { 305 ip, err := netip.ParseAddr(addr) 306 if err == nil && ((ip.Is4() && acceptV4) || (ip.Is6() && acceptV6)) { 307 addrs = append(addrs, netip.AddrPortFrom(ip, uint16(port))) 308 } 309 } 310 if len(addrs) == 0 && len(allAddr) != 0 { 311 return nil, &stdnet.OpError{Op: "dial", Err: ErrNoSuitableAddress} 312 } 313 314 var firstErr error 315 for i, addr := range addrs { 316 select { 317 case <-ctx.Done(): 318 err := ctx.Err() 319 if err == context.Canceled { 320 err = ErrCanceled 321 } else if err == context.DeadlineExceeded { 322 err = ErrTimeout 323 } 324 return nil, &stdnet.OpError{Op: "dial", Err: err} 325 default: 326 } 327 328 dialCtx := ctx 329 if deadline, hasDeadline := ctx.Deadline(); hasDeadline { 330 partialDeadline, err := partialDeadline(time.Now(), deadline, len(addrs)-i) 331 if err != nil { 332 if firstErr == nil { 333 firstErr = &stdnet.OpError{Op: "dial", Err: err} 334 } 335 break 336 } 337 if partialDeadline.Before(deadline) { 338 var cancel context.CancelFunc 339 dialCtx, cancel = context.WithDeadline(ctx, partialDeadline) 340 defer cancel() 341 } 342 } 343 344 fa, pn := convertToFullAddr(addr) 345 346 var c stdnet.Conn 347 switch matches[1] { 348 case "tcp": 349 c, err = gonet.DialContextTCP(dialCtx, net.stack, fa, pn) 350 case "udp": 351 c, err = gonet.DialUDP(net.stack, nil, &fa, pn) 352 } 353 if err == nil { 354 return c, nil 355 } 356 if firstErr == nil { 357 firstErr = err 358 } 359 } 360 if firstErr == nil { 361 firstErr = &stdnet.OpError{Op: "dial", Err: ErrMissingAddress} 362 } 363 364 return nil, firstErr 365 } 366 367 func (net *NoisySocketsNetwork) Listen(network, address string) (stdnet.Listener, error) { 368 net.logger.Debug("Listening", 369 slog.String("network", network), slog.String("address", address)) 370 371 acceptV4, acceptV6 := true, true 372 matches := protoSplitter.FindStringSubmatch(network) 373 if matches == nil { 374 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError(network)} 375 } else if len(matches[2]) != 0 { 376 acceptV4 = matches[2][0] == '4' 377 acceptV6 = !acceptV4 378 } 379 380 if matches[1] != "tcp" { 381 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError(network)} 382 } 383 384 host, sport, err := stdnet.SplitHostPort(address) 385 if err != nil { 386 return nil, &stdnet.OpError{Op: "listen", Err: err} 387 } 388 389 port, err := strconv.Atoi(sport) 390 if err != nil || port < 0 || port > 65535 { 391 return nil, &stdnet.OpError{Op: "listen", Err: ErrNumericPort} 392 } 393 394 var addr netip.AddrPort 395 if host != "" && !(host == "0.0.0.0" || host == "[::]") { 396 ip, err := netip.ParseAddr(host) 397 if err != nil { 398 return nil, &stdnet.OpError{Op: "listen", Err: err} 399 } 400 401 if ip.Is4() && !acceptV4 { 402 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError("tcp4")} 403 } 404 405 if ip.Is6() && !acceptV6 { 406 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError("tcp6")} 407 } 408 409 addr = netip.AddrPortFrom(ip, uint16(port)) 410 } else { 411 for _, localAddr := range net.interfaceAddrs { 412 if localAddr.Is6() && acceptV6 { 413 addr = netip.AddrPortFrom(localAddr, uint16(port)) 414 break 415 } 416 if localAddr.Is4() && acceptV4 { 417 addr = netip.AddrPortFrom(localAddr, uint16(port)) 418 break 419 } 420 } 421 } 422 423 fa, pn := convertToFullAddr(addr) 424 lis, err := gonet.ListenTCP(net.stack, fa, pn) 425 if err != nil { 426 return nil, err 427 } 428 429 return &listener{Listener: lis, peers: net.peers}, nil 430 } 431 432 func (net *NoisySocketsNetwork) ListenPacket(network, address string) (stdnet.PacketConn, error) { 433 net.logger.Debug("Listening for packets", 434 slog.String("network", network), slog.String("address", address)) 435 436 acceptV4, acceptV6 := true, true 437 matches := protoSplitter.FindStringSubmatch(network) 438 if matches == nil { 439 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError(network)} 440 } else if len(matches[2]) != 0 { 441 acceptV4 = matches[2][0] == '4' 442 acceptV6 = !acceptV4 443 } 444 445 if matches[1] != "udp" { 446 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError(network)} 447 } 448 449 host, sport, err := stdnet.SplitHostPort(address) 450 if err != nil { 451 return nil, &stdnet.OpError{Op: "listen", Err: err} 452 } 453 454 port, err := strconv.Atoi(sport) 455 if err != nil || port < 0 || port > 65535 { 456 return nil, &stdnet.OpError{Op: "listen", Err: ErrNumericPort} 457 } 458 459 var addr netip.AddrPort 460 if host != "" && !(host == "0.0.0.0" || host == "[::]") { 461 ip, err := netip.ParseAddr(host) 462 if err != nil { 463 return nil, &stdnet.OpError{Op: "listen", Err: err} 464 } 465 466 if ip.Is4() && !acceptV4 { 467 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError("udp4")} 468 } 469 470 if ip.Is6() && !acceptV6 { 471 return nil, &stdnet.OpError{Op: "listen", Err: stdnet.UnknownNetworkError("udp6")} 472 } 473 474 addr = netip.AddrPortFrom(ip, uint16(port)) 475 } else { 476 for _, localAddr := range net.interfaceAddrs { 477 if localAddr.Is6() && acceptV6 { 478 addr = netip.AddrPortFrom(localAddr, uint16(port)) 479 break 480 } 481 if localAddr.Is4() && acceptV4 { 482 addr = netip.AddrPortFrom(localAddr, uint16(port)) 483 break 484 } 485 } 486 } 487 488 fa, pn := convertToFullAddr(addr) 489 pc, err := gonet.DialUDP(net.stack, &fa, nil, pn) 490 if err != nil { 491 return nil, err 492 } 493 494 return &packetConn{PacketConn: pc, peers: net.peers}, nil 495 } 496 497 // AddPeer adds a wireguard peer to the network. 498 func (net *NoisySocketsNetwork) AddPeer(peerConf latestconfig.PeerConfig) error { 499 var publicKey types.NoisePublicKey 500 if err := publicKey.UnmarshalText([]byte(peerConf.PublicKey)); err != nil { 501 return fmt.Errorf("failed to parse peer public key: %w", err) 502 } 503 504 net.logger.Debug("Adding peer", 505 slog.String("name", peerConf.Name), 506 slog.String("peer", publicKey.DisplayString())) 507 508 var addrs []netip.Addr 509 for _, ip := range peerConf.IPs { 510 addr, err := netip.ParseAddr(ip) 511 if err != nil { 512 return fmt.Errorf("could not parse peer address %q: %v", ip, err) 513 } 514 addrs = append(addrs, addr) 515 } 516 517 transportPeer, err := net.transport.NewPeer(publicKey) 518 if err != nil { 519 return fmt.Errorf("failed to create transport peer: %w", err) 520 } 521 522 p := newPeer(transportPeer, peerConf.Name, publicKey) 523 p.AddAddresses(addrs...) 524 525 // Add the peer to the list of peers. 526 net.peers.add(p) 527 528 // Add the peer to the routing table. 529 if err := net.rt.update(p); err != nil { 530 return fmt.Errorf("could not add peer to routing table: %w", err) 531 } 532 533 // Regularly send keepalives to the peer to keep NAT mappings valid. 534 // This could be configurable but I think it's a good default to avoid footguns. 535 p.SetKeepAliveInterval(25 * time.Second) 536 537 if peerConf.Endpoint != "" { 538 peerEndpointHost, peerEndpointPortStr, err := stdnet.SplitHostPort(peerConf.Endpoint) 539 if err != nil { 540 return fmt.Errorf("failed to parse peer endpoint: %w", err) 541 } 542 543 peerEndpointAddrs, err := stdnet.LookupHost(peerEndpointHost) 544 if err != nil { 545 return fmt.Errorf("failed to resolve peer address: %w", err) 546 } 547 548 peerEndpointPort, err := strconv.Atoi(peerEndpointPortStr) 549 if err != nil { 550 return fmt.Errorf("failed to parse peer port: %w", err) 551 } 552 553 // TODO: try all resolved addresses until one works. 554 p.SetEndpoint(netip.AddrPortFrom(netip.MustParseAddr(peerEndpointAddrs[0]), uint16(peerEndpointPort))) 555 556 p.Start() 557 558 if err := p.SendKeepalive(); err != nil { 559 net.logger.Warn("Failed to send initial keepalive", "peer", peerConf.Name, "error", err) 560 } 561 } 562 563 return nil 564 } 565 566 // RemovePeer removes a wireguard peer from the network. 567 func (net *NoisySocketsNetwork) RemovePeer(publicKey types.NoisePublicKey) error { 568 net.logger.Debug("Removing peer", slog.String("peer", publicKey.DisplayString())) 569 570 // Remove the peer from the transport. 571 net.transport.RemovePeer(publicKey) 572 573 // Remove the peer from the peer list. 574 p, ok := net.peers.remove(publicKey) 575 if !ok { 576 return ErrUnknownPeer 577 } 578 579 // Remove the peer from the routing table. 580 if err := net.rt.remove(p); err != nil { 581 return fmt.Errorf("could not remove peer from routing table: %w", err) 582 } 583 584 return nil 585 } 586 587 // GetPeer returns a peer by its public key. 588 func (net *NoisySocketsNetwork) GetPeer(publicKey types.NoisePublicKey) (*Peer, bool) { 589 return net.peers.get(publicKey) 590 } 591 592 // ListPeers returns a list of the public keys of all known peers. 593 func (net *NoisySocketsNetwork) ListPeers() []types.NoisePublicKey { 594 var publicKeys []types.NoisePublicKey 595 _ = net.peers.forEach(func(p *Peer) error { 596 publicKeys = append(publicKeys, p.PublicKey()) 597 return nil 598 }) 599 600 return publicKeys 601 } 602 603 // AddRoute adds a routing table entry for the network. 604 func (net *NoisySocketsNetwork) AddRoute(destination netip.Prefix, viaPeerName string) error { 605 net.logger.Debug("Adding route", 606 slog.String("destination", destination.String()), 607 slog.String("via", viaPeerName)) 608 609 p, ok := net.peers.getByName(viaPeerName) 610 if !ok { 611 return ErrUnknownPeer 612 } 613 614 p.AddDestinationPrefixes(destination) 615 616 if err := net.rt.update(p); err != nil { 617 return fmt.Errorf("could not sync routing table: %w", err) 618 } 619 620 return nil 621 } 622 623 // RemoveRoute removes a routing table entry for the network. 624 func (net *NoisySocketsNetwork) RemoveRoute(destination netip.Prefix) error { 625 net.logger.Debug("Removing route", slog.String("destination", destination.String())) 626 627 p, ok := net.peers.getByDestination(destination) 628 if !ok { 629 return fmt.Errorf("could not find peer for destination prefix: %v", destination) 630 } 631 632 p.RemoveDestinationPrefixes(destination) 633 634 if err := net.rt.update(p); err != nil { 635 return fmt.Errorf("could not sync routing table: %w", err) 636 } 637 638 return nil 639 } 640 641 func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) { 642 var protoNumber tcpip.NetworkProtocolNumber 643 if endpoint.Addr().Is4() { 644 protoNumber = ipv4.ProtocolNumber 645 } else { 646 protoNumber = ipv6.ProtocolNumber 647 } 648 return tcpip.FullAddress{ 649 NIC: 1, 650 Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()), 651 Port: endpoint.Port(), 652 }, protoNumber 653 } 654 655 func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) { 656 if deadline.IsZero() { 657 return deadline, nil 658 } 659 660 timeRemaining := deadline.Sub(now) 661 if timeRemaining <= 0 { 662 return time.Time{}, ErrTimeout 663 } 664 665 timeout := timeRemaining / time.Duration(addrsRemaining) 666 const saneMinimum = 2 * time.Second 667 if timeout < saneMinimum { 668 if timeRemaining < saneMinimum { 669 timeout = timeRemaining 670 } else { 671 timeout = saneMinimum 672 } 673 } 674 675 return now.Add(timeout), nil 676 }