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 }