如何在腾讯云平台里搭建vpn连接本地局域网
最近需要使用腾讯云平台,了解了一番之后发现cvm必须使用它所指定的代理后使用ssh登录,另外ssh登录的时候还需要一个动态密码,这样对运维来说很不方便,于是就想弄个vpn将cvm和线下环境弄到一个局域网里。这样就有个问题了,openvpn的服务器放在哪里,测试了一下发现cvm只能进不能出,也即不能直接访问外网,除非是有请求进来。于是显然cvm只能做服务器了,但是新问题又来了,cvm做服务器的话tcp协议要求对应用层协议进行改造,必须传一个tgw的头,这就让人郁闷了,难道去修改openvpn的代码吗?这显然不行,于是就想干脆写个proxy得了,它的原理很简单,作为一个透明代理,唯一的不同是,当它作为客户端时,会在连接建立后立即向服务器发送一个tgw头;当它作为服务器时,会将连接建立后收到的tgw头干掉,当这些事情作完之后它就是一个纯粹的透明代理了。这样的程序不难写,用golang一会就可以写一个,下面是写完的程序
package mainimport ("flag""fmt""log""net""time")// 配置参数type TConfig struct {targetAddr string //目标地址,比如app12345.qzoneapp.com:80sourceAddr string //源地址,即本地监听的地址logFile string //日誌文件地址actAsServer bool //是否作为服务器,如果是的话,需要将targetAddr里的参数发给tgwbufferSize int}var (gConfig TConfiggTargetAddr *net.TCPAddrgSourceAddr *net.TCPAddr)const (TGW_PREFIX = "tgw_l7_forward"TGW_HEADER_SIZE = 1024 * 4TGW_HEADER_SEG_COUNT = 3)//初始化参数func initArgs() bool {flag.StringVar(&gConfig.targetAddr, "t", ":54321", "目标地址,比如app12345.qzoneapp.com:80")flag.StringVar(&gConfig.sourceAddr, "s", ":12345", "本地的监听地址,比如192.168.1.200:8080")//flag.StringVar(&gConfig.logFile, "l", "/tmp/tgwProxy.log", "日誌輸出地址")flag.BoolVar(&gConfig.actAsServer, "a", false, "是否作为服务器,如果是的话,需要将targetAddr里的参数发给tgw")flag.IntVar(&gConfig.bufferSize, "b", 1024*1024, "缓存大小")flag.Parse()var err errorgSourceAddr, err = net.ResolveTCPAddr("tcp4", gConfig.sourceAddr)if err != nil {fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())return false}gTargetAddr, err = net.ResolveTCPAddr("tcp4", gConfig.targetAddr)if err != nil {fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.targetAddr, err.Error())return false}if gConfig.bufferSize < 1 {fmt.Printf("buffer size:%d is too small\n", gConfig.bufferSize)return false}return true}//初始化服务器func runServer() {listener, err := net.ListenTCP("tcp4", gSourceAddr)if err != nil {fmt.Printf("listen on tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())return}log.Printf("server started")for {conn, err := listener.AcceptTCP()if err != nil {log.Printf("accept tcp connection failed:%s", err.Error())time.Sleep(1000000000)}log.Printf("get one connection")conn.SetKeepAlive(true)conn.SetNoDelay(true)if gConfig.actAsServer {go doServer(conn)} else {go doClient(conn)}}}//将所有数据写入连接中func writeAllData(conn net.Conn, buffer []byte) error {offset := 0length := len(buffer)for offset < length {bytes, err := conn.Write(buffer[offset:length])if err != nil {log.Printf("write to target:%s failed:%s", conn.RemoteAddr().String(), err.Error())return err}offset += bytes}//log.Printf("write %d bytes to connection", offset)return nil}//处理服务器事务func doServer(conn net.Conn) {defer handlePanic()//去掉tgw的头buffer := make([]byte, TGW_HEADER_SIZE)length, err := conn.Read(buffer)if err != nil {log.Printf("read from client failed:%s", err.Error())return}segCount := 0for i := 1; i < length; i++ {if buffer[i] == '\n' && buffer[i-1] == '\r' {segCount++if segCount == TGW_HEADER_SEG_COUNT {buffer = buffer[i+1 : length]break}}}if segCount < TGW_HEADER_SEG_COUNT {log.Printf("invalid tgw header:%s", string(buffer[0:length]))return}targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)if err != nil {log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())return}log.Printf("connect to server:%s succeed", gTargetAddr.String())//写剩下的字段err = writeAllData(targetConn, buffer)if err != nil {return}//源请求到目标连接,即写请求go proxyTransfer(conn, targetConn)//目标响应到源连接,即写响应go proxyTransfer(targetConn, conn)}//将从sourceConn中读到的数据写给targetConnfunc proxyTransfer(sourceConn net.Conn, targetConn net.Conn) {defer handlePanic()buffer := make([]byte, gConfig.bufferSize)for {length, err := sourceConn.Read(buffer)if err != nil {log.Printf("read from source:%s failed:%s", sourceConn.RemoteAddr().String(), err.Error())targetConn.Close()return}//log.Printf("read %d bytes from source connection", length)err = writeAllData(targetConn, buffer[0:length])if err != nil {targetConn.Close()return}}}//处理客户端事务func doClient(conn net.Conn) {defer handlePanic()//建立连接到目录地址targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)if err != nil {log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())return}log.Printf("connect to server:%s succeed", gTargetAddr.String())//加上tgw的头tgwHeader := TGW_PREFIX + "\r\nHost:" + gConfig.targetAddr + "\r\n\r\n"log.Printf("send tgwHeader:%s to server:%s", tgwHeader, gTargetAddr.String())buffer := []byte(tgwHeader)err = writeAllData(targetConn, buffer)if err != nil {return}//源请求到目标连接,即写请求go proxyTransfer(conn, targetConn)//目标响应到源连接,即写响应go proxyTransfer(targetConn, conn)}//初始化日志func initLogger() bool {log.SetFlags(log.Ldate | log.Lshortfile | log.Lmicroseconds | log.Ltime)log.Printf("logger init")return true}func handlePanic() {err := recover()if err != nil {log.Printf("uncaught panic:%s", err.(error).Error())}}func main() {if !initArgs() || !initLogger() {return}runServer()}
这样的话,在cvm上启动一个服务器端,监听到127.0.0.1:1179上,然后再在cvm上启动一个proxy让它代理到这个端口上,而外部端口则监听到cvm的绑定端口上。
同样的,在本地启动一个proxy,让它代理到cvm的绑定域名和端口上,然后openvpn客户端连到代理的监听端口上,这样果然两台机器就弄到一个局域网里了。
接下来你就只需要在cvm里建一些”合适“的账号就可以不用动态密码直接像局域网一样登录了。