Esempio n. 1
0
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:
}