Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions files/services/S995cloudblock
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#!/bin/sh
#
# Block Creality Cloud Telemetry
# Credit: C0DEbrained
#
# Blackholes the hosts the stock Creality daemons use for telemetry, remote
# access and OTA checks, and drops outbound MQTT to anything off the LAN.
#
# Re-applied at every boot on purpose: iptables rules never survive a reboot on
# any model, and on the K1 2025 the rootfs is a volatile RAM filesystem, so
# /etc/hosts reverts to stock at every start.
#
# This is a blocklist, not a firewall. The OUTPUT policy is left untouched and
# nothing else is filtered, so the cloud relay options the helper installs keep
# working.
#
# Everything written here is owned and delimited: the hosts entries live between
# two markers, the firewall rules live in their own chain. Nothing is matched by
# content, so uninstall cannot remove a hosts line or a firewall rule that
# something else created.

# A root SSH shell on CrealityOS inherits umask 077 while boot uses 022, so pin
# it here, same reason S56moonraker_service does.
umask 022

HOSTS="/etc/hosts"
BEGIN_MARKER="# BEGIN Creality Helper Script - Block Creality Cloud Telemetry"
END_MARKER="# END Creality Helper Script - Block Creality Cloud Telemetry"
CHAIN="CREALITY_CLOUDBLOCK"
MQTT_PORTS="1883 8883"
LAN_WAIT_SECONDS=5

# Hosts found in the stock vectorp and onyxp binaries, in alchemistp's
# config.json, and on the wire during a print.
BLACKHOLE="
api.crealitycloud.com
mqtt.crealitycloud.com
cxsw-cdn.crealitycloud.com
model-cdn.crealitycloud.com
admin-pre.crealitycloud.com
pre-tb-iot.crealitycloud.com
www.crealitycloud.com
api.crealitycloud.cn
mqtt.crealitycloud.cn
admin-pre.crealitycloud.cn
api-dev.crealitycloud.cn
www.crealitycloud.cn
c-smart.cxswyjy.com
c-smart-cn-local.cxswyjy.com
devdata.cxswyjy.com
www.creality.com
"

IPT=""
IPTABLES_STATE=""

# The K1 and Ender-3 V3 series ship without an iptables binary even though the
# kernel module is loaded, which is a normal state this script tolerates. A
# binary that is present but unusable is a real error and must not be reported
# as "this model has no iptables".
probe_iptables() {
[ -n "$IPTABLES_STATE" ] && return 0
if ! command -v iptables > /dev/null 2>&1; then
IPTABLES_STATE="absent"
return 0
fi
# -w serialises against anything else writing the same table. iptables 1.8
# knows the flag, older builds do not, so fall back rather than fail every
# call.
if iptables -w 5 -L OUTPUT -n > /dev/null 2>&1; then
IPT="iptables -w 5"
IPTABLES_STATE="ok"
elif iptables -L OUTPUT -n > /dev/null 2>&1; then
IPT="iptables"
IPTABLES_STATE="ok"
else
IPTABLES_STATE="error"
fi
}

ipt() {
$IPT "$@"
}

# Tested with -S rather than -C so this works on iptables builds predating -C.
chain_is_linked() {
ipt -S OUTPUT 2>/dev/null | grep -q -- "-j $CHAIN"
}

# Directly attached subnets, so a local MQTT broker stays reachable. Never
# guess: no route, no LAN exemption. Matched on "src" plus a CIDR in the first
# field rather than on "proto kernel", which not every ip implementation prints.
lan_networks() {
ip route 2>/dev/null | awk '/[[:space:]]src[[:space:]]/ && $1 ~ /\// { print $1 }'
}

# At boot this can run before DHCP has finished, which would build the chain
# with no LAN exemption and leave a local MQTT broker unreachable until the next
# boot. Wait briefly, but never hold up a printer that has no network at all.
wait_for_lan() {
local waited=0
while [ -z "$(lan_networks)" ] && [ "$waited" -lt "$LAN_WAIT_SECONDS" ]; do
sleep 1
waited=$((waited + 1))
done
}

# Everything outside the managed region, verbatim. A begin marker with no end
# marker deletes to EOF, which is the right recovery from an interrupted write.
hosts_without_block() {
[ -f "$HOSTS" ] || return 0
sed "/^${BEGIN_MARKER}\$/,/^${END_MARKER}\$/d" "$HOSTS"
}

