mirror of
https://github.com/fosrl/newt.git
synced 2026-03-27 04:56:41 +00:00
Compare commits
33 Commits
1.0.0-beta
...
1.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ff8df9a8d | ||
|
|
9d80161ab7 | ||
|
|
f4e17a4dd7 | ||
|
|
ab544fc9ed | ||
|
|
623be5ea0d | ||
|
|
72d264d427 | ||
|
|
a19fc8c588 | ||
|
|
dbc2a92456 | ||
|
|
437d8b67a4 | ||
|
|
6f1d4752f0 | ||
|
|
683312c78e | ||
|
|
29543aece3 | ||
|
|
e68a38e929 | ||
|
|
bc72c96b5e | ||
|
|
3d15ecb732 | ||
|
|
a69618310b | ||
|
|
ed8a2ccd23 | ||
|
|
e8141a177b | ||
|
|
b23eda9c06 | ||
|
|
92bc883b5b | ||
|
|
76503f3f2c | ||
|
|
9c3112f9bd | ||
|
|
462af30d16 | ||
|
|
fa6038eb38 | ||
|
|
f346b6cc5d | ||
|
|
f20b9ebb14 | ||
|
|
39bfe5b230 | ||
|
|
a1a3dd9ba2 | ||
|
|
7b1492f327 | ||
|
|
4e50819785 | ||
|
|
f8dccbec80 | ||
|
|
0c5c59cf00 | ||
|
|
868bb55f87 |
61
.github/workflows/cicd.yml
vendored
Normal file
61
.github/workflows/cicd.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: CI/CD Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Build and Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract tag name
|
||||||
|
id: get-tag
|
||||||
|
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: 1.23.1
|
||||||
|
|
||||||
|
- name: Update version in main.go
|
||||||
|
run: |
|
||||||
|
TAG=${{ env.TAG }}
|
||||||
|
if [ -f main.go ]; then
|
||||||
|
sed -i 's/Newt version replaceme/Newt version '"$TAG"'/' main.go
|
||||||
|
echo "Updated main.go with version $TAG"
|
||||||
|
else
|
||||||
|
echo "main.go not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push Docker images
|
||||||
|
run: |
|
||||||
|
TAG=${{ env.TAG }}
|
||||||
|
make docker-build-release tag=$TAG
|
||||||
|
|
||||||
|
- name: Build binaries
|
||||||
|
run: |
|
||||||
|
make go-build-release
|
||||||
|
|
||||||
|
- name: Upload artifacts from /bin
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binaries
|
||||||
|
path: bin/
|
||||||
1
.go-version
Normal file
1
.go-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1.23.2
|
||||||
10
Dockerfile
10
Dockerfile
@@ -15,19 +15,13 @@ COPY . .
|
|||||||
# Build the application
|
# Build the application
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o /newt
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /newt
|
||||||
|
|
||||||
# Start a new stage from scratch
|
FROM alpine:3.19 AS runner
|
||||||
FROM ubuntu:22.04 AS runner
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install ca-certificates -y && rm -rf /var/lib/apt/lists/*
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
# Copy the pre-built binary file from the previous stage and the entrypoint script
|
|
||||||
COPY --from=builder /newt /usr/local/bin/
|
COPY --from=builder /newt /usr/local/bin/
|
||||||
COPY entrypoint.sh /
|
COPY entrypoint.sh /
|
||||||
|
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
# Copy the entrypoint script
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|
||||||
# Command to run the executable
|
|
||||||
CMD ["newt"]
|
CMD ["newt"]
|
||||||
15
Makefile
15
Makefile
@@ -1,6 +1,14 @@
|
|||||||
|
|
||||||
all: build push
|
all: build push
|
||||||
|
|
||||||
|
docker-build-release:
|
||||||
|
@if [ -z "$(tag)" ]; then \
|
||||||
|
echo "Error: tag is required. Usage: make build-all tag=<tag>"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
docker buildx build --platform linux/arm/v7,linux/arm64,linux/amd64 -t fosrl/newt:latest -f Dockerfile --push .
|
||||||
|
docker buildx build --platform linux/arm/v7,linux/arm64,linux/amd64 -t fosrl/newt:$(tag) -f Dockerfile --push .
|
||||||
|
|
||||||
build:
|
build:
|
||||||
docker build -t fosrl/newt:latest .
|
docker build -t fosrl/newt:latest .
|
||||||
|
|
||||||
@@ -13,12 +21,15 @@ test:
|
|||||||
local:
|
local:
|
||||||
CGO_ENABLED=0 go build -o newt
|
CGO_ENABLED=0 go build -o newt
|
||||||
|
|
||||||
release:
|
go-build-release:
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/newt_linux_arm64
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/newt_linux_arm64
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o bin/newt_linux_arm32
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -o bin/newt_linux_arm32v6
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/newt_linux_amd64
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/newt_linux_amd64
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -o bin/newt_linux_riscv64
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o bin/newt_darwin_arm64
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o bin/newt_darwin_arm64
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/newt_darwin_amd64
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/newt_darwin_amd64
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o newt_windows_amd64.bin/exe
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o bin/newt_windows_amd64.exe
|
||||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o bin/newt_freebsd_amd64
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o bin/newt_freebsd_amd64
|
||||||
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -o bin/newt_freebsd_arm64
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -o bin/newt_freebsd_arm64
|
||||||
|
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -36,6 +36,7 @@ When Newt receives WireGuard control messages, it will use the information encod
|
|||||||
- `secret`: A unique secret (not shared and kept private) used to authenticate the client ID with the websocket in order to receive commands.
|
- `secret`: A unique secret (not shared and kept private) used to authenticate the client ID with the websocket in order to receive commands.
|
||||||
- `dns`: DNS server to use to resolve the endpoint
|
- `dns`: DNS server to use to resolve the endpoint
|
||||||
- `log-level` (optional): The log level to use. Default: INFO
|
- `log-level` (optional): The log level to use. Default: INFO
|
||||||
|
- `updown` (optional): A script to be called when targets are added or removed.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -74,6 +75,38 @@ services:
|
|||||||
- --endpoint https://example.com
|
- --endpoint https://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally a basic systemd service:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=Newt VPN Client
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/newt --id 31frd0uzbjvp721 --secret h51mmlknrvrwv8s4r1i210azhumt6isgbpyavxodibx1k2d6 --endpoint https://example.com
|
||||||
|
Restart=always
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to `mv ./newt /usr/local/bin/newt`!
|
||||||
|
|
||||||
|
### Updown
|
||||||
|
|
||||||
|
You can pass in a updown script for Newt to call when it is adding or removing a target:
|
||||||
|
|
||||||
|
`--updown "python3 test.py"`
|
||||||
|
|
||||||
|
It will get called with args when a target is added:
|
||||||
|
`python3 test.py add tcp localhost:8556`
|
||||||
|
`python3 test.py remove tcp localhost:8556`
|
||||||
|
|
||||||
|
Returning a string from the script in the format of a target (`ip:dst` so `10.0.0.1:8080`) it will override the target and use this value instead to proxy.
|
||||||
|
|
||||||
|
You can look at updown.py as a reference script to get started!
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
### Container
|
### Container
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -4,17 +4,19 @@ go 1.23.1
|
|||||||
|
|
||||||
toolchain go1.23.2
|
toolchain go1.23.2
|
||||||
|
|
||||||
require golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
require (
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
golang.org/x/net v0.30.0
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
golang.org/x/crypto v0.28.0 // indirect
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
|
||||||
golang.org/x/net v0.30.0 // indirect
|
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect
|
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,11 +1,11 @@
|
|||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
|
|||||||
95
main.go
95
main.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -124,7 +125,7 @@ func startPingCheck(tnet *netstack.Net, serverIP string, stopChan chan struct{})
|
|||||||
err := ping(tnet, serverIP)
|
err := ping(tnet, serverIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Periodic ping failed: %v", err)
|
logger.Warn("Periodic ping failed: %v", err)
|
||||||
logger.Warn("HINT: Do you have UDP port 51280 (or the port in config.yml) open on your Pangolin server?")
|
logger.Warn("HINT: Do you have UDP port 51820 (or the port in config.yml) open on your Pangolin server?")
|
||||||
}
|
}
|
||||||
case <-stopChan:
|
case <-stopChan:
|
||||||
logger.Info("Stopping ping check")
|
logger.Info("Stopping ping check")
|
||||||
@@ -136,7 +137,7 @@ func startPingCheck(tnet *netstack.Net, serverIP string, stopChan chan struct{})
|
|||||||
|
|
||||||
func pingWithRetry(tnet *netstack.Net, dst string) error {
|
func pingWithRetry(tnet *netstack.Net, dst string) error {
|
||||||
const (
|
const (
|
||||||
maxAttempts = 5
|
maxAttempts = 15
|
||||||
retryDelay = 2 * time.Second
|
retryDelay = 2 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -244,7 +245,6 @@ func resolveDomain(domain string) (string, error) {
|
|||||||
return ipAddr, nil
|
return ipAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
var (
|
||||||
endpoint string
|
endpoint string
|
||||||
id string
|
id string
|
||||||
@@ -255,8 +255,10 @@ func main() {
|
|||||||
privateKey wgtypes.Key
|
privateKey wgtypes.Key
|
||||||
err error
|
err error
|
||||||
logLevel string
|
logLevel string
|
||||||
|
updownScript string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
// if PANGOLIN_ENDPOINT, NEWT_ID, and NEWT_SECRET are set as environment variables, they will be used as default values
|
// if PANGOLIN_ENDPOINT, NEWT_ID, and NEWT_SECRET are set as environment variables, they will be used as default values
|
||||||
endpoint = os.Getenv("PANGOLIN_ENDPOINT")
|
endpoint = os.Getenv("PANGOLIN_ENDPOINT")
|
||||||
id = os.Getenv("NEWT_ID")
|
id = os.Getenv("NEWT_ID")
|
||||||
@@ -264,6 +266,7 @@ func main() {
|
|||||||
mtu = os.Getenv("MTU")
|
mtu = os.Getenv("MTU")
|
||||||
dns = os.Getenv("DNS")
|
dns = os.Getenv("DNS")
|
||||||
logLevel = os.Getenv("LOG_LEVEL")
|
logLevel = os.Getenv("LOG_LEVEL")
|
||||||
|
updownScript = os.Getenv("UPDOWN_SCRIPT")
|
||||||
|
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server")
|
flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server")
|
||||||
@@ -283,17 +286,24 @@ func main() {
|
|||||||
if logLevel == "" {
|
if logLevel == "" {
|
||||||
flag.StringVar(&logLevel, "log-level", "INFO", "Log level (DEBUG, INFO, WARN, ERROR, FATAL)")
|
flag.StringVar(&logLevel, "log-level", "INFO", "Log level (DEBUG, INFO, WARN, ERROR, FATAL)")
|
||||||
}
|
}
|
||||||
|
if updownScript == "" {
|
||||||
|
flag.StringVar(&updownScript, "updown", "", "Path to updown script to be called when targets are added or removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// do a --version check
|
||||||
|
version := flag.Bool("version", false, "Print the version")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if *version {
|
||||||
|
fmt.Println("Newt version replaceme")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
logger.Init()
|
logger.Init()
|
||||||
loggerLevel := parseLogLevel(logLevel)
|
loggerLevel := parseLogLevel(logLevel)
|
||||||
logger.GetLogger().SetLevel(parseLogLevel(logLevel))
|
logger.GetLogger().SetLevel(parseLogLevel(logLevel))
|
||||||
|
|
||||||
// Validate required fields
|
|
||||||
if endpoint == "" || id == "" || secret == "" {
|
|
||||||
logger.Fatal("endpoint, id, and secret are required either via CLI flags or environment variables")
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the mtu string into an int
|
// parse the mtu string into an int
|
||||||
mtuInt, err = strconv.Atoi(mtu)
|
mtuInt, err = strconv.Atoi(mtu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -348,7 +358,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Handle complete failure after all retries
|
// Handle complete failure after all retries
|
||||||
logger.Warn("Failed to ping %s: %v", wgData.ServerIP, err)
|
logger.Warn("Failed to ping %s: %v", wgData.ServerIP, err)
|
||||||
logger.Warn("HINT: Do you have UDP port 51280 (or the port in config.yml) open on your Pangolin server?")
|
logger.Warn("HINT: Do you have UDP port 51820 (or the port in config.yml) open on your Pangolin server?")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -582,6 +592,18 @@ func updateTargets(pm *proxy.ProxyManager, action string, tunnelIP string, proto
|
|||||||
|
|
||||||
if action == "add" {
|
if action == "add" {
|
||||||
target := parts[1] + ":" + parts[2]
|
target := parts[1] + ":" + parts[2]
|
||||||
|
|
||||||
|
// Call updown script if provided
|
||||||
|
processedTarget := target
|
||||||
|
if updownScript != "" {
|
||||||
|
newTarget, err := executeUpdownScript(action, proto, target)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Updown script error: %v", err)
|
||||||
|
} else if newTarget != "" {
|
||||||
|
processedTarget = newTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only remove the specific target if it exists
|
// Only remove the specific target if it exists
|
||||||
err := pm.RemoveTarget(proto, tunnelIP, port)
|
err := pm.RemoveTarget(proto, tunnelIP, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -592,10 +614,21 @@ func updateTargets(pm *proxy.ProxyManager, action string, tunnelIP string, proto
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the new target
|
// Add the new target
|
||||||
pm.AddTarget(proto, tunnelIP, port, target)
|
pm.AddTarget(proto, tunnelIP, port, processedTarget)
|
||||||
|
|
||||||
} else if action == "remove" {
|
} else if action == "remove" {
|
||||||
logger.Info("Removing target with port %d", port)
|
logger.Info("Removing target with port %d", port)
|
||||||
|
|
||||||
|
target := parts[1] + ":" + parts[2]
|
||||||
|
|
||||||
|
// Call updown script if provided
|
||||||
|
if updownScript != "" {
|
||||||
|
_, err := executeUpdownScript(action, proto, target)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Updown script error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := pm.RemoveTarget(proto, tunnelIP, port)
|
err := pm.RemoveTarget(proto, tunnelIP, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to remove target: %v", err)
|
logger.Error("Failed to remove target: %v", err)
|
||||||
@@ -606,3 +639,45 @@ func updateTargets(pm *proxy.ProxyManager, action string, tunnelIP string, proto
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executeUpdownScript(action, proto, target string) (string, error) {
|
||||||
|
if updownScript == "" {
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the updownScript in case it contains spaces (like "/usr/bin/python3 script.py")
|
||||||
|
parts := strings.Fields(updownScript)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return target, fmt.Errorf("invalid updown script command")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if len(parts) == 1 {
|
||||||
|
// If it's a single executable
|
||||||
|
logger.Info("Executing updown script: %s %s %s %s", updownScript, action, proto, target)
|
||||||
|
cmd = exec.Command(parts[0], action, proto, target)
|
||||||
|
} else {
|
||||||
|
// If it includes interpreter and script
|
||||||
|
args := append(parts[1:], action, proto, target)
|
||||||
|
logger.Info("Executing updown script: %s %s %s %s %s", parts[0], strings.Join(parts[1:], " "), action, proto, target)
|
||||||
|
cmd = exec.Command(parts[0], args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
return "", fmt.Errorf("updown script execution failed (exit code %d): %s",
|
||||||
|
exitErr.ExitCode(), string(exitErr.Stderr))
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("updown script execution failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the script returns a new target, use it
|
||||||
|
newTarget := strings.TrimSpace(string(output))
|
||||||
|
if newTarget != "" {
|
||||||
|
logger.Info("Updown script returned new target: %s", newTarget)
|
||||||
|
return newTarget, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ func NewProxyManager(tnet *netstack.Net) *ProxyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTarget adds a new target for proxying
|
// AddTarget adds as new target for proxying
|
||||||
func (pm *ProxyManager) AddTarget(proto, listenIP string, port int, targetAddr string) error {
|
func (pm *ProxyManager) AddTarget(proto, listenIP string, port int, targetAddr string) error {
|
||||||
pm.mutex.Lock()
|
pm.mutex.Lock()
|
||||||
defer pm.mutex.Unlock()
|
defer pm.mutex.Unlock()
|
||||||
@@ -63,7 +64,7 @@ func (pm *ProxyManager) AddTarget(proto, listenIP string, port int, targetAddr s
|
|||||||
if pm.running {
|
if pm.running {
|
||||||
return pm.startTarget(proto, listenIP, port, targetAddr)
|
return pm.startTarget(proto, listenIP, port, targetAddr)
|
||||||
} else {
|
} else {
|
||||||
logger.Info("Not adding target because not running")
|
logger.Debug("Not adding target because not running")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -279,6 +280,22 @@ func (pm *ProxyManager) handleUDPProxy(conn *gonet.UDPConn, targetAddr string) {
|
|||||||
if !pm.running {
|
if !pm.running {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for connection closed conditions
|
||||||
|
if err == io.EOF || strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
logger.Info("UDP connection closed, stopping proxy handler")
|
||||||
|
|
||||||
|
// Clean up existing client connections
|
||||||
|
clientsMutex.Lock()
|
||||||
|
for _, targetConn := range clientConns {
|
||||||
|
targetConn.Close()
|
||||||
|
}
|
||||||
|
clientConns = nil
|
||||||
|
clientsMutex.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
logger.Error("Error reading UDP packet: %v", err)
|
logger.Error("Error reading UDP packet: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
77
updown.py
Normal file
77
updown.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
Sample updown script for Newt proxy
|
||||||
|
Usage: update.py <action> <protocol> <target>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- action: 'add' or 'remove'
|
||||||
|
- protocol: 'tcp' or 'udp'
|
||||||
|
- target: the target address in format 'host:port'
|
||||||
|
|
||||||
|
If the action is 'add', the script can return a modified target that
|
||||||
|
will be used instead of the original.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
LOG_FILE = "/tmp/newt-updown.log"
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=LOG_FILE,
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_event(action, protocol, target):
|
||||||
|
"""Log each event to a file for auditing purposes"""
|
||||||
|
timestamp = datetime.now().isoformat()
|
||||||
|
event = {
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"action": action,
|
||||||
|
"protocol": protocol,
|
||||||
|
"target": target
|
||||||
|
}
|
||||||
|
logging.info(json.dumps(event))
|
||||||
|
|
||||||
|
def handle_add(protocol, target):
|
||||||
|
"""Handle 'add' action"""
|
||||||
|
logging.info(f"Adding {protocol} target: {target}")
|
||||||
|
|
||||||
|
def handle_remove(protocol, target):
|
||||||
|
"""Handle 'remove' action"""
|
||||||
|
logging.info(f"Removing {protocol} target: {target}")
|
||||||
|
# For remove action, no return value is expected or used
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Check arguments
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
logging.error(f"Invalid arguments: {sys.argv}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
action = sys.argv[1]
|
||||||
|
protocol = sys.argv[2]
|
||||||
|
target = sys.argv[3]
|
||||||
|
|
||||||
|
# Log the event
|
||||||
|
log_event(action, protocol, target)
|
||||||
|
|
||||||
|
# Handle the action
|
||||||
|
if action == "add":
|
||||||
|
new_target = handle_add(protocol, target)
|
||||||
|
# Print the new target to stdout (if empty, no change will be made)
|
||||||
|
if new_target and new_target != target:
|
||||||
|
print(new_target)
|
||||||
|
elif action == "remove":
|
||||||
|
handle_remove(protocol, target)
|
||||||
|
else:
|
||||||
|
logging.error(f"Unknown action: {action}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Unhandled exception: {e}")
|
||||||
|
sys.exit(1)
|
||||||
@@ -228,6 +228,10 @@ func (c *Client) getToken() (string, error) {
|
|||||||
|
|
||||||
var tokenResp TokenResponse
|
var tokenResp TokenResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
||||||
|
// print out the token response for debugging
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(resp.Body)
|
||||||
|
logger.Info("Token response: %s", buf.String())
|
||||||
return "", fmt.Errorf("failed to decode token response: %w", err)
|
return "", fmt.Errorf("failed to decode token response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +309,10 @@ func (c *Client) establishConnection() error {
|
|||||||
go c.readPump()
|
go c.readPump()
|
||||||
|
|
||||||
if c.onConnect != nil {
|
if c.onConnect != nil {
|
||||||
|
err := c.saveConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to save config: %v", err)
|
||||||
|
}
|
||||||
if err := c.onConnect(); err != nil {
|
if err := c.onConnect(); err != nil {
|
||||||
logger.Error("OnConnect callback failed: %v", err)
|
logger.Error("OnConnect callback failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user