A flexible tool for redirecting a program's TCP, UDP, and DNS traffic to SOCKS5 or HTTP proxies.
C
2,586
377 commits
updated Aug 25, 2026
English | 简体中文
graftcp redirects TCP connections from an arbitrary Linux process to SOCKS5 or HTTP proxies by tracing socket syscalls with ptrace(2). It can also redirect UDP/53 DNS queries to an embedded DNS-over-TCP forwarder and proxy generic UDP through SOCKS5 UDP ASSOCIATE or direct UDP when explicitly enabled.
The old graftcp + graftcp-local split runtime has been merged into a single command:
graftcp is now the primary entrypoint.mgraftcp remains available as a compatibility alias./proc scan, or netlink socket reverse lookup.Compared with tsocks, proxychains, or proxychains-ng, this still does not depend on LD_PRELOAD, so statically linked binaries such as most Go programs can be traced as well.
graftcp runs on Linux. Building it requires Go and a C toolchain.
git clone https://github.com/hmgle/graftcp.git
cd graftcp
make
The build output is local/graftcp. local/mgraftcp is created as a compatibility alias.
To install it:
sudo make install
$ ./local/graftcp --help
Usage: graftcp [-hn] [-b value] [--config value] [--disable-dns] [--disable-udp] [--dns-server value] [--enable-debug-log] [--enable-dns] [--enable-udp] [--http_proxy value] [--select_proxy_mode value] [--socks5 value] [--socks5_password value] [--socks5_username value] [-u value] [--version] [-w value] [parameters ...]
-b, --blackip-file=value
The IP in black-ip-file will connect direct
--config=value
Path to the configuration file
--disable-dns Disable DNS proxy
--disable-udp Disable generic UDP proxy
--dns-server=value
DNS upstream server address, e.g.: 1.1.1.1:53 [1.1.1.1:53]
--enable-debug-log
Enable debug log
--enable-dns Enable DNS proxy for UDP/53 queries
--enable-udp Enable generic UDP proxy
-h, --help Display this help and exit
--http_proxy=value
http proxy address, e.g.: 127.0.0.1:8080
-n, --not-ignore-local
Connecting to local is not changed by default, this option
will redirect it to SOCKS5
--select_proxy_mode=value
Set the mode for select a proxy [auto | random |
only_http_proxy | only_socks5 | direct] [auto]
--socks5=value
SOCKS5 address, e.g.: 127.0.0.1:1080 or unix:/path/tor.sock
(default 127.0.0.1:1080 when no proxy is configured)
--socks5_password=value
SOCKS5 password
--socks5_username=value
SOCKS5 username
-u, --username=value
Run command as USERNAME handling setuid and/or setgid
--version Print version information
-w, --whiteip-file=value
Only redirect the connect that destination ip in the
white-ip-file to SOCKS5
Examples:
./local/graftcp --socks5 127.0.0.1:1080 curl https://example.com
./local/graftcp --select_proxy_mode only_socks5 --socks5 unix:/path/tor.sock curl https://example.com
./local/graftcp --enable-dns --dns-server 1.1.1.1:53 curl https://example.com
./local/graftcp --enable-udp --socks5 127.0.0.1:1080 your-udp-client
./local/graftcp --http_proxy 127.0.0.1:8080 git clone https://github.com/hmgle/graftcp.git
./local/graftcp bash --rcfile <(echo 'PS1="(graftcp) $PS1"')
mgraftcp can be used in the same way for users who adopted the merged binary before this command name was restored.
graftcp looks for configuration files in this order:
--config$(dirname $0)/graftcp.conf$(dirname $0)/mgraftcp.conf$XDG_CONFIG_HOME/graftcp/graftcp.conf$XDG_CONFIG_HOME/mgraftcp/mgraftcp.conf$HOME/.config/graftcp/graftcp.conf$HOME/.config/mgraftcp/mgraftcp.conf/etc/graftcp/graftcp.conf/etc/mgraftcp/mgraftcp.confAn example config is available in example-graftcp.conf. example-mgraftcp.conf is kept for compatibility with the alias name.
Config keys mirror the CLI flags: dns_proxy = true -> --enable-dns, udp_proxy = true -> --enable-udp, and ignore_local = false -> --not-ignore-local. CLI flags override config values.
graftcp starts an embedded local TCP listener and then traces the target command with ptrace(2).connect(2), it records the original destination in an in-process route table and allocates a unique loopback token IP from 127.0.0.0/8.LocalAddr(), resolves the original destination from the route table, then dials the configured SOCKS5/HTTP/direct path.graftcp restores the tracee's original sockaddr buffer on a best-effort basis.For IPv6 connect(2), graftcp rewrites to an IPv4-mapped loopback address (::ffff:127.x.y.z) so the same token registry can be reused.
When DNS proxying is enabled, graftcp also starts an embedded UDP DNS listener. UDP connect() and sendto() calls to port 53 are rewritten to that listener, and each DNS payload is forwarded to the configured upstream DNS server over TCP through the same proxy selection path. DNS sockaddr rewrites are intentionally left in place after the syscall returns so resolvers such as musl, which validate replies against the send-side sockaddr buffer, continue to see the embedded DNS proxy as the expected peer.
When generic UDP proxying is enabled, graftcp starts a separate UDP listener. UDP connect(), sendto(), and sendmsg() targets are rewritten to loopback token endpoints; the embedded listener maps each token back to the original destination and forwards packets through SOCKS5 UDP ASSOCIATE when SOCKS5 is selected, or direct UDP in direct mode and fallback cases.
ptrace(2) permissions still apply. If tracing is blocked, check Yama ptrace_scope, capabilities, or run as root when appropriate.graftcp installs a seccomp-BPF filter so only socket-related syscalls trap into the tracer, reducing tracing overhead; on kernels without seccomp filtering it falls back to stopping on every syscall. This is automatic and needs no configuration.--not-ignore-local to proxy loopback/private-local connects as well.--enable-dns to enable the UDP/53 DNS-over-TCP path, and --dns-server to choose the upstream server.--enable-udp to enable it.auto prefers SOCKS5 UDP when available and falls back to direct UDP if the SOCKS5 UDP association fails; only_http_proxy rejects generic UDP sessions.127.0.0.1:1080) or a Unix socket endpoint (unix:/path/tor.sock or /path/tor.sock) for TCP CONNECT traffic. SOCKS5 UDP ASSOCIATE still requires a TCP SOCKS5 endpoint.--socks5 nor --http_proxy is configured, graftcp falls back to SOCKS5 127.0.0.1:1080. Configuring any proxy disables this implicit default, so --http_proxy alone never dials a SOCKS5 endpoint.connect() / sendto() / sendmsg() returns on a best-effort basis; DNS proxy rewrites are kept in place for resolver source-address checks. Clients that require recvfrom() to report the original remote address may still not be fully transparent.IPV6_V6ONLY=1 are out of scope by design.dup* and fcntl(F_DUPFD*) are copied best-effort, but close_range(), unshare(CLONE_FILES), and full shared fd-table semantics are intentionally not modeled.graftcp redirect a connection whose target is localhost?Not by default. Local destinations are ignored. Use --not-ignore-local to redirect them too. To ignore more addresses, add them to a black-ip file (-b); to redirect only certain addresses, add them to a white-ip file (-w). See graftcp --help.
graftcp handle DNS?It can. Use --enable-dns to forward UDP/53 queries as DNS-over-TCP through the selected proxy, and --dns-server to choose the upstream resolver. Tools like dnscrypt-proxy remain good complements.
graftcp yay or graftcp sudo ... errors out and exits. How to fix?yay on Arch Linux actually invokes sudo pacman ..., which requires the tracer to have privileges to trace the child process. Start graftcp with sudo and drop back to your user for the command: sudo graftcp sudo -u $USER yay, or sudo graftcp -u $USER sudo ....
If that is too verbose, give a copy of the binary the needed capabilities:
cp local/graftcp sumg
sudo setcap 'cap_sys_ptrace,cap_sys_admin+ep' ./sumg
# ./sumg yay
# ./sumg sudo ...
clone(2) has a CLONE_UNTRACED flag to avoid being traced. How does graftcp force tracing?graftcp intercepts clone(2) and clears CLONE_UNTRACED, so spawned children cannot escape tracing. That flag is meant for the kernel; user space should not set it. Linux can also restrict ptrace(2) via /proc/sys/kernel/yama/ptrace_scope. If tracing fails, check this value.
No. macOS's ptrace(2) is not capable enough for this. See issue 12.
getpeername() / getsockname() so traced programs observe the original remote addresssendmmsg() for generic UDP proxyingCopyright © 2016, 2018-2026 Hmgle <dustgle@gmail.com>
Released under the terms of the GNU General Public License, version 3.
789 followers · starred Dec 2021
854 followers · starred Apr 2019
3,989 followers · starred Aug 2025
141 followers · starred Apr 2021
A flexible tool for redirecting a program's TCP, UDP, and DNS traffic to SOCKS5 or HTTP proxies.
C
2,586
377 commits
updated Aug 25, 2026
English | 简体中文
graftcp redirects TCP connections from an arbitrary Linux process to SOCKS5 or HTTP proxies by tracing socket syscalls with ptrace(2). It can also redirect UDP/53 DNS queries to an embedded DNS-over-TCP forwarder and proxy generic UDP through SOCKS5 UDP ASSOCIATE or direct UDP when explicitly enabled.
The old graftcp + graftcp-local split runtime has been merged into a single command:
graftcp is now the primary entrypoint.mgraftcp remains available as a compatibility alias./proc scan, or netlink socket reverse lookup.Compared with tsocks, proxychains, or proxychains-ng, this still does not depend on LD_PRELOAD, so statically linked binaries such as most Go programs can be traced as well.
graftcp runs on Linux. Building it requires Go and a C toolchain.
git clone https://github.com/hmgle/graftcp.git
cd graftcp
make
The build output is local/graftcp. local/mgraftcp is created as a compatibility alias.
To install it:
sudo make install
$ ./local/graftcp --help
Usage: graftcp [-hn] [-b value] [--config value] [--disable-dns] [--disable-udp] [--dns-server value] [--enable-debug-log] [--enable-dns] [--enable-udp] [--http_proxy value] [--select_proxy_mode value] [--socks5 value] [--socks5_password value] [--socks5_username value] [-u value] [--version] [-w value] [parameters ...]
-b, --blackip-file=value
The IP in black-ip-file will connect direct
--config=value
Path to the configuration file
--disable-dns Disable DNS proxy
--disable-udp Disable generic UDP proxy
--dns-server=value
DNS upstream server address, e.g.: 1.1.1.1:53 [1.1.1.1:53]
--enable-debug-log
Enable debug log
--enable-dns Enable DNS proxy for UDP/53 queries
--enable-udp Enable generic UDP proxy
-h, --help Display this help and exit
--http_proxy=value
http proxy address, e.g.: 127.0.0.1:8080
-n, --not-ignore-local
Connecting to local is not changed by default, this option
will redirect it to SOCKS5
--select_proxy_mode=value
Set the mode for select a proxy [auto | random |
only_http_proxy | only_socks5 | direct] [auto]
--socks5=value
SOCKS5 address, e.g.: 127.0.0.1:1080 or unix:/path/tor.sock
(default 127.0.0.1:1080 when no proxy is configured)
--socks5_password=value
SOCKS5 password
--socks5_username=value
SOCKS5 username
-u, --username=value
Run command as USERNAME handling setuid and/or setgid
--version Print version information
-w, --whiteip-file=value
Only redirect the connect that destination ip in the
white-ip-file to SOCKS5
Examples:
./local/graftcp --socks5 127.0.0.1:1080 curl https://example.com
./local/graftcp --select_proxy_mode only_socks5 --socks5 unix:/path/tor.sock curl https://example.com
./local/graftcp --enable-dns --dns-server 1.1.1.1:53 curl https://example.com
./local/graftcp --enable-udp --socks5 127.0.0.1:1080 your-udp-client
./local/graftcp --http_proxy 127.0.0.1:8080 git clone https://github.com/hmgle/graftcp.git
./local/graftcp bash --rcfile <(echo 'PS1="(graftcp) $PS1"')
mgraftcp can be used in the same way for users who adopted the merged binary before this command name was restored.
graftcp looks for configuration files in this order:
--config$(dirname $0)/graftcp.conf$(dirname $0)/mgraftcp.conf$XDG_CONFIG_HOME/graftcp/graftcp.conf$XDG_CONFIG_HOME/mgraftcp/mgraftcp.conf$HOME/.config/graftcp/graftcp.conf$HOME/.config/mgraftcp/mgraftcp.conf/etc/graftcp/graftcp.conf/etc/mgraftcp/mgraftcp.confAn example config is available in example-graftcp.conf. example-mgraftcp.conf is kept for compatibility with the alias name.
Config keys mirror the CLI flags: dns_proxy = true -> --enable-dns, udp_proxy = true -> --enable-udp, and ignore_local = false -> --not-ignore-local. CLI flags override config values.
graftcp starts an embedded local TCP listener and then traces the target command with ptrace(2).connect(2), it records the original destination in an in-process route table and allocates a unique loopback token IP from 127.0.0.0/8.LocalAddr(), resolves the original destination from the route table, then dials the configured SOCKS5/HTTP/direct path.graftcp restores the tracee's original sockaddr buffer on a best-effort basis.For IPv6 connect(2), graftcp rewrites to an IPv4-mapped loopback address (::ffff:127.x.y.z) so the same token registry can be reused.
When DNS proxying is enabled, graftcp also starts an embedded UDP DNS listener. UDP connect() and sendto() calls to port 53 are rewritten to that listener, and each DNS payload is forwarded to the configured upstream DNS server over TCP through the same proxy selection path. DNS sockaddr rewrites are intentionally left in place after the syscall returns so resolvers such as musl, which validate replies against the send-side sockaddr buffer, continue to see the embedded DNS proxy as the expected peer.
When generic UDP proxying is enabled, graftcp starts a separate UDP listener. UDP connect(), sendto(), and sendmsg() targets are rewritten to loopback token endpoints; the embedded listener maps each token back to the original destination and forwards packets through SOCKS5 UDP ASSOCIATE when SOCKS5 is selected, or direct UDP in direct mode and fallback cases.
ptrace(2) permissions still apply. If tracing is blocked, check Yama ptrace_scope, capabilities, or run as root when appropriate.graftcp installs a seccomp-BPF filter so only socket-related syscalls trap into the tracer, reducing tracing overhead; on kernels without seccomp filtering it falls back to stopping on every syscall. This is automatic and needs no configuration.--not-ignore-local to proxy loopback/private-local connects as well.--enable-dns to enable the UDP/53 DNS-over-TCP path, and --dns-server to choose the upstream server.--enable-udp to enable it.auto prefers SOCKS5 UDP when available and falls back to direct UDP if the SOCKS5 UDP association fails; only_http_proxy rejects generic UDP sessions.127.0.0.1:1080) or a Unix socket endpoint (unix:/path/tor.sock or /path/tor.sock) for TCP CONNECT traffic. SOCKS5 UDP ASSOCIATE still requires a TCP SOCKS5 endpoint.--socks5 nor --http_proxy is configured, graftcp falls back to SOCKS5 127.0.0.1:1080. Configuring any proxy disables this implicit default, so --http_proxy alone never dials a SOCKS5 endpoint.connect() / sendto() / sendmsg() returns on a best-effort basis; DNS proxy rewrites are kept in place for resolver source-address checks. Clients that require recvfrom() to report the original remote address may still not be fully transparent.IPV6_V6ONLY=1 are out of scope by design.dup* and fcntl(F_DUPFD*) are copied best-effort, but close_range(), unshare(CLONE_FILES), and full shared fd-table semantics are intentionally not modeled.graftcp redirect a connection whose target is localhost?Not by default. Local destinations are ignored. Use --not-ignore-local to redirect them too. To ignore more addresses, add them to a black-ip file (-b); to redirect only certain addresses, add them to a white-ip file (-w). See graftcp --help.
graftcp handle DNS?It can. Use --enable-dns to forward UDP/53 queries as DNS-over-TCP through the selected proxy, and --dns-server to choose the upstream resolver. Tools like dnscrypt-proxy remain good complements.
graftcp yay or graftcp sudo ... errors out and exits. How to fix?yay on Arch Linux actually invokes sudo pacman ..., which requires the tracer to have privileges to trace the child process. Start graftcp with sudo and drop back to your user for the command: sudo graftcp sudo -u $USER yay, or sudo graftcp -u $USER sudo ....
If that is too verbose, give a copy of the binary the needed capabilities:
cp local/graftcp sumg
sudo setcap 'cap_sys_ptrace,cap_sys_admin+ep' ./sumg
# ./sumg yay
# ./sumg sudo ...
clone(2) has a CLONE_UNTRACED flag to avoid being traced. How does graftcp force tracing?graftcp intercepts clone(2) and clears CLONE_UNTRACED, so spawned children cannot escape tracing. That flag is meant for the kernel; user space should not set it. Linux can also restrict ptrace(2) via /proc/sys/kernel/yama/ptrace_scope. If tracing fails, check this value.
No. macOS's ptrace(2) is not capable enough for this. See issue 12.
getpeername() / getsockname() so traced programs observe the original remote addresssendmmsg() for generic UDP proxyingCopyright © 2016, 2018-2026 Hmgle <dustgle@gmail.com>
Released under the terms of the GNU General Public License, version 3.
789 followers · starred Dec 2021
854 followers · starred Apr 2019
3,989 followers · starred Aug 2025
141 followers · starred Apr 2021