美文网首页
写一个做端口转发的小工具

写一个做端口转发的小工具

作者: MathCEC | 来源:发表于2020-05-24 22:10 被阅读0次

场景

有的时候,服务器并不是有那么多端口供我们访问,甚至于22,80,443这些常用的端口都会被关闭来防止有人恶意如入侵。开放到公网的端口将会非常少,当我们部署的应用一多起来,端口可能就不够用了,这个时候,可能就需要用到端口转发,当然如果你是一个经常使用Shell的人,Putty和XShell中的隧道你可能不陌生,这边我们就模拟着实现一个类似的小工具。甚至于我们可以给这个小工具再做一次加密和鉴权,当然此次没有实现。只是实现了端口转发时公网端口的复用。

源代码

package main

import (
    "flag"
    "fmt"
    "io/ioutil"
    "net"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "time"
)

var (
    confFilePath string
    signal       string
    help         bool
    background   bool
    fork         bool
    confList     []conf
)

func init() {
    bingOptions()
}

func main() {
    processOption()
}
//读取命令行的参数
func bingOptions() {
    //使用命令
    flag.StringVar(&confFilePath, "c", "tcpStation.etc", "紧跟配置文件的绝对路径或者相对路径")
    flag.StringVar(&signal, "s", "", "stop 停止监听;reload 重新加载配置文件")
    flag.BoolVar(&help, "h", false, "展示帮助信息")
    flag.BoolVar(&background, "b", false, "后台运行")
    flag.BoolVar(&fork, "f", false, "你别随便乱用")
    flag.Parse()
}
//解析命令行的参数
func processOption() {
    if help {
        showHelp()
    }
    if background {
        startBackgroundProcess()
    }
    if fork {
        pid := fmt.Sprintf("%d", os.Getpid())
        _ = ioutil.WriteFile("./tcp.pid", []byte(pid), os.ModePerm)
        loadConfToConfStruct()
        server()
    }
    switch signal {
    case "stop":
        bs, _ := ioutil.ReadFile("./tcp.pid")
        cmd := "kill -9 " + string(bs)
        execute(cmd)
    case "reload":
                //  此处没有实现重新加载的逻辑;
        loadConfToConfStruct()
    default:
        showHelp()
    }
}

func startBackgroundProcess() {
    file := os.Args[0]
    //> /dev/null 2>&1
    cmd := "nohup " + file + " -f -c " + confFilePath + " > log.log 2>&1 &"
    execute(cmd)
    os.Exit(0)
}

func execute(cmd string) {
    _, err := exec.Command("bash", "-c", cmd).CombinedOutput()
    if err != nil {
        println(err.Error())
    }
}

func showHelp() {
    println(`
隧道建立工具
    -b  后台启动本程序
    -c  指定配置文件
    -h  展示本帮助
    -s  signal  发送信号量 stop 停止监听;reload 重新加载配置文件`)
    os.Exit(-1)
}

type conf struct {
    //看看ProxyIp和Port是不是-1:-1
    toProxy bool
    //看看targetProxy是不是-1:-1
    fromProxy bool
    localPort string
    target    string
    proxy     string
}

func loadConfToConfStruct() {
    data, err := ioutil.ReadFile(confFilePath)
    if err != nil {
        println(err.Error())
        os.Exit(-1)
    }
    dataStr := string(data)
    configArr := strings.Split(dataStr, "\n")
    for _, line := range configArr {
        line = strings.TrimSpace(line)
        println(strings.Index(line, "#"))
        if strings.Index(line, "#") == 0 {
            continue
        }
        items := strings.Split(line, ",")
        if len(items) != 3 {
            continue
        }
        confList = append(confList, conf{
            toProxy:   items[2] != "-1:-1", // fix by mathcec 2020.8.10 逻辑反了
            fromProxy: items[1] == "-1:-1",
            localPort: items[0],
            target:    items[1],
            proxy:     items[2],
        })
    }
    //confList = []conf{{
    //  toProxy:    false,
    //  fromProxy:  true,
    //  localPort:  "8080",
    //  targetIp:   "-1",
    //  targetPort: -1,
    //  proxyIp:    "-1",
    //  proxyPort:  -1,
    //}, {
    //  toProxy:    true,
    //  fromProxy:  false,
    //  localPort:  "9000",
    //  targetIp:   "127.0.0.1",
    //  targetPort: 9001,
    //  proxyIp:    "127.0.0.1",
    //  proxyPort:  8080,
    //}, {
    //  toProxy:    false,
    //  fromProxy:  false,
    //  localPort:  "9002",
    //  targetIp:   "127.0.0.1",
    //  targetPort: 9001,
    //  proxyIp:    "-1",
    //  proxyPort:  -1,
    //}}
}

func server() {
    for _, c := range confList {
        go startProxy(c)
    }
    for {
        time.Sleep(1 * 1000000000)
    }
}