# Rewritten in a single pass rather than appended line by line, so there is one
# window in which a concurrent writer could be clobbered instead of seventeen.
# Copied back with cat rather than mv to keep the original inode, mode and owner
# and to leave a symlinked /etc/hosts pointing where it did before.
hosts_write() {
local host
local last
local tmp="${HOSTS}.cloudblock.$$"
hosts_without_block > "$tmp" 2>/dev/null || { rm -f "$tmp"; return 1; }
if [ "$1" = "block" ]; then
# sed preserves a missing final newline, so without this the begin marker
# would be appended onto the end of the last existing line. That corrupts
# the line and, worse, hides the marker from the range delete on uninstall,
# stranding the whole block. Fails closed: if tail -c is unavailable the
# newline goes in anyway, because a blank line is harmless and a joined line
# is not.
if [ -s "$tmp" ]; then
last=$(tail -c 1 "$tmp" 2>/dev/null) || last="x"
[ -z "$last" ] || printf '\n' >> "$tmp"
fi
{
printf '%s\n' "$BEGIN_MARKER"
for host in $BLACKHOLE; do
printf '0.0.0.0 %s\n' "$host"
done
printf '%s\n' "$END_MARKER"
} >> "$tmp" || { rm -f "$tmp"; return 1; }
fi
cat "$tmp" > "$HOSTS" || { rm -f "$tmp"; return 1; }
rm -f "$tmp"
}

# Rebuilt from scratch on every start, so a changed LAN, a hand-edited entry or
# a host list that grew between versions all self-heal instead of accumulating.
mqtt_block() {
local lan
local port
local rc=0

probe_iptables
case "$IPTABLES_STATE" in
absent)
return 0
;;
error)
echo "Warning: iptables is present but unusable, MQTT port rules skipped." >&2
return 1
;;
esac

wait_for_lan

ipt -N "$CHAIN" 2>/dev/null
ipt -F "$CHAIN" || rc=1

for port in $MQTT_PORTS; do
# A blackholed host resolves to 0.0.0.0, which the kernel routes to
# loopback. Without this the drop below swallows that silently instead of
# letting it fail fast with a RST.
ipt -A "$CHAIN" -o lo -p tcp --dport "$port" -j ACCEPT || rc=1
for lan in $(lan_networks); do
ipt -A "$CHAIN" -p tcp -d "$lan" --dport "$port" -j ACCEPT || rc=1
done
# vectorp carries hardcoded broker IPs (47.114.48.45:1883 and
# 120.55.101.240:1883) that bypass DNS entirely, so the port rules are what
# actually stops it.
ipt -A "$CHAIN" -p tcp --dport "$port" -j DROP || rc=1
done

# Inserted, not appended: an OUTPUT chain that already carries a broad ACCEPT
# would otherwise match first and the drops would never be reached.
chain_is_linked || ipt -I OUTPUT -j "$CHAIN" || rc=1

[ "$rc" -eq 0 ] || echo "Warning: some MQTT port rules could not be applied." >&2
return "$rc"
}

mqtt_unblock() {
probe_iptables
[ "$IPTABLES_STATE" = "ok" ] || return 0

while chain_is_linked; do
ipt -D OUTPUT -j "$CHAIN" 2>/dev/null || break
done
ipt -F "$CHAIN" 2>/dev/null
ipt -X "$CHAIN" 2>/dev/null
return 0
}

rc=0

case "$1" in
start)
echo "Blocking Creality cloud telemetry..."
hosts_write block || { echo "Error: could not update $HOSTS." >&2; rc=1; }
mqtt_block || rc=1
;;
stop)
echo "Unblocking Creality cloud telemetry..."
mqtt_unblock
hosts_write unblock || { echo "Error: could not restore $HOSTS." >&2; rc=1; }
;;
restart|force-reload)
"$0" stop
"$0" start
rc=$?
;;
status)
if grep -q "^${BEGIN_MARKER}\$" "$HOSTS" 2>/dev/null; then
echo "Hosts blocklist: active ($(sed -n "/^${BEGIN_MARKER}\$/,/^${END_MARKER}\$/p" "$HOSTS" | grep -c '^0\.0\.0\.0') entries)"
else
echo "Hosts blocklist: inactive"
fi
probe_iptables
case "$IPTABLES_STATE" in
absent)
echo "MQTT port rules: skipped (no iptables on this model)"
;;
error)
echo "MQTT port rules: iptables is present but unusable"
rc=1
;;
ok)
if chain_is_linked; then
ipt -L "$CHAIN" -v -n
else
echo "MQTT port rules: chain $CHAIN is not linked into OUTPUT"
fi
;;
esac
;;
*)
echo "Usage: $0 {start|stop|restart|force-reload|status}"
exit 1
;;
esac

