Beispiel #1
0
func getServerInfo(host string, timeout int) ([]byte, error) {
	conn, err := net.DialTimeout("udp", host, time.Duration(timeout)*time.Second)
	if err != nil {
		logger.LogSteamError(ErrHostConnection(err.Error()))
		return nil, ErrHostConnection(err.Error())
	}
	defer conn.Close()
	conn.SetDeadline(time.Now().Add(time.Duration(timeout-1) * time.Second))

	_, err = conn.Write(infoChallengeReq)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}

	var buf [maxPacketSize]byte
	numread, err := conn.Read(buf[:maxPacketSize])
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}
	serverInfo := make([]byte, numread)
	copy(serverInfo, buf[:numread])

	if !bytes.HasPrefix(serverInfo, expectedInfoRespHeader) {
		logger.LogSteamError(ErrPacketHeader)
		return nil, ErrPacketHeader
	}

	return serverInfo, nil
}
Beispiel #2
0
func parseRuleInfo(ruleinfo []byte) (map[string]string, error) {
	if !bytes.HasPrefix(ruleinfo, expectedRuleChunkHeader) {
		logger.LogSteamError(ErrPacketHeader)
		return nil, ErrPacketHeader
	}

	ruleinfo = bytes.TrimLeft(ruleinfo, headerStr)
	numrules := int(binary.LittleEndian.Uint16(ruleinfo[1:3]))

	if numrules == 0 {
		return nil, ErrNoRules
	}

	b := bytes.Split(ruleinfo[3:], []byte{0x00})
	m := make(map[string]string)

	var key string
	for i, y := range b {
		if i%2 != 1 {
			key = strings.TrimRight(string(y), "\x00")
		} else {
			m[key] = strings.TrimRight(string(b[i]), "\x00")
		}
	}

	return m, nil
}
Beispiel #3
0
func getRulesInfo(host string, timeout int) ([]byte, error) {
	conn, err := net.DialTimeout("udp", host, time.Duration(timeout)*time.Second)
	if err != nil {
		logger.LogSteamError(ErrHostConnection(err.Error()))
		return nil, ErrHostConnection(err.Error())
	}

	conn.SetDeadline(time.Now().Add(time.Duration(timeout-1) * time.Second))
	defer conn.Close()

	_, err = conn.Write(rulesChallengeReq)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}

	challengeNumResp := make([]byte, maxPacketSize)
	_, err = conn.Read(challengeNumResp)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}
	if !bytes.HasPrefix(challengeNumResp, expectedRulesRespHeader) {
		logger.LogSteamError(ErrChallengeResponse)
		return nil, ErrChallengeResponse
	}

	challengeNum := bytes.TrimLeft(challengeNumResp, headerStr)
	challengeNum = challengeNum[1:5]
	request := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0x56}
	request = append(request, challengeNum...)

	_, err = conn.Write(request)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}

	var buf [maxPacketSize]byte
	numread, err := conn.Read(buf[:maxPacketSize])
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}
	var rulesInfo []byte
	if bytes.HasPrefix(buf[:maxPacketSize], multiPacketRespHeader) {
		// handle multi-packet response
		first := buf[:maxPacketSize]
		first = first[:numread]
		rulesInfo, err = handleMultiPacketResponse(conn, first)
		if err != nil {
			logger.LogSteamError(ErrDataTransmit(err.Error()))
			return nil, ErrDataTransmit(err.Error())
		}
	} else {
		rulesInfo = make([]byte, numread)
		copy(rulesInfo, buf[:numread])
	}
	return rulesInfo, nil
}
Beispiel #4
0
func queryMasterServer(conn net.Conn, startaddress string,
	filter filters.Filter) ([]byte, error) {
	// Note: the connection is closed by the caller, do not close here, otherwise
	// Steam will continue to send the first batch of IPs and won't progress to the next batch
	startaddress = fmt.Sprintf("%s\x00", startaddress)
	addr := []byte(startaddress)
	request := []byte{0x31}
	request = append(request, filter.Region...)
	request = append(request, addr...)

	for i, f := range filter.Filters {
		for _, b := range f {
			request = append(request, b)
		}
		if i == len(filter.Filters)-1 {
			request = append(request, 0x00)
		}
	}

	_, err := conn.Write(request)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}

	var buf [maxPacketSize]byte
	numread, err := conn.Read(buf[:maxPacketSize])
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}

	masterResponse := make([]byte, numread)
	copy(masterResponse, buf[:numread])

	if !bytes.HasPrefix(masterResponse, expectedMasterRespHeader) {
		logger.LogSteamError(ErrPacketHeader)
		return nil, ErrPacketHeader
	}

	return masterResponse, nil
}
Beispiel #5
0
func parsePlayerInfo(unparsed []byte) ([]models.SteamPlayerInfo, error) {
	if !bytes.HasPrefix(unparsed, expectedPlayerChunkHeader) {
		logger.LogSteamError(ErrPacketHeader)
		return nil, ErrPacketHeader
	}
	unparsed = bytes.TrimLeft(unparsed, headerStr)
	numplayers := int(unparsed[1])

	if numplayers == 0 {
		return nil, ErrNoPlayers
	}

	players := []models.SteamPlayerInfo{}

	// index 0 = '44' | 1 = 'numplayers' byte | 2 = player 1 separator byte '00'
	// | 3 = start of player 1 name; additional player start indexes are player separator + 1
	startidx := 3
	var b []byte
	for i := 0; i < numplayers; i++ {
		if i == 0 {
			b = unparsed[startidx:]
		} else {
			b = b[startidx+1:]
		}
		nul := bytes.IndexByte(b, 0x00)
		name := b[:nul]              // string (variable length)
		score := b[nul+1 : nul+5]    // long (4 bytes)
		duration := b[nul+5 : nul+9] // float (4 bytes)
		startidx = nul + 9

		seconds, timeformatted := getDuration(duration)
		players = append(players, models.SteamPlayerInfo{
			Name:              string(name),
			Score:             int32(binary.LittleEndian.Uint32(score)),
			TimeConnectedSecs: seconds,
			TimeConnectedTot:  timeformatted,
		})
	}

	return players, nil
}
Beispiel #6
0
func getPlayerInfo(host string, timeout int) ([]byte, error) {
	conn, err := net.DialTimeout("udp", host, time.Duration(timeout)*time.Second)
	if err != nil {
		logger.LogSteamError(ErrHostConnection(err.Error()))
		return nil, ErrHostConnection(err.Error())
	}

	defer conn.Close()
	conn.SetDeadline(time.Now().Add(time.Duration(timeout-1) * time.Second))

	_, err = conn.Write(playerChallengeReq)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}

	challengeNumResp := make([]byte, maxPacketSize)
	_, err = conn.Read(challengeNumResp)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}
	if !bytes.HasPrefix(challengeNumResp, expectedPlayerRespHeader) {
		logger.LogSteamError(ErrChallengeResponse)
		return nil, ErrChallengeResponse
	}
	challengeNum := bytes.TrimLeft(challengeNumResp, headerStr)
	challengeNum = challengeNum[1:5]
	request := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0x55}
	request = append(request, challengeNum...)

	_, err = conn.Write(request)
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}
	var buf [maxPacketSize]byte
	numread, err := conn.Read(buf[:maxPacketSize])
	if err != nil {
		logger.LogSteamError(ErrDataTransmit(err.Error()))
		return nil, ErrDataTransmit(err.Error())
	}
	pi := make([]byte, numread)
	copy(pi, buf[:numread])

	return pi, nil
}
Beispiel #7
0
func getServers(filter filters.Filter) ([]string, error) {
	maxHosts := config.Config.SteamConfig.MaximumHostsToReceive
	var serverlist []string
	var c net.Conn
	var err error
	retrieved := 0
	addr := "0.0.0.0:0"

	c, err = net.DialTimeout("udp", masterServerHost,
		time.Duration(QueryTimeout)*time.Second)
	if err != nil {
		logger.LogSteamError(ErrHostConnection(err.Error()))
		return nil, ErrHostConnection(err.Error())
	}

	defer c.Close()
	c.SetDeadline(time.Now().Add(time.Duration(QueryTimeout) * time.Second))

	for {
		s, err := queryMasterServer(c, addr, filter)
		if err != nil {
			// usually timeout - Valve throttles >30 UDP packets (>6930 servers) per min
			logger.WriteDebug("Master query error, likely due to Valve throttle/timeout :%s",
				err)
			break
		}
		// get hosts:ports beginning after header (0xFF, 0xFF, 0xFF, 0xFF, 0x66, 0x0A)
		ips, total, err := extractHosts(s[6:])
		if err != nil {
			return nil, logger.LogAppErrorf("Error when extracting addresses: %s",
				err)
		}
		retrieved = retrieved + total
		if retrieved >= maxHosts {
			logger.LogSteamInfo("Max host limit of %d reached!", maxHosts)
			logger.WriteDebug("Max host limit of %d reached!", maxHosts)
			break
		}
		logger.LogSteamInfo("%d hosts retrieved so far from master.", retrieved)
		logger.WriteDebug("%d hosts retrieved so far from master.", retrieved)
		for _, ip := range ips {
			serverlist = append(serverlist, ip)
		}

		if (serverlist[len(serverlist)-1]) != "0.0.0.0:0" {
			logger.LogSteamInfo("More hosts need to be retrieved. Last IP was: %s",
				serverlist[len(serverlist)-1])
			logger.WriteDebug("More hosts need to be retrieved. Last IP was: %s",
				serverlist[len(serverlist)-1])
			addr = serverlist[len(serverlist)-1]
		} else {
			logger.LogSteamInfo("IP retrieval complete!")
			logger.WriteDebug("IP retrieval complete!")
			break
		}
	}
	// remove 0.0.0.0:0
	if len(serverlist) != 0 {
		if serverlist[len(serverlist)-1] == "0.0.0.0:0" {
			serverlist = serverlist[:len(serverlist)-1]
		}
	}
	return serverlist, nil
}
Beispiel #8
0
// Handle multi-packet responses for Source engine games.
func handleMultiPacketResponse(c net.Conn, firstReceived []byte) ([]byte,
	error) {
	// header: 4 bytes, 0xFFFFFFFE (already verified in caller)
	// ID: 4 bytes, signed
	// total # of packets: 1 byte, unsigned
	// current packet #, starts at zero: 1 byte, unsigned
	// size: 2 bytes, only for Orange Box Engine and Newer, signed
	// size & CRC32 sum for bzip2 compressed packets; but no longer used since late 2005

	// first 4 bytes [0:4] determine if split; we've already determined that it is
	id := int32(binary.LittleEndian.Uint32(firstReceived[4:8]))
	total := uint32(firstReceived[8])
	curNum := uint32(firstReceived[9])
	// note: size won't exist for 4 ancient appids (215,17550,17700,240 w/protocol 7)
	//size := int16(binary.LittleEndian.Uint16(firstReceived[10:12]))
	packets := make(map[uint32][]byte, total)
	packets[0] = firstReceived[12:]
	var buf [maxPacketSize]byte
	prevNum := curNum
	for {
		if curNum+1 == total {
			break
		}
		numread, err := c.Read(buf[:maxPacketSize])
		if err != nil {
			logger.LogSteamError(ErrMultiPacketTransmit(err.Error()))
			return nil, ErrMultiPacketTransmit(err.Error())
		}
		packet := buf[:maxPacketSize]
		packet = packet[:numread]
		curNum = uint32(packet[9])

		if prevNum == curNum {
			return nil, ErrMultiPacketDuplicate
		}
		prevNum = curNum

		if int32(binary.LittleEndian.Uint32(packet[4:8])) != id {
			logger.LogSteamError(ErrMultiPacketIDMismatch)
			return nil, ErrMultiPacketIDMismatch
		}
		if uint32(packet[9]) > total {
			logger.LogSteamError(ErrMultiPacketNumExceeded)
			return nil, ErrMultiPacketNumExceeded
		}
		// skip the header
		p := make([]byte, numread-12)
		copy(p, packet[12:])
		packets[curNum] = p

	}
	// sort packet keys
	var pnums u32slice
	for key := range packets {
		pnums = append(pnums, key)
	}
	pnums.Sort()

	var rules []byte
	for _, pn := range pnums {
		rules = append(rules, packets[pn]...)
	}
	return rules, nil
}
Beispiel #9
0
func parseServerInfo(serverinfo []byte) (models.SteamServerInfo, error) {
	if !bytes.HasPrefix(serverinfo, expectedInfoRespHeader) {
		logger.LogSteamError(ErrPacketHeader)
		return models.SteamServerInfo{}, ErrPacketHeader
	}

	serverinfo = bytes.TrimLeft(serverinfo, headerStr)

	// no info (should usually not happen)
	if len(serverinfo) <= 1 {
		logger.LogSteamError(ErrNoInfo)
		return models.SteamServerInfo{}, ErrNoInfo
	}

	serverinfo = serverinfo[1:] // 0x49
	protocol := int(serverinfo[0])
	serverinfo = serverinfo[1:]

	name := util.ReadTillNul(serverinfo)
	serverinfo = serverinfo[len(name)+1:]
	mapname := util.ReadTillNul(serverinfo)
	serverinfo = serverinfo[len(mapname)+1:]
	folder := util.ReadTillNul(serverinfo)
	serverinfo = serverinfo[len(folder)+1:]
	game := util.ReadTillNul(serverinfo)
	serverinfo = serverinfo[len(game)+1:]
	id := int16(binary.LittleEndian.Uint16(serverinfo[:2]))
	serverinfo = serverinfo[2:]
	if id >= 2400 && id <= 2412 {
		return models.SteamServerInfo{},
			logger.LogSteamErrorf("The Ship servers are not supported")
	}
	players := int16(serverinfo[0])
	serverinfo = serverinfo[1:]
	maxplayers := int16(serverinfo[0])
	serverinfo = serverinfo[1:]
	bots := int16(serverinfo[0])
	serverinfo = serverinfo[1:]
	servertype := string(serverinfo[0])
	serverinfo = serverinfo[1:]
	environment := string(serverinfo[0])
	serverinfo = serverinfo[1:]
	visibility := int16(serverinfo[0])
	serverinfo = serverinfo[1:]
	vac := int16(serverinfo[0])
	serverinfo = serverinfo[1:]
	version := util.ReadTillNul(serverinfo)
	serverinfo = serverinfo[len(version)+1:]

	// extra data flags
	var port int16
	var steamid uint64
	var sourcetvport int16
	var sourcetvname string
	var keywords string
	var gameid uint64
	edf := serverinfo[0]
	serverinfo = serverinfo[1:]
	if edf != 0x00 {
		if edf&0x80 > 0 {
			port = int16(binary.LittleEndian.Uint16(serverinfo[:2]))
			serverinfo = serverinfo[2:]
		}
		if edf&0x10 > 0 {
			steamid = binary.LittleEndian.Uint64(serverinfo[:8])
			serverinfo = serverinfo[8:]
		}
		if edf&0x40 > 0 {
			sourcetvport = int16(binary.LittleEndian.Uint16(serverinfo[:2]))
			serverinfo = serverinfo[2:]
			sourcetvname = util.ReadTillNul(serverinfo)
			serverinfo = serverinfo[len(sourcetvname)+1:]
		}
		if edf&0x20 > 0 {
			keywords = util.ReadTillNul(serverinfo)
			serverinfo = serverinfo[len(keywords)+1:]
		}
		if edf&0x01 > 0 {
			gameid = binary.LittleEndian.Uint64(serverinfo[:8])
			serverinfo = serverinfo[len(serverinfo):]
		}
	}

	// format a few ambiguous values
	if environment == "l" {
		environment = "Linux"
	}
	if environment == "w" {
		environment = "Windows"
	}
	if environment == "m" || environment == "o" {
		environment = "Mac"
	}
	if servertype == "d" {
		servertype = "dedicated"
	}
	if servertype == "l" {
		servertype = "listen"
	}
	if servertype == "p" {
		servertype = "sourcetv"
	}

	return models.SteamServerInfo{
		Protocol:    protocol,
		Name:        name,
		Map:         mapname,
		Folder:      folder,
		Game:        game,
		ID:          id,
		Players:     players,
		MaxPlayers:  maxplayers,
		Bots:        bots,
		ServerType:  servertype,
		Environment: environment,
		Visibility:  visibility,
		VAC:         vac,
		Version:     version,
		ExtraData: models.SteamExtraData{
			Port:         port,
			SteamID:      steamid,
			SourceTVPort: sourcetvport,
			SourceTVName: sourcetvname,
			Keywords:     keywords,
			GameID:       gameid,
		},
	}, nil
}