Skip to content

Instantly share code, notes, and snippets.

@itzloop
Last active April 27, 2024 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itzloop/f995cd386803b43f484307202f170e85 to your computer and use it in GitHub Desktop.
Save itzloop/f995cd386803b43f484307202f170e85 to your computer and use it in GitHub Desktop.
isolate_process.sh
#!/usr/bin/env bash
# Inspired by novpn to setup the cgroup v1 and all of it's edge cases
# general config
GP_NAME=${GP_NAME:="susgp"}
IF_NAME=${IF_NAME:="susif"}
IF_ADDR=${IF_ADDR:="10.20.30.40/24"}
DEF_ROUTE=${DEF_ROUTE:="10.20.30.1"}
PROXY=${PROXY:="socks5://127.0.0.1:2080"}
# Advanced config
RT_NAME=${RT_NAME:="susrt"}
RT_ID=${RT_ID:="1234"}
CG_ROOT=${CG_ROOT:="/sys/fs/cgroup"}
NET_CLS_ROOT=${NET_CLS_ROOT:="$CG_ROOT/net_cls"}
RT_DIR=${RT_DIR:="/etc/iproute2"}
RT_FILE=${RT_FILE:="$RT_DIR/rt_tables"}
FW_MARK=${FW_MARK:="20"}
CLASSID=${CLASSID:="0x10f2c"}
# logging configuration
readonly LOG_LEVEL_DEBUG=1
readonly LOG_LEVEL_INFO=2
readonly LOG_LEVEL_ERROR=3
readonly COLOR_INFO="\033[32m" # Green
readonly COLOR_DEBUG="\033[34m" # Blue
readonly COLOR_ERROR="\033[31m" # Red
readonly COLOR_RESET="\033[0m" # Reset color
LOG_LEVEL=${LOG_LEVEL:="info"}
log() {
local level="$1"
local message="$2"
# Check if log level is enabled
if [[ $level -ge $LOG_LEVEL ]]; then
local color=""
case $level in
$LOG_LEVEL_INFO)
color="$COLOR_INFO"
;;
$LOG_LEVEL_DEBUG)
color="$COLOR_DEBUG"
;;
$LOG_LEVEL_ERROR)
color="$COLOR_ERROR"
;;
esac
echo -e "$color[$(date +'%Y-%m-%d %H:%M:%S')] $message$COLOR_RESET"
fi
}
# Specific log level functions (call the general log function)
info() {
log $LOG_LEVEL_INFO "$@"
}
debug() {
log $LOG_LEVEL_DEBUG "$@"
}
error() {
log $LOG_LEVEL_ERROR "$@"
}
show_help() {
echo "Usage: $0 [--i-know-what-im-doing] [--log-level level] [--help]"
echo " -h, --help Display help message."
echo " -i, --i-know-what-im-doing Skip confirmation prompt."
echo " -l, --log-level level Set log level (debug, info, error)."
}
VALID_ARGS=$(getopt -o "hil:" -l "help,i-know-what-im-doing,log-level:" -- "$@")
if [[ $? -ne 0 ]]; then
exit 1;
fi
eval set -- "$VALID_ARGS"
while [ : ]; do
case "$1" in
-i | --i-know-what-im-doing)
CONFIRMATION=true
shift
;;
-l | --log-level)
shift
LOG_LEVEL="$1"
shift
;;
-h | --help)
show_help
exit 0
;;
\?)
error "Invalid option: -$OPTARG"
show_help
exit 1
;;
--) shift;
break
;;
esac
done
shift "$((OPTIND-1))" # Shift past processed options
# Convert LOG_LEVEL string to integer level (lowercase for case-insensitive matching)
case $(tr [:upper:] [:lower:] <<< "$LOG_LEVEL") in
"debug")
LOG_LEVEL="$LOG_LEVEL_DEBUG"
;;
"info")
LOG_LEVEL="$LOG_LEVEL_INFO"
;;
"error")
LOG_LEVEL="$LOG_LEVEL_ERROR"
;;
*)
error "Invalid LOG_LEVEL: '$LOG_LEVEL' possible values are: debug, info, error"
exit 1
;;
esac
debug "Log level set to: $LOG_LEVEL"
debug "Confirmation skipped with --i-know-what-im-doing flag."
# check if tun2socks exists on the machinie
tun2socks -version 2>&1 > /dev/null || (error "this script requires tun2socks\ninstall tun2socks from here: https://github.com/xjasonlyu/tun2socks/wiki/Install-from-Source#build"; exit 1)
# Exit on error
set -e
# Uncomment for debugging
# set -x
check_sudo() {
if ! sudo -n true > /dev/null 2>&1; then
error "This script requires root privileges. Please run with sudo."
exit 1
fi
# Confirmation check (skipped with --i-know-what-im-doing)
if [[ -z "$CONFIRMATION" ]]; then
echo -e "This script will be using root privileges (run with --i-know-what-im-doing if you don't want to see this confirmation).\nAre you sure you want to continue? (y/N)"
read -r confirmation
if [[ "$confirmation" != [Yy] ]]; then
echo "Exiting script."
exit 0
fi
fi
}
create_rt() {
local rt="$1"
local rt_path="$2"
local fwmark="$3"
local def_route="$4"
info "creating routing table: $1"
echo "$rt" >> "$rt_path"
}
setup_routes_and_rules() {
local rt="$1"
local fwmark="$2"
local def_route="$3"
info "adding default route"
ip route add default via $def_route table $rt
info "adding ip rule"
ip rule add fwmark $fwmark table $rt
}
remove_routes_and_rules() {
local rt="$1"
local fwmark="$2"
local def_route="$3"
info "removing default route"
ip route del default via $def_route table $rt
info "removing ip rule"
ip rule del fwmark $fwmark table $rt
}
create_iface() {
local ifname=$1
local ifaddr=$2
info "creating tun interface: $ifname"
ip tuntap add mode tun dev $ifname
info "adding addr to interface: $ifaddr"
ip addr add $ifaddr dev $ifname
info "bringing up the interface"
ip link set dev $ifname up
}
remove_iface() {
local ifname=$1
local ifaddr=$2
info "bringing down the interface"
ip link set dev $ifname down
info "removing addr to interface: $ifaddr"
ip addr del $ifaddr dev $ifname
info "removing tun interface: $ifname"
ip link del $ifname
}
create_cgroup() {
local cgpath="$1"
local classid="$2"
info "creating new cgroup $cgpath"
mkdir $cgpath
info "setting classid to $classid"
echo -n "$classid" | tee "$cgpath/net_cls.classid" 2>&1 > /dev/null
}
remove_cgroup() {
local cgpath="$1"
local cgroot="$2"
info "moving all processes to default net_cls groups"
while read -r pid; do
echo $pid | tee -a /sys/fs/cgroup/net_cls/cgroup.procs 2>&1 > /dev/null
done < "$cgpath/cgroup.procs"
info "removing net_cls group"
rmdir "$cgpath"
if [ $NET_CLS_MOUNTED == "1" ]; then
info "unmount net_cls"
umount "$cgroot"
info "removing net_cls default group"
rmdir "$cgroot"
fi
}
check_sudo
if [ ! -d "$CG_ROOT" ]; then
error "$CG_ROOT does not exists"
exit 1
fi
NET_CLS_MOUNTED="0"
if [ ! -d "$NET_CLS_ROOT" ]; then
info "net_cls is not mounted, mounting on $NET_CLS_ROOT"
mkdir "$NET_CLS_ROOT"
mount -t cgroup -o net_cls net_cls "$NET_CLS_ROOT"
NET_CLS_MOUNTED="1"
fi
if [ ! -d "$RT_DIR" ]; then
info "$RT_DIR does not exist, creating..."
mkdir "$RT_DIR"
fi
if [ ! -f "$RT_FILE" ]; then
info "$RT_FILE does not exist, creating..."
touch "$RT_FILE"
fi
# check if rt exists if not create it
#
[ "$(grep -nc $RT_NAME $RT_FILE)" != "0" ] || info "creating routing table: $1"; echo "$RT_ID $RT_NAME" >> $RT_FILE
[ $(ip -j link | jq -rc ".[] | select(.ifname==\"$IF_NAME\")" | wc -l) != "0" ] || create_iface $IF_NAME $IF_ADDR
setup_routes_and_rules $RT_NAME $FW_MARK $DEF_ROUTE
create_cgroup "$NET_CLS_ROOT/$GP_NAME" $CLASSID
info "adding firewall rule to mark packets"
iptables -t mangle -A OUTPUT -m cgroup --cgroup $CLASSID -j MARK --set-mark $FW_MARK
info "bring up tun2socks"
tun2socks -device $IF_NAME -proxy $PROXY -interface lo --loglevel error
info "removing firewall rule to mark packets"
iptables -t mangle -D OUTPUT -m cgroup --cgroup $CLASSID -j MARK --set-mark $FW_MARK
remove_cgroup "$NET_CLS_ROOT/$GP_NAME" "$NET_CLS_ROOT"
remove_routes_and_rules $RT_NAME $FW_MARK $DEF_ROUTE
remove_iface $IF_NAME $IF_ADDR
@itzloop
Copy link
Author

itzloop commented Apr 24, 2024

How to use the script

This script assumes that you have a socks5 proxy running at 127.0.0.1:2080. You can overwrite this by changing PROXY variable.

  1. Install tun2socks
  2. Run the script (requires root)
sudo ./isolate_process.sh

Run it with different log_level:

sudo LOG_LEVEL=debug ./isolate_process.sh

Possible log values: [debug, info, error]

Bringing a process to or out of the group

After running the script, in a different shell, put the process ids you want to tunnel in the created CGroup with the following command

$ echo <process id> | sudo tee -a /sys/fs/cgroup/net_cls/susgp/cgroup.procs

If you have changed GP_NAME in the script, you have to use that insteada.

To put a process to the default group:

$ echo <process id> | sudo tee -a /sys/fs/cgroup/net_cls/cgroup.procs

@itzloop
Copy link
Author

itzloop commented Apr 24, 2024

Also, it runs on my machine 😉

$ uname -msr
Linux 6.1.55-1-MANJARO x86_64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment