-
Notifications
You must be signed in to change notification settings - Fork 98
/
listener.go
313 lines (280 loc) · 11.5 KB
/
listener.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
package minecraft
import (
"fmt"
"github.com/sandertv/go-raknet"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource"
"io/ioutil"
"log"
"net"
"os"
"sync"
"sync/atomic"
"time"
)
// Listener implements a Minecraft listener on top of an unspecific net.Listener. It abstracts away the
// login sequence of connecting clients and provides the implements the net.Listener interface to provide a
// consistent API.
type Listener struct {
// ErrorLog is a log.Logger that errors that occur during packet handling of clients are written to. By
// default, ErrorLog is set to one equal to the global logger.
ErrorLog *log.Logger
// AuthenticationDisables specifies if authentication of players that join is disabled. If set to true, no
// verification will be done to ensure that the player connecting is authenticated using their XBOX Live
// account.
AuthenticationDisabled bool
// ServerName is the server name shown in the in-game menu, above the player list. The name cannot be
// changed after a player is connected. By default, 'Minecraft Server' will be set.
ServerName string
// MaximumPlayers is the maximum amount of players accepted in the server. If non-zero, players that
// attempt to join while the server is full will be kicked during login. If zero, the maximum player count
// will be dynamically updated each time a player joins, so that an unlimited amount of players is
// accepted into the server.
MaximumPlayers int
// ShowVersion specifies if the supported Minecraft version should be shown in the MOTD of the server. If
// set to false, if set to true, the lowest supported version will be displayed.
ShowVersion bool
// ResourcePacks is a slice of resource packs that the listener may hold. Each client will be asked to
// download these resource packs upon joining.
ResourcePacks []*resource.Pack
// TexturePacksRequired specifies if clients that join must accept the texture pack in order for them to
// be able to join the server. If they don't accept, they can only leave the server.
TexturePacksRequired bool
// PacketFunc is called whenever a packet is read from or written to a connection returned when using
// Listener.Accept. It includes packets that are otherwise covered in the connection sequence, such as the
// Login packet. The function is called with the header of the packet and its raw payload, the address
// from which the packet originated, and the destination address.
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
// SendPacketViolations makes the Listener send PacketViolationWarnings to clients connected when it
// receives packets it cannot decode properly. Additionally, it will log PacketViolationWarnings coming
// from the client.
SendPacketViolations bool
// playerCount is the amount of players connected to the server. If MaximumPlayers is non-zero and equal
// to the playerCount, no more players will be accepted.
playerCount *int32
listener net.Listener
hijackingPong atomic.Value
incoming chan *Conn
close chan struct{}
mu sync.Mutex
p ServerStatusProvider
}
// Listen announces on the local network address. The network is typically "raknet".
// If the host in the address parameter is empty or a literal unspecified IP address, Listen listens on all
// available unicast and anycast IP addresses of the local system.
func (listener *Listener) Listen(network, address string) error {
var netListener net.Listener
var err error
switch network {
case "raknet":
// Listen specifically for the RakNet network type, as the standard library (obviously) doesn't
// implement that.
var l *raknet.Listener
l, err = raknet.Listen(address)
if err == nil {
l.ErrorLog = log.New(ioutil.Discard, "", 0)
netListener = l
}
default:
// Otherwise fall back to the standard net.Listen.
netListener, err = net.Listen(network, address)
}
if err != nil {
return err
}
var count int32
if listener.ErrorLog == nil {
listener.ErrorLog = log.New(os.Stderr, "", log.LstdFlags)
}
if listener.ServerName == "" {
listener.ServerName = "Minecraft Server"
}
listener.listener = netListener
listener.incoming = make(chan *Conn)
listener.close = make(chan struct{})
listener.hijackingPong.Store(false)
listener.playerCount = &count
// Actually start listening.
go listener.listen()
return nil
}
// Listen announces on the local network address. The network must be "tcp", "tcp4", "tcp6", "unix",
// "unixpacket" or "raknet". A Listener is returned which may be used to accept connections.
// If the host in the address parameter is empty or a literal unspecified IP address, Listen listens on all
// available unicast and anycast IP addresses of the local system.
// Listen has the default values for the fields of Listener filled out. To use different values for these
// fields, call &Listener{}.Listen() instead.
func Listen(network, address string) (*Listener, error) {
l := &Listener{}
err := l.Listen(network, address)
return l, err
}
// Accept accepts a fully connected (on Minecraft layer) connection which is ready to receive and send
// packets. It is recommended to cast the net.Conn returned to a *minecraft.Conn so that it is possible to
// use the conn.ReadPacket() and conn.WritePacket() methods.
// Accept returns an error if the listener is closed.
func (listener *Listener) Accept() (net.Conn, error) {
conn, ok := <-listener.incoming
if !ok {
return nil, fmt.Errorf("accept: listener closed")
}
return conn, nil
}
// Disconnect disconnects a Minecraft Conn passed by first sending a disconnect with the message passed, and
// closing the connection after. If the message passed is empty, the client will be immediately sent to the
// player list instead of a disconnect screen.
func (listener *Listener) Disconnect(conn *Conn, message string) error {
_ = conn.WritePacket(&packet.Disconnect{
HideDisconnectionScreen: message == "",
Message: message,
})
return conn.Close()
}
// StatusProvider sets a server status provider to dynamically provide the status of the server.
// StatusProvider will overwrite the status shown in the server list through the MaximumPlayers field and the
// current connected players.
func (listener *Listener) StatusProvider(p ServerStatusProvider) {
listener.mu.Lock()
listener.p = p
listener.mu.Unlock()
listener.updatePongData()
go func() {
ticker := time.NewTicker(time.Second * 3)
defer ticker.Stop()
for {
select {
case <-ticker.C:
listener.updatePongData()
case <-listener.close:
return
}
}
}()
}
// HijackPong hijacks the pong response from a server at an address passed. The listener passed will
// continuously update its pong data by hijacking the pong data of the server at the address.
// The hijack will last until the listener is shut down.
// If the address passed could not be resolved, an error is returned.
// Calling HijackPong means that any current and future pong data set using listener.PongData is overwritten
// each update.
func (listener *Listener) HijackPong(address string) error {
listener.hijackingPong.Store(true)
return listener.listener.(*raknet.Listener).HijackPong(address)
}
// Addr returns the address of the underlying listener.
func (listener *Listener) Addr() net.Addr {
return listener.listener.Addr()
}
// Close closes the listener and the underlying net.Listener. Pending calls to Accept will fail immediately.
func (listener *Listener) Close() error {
return listener.listener.Close()
}
// updatePongData updates the pong data of the listener using the current only players, maximum players and
// server name of the listener, provided the listener isn't currently hijacking the pong of another server.
func (listener *Listener) updatePongData() {
if listener.hijackingPong.Load().(bool) {
return
}
listener.mu.Lock()
m := listener.p
listener.mu.Unlock()
var (
maxCount, current int32
serverName string
)
if m == nil {
maxCount = int32(listener.MaximumPlayers)
serverName = listener.ServerName
current = atomic.LoadInt32(listener.playerCount)
if maxCount == 0 {
// If the maximum amount of allowed players is 0, we set it to the the current amount of line players
// plus 1, so that new players can always join.
maxCount = current + 1
}
} else {
motd, online, max := m.ServerStatus()
serverName, maxCount, current = motd, int32(max), int32(online)
}
var ver string
if listener.ShowVersion {
ver = protocol.CurrentVersion
}
rakListener := listener.listener.(*raknet.Listener)
rakListener.PongData([]byte(fmt.Sprintf("MCPE;%v;%v;%v;%v;%v;%v;Minecraft Server;%v;%v;%v;%v;",
serverName, protocol.CurrentProtocol, ver, current, maxCount, rakListener.ID(),
"Creative", 1, listener.Addr().(*net.UDPAddr).Port, listener.Addr().(*net.UDPAddr).Port,
)))
}
// listen starts listening for incoming connections and packets. When a player is fully connected, it submits
// it to the accepted connections channel so that a call to Accept can pick it up.
func (listener *Listener) listen() {
listener.updatePongData()
defer func() {
close(listener.incoming)
close(listener.close)
_ = listener.Close()
}()
for {
netConn, err := listener.listener.Accept()
if err != nil {
// The underlying listener was closed, meaning we should return immediately so this listener can
// close too.
return
}
listener.createConn(netConn)
}
}
// createConn creates a connection for the net.Conn passed and adds it to the listener, so that it may be
// accepted once its login sequence is complete.
func (listener *Listener) createConn(netConn net.Conn) {
conn := newConn(netConn, nil, listener.ErrorLog)
conn.packetFunc = listener.PacketFunc
conn.texturePacksRequired = listener.TexturePacksRequired
conn.resourcePacks = listener.ResourcePacks
conn.gameData.WorldName = listener.ServerName
conn.authEnabled = !listener.AuthenticationDisabled
conn.sendPacketViolations = listener.SendPacketViolations
if atomic.LoadInt32(listener.playerCount) == int32(listener.MaximumPlayers) && listener.MaximumPlayers != 0 {
// The server was full. We kick the player immediately and close the connection.
_ = conn.WritePacket(&packet.PlayStatus{Status: packet.PlayStatusLoginFailedServerFull})
_ = conn.Close()
return
}
atomic.AddInt32(listener.playerCount, 1)
listener.updatePongData()
go listener.handleConn(conn)
}
// handleConn handles an incoming connection of the Listener. It will first attempt to get the connection to
// log in, after which it will expose packets received to the user.
func (listener *Listener) handleConn(conn *Conn) {
defer func() {
_ = conn.Close()
atomic.AddInt32(listener.playerCount, -1)
listener.updatePongData()
}()
for {
// We finally arrived at the packet decoding loop. We constantly decode packets that arrive
// and push them to the Conn so that they may be processed.
packets, err := conn.decoder.Decode()
if err != nil {
if !raknet.ErrConnectionClosed(err) {
listener.ErrorLog.Printf("error reading from client connection: %v\n", err)
}
return
}
for _, data := range packets {
loggedInBefore := conn.loggedIn
if err := conn.handleIncoming(data); err != nil {
listener.ErrorLog.Printf("error: %v", err)
return
}
if !loggedInBefore && conn.loggedIn {
// The connection was previously not logged in, but was after receiving this packet,
// meaning the connection is fully completely now. We add it to the channel so that
// a call to Accept() can receive it.
listener.incoming <- conn
}
}
}
}