Dealing with TV ads
Or Spending hours on a terminal to avoid the mental tax
by on 2024-06-20My networking setup at home is now perfectlyTM working, and I’m now dealing with annoyances. Those annoyances include advertisement on services I already pay for (Amazon Prime and M6+).
§ M6+
M6+ is the current name of the VOD service from the M6 group. That group holds diffusion rights in France for Lego Masters, which is a fun and interesting show. Too bad M6 litters the episodes with ads (1 every 10 or so minutes).
I’m using the UHD TV decoder to access M6+, and it has not been a smooth experience.
§ adding the UHD TV decoder to the network
First things first: connect the TV decoder to the network and stop it from complaining.
The TV box lives in its own VLAN (vlan52). And it needs to be sent some specific DHCP options to accept the lease.
# TV
subnet 10.207.52.1 netmask 255.255.255.0 {
option routers 10.207.52.1;
option domain-name-servers 10.207.52.1; # I control that, check https://try.popho.be/securing-home3.html
# NB: use a forward-zone for orange.fr or use Orange's DNS - they resolve some
# domains differently! See below.
option ntp-servers 10.207.51.1;
range 10.207.52.100 10.207.52.200;
host decodeur-tv {
hardware ethernet 4x:xx:xx:xx:xx:xx;
# https://forums.framboise314.fr/viewtopic.php?f=57&t=5960
option option-125 00:00:0D:E9:24:04:06:3x:xx:xx:xx:xx:xx:05:0F:4x:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:06:09:4C:69:76:65:62:6F:78:20:34;
# option option-125 00:00:0D:E9:28:04:06:3x:xx:xx:xx:xx:xx:05:0F:4x:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:06:0d:4C:69:76:65:62:6F:78:20:46:69:62:72:65;
# ............^^.......^^^^^^^^^^^^^^^^^.......^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^....^^..L..i..v..e..b..o..x.. ..F..i..b..r..e
# `length `hex(first 3 bytes of Livebox MAC) `hex(serial Livebox) `length of next item
option domain-name "orange.fr";
option domain-search "ORL.access.orange-multimedia.net";
}
}
§ IGMPProxy
This is for live TV, using IGMP on the Orange network. The router has
to have vlan840
interface on the uplink, with a random IP
address on it.
Largely inspired by this OpenBSD Journal article.
chroot /var/empty
user _igmpproxy
quickleave
phyint vlan840 upstream ratelimit 0
altnet 0.0.0.0/0
phyint vlan52 downstream ratelimit 0
§ pf.conf(5)
tv_in_if = "vlan52"
tv_if = "vlan840"
...
# TV stuff is prio 5? https://lafibre.info/remplacer-livebox/tuto-remplacer-la-livebox-par-un-routeur-dd-wrt-internet-tv/
match out log on $tv_if proto igmp set (prio 5, tos 0xc0)
match out log on egress inet from ($tv_in_if:network) to ! (self:network) nat-to (egress)
pass in quick log on $tv_in_if to 224.0.0.0/4 allow-opts # decoder -> router
pass out quick log on $tv_if to 224.0.0.0/4 allow-opts # router -> decoder
pass in quick log on $tv_if inet proto udp from any to 224.0.0.0/4 port { 8200 8202 } # we avoid 'any' and can't use '($tv_in_if:network)'
pass quick log on { $tv_if $tv_in_if } proto igmp allow-opts
# no idea what 7443 and 9443 ports are for, but they seem to be necessary
pass in quick log on $tv_in_if proto { tcp udp } to ! (self:network) port { ntp https http 7443 9443 } flags any
The TV decoder can now access live TV!
§ gaslighting DNS
We already covered that part in a previous article. M6+ is using known advertising servers, so needs no further config.
After quickly trying TF1’s replay service, it looks like some ads make it through, but suspicious domains can be listed:
tcpdump -nei vlan52 -s 65535 port domain
Orange is doing some strange things with DNS (it’s always
DNS) so by telling the TV decoder to use our gaslighting DNS, we
will run into issues (try resolving
authentification.stb.orion.itv.orange.fr.
). A specific forward-zone
can be configured:
forward-zone:
name: orange.fr
# DNS servers from the DHCP leases
forward-addr: 80.xx.xx.xx
forward-addr: 81.xx.xx.xx
TODO: restrict that forward-zone
to only
10.207.52.0/24
.
For what it’s worth, here is the list of allow- and block-listed domains for my LG C1:
# TV
## LG OS
lgtvcommon.com
lge.com
lgeapi.com
# Only fr. needed for primevideo to work (refuses to work otherwise)
fr.lgtvsdp.com
fr.security.lgtvsdp.com
apple.com
cdn-apple.com
# Apple CDN and related stuff
mzstatic.com
lgtviot.com
d184dfn36gombl.cloudfront.net
lgappstv.com
go.microsoft.com
NevoAI-IOTHub-43-Prod.azure-devices.net
## primevideo
# same site as Netflix
aiv-cdn.net
aiv-delivery.net
amazonvideo.com
atv-ext-eu.amazon.com
atv-ext-fe.amazon.com
atv-ext.amazon.com
atv-ps-eu.amazon.co.uk
atv-ps-eu.amazon.com
atv-ps-fe.amazon.co.jp
atv-ps-fe.amazon.com
atv-ps.amazon.com
primevideo.com
pv-cdn.net
video.a2z.com
media-amazon.com
api.amazon.com
api.amazon.co.uk
fls-eu.amazon.com
s3.amazonaws.com
# Disney+
bamgrid.com
disneyplus.com
disney-plus.net
dssott.com
www.googleapis.com
# Not sure
ssl-images-amazon.com
securetime.playready.microsoft.com
akamaihd.net
ia.media-imdb.com
# TLS
ocsp.digicert.com
crl3.digicert.com
# From https://raw.githubusercontent.com/Perflyst/PiHoleBlocklist/master/SmartTV.txt
aic-ngfts.lge.com
ngfts.lge.com
cdpbeacon.lgtvcommon.com
rdl.lgtvcommon.com
sports.lgtviot.com
lgtvonline.lge.com
# https://old.reddit.com/r/LGOLED/comments/qt392k/remove_recommended_and_trending_videos_on_webos/
homeprv.lgtvcommon.com
nudge.lgtvcommon.com
recommend.lgtvcommon.com
service.lgtvcommon.com
#!/bin/sh
# transform a list into a redirect file for unbound(8)
: "${tag?"\$tag env not set"}"
awk -v "tag=$tag" \
'BEGIN { if( tag == "" ) { tag = "iot_allowlist" } }
/^#/ { print $0 }
/^[^ #]+/ { printf("local-zone: %s redirect\nlocal-zone-tag: %s \"%s\"\n", $1, $1, tag ) }'
< /var/unbound/etc/iot_blocklist.raw | tag=iot_blocklist /usr/local/bin/list_to_unbound > /var/unbound/etc/iot_blocklist.conf
[ SNIP ]
# IoT
access-control-tag: 10.207.51.0/24 "bad malware iot_allowlist iot_blocklist"
access-control-tag-action: 10.207.51.0/24 "iot_allowlist" transparent
access-control-tag-data: 10.207.51.0/24 "bad" "A 127.0.51.1"
access-control-tag-data: 10.207.51.0/24 "bad" "TXT bad"
access-control-tag-data: 10.207.51.0/24 "iot_blocklist" "A 127.0.51.2"
access-control-tag-data: 10.207.51.0/24 "iot_blocklist" "TXT iot_blocklist"
access-control-tag-data: 10.207.51.0/24 "malware" "A 127.0.51.3"
access-control-tag-data: 10.207.51.0/24 "malware" "TXT malware"
# define the blocklist
local-zone-tag: . "iot_blocklist"
local-zone: . redirect
# the lists are stored elsewhere
include: /var/unbound/etc/iot_allowlist.conf
include: /var/unbound/etc/iot_blocklist.conf
§ Amazon Prime
In 2024, Amazon started injecting ads into its Prime Video service for which I already pay 70€/year. I’m not paying 50% more for the privilege of not watching ads.
This time, I’m using the Prime app on my TV itself (4k, HDR), not on the decoder. The TV is inside its VLAN (number 51) with my robot vacuum and my doorbell, with only a DNS allowlist.
Check the usual suspects:
- block advertising domains? (nope, nothing stands out)
- block advertising requests? (LOL, that would require requests be made in plain text… wait, what?)
tcpdump -n -r /var/log/pflog port http
Jun 20 16:15:24.577397 2a01:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx.54339 > 2a00:1450:4007:81a::2003.80: S 3804016915:3804016915(0) win 64800 <[|tcp]> [flowlabel 0xa4146]
Jun 20 16:25:13.652786 2a01:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx.54398 > 2606:4700::6812:154c.80: S 3071556733:3071556733(0) win 64800 <[|tcp]> [flowlabel 0xce112]
Ah, that’s helpful. Thank you Amazon!
A short tcpdump(8)
capture later, we have some
suspects:
GET /interstitial/3/8629688/video_init.mp4?amznDtid=xxxxxxxxxxxx
GET /interstitial/3/8629688/audio_init.mp4?amznDtid=xxxxxxxxxxxx
GET /interstitial/3/8629688/video_39.mp4?amznDtid=xxxxxxxxxxxx
...
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_video_10.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_video_10.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_video_10.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
GET /ddf3/76ed/6401/4699-8471-3e4963bc91b1/563bf221-69e7-4ca2-bb11-299505c8bac8_audio_143.mp4?amznDtid=xxxxxxxx
...
§ relayd(8)
From the manpage:
Layer 7 relaying happens at the application level and is handled by relayd itself. Various application level filtering and protocol-specific load-balancing options are available for relays.
Looking through examples (/etc/examples/relayd.conf
), I
found exactly what I needed! It only needed to be tweaked for the
listen on
directive: because Orange doesn’t guarantee
addresses are stable, I can’t use the IPs of the vlan51
interface. Instead, I had to listen on lo0
.
#
# Relay and protocol for a transparent HTTP proxy
#
http protocol httpfilter {
# Return HTTP/HTML error pages to the client
return error
# Block disallowed URLs
match request label "URL filtered!"
block request quick url "pop-fr-cf.dash.pv-cdn.net/interstitial/" value "*"
}
relay httpproxy {
# Listen on localhost, accept diverted connections from pf(4)
listen on 127.0.0.1 port 8080
listen on ::1 port 8080
protocol httpfilter
# Forward to the original target host
forward to destination
}
§ pf.conf(5)
We use divert-to
as suggested in relayd.conf(5)
.
pass in quick log on $iot_if inet proto tcp to ! (self:network) port www divert-to 127.0.0.1 port 8080
pass in quick log on $iot_if inet6 proto tcp to ! (self:network) port www divert-to ::1 port 8080
Syntax nitpick: not possible to implicitly use both IPv4 and IPv6 with e.g.
pass in quick log on $iot_if proto tcp to ! (self:network) port www divert-to lo0 port 8080
# does NOT expand to cover both 127.0.0.1 and ::1 ----------------------------^^^
And… that’s it.