mirror of
https://github.com/xmdhs/natupnp.git
synced 2025-10-01 21:32:10 +08:00
160 lines
3.4 KiB
Go
160 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/xmdhs/natupnp/natmap"
|
|
"github.com/xmdhs/natupnp/reuse"
|
|
)
|
|
|
|
var (
|
|
stun string
|
|
localAddr string
|
|
port string
|
|
test bool
|
|
target string
|
|
comm string
|
|
udp bool
|
|
)
|
|
|
|
func init() {
|
|
flag.StringVar(&stun, "s", "turn.cloudflare.com:3478", "stun")
|
|
flag.StringVar(&localAddr, "l", "", "local addr")
|
|
flag.StringVar(&port, "p", "8086", "port")
|
|
flag.StringVar(&target, "d", "", "forward to target host")
|
|
flag.BoolVar(&test, "t", false, "test server (only tcp)")
|
|
flag.StringVar(&comm, "e", "", "run script for mapped address")
|
|
flag.BoolVar(&udp, "u", false, "udp")
|
|
flag.Parse()
|
|
}
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
if localAddr == "" {
|
|
s, err := natmap.GetLocalAddr()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
h, _, err := net.SplitHostPort(s.String())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
localAddr = h
|
|
}
|
|
portu, err := strconv.ParseUint(port, 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
laddrPort := netip.AddrPortFrom(netip.MustParseAddr(localAddr), uint16(portu))
|
|
|
|
for {
|
|
err := openPort(ctx, target, laddrPort, stun, func(s netip.AddrPort) {
|
|
fmt.Println(s)
|
|
if comm != "" {
|
|
c := exec.CommandContext(ctx, comm, localAddr, port, s.Addr().String(), strconv.Itoa(int(s.Port())))
|
|
c.Stdin = os.Stdin
|
|
c.Stdout = os.Stdout
|
|
c.Stderr = os.Stderr
|
|
err = c.Run()
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
}, udp, test)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func openPort(ctx context.Context, target string, laddr netip.AddrPort,
|
|
stun string, finish func(netip.AddrPort), udp bool, testserver bool) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
if target != "" {
|
|
var forward func(ctx context.Context, laddr netip.AddrPort, target string, log func(string)) (io.Closer, error)
|
|
if udp {
|
|
forward = natmap.ForwardUdp
|
|
} else {
|
|
forward = natmap.Forward
|
|
}
|
|
l, err := forward(ctx, laddr, target, func(s string) {
|
|
log.Println(s)
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("openPort: %w", err)
|
|
}
|
|
defer l.Close()
|
|
}
|
|
if testserver {
|
|
l, err := testServer(ctx, laddr)
|
|
if err != nil {
|
|
return fmt.Errorf("openPort: %w", err)
|
|
}
|
|
defer l.Close()
|
|
}
|
|
errCh := make(chan error, 1)
|
|
var nmap func(ctx context.Context, stunAddr string, laddr netip.AddrPort, log func(error)) (*natmap.Map, netip.AddrPort, error)
|
|
if udp {
|
|
nmap = natmap.NatMapUdp
|
|
} else {
|
|
nmap = natmap.NatMap
|
|
}
|
|
|
|
m, s, err := nmap(ctx, stun, laddr, func(s error) {
|
|
cancel()
|
|
select {
|
|
case errCh <- s:
|
|
default:
|
|
}
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("openPort: %w", err)
|
|
}
|
|
defer m.Close()
|
|
|
|
finish(s)
|
|
|
|
err = <-errCh
|
|
if err != nil {
|
|
return fmt.Errorf("openPort: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func testServer(ctx context.Context, laddr netip.AddrPort) (net.Listener, error) {
|
|
s := http.Server{
|
|
ReadTimeout: 5 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
Addr: laddr.String(),
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("ok"))
|
|
}),
|
|
}
|
|
l, err := reuse.Listen(ctx, "tcp", laddr.String())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("testServer: %w", err)
|
|
}
|
|
go func() {
|
|
err = s.Serve(l)
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
log.Println(err)
|
|
}
|
|
}()
|
|
return l, nil
|
|
}
|