├── .gitignore ├── .idea ├── .gitignore ├── go-drcom-jlu.iml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── config.go ├── drcom ├── LICENSE ├── Readme.md ├── challenge.go ├── client.go ├── encrypt.go ├── keepalive.go ├── login.go └── logout.go ├── go.mod ├── go.sum ├── logger └── logger.go ├── main.go ├── network.go └── release ├── PKGBUILD ├── config.json └── go-drcom-jlu.service /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | !release/config.json -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Default ignored files 3 | /workspace.xml -------------------------------------------------------------------------------- /.idea/go-drcom-jlu.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yesterday17 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/Yesterday17/go-drcom-jlu/drcom" 7 | "github.com/Yesterday17/go-drcom-jlu/logger" 8 | "io/ioutil" 9 | "os" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | func ReadConfig(path string) (*drcom.Config, error) { 15 | var config drcom.Config 16 | 17 | if content, err := ioutil.ReadFile(path); err != nil { 18 | return nil, err 19 | } else if err = json.Unmarshal(content, &config); err != nil { 20 | return nil, err 21 | } 22 | 23 | if match, _ := regexp.MatchString("(?:[0-9A-Za-z]{2}:){5}[0-9A-Za-z]{2}", config.MAC); !match { 24 | return nil, fmt.Errorf("invalid MAC address") 25 | } 26 | 27 | if match, _ := regexp.MatchString("^[a-z]{4,}\\d{4}$", config.Username); !match { 28 | return nil, fmt.Errorf("invalid username") 29 | } 30 | 31 | if config.Password == "" { 32 | return nil, fmt.Errorf("password cannot be empty") 33 | } 34 | 35 | // convert MAC to lower case 36 | config.MAC = strings.ToLower(config.MAC) 37 | 38 | // Default Timeout = 3 seconds 39 | if config.Timeout <= 0 { 40 | config.Timeout = 3 41 | } 42 | 43 | // Default Retry = 3 times 44 | if config.Retry <= 0 { 45 | config.Retry = 3 46 | } 47 | 48 | // Default LogLevel = "info" 49 | if config.LogLevel == "" || (config.LogLevel != "error" && config.LogLevel != "info" && config.LogLevel != "debug") { 50 | config.LogLevel = "info" 51 | } 52 | 53 | if config.LogPath == "" { 54 | switch config.LogLevel { 55 | case "error": 56 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr) 57 | case "info": 58 | logger.Init(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr) 59 | case "debug": 60 | logger.Init(os.Stdout, os.Stdout, os.Stdout, os.Stderr) 61 | } 62 | } else { 63 | // TODO: 写入日志到文件 64 | switch config.LogLevel { 65 | case "error": 66 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, os.Stderr) 67 | case "info": 68 | logger.Init(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr) 69 | case "debug": 70 | logger.Init(os.Stdout, os.Stdout, os.Stdout, os.Stderr) 71 | } 72 | } 73 | 74 | // Write change to config file 75 | jsonConfig, _ := json.MarshalIndent(config, "", " ") 76 | if err := ioutil.WriteFile(path, jsonConfig, os.FileMode(0644)); err != nil { 77 | return nil, err 78 | } 79 | 80 | return &config, nil 81 | } 82 | -------------------------------------------------------------------------------- /drcom/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /drcom/Readme.md: -------------------------------------------------------------------------------- 1 | # Drcom 2 | 3 | Code here is edited from [go-jlu-drcom-client](https://github.com/wucongyou/go-jlu-drcom-client), which uses Apache License 2.0. 4 | 5 | ## License 6 | 7 | [LICENSE](LICENSE) 8 | -------------------------------------------------------------------------------- /drcom/challenge.go: -------------------------------------------------------------------------------- 1 | package drcom 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | func (c *Client) Challenge() error { 9 | var ( 10 | response []byte 11 | packet = []byte{ 12 | 0x01, (byte)(0x02 + c.ChallengeTimes), 13 | byte(rand.Int()), byte(rand.Int()), 14 | 0x6a, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 18 | } 19 | ) 20 | if err := c.WriteWithTimeout(packet); err != nil { 21 | c.ChallengeTimes++ 22 | return fmt.Errorf("conn.Write(%v) error(%v)", packet, err) 23 | } 24 | 25 | response = make([]byte, 76) 26 | if err := c.ReadWithTimeout(response); err != nil { 27 | c.ChallengeTimes++ 28 | return fmt.Errorf("conn.Read() error(%v)", err) 29 | } 30 | 31 | if response[0] == 0x02 { 32 | copy(c.salt, response[4:8]) 33 | copy(c.clientIP, response[20:24]) 34 | return nil 35 | } 36 | 37 | c.ChallengeTimes++ 38 | return ErrChallengeHeadError 39 | } 40 | -------------------------------------------------------------------------------- /drcom/client.go: -------------------------------------------------------------------------------- 1 | package drcom 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "github.com/Yesterday17/go-drcom-jlu/logger" 7 | "hash" 8 | "math/big" 9 | "net" 10 | "os" 11 | "time" 12 | ) 13 | 14 | const ( 15 | _codeIn = byte(0x03) 16 | _codeOut = byte(0x06) 17 | _type = byte(0x01) 18 | _eof = byte(0x00) 19 | _controlCheck = byte(0x20) 20 | _adapterNum = byte(0x05) 21 | _ipDog = byte(0x01) 22 | ) 23 | 24 | const ( 25 | authIP = "10.100.61.3" 26 | authPort = "61440" 27 | ) 28 | 29 | var ( 30 | _delimiter = []byte{0x00, 0x00, 0x00, 0x00} 31 | _emptyIP = []byte{0, 0, 0, 0} 32 | _primaryDNS = []byte{10, 10, 10, 10} 33 | _dhcpServer = []byte{0, 0, 0, 0} 34 | _authVersion = []byte{0x6a, 0x00} 35 | _magic1 = big.NewInt(1968) 36 | _magic2 = big.NewInt(int64(0xffffffff)) 37 | _magic3 = big.NewInt(int64(711)) 38 | _hostName = (func() string { 39 | name, err := os.Hostname() 40 | if err != nil { 41 | return "unknown" 42 | } 43 | return name 44 | })() 45 | ) 46 | 47 | type Config struct { 48 | MAC string `json:"mac"` 49 | Username string `json:"username"` 50 | Password string `json:"password"` 51 | Retry int `json:"retry"` 52 | Timeout time.Duration `json:"timeout"` 53 | LogLevel string `json:"log_level"` 54 | LogPath string `json:"log_path"` 55 | } 56 | 57 | type Client struct { 58 | config *Config 59 | md5Ctx hash.Hash 60 | salt []byte // [4:8] 61 | clientIP []byte // [20:24] 62 | md5a []byte 63 | tail1 []byte 64 | tail2 []byte 65 | keepAliveVer []byte // [28:30] 66 | conn *net.UDPConn 67 | ChallengeTimes int 68 | Count int 69 | FailCount int 70 | timeout time.Duration 71 | retry int 72 | logoutCh chan struct{} 73 | } 74 | 75 | func New(cfg *Config) *Client { 76 | time.Sleep(2 * time.Second) 77 | addr := fmt.Sprintf("%s:%s", authIP, authPort) 78 | conn, err := net.DialTimeout("udp", addr, cfg.Timeout*time.Second) 79 | if err != nil { 80 | logger.Errorf("[New-Client] DialTimeout error: %v", err) 81 | os.Exit(1) 82 | } 83 | 84 | return &Client{ 85 | conn: conn.(*net.UDPConn), 86 | config: cfg, 87 | md5Ctx: md5.New(), 88 | md5a: make([]byte, 16), 89 | tail1: make([]byte, 16), 90 | tail2: make([]byte, 4), 91 | keepAliveVer: []byte{0xdc, 0x02}, 92 | clientIP: make([]byte, 4), 93 | salt: make([]byte, 4), 94 | ChallengeTimes: 0, 95 | Count: 0, 96 | FailCount: 0, 97 | logoutCh: make(chan struct{}, 1), 98 | } 99 | } 100 | 101 | func (c *Client) Start() { 102 | logger.Info("- Starting...") 103 | 104 | // Challenge 105 | for i := 0; i < c.config.Retry; i++ { 106 | if err := c.Challenge(); err != nil { 107 | logger.Errorf("Challenge #%d error: %v", c.ChallengeTimes, err) 108 | if i == c.retry-1 { 109 | logger.Error("Failed to challenge for 3 times! Exiting...") 110 | os.Exit(1) 111 | } 112 | } 113 | } 114 | logger.Info("☑ Challenge") 115 | 116 | // Login 117 | if err := c.Login(); err != nil { 118 | logger.Errorf("Login error: %v", err) 119 | os.Exit(1) 120 | } 121 | logger.Info("☑ Login") 122 | 123 | go c.keepalive() 124 | logger.Info("☑ Keepalive") 125 | } 126 | 127 | // Close close service. 128 | func (c *Client) Close() error { 129 | close(c.logoutCh) 130 | _ = c.conn.Close() 131 | return nil 132 | } 133 | 134 | func (c *Client) WriteWithTimeout(b []byte) (err error) { 135 | if err = c.conn.SetWriteDeadline(time.Now().Add(time.Second * c.config.Timeout)); err != nil { 136 | return 137 | } 138 | _, err = c.conn.Write(b) 139 | 140 | if err != nil && err.(net.Error).Timeout() { 141 | c.FailCount++ 142 | if c.FailCount > c.config.Retry { 143 | logger.Errorf("WriteWithTimeout failed for %d times, exiting...", c.config.Retry) 144 | os.Exit(2) 145 | } 146 | } else if err != nil { 147 | logger.Errorf("WriteWithTimeout failed: %v", err) 148 | os.Exit(3) 149 | } else { 150 | c.FailCount = 0 151 | } 152 | return 153 | } 154 | 155 | func (c *Client) ReadWithTimeout(b []byte) (err error) { 156 | if err = c.conn.SetReadDeadline(time.Now().Add(time.Second * c.config.Timeout)); err != nil { 157 | return 158 | } 159 | _, err = c.conn.Read(b) 160 | 161 | if err != nil && err.(net.Error).Timeout() { 162 | c.FailCount++ 163 | if c.FailCount > c.config.Retry { 164 | logger.Errorf("ReadWithTimeout failed for %d times, exiting...", c.config.Retry) 165 | os.Exit(2) 166 | } 167 | } else if err != nil { 168 | logger.Errorf("ReadWithTimeout failed: %v", err) 169 | os.Exit(3) 170 | } else { 171 | c.FailCount = 0 172 | } 173 | return 174 | } 175 | -------------------------------------------------------------------------------- /drcom/encrypt.go: -------------------------------------------------------------------------------- 1 | package drcom 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "strings" 7 | ) 8 | 9 | func (c *Client) md5(items ...[]byte) (ret []byte) { 10 | for _, v := range items { 11 | c.md5Ctx.Write(v) 12 | } 13 | ret = c.md5Ctx.Sum(nil) 14 | c.md5Ctx.Reset() 15 | return 16 | } 17 | 18 | // MACHex2Bytes convert mac address to bytes, the input mac format should be 2a:1b:4c:fe:a9:e9. 19 | func MACHex2Bytes(mac string) (res []byte, err error) { 20 | as := strings.Replace(mac, ":", "", -1) 21 | res = make([]byte, 0, 6) 22 | return hex.DecodeString(as) 23 | } 24 | 25 | func (c *Client) ror(md5a, password []byte) (ret []byte) { 26 | l := len(password) 27 | ret = make([]byte, l) 28 | for i := 0; i < l; i++ { 29 | x := md5a[i] ^ password[i] 30 | ret[i] = (byte)((x << 3) + (x >> 5)) 31 | } 32 | return 33 | } 34 | 35 | func (c *Client) checkSum(data []byte) (ret []byte) { 36 | // 1234 = 0x_00_00_04_d2 37 | sum := []byte{0x00, 0x00, 0x04, 0xd2} 38 | l := len(data) 39 | i := 0 40 | //0123_4567_8901_23 41 | for ; i+3 < l; i = i + 4 { 42 | //abcd ^ 3210 43 | //abcd ^ 7654 44 | //abcd ^ 1098 45 | sum[0] ^= data[i+3] 46 | sum[1] ^= data[i+2] 47 | sum[2] ^= data[i+1] 48 | sum[3] ^= data[i] 49 | } 50 | if i < l { 51 | //剩下_23 52 | //i=12,len=14 53 | tmp := make([]byte, 4) 54 | for j := 3; j >= 0 && i < l; j-- { 55 | //j=3 tmp = 0 0 0 2 i=12 13 56 | //j=2 tmp = 0 0 3 2 i=13 14 57 | tmp[j] = data[i] 58 | i++ 59 | } 60 | for j := 0; j < 4; j++ { 61 | sum[j] ^= tmp[j] 62 | } 63 | } 64 | var x = big.NewInt(int64(0)) 65 | tmpBytes := x.SetBytes(sum).Mul(x, _magic1).Add(x, _magic2).Bytes() 66 | l = len(tmpBytes) 67 | i = 0 68 | ret = make([]byte, 4) 69 | for j := l - 1; j >= 0 && i < 4; j-- { 70 | ret[i] = tmpBytes[j] 71 | i++ 72 | } 73 | return 74 | } 75 | 76 | func (c *Client) extra() bool { 77 | return c.Count%21 == 0 78 | } 79 | 80 | func (c *Client) crc(buf []byte) (ret []byte) { 81 | sum := make([]byte, 2) 82 | l := len(buf) 83 | for i := 0; i < l-1; i += 2 { 84 | sum[0] ^= buf[i+1] 85 | sum[1] ^= buf[i] 86 | } 87 | x := big.NewInt(int64(0)) 88 | tmpBytes := x.SetBytes(sum).Mul(x, _magic3).Bytes() 89 | ret = make([]byte, 4) 90 | l = len(tmpBytes) 91 | for i := 0; i < 4 && l > 0; i++ { 92 | l-- 93 | ret[i] = tmpBytes[l] 94 | } 95 | return 96 | } 97 | -------------------------------------------------------------------------------- /drcom/keepalive.go: -------------------------------------------------------------------------------- 1 | package drcom 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/Yesterday17/go-drcom-jlu/logger" 7 | "math/rand" 8 | "time" 9 | ) 10 | 11 | var ErrChallengeHeadError = errors.New("challenge receive head is not correct") 12 | 13 | func (c *Client) Alive() (err error) { 14 | var r, buf []byte 15 | 16 | buf = c.buf38() 17 | if err = c.WriteWithTimeout(buf); err != nil { 18 | logger.Errorf("conn.Write(%v) error(%v)", buf, err) 19 | return 20 | } 21 | r = make([]byte, 128) 22 | if err = c.ReadWithTimeout(r); err != nil { 23 | logger.Errorf("conn.Read() error(%v)", err) 24 | return 25 | } 26 | c.keepAliveVer[0] = r[28] 27 | c.keepAliveVer[1] = r[29] 28 | if c.extra() { 29 | buf = c.buf40(true, true) 30 | if err = c.WriteWithTimeout(buf); err != nil { 31 | logger.Errorf("conn.Write(%v) error(%v)", buf, err) 32 | return 33 | } 34 | r = make([]byte, 512) 35 | if err = c.ReadWithTimeout(r); err != nil { 36 | logger.Errorf("conn.Read() error(%v)", err) 37 | return 38 | } 39 | c.Count++ 40 | } 41 | // 40_1 42 | buf = c.buf40(true, false) 43 | if err = c.WriteWithTimeout(buf); err != nil { 44 | logger.Errorf("conn.Write(%v) error(%v)", buf, err) 45 | return 46 | } 47 | r = make([]byte, 64) 48 | if err = c.ReadWithTimeout(r); err != nil { 49 | logger.Errorf("conn.Read() error(%v)", err) 50 | return 51 | } 52 | c.Count++ 53 | copy(c.tail2, r[16:20]) 54 | // 40_2 55 | buf = c.buf40(false, false) 56 | if err = c.WriteWithTimeout(buf); err != nil { 57 | logger.Errorf("conn.Write(%v) error(%v)", buf, err) 58 | return 59 | } 60 | if err = c.ReadWithTimeout(r); err != nil { 61 | logger.Errorf("conn.Read() error(%v)", err) 62 | } 63 | c.Count++ 64 | return 65 | } 66 | 67 | func (c *Client) buf38() (buf []byte) { 68 | buf = make([]byte, 0, 38) 69 | buf = append(buf, byte(0xff)) // [0:1] 70 | buf = append(buf, c.md5a...) // [1:17] 71 | buf = append(buf, bytes.Repeat([]byte{0x00}, 3)...) // [17:20] 72 | buf = append(buf, c.tail1...) // [20:36] 73 | for i := 0; i < 2; i++ { // [36:38] 74 | buf = append(buf, byte(rand.Int())) 75 | } 76 | return 77 | } 78 | 79 | func (c *Client) buf40(first, extra bool) (buf []byte) { 80 | buf = make([]byte, 0, 40) 81 | buf = append(buf, []byte{0x07, byte(c.Count), 0x28, 0x00, 0x0b}...) // [0:5] 82 | // keep40_1 keep40_2 83 | // 发送 接收 发送 接收 84 | // 0x01 0x02 0x03 0xx04 85 | // [5:6] 86 | if first || extra { //keep40_1 keep40_extra 是 0x01 87 | buf = append(buf, byte(0x01)) 88 | } else { 89 | buf = append(buf, byte(0x03)) 90 | } 91 | // [6:8] 92 | if extra { 93 | buf = append(buf, []byte{0x0f, 0x27}...) 94 | } else { 95 | buf = append(buf, []byte{c.keepAliveVer[0], c.keepAliveVer[1]}...) 96 | } 97 | // [8:10] 98 | for i := 0; i < 2; i++ { 99 | buf = append(buf, byte(rand.Int())) 100 | } 101 | buf = append(buf, bytes.Repeat([]byte{0x00}, 6)...) //[10:16] 102 | buf = append(buf, c.tail2...) // [16:20] 103 | buf = append(buf, bytes.Repeat([]byte{0x00}, 4)...) //[20:24] 104 | if !first { 105 | tmp := make([]byte, len(buf)) 106 | copy(tmp, buf) 107 | tmp = append(tmp, c.clientIP...) 108 | sum := c.crc(tmp) 109 | buf = append(buf, sum...) // [24:28] 110 | buf = append(buf, c.clientIP...) // [28:32] 111 | buf = append(buf, bytes.Repeat([]byte{0x00}, 8)...) //[32:40] 112 | } 113 | if len(buf) < 40 { 114 | buf = append(buf, bytes.Repeat([]byte{0x00}, 40-len(buf))...) 115 | } 116 | return 117 | } 118 | 119 | func (c *Client) keepalive() { 120 | count := 0 121 | for { 122 | select { 123 | case _, ok := <-c.logoutCh: 124 | if !ok { 125 | logger.Debug("☑ Exited keepalive daemon") 126 | return 127 | } 128 | default: 129 | count++ 130 | logger.Debugf("- Sending keepalive #%d", count) 131 | if err := c.Alive(); err != nil { 132 | logger.Errorf("drcom.keepalive.Alive() error(%v)", err) 133 | time.Sleep(time.Second * 5) 134 | continue 135 | } 136 | logger.Debugf("- Keepalive #%d success", count) 137 | time.Sleep(time.Second * 20) 138 | } 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /drcom/login.go: -------------------------------------------------------------------------------- 1 | package drcom 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/Yesterday17/go-drcom-jlu/logger" 7 | "math/rand" 8 | ) 9 | 10 | var ( 11 | ErrMACAddrError = errors.New("invalid mac address") 12 | ErrIdentifyError = errors.New("invalid username or password") 13 | ErrUnknown = errors.New("login failed: unknown error") 14 | ) 15 | 16 | func (c *Client) Login() (err error) { 17 | var r, buf []byte 18 | 19 | buf = c.packetLogin() 20 | if err = c.WriteWithTimeout(buf); err != nil { 21 | logger.Errorf("conn.Write(%v) error(%v)", buf, err) 22 | return 23 | } 24 | r = make([]byte, 128) 25 | if err = c.ReadWithTimeout(r); err != nil { 26 | logger.Errorf("conn.Read() error(%v)", err) 27 | return 28 | } 29 | if r[0] != 0x04 { 30 | if r[0] == 0x05 { 31 | if r[4] == 0x0B { 32 | err = ErrMACAddrError 33 | } else { 34 | err = ErrIdentifyError 35 | } 36 | } else { 37 | err = ErrUnknown 38 | } 39 | return 40 | } 41 | // 保存 tail1. 构造 keep38 要用 md5a(在mkptk中保存) 和 tail1 42 | // 注销也要用 tail1 43 | copy(c.tail1, r[23:39]) 44 | return 45 | } 46 | 47 | func (c *Client) packetLogin() (buf []byte) { 48 | var md5a, md5b, md5c, mac []byte 49 | 50 | buf = make([]byte, 0, 334+(len(c.config.Password)-1)/4*4) 51 | buf = append(buf, _codeIn, _type, _eof, byte(len(c.config.Username)+20)) // [0:4] 52 | 53 | // md5a 54 | md5a = c.md5([]byte{_codeIn, _type}, c.salt, []byte(c.config.Password)) 55 | copy(c.md5a, md5a) 56 | buf = append(buf, md5a...) // [4:20] 57 | 58 | // username 59 | user := make([]byte, 36) 60 | copy(user, c.config.Username) 61 | buf = append(buf, user...) // [20:56] 62 | buf = append(buf, _controlCheck, _adapterNum) //[56:58] 63 | 64 | // md5a xor mac 65 | mac, _ = MACHex2Bytes(c.config.MAC) 66 | for i := 0; i < 6; i++ { 67 | mac[i] = mac[i] ^ c.md5a[i] 68 | } 69 | buf = append(buf, mac...) // [58:64] 70 | 71 | // md5b 72 | md5b = c.md5([]byte{0x01}, []byte(c.config.Password), []byte(c.salt), []byte{0x00, 0x00, 0x00, 0x00}) 73 | buf = append(buf, md5b...) // [64:80] 74 | buf = append(buf, byte(0x01)) // [80:81] 75 | buf = append(buf, c.clientIP...) // [81:85] 76 | buf = append(buf, bytes.Repeat(_emptyIP, 3)...) // [85:97] 77 | 78 | // md5c 79 | tmp := make([]byte, len(buf)) 80 | copy(tmp, buf) 81 | tmp = append(tmp, []byte{0x14, 0x00, 0x07, 0x0b}...) 82 | md5c = c.md5(tmp) 83 | buf = append(buf, md5c[:8]...) // [97:105] 84 | buf = append(buf, _ipDog) // [105:106] 85 | buf = append(buf, _delimiter...) // [106:110] 86 | hostname := make([]byte, 32) 87 | copy(hostname, []byte(_hostName)) 88 | buf = append(buf, hostname...) // [110:142] 89 | buf = append(buf, _primaryDNS...) // [142:146] 90 | buf = append(buf, _dhcpServer...) // [146:150] 91 | buf = append(buf, _emptyIP...) // secondary dns, [150:154] 92 | buf = append(buf, bytes.Repeat(_delimiter, 2)...) // [154,162] 93 | buf = append(buf, []byte{0x94, 0x00, 0x00, 0x00}...) // [162,166] 94 | buf = append(buf, []byte{0x06, 0x00, 0x00, 0x00}...) // [166,170] 95 | buf = append(buf, []byte{0x02, 0x00, 0x00, 0x00}...) // [170,174] 96 | buf = append(buf, []byte{0xf0, 0x23, 0x00, 0x00}...) // [174,178] 97 | buf = append(buf, []byte{0x02, 0x00, 0x00, 0x00}...) // [178,182] 98 | buf = append(buf, []byte{ 99 | 0x44, 0x72, 0x43, 0x4f, 100 | 0x4d, 0x00, 0xcf, 0x07}...) // [182,190] 101 | buf = append(buf, 0x6a) // [190,191] 102 | buf = append(buf, bytes.Repeat([]byte{0x00}, 55)...) // [191:246] 103 | buf = append(buf, []byte{ 104 | 0x33, 0x64, 0x63, 0x37, 105 | 0x39, 0x66, 0x35, 0x32, 106 | 0x31, 0x32, 0x65, 0x38, 107 | 0x31, 0x37, 0x30, 0x61, 108 | 0x63, 0x66, 0x61, 0x39, 109 | 0x65, 0x63, 0x39, 0x35, 110 | 0x66, 0x31, 0x64, 0x37, 111 | 0x34, 0x39, 0x31, 0x36, 112 | 0x35, 0x34, 0x32, 0x62, 113 | 0x65, 0x37, 0x62, 0x31, 114 | }...) // [246:286] 115 | buf = append(buf, bytes.Repeat([]byte{0x00}, 24)...) // [286:310] 116 | buf = append(buf, _authVersion...) // [310:312] 117 | buf = append(buf, 0x00) // [312:313] 118 | pwdLen := len(c.config.Password) 119 | if pwdLen > 16 { 120 | pwdLen = 16 121 | } 122 | buf = append(buf, byte(pwdLen)) // [313:314] 123 | ror := c.ror(c.md5a, []byte(c.config.Password)) 124 | buf = append(buf, ror[:pwdLen]...) // [314:314+pwdLen] 125 | buf = append(buf, []byte{0x02, 0x0c}...) // [314+l:316+l] 126 | tmp = make([]byte, 0, len(buf)) 127 | copy(tmp, buf) 128 | tmp = append(tmp, []byte{0x01, 0x26, 0x07, 0x11, 0x00, 0x00}...) 129 | tmp = append(tmp, mac[:4]...) 130 | sum := c.checkSum(tmp) 131 | buf = append(buf, sum[:4]...) // [316+l,320+l] 132 | buf = append(buf, []byte{0x00, 0x00}...) // [320+l,322+l] 133 | buf = append(buf, mac...) // [322+l,328+l] 134 | zeroCount := (4 - pwdLen%4) % 4 135 | buf = append(buf, bytes.Repeat([]byte{0x00}, zeroCount)...) 136 | for i := 0; i < 2; i++ { 137 | buf = append(buf, byte(rand.Int())) 138 | } 139 | return 140 | } 141 | -------------------------------------------------------------------------------- /drcom/logout.go: -------------------------------------------------------------------------------- 1 | package drcom 2 | 3 | import ( 4 | "errors" 5 | "github.com/Yesterday17/go-drcom-jlu/logger" 6 | ) 7 | 8 | func (c *Client) logout() (err error) { 9 | var r, buf []byte 10 | 11 | buf = c.packetLogout() 12 | if err = c.WriteWithTimeout(buf); err != nil { 13 | logger.Errorf("conn.Write(%v) error(%v)", buf, err) 14 | return 15 | } 16 | r = make([]byte, 512) 17 | if err = c.ReadWithTimeout(r); err != nil { 18 | logger.Errorf("conn.Read() error(%v)", err) 19 | return 20 | } 21 | if r[0] != 0x04 { 22 | err = errors.New("failed to logout: unknown error") 23 | } 24 | return 25 | } 26 | 27 | func (c *Client) packetLogout() (buf []byte) { 28 | var md5, mac []byte 29 | 30 | buf = make([]byte, 0, 80) 31 | buf = append(buf, _codeOut, _type, _eof, byte(len(c.config.Username)+20)) 32 | 33 | // md5 34 | md5 = c.md5([]byte{_codeOut, _type}, c.salt, []byte(c.config.Password)) 35 | buf = append(buf, md5...) 36 | tmp := make([]byte, 36) 37 | copy(tmp, c.config.Username) 38 | buf = append(buf, tmp...) 39 | buf = append(buf, _controlCheck, _adapterNum) 40 | 41 | // md5 xor mac 42 | mac, _ = MACHex2Bytes(c.config.MAC) 43 | for i := 0; i < 6; i++ { 44 | mac[i] = mac[i] ^ md5[i] 45 | } 46 | buf = append(buf, mac...) // [58:64] 47 | buf = append(buf, c.tail1...) // [64:80] 48 | return 49 | } 50 | 51 | func (c *Client) Logout() { 52 | logger.Info("Logging out...") 53 | if err := c.Challenge(); err != nil { 54 | logger.Errorf("drcomSvc.Challenge(%d) error(%v)", c.ChallengeTimes, err) 55 | return 56 | } 57 | if err := c.logout(); err != nil { 58 | logger.Errorf("service.logout() error(%v)", err) 59 | return 60 | } 61 | logger.Info("Logged out") 62 | } 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Yesterday17/go-drcom-jlu 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/mdlayher/genetlink v0.0.0-20190828143517-e35f2bf499b9 // indirect 7 | github.com/mdlayher/wifi v0.0.0-20190303161829-b1436901ddee 8 | github.com/vishvananda/netlink v1.0.0 9 | github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect 10 | golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 2 | github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= 3 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 4 | github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a h1:84IpUNXj4mCR9CuCEvSiCArMbzr/TMbuPIadKDwypkI= 5 | github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= 6 | github.com/mdlayher/genetlink v0.0.0-20190828143517-e35f2bf499b9 h1:o+ckyx58UC6Itoo7sEwmXMpHcnI31lRK6w4M5gQMIMw= 7 | github.com/mdlayher/genetlink v0.0.0-20190828143517-e35f2bf499b9/go.mod h1:jdlTGSEt8SRUJPk5+vLsPyojmLVAxsOKNjVkqrixnJ8= 8 | github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= 9 | github.com/mdlayher/netlink v0.0.0-20190828143259-340058475d09 h1:U2vuol6i4UF6MSpZJclH4HHiLRMoq1NAzxpIpCUJK/Y= 10 | github.com/mdlayher/netlink v0.0.0-20190828143259-340058475d09/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= 11 | github.com/mdlayher/wifi v0.0.0-20190303161829-b1436901ddee h1:hZDujBrW3ye2xxdKNFYT59D4yCH5Q0zLuNBNtysKtok= 12 | github.com/mdlayher/wifi v0.0.0-20190303161829-b1436901ddee/go.mod h1:Evt/EIne46u9PtQbeTx2NTcqURpr5K4SvKtGmBuDPN8= 13 | github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= 14 | github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= 15 | github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= 16 | github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= 17 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 18 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 19 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= 20 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 21 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 22 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 23 | golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 24 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 25 | golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69 h1:Wdn4Yb8d5VrsO3jWgaeSZss09x1VLVBMePDh4VW/xSQ= 26 | golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 28 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "time" 8 | ) 9 | 10 | type Logger struct { 11 | logger *log.Logger 12 | Prefix string 13 | Icon string 14 | } 15 | 16 | func NewLogger(out io.Writer, name string, icon string, flag int) *Logger { 17 | return &Logger{ 18 | logger: log.New(out, "", flag), 19 | Prefix: fmt.Sprintf("[GDJ][%s]", name), 20 | Icon: icon, 21 | } 22 | } 23 | 24 | func (l *Logger) SetPrefix() { 25 | prefix := fmt.Sprintf("%s[%s] ", l.Prefix, time.Now().Format("15:04:05")) 26 | if l.Icon != "" { 27 | prefix += l.Icon + " " 28 | } 29 | l.logger.SetPrefix(prefix) 30 | } 31 | 32 | func (l *Logger) Print(text string) { 33 | l.SetPrefix() 34 | l.logger.Print(text) 35 | } 36 | 37 | func (l *Logger) Printf(format string, v ...interface{}) { 38 | l.SetPrefix() 39 | l.logger.Printf(format, v...) 40 | } 41 | 42 | func (l *Logger) Println(v ...interface{}) { 43 | l.logger.Println(v...) 44 | } 45 | 46 | var debugLogger, infoLogger, warnLogger, errorLogger *Logger 47 | 48 | func Init(dw, iw, ww, ew io.Writer) { 49 | debugLogger = NewLogger(dw, "DEBUG", "", 0) 50 | infoLogger = NewLogger(iw, "INFO", "", 0) 51 | warnLogger = NewLogger(ww, "WARN", "⚠️", 0) 52 | errorLogger = NewLogger(ew, "ERROR", "☒", log.Lshortfile) 53 | } 54 | 55 | func Debug(v ...interface{}) { 56 | debugLogger.Println(v...) 57 | } 58 | 59 | func Debugf(format string, v ...interface{}) { 60 | debugLogger.Printf(format, v...) 61 | } 62 | 63 | func Info(v ...interface{}) { 64 | infoLogger.Println(v...) 65 | } 66 | 67 | func Infof(format string, v ...interface{}) { 68 | infoLogger.Printf(format, v...) 69 | } 70 | 71 | func Warn(v ...interface{}) { 72 | warnLogger.Println(v...) 73 | } 74 | 75 | func Warnf(format string, v ...interface{}) { 76 | warnLogger.Printf(format, v...) 77 | } 78 | 79 | func Error(v ...interface{}) { 80 | errorLogger.Println(v...) 81 | } 82 | 83 | func Errorf(format string, v ...interface{}) { 84 | errorLogger.Printf(format, v...) 85 | } 86 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/Yesterday17/go-drcom-jlu/drcom" 6 | "github.com/Yesterday17/go-drcom-jlu/logger" 7 | "log" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | ) 12 | 13 | var ( 14 | activeMAC = "" 15 | client *drcom.Client 16 | cfg *drcom.Config 17 | ) 18 | 19 | // return code list 20 | // 10 failed to parse config file 21 | 22 | func main() { 23 | var cfgPath string 24 | var err error 25 | 26 | flag.StringVar(&cfgPath, "config", "./config.json", "配置文件的路径") 27 | flag.Parse() 28 | 29 | Interfaces = make(map[string]*Interface) 30 | 31 | if err = initWireless(); err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | if err = initWired(); err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | // 加载配置文件 40 | cfg, err = ReadConfig(cfgPath) 41 | if err != nil { 42 | log.Println(err) 43 | os.Exit(10) 44 | } 45 | 46 | // 检查配置文件的 MAC 地址是否与 WiFi / 有线网卡 的 MAC 匹配 47 | var MAC string 48 | for _, inf := range Interfaces { 49 | if inf.Address == cfg.MAC { 50 | if inf.IsWireless { 51 | logger.Warn("Wireless MAC address detected") 52 | } 53 | MAC = inf.Address 54 | break 55 | } 56 | } 57 | 58 | // 未检测到对应配置文件的 MAC 地址 59 | if MAC == "" { 60 | logger.Error("No matching MAC address detected") 61 | os.Exit(10) 62 | } else { 63 | inf := Interfaces[MAC] 64 | if !inf.IsSchoolNet() { 65 | MAC = "" 66 | } 67 | 68 | // 当 MAC 对应的接口未连接时 搜索无线网卡 69 | if !inf.Connected { 70 | for _, inf2 := range Interfaces { 71 | if inf2.IsWireless && inf2.IsSchoolNet() { 72 | MAC = inf2.Address 73 | break 74 | } 75 | } 76 | } 77 | } 78 | 79 | go watchNetStatus() 80 | 81 | if MAC != "" { 82 | NewClient(MAC) 83 | } 84 | 85 | // 处理退出信号 86 | sig := make(chan os.Signal, 1) 87 | signal.Notify(sig, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) 88 | for { 89 | s := <-sig 90 | log.Printf("Exiting with signal %s", s.String()) 91 | if activeMAC != "" && client != nil { 92 | // client.Logout() 93 | _ = client.Close() 94 | } 95 | return 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /network.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Yesterday17/go-drcom-jlu/drcom" 6 | "github.com/Yesterday17/go-drcom-jlu/logger" 7 | "github.com/mdlayher/wifi" 8 | "github.com/vishvananda/netlink" 9 | "log" 10 | ) 11 | 12 | type Interface struct { 13 | // 初始化后不应改变的值 14 | Name string 15 | Address string 16 | IsWireless bool 17 | 18 | // 随接口状态变化的值 19 | Connected bool 20 | SSID string 21 | CanPingUIMS bool 22 | } 23 | 24 | func (i *Interface) IsSchoolNet() bool { 25 | if !i.Connected { 26 | return false 27 | } 28 | 29 | if i.IsWireless { 30 | return i.SSID == "JLU.PC" 31 | } else { 32 | return true 33 | // return i.CanPingUIMS 34 | } 35 | } 36 | 37 | // MAC -> Interface 的 Map 38 | var Interfaces map[string]*Interface 39 | 40 | // TODO: 支持 Windows 41 | func initWireless() error { 42 | if client, err := wifi.New(); err != nil { 43 | return err 44 | } else { 45 | defer client.Close() 46 | interfaces, err := client.Interfaces() 47 | if err != nil { 48 | return err 49 | } 50 | 51 | for _, inf := range interfaces { 52 | if inf.Name == "" { 53 | continue 54 | } 55 | 56 | MAC := inf.HardwareAddr.String() 57 | _interface := &Interface{ 58 | Name: inf.Name, 59 | Address: MAC, 60 | Connected: false, 61 | IsWireless: true, 62 | SSID: "", 63 | } 64 | Interfaces[MAC] = _interface 65 | 66 | bss, err := client.BSS(inf) 67 | if err != nil { 68 | continue 69 | } 70 | _interface.Connected = true 71 | _interface.SSID = bss.SSID 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | // TODO: https://github.com/Microsoft/hcsshim 78 | func initWired() error { 79 | list, err := netlink.LinkList() 80 | if err != nil { 81 | return err 82 | } 83 | 84 | for _, link := range list { 85 | // 避免初始化环回接口 86 | if link.Attrs().EncapType == "loopback" { 87 | continue 88 | } 89 | 90 | MAC := link.Attrs().HardwareAddr.String() 91 | if Interfaces[MAC] != nil { 92 | // WiFi 网络已初始化 跳过 93 | continue 94 | } 95 | 96 | // 设置有线网络 97 | inf := &Interface{ 98 | Name: link.Attrs().Name, 99 | Address: MAC, 100 | Connected: false, 101 | IsWireless: false, 102 | SSID: "", 103 | } 104 | Interfaces[MAC] = inf 105 | inf.Connected = link.Attrs().OperState.String() == "up" 106 | } 107 | return nil 108 | } 109 | 110 | func getSSID(MAC string) (string, error) { 111 | if client, err := wifi.New(); err != nil { 112 | return "", err 113 | } else { 114 | defer client.Close() 115 | interfaces, err := client.Interfaces() 116 | if err != nil { 117 | return "", err 118 | } 119 | 120 | for _, inf := range interfaces { 121 | if inf.HardwareAddr.String() != MAC || inf.Name == "" { 122 | continue 123 | } 124 | 125 | bss, err := client.BSS(inf) 126 | if err != nil { 127 | return "", nil 128 | } 129 | 130 | return bss.SSID, nil 131 | } 132 | return "", fmt.Errorf("failed to get ssid") 133 | } 134 | } 135 | 136 | func watchNetStatus() { 137 | // 建立 ch 138 | ch := make(chan netlink.LinkUpdate) 139 | 140 | // 注册监听 Addr 变化 141 | done := make(chan struct{}) 142 | if err := netlink.LinkSubscribe(ch, done); err != nil { 143 | log.Fatal(err) 144 | } 145 | 146 | for { 147 | select { 148 | case update := <-ch: 149 | if update.Attrs().MTU > 0 && 150 | update.Attrs().Name != "" && 151 | update.Flags >= 65536 { 152 | // 更新接口状态 153 | MAC := update.Attrs().HardwareAddr.String() 154 | inf := Interfaces[MAC] 155 | if inf == nil { 156 | continue 157 | } 158 | 159 | // 标记接口为已连接 160 | inf.Connected = true 161 | 162 | if inf.IsWireless { 163 | if ssid, err := getSSID(inf.Address); err != nil { 164 | logger.Error("Failed to get SSID of connecting WiFi") 165 | continue 166 | } else { 167 | inf.SSID = ssid 168 | if ssid != "JLU.PC" { 169 | logger.Info("Skipping non-JLU.PC WiFI") 170 | continue 171 | } 172 | } 173 | } 174 | 175 | // TODO: 检查网络可用性 176 | // 有线连接代替无线连接 177 | if infNow := Interfaces[activeMAC]; !inf.IsWireless && infNow.IsWireless { 178 | _ = client.Close() 179 | activeMAC = "" 180 | inf.SSID = "" 181 | logger.Infof("- Interface %s disconnected: %s", inf.Name, inf.Address) 182 | } 183 | 184 | if activeMAC == "" { 185 | logger.Debugf("%v", update) 186 | NewClient(MAC) 187 | } 188 | } else if update.Flags < 65536 && 189 | update.Attrs().Name != "" { 190 | // 更新接口状态 191 | MAC := update.Attrs().HardwareAddr.String() 192 | inf := Interfaces[MAC] 193 | if inf == nil { 194 | continue 195 | } 196 | inf.Connected = false 197 | 198 | if activeMAC != "" && 199 | update.Attrs().HardwareAddr.String() == activeMAC { 200 | _ = client.Close() 201 | activeMAC = "" 202 | inf.SSID = "" 203 | logger.Info("- Network disconnected") 204 | 205 | // 寻找其他已接入网络 206 | for _, inf := range Interfaces { 207 | if inf.IsSchoolNet() { 208 | NewClient(inf.Address) 209 | break 210 | } 211 | } 212 | } 213 | } 214 | } 215 | } 216 | } 217 | 218 | func NewClient(MAC string) { 219 | inf := Interfaces[MAC] 220 | logger.Infof("- Connecting with interface %s - %s", inf.Name, inf.Address) 221 | 222 | activeMAC = MAC 223 | client = drcom.New(cfg) 224 | client.Start() 225 | } 226 | -------------------------------------------------------------------------------- /release/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Yesterday17 2 | pkgname=go-drcom-jlu-git 3 | _pkgname=go-drcom-jlu 4 | pkgver=1.0.7 5 | pkgrel=8 6 | pkgdesc="JLU drcom client written in golang." 7 | arch=('i686' 'x86_64') 8 | url="https://github.com/Yesterday17/go-drcom-jlu" 9 | license=('MIT') 10 | makedepends=( 11 | 'go' 12 | 'git' 13 | ) 14 | backup=('etc/go-drcom-jlu/config.json') 15 | 16 | source=( 17 | "$_pkgname::git+https://github.com/Yesterday17/go-drcom-jlu.git" 18 | ) 19 | 20 | md5sums=('SKIP') 21 | 22 | build() { 23 | export GO111MODULE=on 24 | 25 | if [ -L "$srcdir/$_pkgname" ]; then 26 | rm "$srcdir/$_pkgname" -rf 27 | mv "$srcdir/go/src/$_pkgname/" "$srcdir/$_pkgname" 28 | fi 29 | 30 | rm -rf "go/src" 31 | 32 | mkdir -p "go/src" 33 | 34 | mv "$_pkgname" "go/src/" 35 | 36 | ln -sf "go/src/$_pkgname/" "$_pkgname" 37 | cd "go/src/${_pkgname}/" 38 | 39 | echo ":: Building binary" 40 | go build 41 | } 42 | 43 | package() { 44 | cd "${_pkgname}" 45 | install -Dm755 ${_pkgname} "${pkgdir}/usr/bin/${_pkgname}" 46 | install -Dm644 release/config.json -t "${pkgdir}/etc/go-drcom-jlu/" 47 | install -Dm644 release/go-drcom-jlu.service -t "${pkgdir}/usr/lib/systemd/system" 48 | } 49 | -------------------------------------------------------------------------------- /release/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mac": "MAC", 3 | "username": "username", 4 | "password": "password", 5 | "retry": 3, 6 | "timeout": 3, 7 | "log_level": "info", 8 | "log_path": "" 9 | } -------------------------------------------------------------------------------- /release/go-drcom-jlu.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=go-drcom-jlu Service 3 | After=network.target 4 | Wants=network.target 5 | StartLimitInterval=1500 6 | StartLimitBurst=10 7 | 8 | [Service] 9 | Type=simple 10 | ExecStart=/usr/bin/go-drcom-jlu -config /etc/go-drcom-jlu/config.json 11 | Restart=on-failure 12 | Restart=always 13 | RestartSec=20 14 | # Don't restart in the case of configuration error 15 | RestartPreventExitStatus=10 16 | 17 | [Install] 18 | WantedBy=multi-user.target --------------------------------------------------------------------------------