#!/usr/bin/env bash # network-metadata-logger/netlog.sh # Logs FIFA 23 network connection metadata: host, port, protocol, timing. # Does NOT capture payload, does NOT decrypt TLS. # # Usage: netlog.sh [--interval 2] [--duration 120] # Polls /proc/net/tcp* and resolves FIFA23 PIDs every N seconds. # Output: CSV to stdout and netlog-TIMESTAMP.csv in this directory. set -euo pipefail INTERVAL="${INTERVAL:-2}" DURATION="${DURATION:-300}" OUT_DIR="$(cd "$(dirname "$0")" && pwd)/logs" mkdir -p "$OUT_DIR" OUT_FILE="$OUT_DIR/netlog-$(date +%Y%m%d-%H%M%S).csv" log() { echo "$*" | tee -a "$OUT_FILE"; } find_fifa_pids() { pgrep -x FIFA23.exe 2>/dev/null || \ pgrep -f FIFA23.exe 2>/dev/null || \ true } # Decode a hex IP:port pair from /proc/net/tcp # e.g. "0F02000A:01BB" → "10.0.2.15:443" decode_addr() { local hex_ip="${1%%:*}" local hex_port="${1##*:}" local port=$((16#$hex_port)) # IPv4 in /proc/net/tcp is little-endian local b1=$((16#${hex_ip:6:2})) local b2=$((16#${hex_ip:4:2})) local b3=$((16#${hex_ip:2:2})) local b4=$((16#${hex_ip:0:2})) echo "${b1}.${b2}.${b3}.${b4}:${port}" } # TCP state codes state_name() { case "$1" in 01) echo ESTABLISHED ;; 02) echo SYN_SENT ;; 03) echo SYN_RECV ;; 04) echo FIN_WAIT1 ;; 05) echo FIN_WAIT2 ;; 06) echo TIME_WAIT ;; 07) echo CLOSE ;; 08) echo CLOSE_WAIT ;; 09) echo LAST_ACK ;; 0A) echo LISTEN ;; 0B) echo CLOSING ;; *) echo "STATE_$1" ;; esac } # Resolve remote IP → hostname (best-effort, non-blocking) resolve_host() { local ip="${1%%:*}" # Skip local/loopback case "$ip" in 127.*|10.*|192.168.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*) echo "$ip"; return ;; esac getent hosts "$ip" 2>/dev/null | awk '{print $2}' | head -1 || echo "$ip" } # Categorise destination by port/host heuristic categorise() { local host="$1" port="$2" case "$port" in 443|8443) echo "HTTPS/TLS" ;; 80) echo "HTTP" ;; 10853) echo "EA-LSX-auth" ;; 3216|3478|3479|3480) echo "EA-UDP-STUN-or-relay" ;; *) case "$host" in *ea.com*|*easports.com*|*footapi.com*|*ugc.*) echo "EA-service" ;; *akamai*|*amazonaws*|*cloudfront*) echo "CDN" ;; *) echo "other" ;; esac ;; esac } # Build inode→pid map from /proc build_inode_pid_map() { declare -gA INODE_PID INODE_PID=() for pid_dir in /proc/[0-9]*/fd; do local pid="${pid_dir%%/fd}" pid="${pid##/proc/}" for link in "$pid_dir"/[0-9]* 2>/dev/null; do local target target=$(readlink "$link" 2>/dev/null) || continue if [[ "$target" == "socket:["* ]]; then local inode="${target//[^0-9]/}" INODE_PID["$inode"]="$pid" fi done done 2>/dev/null || true } log "timestamp,pid,proc,local_addr,remote_host,remote_addr,port,state,category" declare -A SEEN_CONNS END_TIME=$(( $(date +%s) + DURATION )) echo "Logging FIFA 23 network metadata to $OUT_FILE (${DURATION}s, Ctrl-C to stop)..." while [ "$(date +%s)" -lt "$END_TIME" ]; do TS=$(date +%H:%M:%S) build_inode_pid_map # Read /proc/net/tcp (IPv4 ESTABLISHED and SYN_SENT) while read -r _ local_hex remote_hex state _ _ _ _ _ inode _rest; do [ "$state" = "01" ] || [ "$state" = "02" ] || continue local_addr=$(decode_addr "$local_hex") remote_addr=$(decode_addr "$remote_hex") remote_ip="${remote_addr%%:*}" remote_port="${remote_addr##*:}" pid="${INODE_PID[$inode]:-}" [ -z "$pid" ] && continue # Filter to FIFA-related PIDs (or log all if no FIFA found) proc=$(cat "/proc/$pid/comm" 2>/dev/null) || continue state_str=$(state_name "$state") key="${pid}:${local_addr}:${remote_addr}" if [ -z "${SEEN_CONNS[$key]:-}" ] || [ "$state_str" != "${SEEN_CONNS[$key]}" ]; then SEEN_CONNS["$key"]="$state_str" host=$(resolve_host "$remote_ip") cat=$(categorise "$host" "$remote_port") log "$TS,$pid,$proc,$local_addr,$host,$remote_addr,$remote_port,$state_str,$cat" fi done < /proc/net/tcp 2>/dev/null || true sleep "$INTERVAL" done echo "Done. Output: $OUT_FILE"