exit "$rc"
86 changes: 86 additions & 0 deletions scripts/block_creality_cloud.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/sh

set -e

function block_creality_cloud_message(){
top_line
title 'Block Creality Cloud Telemetry' "${yellow}"
inner_line
hr
echo -e " │ ${cyan}This allows to blackhole the Creality cloud hosts used by ${white}│"
echo -e " │ ${cyan}stock daemons for telemetry, remote access and OTA checks, ${white}│"
echo -e " │ ${cyan}and to drop outbound MQTT to anything outside your network. ${white}│"
hr
echo -e " │ ${cyan}Remote access options (OctoEverywhere, Obico, GuppyFLO, ${white}│"
echo -e " │ ${cyan}SimplyPrint, OctoApp, Mobileraker) are not affected. ${white}│"
hr
bottom_line
}

function install_block_creality_cloud(){
block_creality_cloud_message
local yn
while true; do
install_msg "Block Creality Cloud Telemetry" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Copying service file..."
cp "$CLOUD_BLOCK_SERVICE_URL" "$CLOUD_BLOCK_SERVICE_FILE"
chmod 755 "$CLOUD_BLOCK_SERVICE_FILE"
echo -e "Info: Applying blocklist..."
local start_rc
set +e
"$CLOUD_BLOCK_SERVICE_FILE" start
start_rc=$?
set -e
if [ "$start_rc" -ne 0 ]; then
error_msg "Block Creality Cloud Telemetry was installed but did not apply cleanly!"
echo -e " ${darkred}Run '$CLOUD_BLOCK_SERVICE_FILE status' to see what is missing.${white}"
echo
return
fi
ok_msg "Block Creality Cloud Telemetry has been installed successfully!"
if ! command -v iptables > /dev/null 2>&1; then
echo -e " ${darkred}Note: this printer has no iptables, so the MQTT port rules were skipped.${white}"
echo -e " ${darkred}The hosts blocklist is still active.${white}"
echo
fi
echo -e " ${white}Connections already open are not torn down, reboot to apply it fully.${white}"
echo
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

function remove_block_creality_cloud(){
block_creality_cloud_message
local yn
while true; do
remove_msg "Block Creality Cloud Telemetry" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Restoring hosts file and removing rules..."
set +e
if [ -f "$CLOUD_BLOCK_SERVICE_FILE" ]; then
"$CLOUD_BLOCK_SERVICE_FILE" stop
fi
set -e
echo -e "Info: Removing file..."
rm -f "$CLOUD_BLOCK_SERVICE_FILE"
ok_msg "Block Creality Cloud Telemetry has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
15 changes: 15 additions & 0 deletions scripts/menu/10SE/customize_menu_10SE.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ function customize_menu_ui_10se() {
hr
menu_option '5' 'Install' 'Creality Dynamic Logos for Fluidd'
hr
menu_option '6' 'Install' 'Block Creality Cloud Telemetry'
menu_option '7' 'Remove' 'Block Creality Cloud Telemetry'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
Expand Down Expand Up @@ -79,6 +82,18 @@ function customize_menu_10se() {
else
run "install_creality_dynamic_logos" "customize_menu_ui_10se"
fi;;
6)
if [ -f "$CLOUD_BLOCK_SERVICE_FILE" ]; then
error_msg "Block Creality Cloud Telemetry is already installed!"
else
run "install_block_creality_cloud" "customize_menu_ui_10se"
fi;;
7)
if [ ! -f "$CLOUD_BLOCK_SERVICE_FILE" ]; then
error_msg "Block Creality Cloud Telemetry is not installed!"
else
run "remove_block_creality_cloud" "customize_menu_ui_10se"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
Expand Down
1 change: 1 addition & 0 deletions scripts/menu/10SE/info_menu_10SE.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function info_menu_ui_10se() {
info_line "$(check_file_10se "$CREALITY_WEB_FILE")" 'Creality Web Interface'
info_line "$(check_folder_10se "$GUPPY_SCREEN_FOLDER")" 'Guppy Screen'
info_line "$(check_file_10se "$FLUIDD_LOGO_FILE")" 'Creality Dynamic Logos for Fluidd'
info_line "$(check_file_10se "$CLOUD_BLOCK_SERVICE_FILE")" 'Block Creality Cloud Telemetry'
hr
inner_line
hr
Expand Down
Loading