本篇文章主要介绍使用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的文件

需要使用到golang.org/x/net/proxy这个包

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)
	}
}

运行结果: === RUN TestCheckHttpProxy 2019/07/07 14:35:17 [001] INFO: Got request / www.baidu.com GET http://www.baidu.com/ 2019/07/07 14:35:17 [001] INFO: Sending request GET http://www.baidu.com/ 2019/07/07 14:35:17 [001] INFO: Received response 200 OK 2019/07/07 14:35:17 [001] INFO: Copying response to client 200 OK [200] 2019/07/07 14:35:17 [001] INFO: Copied 153978 bytes to client error= — PASS: TestCheckHttpProxy (2.15s) PASS

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)
}

结果如下:

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) 用时20秒 map[9999:true]

最后发现扫描出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]