github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/test/e2e/pkg/testnet.go (about)

     1  package e2e
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"math/rand"
     9  	"net"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"text/template"
    16  	"time"
    17  
    18  	"github.com/badrootd/nibiru-cometbft/crypto"
    19  	"github.com/badrootd/nibiru-cometbft/crypto/ed25519"
    20  	"github.com/badrootd/nibiru-cometbft/crypto/secp256k1"
    21  	rpchttp "github.com/badrootd/nibiru-cometbft/rpc/client/http"
    22  
    23  	_ "embed"
    24  )
    25  
    26  const (
    27  	randomSeed               int64  = 2308084734268
    28  	proxyPortFirst           uint32 = 5701
    29  	prometheusProxyPortFirst uint32 = 6701
    30  
    31  	defaultBatchSize   = 2
    32  	defaultConnections = 1
    33  	defaultTxSizeBytes = 1024
    34  
    35  	localVersion = "cometbft/e2e-node:local-version"
    36  )
    37  
    38  type (
    39  	Mode         string
    40  	Protocol     string
    41  	Perturbation string
    42  )
    43  
    44  const (
    45  	ModeValidator Mode = "validator"
    46  	ModeFull      Mode = "full"
    47  	ModeLight     Mode = "light"
    48  	ModeSeed      Mode = "seed"
    49  
    50  	ProtocolBuiltin Protocol = "builtin"
    51  	ProtocolFile    Protocol = "file"
    52  	ProtocolGRPC    Protocol = "grpc"
    53  	ProtocolTCP     Protocol = "tcp"
    54  	ProtocolUNIX    Protocol = "unix"
    55  
    56  	PerturbationDisconnect Perturbation = "disconnect"
    57  	PerturbationKill       Perturbation = "kill"
    58  	PerturbationPause      Perturbation = "pause"
    59  	PerturbationRestart    Perturbation = "restart"
    60  	PerturbationUpgrade    Perturbation = "upgrade"
    61  
    62  	EvidenceAgeHeight int64         = 7
    63  	EvidenceAgeTime   time.Duration = 500 * time.Millisecond
    64  )
    65  
    66  // Testnet represents a single testnet.
    67  type Testnet struct {
    68  	Name                                                 string
    69  	File                                                 string
    70  	Dir                                                  string
    71  	IP                                                   *net.IPNet
    72  	InitialHeight                                        int64
    73  	InitialState                                         map[string]string
    74  	Validators                                           map[*Node]int64
    75  	ValidatorUpdates                                     map[int64]map[*Node]int64
    76  	Nodes                                                []*Node
    77  	KeyType                                              string
    78  	Evidence                                             int
    79  	LoadTxSizeBytes                                      int
    80  	LoadTxBatchSize                                      int
    81  	LoadTxConnections                                    int
    82  	ABCIProtocol                                         string
    83  	PrepareProposalDelay                                 time.Duration
    84  	ProcessProposalDelay                                 time.Duration
    85  	CheckTxDelay                                         time.Duration
    86  	UpgradeVersion                                       string
    87  	Prometheus                                           bool
    88  	ExperimentalMaxGossipConnectionsToPersistentPeers    uint
    89  	ExperimentalMaxGossipConnectionsToNonPersistentPeers uint
    90  }
    91  
    92  // Node represents a CometBFT node in a testnet.
    93  type Node struct {
    94  	Name                string
    95  	Version             string
    96  	Testnet             *Testnet
    97  	Mode                Mode
    98  	PrivvalKey          crypto.PrivKey
    99  	NodeKey             crypto.PrivKey
   100  	IP                  net.IP
   101  	ProxyPort           uint32
   102  	StartAt             int64
   103  	BlockSync           string
   104  	StateSync           bool
   105  	Mempool             string
   106  	Database            string
   107  	ABCIProtocol        Protocol
   108  	PrivvalProtocol     Protocol
   109  	PersistInterval     uint64
   110  	SnapshotInterval    uint64
   111  	RetainBlocks        uint64
   112  	Seeds               []*Node
   113  	PersistentPeers     []*Node
   114  	Perturbations       []Perturbation
   115  	SendNoLoad          bool
   116  	Prometheus          bool
   117  	PrometheusProxyPort uint32
   118  }
   119  
   120  // LoadTestnet loads a testnet from a manifest file, using the filename to
   121  // determine the testnet name and directory (from the basename of the file).
   122  // The testnet generation must be deterministic, since it is generated
   123  // separately by the runner and the test cases. For this reason, testnets use a
   124  // random seed to generate e.g. keys.
   125  func LoadTestnet(manifest Manifest, fname string, ifd InfrastructureData) (*Testnet, error) {
   126  	dir := strings.TrimSuffix(fname, filepath.Ext(fname))
   127  	keyGen := newKeyGenerator(randomSeed)
   128  	proxyPortGen := newPortGenerator(proxyPortFirst)
   129  	prometheusProxyPortGen := newPortGenerator(prometheusProxyPortFirst)
   130  	_, ipNet, err := net.ParseCIDR(ifd.Network)
   131  	if err != nil {
   132  		return nil, fmt.Errorf("invalid IP network address %q: %w", ifd.Network, err)
   133  	}
   134  
   135  	testnet := &Testnet{
   136  		Name:                 filepath.Base(dir),
   137  		File:                 fname,
   138  		Dir:                  dir,
   139  		IP:                   ipNet,
   140  		InitialHeight:        1,
   141  		InitialState:         manifest.InitialState,
   142  		Validators:           map[*Node]int64{},
   143  		ValidatorUpdates:     map[int64]map[*Node]int64{},
   144  		Nodes:                []*Node{},
   145  		Evidence:             manifest.Evidence,
   146  		LoadTxSizeBytes:      manifest.LoadTxSizeBytes,
   147  		LoadTxBatchSize:      manifest.LoadTxBatchSize,
   148  		LoadTxConnections:    manifest.LoadTxConnections,
   149  		ABCIProtocol:         manifest.ABCIProtocol,
   150  		PrepareProposalDelay: manifest.PrepareProposalDelay,
   151  		ProcessProposalDelay: manifest.ProcessProposalDelay,
   152  		CheckTxDelay:         manifest.CheckTxDelay,
   153  		UpgradeVersion:       manifest.UpgradeVersion,
   154  		Prometheus:           manifest.Prometheus,
   155  		ExperimentalMaxGossipConnectionsToPersistentPeers:    manifest.ExperimentalMaxGossipConnectionsToPersistentPeers,
   156  		ExperimentalMaxGossipConnectionsToNonPersistentPeers: manifest.ExperimentalMaxGossipConnectionsToNonPersistentPeers,
   157  	}
   158  	if len(manifest.KeyType) != 0 {
   159  		testnet.KeyType = manifest.KeyType
   160  	}
   161  	if manifest.InitialHeight > 0 {
   162  		testnet.InitialHeight = manifest.InitialHeight
   163  	}
   164  	if testnet.ABCIProtocol == "" {
   165  		testnet.ABCIProtocol = string(ProtocolBuiltin)
   166  	}
   167  	if testnet.UpgradeVersion == "" {
   168  		testnet.UpgradeVersion = localVersion
   169  	}
   170  	if testnet.LoadTxConnections == 0 {
   171  		testnet.LoadTxConnections = defaultConnections
   172  	}
   173  	if testnet.LoadTxBatchSize == 0 {
   174  		testnet.LoadTxBatchSize = defaultBatchSize
   175  	}
   176  	if testnet.LoadTxSizeBytes == 0 {
   177  		testnet.LoadTxSizeBytes = defaultTxSizeBytes
   178  	}
   179  
   180  	// Set up nodes, in alphabetical order (IPs and ports get same order).
   181  	nodeNames := []string{}
   182  	for name := range manifest.Nodes {
   183  		nodeNames = append(nodeNames, name)
   184  	}
   185  	sort.Strings(nodeNames)
   186  
   187  	for _, name := range nodeNames {
   188  		nodeManifest := manifest.Nodes[name]
   189  		ind, ok := ifd.Instances[name]
   190  		if !ok {
   191  			return nil, fmt.Errorf("information for node '%s' missing from infrastructure data", name)
   192  		}
   193  		v := nodeManifest.Version
   194  		if v == "" {
   195  			v = localVersion
   196  		}
   197  
   198  		node := &Node{
   199  			Name:             name,
   200  			Version:          v,
   201  			Testnet:          testnet,
   202  			PrivvalKey:       keyGen.Generate(manifest.KeyType),
   203  			NodeKey:          keyGen.Generate("ed25519"),
   204  			IP:               ind.IPAddress,
   205  			ProxyPort:        proxyPortGen.Next(),
   206  			Mode:             ModeValidator,
   207  			Database:         "goleveldb",
   208  			ABCIProtocol:     Protocol(testnet.ABCIProtocol),
   209  			PrivvalProtocol:  ProtocolFile,
   210  			StartAt:          nodeManifest.StartAt,
   211  			BlockSync:        nodeManifest.BlockSync,
   212  			Mempool:          nodeManifest.Mempool,
   213  			StateSync:        nodeManifest.StateSync,
   214  			PersistInterval:  1,
   215  			SnapshotInterval: nodeManifest.SnapshotInterval,
   216  			RetainBlocks:     nodeManifest.RetainBlocks,
   217  			Perturbations:    []Perturbation{},
   218  			SendNoLoad:       nodeManifest.SendNoLoad,
   219  			Prometheus:       testnet.Prometheus,
   220  		}
   221  		if node.StartAt == testnet.InitialHeight {
   222  			node.StartAt = 0 // normalize to 0 for initial nodes, since code expects this
   223  		}
   224  		if nodeManifest.Mode != "" {
   225  			node.Mode = Mode(nodeManifest.Mode)
   226  		}
   227  		if node.Mode == ModeLight {
   228  			node.ABCIProtocol = ProtocolBuiltin
   229  		}
   230  		if nodeManifest.Database != "" {
   231  			node.Database = nodeManifest.Database
   232  		}
   233  		if nodeManifest.PrivvalProtocol != "" {
   234  			node.PrivvalProtocol = Protocol(nodeManifest.PrivvalProtocol)
   235  		}
   236  		if nodeManifest.PersistInterval != nil {
   237  			node.PersistInterval = *nodeManifest.PersistInterval
   238  		}
   239  		if node.Prometheus {
   240  			node.PrometheusProxyPort = prometheusProxyPortGen.Next()
   241  		}
   242  		for _, p := range nodeManifest.Perturb {
   243  			node.Perturbations = append(node.Perturbations, Perturbation(p))
   244  		}
   245  		testnet.Nodes = append(testnet.Nodes, node)
   246  	}
   247  
   248  	// We do a second pass to set up seeds and persistent peers, which allows graph cycles.
   249  	for _, node := range testnet.Nodes {
   250  		nodeManifest, ok := manifest.Nodes[node.Name]
   251  		if !ok {
   252  			return nil, fmt.Errorf("failed to look up manifest for node %q", node.Name)
   253  		}
   254  		for _, seedName := range nodeManifest.Seeds {
   255  			seed := testnet.LookupNode(seedName)
   256  			if seed == nil {
   257  				return nil, fmt.Errorf("unknown seed %q for node %q", seedName, node.Name)
   258  			}
   259  			node.Seeds = append(node.Seeds, seed)
   260  		}
   261  		for _, peerName := range nodeManifest.PersistentPeers {
   262  			peer := testnet.LookupNode(peerName)
   263  			if peer == nil {
   264  				return nil, fmt.Errorf("unknown persistent peer %q for node %q", peerName, node.Name)
   265  			}
   266  			node.PersistentPeers = append(node.PersistentPeers, peer)
   267  		}
   268  
   269  		// If there are no seeds or persistent peers specified, default to persistent
   270  		// connections to all other nodes.
   271  		if len(node.PersistentPeers) == 0 && len(node.Seeds) == 0 {
   272  			for _, peer := range testnet.Nodes {
   273  				if peer.Name == node.Name {
   274  					continue
   275  				}
   276  				node.PersistentPeers = append(node.PersistentPeers, peer)
   277  			}
   278  		}
   279  	}
   280  
   281  	// Set up genesis validators. If not specified explicitly, use all validator nodes.
   282  	if manifest.Validators != nil {
   283  		for validatorName, power := range *manifest.Validators {
   284  			validator := testnet.LookupNode(validatorName)
   285  			if validator == nil {
   286  				return nil, fmt.Errorf("unknown validator %q", validatorName)
   287  			}
   288  			testnet.Validators[validator] = power
   289  		}
   290  	} else {
   291  		for _, node := range testnet.Nodes {
   292  			if node.Mode == ModeValidator {
   293  				testnet.Validators[node] = 100
   294  			}
   295  		}
   296  	}
   297  
   298  	// Set up validator updates.
   299  	for heightStr, validators := range manifest.ValidatorUpdates {
   300  		height, err := strconv.Atoi(heightStr)
   301  		if err != nil {
   302  			return nil, fmt.Errorf("invalid validator update height %q: %w", height, err)
   303  		}
   304  		valUpdate := map[*Node]int64{}
   305  		for name, power := range validators {
   306  			node := testnet.LookupNode(name)
   307  			if node == nil {
   308  				return nil, fmt.Errorf("unknown validator %q for update at height %v", name, height)
   309  			}
   310  			valUpdate[node] = power
   311  		}
   312  		testnet.ValidatorUpdates[int64(height)] = valUpdate
   313  	}
   314  
   315  	return testnet, testnet.Validate()
   316  }
   317  
   318  // Validate validates a testnet.
   319  func (t Testnet) Validate() error {
   320  	if t.Name == "" {
   321  		return errors.New("network has no name")
   322  	}
   323  	if t.IP == nil {
   324  		return errors.New("network has no IP")
   325  	}
   326  	if len(t.Nodes) == 0 {
   327  		return errors.New("network has no nodes")
   328  	}
   329  	for _, node := range t.Nodes {
   330  		if err := node.Validate(t); err != nil {
   331  			return fmt.Errorf("invalid node %q: %w", node.Name, err)
   332  		}
   333  	}
   334  	return nil
   335  }
   336  
   337  // Validate validates a node.
   338  func (n Node) Validate(testnet Testnet) error {
   339  	if n.Name == "" {
   340  		return errors.New("node has no name")
   341  	}
   342  	if n.IP == nil {
   343  		return errors.New("node has no IP address")
   344  	}
   345  	if !testnet.IP.Contains(n.IP) {
   346  		return fmt.Errorf("node IP %v is not in testnet network %v", n.IP, testnet.IP)
   347  	}
   348  	if n.ProxyPort == n.PrometheusProxyPort {
   349  		return fmt.Errorf("node local port %v used also for Prometheus local port", n.ProxyPort)
   350  	}
   351  	if n.ProxyPort > 0 && n.ProxyPort <= 1024 {
   352  		return fmt.Errorf("local port %v must be >1024", n.ProxyPort)
   353  	}
   354  	if n.PrometheusProxyPort > 0 && n.PrometheusProxyPort <= 1024 {
   355  		return fmt.Errorf("local port %v must be >1024", n.PrometheusProxyPort)
   356  	}
   357  	for _, peer := range testnet.Nodes {
   358  		if peer.Name != n.Name && peer.ProxyPort == n.ProxyPort {
   359  			return fmt.Errorf("peer %q also has local port %v", peer.Name, n.ProxyPort)
   360  		}
   361  		if n.PrometheusProxyPort > 0 {
   362  			if peer.Name != n.Name && peer.PrometheusProxyPort == n.PrometheusProxyPort {
   363  				return fmt.Errorf("peer %q also has local port %v", peer.Name, n.PrometheusProxyPort)
   364  			}
   365  		}
   366  	}
   367  	switch n.BlockSync {
   368  	case "", "v0":
   369  	default:
   370  		return fmt.Errorf("invalid block sync setting %q", n.BlockSync)
   371  	}
   372  	switch n.Mempool {
   373  	case "", "v0", "v1":
   374  	default:
   375  		return fmt.Errorf("invalid mempool version %q", n.Mempool)
   376  	}
   377  	switch n.Database {
   378  	case "goleveldb", "cleveldb", "boltdb", "rocksdb", "badgerdb":
   379  	default:
   380  		return fmt.Errorf("invalid database setting %q", n.Database)
   381  	}
   382  	switch n.ABCIProtocol {
   383  	case ProtocolBuiltin, ProtocolUNIX, ProtocolTCP, ProtocolGRPC:
   384  	default:
   385  		return fmt.Errorf("invalid ABCI protocol setting %q", n.ABCIProtocol)
   386  	}
   387  	if n.Mode == ModeLight && n.ABCIProtocol != ProtocolBuiltin {
   388  		return errors.New("light client must use builtin protocol")
   389  	}
   390  	switch n.PrivvalProtocol {
   391  	case ProtocolFile, ProtocolUNIX, ProtocolTCP:
   392  	default:
   393  		return fmt.Errorf("invalid privval protocol setting %q", n.PrivvalProtocol)
   394  	}
   395  
   396  	if n.StartAt > 0 && n.StartAt < n.Testnet.InitialHeight {
   397  		return fmt.Errorf("cannot start at height %v lower than initial height %v",
   398  			n.StartAt, n.Testnet.InitialHeight)
   399  	}
   400  	if n.StateSync && n.StartAt == 0 {
   401  		return errors.New("state synced nodes cannot start at the initial height")
   402  	}
   403  	if n.RetainBlocks != 0 && n.RetainBlocks < uint64(EvidenceAgeHeight) {
   404  		return fmt.Errorf("retain_blocks must be 0 or be greater or equal to max evidence age (%d)",
   405  			EvidenceAgeHeight)
   406  	}
   407  	if n.PersistInterval == 0 && n.RetainBlocks > 0 {
   408  		return errors.New("persist_interval=0 requires retain_blocks=0")
   409  	}
   410  	if n.PersistInterval > 1 && n.RetainBlocks > 0 && n.RetainBlocks < n.PersistInterval {
   411  		return errors.New("persist_interval must be less than or equal to retain_blocks")
   412  	}
   413  	if n.SnapshotInterval > 0 && n.RetainBlocks > 0 && n.RetainBlocks < n.SnapshotInterval {
   414  		return errors.New("snapshot_interval must be less than er equal to retain_blocks")
   415  	}
   416  
   417  	var upgradeFound bool
   418  	for _, perturbation := range n.Perturbations {
   419  		switch perturbation {
   420  		case PerturbationUpgrade:
   421  			if upgradeFound {
   422  				return fmt.Errorf("'upgrade' perturbation can appear at most once per node")
   423  			}
   424  			upgradeFound = true
   425  		case PerturbationDisconnect, PerturbationKill, PerturbationPause, PerturbationRestart:
   426  		default:
   427  			return fmt.Errorf("invalid perturbation %q", perturbation)
   428  		}
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  // LookupNode looks up a node by name. For now, simply do a linear search.
   435  func (t Testnet) LookupNode(name string) *Node {
   436  	for _, node := range t.Nodes {
   437  		if node.Name == name {
   438  			return node
   439  		}
   440  	}
   441  	return nil
   442  }
   443  
   444  // ArchiveNodes returns a list of archive nodes that start at the initial height
   445  // and contain the entire blockchain history. They are used e.g. as light client
   446  // RPC servers.
   447  func (t Testnet) ArchiveNodes() []*Node {
   448  	nodes := []*Node{}
   449  	for _, node := range t.Nodes {
   450  		if !node.Stateless() && node.StartAt == 0 && node.RetainBlocks == 0 {
   451  			nodes = append(nodes, node)
   452  		}
   453  	}
   454  	return nodes
   455  }
   456  
   457  // RandomNode returns a random non-seed node.
   458  func (t Testnet) RandomNode() *Node {
   459  	for {
   460  		node := t.Nodes[rand.Intn(len(t.Nodes))] //nolint:gosec
   461  		if node.Mode != ModeSeed {
   462  			return node
   463  		}
   464  	}
   465  }
   466  
   467  // IPv6 returns true if the testnet is an IPv6 network.
   468  func (t Testnet) IPv6() bool {
   469  	return t.IP.IP.To4() == nil
   470  }
   471  
   472  // HasPerturbations returns whether the network has any perturbations.
   473  func (t Testnet) HasPerturbations() bool {
   474  	for _, node := range t.Nodes {
   475  		if len(node.Perturbations) > 0 {
   476  			return true
   477  		}
   478  	}
   479  	return false
   480  }
   481  
   482  //go:embed templates/prometheus-yaml.tmpl
   483  var prometheusYamlTemplate string
   484  
   485  func (t Testnet) prometheusConfigBytes() ([]byte, error) {
   486  	tmpl, err := template.New("prometheus-yaml").Parse(prometheusYamlTemplate)
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  	var buf bytes.Buffer
   491  	err = tmpl.Execute(&buf, t)
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  	return buf.Bytes(), nil
   496  }
   497  
   498  func (t Testnet) WritePrometheusConfig() error {
   499  	bytes, err := t.prometheusConfigBytes()
   500  	if err != nil {
   501  		return err
   502  	}
   503  	err = os.WriteFile(filepath.Join(t.Dir, "prometheus.yaml"), bytes, 0o644) //nolint:gosec
   504  	if err != nil {
   505  		return err
   506  	}
   507  	return nil
   508  }
   509  
   510  // Address returns a P2P endpoint address for the node.
   511  func (n Node) AddressP2P(withID bool) string {
   512  	ip := n.IP.String()
   513  	if n.IP.To4() == nil {
   514  		// IPv6 addresses must be wrapped in [] to avoid conflict with : port separator
   515  		ip = fmt.Sprintf("[%v]", ip)
   516  	}
   517  	addr := fmt.Sprintf("%v:26656", ip)
   518  	if withID {
   519  		addr = fmt.Sprintf("%x@%v", n.NodeKey.PubKey().Address().Bytes(), addr)
   520  	}
   521  	return addr
   522  }
   523  
   524  // Address returns an RPC endpoint address for the node.
   525  func (n Node) AddressRPC() string {
   526  	ip := n.IP.String()
   527  	if n.IP.To4() == nil {
   528  		// IPv6 addresses must be wrapped in [] to avoid conflict with : port separator
   529  		ip = fmt.Sprintf("[%v]", ip)
   530  	}
   531  	return fmt.Sprintf("%v:26657", ip)
   532  }
   533  
   534  // Client returns an RPC client for a node.
   535  func (n Node) Client() (*rpchttp.HTTP, error) {
   536  	return rpchttp.New(fmt.Sprintf("http://127.0.0.1:%v", n.ProxyPort), "/websocket")
   537  }
   538  
   539  // Stateless returns true if the node is either a seed node or a light node
   540  func (n Node) Stateless() bool {
   541  	return n.Mode == ModeLight || n.Mode == ModeSeed
   542  }
   543  
   544  // keyGenerator generates pseudorandom Ed25519 keys based on a seed.
   545  type keyGenerator struct {
   546  	random *rand.Rand
   547  }
   548  
   549  func newKeyGenerator(seed int64) *keyGenerator {
   550  	return &keyGenerator{
   551  		random: rand.New(rand.NewSource(seed)), //nolint:gosec
   552  	}
   553  }
   554  
   555  func (g *keyGenerator) Generate(keyType string) crypto.PrivKey {
   556  	seed := make([]byte, ed25519.SeedSize)
   557  
   558  	_, err := io.ReadFull(g.random, seed)
   559  	if err != nil {
   560  		panic(err) // this shouldn't happen
   561  	}
   562  	switch keyType {
   563  	case "secp256k1":
   564  		return secp256k1.GenPrivKeySecp256k1(seed)
   565  	case "", "ed25519":
   566  		return ed25519.GenPrivKeyFromSecret(seed)
   567  	default:
   568  		panic("KeyType not supported") // should not make it this far
   569  	}
   570  }
   571  
   572  // portGenerator generates local Docker proxy ports for each node.
   573  type portGenerator struct {
   574  	nextPort uint32
   575  }
   576  
   577  func newPortGenerator(firstPort uint32) *portGenerator {
   578  	return &portGenerator{nextPort: firstPort}
   579  }
   580  
   581  func (g *portGenerator) Next() uint32 {
   582  	port := g.nextPort
   583  	g.nextPort++
   584  	if g.nextPort == 0 {
   585  		panic("port overflow")
   586  	}
   587  	return port
   588  }
   589  
   590  // ipGenerator generates sequential IP addresses for each node, using a random
   591  // network address.
   592  type ipGenerator struct {
   593  	network *net.IPNet
   594  	nextIP  net.IP
   595  }
   596  
   597  func newIPGenerator(network *net.IPNet) *ipGenerator {
   598  	nextIP := make([]byte, len(network.IP))
   599  	copy(nextIP, network.IP)
   600  	gen := &ipGenerator{network: network, nextIP: nextIP}
   601  	// Skip network and gateway addresses
   602  	gen.Next()
   603  	gen.Next()
   604  	return gen
   605  }
   606  
   607  func (g *ipGenerator) Network() *net.IPNet {
   608  	n := &net.IPNet{
   609  		IP:   make([]byte, len(g.network.IP)),
   610  		Mask: make([]byte, len(g.network.Mask)),
   611  	}
   612  	copy(n.IP, g.network.IP)
   613  	copy(n.Mask, g.network.Mask)
   614  	return n
   615  }
   616  
   617  func (g *ipGenerator) Next() net.IP {
   618  	ip := make([]byte, len(g.nextIP))
   619  	copy(ip, g.nextIP)
   620  	for i := len(g.nextIP) - 1; i >= 0; i-- {
   621  		g.nextIP[i]++
   622  		if g.nextIP[i] != 0 {
   623  			break
   624  		}
   625  	}
   626  	return ip
   627  }