Windows supported

Support build windows client.
Use in windows, make sure you have been install tap-windows properly.
After launch, the windows client will now listen udp multicast
so if you need to config routes by yourself
This commit is contained in:
lucheng
2024-07-02 14:49:06 +08:00
parent 550901f699
commit 395129216e
15 changed files with 322 additions and 8 deletions

View File

@@ -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} .
$(CONTAINER_TOOL) build -t ${IMG} .
.PHONY: build-windows
build-windows:
GOOS=windows GOARCH=amd64 go build -o virtuallan.exe main.go

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1,9 @@
package cli
import (
"github.com/urfave/cli/v2"
)
func NewServerCmd() *cli.Command {
return nil
}

View File

@@ -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
}

View File

@@ -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
}