Capture, decrypt, and restream toolkit with compiled-in app modules (RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www. BBC module.yaml is published (clear streams); other module values stay local.
101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// passthroughAll, when true, splices every host without MITM. Used to prove
|
|
// transparent redirect + VPN egress work before narrowing MITM targets.
|
|
var passthroughAll bool
|
|
|
|
// passthroughDefault, when true (transparent mode), MITM only forceMITM / open.live.
|
|
// Avoids breaking connectivity checks on Google/Firebase when Magisk CA is not
|
|
// in the app mount namespace.
|
|
var passthroughDefault bool
|
|
|
|
// forceMITM hosts (lowercase) always MITM even if a passthrough rule matches.
|
|
// Use for known license endpoints once identified: -mitm-host license.example.com
|
|
var forceMITM []string
|
|
|
|
// Hosts that must NOT be MITM'd for playback to work (CDN / media / BBC APIs).
|
|
// Transparent mode defaults to passthrough; carve in with open.live / -mitm-host.
|
|
func shouldPassthrough(host string) bool {
|
|
if passthroughAll {
|
|
return true
|
|
}
|
|
h := strings.ToLower(stripHostPort(host))
|
|
if h == "" {
|
|
return false
|
|
}
|
|
for _, m := range forceMITM {
|
|
if h == m || strings.HasSuffix(h, "."+m) {
|
|
return false
|
|
}
|
|
}
|
|
// No SNI → only have a destination IP; MITM cert/SNI would be wrong.
|
|
if ip := net.ParseIP(h); ip != nil {
|
|
return true
|
|
}
|
|
// MITM open.live (mediaselector) — stream + Widevine licence URLs live here.
|
|
// Upstream MUST dial SO_ORIGINAL_DST (VPN fake-IP) or BBC returns geolocation 403.
|
|
if h == "open.live.bbc.co.uk" {
|
|
return false
|
|
}
|
|
// Other BBC APIs/CDNs: MITM breaks play; passthrough.
|
|
if strings.Contains(h, "bbc.co.uk") || strings.Contains(h, "bbci.co.uk") ||
|
|
strings.Contains(h, "bbc.com") || strings.Contains(h, "bbci.com") {
|
|
return true
|
|
}
|
|
needles := []string{
|
|
"akamai", "akamaized", "cloudfront.net", "fastly",
|
|
"edgesuite", "cmaf", "2cnt.net", "springstreams",
|
|
"fingerprint", "optimizely", "appsflyer", "urbanairship",
|
|
"googleusercontent", "gvt1.com", "googleapis.com",
|
|
"firebaselogging", "crashlytics", "app-measurement",
|
|
}
|
|
for _, n := range needles {
|
|
if strings.Contains(h, n) {
|
|
return true
|
|
}
|
|
}
|
|
if passthroughDefault {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func handlePassthrough(client net.Conn, host string, port int, dial func(network, addr string) (net.Conn, error)) {
|
|
defer client.Close()
|
|
target := net.JoinHostPort(host, strconv.Itoa(port))
|
|
up, err := dial("tcp", target)
|
|
if err != nil {
|
|
log.Printf("[PASS] dial %s: %v", target, err)
|
|
return
|
|
}
|
|
defer up.Close()
|
|
log.Printf("[PASS] %s (no MITM)", target)
|
|
errc := make(chan error, 2)
|
|
var once sync.Once
|
|
closeWrite := func(c net.Conn) {
|
|
once.Do(func() {})
|
|
if tc, ok := c.(*net.TCPConn); ok {
|
|
_ = tc.CloseWrite()
|
|
}
|
|
}
|
|
go func() {
|
|
_, err := io.Copy(up, client)
|
|
errc <- err
|
|
closeWrite(up)
|
|
}()
|
|
go func() {
|
|
_, err := io.Copy(client, up)
|
|
errc <- err
|
|
closeWrite(client)
|
|
}()
|
|
<-errc
|
|
}
|