lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xnet/pipenet/pipenet.go (about)

     1  // Copyright (C) 2017-2021  Nexedi SA and Contributors.
     2  //                          Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  // Package pipenet provides TCP-like synchronous in-memory network of net.Pipes.
    21  //
    22  // Addresses on pipenet are host:port pairs. A host is xnet.Networker and so
    23  // can be worked with similarly to regular TCP network with Dial/Listen/Accept/...
    24  //
    25  // Example:
    26  //
    27  //	net := pipenet.New("")
    28  //	h1 := net.Host("abc")
    29  //	h2 := net.Host("def")
    30  //
    31  //	l, err := h1.Listen(ctx, ":10")     // starts listening on address "abc:10"
    32  //	go func() {
    33  //		csrv, err := l.Accept(ctx)  // csrv will have LocalAddr "abc:1"
    34  //	}()
    35  //	ccli, err := h2.Dial(ctx, "abc:10") // ccli will be connection between "def:1" - "abc:1"
    36  //
    37  // Pipenet might be handy for testing interaction of networked applications in 1
    38  // process without going to OS networking stack.
    39  //
    40  // See also package lab.nexedi.com/kirr/go123/xnet/lonet for similar network
    41  // that can work across several OS-level processes.
    42  package pipenet
    43  
    44  import (
    45  	"context"
    46  	"fmt"
    47  	"net"
    48  	"sync"
    49  
    50  	"github.com/pkg/errors"
    51  
    52  	"lab.nexedi.com/kirr/go123/xnet/virtnet"
    53  )
    54  
    55  const netPrefix = "pipe" // pipenet package creates only "pipe*" networks
    56  
    57  // Network implements synchronous in-memory TCP-like network of pipes.
    58  type Network struct {
    59  	vnet    *virtnet.SubNetwork
    60  	vnotify virtnet.Notifier
    61  }
    62  
    63  // vengine implements virtnet.Engine for Network.
    64  type vengine struct {
    65  	network *Network
    66  }
    67  
    68  // ramRegistry implements virtnet.Registry in RAM.
    69  //
    70  // Pipenet does not need a registry but virtnet is built for general case which
    71  // needs one.
    72  //
    73  // Essentially it works as map protected by mutex.
    74  type ramRegistry struct {
    75  	name string
    76  
    77  	mu      sync.Mutex
    78  	hostTab map[string]string // hostname -> hostdata
    79  	closed  bool              // 1 after Close
    80  }
    81  
    82  // New creates new pipenet Network.
    83  //
    84  // Name is name of this network under "pipe" namespace, e.g. "α" will give full
    85  // network name "pipeα".
    86  //
    87  // New does not check whether network name provided is unique.
    88  func New(name string) *Network {
    89  	netname := netPrefix + name
    90  	n := &Network{}
    91  	v := &vengine{n}
    92  	r := newRAMRegistry(fmt.Sprintf("ram(%s)", netname))
    93  	subnet, vnotify := virtnet.NewSubNetwork(netname, v, r)
    94  	n.vnet = subnet
    95  	n.vnotify = vnotify
    96  	return n
    97  }
    98  
    99  // AsVirtNet exposes Network as virtnet subnetwork.
   100  //
   101  // Since pipenet works entirely in RAM and in 1 OS process, its user interface
   102  // is simpler compared to more general virtnet - for example there is no error
   103  // when creating hosts. However sometimes it is handy to get access to pipenet
   104  // network via full virtnet interface, when the code that is using pipenet
   105  // network does not want to depend on pipenet API specifics.
   106  func AsVirtNet(n *Network) *virtnet.SubNetwork {
   107  	return n.vnet
   108  }
   109  
   110  // Network returns name of the network.
   111  func (n *Network) Network() string {
   112  	return n.vnet.Network()
   113  }
   114  
   115  // Host returns network access point by name.
   116  //
   117  // If there was no such host before it creates new one.
   118  //
   119  // Host panics if underlying virtnet subnetwork was shut down.
   120  func (n *Network) Host(name string) *virtnet.Host {
   121  	// check if it is already there
   122  	host := n.vnet.Host(name)
   123  	if host != nil {
   124  		return host
   125  	}
   126  
   127  	// if not - create it. Creation will not block.
   128  	host, err := n.vnet.NewHost(context.Background(), name)
   129  	if host != nil {
   130  		return host
   131  	}
   132  
   133  	// the only way we could get error here is due to either someone else
   134  	// making the host in parallel to us, or virtnet shutdown.
   135  	switch errors.Cause(err) {
   136  	case virtnet.ErrHostDup:
   137  		// ok
   138  	case virtnet.ErrNetDown:
   139  		panic(err)
   140  
   141  	default:
   142  		panic(fmt.Sprintf("pipenet: NewHost failed not due to dup or shutdown: %s", err))
   143  	}
   144  
   145  	// if it was dup - we should be able to get it.
   146  	//
   147  	// even if dup.Close is called in the meantime it will mark the host as
   148  	// down, but won't remove it from vnet .hostMap.
   149  	host = n.vnet.Host(name)
   150  	if host == nil {
   151  		panic("pipenet: NewHost said host already is there, but it was not found")
   152  	}
   153  
   154  	return host
   155  }
   156  
   157  // VNetNewHost implements virtnet.Engine .
   158  func (v *vengine) VNetNewHost(ctx context.Context, hostname string, registry virtnet.Registry) error {
   159  	// for pipenet there is neither need to create host resources, nor need
   160  	// to keep any hostdata.
   161  	return registry.Announce(ctx, hostname, "")
   162  }
   163  
   164  // VNetDial implements virtnet dialing for pipenet.
   165  //
   166  // Simply create pipe pair and send one end directly to virtnet acceptor.
   167  func (v *vengine) VNetDial(ctx context.Context, src, dst *virtnet.Addr, _ string) (_ net.Conn, addrAccept *virtnet.Addr, _ error) {
   168  	pc, ps := net.Pipe()
   169  	accept, err := v.network.vnotify.VNetAccept(ctx, src, dst, ps)
   170  	if err != nil {
   171  		pc.Close()
   172  		ps.Close()
   173  		return nil, nil, err
   174  	}
   175  
   176  	accept.Ack <- nil
   177  	return pc, accept.Addr, nil
   178  }
   179  
   180  // Close implements virtnet.Engine .
   181  func (v *vengine) Close() error {
   182  	return nil // nop: there is no underlying resources to release.
   183  }
   184  
   185  
   186  
   187  // Announce implements virtnet.Registry .
   188  func (r *ramRegistry) Announce(ctx context.Context, hostname, hostdata string) (err error) {
   189  	defer r.regerr(&err, "announce", hostname, hostdata)
   190  
   191  	r.mu.Lock()
   192  	defer r.mu.Unlock()
   193  
   194  	if r.closed {
   195  		return virtnet.ErrRegistryDown
   196  	}
   197  
   198  	if _, already := r.hostTab[hostname]; already {
   199  		return virtnet.ErrHostDup
   200  	}
   201  
   202  	r.hostTab[hostname] = hostdata
   203  	return nil
   204  }
   205  
   206  // Query implements virtnet.Registry .
   207  func (r *ramRegistry) Query(ctx context.Context, hostname string) (hostdata string, err error) {
   208  	defer r.regerr(&err, "query", hostname)
   209  
   210  	r.mu.Lock()
   211  	defer r.mu.Unlock()
   212  
   213  	if r.closed {
   214  		return "", virtnet.ErrRegistryDown
   215  	}
   216  
   217  	hostdata, ok := r.hostTab[hostname]
   218  	if !ok {
   219  		return "", virtnet.ErrNoHost
   220  	}
   221  
   222  	return hostdata, nil
   223  }
   224  
   225  // Close implements virtnet.Registry .
   226  func (r *ramRegistry) Close() error {
   227  	r.mu.Lock()
   228  	defer r.mu.Unlock()
   229  
   230  	r.closed = true
   231  	return nil
   232  }
   233  
   234  func newRAMRegistry(name string) *ramRegistry {
   235  	return &ramRegistry{name: name, hostTab: make(map[string]string)}
   236  }
   237  
   238  // regerr is syntactic sugar to wrap !nil *errp into RegistryError.
   239  //
   240  // intended too be used like
   241  //
   242  //	defer r.regerr(&err, "operation", arg1, arg2, ...)
   243  func (r *ramRegistry) regerr(errp *error, op string, args ...interface{}) {
   244  	if *errp == nil {
   245  		return
   246  	}
   247  
   248  	*errp = &virtnet.RegistryError{
   249  		Registry: r.name,
   250  		Op:       op,
   251  		Args:     args,
   252  		Err:      *errp,
   253  	}
   254  }