diff --git a/Makefile b/Makefile index ca1d561..77b1f1d 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,12 @@ build: gen .PHONY: clean clean: - rm -rf virtuallan + rm -rf virtuallan virtuallan.exe .PHONY: build-docker build-docker: gen - $(CONTAINER_TOOL) build -t ${IMG} . \ No newline at end of file + $(CONTAINER_TOOL) build -t ${IMG} . + +.PHONY: build-windows +build-windows: + GOOS=windows GOARCH=amd64 go build -o virtuallan.exe main.go \ No newline at end of file diff --git a/README.md b/README.md index 5d163c8..f29cc2f 100644 --- a/README.md +++ b/README.md @@ -41,14 +41,13 @@ make ``` .\devcon.exe OemVista.inf tap0901 ``` -3. Checkout to win branch -4. Build a windows exe +3. Build a windows exe ``` - GOOS=windows GOARCH=amd64 go build -o virtuallan.exe main.go + make build-windows ``` -5. Launch virtuallan.exe +4. Launch virtuallan.exe ![](./docs/statics/login.png) -6. Enjoy it +5. Enjoy it ![](./docs/statics/ip.png) ![](./docs/statics/ping.png) diff --git a/main.go b/main.go index 7b13542..3f41bef 100644 --- a/main.go +++ b/main.go @@ -11,12 +11,16 @@ import ( func main() { app := &cli.App{ Commands: []*cli.Command{ - vcli.NewServerCmd(), vcli.NewClientCmd(), vcli.NewUserCmd(), }, } + serverCmd := vcli.NewServerCmd() + if serverCmd != nil { + app.Commands = append(app.Commands, serverCmd) + } + if err := app.Run(os.Args); err != nil { log.Panic(err) os.Exit(1) diff --git a/pkg/cli/server.go b/pkg/cli/server_linux.go similarity index 100% rename from pkg/cli/server.go rename to pkg/cli/server_linux.go diff --git a/pkg/cli/server_windows.go b/pkg/cli/server_windows.go new file mode 100644 index 0000000..122a870 --- /dev/null +++ b/pkg/cli/server_windows.go @@ -0,0 +1,9 @@ +package cli + +import ( + "github.com/urfave/cli/v2" +) + +func NewServerCmd() *cli.Command { + return nil +} diff --git a/pkg/client/client.go b/pkg/client/client_linux.go similarity index 100% rename from pkg/client/client.go rename to pkg/client/client_linux.go diff --git a/pkg/client/client_windows.go b/pkg/client/client_windows.go new file mode 100644 index 0000000..3ad0c5f --- /dev/null +++ b/pkg/client/client_windows.go @@ -0,0 +1,206 @@ +package client + +import ( + "bufio" + "fmt" + "net" + "os" + "os/signal" + "strings" + "sync" + "syscall" + "time" + + "github.com/lucheng0127/virtuallan/pkg/cipher" + "github.com/lucheng0127/virtuallan/pkg/packet" + "github.com/lucheng0127/virtuallan/pkg/utils" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + "golang.org/x/term" +) + +func GetLoginInfo() (string, string, error) { + reader := bufio.NewReader(os.Stdin) + fmt.Println("Username:") + user, err := reader.ReadString('\n') + if err != nil { + return "", "", err + } + + fmt.Println("Password:") + bytePasswd, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + return "", "", err + } + + passwd := string(bytePasswd) + + return strings.TrimSpace(user), strings.TrimSpace(passwd), nil +} + +func checkLoginTimeout(c chan string) { + select { + case <-c: + return + case <-time.After(10 * time.Second): + log.Error("login timeout") + os.Exit(1) + } +} + +func handleSignal(conn *net.UDPConn, sigChan chan os.Signal) { + sig := <-sigChan + log.Infof("received signal: %v, send fin pkt to close conn\n", sig) + finPkt := packet.NewFinPkt() + + stream, err := finPkt.Encode() + if err != nil { + log.Error(err) + } + + _, err = conn.Write(stream) + if err != nil { + log.Error(err) + } + + os.Exit(0) +} + +func Run(cCtx *cli.Context) error { + logLevel := cCtx.String("log-level") + + switch strings.ToUpper(logLevel) { + case "DEBUG": + log.SetLevel(log.DebugLevel) + case "INFO": + log.SetLevel(log.InfoLevel) + case "WARN": + log.SetLevel(log.WarnLevel) + default: + log.SetLevel(log.InfoLevel) + } + + var user, passwd string + + if err := cipher.SetAESKey(cCtx.String("key")); err != nil { + return err + } + + if cCtx.String("passwd") == "" || cCtx.String("user") == "" { + u, p, err := GetLoginInfo() + if err != nil { + return err + } + + user = u + passwd = p + } else { + user = cCtx.String("user") + passwd = cCtx.String("passwd") + } + + udpAddr, err := net.ResolveUDPAddr("udp4", cCtx.String("target")) + if err != nil { + return err + } + + conn, err := net.DialUDP("udp4", nil, udpAddr) + if err != nil { + return err + } + + // Handle signal + sigChan := make(chan os.Signal, 8) + signal.Notify(sigChan, os.Interrupt) + go handleSignal(conn, sigChan) + + // Do auth + ipChan := make(chan string) + netToIface := make(chan *packet.VLPkt, 1024) + var wg sync.WaitGroup + wg.Add(3) + + // Handle udp packet + go func() { + for { + var buf [65535]byte + n, _, err := conn.ReadFromUDP(buf[:]) + + if err != nil { + log.Error("read from conn ", err) + os.Exit(1) + } + + if n < 2 { + continue + } + + pkt, err := packet.Decode(buf[:n]) + if err != nil { + log.Error("parse packet ", err) + continue + } + + switch pkt.Type { + case packet.P_RESPONSE: + switch pkt.VLBody.(*packet.RspBody).Code { + case packet.RSP_AUTH_REQUIRED: + log.Error("auth failed") + os.Exit(1) + case packet.RSP_IP_NOT_MATCH: + log.Error("ip not match") + os.Exit(1) + case packet.RSP_USER_LOGGED: + log.Error("user already logged by other endpoint") + os.Exit(1) + default: + continue + } + case packet.P_RAW: + netToIface <- pkt + case packet.P_DHCP: + ipAddr := pkt.VLBody.(*packet.KeepaliveBody).Parse() + ipChan <- ipAddr + default: + log.Debug("unknow stream, do nothing") + continue + } + } + }() + + // Auth + authPkt := packet.NewAuthPkt(user, passwd) + authStream, err := authPkt.Encode() + if err != nil { + log.Error("encode auth packet ", err) + os.Exit(1) + } + + _, err = conn.Write(authStream) + if err != nil { + log.Error("send auth packet ", err) + os.Exit(1) + } + + authChan := make(chan string, 1) + go checkLoginTimeout(authChan) + + // Waiting for dhcp ip + ipAddr := <-ipChan + authChan <- "ok" + log.Infof("auth with %s succeed, endpoint ip %s\n", user, ipAddr) + + iface, err := utils.NewTap(ipAddr) + if err != nil { + return err + } + + // Send keepalive + go DoKeepalive(conn, ipAddr, 10) + + // Switch io between udp net and tap interface + go HandleConn(iface, netToIface, conn) + + wg.Wait() + return nil +} diff --git a/pkg/packet/multicast.go b/pkg/packet/multicast_linux.go similarity index 100% rename from pkg/packet/multicast.go rename to pkg/packet/multicast_linux.go diff --git a/pkg/server/alive.go b/pkg/server/alive_linux.go similarity index 100% rename from pkg/server/alive.go rename to pkg/server/alive_linux.go diff --git a/pkg/server/conn.go b/pkg/server/conn_linux.go similarity index 100% rename from pkg/server/conn.go rename to pkg/server/conn_linux.go diff --git a/pkg/server/ipmgr.go b/pkg/server/ipmgr_linux.go similarity index 100% rename from pkg/server/ipmgr.go rename to pkg/server/ipmgr_linux.go diff --git a/pkg/server/routes.go b/pkg/server/routes_linux.go similarity index 100% rename from pkg/server/routes.go rename to pkg/server/routes_linux.go diff --git a/pkg/server/server.go b/pkg/server/server_linux.go similarity index 100% rename from pkg/server/server.go rename to pkg/server/server_linux.go diff --git a/pkg/server/web.go b/pkg/server/web_linux.go similarity index 100% rename from pkg/server/web.go rename to pkg/server/web_linux.go diff --git a/pkg/utils/interface_windows.go b/pkg/utils/interface_windows.go new file mode 100644 index 0000000..b5cdf37 --- /dev/null +++ b/pkg/utils/interface_windows.go @@ -0,0 +1,92 @@ +package utils + +import ( + "fmt" + "math/rand" + "net" + "os/exec" + + "github.com/songgao/water" +) + +const ( + UNKNOW_IP = "UNKNOW_IP" +) + +type LinkMessages struct { + InterfaceName string + RX_SIZE string + TX_SIZE string + RX_PKT uint64 + TX_PKT uint64 +} + +func RandStr(n int) string { + var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + +func GetMacFromIP(ip net.IP) net.HardwareAddr { + ip = ip.To4() + return net.HardwareAddr{0x60, 0xe2, ip[0], ip[1], ip[2], ip[3]} +} + +//func SetMACAddress(interfaceName, macAddress string) error { +// // Disable the network interface +// disableCmd := exec.Command("netsh", "interface", "set", "interface", interfaceName, "admin=disable") +// if err := disableCmd.Run(); err != nil { +// return fmt.Errorf("failed to disable interface: %v", err) +// } +// +// // Set the new MAC address +// setMacCmd := exec.Command("netsh", "interface", "set", "interface", interfaceName, "newmac", macAddress) +// if err := setMacCmd.Run(); err != nil { +// return fmt.Errorf("failed to set MAC address: %v", err) +// } +// +// // Enable the network interface +// enableCmd := exec.Command("netsh", "interface", "set", "interface", interfaceName, "admin=enable") +// if err := enableCmd.Run(); err != nil { +// return fmt.Errorf("failed to enable interface: %v", err) +// } +// +// return nil +//} + +func NewTap(ipAddr string) (*water.Interface, error) { + // Create tap + config := new(water.Config) + config.DeviceType = water.TAP + config.PlatformSpecificParams = water.PlatformSpecificParams{ + ComponentID: "tap0901", + } + + iface, err := water.New(*config) + if err != nil { + return nil, fmt.Errorf("create tap %s", err.Error()) + } + + ip, ipNet, err := net.ParseCIDR(ipAddr) + if err != nil { + return nil, fmt.Errorf("parse cidr %s %s", ipAddr, err.Error()) + } + + // Add ip to tap + netMask := fmt.Sprintf("%d.%d.%d.%d", ipNet.Mask[0], ipNet.Mask[1], ipNet.Mask[2], ipNet.Mask[3]) + cmd := exec.Command("netsh", "interface", "ip", "set", "address", fmt.Sprintf("name=%s", iface.Name()), "static", ip.String(), netMask) + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("assign ip %s to %s %s", ipAddr, iface.Name(), err.Error()) + } + + // Set tap mac + //mac := GetMacFromIP(ip) + //if err := SetMACAddress(iface.Name(), mac.String()); err != nil { + // return nil, err + //} + return iface, nil +}