(摘) 端口转发及使用

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

代码很简单,这里的应用场景是可以通过端口转发,将目的IP指向动态IP上,从而实现直接访问通过电信等家庭网络中的非标准服务器。
如果外网IP较难获取到,那估计只能等IPV6了。
至于如何获取到目的IP(动态变化),只需要花百元购一个云服务器/云空间(它的IP是固定的)转发一下即可。
当然,现在的云服务器也不贵。这都是穷闹的。不过毕竟云服务器没家里的带宽和自由。
通过以下转发,即可浏览器输入localhost看到指定的内容,目的端口就任意了(家庭宽带是封禁了80端口的)。
如果通过修改本机hosts,是不是可以实现自定义域名访问?

package main

import (
	"errors"
	"fmt"
	"io"
	"net"
)

func main() {
	// 要监听的本地端口
	localPort := "80"
	// 要转发到的远程端口
	remoteHost := "192.168.1.40:9999"

	go func() {
		// 监听本地端口
		listener, err := net.Listen("tcp", ":"+localPort)
		if err != nil {
			fmt.Println("Error listening:", err)
			return
		}
		defer listener.Close()
		fmt.Println("Listening on :" + localPort)

		for {
			conn, err := listener.Accept()
			if err != nil {
				fmt.Println("Error accepting:", err)
				continue
			}

			// 处理连接
			go handleConnection(conn, remoteHost)
		}
	}()

	// 阻塞主线程
	select {}
}

func handleConnection(local net.Conn, remoteAddr string) {
	defer local.Close()
	remote, err := net.Dial("tcp", remoteAddr)
	if err != nil {
		fmt.Println("Error dialing remote:", err)
		return
	}
	defer remote.Close()

	// 转发数据
	go copyData(local, remote)
	copyData(remote, local)
}

func copyData(source, dest net.Conn) {
	defer source.Close()
	buf := make([]byte, 1024)
	for {
		n, err := source.Read(buf)
		if err != nil {
			if errors.Is(err, io.EOF) {
			} else {
				fmt.Println("Error copying data:", err)
				break
			}
		}
		dest.Write(buf[:n])
	}
}

解决动态外网IP的问题,而无需中转(云服务器),前文提到的百元也可以节约了。
通过ntfy.sh这个公共消息服务,发送和接收消息,可以中转IP地址信息。觉得不安全也可以自建服务。
顺便实现了消息显示,以及IP更换服务。
这样一来,家里的千兆宽带也至少有30MB的上行可作为峰值宽带使用。
服务器可以很简单的POST一个消息:curl -H “Title:ip” -d “18.112.8.11:88” https://ntfy.sh/SCCD001
编译并UPX后,程序4.13MB,占用内存11MB,可以说是非常小巧了。绿色跨平台自是不必说了。

package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

const DEVICECODE = "SCCD001"

var remoteHost string // 要转发到的远程端口
var localPort = "80"  // 要监听的本地端口
var ResetServer bool  // 重置服务

func main() {
	if len(os.Args) == 2 && os.Args[1] == "help" {
		fmt.Printf("%s 远端IP:端口 [本地端口]", os.Args[0])
		return
	}

	if len(os.Args) >= 2 {
		remoteHost = os.Args[1]
		if len(os.Args) == 3 {
			localPort = os.Args[2]
		}
	}

	go ListenServerIP()
	go func() {
		fmt.Print("等待获取服务器地址")
		for {
			if remoteHost != "" {
				PortServer()
				ResetServer = false
			} else {
				fmt.Print(".")
				time.Sleep(3 * time.Second)
			}
		}
	}()

	select {} // 阻塞主线程
}

// 转发服务
func PortServer() {
	listener, err := net.Listen("tcp", ":"+localPort) // 监听本地端口
	if err != nil {
		fmt.Println("\n监听本地服务出错:", err)
		return
	}
	defer listener.Close()
	fmt.Printf("\n服务监听中 %s -> %s\n", localPort, remoteHost)

	for {
		if !ResetServer { // 重置服务(用于IP变更)
			conn, err := listener.Accept()
			if err != nil {
				fmt.Println("Error accepting:", err)
				continue
			}
			go handleConnection(conn, remoteHost) // 处理连接
		} else {
			break
		}
	}
}

func handleConnection(local net.Conn, remoteAddr string) {
	defer local.Close()
	remote, err := net.Dial("tcp", remoteAddr)
	if err != nil {
		fmt.Println("Error dialing remote:", err)
		return
	}
	defer remote.Close()

	// 转发数据
	go copyData(local, remote)
	copyData(remote, local)
}

func copyData(source, dest net.Conn) {
	defer source.Close()
	buf := make([]byte, 1024)
	for {
		if !ResetServer {
			n, err := source.Read(buf)
			if err != nil {
				if errors.Is(err, io.EOF) {
				} else {
					fmt.Println("Error copying data:", err)
					break
				}
			}
			dest.Write(buf[:n])
		} else {
			break
		}
	}
}

// 监听ntfy.sh获取服务器IP及端口
// TODO: 通过加密增加安全
func ListenServerIP() {
	// 获取最后一次更新的IP
	resp, err := http.Get(fmt.Sprintf("https://ntfy.sh/ease_%s/json?poll=1&sched=1&title=ip", DEVICECODE))
	if err != nil {
		fmt.Println("发送http请求出错:", err)
	}
	defer resp.Body.Close()

	var keyVal map[string]interface{}
	body, _ := ioutil.ReadAll(resp.Body)
	allMessage := strings.Split(string(body), "\n")
	if len(allMessage) > 1 {
		latMessage := allMessage[len(allMessage)-2]
		json.Unmarshal([]byte(latMessage), &keyVal)
		remoteHost = keyVal["message"].(string)
	}

	// 循环监听消息
	resp, err = http.Get(fmt.Sprintf("https://ntfy.sh/ease_%s/json", DEVICECODE))
	if err != nil {
		fmt.Println("监听ntfy错误:", err)
	}
	defer resp.Body.Close()
	scanner := bufio.NewScanner(resp.Body)
	for scanner.Scan() {
		var keyVal map[string]interface{}
		info := scanner.Text()
		json.Unmarshal([]byte(info), &keyVal)
		if keyVal["title"] == "ip" {
			// 变更IP
			if keyVal["message"].(string) != remoteHost {
				fmt.Println("IP变更,重置服务")
				ResetServer = true
				remoteHost = keyVal["message"].(string)
			}
		}
		if keyVal["title"] == "message" {
			msgbox(keyVal["message"].(string))
		}
	}
}

// 简单的消息弹出
func msgbox(message string) {
	title := "消息"
	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "windows":
		// Windows下使用msg命令
		cmd = exec.Command("msg", "*", title, message)
	case "linux":
		// Linux下使用zenity(如果安装了的话)
		cmd = exec.Command("zenity", "--info", "--text", message, "--title", title)
	default:
		return
	}

	cmd.Run()
}

将轮循消息部份取消,改为自已定时循环获取。内存占用3M-6M,有访问时变大一点(10M),偶尔CPU占用较高(<40%)不知何为。观察。

相关文章