Skip to content

Instantly share code, notes, and snippets.

@sinuscosinustan
Last active April 3, 2025 12:29
Show Gist options
  • Save sinuscosinustan/311a17dccdb7d5c2f9b80cc9be179242 to your computer and use it in GitHub Desktop.
Save sinuscosinustan/311a17dccdb7d5c2f9b80cc9be179242 to your computer and use it in GitHub Desktop.
Small webserver for easy firewalling for Satisfactory 1.1 Experimental ("workaround" the ReliableMessaging crash)
---
services:
firewall-web:
build: firewall-shit/
container_name: firewall-server
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.fwshit-strip.stripprefix.prefixes=/fwshit"
- "traefik.http.routers.fwshit.rule=Host(`MYDOMAIN.LOCAL`) && PathRegexp(`^/(fwshit)`)"
- "traefik.http.routers.fwshit.entrypoints=websecure"
- "traefik.http.routers.fwshit.tls.certresolver=myresolver"
- "traefik.http.routers.fwshit.middlewares=fwshit-strip"
- "traefik.http.services.fwshit.loadbalancer.server.port=8080"
network_mode: host
cap_add:
- NET_ADMIN
- SYS_ADMIN
FROM golang:1.24-alpine as builder
WORKDIR /app
COPY . .
RUN go build -o server
FROM alpine:latest
RUN apk add --no-cache iptables ip6tables util-linux iptables-legacy
WORKDIR /app
COPY --from=builder /app/server /server
CMD ["/server"]
package main
import (
"fmt"
"html/template"
"log"
"net"
"net/http"
"os/exec"
"strings"
"net/netip"
)
const listenPort = "8080"
const ipv4Chain = "FIREWALL-WEB"
const ipv6Chain = "FIREWALL-WEB6"
var ports = []string{"8888/tcp", "7777/tcp", "7777/udp", "27777/tcp"}
var tmpl = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Firewall Access</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
}
.container {
max-width: 600px;
margin-top: 10vh;
}
</style>
</head>
<body>
<div class="container text-center">
<h1 class="mb-4">Request Firewall Access</h1>
<p>This will add your IPv4 and IPv6 address (if present) to the firewall on ports 8888 and 7777.</p>
<form method="POST">
<input type="hidden" name="v4" id="v4">
<input type="hidden" name="v6" id="v6">
<button type="submit" class="btn btn-primary btn-lg">Allow My IP</button>
</form>
{{if .Message}}
<div class="alert alert-info mt-4" role="alert">
{{.Message}}
</div>
{{end}}
</div>
<script>
async function getIPs() {
try {
const v4 = await fetch('https://ipv4.icanhazip.com').then(r => r.text());
document.getElementById('v4').value = v4.trim();
} catch {}
try {
const v6 = await fetch('https://ipv6.icanhazip.com').then(r => r.text());
document.getElementById('v6').value = v6.trim();
} catch {}
}
getIPs();
</script>
</body>
</html>
`
type PageData struct {
Message template.HTML
}
func main() {
ensureChainsExist()
http.HandleFunc("/", handler)
log.Printf("Listening on :%s...", listenPort)
log.Fatal(http.ListenAndServe(":"+listenPort, nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
if r.Method == http.MethodGet {
tmplParsed := template.Must(template.New("page").Funcs(template.FuncMap{"safeHTML": func(s string) template.HTML { return template.HTML(s) }}).Parse(tmpl))
tmplParsed.Execute(w, nil)
return
}
r.ParseForm()
ips := []string{}
if v4 := strings.TrimSpace(r.FormValue("v4")); net.ParseIP(v4) != nil {
ips = append(ips, v4)
}
if v6 := strings.TrimSpace(r.FormValue("v6")); net.ParseIP(v6) != nil {
ips = append(ips, v6)
}
if len(ips) == 0 {
http.Error(w, "No valid IP provided", http.StatusBadRequest)
return
}
var messages []string
for _, ip := range ips {
log.Printf("Processing IP: %s", ip)
for _, portProto := range ports {
msg := allowInFirewall(ip, portProto)
log.Println(msg)
messages = append(messages, template.HTMLEscapeString(msg))
}
}
tmplParsed := template.Must(template.New("page").Funcs(template.FuncMap{"safeHTML": func(s string) template.HTML { return template.HTML(s) }}).Parse(tmpl))
tmplParsed.Execute(w, PageData{Message: template.HTML(strings.Join(messages, "<br>"))})
}
func ensureChainsExist() {
exec.Command("iptables-legacy", "-N", ipv4Chain).Run()
exec.Command("ip6tables-legacy", "-N", ipv6Chain).Run()
exec.Command("iptables-legacy", "-C", "INPUT", "-j", ipv4Chain).Run()
exec.Command("iptables-legacy", "-I", "INPUT", "-j", ipv4Chain).Run()
exec.Command("ip6tables-legacy", "-C", "INPUT", "-j", ipv6Chain).Run()
exec.Command("ip6tables-legacy", "-I", "INPUT", "-j", ipv6Chain).Run()
for _, portProto := range ports {
parts := strings.Split(portProto, "/")
port := parts[0]
proto := parts[1]
if err := exec.Command("iptables-legacy", "-C", ipv4Chain, "-p", proto, "--dport", port, "-j", "DROP").Run(); err != nil {
exec.Command("iptables-legacy", "-A", ipv4Chain, "-p", proto, "--dport", port, "-j", "DROP").Run()
}
if err := exec.Command("ip6tables-legacy", "-C", ipv6Chain, "-p", proto, "--dport", port, "-j", "DROP").Run(); err != nil {
exec.Command("ip6tables-legacy", "-A", ipv6Chain, "-p", proto, "--dport", port, "-j", "DROP").Run()
}
}
}
func allowInFirewall(ip string, portProto string) string {
parsed := net.ParseIP(ip)
if parsed == nil {
return fmt.Sprintf("Invalid IP: %s", ip)
}
parts := strings.Split(portProto, "/")
port := parts[0]
proto := parts[1]
var (
checkCmd *exec.Cmd
addCmd *exec.Cmd
ruleDesc string
)
if strings.Contains(ip, ":") {
addr, err := netip.ParseAddr(ip)
if err != nil || !addr.Is6() {
return fmt.Sprintf("Invalid IPv6 address: %s", ip)
}
prefix, err := addr.Prefix(64)
if err != nil {
return fmt.Sprintf("Error while getting /64 prefix: %s", err)
}
checkCmd = exec.Command("ip6tables-legacy", "-C", ipv6Chain, "-p", proto, "-s", prefix.String(), "--dport", port, "-j", "ACCEPT")
addCmd = exec.Command("ip6tables-legacy", "-I", ipv6Chain, "1", "-p", proto, "-s", prefix.String(), "--dport", port, "-j", "ACCEPT")
ruleDesc = fmt.Sprintf("%s", prefix)
} else {
checkCmd = exec.Command("iptables-legacy", "-C", ipv4Chain, "-p", proto, "-s", ip, "--dport", port, "-j", "ACCEPT")
addCmd = exec.Command("iptables-legacy", "-I", ipv4Chain, "1", "-p", proto, "-s", ip, "--dport", port, "-j", "ACCEPT")
ruleDesc = fmt.Sprintf("%s", ip)
}
if err := checkCmd.Run(); err == nil {
return fmt.Sprintf("🔁 %s is already whitelisted for port %s/%s", ruleDesc, port, proto)
}
if err := addCmd.Run(); err != nil {
return fmt.Sprintf("❌ Failed to allow %s: %v", ruleDesc, err)
}
return fmt.Sprintf("✅ Allowed %s on port %s/%s", ruleDesc, port, proto)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment