├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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
--------------------------------------------------------------------------------