设为首页 加入收藏

TOP

如何在Web服务器80端口上开启SSH服务(一)
2017-09-30 13:41:05 】 浏览:9611
Tags:何在 Web 服务器 开启 SSH 服务

本文所讨论的网络端口复用并非指网络编程中采用SO_REUSEADDR选项的 Socket Bind 复用。它更像是一个带特定路由功能的端口转发工具,在应用层实现。

背景

笔者所处网络中防火墙只开放了一个端口,但却希望能够提供多种网络服务用于测试。所以需要寻求一种解决方案,能够对TCP数据包特征进行识别,用以实现在一个开放端口上同时提供HTTP/SSH/MQTT等多种服务。

比如说,你可以在80端口上复用一个SSH服务,普通用户只知道浏览器访问http://x.x.x.x/ ,而你却可以用 ssh user@x.x.x.x -p 80 这样的方式来访问你的服务器,这也不失为一种隐藏SSH服务的办法。

端口复用神器 - sslh

sslh是一款采用C语言编写的开源端口复用软件,目前支持 HTTP、SSL、SSH、OpenVPN、tinc、XMPP等多种协议识别。它主要运行于*nix环境,源代码托管在GitHub上。据官网介绍,Windows系统下可在Cygwin环境中编译运行,笔者未作测试。

编译过程并不复杂,直接按照官方文档操作,不在此赘述。Debian用户可直接通过sudo apt-get install sslh安装。

编译生成两个可执行文件:sslh-fork 和 sslh-select 。二者的区别在于工作模式的差异:

  • sslh-fork 采用*nix的进程fork模型,为每一个TCP连接fork一个子进程来处理包的转发。对于长连接而言,无需频繁建立大量新连接,fork带来的开销基本可以忽略。但是如果对像HTTP这样的短连接请求,采用fork子进程的方式来进行包转发的话,在出现大量并发请求时,效率会受到一定影响。不过fork模式经过了良好测试,运行起来稳定可靠。
  • sslh-select 采用单线程监控管理所有网络连接,是比较新的一种方式。但相对epool等基于事件的I/O机制来说,select的传统轮询模式效率还是相对较低的。

sslh支持在配置文件中使用正则表达式来自定义协议识别规则,但是我在尝试 MQTT v3.1 协议识别时,出现了问题。当然也有可能是我编写的正则表达式和它使用的正则库不匹配。

高性能负载均衡器 - HAProxy

HAProxy是一款开源高性能的 TCP/HTTP 软件负载均衡器,目前在游戏后端服务和Web服务器负载均衡等方面都有着非常广泛的应用。通过配置,可以实现多种SSL应用复用同一个端口,比如 HTTPS、SSH、OpenVPN等。这里有一篇参考文档

虽然HAProxy性能卓越,但它不容易通过扩展来满足特定的需求。

为网络而生的现代语言 - Go

Go语言是近几年我学习研究过的优秀编程语言之一,它的简洁和高效深深吸引了我(我喜欢简单的东西,比如Python)。Go语言的goroutine在语言级别提供并发支持,channel又在这些协程之间提供便捷可靠的通信机制。结合起来,Go语言非常适合编写高并发的网络应用。之前也打算过用Python+gevent的方式,最后还是考虑到Go语言静态编译后的高效率,没有选择Python。

在Github上翻腾,找到一个Go语言实现的类sslh项目——Switcher。它很久没有更新,支持的协议也非常少——实际上它只能识别SSH协议。Switcher的实现非常简单,核心代码不到200行。于是决定在它的基础上进行改造,实现我所需要的功能。

D——I——Y

到Github上fork了一份Switcher代码,在它的基础上修改。说是修改,其实已面目全非。新的实现中调整了原有架构,去掉对SSH协议的直接支持,转而采用更加通用的协议识别模式,以求达到可以不通过修改程序而只需简单配置即可支持大部分协议,让程序通用性更强一些。

首先最常见的协议匹配模式是根据packet头几个字节对目标协议特征进行比对。如果只是保存每个协议的头N个字节,不加任何处理逐一比对的话,可能会存在一定的效率问题。一方面,需要对所有pattern进行遍历,逐个与收到的packet进行比较;另一方面,如果网络延时较大,不能一次性收集到足够多的字节,则需要反复多次比对。举一个比较极端的例子,假设我有100个目标协议需要比对匹配,pattern大小都在10字节以上,这时候我通过telnet/netcat连接服务器,一个字节一个字节的发送数据,则服务器可能要进行10*100次字符串比较。

为了解决这个问题,简单设计了一个树形结构,把所有的pattern都以字节为单位填充到这棵树上,直至末梢。叶节点上保存协议对应的目标IP和端口值。

func (t *MatchTree) Add(p *PREFIX) {
    for _, patternStr := range p.Patterns {
        pattern := []byte(patternStr)
        node := t.Root
        for i, b := range pattern {
            nodes := node.ChildNodes
            if next_node, ok := nodes[b]; ok {
                node = next_node
                continue
            }

            if nodes == nil {
                nodes = make(map[byte]*MatchTreeNode)
                node.ChildNodes = nodes
            }

            root, leaf := createSubTree(pattern[i+1:])
            leaf.Address = p.Address
            nodes[b] = root

            break
        }
    }
}

也许是我想太多,在需要比对的协议数量很少的情况下,可能这样的设计并不能带来根本上的效率提升。不过我喜欢这种为了可能的效率提升而不断努力的赶脚 ^_^

相比packet prefix匹配的模式,正则表达式会更加灵活。所以我采取类似sslh的方式,加入了对正则表达式的支持。考虑到效率和具体实现的问题,对正则表达式匹配规则加入了一定的限制,比如需要知道目标字符串的最大长度。正则表达式只能在packet buffer达到一定长度要求的情况下逐一匹配。

func (p *REGEX) Probe(header []byte) (result ProbeResult, address string) {
    if p.MinLength > 0 && len(header) < p.MinLength {
        return TRYAGAIN, ""
    }
    for _, re := range p.regexpList {
        if re.Match(header) {
            return MATCH, p.Address
        }
    }

    if p.MaxLength > 0 && len(header) >= p.MaxLength {
        return UNMATCH, ""
    }

    return TRYAGAIN, ""
}

基于上述两种简单的匹配规则,很容易可以构造出ssh、http等常用的协议。在实现中,我加入了一些常用协议的支持,省去用户自定义的麻烦。

    case "ssh":
        service = "prefix"
        p = &PREFIX{ps.BaseConfig, []string{"SSH"}}
    c
首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇记一次Golang routine卡死 下一篇Cheatsheet: 2016 10.01 ~ 10.31

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目