本篇文章主要介绍使用golang实现一个代理扫描工具,目前主流常用的代理有http代理,和socks5代理,当然还有什么vpn,ss这种代理,不在本文讨论范畴,因为这些是加密的。

扫描出来可以被我们使用的代理,当然是不需要密码,免费的代理了。

首先是httpProxy的实现:新建一个go文件httpproxy.go

下面的CheckProxy在后面会实现

package proxy

import (
    "fmt"
    "net/http"
    "net/url"
    "time"
)

type HttpProxy struct {
}

func (HttpProxy) IsProxy(proxyIp string, proxyPort int) (isProxy bool, err error) {
    proxyUrl := fmt.Sprintf("http://%s:%d", proxyIp, proxyPort)
    proxy, err := url.Parse(proxyUrl)
    if err != nil {
        return false, err
    }
    netTransport := &http.Transport{
        Proxy: http.ProxyURL(proxy),
    }
    client := &http.Client{
        Timeout:   time.Second * 20,  //设置连接超时时间
        Transport: netTransport,
    }
    return CheckProxy(client)
}

其次是SocksProxy的实现:新建socksproxy.go的文件

http client访问网址的超时时间可以自行设置

package proxy

import (
    "context"
    "fmt"
    "golang.org/x/net/proxy"
    "net"
    "net/http"
    "time"
)

type SocksProxy struct {
}

func (SocksProxy) IsProxy(proxyIp string, proxyPort int) (isProxy bool, err error) {
    proxyAddr := fmt.Sprintf("%s:%d", proxyIp, proxyPort)
    dialSocksProxy, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
    if err != nil {
        return false, nil
    }
    netTransport := &http.Transport{DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
        c, e := dialSocksProxy.Dial(network, addr)
        return c, e
    }}
    client := &http.Client{
        Timeout:   time.Second * 10,
        Transport: netTransport,
    }
    return CheckProxy(client)
}

新建basic.go实现CheckProxy方法,通过访问一个url,测试一下是否可以连通,达到检测代理是否可用的目的

这里使用百度的地址,作为检测的url,如果需要的是可以fq的代理,可以选择谷歌首页地址

package proxy

import (
    "io/ioutil"
    "net/http"
    "strings"
)

const TestUrl = "http://www.baidu.com"

type Proxyer interface {
    IsProxy(proxyIp string, proxyPort int) (isProxy bool, err error)
}

func CheckProxy(client *http.Client) (isProxy bool, err error){
    res, err := client.Get(TestUrl)
    if err != nil {
        return false, err
    } else {
        defer res.Body.Close()
        if res.StatusCode == 200 {
            body, err := ioutil.ReadAll(res.Body)
            if err == nil && strings.Contains(string(body), "baidu") {
                return true, nil
            } else {
                return false, err
            }
        } else {
            return false, nil
        }
    }
}

新建单元测试用例来测试写好的httpproxy和socksproxy

httpproxy_test.go

package proxy

import (
    "github.com/elazarl/goproxy"
    "log"
    "net/http"
    "testing"
    "time"
)

func TestCheckHttpProxy(t *testing.T) {
    go func() {
        proxy := goproxy.NewProxyHttpServer()
        proxy.Verbose = true
        log.Fatal(http.ListenAndServe(":8080", proxy))
    }()
    time.Sleep(time.Second * 2)
    isProxy, err := HttpProxy{}.IsProxy("127.0.0.1", 8080)
    if !isProxy {
        t.Error("should be a proxy", err)
    }
}

socksproxy_test.go

package proxy

import (
    "github.com/armon/go-socks5"
    "testing"
    "time"
)

func TestIsSocksProxy(t *testing.T) {
    go func() {
        conf := &socks5.Config{}
        server, err := socks5.New(conf)
        if err != nil {
            panic(err)
        }
        if err := server.ListenAndServe("tcp", "127.0.0.1:8002"); err != nil {
            panic(err)
        }
    }()
    time.Sleep(time.Second*2)
    isProxy, err := SocksProxy{}.IsProxy("127.0.0.1", 8002)
    if !isProxy {
        t.Error("should be a proxy", err)
    }
}
运行结果:
=== RUN TestIsSocksProxy
--- PASS: TestIsSocksProxy (2.28s) PASS

测试用例显示都通过了,接下来写个main函数来具体实现扫描逻辑

网上找个可以用的代理 183.30.204.91 9999,随便百度搜索的,测试也发现是可用的。那么用代理扫描工具扫一下,看一下效果

定义好ip,和扫描端口范围,8000-10000这个端口范围,并发控制在500以内,也就是最多同时500个线程在执行扫描端口。

注意这个并发数,会涉及到打开的文件数,因为需要不断发起tcp连接,在unix哲学上,一切皆文件。

所以在mac上或者在linux上执行扫描的时候,需要修改文件打开数的限制,可以使用ulimit命令修改当前用户可以打开的文件数。

使用一个map来存放我们扫出来的代理,最后记录下扫描用时。

package main

import (
    "fmt"
    "sync"
    "time"
)
import "scanproxy/proxy"

var (
    Threads = make(chan int, 500) //控制在500以内
)

func main() {
    var start = 8000
    var end = 10000
    var proxyIp = "183.30.204.91"
    var now = time.Now().Unix()
    var Map = make(map[int]bool)
    var waitGroup = sync.WaitGroup{}
    for port := start; port < end; port++ {
        Threads <- 1
        go func(port int) {
            waitGroup.Add(1)
            defer waitGroup.Add(-1)
            isProxy, err := proxy.HttpProxy{}.IsProxy(proxyIp, port)
            if isProxy {
                fmt.Printf("%s:%d\n", proxyIp, port)
                Map[port] = true
            }
            if err != nil {
                fmt.Println(err)
            }
            <-Threads
        }(port)
    }
    waitGroup.Wait()
    fmt.Printf("用时%d秒\n",time.Now().Unix()-now)
    fmt.Println(Map)
}

结果如下:

最后发现扫描出9999是代理,为什么用时是20秒呢?

这是因为在http_proxy.go里面我设置的超时时间是20秒

client := &http.Client{
        Timeout:   time.Second * 20,
        Transport: netTransport,
    }

修改成5秒后,可以看到效果,用时5秒。

可以感受到go的并发是真的快。

183.30.204.91:9999
Get http://www.baidu.com: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
Get http://www.baidu.com: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
用时5秒
map[9999:true]