Skip to content
Merged
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ sudo ./gotproxy [flags]
| :--- | :--- |
| **--cmd** | The command name to be proxied. If not provided, all traffic will be proxied globally. |
| **--pids** | The pid to be proxied, seperate by ','. |
| **--container-name** | The container name to be proxied (Docker running container name). |
| **--ip** | The Target IP address to be proxied. Supports IPv4 and IPv4 CIDR notation.|
| **--p-pid** | The process ID of the proxy. If not provided, the program will automatically start a forwarding proxy. |
| **--p-port** | The proxy port. |
Expand Down Expand Up @@ -75,6 +76,17 @@ sudo ./gotproxy --proto tcp
sudo ./gotproxy --proto udp
```

5. Proxy by container name:
```bash
sudo ./gotproxy --container-name test-kyanos
```

6. Use container and pid together:
```bash
sudo ./gotproxy --container-name test-kyanos --pids 1234
```
When multiple process/container filters are specified (such as `--container-name`, `--cmd`, `--pids`), they use OR semantics: matching any one filter will be proxied.


## Known Limitations ##
* Theoretically, a connection should be determined by a 5-tuple, but for most cases, connection mapping is currently based only on protocol type and source port.
Expand Down
12 changes: 12 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ sudo ./gotproxy [flags]
| :--- | :--- |
| **--cmd** | 需要代理的进程名称. 如果没有配置,则会进行全局流量代理. |
| **--pids** | 需要代理的进程id, 按照逗号进行分割. |
| **--container-name** | 需要代理的容器名称(Docker 运行中的容器名)。 |
| **--ip** | 需要代理的目标ip. 支持ipv4和ipv4 CIDR.|
| **--p-pid** | 代理程序的进程id. 会自动过滤不代理该进程的网络通信,以免网络循环。如果没有配置, 本程序会自动启动一个转发代理服务. |
| **--p-port** | 代理服务监听的端口。 |
Expand Down Expand Up @@ -80,6 +81,17 @@ sudo ./gotproxy --proto tcp
sudo ./gotproxy --proto udp
```

5. 按容器名称代理:
```bash
sudo ./gotproxy --container-name test-kyanos
```

6. 容器名 + pid 同时过滤:
```bash
sudo ./gotproxy --container-name test-kyanos --pids 1234
```
当同时配置多个进程/容器过滤条件(如 `--container-name`、`--cmd`、`--pids`)时,使用 OR 关系:命中任意一个条件就会被代理。

## 已知限制:
* 理论上应该根据5元组确定一个连接,但是考虑大多数情况目前只根据协议类型和源端口进行连接映射。
* 在根据进程名称进行代理的场景中,如果进程启动了子进程并使用了execve执行一个新命令,会无法进行代理。
Expand Down
13 changes: 8 additions & 5 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
proxyPort uint16
proxyPid uint64
pids []string
containerName string
ipStr string
socks5ProxyAddr string
socks5User string
Expand Down Expand Up @@ -43,11 +44,12 @@ var rootCmd = &cobra.Command{
}

Options := &Options{
Command: command,
ProxyPid: proxyPid,
ProxyPort: proxyPort,
EnableTCP: enableTCP,
EnableUDP: enableUDP,
Command: command,
ProxyPid: proxyPid,
ProxyPort: proxyPort,
ContainerName: containerName,
EnableTCP: enableTCP,
EnableUDP: enableUDP,
}

