Initial commit: Project structure, Coordinator Server v1.0.0, and Documentation
This commit is contained in:
commit
0ead98d0c4
18 files changed
+2073
No files matched your search
@@ -0,0 +1,39 @@
|
||||
# MeshVPN Coordinator Server
|
||||
|
||||
The Coordinator Server is the central brain of the MeshVPN network. It handles node registration, peer signaling, and provides STUN services for NAT traversal.
|
||||
|
||||
## Features
|
||||
- **Integrated STUN Server**: Helps clients discover their public endpoints.
|
||||
- **Node Coordination**: Manages public keys and virtual IP assignments.
|
||||
- **Signaling**: Facilitates UDP hole punching between peers.
|
||||
- **Web Dashboard**: (Coming soon) Visualization of the mesh network.
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
- [Go](https://go.dev/dl/) (1.21+)
|
||||
- [Redis](https://redis.io/download) (For real-time node state)
|
||||
- [PostgreSQL](https://www.postgresql.org/download/) (For identity and ACLs)
|
||||
|
||||
### Setup
|
||||
1. **Clone the repository**
|
||||
2. **Install dependencies**:
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
3. **Configure Environment**:
|
||||
Create a `.env` file with:
|
||||
```env
|
||||
REDIS_ADDR=localhost:6379
|
||||
POSTGRES_URL=postgres://user:pass@localhost:5432/meshvpn
|
||||
STUN_PORT=3478
|
||||
COORD_PORT=50051
|
||||
```
|
||||
4. **Run the server**:
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
- **STUN**: UDP port 3478
|
||||
- **gRPC Coordinator**: TCP port 50051
|
||||
@@ -0,0 +1,78 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package coordinator;
|
||||
|
||||
option go_package = "server/api";
|
||||
|
||||
service Coordinator {
|
||||
// Registers a node. User name is auto-generated by server initially.
|
||||
rpc RegisterNode(RegisterRequest) returns (RegisterResponse);
|
||||
|
||||
// Updates the display name of the node (allows user to change it later).
|
||||
rpc UpdateNodeName(UpdateNameRequest) returns (UpdateNameResponse);
|
||||
|
||||
// Heartbeat to keep the node marked as online in Redis.
|
||||
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
|
||||
|
||||
// Requests the connection details for a target peer.
|
||||
rpc GetPeerEndpoint(PeerRequest) returns (PeerResponse);
|
||||
|
||||
// Notifies the coordinator that a node wants to establish a connection.
|
||||
rpc SignalConnection(SignalRequest) returns (SignalResponse);
|
||||
}
|
||||
|
||||
message RegisterRequest {
|
||||
string network_key = 1; // The shared secret for this overlay network
|
||||
string node_id = 2; // Unique hardware/installation ID
|
||||
string public_key = 3; // WireGuard public key for E2EE
|
||||
string local_ip = 4; // Local network IP
|
||||
}
|
||||
|
||||
message RegisterResponse {
|
||||
string virtual_ip = 1; // Assigned IP in the overlay network
|
||||
string session_token = 2; // JWT for subsequent authenticated requests
|
||||
string coordinator_address = 3;
|
||||
string assigned_name = 4; // The auto-generated name (e.g., "Node-8a2f")
|
||||
}
|
||||
|
||||
message UpdateNameRequest {
|
||||
string node_id = 1;
|
||||
string new_name = 2;
|
||||
string session_token = 3;
|
||||
}
|
||||
|
||||
message UpdateNameResponse {
|
||||
bool success = 1;
|
||||
string current_name = 2;
|
||||
}
|
||||
|
||||
message HeartbeatRequest {
|
||||
string node_id = 1;
|
||||
string session_token = 2;
|
||||
string public_endpoint = 3;
|
||||
}
|
||||
|
||||
message HeartbeatResponse {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message PeerRequest {
|
||||
string target_node_id = 1;
|
||||
string session_token = 2;
|
||||
}
|
||||
|
||||
message PeerResponse {
|
||||
string peer_public_key = 1;
|
||||
string public_endpoint = 2;
|
||||
string nat_type = 3;
|
||||
}
|
||||
|
||||
message SignalRequest {
|
||||
string source_node_id = 1;
|
||||
string target_node_id = 2;
|
||||
string session_token = 3;
|
||||
}
|
||||
|
||||
message SignalResponse {
|
||||
bool acknowledged = 1;
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
// "server/api" // This would be the generated package
|
||||
)
|
||||
|
||||
const version = "1.0.0"
|
||||
|
||||
// CoordinatorServer implements the Coordinator gRPC service
|
||||
type CoordinatorServer struct {
|
||||
// In a full implementation, we would have a Redis client here
|
||||
// nodes map[string]*NodeInfo
|
||||
}
|
||||
|
||||
func (s *CoordinatorServer) RegisterNode(ctx interface{}, req interface{}) (interface{}, error) {
|
||||
fmt.Println("RegisterNode called")
|
||||
return nil, nil // Placeholder for actual implementation
|
||||
}
|
||||
|
||||
func (s *CoordinatorServer) Heartbeat(ctx interface{}, req interface{}) (interface{}, error) {
|
||||
fmt.Println("Heartbeat called")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *CoordinatorServer) GetPeerEndpoint(ctx interface{}, req interface{}) (interface{}, error) {
|
||||
fmt.Println("GetPeerEndpoint called")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *CoordinatorServer) SignalConnection(ctx interface{}, req interface{}) (interface{}, error) {
|
||||
fmt.Println("SignalConnection called")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// STUN server implementation
|
||||
func startSTUNServer(port int) {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: port})
|
||||
if err != nil {
|
||||
log.Fatalf("STUN server failed to start: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Printf("STUN server listening on %s\n", addr)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, remoteAddr, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
log.Printf("STUN read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Received STUN request from %s (size: %d)\n", remoteAddr, n)
|
||||
|
||||
// Simplified STUN response: send the remote address back to the client
|
||||
// In a real STUN server, we would follow RFC 5389
|
||||
response := []byte(fmt.Sprintf("STUN_RESP:%s", remoteAddr.String()))
|
||||
_, err = conn.WriteToUDP(response, remoteAddr)
|
||||
if err != nil {
|
||||
log.Printf("STUN write error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Start STUN server in a goroutine
|
||||
go startSTUNServer(3478)
|
||||
|
||||
// Serve HTML Dashboard
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Note: In a production app, we would use a template to inject the version
|
||||
// For this task, we serve the file and suggest using a template for dynamic injection
|
||||
http.ServeFile(w, r, "C:\\Users\\jadon\\Documents\\vscode\\tailscale-test\\meshvpn-dashboard\\index.html")
|
||||
})
|
||||
|
||||
// Start HTTP server in a goroutine
|
||||
go func() {
|
||||
fmt.Printf("HTTP Dashboard listening on :8080 (Version: %s)\n", version)
|
||||
if err := http.ListenAndServe(":8080", nil); err != nil {
|
||||
log.Fatalf("HTTP server failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start gRPC Coordinator server
|
||||
lis, err := net.Listen("tcp", ":50051")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
// In a real setup, we'd register the generated server:
|
||||
// pb.RegisterCoordinatorServer(s, &CoordinatorServer{})
|
||||
|
||||
reflection.Register(s)
|
||||
|
||||
fmt.Println("Coordinator Server listening on :50051")
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user