diff --git a/rconRequest.go b/rconRequest.go new file mode 100644 index 0000000..cb2f813 --- /dev/null +++ b/rconRequest.go @@ -0,0 +1,41 @@ +package steam + +import ( + "bytes" + + "math/rand" +) + +const ( + SERVERDATA_AUTH = 3 + SERVERDATA_EXECCOMMAND = 2 + SERVERDATA_AUTH_RESPONSE = 2 + SERVERDATA_RESPONSE_VALUE = 0 +) + +type rconRequest struct { + size int32 + id int32 + reqType int32 + body string +} + +func newrconRequest(reqType int32, body string) *rconRequest { + return &rconRequest{ + size: int32(len(body) + 10), + id: rand.Int31(), + reqType: reqType, + body: body, + } +} + +func (r *rconRequest) constructPacket() []byte { + buf := new(bytes.Buffer) + writeLilEndianInt32(buf, r.size) + writeLilEndianInt32(buf, r.id) + writeLilEndianInt32(buf, r.reqType) + buf.WriteString(r.body) + writeNullTerminator(buf) + writeNullTerminator(buf) + return buf.Bytes() +} diff --git a/rconresponse.go b/rconresponse.go new file mode 100644 index 0000000..5cbd34d --- /dev/null +++ b/rconresponse.go @@ -0,0 +1,26 @@ +package steam + +import ( + "bytes" + + "github.com/golang/glog" +) + +type rconResponse struct { + size int32 + id int32 + reqType int32 + body string +} + +func newRconResponse(b []byte) *rconResponse { + buf := bytes.NewBuffer(b) + s := readLong(buf) + id := readLong(buf) + t := readLong(buf) + body := readBytes(buf, int(s-8)) + + resp := &rconResponse{s, id, t, string(body)} + glog.V(2).Infof("steam: rconResponse: %v", resp) + return resp +} diff --git a/samples/samplercon.go b/samples/samplercon.go new file mode 100644 index 0000000..f06e6d9 --- /dev/null +++ b/samples/samplercon.go @@ -0,0 +1,28 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/kidoman/go-steam" +) + +func main() { + flag.Parse() + server := &steam.Server{Addr: "0.1.2.3:27015"} + defer server.Close() + + authenticated, err := server.AuthenticateRcon("abc") + if err != nil { + panic(err) + } + fmt.Printf("authentication status %v\n", authenticated) + + comm := "status" + resp, err := server.ExecRconCommand(comm) + if err != nil { + panic(err) + } + + fmt.Printf("rcon command: %v response: %v\n", comm, resp) +} diff --git a/server.go b/server.go index e5e3826..6f111a3 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,8 @@ package steam import ( "errors" "time" + + "github.com/golang/glog" ) // Server represents a Source server. @@ -10,7 +12,9 @@ type Server struct { // IP:Port combination designating a single server. Addr string - socket *socket + udpSocket *udpSocket + + tcpSocket *tcpSocket initialized bool } @@ -25,7 +29,11 @@ func (s *Server) init() error { } var err error - if s.socket, err = newSocket(s.Addr); err != nil { + if s.udpSocket, err = newUdpSocket(s.Addr); err != nil { + return err + } + + if s.tcpSocket, err = newTcpSocket(s.Addr); err != nil { return err } @@ -39,7 +47,8 @@ func (s *Server) Close() { return } - s.socket.close() + s.udpSocket.close() + s.tcpSocket.close() } // Ping returns the RTT (round-trip time) to the server. @@ -54,8 +63,8 @@ func (s *Server) Ping() (time.Duration, error) { } start := time.Now() - s.socket.send(data) - if _, err := s.socket.receive(); err != nil { + s.udpSocket.send(data) + if _, err := s.udpSocket.receive(); err != nil { return 0, err } @@ -74,10 +83,10 @@ func (s *Server) Info() (*InfoResponse, error) { return nil, err } - if err := s.socket.send(data); err != nil { + if err := s.udpSocket.send(data); err != nil { return nil, err } - b, err := s.socket.receive() + b, err := s.udpSocket.receive() if err != nil { return nil, err } @@ -101,10 +110,10 @@ func (s *Server) PlayersInfo() (*PlayersInfoResponse, error) { if err != nil { return nil, err } - if err := s.socket.send(data); err != nil { + if err := s.udpSocket.send(data); err != nil { return nil, err } - b, err := s.socket.receive() + b, err := s.udpSocket.receive() if err != nil { return nil, err } @@ -121,10 +130,10 @@ func (s *Server) PlayersInfo() (*PlayersInfoResponse, error) { if err != nil { return nil, err } - if err := s.socket.send(data); err != nil { + if err := s.udpSocket.send(data); err != nil { return nil, err } - b, err = s.socket.receive() + b, err = s.udpSocket.receive() if err != nil { return nil, err } @@ -138,3 +147,56 @@ func (s *Server) PlayersInfo() (*PlayersInfoResponse, error) { return res, nil } + +func (s *Server) AuthenticateRcon(rconpasswd string) (bool, error) { + if err := s.init(); err != nil { + return false, err + } + + req := newrconRequest(SERVERDATA_AUTH, rconpasswd) + glog.V(2).Infof("steam: sending rcon auth request: %v", req) + packet := req.constructPacket() + + if err := s.tcpSocket.send(packet); err != nil { + return false, err + } + + resp, err := s.tcpSocket.receive() + if err != nil { + return false, err + } + + authResponse := newRconResponse(resp) + if req.id == authResponse.id { + return true, nil + } + return false, nil +} + +func (s *Server) ExecRconCommand(command string) (result string, err error) { + if err := s.init(); err != nil { + return "", err + } + + req := newrconRequest(SERVERDATA_EXECCOMMAND, command) + glog.V(2).Infof("steam: sending rcon exec command request: %v", req) + packet := req.constructPacket() + + if err := s.tcpSocket.send(packet); err != nil { + return "", err + } + + resp, err := s.tcpSocket.receive() + if err != nil { + return "", err + } + + commandResp := newRconResponse(resp) + + if req.id != commandResp.id { + err := errors.New("steam: response id does not match request id") + return "", err + } + + return commandResp.body, nil +} diff --git a/tcpsocket.go b/tcpsocket.go new file mode 100644 index 0000000..6b295d5 --- /dev/null +++ b/tcpsocket.go @@ -0,0 +1,57 @@ +package steam + +import ( + "net" + + "github.com/golang/glog" +) + +type tcpSocket struct { + conn *net.TCPConn + raddr *net.TCPAddr +} + +func newTcpSocket(addr string) (*tcpSocket, error) { + + raddr, err := net.ResolveTCPAddr("tcp4", addr) + if err != nil { + glog.Errorf("steam: could not resolve tcp addr coz: %v", err.Error()) + return nil, err + } + + conn, err := net.DialTCP("tcp4", nil, raddr) + if err != nil { + glog.Errorf("steam: could not dial tcp coz: %v", err.Error()) + return nil, err + } + + glog.V(2).Infof("steam: succesfully created tcp connection. conn:%v, raddr: %v", conn, raddr) + return &tcpSocket{conn, raddr}, nil +} + +func (s *tcpSocket) close() { + glog.V(2).Infof("steam: closing tcp connection") + s.conn.Close() +} + +func (s *tcpSocket) send(payload []byte) error { + glog.V(2).Infof("steam: sending payload %v", payload) + _, err := s.conn.Write(payload) + if err != nil { + glog.V(2).Infof("steam: error sending data: %v", err.Error()) + } + return nil +} + +func (s *tcpSocket) receive() ([]byte, error) { + var buf [4095]byte + glog.V(1).Infof("steam: reading from %v", s.raddr) + + n, err := s.conn.Read(buf[:]) + if err != nil { + return nil, err + } + glog.V(1).Infof("steam: received %v bytes from %v", n, s.raddr) + + return buf[:n], nil +} diff --git a/socket.go b/udpsocket.go similarity index 78% rename from socket.go rename to udpsocket.go index 350fe2b..03fd196 100644 --- a/socket.go +++ b/udpsocket.go @@ -7,12 +7,12 @@ import ( "github.com/golang/glog" ) -type socket struct { +type udpSocket struct { conn *net.UDPConn raddr *net.UDPAddr } -func newSocket(addr string) (*socket, error) { +func newUdpSocket(addr string) (*udpSocket, error) { raddr, err := net.ResolveUDPAddr("udp4", addr) if err != nil { return nil, err @@ -23,14 +23,14 @@ func newSocket(addr string) (*socket, error) { return nil, err } - return &socket{conn, raddr}, nil + return &udpSocket{conn, raddr}, nil } -func (s *socket) close() { +func (s *udpSocket) close() { s.conn.Close() } -func (s *socket) send(payload []byte) error { +func (s *udpSocket) send(payload []byte) error { glog.V(1).Infof("steam: sending %v bytes payload to %v", len(payload), s.raddr) glog.V(2).Infof("steam: sending payload to %v: %X", s.raddr, payload) n, err := s.conn.WriteToUDP(payload, s.raddr) @@ -44,7 +44,7 @@ func (s *socket) send(payload []byte) error { return nil } -func (s *socket) receivePacket() ([]byte, error) { +func (s *udpSocket) receivePacket() ([]byte, error) { var buf [1500]byte n, _, err := s.conn.ReadFromUDP(buf[:]) if err != nil { @@ -56,7 +56,7 @@ func (s *socket) receivePacket() ([]byte, error) { return buf[:n], nil } -func (s *socket) receive() ([]byte, error) { +func (s *udpSocket) receive() ([]byte, error) { buf, err := s.receivePacket() if err != nil { return nil, err diff --git a/wire.go b/wire.go index e38170b..3390ca1 100644 --- a/wire.go +++ b/wire.go @@ -139,3 +139,12 @@ func writeLong(buf *bytes.Buffer, v int32) { bytes := [4]byte{byte(v & 0xFF), byte(v >> 8 & 0xFF), byte(v >> 16 & 0xFF), byte(v >> 24 & 0xFF)} buf.Write(bytes[:]) } + +func writeLilEndianInt32(buf *bytes.Buffer, n int32) { + var b = []uint8{uint8(n), uint8(n >> 8), uint8(n >> 16), uint8(n >> 24)} + buf.Write(b) +} + +func writeNullTerminator(buf *bytes.Buffer) { + buf.WriteByte(0) +}