-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
160 lines (120 loc) · 3.14 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package rcon
import (
"encoding/binary"
"fmt"
"net"
"sync"
"time"
"github.com/pkg/errors"
"github.com/refractorgscm/rcon2/packet"
)
type Client struct {
config *Config
conn *net.TCPConn
log Logger
cmdMutex sync.Mutex
}
type Config struct {
Host string
Port uint16
Password string
// ConnTimeout is the timeout used when establishing a new RCON connection
//
// Default: 5 seconds
ConnTimeout time.Duration
// EndianMode represents the byte order being used by whatever game you're using this module with.
// Valve games typically use little endian, but others may use big endian. ymmv.
EndianMode binary.ByteOrder
ReadDeadline time.Duration
WriteDeadline time.Duration
RestrictedPacketIDs []int32
}
var DefaultConfig = &Config{
ConnTimeout: time.Second*5,
EndianMode: binary.LittleEndian,
ReadDeadline: time.Second*2,
WriteDeadline: time.Second*2,
RestrictedPacketIDs: []int32{},
}
func NewClient(host string, port uint16, password string) *Client {
config := DefaultConfig
config.Host = host
config.Port = port
config.Password = password
return NewClientFromConfig(DefaultConfig)
}
func NewClientFromConfig(config *Config) *Client {
c := &Client{
config: config,
}
if c.log == nil {
c.log = &DefaultLogger{}
}
if c.config.EndianMode == nil {
c.config.EndianMode = binary.LittleEndian
}
if c.config.ConnTimeout == 0 {
c.config.ConnTimeout = DefaultConfig.ConnTimeout
}
if c.config.ReadDeadline == 0 {
c.config.ConnTimeout = DefaultConfig.ReadDeadline
}
if c.config.WriteDeadline == 0 {
c.config.ConnTimeout = DefaultConfig.WriteDeadline
}
return c
}
func (c *Client) SetLogger(logger Logger) {
c.log = logger
}
func (c *Client) Connect() error {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.config.Host, c.config.Port), c.config.ConnTimeout)
if err != nil {
return errors.Wrap(err, "tcp dial error")
}
c.log.Debug("Connection established")
var ok bool
c.conn, ok = conn.(*net.TCPConn)
if !ok {
return errors.Wrap(err, "tcp dial error")
}
if err := c.authenticate(); err != nil {
c.log.Debug("Authentication failed", err)
return err
}
return nil
}
func (c *Client) authenticate() error {
p := c.newPacket(packet.TypeAuth, c.config.Password)
if err := c.sendPacket(p); err != nil {
return errors.Wrap(err, "could not send packet")
}
res, err := c.readPacketTimeout()
if err != nil {
return errors.Wrap(err, "could not get auth response")
}
if res.Type != packet.TypeAuthRes {
return errors.New("packet was not of the type auth response")
}
if res.ID == packet.AuthFailedID {
return errors.Wrap(ErrAuthentication, "authentication failed")
}
c.log.Debug("Authenticated")
return nil
}
func (c *Client) RunCommand(cmd string) (string, error) {
c.cmdMutex.Lock()
defer c.cmdMutex.Unlock()
p := c.newPacket(packet.TypeCommand, cmd)
if err := c.sendPacket(p); err != nil {
return "", err
}
res, err := c.readPacket()
if err != nil {
return "", err
}
return string(res.Body), nil
}
func (c *Client) newPacket(pType packet.PacketType, body string) *packet.Packet {
return packet.New(c.config.EndianMode, pType, []byte(body), c.config.RestrictedPacketIDs)
}