func (server *Server) Listen(addr string) error { laddr, err := net.ResolveUDPAddr("udp", addr) if err != nil { return err } conn, err := net.ListenUDP("udp", laddr) if err != nil { return err } newPacket := make(chan packet) go func() { buffer := make([]byte, 4096) for { n, raddr, err := conn.ReadFromUDP(buffer) if err != nil { log.Println(err) continue } newPacket <- packet{addr: raddr, data: buffer[:n]} } }() services := make(map[string]*Service) tunnelsByAddr := make(map[string]*tunnel) tunnelsByTID := make(map[uint64]*tunnel) for { select { case importRequest := <-server.importRequest: service := services[importRequest.serviceName] if service == nil { importRequest.fail <- errors.New("No service by that name") } else { pendingImports := service.pendingImports go func() { pendingImports <- importRequest }() } case outgoing := <-server.outgoing: tunnel := tunnelsByTID[outgoing.tid] if tunnel == nil { log.Println("No tunnel", outgoing.tid) continue } c := tunnel.conns[outgoing.cid] if c == nil { log.Println("No conn", outgoing.cid) continue } nonce := tunnel.lastNonce.Next() tunnel.lastNonce = nonce messageWithCID := make([]byte, 8, 8+len(outgoing.data)) binary.LittleEndian.PutUint64(messageWithCID[0:8], uint64(outgoing.cid)) messageWithCID = append(messageWithCID, outgoing.data...) packet := buildPacket(tunnel.boxer, outgoing.tid, nonce, nil, nil, messageWithCID) if _, err := conn.WriteTo(packet, tunnel.addr); err != nil { outgoing.done <- err } else { outgoing.done <- nil } case packet := <-newPacket: // Puzzle is unused at the moment. tid, nonce, remotePk, _, boxedMessage := parsePacket(packet.data) tunnel := tunnelsByTID[tid] if tunnel == nil && remotePk != nil { // Attempt to establish a tunnel with us. boxer := cryptonacl.NewBoxer(remotePk, server.SecretKey) nonce := nonce.Next() tunnel = createTunnel(tid, packet.addr, 0, nonce, boxer, server) tunnel.conns[0] = createConn(tunnel, 0) tunnelsByTID[tid] = tunnel } else if tunnel != nil && remotePk != nil { // The tunnel already exists, yet this packet seems to be trying to // make a connection again. It's most likely that this is a reincident // packet, so ignore it. log.Println("Dropping because tunnel already exists, but public key given") continue } messageWithCID, err := tunnel.boxer.Unbox(boxedMessage, nonce) if err != nil { // Drop the packet. log.Println("Could not unbox") continue } cid := binary.LittleEndian.Uint64(messageWithCID[0:8]) message := messageWithCID[8:] if cid == 0 { rpc := []json.RawMessage{} if err := json.Unmarshal(message, &rpc); err != nil { log.Println("err", err) continue } var command string if err := json.Unmarshal(rpc[0], &command); err != nil { log.Println("err", err) continue } args := rpc[1:] if command == "create" { var createCID uint64 var serviceName string if err := json.Unmarshal(args[0], &createCID); err != nil { log.Println("json.Unmarshal", err) continue } if err := json.Unmarshal(args[1], &serviceName); err != nil { log.Println("json.Unmarshal", err) continue } service := services[serviceName] if service == nil { log.Println("No service", serviceName) continue } conn := createConn(tunnel, createCID) buf, err := json.Marshal([]interface{}{"created", createCID}) if err != nil { log.Println("json.Marshal", err) continue } importRequest := <-service.pendingImports tunnel.conns[createCID] = conn importRequest.done <- conn go func() { if _, err := tunnel.conns[0].Write(buf); err != nil { return } }() } else if command == "created" { var createdCID uint64 if err := json.Unmarshal(args[0], &createdCID); err != nil { log.Println("json.Unmarshal", err) continue } req := tunnel.pendingConnections[createdCID] delete(tunnel.pendingConnections, createdCID) c := createConn(tunnel, createdCID) tunnel.conns[createdCID] = c req.done <- c } else { log.Println("Invalid command", command) continue } } else { conn := tunnel.conns[cid] if conn != nil { conn.inbox <- message } } case req := <-server.advertiseRequest: _, serviceExists := services[req.serviceName] if serviceExists { req.fail <- errors.New("Service already exists") } else { service := &Service{ ServiceName: req.serviceName, server: server, pendingImports: make(chan importRequest), } services[req.serviceName] = service req.done <- service } case req := <-server.ipcRequest: tunnel := tunnelsByAddr[req.addr.String()] if tunnel == nil { remotePk, err := server.Directory(req.addr) if err != nil { req.fail <- err continue } tid := createTID() localPk, localSk := cryptonacl.BoxKeypair() nonce := cryptonacl.CreateNonce(localPk, remotePk) boxer := cryptonacl.NewBoxer(remotePk, localSk) message, err := json.Marshal([]interface{}{"create", 1, req.serviceName}) if err != nil { req.fail <- err continue } messageWithCID := make([]byte, 8, 8+len(message)) binary.LittleEndian.PutUint64(messageWithCID[0:8], 0) messageWithCID = append(messageWithCID, message...) packet := buildPacket(boxer, tid, nonce, localPk, nil, messageWithCID) tunnel := createTunnel(tid, req.addr, 1, nonce, boxer, server) tunnel.conns[0] = createConn(tunnel, 0) if _, err := conn.WriteTo(packet, req.addr); err != nil { req.fail <- err continue } tunnel.pendingConnections[1] = &req // We can't even be sure yet if the tunnel is established; might need // a 'pending' mechanism like for connections. tunnelsByAddr[req.addr.String()] = tunnel tunnelsByTID[tid] = tunnel } else { cid := tunnel.lastCID + 1 nonce := tunnel.lastNonce.Next() tunnel.lastNonce = nonce tunnel.lastCID = cid message, err := json.Marshal([]interface{}{"create", cid, req.serviceName}) if err != nil { req.fail <- err continue } messageWithCID := make([]byte, 8+len(message)) binary.LittleEndian.PutUint64(messageWithCID[0:8], uint64(cid)) messageWithCID = append(messageWithCID, message...) packet := buildPacket(tunnel.boxer, tunnel.tid, nonce, nil, nil, messageWithCID) if _, err := conn.WriteTo(packet, tunnel.addr); err != nil { req.fail <- err continue } tunnel.pendingConnections[cid] = &req } case <-server.stop: return nil } } }
func TestConnection(t *testing.T) { alice := CreateServer() bob := CreateServer() aliceDone := make(chan error) bobDone := make(chan error) advertiseDone := make(chan error) ipcDone := make(chan error) alice.PublicKey, alice.SecretKey = cryptonacl.BoxKeypair() aliceAddr := "127.0.0.1:9123" bobAddr := "127.0.0.1:9124" bob.Directory = func(addr *net.UDPAddr) (cryptonacl.PublicKey, error) { if addr.String() == aliceAddr { return alice.PublicKey, nil } else { return nil, errors.New("Could not lookup host " + addr.String()) } } go func() { aliceDone <- alice.Listen(aliceAddr) }() go func() { bobDone <- bob.Listen(bobAddr) }() go func() { ping, err := alice.Advertise("ping") if err != nil { advertiseDone <- err return } conn, err := ping.Import() if err != nil { advertiseDone <- err return } buffer := make([]byte, len("ping")) if n, err := conn.Read(buffer); err != nil { advertiseDone <- err return } else if !bytes.Equal(buffer[:n], []byte("ping")) { advertiseDone <- errors.New(fmt.Sprintf("Expected ping, got %s", buffer[:n])) return } response := []byte("pong") if _, err := conn.Write(response); err != nil { advertiseDone <- err return } advertiseDone <- nil }() go func() { conn, err := bob.IPC("ping", aliceAddr) if err != nil { ipcDone <- err return } message := []byte("ping") if _, err := conn.Write(message); err != nil { ipcDone <- err return } response := make([]byte, len("pong")) if n, err := conn.Read(response); err != nil { ipcDone <- err return } else if !bytes.Equal(response[:n], []byte("pong")) { ipcDone <- errors.New(fmt.Sprintf("Expected pong, got %s", response[:n])) return } ipcDone <- nil }() // Wait for the client and server to finish (or fail or timeout). errs := make(chan error) go func() { errs <- <-advertiseDone }() go func() { errs <- <-ipcDone }() timeout := time.After(80 * time.Millisecond) for i := 0; i < 2; i++ { select { case err := <-errs: if err != nil { t.Error(err) } case <-timeout: t.Error("Timeout waiting on advertise and IPC;", i, "processes finished.") goto timeout } } timeout: // Wait for the MinimaLT services themselves to stop (or fail or timeout). alice.Stop() bob.Stop() serverErrs := make(chan error) go func() { serverErrs <- <-aliceDone }() go func() { serverErrs <- <-bobDone }() timeout2 := time.After(80 * time.Millisecond) for i := 0; i < 2; i++ { select { case err := <-serverErrs: if err != nil { t.Error(err) } case <-timeout2: t.Error("Timeout waiting on Alice and Bob;", i, "processes finished.") goto timeout2 } } timeout2: }