diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a3b1cba..9b6a1d1 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - go-version: ^1.14 + go-version: ^1.16 id: go - name: Check out code into the Go module directory diff --git a/README.md b/README.md index fe5eace..207bbc8 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,13 @@ AnyLink 服务端仅在CentOS7测试通过,如需要安装在其他系统, ## Screenshot -![online](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/online.jpg) +![online](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/online.jpg) ## Installation -``` +> 升级 go version >= 1.16 + +```shell rootPath=`pwd` git clone https://github.com/bjdgyc/anylink.git @@ -36,12 +38,13 @@ npm install npm run build cd $rootPath/anylink +cp -r $rootPath/anylink-web/ui . go build -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`" #整理部署文件 mkdir $rootPath/anylink-deploy cd $rootPath/anylink-deploy -cp -r $rootPath/anylink-web/ui . + cp -r $rootPath/anylink/anylink . cp -r $rootPath/anylink/conf . cp -r $rootPath/anylink/downfiles . @@ -71,7 +74,15 @@ sudo ./anylink -conf="conf/server.toml" 默认配置文件内有详细的注释,根据注释填写配置即可。 -- [conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml) +```shell +# 生成后台密码 +./anylink -passwd 123456 + +# 生成jwt密钥 +./anylink -secret +``` + +[conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml) ## Setting @@ -82,43 +93,48 @@ sudo ./anylink -conf="conf/server.toml" ### tun设置 1. 开启服务器转发 - ``` - # flie: /etc/sysctl.conf - net.ipv4.ip_forward = 1 - #执行如下命令 - sysctl -w net.ipv4.ip_forward=1 - ``` + ```shell + # flie: /etc/sysctl.conf + net.ipv4.ip_forward = 1 + + #执行如下命令 + sysctl -w net.ipv4.ip_forward=1 + ``` 2. 设置nat转发规则 - ``` - # eth0为服务器内网网卡 - iptables -t nat -A POSTROUTING -s 192.168.10.0/255.255.255.0 -o eth0 -j MASQUERADE - ``` + +```shell +# eth0为服务器内网网卡 +iptables -t nat -A POSTROUTING -s 192.168.10.0/255.255.255.0 -o eth0 -j MASQUERADE +``` 3. 使用AnyConnect客户端连接即可 ### tap设置 1. 创建桥接网卡 - ``` - 注意 server.toml 的ip参数,需要与 bridge.sh 的配置参数一致 - ``` + +``` +注意 server.toml 的ip参数,需要与 bridge.sh 的配置参数一致 +``` 2. 修改 bridge.sh 内的参数 - ``` - # file: ./bridge.sh - eth="eth0" - eth_ip="192.168.1.4" - eth_netmask="255.255.255.0" - eth_broadcast="192.168.1.255" - eth_gateway="192.168.1.1" - ``` + +``` +# file: ./bridge.sh +eth="eth0" +eth_ip="192.168.1.4" +eth_netmask="255.255.255.0" +eth_broadcast="192.168.1.255" +eth_gateway="192.168.1.1" +``` 3. 执行 bridge.sh 文件 - ``` - sh bridge.sh - ``` + +``` +sh bridge.sh +``` ## Soft @@ -126,11 +142,11 @@ sudo ./anylink -conf="conf/server.toml" ## Other Screenshot -![system.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/system.jpg) -![setting.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/setting.jpg) -![users.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/users.jpg) -![ip_map.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/ip_map.jpg) -![group.jpg](https://raw.githubusercontent.com/bjdgyc/anylink/master/screenshot/group.jpg) +![system.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/system.jpg) +![setting.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/setting.jpg) +![users.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/users.jpg) +![ip_map.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/ip_map.jpg) +![group.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/group.jpg) ## License diff --git a/admin/api_base.go b/admin/api_base.go index 805b9d4..38a41a0 100644 --- a/admin/api_base.go +++ b/admin/api_base.go @@ -5,9 +5,8 @@ import ( "net/http" "time" - "github.com/bjdgyc/anylink/pkg/utils" - "github.com/bjdgyc/anylink/base" + "github.com/bjdgyc/anylink/pkg/utils" "github.com/gorilla/mux" ) diff --git a/admin/server.go b/admin/server.go index 838104a..bb24621 100644 --- a/admin/server.go +++ b/admin/server.go @@ -2,6 +2,7 @@ package admin import ( + "embed" "net/http" "net/http/pprof" @@ -9,11 +10,20 @@ import ( "github.com/gorilla/mux" ) +var UiPath embed.FS + // 开启服务 func StartAdmin() { + r := mux.NewRouter() r.Use(authMiddleware) + r.Handle("/", http.RedirectHandler("/ui/", http.StatusFound)). + Name("static") + r.PathPrefix("/ui/").Handler(http.FileServer( + http.FS(UiPath), + )).Name("static") + r.HandleFunc("/base/login", Login).Name("login") r.HandleFunc("/set/home", SetHome) r.HandleFunc("/set/system", SetSystem) @@ -49,8 +59,6 @@ func StartAdmin() { r.HandleFunc("/debug/pprof", location("/debug/pprof/")) r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index) - r.PathPrefix("/").Handler(http.FileServer(http.Dir(base.Cfg.UiPath))).Name("static") - base.Info("Listen admin", base.Cfg.AdminAddr) err := http.ListenAndServe(base.Cfg.AdminAddr, r) if err != nil { diff --git a/base/cfg_server.go b/base/cfg_server.go index f08e266..280d95b 100644 --- a/base/cfg_server.go +++ b/base/cfg_server.go @@ -3,6 +3,7 @@ package base import ( "fmt" "io/ioutil" + "os" "path/filepath" "reflect" "strings" @@ -38,8 +39,7 @@ type ServerConfig struct { DbFile string `toml:"db_file" info:"数据库地址"` CertFile string `toml:"cert_file" info:"证书文件"` CertKey string `toml:"cert_key" info:"证书密钥"` - UiPath string `toml:"ui_path" info:"ui文件路径"` - FilesPath string `toml:"files_path" info:"外部下载文件路径"` + DownFilesPath string `json:"down_files_path" info:"外部下载文件路径"` LogLevel string `toml:"log_level" info:"日志等级"` Issuer string `toml:"issuer" info:"系统名称"` AdminUser string `toml:"admin_user" info:"管理用户名"` @@ -82,8 +82,12 @@ func initServerCfg() { Cfg.DbFile = getAbsPath(base, Cfg.DbFile) Cfg.CertFile = getAbsPath(base, Cfg.CertFile) Cfg.CertKey = getAbsPath(base, Cfg.CertKey) - Cfg.UiPath = getAbsPath(base, Cfg.UiPath) - Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath) + Cfg.DownFilesPath = getAbsPath(base, Cfg.DownFilesPath) + + if len(Cfg.JwtSecret) < 20 { + fmt.Println("请设置 jwt_secret 长度20位以上") + os.Exit(0) + } fmt.Printf("ServerCfg: %+v \n", Cfg) } diff --git a/base/flag.go b/base/flag.go index 800fa4f..2324747 100644 --- a/base/flag.go +++ b/base/flag.go @@ -3,8 +3,11 @@ package base import ( "flag" "fmt" + "math/rand" "os" "runtime" + "strings" + "time" "github.com/bjdgyc/anylink/pkg/utils" ) @@ -16,13 +19,16 @@ var ( serverFile string // pass明文 passwd string + // 生成密钥 + secret bool // 显示版本信息 rev bool ) func initFlag() { flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config file path") - flag.StringVar(&passwd, "passwd", "", "the password plaintext") + flag.StringVar(&passwd, "passwd", "", "convert the password plaintext") + flag.BoolVar(&secret, "secret", false, "generate a random jwt secret") flag.BoolVar(&rev, "rev", false, "display version info") flag.Parse() @@ -32,6 +38,14 @@ func initFlag() { os.Exit(0) } + if secret { + rand.Seed(time.Now().UnixNano()) + s, _ := utils.RandSecret(40, 60) + s = strings.Trim(s, "=") + fmt.Printf("Secret:%s\n", s) + os.Exit(0) + } + if rev { fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n", APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId) diff --git a/base/log.go b/base/log.go index a4a7974..2a0516f 100644 --- a/base/log.go +++ b/base/log.go @@ -18,7 +18,7 @@ const ( var ( baseLog *log.Logger baseLevel int - level map[int]string + levels map[int]string ) func initLog() { @@ -27,7 +27,7 @@ func initLog() { } func logLevel2Int(l string) int { - level = map[int]string{ + levels = map[int]string{ _Debug: "Debug", _Info: "Info", _Warn: "Warn", @@ -35,7 +35,7 @@ func logLevel2Int(l string) int { _Fatal: "Fatal", } lvl := _Info - for k, v := range level { + for k, v := range levels { if strings.ToLower(l) == strings.ToLower(v) { lvl = k } @@ -44,7 +44,7 @@ func logLevel2Int(l string) int { } func output(l int, s ...interface{}) { - lvl := fmt.Sprintf("[%s] ", level[l]) + lvl := fmt.Sprintf("[%s] ", levels[l]) baseLog.Output(3, lvl+fmt.Sprintln(s...)) } diff --git a/conf/server.toml b/conf/server.toml index a3c4d66..4c093e5 100644 --- a/conf/server.toml +++ b/conf/server.toml @@ -8,8 +8,7 @@ db_file = "./data.db" #证书文件 cert_file = "./vpn_cert.pem" cert_key = "./vpn_cert.key" -ui_path = "../ui" -files_path = "../downfiles" +down_files_path = "../down_files" log_level = "info" @@ -19,7 +18,7 @@ issuer = "XX公司VPN" admin_user = "admin" #pass 123456 admin_pass = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke" -jwt_secret = "7IrsKW3JuDJ68TPPrdsfweDFYJrO1Xg7JcdsfasMv3P3" +jwt_secret = "" #vpn服务对外地址 diff --git a/dbdata/group.go b/dbdata/group.go index e21b07e..81780c8 100644 --- a/dbdata/group.go +++ b/dbdata/group.go @@ -69,6 +69,9 @@ func SetGroup(g *Group) error { clientDns = append(clientDns, v) } } + if len(clientDns) == 0 { + return errors.New("DNS错误") + } g.ClientDns = clientDns routeInclude := []ValData{} diff --git a/dbdata/user.go b/dbdata/user.go index cd54868..ff167e8 100644 --- a/dbdata/user.go +++ b/dbdata/user.go @@ -2,6 +2,8 @@ package dbdata import ( "errors" + "fmt" + "sync" "time" "github.com/bjdgyc/anylink/pkg/utils" @@ -23,44 +25,6 @@ type User struct { UpdatedAt time.Time `json:"updated_at"` } -// 验证用户登陆信息 -func CheckUser(name, pwd, group string) error { - // return nil - - pl := len(pwd) - if name == "" || pl < 6 { - return errors.New("密码错误") - } - v := &User{} - err := One("Username", name, v) - if err != nil || v.Status != 1 { - return errors.New("用户名错误") - } - pass := pwd[:pl-6] - // if !utils.PasswordVerify(pass, v.Password) { - if pass != v.PinCode { - return errors.New("密码错误") - } - otp := pwd[pl-6:] - totp := gotp.NewDefaultTOTP(v.OtpSecret) - unix := time.Now().Unix() - verify := totp.Verify(otp, int(unix)) - if !verify { - return errors.New("动态码错误") - } - - // 判断用户组信息 - if !utils.InArrStr(v.Groups, group) { - return errors.New("用户组错误") - } - groupData := &Group{} - err = One("Name", group, groupData) - if err != nil || groupData.Status != 1 { - return errors.New("用户组错误") - } - return nil -} - func SetUser(v *User) error { var err error if v.Username == "" || len(v.Groups) == 0 { @@ -96,3 +60,84 @@ func SetUser(v *User) error { return err } + +// 验证用户登陆信息 +func CheckUser(name, pwd, group string) error { + // return nil + + pl := len(pwd) + if name == "" || pl < 6 { + return fmt.Errorf("%s %s", name, "密码错误") + } + v := &User{} + err := One("Username", name, v) + if err != nil || v.Status != 1 { + return fmt.Errorf("%s %s", name, "用户名错误") + } + // 判断用户组信息 + if !utils.InArrStr(v.Groups, group) { + return fmt.Errorf("%s %s", name, "用户组错误") + } + groupData := &Group{} + err = One("Name", group, groupData) + if err != nil || groupData.Status != 1 { + return fmt.Errorf("%s %s", name, "用户组错误") + } + + // 判断otp信息 + otp := pwd[pl-6:] + if !checkOtp(name, otp) { + return fmt.Errorf("%s %s", name, "动态码错误") + } + totp := gotp.NewDefaultTOTP(v.OtpSecret) + unix := time.Now().Unix() + verify := totp.Verify(otp, int(unix)) + if !verify { + return fmt.Errorf("%s %s", name, "动态码错误") + } + + pinCode := pwd[:pl-6] + if pinCode != v.PinCode { + return fmt.Errorf("%s %s", name, "密码错误") + } + + return nil +} + +var ( + userOtpMux = sync.Mutex{} + userOtp = map[string]time.Time{} +) + +func init() { + go func() { + expire := time.Second * 60 + + for range time.Tick(time.Second * 10) { + tnow := time.Now() + userOtpMux.Lock() + for k, v := range userOtp { + if tnow.After(v.Add(expire)) { + delete(userOtp, k) + } + } + userOtpMux.Unlock() + } + }() +} + +// 令牌只能使用一次 +func checkOtp(username, otp string) bool { + key := fmt.Sprintf("%s:%s", username, otp) + + userOtpMux.Lock() + defer userOtpMux.Unlock() + + if _, ok := userOtp[key]; ok { + // 已经存在 + return false + } + + userOtp[key] = time.Now() + return true +} diff --git a/downfiles/.gitignore b/down_files/.gitignore similarity index 62% rename from downfiles/.gitignore rename to down_files/.gitignore index e2718ef..f6f2015 100644 --- a/downfiles/.gitignore +++ b/down_files/.gitignore @@ -1,4 +1,5 @@ # Binaries for programs and plugins * -!.gitignore \ No newline at end of file +!.gitignore +!index.html \ No newline at end of file diff --git a/down_files/index.html b/down_files/index.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/down_files/index.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod index 22af352..243057d 100644 --- a/go.mod +++ b/go.mod @@ -22,4 +22,5 @@ require ( golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) diff --git a/go.sum b/go.sum index 207da05..9c7a53a 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/handler/link_auth.go b/handler/link_auth.go index 9e82bdf..e5ab17e 100644 --- a/handler/link_auth.go +++ b/handler/link_auth.go @@ -2,6 +2,7 @@ package handler import ( "encoding/xml" + "fmt" "io" "io/ioutil" "net/http" @@ -14,6 +15,17 @@ import ( ) func LinkAuth(w http.ResponseWriter, r *http.Request) { + // 判断anyconnect客户端 + userAgent := strings.ToLower(r.UserAgent()) + x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth") + x_Transcend_Version := r.Header.Get("X-Transcend-Version") + if !(strings.Contains(userAgent, "anyconnect") && + x_Aggregate_Auth == "1" && x_Transcend_Version == "1") { + w.WriteHeader(http.StatusForbidden) + fmt.Fprintf(w, "error request") + return + } + body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) diff --git a/handler/link_cstp.go b/handler/link_cstp.go index 4313c1a..7edf3ba 100644 --- a/handler/link_cstp.go +++ b/handler/link_cstp.go @@ -20,7 +20,7 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) { err error n int dataLen uint16 - dead = time.Duration(cSess.CstpDpd*2) * time.Second + dead = time.Duration(cSess.CstpDpd+5) * time.Second ) go cstpWrite(conn, cSess) diff --git a/handler/link_tunnel.go b/handler/link_tunnel.go index 57fc8d8..21b2175 100644 --- a/handler/link_tunnel.go +++ b/handler/link_tunnel.go @@ -8,7 +8,6 @@ import ( "os" "github.com/bjdgyc/anylink/base" - "github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/sessdata" ) @@ -51,7 +50,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { masterSecret := r.Header.Get("X-DTLS-Master-Secret") localIp := r.Header.Get("X-Cstp-Local-Address-Ip4") mobile := r.Header.Get("X-Cstp-License") - platform := r.Header.Get("X-AnyConnect-Identifier-Platform") + cSess.SetMtu(cstpMtu) cSess.MasterSecret = masterSecret cSess.RemoteAddr = r.RemoteAddr @@ -67,12 +66,6 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { } cSess.CstpDpd = cstpDpd - // iPhone手机需要最少一个dns - if platform == "apple-ios" && len(cSess.Group.ClientDns) == 0 { - dnsVal := dbdata.ValData{Val: "114.114.114.114"} - cSess.Group.ClientDns = append(cSess.Group.ClientDns, dnsVal) - } - base.Debug(cSess.IpAddr, cSess.MacHw, sess.Username, mobile) // 返回客户端数据 diff --git a/handler/server.go b/handler/server.go index 941e506..d05d5f0 100644 --- a/handler/server.go +++ b/handler/server.go @@ -6,6 +6,7 @@ import ( "log" "net" "net/http" + "os" "time" "github.com/bjdgyc/anylink/base" @@ -18,6 +19,7 @@ func startTls() { certFile := base.Cfg.CertFile keyFile := base.Cfg.CertKey + logger := log.New(os.Stdout, "[SERVER]", log.Lshortfile|log.Ldate) // 设置tls信息 tlsConfig := &tls.Config{ NextProtos: []string{"http/1.1"}, @@ -27,6 +29,7 @@ func startTls() { Addr: addr, Handler: initRoute(), TLSConfig: tlsConfig, + ErrorLog: logger, } var ln net.Listener @@ -50,13 +53,13 @@ func startTls() { func initRoute() http.Handler { r := mux.NewRouter() - // r.HandleFunc("/", checkLinkClient(LinkHome)).Methods(http.MethodGet) - r.HandleFunc("/", checkLinkClient(LinkAuth)).Methods(http.MethodPost) + r.HandleFunc("/", LinkHome).Methods(http.MethodGet) + r.HandleFunc("/", LinkAuth).Methods(http.MethodPost) r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect) r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet) - r.PathPrefix("/files/").Handler( - http.StripPrefix("/files/", - http.FileServer(http.Dir(base.Cfg.FilesPath)), + r.PathPrefix("/down_files/").Handler( + http.StripPrefix("/down_files/", + http.FileServer(http.Dir(base.Cfg.DownFilesPath)), ), ) r.NotFoundHandler = http.HandlerFunc(notFound) diff --git a/main.go b/main.go index 9157d22..624d139 100644 --- a/main.go +++ b/main.go @@ -2,21 +2,29 @@ package main import ( + "embed" "os" "os/signal" "syscall" + "github.com/bjdgyc/anylink/admin" "github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/handler" ) +//go:embed ui/* +var UiPath embed.FS + // 程序版本 var COMMIT_ID string func main() { base.CommitId = COMMIT_ID + admin.UiPath = UiPath + base.Start() handler.Start() + signalWatch() } diff --git a/pkg/utils/password_hash.go b/pkg/utils/password_hash.go index ca2cf91..9254b7c 100644 --- a/pkg/utils/password_hash.go +++ b/pkg/utils/password_hash.go @@ -25,8 +25,8 @@ const ( delmiter = "$" ) -func saltSecret() (string, error) { - rb := make([]byte, randInt(10, 100)) +func RandSecret(min int, max int) (string, error) { + rb := make([]byte, randInt(min, max)) _, err := rand.Read(rb) if err != nil { return "", err diff --git a/sessdata/session.go b/sessdata/session.go index d8ce5d4..35e8567 100644 --- a/sessdata/session.go +++ b/sessdata/session.go @@ -145,8 +145,8 @@ func (s *Session) NewConn() *ConnSession { macHw, err := net.ParseMAC(macAddr) if err != nil { sum := md5.Sum([]byte(s.UniqueIdGlobal)) - macHw = sum[8:13] // 5个byte - macHw = append([]byte{0x00}, macHw...) + macHw = sum[0:5] // 5个byte + macHw = append([]byte{0x02}, macHw...) macAddr = macHw.String() } ip := AcquireIp(username, macAddr)