github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/apps/mixnet/router.go (about) 1 // Copyright (c) 2015, Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mixnet 16 17 import ( 18 "crypto/rand" 19 "crypto/tls" 20 "crypto/x509" 21 "crypto/x509/pkix" 22 "encoding/binary" 23 "errors" 24 "io" 25 "log" 26 "net" 27 "strings" 28 "sync" 29 "time" 30 31 "golang.org/x/crypto/nacl/box" 32 33 "github.com/golang/glog" 34 "github.com/golang/protobuf/proto" 35 "github.com/jlmucb/cloudproxy/go/tao" 36 ) 37 38 // RouterContext stores the runtime environment for a Tao-delegated router. 39 type RouterContext struct { 40 keys *tao.Keys // Signing keys of this hosted program. 41 domain *tao.Domain // Policy guard and public key. 42 listener net.Listener // Socket where server listens for proxies/routers 43 publicKey *[32]byte 44 privateKey *[32]byte 45 46 addr string 47 48 // Data structures for queueing and batching messages 49 queue *Queue 50 proxyReq *Queue 51 proxyResp *Queue 52 53 // Connections to next hop routers 54 conns map[string]*Conn 55 // Maps circuit id to circuits 56 circuits map[uint64]*Circuit 57 // Used to check duplicate connection ids 58 connIds struct { 59 sync.Mutex 60 m map[uint32]bool 61 } 62 63 // address of the directories 64 directories []string 65 // list of available servers and their keys for exit encryption 66 dirLock *sync.Mutex 67 directory []string 68 serverKeys [][]byte 69 70 mapLock *sync.RWMutex 71 72 // The queues and error handlers are instantiated as go routines; these 73 // channels are for tearing them down. 74 killQueue chan bool 75 killQueueErrorHandler chan bool 76 77 network string // Network protocol, e.g. "tcp" 78 timeout time.Duration // Timeout on read/write/dial. 79 80 errs chan error 81 done chan bool 82 } 83 84 // NewRouterContext generates new keys, loads a local domain configuration from 85 // path and binds an anonymous listener socket to addr using network protocol. 86 // It also creates a regular listener socket for other routers to connect to. 87 // A delegation is requested from the Tao t which is nominally 88 // the parent of this hosted program. 89 func NewRouterContext(path, network, addr string, timeout time.Duration, 90 directories []string, batchSize int, 91 x509Identity *pkix.Name, t tao.Tao) (r *RouterContext, err error) { 92 93 r = new(RouterContext) 94 r.network = network 95 r.timeout = timeout 96 97 r.addr = addr 98 port := addr 99 if addr != "127.0.0.1:0" { 100 port = ":" + strings.Split(addr, ":")[1] 101 } 102 103 r.conns = make(map[string]*Conn) 104 r.circuits = make(map[uint64]*Circuit) 105 r.connIds = struct { 106 sync.Mutex 107 m map[uint32]bool 108 }{m: make(map[uint32]bool)} 109 110 r.dirLock = new(sync.Mutex) 111 r.mapLock = new(sync.RWMutex) 112 113 r.errs = make(chan error) 114 r.done = make(chan bool) 115 116 // Generate keys and get attestation from parent. 117 if r.keys, err = tao.NewTemporaryTaoDelegatedKeys(tao.Signing|tao.Crypting, t); err != nil { 118 return nil, err 119 } 120 121 // Create a certificate. 122 pkInt := tao.PublicKeyAlgFromSignerAlg(*r.keys.SigningKey.Header.KeyType) 123 sigInt := tao.SignatureAlgFromSignerAlg(*r.keys.SigningKey.Header.KeyType) 124 r.keys.Cert, err = r.keys.SigningKey.CreateSelfSignedX509(pkInt, sigInt, int64(1), x509Identity) 125 if err != nil { 126 return nil, err 127 } 128 129 // Load domain from local configuration. 130 if r.domain, err = tao.LoadDomain(path, nil); err != nil { 131 return nil, err 132 } 133 134 // Encode TLS certificate. 135 cert, err := tao.EncodeTLSCert(r.keys) 136 if err != nil { 137 return nil, err 138 } 139 140 tlsConfig := &tls.Config{ 141 RootCAs: x509.NewCertPool(), 142 Certificates: []tls.Certificate{*cert}, 143 InsecureSkipVerify: true, 144 ClientAuth: tls.RequestClientCert, 145 } 146 147 if r.listener, err = Listen(network, port, tlsConfig, 148 r.domain.Guard, r.domain.Keys.VerifyingKey, r.keys.Delegation); err != nil { 149 return nil, err 150 } 151 152 // NaCl keys 153 r.publicKey, r.privateKey, err = box.GenerateKey(rand.Reader) 154 if err != nil { 155 return nil, err 156 } 157 158 r.directories = directories 159 r.register() 160 go r.updateDirectory() 161 162 // Instantiate the queues. 163 r.queue = NewQueue(network, t, batchSize, timeout) 164 r.proxyReq = NewQueue(network, t, batchSize, timeout) 165 r.proxyResp = NewQueue(network, t, batchSize, timeout) 166 r.killQueue = make(chan bool) 167 r.killQueueErrorHandler = make(chan bool) 168 go r.queue.DoQueue(r.killQueue) 169 go r.proxyReq.DoQueue(r.killQueue) 170 go r.proxyResp.DoQueue(r.killQueue) 171 go r.queue.DoQueueErrorHandler(r.queue, r.killQueueErrorHandler) 172 go r.proxyReq.DoQueueErrorHandler(r.queue, r.killQueueErrorHandler) 173 go r.proxyResp.DoQueueErrorHandler(r.queue, r.killQueueErrorHandler) 174 175 return r, nil 176 } 177 178 func (r *RouterContext) register() { 179 if r.addr == "127.0.0.1:0" { 180 r.addr = r.listener.Addr().String() 181 } 182 for _, dirAddr := range r.directories { 183 err := r.Register(dirAddr) 184 if err != nil { 185 log.Println("Register err:", err) 186 } 187 } 188 } 189 190 func (r *RouterContext) directoryConsensus() []string { 191 r.dirLock.Lock() 192 defer r.dirLock.Unlock() 193 for _, dirAddr := range r.directories { 194 directory, keys, err := r.GetDirectory(dirAddr) 195 if err != nil { 196 log.Println("GetDirectory err:", err) 197 } 198 // TODO(kwonalbert): Check directory consensus 199 r.directory = directory 200 r.serverKeys = keys 201 } 202 directory := make([]string, len(r.directory)) 203 copy(directory, r.directory) 204 return directory 205 } 206 207 func (r *RouterContext) updateDirectory() { 208 for { 209 r.directoryConsensus() 210 time.Sleep(DefaultUpdateFrequency) 211 } 212 } 213 214 // AcceptRouter Waits for connectons from other routers. 215 func (r *RouterContext) Accept() (*Conn, error) { 216 c, err := r.listener.Accept() 217 if err != nil { 218 return nil, err 219 } 220 id, err := r.newConnID() 221 if err != nil { 222 return nil, err 223 } 224 conn := &Conn{c, id, r.timeout, make(map[uint64]*Circuit), new(sync.RWMutex), true} 225 if len(c.(*tls.Conn).ConnectionState().PeerCertificates) > 0 { 226 conn.withProxy = false 227 } 228 go r.handleConn(conn) 229 return conn, nil 230 } 231 232 // DialRouter connects to a remote Tao-delegated mixnet router. 233 func (r *RouterContext) DialRouter(network, addr string) (*Conn, error) { 234 c, err := tao.Dial(network, addr, r.domain.Guard, r.domain.Keys.VerifyingKey, r.keys) 235 if err != nil { 236 return nil, err 237 } 238 id, err := r.newConnID() 239 if err != nil { 240 return nil, err 241 } 242 conn := &Conn{c, id, r.timeout, make(map[uint64]*Circuit), new(sync.RWMutex), false} 243 r.conns[addr] = conn 244 go r.handleConn(conn) 245 return conn, nil 246 } 247 248 // Register the current router to a directory server 249 func (r *RouterContext) Register(dirAddr string) error { 250 c, err := tao.Dial(r.network, dirAddr, r.domain.Guard, r.domain.Keys.VerifyingKey, r.keys) 251 if err != nil { 252 return err 253 } 254 err = RegisterRouter(c, []string{r.addr}, [][]byte{(*r.publicKey)[:]}) 255 if err != nil { 256 return err 257 } 258 return c.Close() 259 260 } 261 262 // Read the directory from a directory server 263 func (r *RouterContext) GetDirectory(dirAddr string) ([]string, [][]byte, error) { 264 c, err := tao.Dial(r.network, dirAddr, r.domain.Guard, r.domain.Keys.VerifyingKey, r.keys) 265 if err != nil { 266 return nil, nil, err 267 } 268 directory, keys, err := GetDirectory(c) 269 if err != nil { 270 return nil, nil, err 271 } 272 return directory, keys, c.Close() 273 } 274 275 func (r *RouterContext) DeleteRouter() { 276 dm := &DirectoryMessage{ 277 Type: DirectoryMessageType_DELETE.Enum(), 278 Addrs: []string{r.addr}, 279 Keys: nil, 280 } 281 b, err := proto.Marshal(dm) 282 if err != nil { 283 log.Println(err) 284 } 285 286 for _, dirAddr := range r.directories { 287 c, err := tao.Dial(r.network, dirAddr, r.domain.Guard, r.domain.Keys.VerifyingKey, r.keys) 288 if err != nil { 289 log.Println(err) 290 } 291 _, err = c.Write(b) 292 if err != nil { 293 log.Println(err) 294 } 295 c.Read([]byte{0}) 296 } 297 } 298 299 // Close releases any resources held by the hosted program. 300 func (r *RouterContext) Close() { 301 r.killQueue <- true 302 r.killQueue <- true 303 r.killQueue <- true 304 r.killQueueErrorHandler <- true 305 r.killQueueErrorHandler <- true 306 r.killQueueErrorHandler <- true 307 if r.listener != nil { 308 r.listener.Close() 309 } 310 for _, conn := range r.conns { 311 r.done <- true 312 for _, circuit := range conn.circuits { 313 circuit.Close() 314 } 315 conn.Close() 316 } 317 r.DeleteRouter() 318 } 319 320 // Return a random circuit ID 321 func (r *RouterContext) newID() (uint64, error) { 322 id := uint64(0) 323 ok := true 324 // Reserve ids < 2^32 to connection ids 325 for ok || id < (1<<32) { 326 b := make([]byte, 8) 327 if _, err := rand.Read(b); err != nil { 328 return 0, err 329 } 330 id = binary.LittleEndian.Uint64(b) 331 _, ok = r.circuits[id] 332 } 333 return id, nil 334 } 335 336 // Return a random connection ID 337 func (r *RouterContext) newConnID() (uint32, error) { 338 r.connIds.Lock() 339 var id uint32 340 ok := true 341 for ok { 342 b := make([]byte, 8) 343 if _, err := rand.Read(b); err != nil { 344 return 0, err 345 } 346 id = binary.LittleEndian.Uint32(b) 347 _, ok = r.connIds.m[id] 348 } 349 r.connIds.m[id] = true 350 r.connIds.Unlock() 351 return id, nil 352 } 353 354 // Handle errors internal to the router 355 // When instantiating a real router (not for testing), 356 // one start this function as well to handle the errors 357 func (r *RouterContext) HandleErr() { 358 for { 359 err := <-r.errs 360 if err != nil { 361 // TODO(kwonalbert) Handle errors properly 362 glog.Error("Router err:", err) 363 } 364 } 365 } 366 367 // handleConn reads a directive or a message from a proxy. The directives 368 // are handled here, but actual messages are handled in handleMessages 369 func (r *RouterContext) handleConn(c *Conn) { 370 for { 371 var err error 372 cell := make([]byte, CellBytes) 373 if _, err = c.Read(cell); err != nil { 374 if err == io.EOF { 375 break 376 } else { 377 select { 378 case <-r.done: // Indicate this is done 379 case r.errs <- err: 380 } 381 break 382 } 383 } 384 385 id := getID(cell) 386 r.mapLock.RLock() 387 sendQ, respQ := r.queue, r.queue 388 circ, ok := r.circuits[id] 389 var nextCirc *Circuit 390 var sId, rId uint64 391 if ok { 392 nextCirc = circ.next 393 sId = nextCirc.id 394 if circ.entry { 395 sendQ = r.proxyReq 396 respQ = r.proxyResp 397 } 398 if nextCirc.entry { 399 respQ = r.proxyResp 400 rId = uint64(nextCirc.conn.id) 401 } else { 402 rId = nextCirc.id 403 } 404 } 405 if c.withProxy { 406 sId, rId = uint64(c.id), uint64(c.id) 407 } 408 r.mapLock.RUnlock() 409 410 if cell[TYPE] == msgCell { 411 if !circ.exit { // if it's not exit, just relay the cell 412 binary.LittleEndian.PutUint64(cell[ID:], nextCirc.id) 413 if !circ.forward { 414 sendQ.EnqueueMsg(sId, cell, nextCirc.conn, c) 415 } else { 416 respQ.EnqueueMsg(rId, cell, nextCirc.conn, c) 417 } 418 } else { // actually handle the message 419 c.GetCircuit(id).BufferCell(cell, err) 420 } 421 } else if cell[TYPE] == dirCell { // Handle a directive. 422 var d Directive 423 if err = unmarshalDirective(cell, &d); err != nil { 424 r.errs <- err 425 break 426 } 427 428 // relay the errors back to users 429 if *d.Type == DirectiveType_ERROR { 430 binary.LittleEndian.PutUint64(cell[ID:], nextCirc.id) 431 respQ.EnqueueMsg(rId, cell, nextCirc.conn, c) 432 } else if *d.Type == DirectiveType_CREATE { 433 err := r.handleCreate(d, c, id, sendQ, respQ, sId, rId) 434 if err != nil { 435 r.errs <- err 436 break 437 } 438 } else if *d.Type == DirectiveType_DESTROY { 439 err := r.handleDestroy(d, c, circ, sendQ, respQ, sId, rId) 440 if err != nil { 441 r.errs <- err 442 break 443 } 444 } else if *d.Type == DirectiveType_CREATED { 445 // Simply relay created back 446 cell, err = marshalDirective(nextCirc.id, dirCreated) 447 if err != nil { 448 r.errs <- err 449 break 450 } 451 respQ.EnqueueMsg(rId, cell, nextCirc.conn, c) 452 } else if *d.Type == DirectiveType_DESTROYED { 453 cell, err = marshalDirective(nextCirc.id, dirDestroyed) 454 if err != nil { 455 r.errs <- err 456 break 457 } 458 empty := r.delete(c, circ) 459 // Close the forward circuit if it's an exit or empty now 460 // Relay back destroyed 461 sendQ.Close(sId, nil, empty, c, nextCirc.conn) 462 respQ.Close(rId, cell, empty, nextCirc.conn, nil) 463 if empty { 464 break 465 } 466 } 467 } else { // Unknown cell type, return an error. 468 if err = r.SendError(respQ, rId, id, errBadCellType, c); err != nil { 469 r.errs <- err 470 break 471 } 472 } 473 } 474 } 475 476 func member(s string, set []string) bool { 477 for _, member := range set { 478 if member == s { 479 return true 480 } 481 } 482 return false 483 } 484 485 // handleCreated handles the create directive by either relaying it on 486 // (which opens a new connection), or sending back created directive 487 // if this is an exit. 488 func (r *RouterContext) handleCreate(d Directive, c *Conn, id uint64, 489 sendQ, respQ *Queue, sId, rId uint64) error { 490 if c.withProxy { 491 // Get the most recent directory before forming a circuit 492 directory := r.directoryConsensus() 493 494 if len(directory) < len(d.Addrs)-1 { 495 err := errors.New("Not enough servers online") 496 if e := r.SendError(respQ, rId, id, err, c); e != nil { 497 return e 498 } 499 } 500 501 // A fresh path of the same length if user has no preference 502 // (Random selection without replacement) 503 for _, router := range d.Addrs { 504 for i, addr := range directory { 505 if addr == router { 506 directory[i] = directory[len(directory)-1] 507 directory = directory[:len(directory)-1] 508 break 509 } 510 } 511 } 512 for i := 1; i < len(d.Addrs)-1; i++ { 513 if d.Addrs[i] != "" { 514 // TODO(kwonalbert) Currently, we allow the 515 // clients to pick the path, if they want, 516 // mostly for testing. But for better security, 517 // we should not allows this for final version. 518 continue 519 } 520 b := make([]byte, LEN_SIZE) 521 if _, err := rand.Read(b); err != nil { 522 return err 523 } 524 idx := int(binary.LittleEndian.Uint32(b)) % len(directory) 525 d.Addrs[i] = directory[idx] 526 directory[idx] = directory[len(directory)-1] 527 directory = directory[:len(directory)-1] 528 } 529 } 530 531 r.mapLock.Lock() 532 defer r.mapLock.Unlock() 533 534 newId, err := r.newID() 535 if err != nil { 536 return err 537 } 538 circuit := NewCircuit(c, id, c.withProxy, false, false) 539 newCirc := NewCircuit(nil, newId, false, false, true) 540 circuit.next = newCirc 541 newCirc.next = circuit 542 543 c.AddCircuit(circuit) 544 r.circuits[id] = circuit 545 546 // Add next hop for this circuit to queue and send a CREATED 547 // directive to sender to inform the sender. 548 relayIdx := -1 549 for i, addr := range d.Addrs { 550 if addr == r.addr { 551 relayIdx = i 552 } 553 } 554 if relayIdx != len(d.Addrs)-2 { // last addr is the final dest, so check -2 555 // Relay the CREATE message 556 if err != nil { 557 return err 558 } 559 var nextConn *Conn 560 if _, ok := r.conns[d.Addrs[relayIdx+1]]; !ok { 561 nextConn, err = r.DialRouter(r.network, d.Addrs[relayIdx+1]) 562 if err != nil { 563 if e := r.SendError(respQ, rId, id, err, c); e != nil { 564 return e 565 } 566 } 567 } else { 568 nextConn = r.conns[d.Addrs[relayIdx+1]] 569 } 570 newCirc.conn = nextConn 571 nextConn.AddCircuit(newCirc) 572 r.circuits[newId] = newCirc 573 574 nextCell, err := marshalDirective(newId, &d) 575 if err != nil { 576 return err 577 } 578 // middle node, then just queue to the generic queue, not one of the proxy queue 579 if !c.withProxy { 580 sId = newId 581 } 582 sendQ.EnqueueMsg(sId, nextCell, nextConn, c) 583 } else { 584 // Response id should be just id here not rId if it's not an entry 585 var key [32]byte 586 copy(key[:], d.Key) 587 circuit.exit = true 588 circuit.SetKeys(&key, r.publicKey, r.privateKey) 589 if !c.withProxy { 590 sId = newId 591 rId = id 592 } 593 594 dest, ok := circuit.Decrypt([]byte(d.Addrs[len(d.Addrs)-1])) 595 if !ok { 596 log.Fatal("Misauthenticated ciphertext") 597 } 598 go r.handleMessage(string(dest), circuit, sendQ, respQ, sId, rId) 599 // Tell the previous hop (proxy or router) it's created 600 cell, err := marshalDirective(id, dirCreated) 601 if err != nil { 602 return err 603 } 604 respQ.EnqueueMsg(rId, cell, c, nil) 605 } 606 return nil 607 } 608 609 // handleDestroy handles the destroy directive by either relaying it on, 610 // or sending back destroyed directive if this is an exit 611 func (r *RouterContext) handleDestroy(d Directive, c *Conn, circ *Circuit, 612 sendQ, respQ *Queue, sId, rId uint64) error { 613 // Close the connection if you are an exit for this circuit 614 615 if !c.Member(circ.id) { 616 return errors.New("Cannot destroy a circuit that does not belong to the connection") 617 } 618 619 if circ.exit { 620 // Send back destroyed msg 621 cell, err := marshalDirective(circ.id, dirDestroyed) 622 if err != nil { 623 return err 624 } 625 if circ.next.conn != nil { 626 circ.next.conn.Close() 627 } 628 empty := r.delete(c, circ) // there is not previous id for this, so delete id 629 respQ.Close(rId, cell, empty, circ.conn, circ.conn) 630 } else { 631 nextCell, err := marshalDirective(circ.next.id, dirDestroy) 632 if err != nil { 633 return err 634 } 635 sendQ.EnqueueMsg(sId, nextCell, circ.next.conn, circ.conn) 636 } 637 return nil 638 } 639 640 // handleMessages reconstructs the full message at the exit node, and sends it 641 // out to the final destination. The directives are handled in handleConn. 642 func (r *RouterContext) handleMessage(dest string, circ *Circuit, 643 sendQ, respQ *Queue, sId, rId uint64) { 644 var conn *Conn = nil 645 var netConn net.Conn = nil 646 for { 647 msg, err := circ.ReceiveMessage() 648 if err == io.EOF { 649 break 650 } else if err != nil { 651 if err = r.SendError(respQ, rId, circ.id, err, circ.conn); err != nil { 652 r.errs <- err 653 } 654 continue 655 } 656 657 if conn == nil { // dial when you receive the first message to send 658 netConn, err = net.DialTimeout(r.network, dest, r.timeout) 659 if err != nil { 660 if err = r.SendError(respQ, rId, circ.id, err, circ.conn); err != nil { 661 r.errs <- err 662 } 663 continue 664 } 665 r.mapLock.Lock() 666 conn = &Conn{netConn, 0, r.timeout, make(map[uint64]*Circuit), new(sync.RWMutex), false} 667 circ.next.conn = conn 668 // Add the circuit to final dest to list of circuits 669 conn.AddCircuit(circ.next) 670 r.circuits[circ.next.id] = circ.next 671 r.mapLock.Unlock() 672 // Create handler for responses from the destination 673 go r.handleResponse(netConn, circ, respQ, rId) 674 } 675 sendQ.EnqueueMsg(sId, msg, netConn, circ.conn) 676 } 677 if conn != nil { 678 conn.Close() 679 conn.DeleteCircuit(circ.next) 680 } 681 } 682 683 // handleResponse receives a message from the final destination, breaks it down 684 // into cells, and sends it back to the user 685 func (r *RouterContext) handleResponse(conn net.Conn, respCirc *Circuit, queue *Queue, queueId uint64) { 686 for { 687 resp := make([]byte, MaxMsgBytes+1) 688 conn.SetDeadline(time.Now().Add(r.timeout)) 689 n, e := conn.Read(resp) 690 if e == io.EOF { 691 // the connection closed 692 return 693 } else if e != nil { 694 if strings.Contains(e.Error(), "closed network connection") { 695 // TODO(kwonalbert) Currently, this is a hack to 696 // avoid sending unnecesary error back to user, 697 // since Read returns this error when the conn 698 // closes while it's constructing response cells 699 return 700 } else { 701 r.SendError(queue, queueId, respCirc.id, e, respCirc.conn) 702 return 703 } 704 } else if n > MaxMsgBytes { 705 r.SendError(queue, queueId, respCirc.id, errors.New("Response message too long"), respCirc.conn) 706 return 707 } 708 709 cell := make([]byte, CellBytes) 710 binary.LittleEndian.PutUint64(cell[ID:], respCirc.id) 711 712 cell[TYPE] = msgCell 713 714 bodies := make(chan []byte) 715 go breakMessages(resp[:n], bodies) 716 for { 717 body, ok := <-bodies 718 if !ok { 719 break 720 } 721 boxed := respCirc.Encrypt(body) 722 copy(cell[BODY:], boxed) 723 queue.EnqueueMsg(queueId, cell, respCirc.conn, nil) 724 } 725 } 726 } 727 728 // SendError sends an error message to a client. 729 func (r *RouterContext) SendError(queue *Queue, queueId, id uint64, err error, c *Conn) error { 730 var d Directive 731 d.Type = DirectiveType_ERROR.Enum() 732 d.Error = proto.String(err.Error()) 733 cell, err := marshalDirective(id, &d) 734 if err != nil { 735 return err 736 } 737 queue.EnqueueMsg(queueId, cell, c, nil) 738 return nil 739 } 740 741 func (r *RouterContext) delete(c *Conn, circuit *Circuit) bool { 742 r.mapLock.Lock() 743 delete(r.circuits, circuit.id) 744 delete(r.circuits, circuit.next.id) 745 empty := c.DeleteCircuit(circuit) 746 if empty { 747 delete(r.conns, c.RemoteAddr().String()) 748 } 749 r.mapLock.Unlock() 750 return empty 751 }