Пример #1
0
// GetIDsForServerList retrieves the server ID numbers for a given set of hosts,
// from the server database file, in response to a request to build the master
// server detail list or the list of server details in response to a request
// coming in over the API. It sends its results over a map channel consisting of
// a host to id mapping.
func (sdb *SDB) GetIDsForServerList(result chan map[string]int64,
	hosts map[string]string) {
	m := make(map[string]int64, len(hosts))
	for host, game := range hosts {
		rows, err := sdb.db.Query(
			"SELECT server_id FROM servers WHERE host =? AND game =? LIMIT 1",
			host, game)
		if err != nil {
			logger.LogAppErrorf(
				"GetIDsForServerList: Error querying database to retrieve ID for host %s and game %s: %s",
				host, game, err)
			return
		}
		defer rows.Close()
		var id int64
		for rows.Next() {
			if err := rows.Scan(&id); err != nil {
				logger.LogAppErrorf(
					"GetIDsForServerList: Error querying database to retrieve ID for host %s: %s",
					host, err)
				return
			}
		}
		m[host] = id
	}
	result <- m
}
Пример #2
0
// GetIDsAPIQuery Retrieves the server ID numbers, hosts, and game name for a given
// set of hosts (represented by query string values) from the server database
// file in response to a query from the API. Sends the results over a DbServerID
// channel for consumption.
func (sdb *SDB) GetIDsAPIQuery(result chan *models.DbServerID, hosts []string) {
	m := &models.DbServerID{}
	for _, h := range hosts {
		logger.WriteDebug("DB: GetIDsAPIQuery, host: %s", h)
		rows, err := sdb.db.Query(
			"SELECT server_id, host, game FROM servers WHERE host LIKE ?",
			fmt.Sprintf("%%%s%%", h))
		if err != nil {
			logger.LogAppErrorf(
				"GetIDsAPIQuery: Error querying database to retrieve ID for host %s: %s",
				h, err)
			return
		}
		defer rows.Close()
		var id int64
		host, game := "", ""

		for rows.Next() {
			sid := models.DbServer{}
			if err := rows.Scan(&id, &host, &game); err != nil {
				logger.LogAppErrorf(
					"GetIDsAPIQuery: Error querying database to retrieve ID for host %s: %s",
					h, err)
				return
			}
			sid.ID = id
			sid.Host = host
			sid.Game = game
			m.Servers = append(m.Servers, sid)
		}
	}
	m.ServerCount = len(m.Servers)
	result <- m
}
Пример #3
0
func useDumpFileAsMasterList(dumppath string) *models.APIServerList {
	f, err := os.Open(dumppath)
	if err != nil {
		logger.LogAppErrorf("Unable to open test API server dump file: %s", err)
		return nil
	}
	defer f.Close()
	r := bufio.NewReader(f)
	d := json.NewDecoder(r)
	ml := &models.APIServerList{}
	if err := d.Decode(ml); err != nil {
		logger.LogAppErrorf("Unable to decode test API server dump as json: %s", err)
		return nil
	}
	return ml
}
Пример #4
0
func extractHosts(hbs []byte) ([]string, int, error) {
	var sl []string
	pos, total := 0, 0
	for i := 0; i < len(hbs); i++ {
		if len(sl) > 0 && sl[len(sl)-1] == "0.0.0.0:0" {
			logger.LogSteamInfo("0.0.0.0:0 detected. Got %d total hosts.", total-1)
			break
		}
		if pos+6 > len(hbs) {
			logger.LogSteamInfo("Got %d total hosts.", total)
			break
		}

		host, err := parseIP(hbs[pos : pos+6])
		if err != nil {
			logger.LogAppErrorf("Error parsing host: %s", err)
		} else {
			sl = append(sl, host)
			total++
		}
		// host:port = 6 bytes
		pos = pos + 6
	}
	return sl, total, nil
}
Пример #5
0
func (sdb *SDB) getHostAndGame(id string) (host, game string, err error) {
	rows, err := sdb.db.Query("SELECT host, game FROM servers WHERE server_id =? LIMIT 1",
		id)
	if err != nil {
		return host, game,
			logger.LogAppErrorf("getHostAndGame: Error querying database for id %s: %s",
				id, err)
	}
	defer rows.Close()
	for rows.Next() {
		if err := rows.Scan(&host, &game); err != nil {
			return host, game,
				logger.LogAppErrorf("getHostAndGame: Error querying database for id %s: %s",
					id, err)
		}
	}
	return host, game, nil
}
Пример #6
0
// AddServersToDB inserts a specified host and port with its game name into the
// server database.
func (sdb *SDB) AddServersToDB(hostsgames map[string]string) {
	toInsert := make(map[string]string, len(hostsgames))
	for host, game := range hostsgames {
		// If direct queries are enabled, don't add 'Unspecified' game to server DB
		if game == filters.GameUnspecified.String() {
			continue
		}
		exists, err := sdb.serverExists(host, game)
		if err != nil {
			continue
		}
		if exists {
			continue
		}
		toInsert[host] = game
	}
	tx, err := sdb.db.Begin()
	if err != nil {
		logger.LogAppErrorf("AddServersToDB error creating tx: %s", err)
		return
	}
	var txexecerr error
	for host, game := range toInsert {
		_, txexecerr = tx.Exec("INSERT INTO servers (host, game) VALUES ($1, $2)",
			host, game)
		if txexecerr != nil {
			logger.LogAppErrorf(
				"AddServersToDB exec error for host %s and game %s: %s", host, game, err)
			break
		}
	}
	if txexecerr != nil {
		if err = tx.Rollback(); err != nil {
			logger.LogAppErrorf("AddServersToDB error rolling back tx: %s", err)
			return
		}
	}
	if err = tx.Commit(); err != nil {
		logger.LogAppErrorf("AddServersToDB error committing tx: %s", err)
		return
	}
}
Пример #7
0
func dumpServersToDisk(gamename string, sl *models.APIServerList) error {
	j, err := json.Marshal(sl)
	if err != nil {
		return logger.LogAppErrorf("Error marshaling json: %s", err)
	}
	t := time.Now()
	if err := util.CreateDirectory(constants.DumpDirectory); err != nil {
		return logger.LogAppErrorf("Couldn't create '%s' dir: %s\n",
			constants.DumpDirectory, err)
	}
	// Windows doesn't allow ":" in filename so use '-' separators for time
	err = util.CreateByteFile(j, constants.DumpFileFullPath(
		fmt.Sprintf("%s-servers-%d-%02d-%02d.%02d-%02d-%02d.json",
			gamename, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())),
		true)
	if err != nil {
		return logger.LogAppErrorf("Error creating server dump file: %s", err)
	}
	return nil
}
Пример #8
0
// StartMasterRetrieval starts a timed retrieval of servers specified by a given
// filter from the Steam Master server after an initial delay of initialDelay
// seconds. It retrieves the list every timeBetweenQueries seconds thereafter.
// A bool can be sent to the stop channel to cancel all timed retrievals.
func StartMasterRetrieval(stop chan bool, filter filters.Filter,
	initialDelay int, timeBetweenQueries int) {
	retrticker := time.NewTicker(time.Duration(timeBetweenQueries) * time.Second)

	logger.WriteDebug(
		"Waiting %d seconds before grabbing %s servers. Will retrieve servers every %d secs afterwards.", initialDelay, filter.Game.Name, timeBetweenQueries)

	logger.LogAppInfo(
		"Waiting %d seconds before grabbing %s servers from master. Will retrieve every %d secs afterwards.", initialDelay, filter.Game.Name, timeBetweenQueries)

	firstretrieval := time.NewTimer(time.Duration(initialDelay) * time.Second)
	<-firstretrieval.C
	logger.WriteDebug("Starting first retrieval of %s servers from master.",
		filter.Game.Name)
	sl, err := retrieve(filter)
	if err != nil {
		logger.LogAppErrorf("Error when performing timed master retrieval: %s", err)
	}
	models.MasterList = sl

	for {
		select {
		case <-retrticker.C:
			go func(filters.Filter) {
				logger.WriteDebug("%s: Starting %s master server query", time.Now().Format(
					"Mon Jan 2 15:04:05 2006 EST"), filter.Game.Name)
				logger.LogAppInfo("%s: Starting %s master server query", time.Now().Format(
					"Mon Jan 2 15:04:05 2006 EST"), filter.Game.Name)
				sl, err := retrieve(filter)
				if err != nil {
					logger.LogAppErrorf("Error when performing timed master retrieval: %s",
						err)
				}
				models.MasterList = sl
			}(filter)
		case <-stop:
			retrticker.Stop()
			return
		}
	}
}
Пример #9
0
func retrieve(filter filters.Filter) (*models.APIServerList, error) {
	var mq MasterQuery
	var err error
	if config.Config.SteamConfig.UseWebServerList {
		mq, err = NewMasterWebQuery(filter)
	} else {
		mq, err = NewMasterQuery(filter)
	}

	if err != nil {
		return nil, logger.LogSteamErrorf("Master server error: %s", err)
	}

	if filter.Game.IgnoreInfo && filter.Game.IgnorePlayers && filter.Game.IgnoreRules {
		return nil, logger.LogAppErrorf("Cannot ignore all three AS2 requests!")
	}

	data := a2sData{}
	hg := make(map[string]filters.Game, len(mq.Servers))
	for _, h := range mq.Servers {
		hg[h] = filter.Game
	}
	data.HostsGames = hg

	// Order of retrieval is by amount of work that must be done (generally 1, 2, 3)
	// 1. rules (request chal #, recv chal #, req rules, recv rules)
	// games with multi-packet A2S_RULES replies do the most work; otherwise 1 = 2, 3
	// 2. players (request chal #, recv chal #, req players, recv players)
	// 3. info: just request info & receive info
	// Note: some servers (i.e. new beta games) don't have all 3 of AS2_RULES/PLAYER/INFO
	if !filter.Game.IgnoreRules {
		data.Rules = batchRuleQuery(mq.Servers)
	}
	if !filter.Game.IgnorePlayers {
		data.Players = batchPlayerQuery(mq.Servers)
	}
	if !filter.Game.IgnoreInfo {
		data.Info = batchInfoQuery(mq.Servers)
	}

	serverlist, err := buildServerList(data, true)
	if err != nil {
		return nil, logger.LogAppError(err)
	}

	if config.Config.DebugConfig.EnableServerDump {
		if err := dumpServersToDisk(filter.Game.Name, serverlist); err != nil {
			logger.LogAppError(err)
		}
	}

	return serverlist, nil
}
Пример #10
0
func verifyServerDbPath() error {
	if err := util.CreateDirectory(constants.DbDirectory); err != nil {
		logger.LogAppError(err)
		panic(fmt.Sprintf("Unable to create database directory %s: %s",
			constants.DbDirectory, err))
	}
	if err := createServerDBtable(constants.GetServerDBPath()); err != nil {
		logger.LogAppErrorf("Unable to verify database path: %s", err)
		panic("Unable to verify database path")
	}

	return nil
}
Пример #11
0
func (sdb *SDB) serverExists(host string, game string) (bool, error) {
	rows, err := sdb.db.Query(
		"SELECT host, game FROM servers WHERE host =? AND GAME =? LIMIT 1",
		host, game)
	if err != nil {
		return false, logger.LogAppErrorf(
			"serverExists: Error querying database for host %s and game %s: %s",
			host, game, err)
	}

	defer rows.Close()
	h, g := "", ""
	for rows.Next() {
		if err := rows.Scan(&h, &g); err != nil {
			return false, logger.LogAppErrorf(
				"serverExists: Error querying database for host %s and game %s: %s",
				host, game, err)
		}
	}
	if h != "" && g != "" {
		return true, nil
	}
	return false, nil
}
Пример #12
0
// GetHostsAndGameFromIDAPIQuery Retrieves the hosts and game names from the
// server database file in response to a user-specified API query for a given
// set of server ID numbers. Sends the results over a channel consisting of a
// host to game name string mapping.
func (sdb *SDB) GetHostsAndGameFromIDAPIQuery(result chan map[string]string,
	ids []string) {
	hosts := make(map[string]string, len(ids))
	for _, id := range ids {
		host, game, err := sdb.getHostAndGame(id)
		if err != nil {
			logger.LogAppErrorf("Error getting host from ID for API query: %s", err)
			return
		}
		if host == "" && game == "" {
			continue
		}
		hosts[host] = game
	}
	result <- hosts
}
Пример #13
0
func createServerDBtable(dbfile string) error {
	create := `CREATE TABLE servers (
	server_id INTEGER NOT NULL,
	host TEXT NOT NULL,
	game TEXT NOT NULL,
	PRIMARY KEY(server_id)
	)`

	if util.FileExists(dbfile) {
		// already exists, so verify integrity
		db, err := sql.Open("sqlite3", dbfile)
		if err != nil {
			return logger.LogAppErrorf(
				"Unable to open server DB file for verification: %s", err)
		}
		defer db.Close()
		var name string
		err = db.QueryRow(
			"SELECT name from sqlite_master where type='table' and name='servers'").Scan(&name)
		switch {
		case err == sql.ErrNoRows:
			if _, err = db.Exec(create); err != nil {
				return logger.LogAppErrorf("Unable to create servers table in DB: %s", err)
			}
		case err != nil:
			return logger.LogAppErrorf("Server DB table verification error: %s", err)
		}
		return nil
	}

	err := util.CreateEmptyFile(dbfile, true)
	if err != nil {
		return logger.LogAppErrorf("Unable to create server DB: %s", err)
	}

	db, err := sql.Open("sqlite3", dbfile)
	if err != nil {
		return logger.LogAppErrorf(
			"Unable to open server DB file for table creation: %s", err)
	}
	defer db.Close()
	_, err = db.Exec(create)
	if err != nil {
		return logger.LogAppErrorf("Unable to create servers table in servers DB: %s",
			err)
	}
	return nil
}
Пример #14
0
func buildServerList(data a2sData, addtoServerDB bool) (*models.APIServerList,
	error) {
	// Cannot ignore all three requests
	for _, g := range data.HostsGames {
		if g.IgnoreInfo && g.IgnorePlayers && g.IgnoreRules {
			return nil, logger.LogAppErrorf("Cannot ignore all three A2S_ requests!")
		}
	}
	successcount := 0
	var success bool
	srvDBhosts := make(map[string]string, len(data.HostsGames))
	sl := &models.APIServerList{
		Servers:       make([]models.APIServer, 0),
		FailedServers: make([]string, 0),
	}

	for host, game := range data.HostsGames {
		info, iok := data.Info[host]
		players, pok := data.Players[host]
		if players == nil {
			// return empty array instead of nil pointers (null) in json
			players = make([]models.SteamPlayerInfo, 0)
		}
		rules, rok := data.Rules[host]
		success = iok && rok && pok

		if game.IgnoreInfo {
			success = pok && rok
		}
		if game.IgnorePlayers {
			success = iok && rok
		}
		if game.IgnoreRules {
			rules = make(map[string]string, 0)
			success = iok && pok
		}
		if game.IgnoreInfo && game.IgnorePlayers {
			success = rok
		}
		if game.IgnoreInfo && game.IgnoreRules {
			success = pok
		}
		if game.IgnorePlayers && game.IgnoreRules {
			success = iok
		}

		if success {
			srv := models.APIServer{
				Game:            game.Name,
				Players:         players,
				FilteredPlayers: removeBuggedPlayers(players),
				Rules:           rules,
				Info:            info,
			}
			// Gametype support: gametype can be found in rules, info, or not
			// at all depending on the game (currently just for QuakeLive & Reflex)
			srv.Info.GameTypeShort, srv.Info.GameTypeFull = getGameType(game, srv)

			ip, port, serr := net.SplitHostPort(host)
			if serr == nil {
				srv.IP = ip
				srv.Host = host
				p, perr := strconv.Atoi(port)
				if perr == nil {
					srv.Port = p
				}
				if !strings.EqualFold(game.Name, filters.GameUnspecified.String()) {
					srvDBhosts[host] = game.Name
				}
				loc := make(chan models.DbCountry, 1)
				go db.CountryDB.GetCountryInfo(loc, ip)
				srv.CountryInfo = <-loc
			}
			sl.Servers = append(sl.Servers, srv)
			successcount++
		} else {
			sl.FailedServers = append(sl.FailedServers, host)
		}
	}

	sl.RetrievedAt = time.Now().Format("Mon Jan 2 15:04:05 2006 EST")
	sl.RetrievedTimeStamp = time.Now().Unix()
	sl.ServerCount = len(sl.Servers)
	sl.FailedCount = len(sl.FailedServers)

	if len(srvDBhosts) != 0 {
		go db.ServerDB.AddServersToDB(srvDBhosts)
		sl.Servers = setServerIDsForList(sl.Servers)
	}

	logger.LogAppInfo(
		"Successfully queried (%d/%d) servers. %d timed out or otherwise failed.",
		successcount, len(data.HostsGames), sl.FailedCount)
	logger.WriteDebug("Server Queries: Successful: (%d/%d) servers\tFailed: %d servers",
		successcount, len(data.HostsGames), sl.FailedCount)
	return sl, nil
}
Пример #15
0
// Close closes the country geolocation database.
func (cdb *CDB) Close() {
	err := cdb.db.Close()
	if err != nil {
		logger.LogAppErrorf("Error closing country database: %s", err)
	}
}
Пример #16
0
// Close closes the server database's underlying connection.
func (sdb *SDB) Close() {
	err := sdb.db.Close()
	if err != nil {
		logger.LogAppErrorf("Error closing server DB: %s", err)
	}
}
Пример #17
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
}