if ok, err := common.HasPermission(); err != nil {
Expand Down Expand Up @@ -93,6 +95,7 @@ func init() {
rootCmd.PersistentFlags().Uint16Var(&proxyPort, "p-port", 18000, "The proxy port")
rootCmd.PersistentFlags().Uint64Var(&proxyPid, "p-pid", 0, "The process ID of the proxy. If not provided, the program will automatically start a forwarding proxy.")
rootCmd.PersistentFlags().StringSliceVar(&pids, "pids", []string{}, "The pid to be proxied, seperate by ','")
rootCmd.PersistentFlags().StringVar(&containerName, "container-name", "", "The container name to be proxied")
rootCmd.PersistentFlags().StringVar(&ipStr, "ip", "", "The ip to be proxied,only support ipv4")
rootCmd.PersistentFlags().StringVar(&socks5ProxyAddr, "socks5", "", "The socks5 proxyAddr.")
rootCmd.PersistentFlags().StringVar(&socks5User, "socks5-user", "", "The SOCKS5 username. Requires --socks5-pass.")
Expand Down
90 changes: 73 additions & 17 deletions cmd/loadBpf.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import (
"context"
"encoding/binary"
"gotproxy/common"
"log"
"os"

Expand All @@ -25,14 +28,15 @@
Pad [8]byte
}
type Options struct {
ProxyPort uint16 // Port where the proxy server listens
ProxyPid uint64 // PID of the proxy server
Command string
Pids []uint64
Ip4 uint32
Ip4Mask uint8
EnableTCP bool
EnableUDP bool
ProxyPort uint16 // Port where the proxy server listens
ProxyPid uint64 // PID of the proxy server
Command string
Pids []uint64
ContainerName string
Ip4 uint32
Ip4Mask uint8
EnableTCP bool
EnableUDP bool
}

func LoadBpf(options *Options) {
Expand All @@ -59,9 +63,29 @@
}
log.Printf("Proxy protocol enabled: %s (tcp=%v udp=%v)", mode, options.EnableTCP, options.EnableUDP)

proxyListenHost := "127.0.0.1"
proxyRedirectIP := uint32(0x7f000001)
if options.ContainerName != "" {
proxyListenHost = "0.0.0.0"
gatewayIP, err := common.ResolveDockerBridgeGatewayIPv4(context.Background())
if err != nil {
// Fallback to Docker default bridge gateway for compatibility with typical setups.
log.Printf("Resolve Docker bridge gateway failed: %v, fallback to 172.17.0.1", err)
proxyRedirectIP = 0xac110001
} else {
proxyRedirectIP = binary.BigEndian.Uint32(gatewayIP.To4())
}
log.Printf("Container mode enabled: proxy listen=%s, redirect gateway=%d.%d.%d.%d",
proxyListenHost,
byte(proxyRedirectIP>>24),
byte(proxyRedirectIP>>16),
byte(proxyRedirectIP>>8),
byte(proxyRedirectIP))
}

// Start TCP (and UDP) proxy so it can use objs.MapUdpDest for UDP original-dest lookup
if options.ProxyPid == 0 {
StartProxy(objs.MapUdpDest, options.EnableTCP, options.EnableUDP)
StartProxy(objs.MapUdpDest, options.EnableTCP, options.EnableUDP, proxyListenHost)
}

// Attach eBPF programs to the root cgroup
Expand Down Expand Up @@ -109,14 +133,16 @@
pid = options.ProxyPid
}
config := proxyConfig{
ProxyPort: options.ProxyPort,
ProxyPid: pid,
FilterByPid: len(options.Pids) > 0,
FilterByPgid: len(options.Pids) > 0,
FilterIp: options.Ip4,
FilterIpMask: options.Ip4Mask,
EnableTcp: options.EnableTCP,
EnableUdp: options.EnableUDP,
ProxyPort: options.ProxyPort,
ProxyPid: pid,
ProxyIp: proxyRedirectIP,
FilterByPid: len(options.Pids) > 0,
FilterByPgid: len(options.Pids) > 0,
FilterByContainer: options.ContainerName != "",
FilterIp: options.Ip4,
FilterIpMask: options.Ip4Mask,
EnableTcp: options.EnableTCP,
EnableUdp: options.EnableUDP,
}
stringToInt8Array(config.Command[:], options.Command)
err = objs.proxyMaps.MapConfig.Update(&key, &config, ebpf.UpdateAny)
Expand All @@ -129,6 +155,36 @@
log.Fatalf("Failed to update FilterPidMap: %v", err)
}
}
if options.ContainerName != "" {
if err := common.EnsureKernelAtLeast(common.KernelVersion{Major: 4, Minor: 14, Patch: 0}, "container-name filter"); err != nil {

Check failure on line 159 in cmd/loadBpf.go

View workflow job for this annotation

GitHub Actions / build (amd64)

undefined: common.KernelVersion

Check failure on line 159 in cmd/loadBpf.go

View workflow job for this annotation

GitHub Actions / build (amd64)

undefined: common.EnsureKernelAtLeast

Check failure on line 159 in cmd/loadBpf.go

View workflow job for this annotation

GitHub Actions / build (arm64)

undefined: common.KernelVersion

Check failure on line 159 in cmd/loadBpf.go

View workflow job for this annotation

GitHub Actions / build (arm64)

undefined: common.EnsureKernelAtLeast
log.Fatal(err)
}

containerNS, err := common.ResolveContainerNamespacesByName(context.Background(), options.ContainerName)
if err != nil {
log.Fatalf("Failed to resolve container namespaces: %v", err)
}
if containerNS.PidNS == 0 && containerNS.MntNS == 0 && containerNS.NetNS == 0 {
log.Fatalf("Container %q does not expose usable namespaces for filtering", options.ContainerName)
}
log.Printf("Container %q namespaces: pid=%d mnt=%d net=%d", options.ContainerName, containerNS.PidNS, containerNS.MntNS, containerNS.NetNS)

if containerNS.PidNS != 0 {
if err := objs.FilterPidnsMap.Update(containerNS.PidNS, int8(1), ebpf.UpdateAny); err != nil {
log.Fatalf("Failed to update FilterPidnsMap: %v", err)
}
}
if containerNS.MntNS != 0 {
if err := objs.FilterMntnsMap.Update(containerNS.MntNS, int8(1), ebpf.UpdateAny); err != nil {
log.Fatalf("Failed to update FilterMntnsMap: %v", err)
}
}
if containerNS.NetNS != 0 {
if err := objs.FilterNetnsMap.Update(containerNS.NetNS, int8(1), ebpf.UpdateAny); err != nil {
log.Fatalf("Failed to update FilterNetnsMap: %v", err)
}
}
}

select {}
}
Expand Down
Loading
Loading