func startProxy(localConf conf) {
    proxyListen, err := net.Listen("tcp", ":"+localConf.localPort)
    if err != nil {
        println(err.Error())
        return
    }
    defer proxyListen.Close()
    for {
        proxyConn, err := proxyListen.Accept()
        if err != nil {
            println(err.Error())
            continue
        }
        var buffer []byte
        if localConf.fromProxy {
            buffer = make([]byte, 12)
        } else {
            buffer = make([]byte, 6)
        }
        n, err := proxyConn.Read(buffer)
        if err != nil {
            println(err.Error())
            continue
        }
        var targetConn net.Conn;
        //判断是不是需要进行代理 需要的话 在最开始设置6位作为target
        if localConf.toProxy {
            buffer = append(ip2byte(localConf.target), buffer...)
            targetConn, err = net.Dial("tcp", localConf.proxy)
        } else {
            //判断是不是代理 如果是代理
            var target = ""
            if localConf.fromProxy {
                target = byte2ip(buffer[:6])
            } else {
                target = localConf.target
            }
            targetConn, err = net.Dial("tcp", target)
        }
        if err != nil {
            println(err.Error())
            proxyConn.Close()
            continue
        }
        //如果代理过来的,前6为需要拿掉
        if localConf.fromProxy {
            //因为只拿了6个,所以不能发送其余的内容
            n, err = targetConn.Write(buffer[6:n])
        } else {
            //这里有BUG的风险,如果没有6个呢
            n, err = targetConn.Write(buffer[:])
        }
        if err != nil {
            println(err.Error())
            proxyConn.Close()
            targetConn.Close()
            continue
        }
        //在前面
        go proxy(proxyConn, targetConn)
        go proxy(targetConn, proxyConn)

    }
}

func proxy(c1, c2 net.Conn) {
    defer c1.Close()
    defer c2.Close()
    var buffer = make([]byte, 409600)
    for {
        n, err := c1.Read(buffer)
        if err != nil {
            break
        }
        n, err = c2.Write(buffer[:n])
        if err != nil {
            break
        }
    }

}

func ip2byte(ipAndPort string) []byte {
    brr := strings.Split(ipAndPort, ":")
    arr := strings.Split(brr[0], ".")
    res := make([]byte, 6)
    for i, a := range arr {
        u, _ := strconv.ParseUint(a, 10, 8)
        res[i] = byte(u)
    }
    u, _ := strconv.ParseUint(brr[1], 10, 16)
    res[4] = byte(u / 256)
    res[5] = byte(u - u/256*256)
    return res
}

func byte2ip(byte []byte) string {
    return fmt.Sprintf("%d.%d.%d.%d:%d", byte[0], byte[1], byte[2], byte[3], int(byte[4])*256+int(byte[5]))
}

原理

原理其实很简单。我们将监听模式分为两种,一种是监听本地端口并转发至目标端口,一种是监听本地端口并转发至代理端口,由代理端口转发至目标端口。

第一种很简单,当获端口监听到内容时,会首先读取一点点内容,然后开启两个个协程去处理。每个协程都会将自己接收到的输出流写入到另一端的输入流中。

第二中就比较麻烦了,也是端口监听到内容,此时会将真正的目的地写到整个流的初始位置。代理端口收到后会建立和目标端口的连接,同时将流中的标记数据删除后转发到目的端口。

配置文件是这个样子的

# 本地端口,目标IP:目标端口,代理服务器:代理端口;
# 代理服务器和代理端口不是-1:-1则认为有代理服务器
# 当目标服务器和端口是-1:-1时认为从数据包中获取目标服务器

8080,-1:-1,-1:-1
9000,127.0.0.1:9001,127.0.0.1:8080

这个时候,你可以启动一个Http服务器测试一下

package main

import "net/http"

func main() {
    http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
        writer.Write([]byte("HelloWorld"))
    })
    http.ListenAndServe(":9001", nil)
}

此时按照配置文件的描述,你访问本地的9000端口,就会经过8080端口转发到9001端口。此处是将两个配置写到一起了。理论上这两个配置是要分到两个程序中。代理端口8080,-1:-1,-1:-1需要部署在服务器上。转发端口9000,127.0.0.1:9001,127.0.0.1:8080随便部署在一个你机器和服务器网络都通的地方。

考虑到拓展,你这个时候是不是可以在配置文件中写一个盐值对时间戳加盐,然后就可以双端校验了,甚至于,你都以可仿照SSH免密登陆的形式,搞一个密钥对。当然这不属于基本功能,这边就先不实现了。

同时,还实现了Go语言在后台执行,不过这边使用的是nohup,感觉应该还会有更好的办法。不过不太想在这种小工具中引入过多的三方库。我本人对这种小工具或者小工具包调用第三方库还是不是理解的。

相关文章

  • 写一个做端口转发的小工具

    场景 有的时候,服务器并不是有那么多端口供我们访问,甚至于22,80,443这些常用的端口都会被关闭来防止有人恶意...

  • Golang 端口转发工具

    初学go,写一个端口转发工具。很方便的小工具,希望能对大家学习go语言有所帮助。 ```Golang packag...

  • 80端口被占用

    可以通过pfctl做网络层的端口转发, 让连接到本机80端口的请求, 都转发到8080端口。 sudo vim /...

  • 5. 调试利器 - 端口转发 - ssh隧道技术

    说明 使用技术:** SSH隧道**端口转发分为 本地端口转发 和 远程端口转发。本地端口转发:将远程的端口映射到...

  • Docker-10 端口转发、容器卷 、网络、数据存储

    端口转发 使用端口转发解决容器端口访问问题 mysql应用端口转发: 查看本地地址: 运行容器:使用-p作端口转发...

  • iptables实现端口转发

    Linux下iptables不仅可以用来做防火墙还可以用来做端口转发 示例: 将本机的8080端口转发至其他主机,...

  • Firewall端口转发

    添加端口转发 删除端口转发

  • SSH 端口转发

    SSH端口转发分为三种情况,分别为本地端口转发,远程端口转发以及动态端口转发.本文只介绍前两种. 什么是端口转发 ...

  • iptables的端口映射

    iptables的端口范围映射 iptables设置端口转发规则原理 Linux - iptables做UDP数据...

  • Linux下iptables的使用

    查看端口转发规则(80): 新增端口转发规则(80转发到8080): 删除端口转发规则(80转发到8080): 添...

网友评论

      本文标题:写一个做端口转发的小工具

      本文链接:https://www.haomeiwen.com/subject/iadsahtx.html