package main import ( // "bufio" "fmt" "log" "os" "net" "strconv" "io" "context" "time" "math/rand" "bytes" "golang.org/x/crypto/ssh" // "github.com/songgao/packets/ethernet" "github.com/songgao/water" "github.com/vishvananda/netlink" ) type Client struct { ifce *water.Interface hostname string addr *netlink.Addr addr_str string } // -- funcs -- // writes out desired hostname to file. generates a small bash script for // the client to run //func generateNewClient() { // // var desired_hostname string // fmt.Printf("Please enter a desired hostname for the new client: ") // // scanner := bufio.NewScanner(os.Stdin) // scanner.Scan() // err := scanner.Err() // if err != nil { // log.Fatal(err) // panic(err) // } // desired_hostname = scanner.Text() // // //} // maps ip --> hostname // used to verify collisions var client_leases = make(map[string]string) var mode string func detectMode() { mode = os.Getenv("LAZYVPN_MODE") if len(mode) == 0 { fmt.Println("Mode not set! assuming CLIENT") mode = "CLIENT" } fmt.Printf("Running in mode %s\n", mode) return } func generateClientTUN(hn string) Client { // configure TUN interface config := water.Config{ DeviceType: water.TUN, } config.Name = "tun_"+hn cl_ifce, err := water.New(config) if err != nil { log.Fatal(err) } // Generate a free address to assign var cl_Addr_str string if mode == "CLIENT" { // This is a bad way of doing it, and only accounts for local conflicts // theres definitely a way to find if it conflicts somewhere else // ideally, it shouldnt be locked to this 16 block, either. eventually, // the allowed range (CIDR included) should be defined. // this is an alpha for now, just to get *something* nice working r := rand.New(rand.NewSource(255)) for { var b1 int = int(r.Int31n(255)) var b2 int = int(r.Int31n(255)) cl_Addr_str = "." + strconv.Itoa(b1) + "." + strconv.Itoa(b2) fmt.Println(cl_Addr_str) _, isLeased := client_leases[cl_Addr_str] if !isLeased { break } } } else { // its the server cl_Addr_str = ".0.1" } cl_Link, err := netlink.LinkByName(config.Name) if err != nil { log.Fatal(err) } cl_Addr_str = "169.254"+cl_Addr_str cl_Addr, err := netlink.ParseAddr(cl_Addr_str+"/16") if err != nil { log.Fatal(err) } // register addr -> ifce with host netlink.AddrAdd(cl_Link, cl_Addr) return Client { ifce: cl_ifce, hostname: hn, addr: cl_Addr, addr_str: cl_Addr_str, } } func StartListener(addr string) { // listen on port 28173 l, err := net.Listen("tcp", addr+":28173") if err != nil { log.Fatal(err) } defer l.Close() fmt.Printf("Listening on %s port 28173", addr) for { // Wait for a connection. conn, err := l.Accept() if err != nil { log.Fatal(err) } // Handle the connection in a new goroutine. // The loop then returns to accepting, so that // multiple connections may be served concurrently. go func(c net.Conn) { // Echo all incoming data. io.Copy(c, c) // Shut down the connection. c.Close() }(conn) } } func StartDialer() { var d net.Dialer ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() fmt.Printf("Attempting autodiscovery") conn, err := d.DialContext(ctx, "tcp", "169.254.0.1:28173") if err != nil { log.Fatalf("Failed to dial: %v", err) } defer conn.Close() if _, err := conn.Write([]byte("Hello, World!")); err != nil { log.Fatal(err) } } func main() { // Detect if we are in CLIENT or SERVER mode detectMode() // Zeroth stage - gather info var hostKey ssh.PublicKey config := &ssh.ClientConfig { User: os.Getenv("LAZYUSER"), Auth: []ssh.authmethod { ssh.Password(os.Getenv("LAZYPASS")), }, HostKeyCallback: ssh.FixedHostKey(hostKey), } // First stage - attempt to connect to public host var host string = "192.168.122.1" fmt.Printf("Dialing host %s....\n", host) client, err := ssh.Dial("tcp", host+":22278", config) if err != nil { log.Fatal("Failed to dial: ", err) } defer client.Close() // Final stage if mode == "SERVER" { // open, listen and wait StartListener(registered_client.addr_str) } else { // connect to far end StartDialer() } }