更新0.4
18
README.md
@ -101,7 +101,23 @@
|
||||
|
||||
## MemCache 蜜罐
|
||||
|
||||

|
||||

|
||||
|
||||
## HTTP 蜜罐
|
||||
|
||||

|
||||
|
||||
## TFTP 蜜罐
|
||||
|
||||

|
||||
|
||||
## ES 蜜罐
|
||||
|
||||

|
||||
|
||||
## VNC 蜜罐
|
||||
|
||||

|
||||
|
||||
|
||||
# 注意事项
|
||||
|
@ -63,15 +63,19 @@
|
||||
<tr>
|
||||
<th width="10%">集群名称</th>
|
||||
<th width="10%">集群 IP</th>
|
||||
<th width="8%" style="text-align: center;">插件</th>
|
||||
<th width="8%" style="text-align: center;">WEB</th>
|
||||
<th width="8%" style="text-align: center;">暗网</th>
|
||||
<th width="8%" style="text-align: center;">SSH</th>
|
||||
<th width="8%" style="text-align: center;">Redis</th>
|
||||
<th width="8%" style="text-align: center;">Mysql</th>
|
||||
<th width="8%" style="text-align: center;">Telnet</th>
|
||||
<th width="8%" style="text-align: center;">FTP</th>
|
||||
<th width="8%" style="text-align: center;">MemCache</th>
|
||||
<th width="6%" style="text-align: center;">插件</th>
|
||||
<th width="6%" style="text-align: center;">WEB</th>
|
||||
<th width="6%" style="text-align: center;">暗网</th>
|
||||
<th width="6%" style="text-align: center;">SSH</th>
|
||||
<th width="6%" style="text-align: center;">Redis</th>
|
||||
<th width="6%" style="text-align: center;">Mysql</th>
|
||||
<th width="6%" style="text-align: center;">Telnet</th>
|
||||
<th width="6%" style="text-align: center;">FTP</th>
|
||||
<th width="6%" style="text-align: center;">MemCache</th>
|
||||
<th width="6%" style="text-align: center;">HTTP</th>
|
||||
<th width="6%" style="text-align: center;">TFTP</th>
|
||||
<th width="6%" style="text-align: center;">VNC</th>
|
||||
<th width="6%" style="text-align: center;">ES</th>
|
||||
<th width="1%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -185,6 +189,30 @@
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].http_status == 1) {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].es_status == 1) {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].tftp_status == 1) {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].vnc_status == 1) {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
|
||||
// if (data[i].http_status == "1") {
|
||||
// _h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
@ -207,6 +235,10 @@
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
// _h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;">';
|
||||
_h += ' <i class="fa fa-trash-o" onclick="del(' + data[i].id + ')"></i>';
|
||||
|
@ -133,7 +133,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<div class="card-box" style="height: 550px;">
|
||||
<div class="card-box" style="height: 600px;">
|
||||
<p class="title">服务状态</p>
|
||||
<p><span class="openx"></span>ADMIN</p>
|
||||
|
||||
@ -194,11 +194,36 @@
|
||||
{{end}}
|
||||
|
||||
{{if eq .memCacheStatus "1"}}
|
||||
<p><span class="openx"></span>MEMCACHE</p>
|
||||
<p><span class="openx"></span>MemCache</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>MEMCACHE</p>
|
||||
<p><span class="closex"></span>MemCache</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .httpStatus "1"}}
|
||||
<p><span class="openx"></span>HTTP</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>HTTP</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .tftpStatus "1"}}
|
||||
<p><span class="openx"></span>TFTP</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>TFTP</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .esStatus "1"}}
|
||||
<p><span class="openx"></span>ElasticSearch</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>ElasticSearch</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .vncStatus "1"}}
|
||||
<p><span class="openx"></span>VNC</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>VNC</p>
|
||||
{{end}}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -209,12 +234,12 @@
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="card-box">
|
||||
<div id="myChart1" style="width:100%;height:250px;"></div>
|
||||
<div id="myChart1" style="width:100%;height:300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="card-box">
|
||||
<div id="myChart2" style="width:100%;height:250px;"></div>
|
||||
<div id="myChart2" style="width:100%;height:300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -70,6 +70,22 @@
|
||||
background-color: #61772d;
|
||||
}
|
||||
|
||||
.lb_es {
|
||||
background-color: #0177a3;
|
||||
}
|
||||
|
||||
.lb_tftp {
|
||||
background-color: #452b6d;
|
||||
}
|
||||
|
||||
.lb_vnc {
|
||||
background-color: #3c6f40;
|
||||
}
|
||||
|
||||
.lb_http {
|
||||
background-color: #56a996;
|
||||
}
|
||||
|
||||
.pre {
|
||||
background: #2c2c31;
|
||||
color: #4fe21b;
|
||||
@ -372,6 +388,14 @@
|
||||
_h += ' <span class="label lb_mem_cache">MEMCACHE</span> ';
|
||||
} else if (data.result[i].type == "PLUG") {
|
||||
_h += ' <span class="label lb_plug">PLUG</span> ';
|
||||
}else if (data.result[i].type == "ES") {
|
||||
_h += ' <span class="label lb_es">ES</span> ';
|
||||
}else if (data.result[i].type == "TFTP") {
|
||||
_h += ' <span class="label lb_tftp">TFTP</span> ';
|
||||
}else if (data.result[i].type == "VNC") {
|
||||
_h += ' <span class="label lb_vnc">VNC</span> ';
|
||||
} else if (data.result[i].type == "HTTP") {
|
||||
_h += ' <span class="label lb_http">HTTP</span> ';
|
||||
}
|
||||
|
||||
_h += ' <span class="project">' + data.result[i].project_name + '</span>';
|
||||
@ -394,7 +418,7 @@
|
||||
}
|
||||
|
||||
_h += ' <td><span class="info" onclick="show(' + data.result[i].id + ')">点击查看</span></td>';
|
||||
_h += ' <td class="td"><span class="cinfo">' + data.result[i].info.length + '</span></td>';
|
||||
_h += ' <td class="td"><span class="cinfo">' + data.result[i].info.replace("&&","").length + '</span></td>';
|
||||
_h += ' <td class="td">' + data.result[i].create_time.replace("T", " ").replace("+08:00", " ").replace("Z", "") + '</td>';
|
||||
_h += '</tr>';
|
||||
}
|
||||
|
@ -25,6 +25,12 @@
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade in active" id="home-2">
|
||||
<div class="timeline-2">
|
||||
<div class="time-item">
|
||||
<div class="item-info">
|
||||
<small class="text-muted">2019-10-29</small>
|
||||
<p>发布 v0.4 版本</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-item">
|
||||
<div class="item-info">
|
||||
<small class="text-muted">2019-09-16</small>
|
||||
|
@ -20,8 +20,7 @@
|
||||
<!-- LOGO -->
|
||||
<div class="topbar-left">
|
||||
<a href="/dashboard" class="logo">
|
||||
{{/*<img src="/res/images/logo.png" class="logoimg">*/}}
|
||||
<span>HFish</span>
|
||||
<img src="/static/images/logo.png" style="width: 100px;margin-top: -5px;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -56,7 +55,7 @@
|
||||
<a class="nav-link dropdown-toggle arrow-none waves-effect waves-light nav-user"
|
||||
data-toggle="dropdown" href="#" role="button"
|
||||
aria-haspopup="false" aria-expanded="false">
|
||||
<img src="/static/images/avatar.png" alt="user" class="img-circle avatarx">
|
||||
<img src="/static/images/avatar.jpg" alt="user" class="img-circle avatarx">
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right dropdown-arrow profile-dropdown "
|
||||
aria-labelledby="Preview">
|
||||
|
@ -29,8 +29,7 @@
|
||||
<div class="card-box m-b-0">
|
||||
<div class="text-xs-center m-t-20">
|
||||
<a href="/" class="logo">
|
||||
<img src="/static/images/logo.png" class="logoimg">
|
||||
<div style="letter-spacing: 7px;font-size: 65px;">HFish</div>
|
||||
<img src="/static/images/hfish.png" class="logoimg">
|
||||
</a>
|
||||
</div>
|
||||
<div class="m-t-10 p-20">
|
||||
|
21
config.ini
@ -1,5 +1,5 @@
|
||||
[rpc]
|
||||
status = 0 # 模式 0关闭 1服务端 2客户端
|
||||
status = 1 # 模式 0关闭 1服务端 2客户端
|
||||
addr = 0.0.0.0:7879 # RPC 服务端地址 or 客户端地址
|
||||
name = Server # 状态1 服务端 名称 状态2 客户端 名称
|
||||
|
||||
@ -42,7 +42,7 @@ static = deep/static # 暗网 WEB 静态文件路径
|
||||
url = / # 暗网 WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
|
||||
|
||||
[ssh]
|
||||
status = 2 # 是否启动 SSH 0 关闭 1 低交互 2 高交互
|
||||
status = 1 # 是否启动 SSH 0 关闭 1 低交互 2 高交互
|
||||
addr = 0.0.0.0:22 # SSH 服务端地址 注意端口冲突,请先关闭服务器 openssh 服务 或 修改端口
|
||||
|
||||
[redis]
|
||||
@ -65,4 +65,19 @@ addr = 0.0.0.0:21 # Ftp 服务端地址 注意端
|
||||
[mem_cache]
|
||||
status = 1 # 是否启动 MemCache 0 关闭 1 启动
|
||||
addr = 0.0.0.0:11211 # Memcache 服务端地址 注意端口冲突
|
||||
rate_limit = 4 # 每秒响应次数
|
||||
|
||||
[http]
|
||||
status = 1 # 是否启动 HTTP代理 0 关闭 1 启动
|
||||
addr = 0.0.0.0:8080 # HTTP代理地址 注意端口冲突
|
||||
|
||||
[tftp]
|
||||
status = 1 # 是否启动 tftp 0 关闭 1 启动
|
||||
addr = 0.0.0.0:69 # tftp 服务端地址 注意端口冲突
|
||||
|
||||
[elasticsearch]
|
||||
status = 1 # 是否启动 ES蜜罐 0 关闭 1 启动
|
||||
addr = 0.0.0.0:9200 # ES蜜罐 服务端地址 注意端口冲突
|
||||
|
||||
[vnc]
|
||||
status = 1 # 是否启动 VNC蜜罐 0 关闭 1 启动
|
||||
addr = 0.0.0.0:5900 # VNC蜜罐 服务端地址 注意端口冲突
|
205
core/protocol/elasticsearch/elasticsearch.go
Normal file
@ -0,0 +1,205 @@
|
||||
package elasticsearch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
"strings"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
"HFish/core/report"
|
||||
)
|
||||
|
||||
// Config represents the configuration information.
|
||||
type Config struct {
|
||||
LogFile string `json:"logfile"`
|
||||
UseRemote bool `json:"use_remote"`
|
||||
Remote Remote `json:"remote"`
|
||||
HpFeeds HpFeeds `json:"hpfeeds"`
|
||||
InstanceName string `json:"instance_name"`
|
||||
Anonymous bool `json:"anonymous"`
|
||||
SensorIP string `json:"honeypot_ip"`
|
||||
SpoofedVersion string `json:"spoofed_version"`
|
||||
PublicIpUrl string `json:"public_ip_url"`
|
||||
}
|
||||
|
||||
// Remote is a struct used to contain the details for a remote server connection
|
||||
type Remote struct {
|
||||
URL string `json:"url"`
|
||||
UseAuth bool `json:"use_auth"`
|
||||
Auth Auth `json:"auth"`
|
||||
}
|
||||
|
||||
type HpFeeds struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Channel string `json:"channel"`
|
||||
Ident string `json:"ident"`
|
||||
Secret string `json:"secret"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// Auth contains the details in case basic auth is to be used when connecting
|
||||
// to the remote server
|
||||
type Auth struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// Conf holds the global config
|
||||
var Conf Config
|
||||
|
||||
// Attack is a struct that contains the details of an attack entry
|
||||
type Attack struct {
|
||||
SourceIP string `json:"source"`
|
||||
Timestamp time.Time `json:"@timestamp"`
|
||||
URL string `json:"url"`
|
||||
Method string `json:"method"`
|
||||
Form string `json:"form"`
|
||||
Payload string `json:"payload"`
|
||||
Headers Headers `json:"headers"`
|
||||
Type string `json:"type"`
|
||||
SensorIP string `json:"honeypot"`
|
||||
}
|
||||
|
||||
// Headers contains the filtered headers of the HTTP request
|
||||
type Headers struct {
|
||||
UserAgent string `json:"user_agent"`
|
||||
Host string `json:"host"`
|
||||
ContentType string `json:"content_type"`
|
||||
AcceptLanguage string `json:"accept_language"`
|
||||
}
|
||||
|
||||
func FakeBanner(w http.ResponseWriter, r *http.Request) {
|
||||
printInfo(r)
|
||||
response := fmt.Sprintf(`{
|
||||
"status" : 200,
|
||||
"name" : "%s",
|
||||
"cluster_name" : "elasticsearch",
|
||||
"version" : {
|
||||
"number" : "%s",
|
||||
"build_hash" : "89d3241d670db65f994242c8e838b169779e2d4",
|
||||
"build_snapshot" : false,
|
||||
"lucene_version" : "4.10.2"
|
||||
},
|
||||
"tagline" : "You Know, for Search"
|
||||
}`, Conf.InstanceName, Conf.SpoofedVersion)
|
||||
WriteResponse(w, response)
|
||||
return
|
||||
}
|
||||
|
||||
// FakeNodes presents a fake /_nodes result
|
||||
// TODO: Change IP Address with actual server IP address
|
||||
func FakeNodes(w http.ResponseWriter, r *http.Request) {
|
||||
printInfo(r)
|
||||
response := fmt.Sprintf(`
|
||||
{
|
||||
"cluster_name" : "elasticsearch",
|
||||
"nodes" : {
|
||||
"x1JG6g9PRHy6ClCOO2-C4g" : {
|
||||
"name" : "%s",
|
||||
"transport_address" : "inet[/
|
||||
%s:9300]",
|
||||
"host" : "elk",
|
||||
"ip" : "127.0.1.1",
|
||||
"version" : "%s",
|
||||
"build" : "89d3241",
|
||||
"http_address" : "inet[/%s:9200]",
|
||||
"os" : {
|
||||
"refresh_interval_in_millis" : 1000,
|
||||
"available_processors" : 12,
|
||||
"cpu" : {
|
||||
"total_cores" : 24,
|
||||
"total_sockets" : 48,
|
||||
"cores_per_socket" : 2
|
||||
}
|
||||
},
|
||||
"process" : {
|
||||
"refresh_interval_in_millis" : 1000,
|
||||
"id" : 2039,
|
||||
"max_file_descriptors" : 65535,
|
||||
"mlockall" : false
|
||||
},
|
||||
"jvm" : {
|
||||
"version" : "1.7.0_65"
|
||||
},
|
||||
"network" : {
|
||||
"refresh_interval_in_millis" : 5000,
|
||||
"primary_interface" : {
|
||||
"address" : "%s",
|
||||
"name" : "eth0",
|
||||
"mac_address" : "08:01:c7:3F:15:DD"
|
||||
}
|
||||
},
|
||||
"transport" : {
|
||||
"bound_address" : "inet[/0:0:0:0:0:0:0:0:9300]",
|
||||
"publish_address" : "inet[/%s:9300]"
|
||||
},
|
||||
"http" : {
|
||||
"bound_address" : "inet[/0:0:0:0:0:0:0:0:9200]",
|
||||
"publish_address" : "inet[/%s:9200]",
|
||||
"max_content_length_in_bytes" : 104857600
|
||||
}}
|
||||
}
|
||||
}`, Conf.InstanceName, Conf.SensorIP, Conf.SpoofedVersion, Conf.SensorIP, Conf.SensorIP, Conf.SensorIP, Conf.SensorIP)
|
||||
WriteResponse(w, response)
|
||||
return
|
||||
}
|
||||
|
||||
// FakeSearch returns fake search results
|
||||
func FakeSearch(w http.ResponseWriter, r *http.Request) {
|
||||
printInfo(r)
|
||||
response := fmt.Sprintf(`
|
||||
{
|
||||
"took" : 6,
|
||||
"timed_out" : false,
|
||||
"_shards" : {
|
||||
"total" : 6,
|
||||
"successful" : 6,
|
||||
"failed" : 0
|
||||
},
|
||||
"hits" : {
|
||||
"total" : 1,
|
||||
"max_score" : 1.0,
|
||||
"hits" : [ {
|
||||
"_index" : ".kibana",
|
||||
"_type" : "index-pattern",
|
||||
"_id" : "logstash-*",
|
||||
"_score" : 1.0,
|
||||
"_source":{"title":"logstash-*","timeFieldName":"@timestamp","customFormats":"{}","fields":"[{\"type\":\"string\",\"indexed\":true,\"analyzed\":true,\"doc_values\":false,\"name\":\"host\",\"count\":0},{\"type\":\"string\",\"indexed\":false,\"analyzed\":false,\"name\":\"_source\",\"count\":0},{\"type\":\"string\",\"indexed\":true,\"analyzed\":false,\"doc_values\":false,\"name\":\"message.raw\",\"count\":0},{\"type\":\"string\",\"indexed\":false,\"analyzed\":false,\"name\":\"_index\",\"count\":0},{\"type\":\"string\",\"indexed\":true,\"analyzed\":false,\"doc_values\":false,\"name\":\"@version\",\"count\":0},{\"type\":\"string\",\"indexed\":true,\"analyzed\":true,\"doc_values\":false,\"name\":\"message\",\"count\":0},{\"type\":\"date\",\"indexed\":true,\"analyzed\":false,\"doc_values\":false,\"name\":\"@timestamp\",\"count\":0},{\"type\":\"string\",\"indexed\":true,\"analyzed\":false,\"name\":\"_type\",\"count\":0},{\"type\":\"string\",\"indexed\":true,\"analyzed\":false,\"name\":\"_id\",\"count\":0},{\"type\":\"string\",\"indexed\":true,\"analyzed\":false,\"doc_values\":false,\"name\":\"host.raw\",\"count\":0},{\"type\":\"geo_point\",\"indexed\":true,\"analyzed\":false,\"doc_values\":false,\"name\":\"geoip.location\",\"count\":0}]"}
|
||||
}]
|
||||
}
|
||||
}`)
|
||||
WriteResponse(w, response)
|
||||
return
|
||||
}
|
||||
|
||||
func WriteResponse(w http.ResponseWriter, d string) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.Write([]byte(d))
|
||||
return
|
||||
}
|
||||
|
||||
func printInfo(r *http.Request) {
|
||||
info := "URL:" + r.URL.String() + "&&Method:" + r.Method + "&&RemoteAddr:" + r.RemoteAddr
|
||||
|
||||
arr := strings.Split(r.RemoteAddr, ":")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("ES", "ES蜜罐", arr[0], info, "0")
|
||||
} else {
|
||||
go report.ReportEs("ES蜜罐", "本机", arr[0], info)
|
||||
}
|
||||
}
|
||||
|
||||
func Start(address string) {
|
||||
// Create the handlers
|
||||
http.HandleFunc("/", FakeBanner)
|
||||
http.HandleFunc("/_nodes", FakeNodes)
|
||||
http.HandleFunc("/_search", FakeSearch)
|
||||
|
||||
// Start the server
|
||||
http.ListenAndServe(address, nil)
|
||||
}
|
@ -3,28 +3,39 @@ package httpx
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/elazarl/goproxy"
|
||||
"net/url"
|
||||
"fmt"
|
||||
"strings"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
"HFish/core/report"
|
||||
)
|
||||
|
||||
/*http 正向代理*/
|
||||
func Start(address string) {
|
||||
proxy := goproxy.NewProxyHttpServer()
|
||||
|
||||
func Start(addr string, proxyUrl string) {
|
||||
var info string
|
||||
|
||||
gp := goproxy.NewProxyHttpServer()
|
||||
pu, err := url.Parse(proxyUrl)
|
||||
if err == nil {
|
||||
gp.Tr.Proxy = http.ProxyURL(&url.URL{
|
||||
Scheme: pu.Scheme,
|
||||
Host: pu.Host,
|
||||
})
|
||||
proxy.OnRequest().DoFunc(
|
||||
func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||
info = "URL:" + r.URL.String() + "&&Method:" + r.Method + "&&RemoteAddr:" + r.RemoteAddr
|
||||
|
||||
arr := strings.Split(r.RemoteAddr, ":")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("HTTP", "HTTP代理蜜罐", arr[0], info, "0")
|
||||
} else {
|
||||
go report.ReportHttp("HTTP代理蜜罐", "本机", arr[0], info)
|
||||
}
|
||||
|
||||
gp.OnRequest().HandleConnect(goproxy.AlwaysMitm)
|
||||
gp.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||
// Report Send
|
||||
fmt.Println(req.RemoteAddr)
|
||||
return req, nil
|
||||
return r, nil
|
||||
})
|
||||
http.ListenAndServe(addr, gp)
|
||||
|
||||
//proxy.OnResponse().DoFunc(
|
||||
// func(r *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
|
||||
// input, _ := ioutil.ReadAll(r.Body)
|
||||
// info += "Response Info&&||kon||&&Status:" + r.Status + "&&Body:" + string(input)
|
||||
// return r
|
||||
// })
|
||||
|
||||
http.ListenAndServe(address, proxy)
|
||||
}
|
||||
|
24
core/protocol/tftp/libs/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
8
core/protocol/tftp/libs/.travis.yml
Normal file
@ -0,0 +1,8 @@
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
before_install:
|
||||
- ulimit -n 4096
|
7
core/protocol/tftp/libs/CONTRIBUTORS
Normal file
@ -0,0 +1,7 @@
|
||||
Dmitri Popov
|
||||
Mojo Talantikite
|
||||
Giovanni Bajo
|
||||
Andrew Danforth
|
||||
Victor Lowther
|
||||
minghuadev on github.com
|
||||
Owen Mooney
|
21
core/protocol/tftp/libs/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2016 Dmitri Popov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
180
core/protocol/tftp/libs/README.md
Normal file
@ -0,0 +1,180 @@
|
||||
TFTP server and client library for Golang
|
||||
=========================================
|
||||
|
||||
[](https://godoc.org/github.com/pin/tftp)
|
||||
[](https://travis-ci.org/pin/tftp)
|
||||
|
||||
Implements:
|
||||
* [RFC 1350](https://tools.ietf.org/html/rfc1350) - The TFTP Protocol (Revision 2)
|
||||
* [RFC 2347](https://tools.ietf.org/html/rfc2347) - TFTP Option Extension
|
||||
* [RFC 2348](https://tools.ietf.org/html/rfc2348) - TFTP Blocksize Option
|
||||
|
||||
Partially implements (tsize server side only):
|
||||
* [RFC 2349](https://tools.ietf.org/html/rfc2349) - TFTP Timeout Interval and Transfer Size Options
|
||||
|
||||
Set of features is sufficient for PXE boot support.
|
||||
|
||||
``` go
|
||||
import "github.com/pin/tftp"
|
||||
```
|
||||
|
||||
The package is cohesive to Golang `io`. Particularly it implements
|
||||
`io.ReaderFrom` and `io.WriterTo` interfaces. That allows efficient data
|
||||
transmission without unnecessary memory copying and allocations.
|
||||
|
||||
|
||||
TFTP Server
|
||||
-----------
|
||||
|
||||
```go
|
||||
|
||||
// readHandler is called when client starts file download from server
|
||||
func readHandler(filename string, rf io.ReaderFrom) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return err
|
||||
}
|
||||
n, err := rf.ReadFrom(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d bytes sent\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeHandler is called when client starts file upload to server
|
||||
func writeHandler(filename string, wt io.WriterTo) error {
|
||||
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return err
|
||||
}
|
||||
n, err := wt.WriteTo(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d bytes received\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// use nil in place of handler to disable read or write operations
|
||||
s := tftp.NewServer(readHandler, writeHandler)
|
||||
s.SetTimeout(5 * time.Second) // optional
|
||||
err := s.ListenAndServe(":69") // blocks until s.Shutdown() is called
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "server: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
TFTP Client
|
||||
-----------
|
||||
Upload file to server:
|
||||
|
||||
```go
|
||||
c, err := tftp.NewClient("172.16.4.21:69")
|
||||
file, err := os.Open(path)
|
||||
c.SetTimeout(5 * time.Second) // optional
|
||||
rf, err := c.Send("foobar.txt", "octet")
|
||||
n, err := rf.ReadFrom(file)
|
||||
fmt.Printf("%d bytes sent\n", n)
|
||||
```
|
||||
|
||||
Download file from server:
|
||||
|
||||
```go
|
||||
c, err := tftp.NewClient("172.16.4.21:69")
|
||||
wt, err := c.Receive("foobar.txt", "octet")
|
||||
file, err := os.Create(path)
|
||||
// Optionally obtain transfer size before actual data.
|
||||
if n, ok := wt.(IncomingTransfer).Size(); ok {
|
||||
fmt.Printf("Transfer size: %d\n", n)
|
||||
}
|
||||
n, err := wt.WriteTo(file)
|
||||
fmt.Printf("%d bytes received\n", n)
|
||||
```
|
||||
|
||||
Note: please handle errors better :)
|
||||
|
||||
TSize option
|
||||
------------
|
||||
|
||||
PXE boot ROM often expects tsize option support from a server: client
|
||||
(e.g. computer that boots over the network) wants to know size of a
|
||||
download before the actual data comes. Server has to obtain stream
|
||||
size and send it to a client.
|
||||
|
||||
Often it will happen automatically because TFTP library tries to check
|
||||
if `io.Reader` provided to `ReadFrom` method also satisfies
|
||||
`io.Seeker` interface (`os.File` for instance) and uses `Seek` to
|
||||
determine file size.
|
||||
|
||||
In case `io.Reader` you provide to `ReadFrom` in read handler does not
|
||||
satisfy `io.Seeker` interface or you do not want TFTP library to call
|
||||
`Seek` on your reader but still want to respond with tsize option
|
||||
during outgoing request you can use an `OutgoingTransfer` interface:
|
||||
|
||||
```go
|
||||
|
||||
func readHandler(filename string, rf io.ReaderFrom) error {
|
||||
...
|
||||
// Set transfer size before calling ReadFrom.
|
||||
rf.(tftp.OutgoingTransfer).SetSize(myFileSize)
|
||||
...
|
||||
// ReadFrom ...
|
||||
|
||||
```
|
||||
|
||||
Similarly, it is possible to obtain size of a file that is about to be
|
||||
received using `IncomingTransfer` interface (see `Size` method).
|
||||
|
||||
Local and Remote Address
|
||||
------------------------
|
||||
|
||||
The `OutgoingTransfer` and `IncomingTransfer` interfaces also provide
|
||||
the `RemoteAddr` method which returns the peer IP address and port as
|
||||
a `net.UDPAddr`. The `RequestPacketInfo` interface provides a
|
||||
`LocalIP` method with returns the local IP address as a `net.IP` that
|
||||
the request is being handled on. These can be used for detailed
|
||||
logging in a server handler, among other things.
|
||||
|
||||
Note that LocalIP may return nil or an unspecified IP address
|
||||
if finding that is not supported on a particular operating system by
|
||||
the Go net libraries, or if you call it as a TFTP client.
|
||||
|
||||
```go
|
||||
|
||||
func readHandler(filename string, rf io.ReaderFrom) error {
|
||||
...
|
||||
raddr := rf.(tftp.OutgoingTransfer).RemoteAddr()
|
||||
laddr := rf.(tftp.RequestPacketInfo).LocalIP()
|
||||
log.Println("RRQ from", raddr.String(), "To ",laddr.String())
|
||||
log.Println("")
|
||||
...
|
||||
// ReadFrom ...
|
||||
```
|
||||
|
||||
Backoff
|
||||
-------
|
||||
|
||||
The default backoff before retransmitting an unacknowledged packet is a
|
||||
random duration between 0 and 1 second. This behavior can be overridden
|
||||
in clients and servers by providing a custom backoff calculation function.
|
||||
|
||||
```go
|
||||
s := tftp.NewServer(readHandler, writeHandler)
|
||||
s.SetBackoff(func (attempts int) time.Duration {
|
||||
return time.Duration(attempts) * time.Second
|
||||
})
|
||||
```
|
||||
|
||||
or, for no backoff
|
||||
|
||||
```go
|
||||
s.SetBackoff(func (int) time.Duration { return 0 })
|
||||
```
|
35
core/protocol/tftp/libs/backoff.go
Normal file
@ -0,0 +1,35 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 5 * time.Second
|
||||
defaultRetries = 5
|
||||
)
|
||||
|
||||
type backoffFunc func(int) time.Duration
|
||||
|
||||
type backoff struct {
|
||||
attempt int
|
||||
handler backoffFunc
|
||||
}
|
||||
|
||||
func (b *backoff) reset() {
|
||||
b.attempt = 0
|
||||
}
|
||||
|
||||
func (b *backoff) count() int {
|
||||
return b.attempt
|
||||
}
|
||||
|
||||
func (b *backoff) backoff() {
|
||||
if b.handler == nil {
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||
} else {
|
||||
time.Sleep(b.handler(b.attempt))
|
||||
}
|
||||
b.attempt++
|
||||
}
|
138
core/protocol/tftp/libs/client.go
Normal file
@ -0,0 +1,138 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewClient creates TFTP client for server on address provided.
|
||||
func NewClient(addr string) (*Client, error) {
|
||||
a, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving address %s: %v", addr, err)
|
||||
}
|
||||
return &Client{
|
||||
addr: a,
|
||||
timeout: defaultTimeout,
|
||||
retries: defaultRetries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetTimeout sets maximum time client waits for single network round-trip to succeed.
|
||||
// Default is 5 seconds.
|
||||
func (c *Client) SetTimeout(t time.Duration) {
|
||||
if t <= 0 {
|
||||
c.timeout = defaultTimeout
|
||||
}
|
||||
c.timeout = t
|
||||
}
|
||||
|
||||
// SetRetries sets maximum number of attempts client made to transmit a packet.
|
||||
// Default is 5 attempts.
|
||||
func (c *Client) SetRetries(count int) {
|
||||
if count < 1 {
|
||||
c.retries = defaultRetries
|
||||
}
|
||||
c.retries = count
|
||||
}
|
||||
|
||||
// SetBackoff sets a user provided function that is called to provide a
|
||||
// backoff duration prior to retransmitting an unacknowledged packet.
|
||||
func (c *Client) SetBackoff(h backoffFunc) {
|
||||
c.backoff = h
|
||||
}
|
||||
|
||||
// SetBlockSize sets a custom block size used in the transmission.
|
||||
func (c *Client) SetBlockSize(s int) {
|
||||
c.blksize = s
|
||||
}
|
||||
|
||||
// RequestTSize sets flag to indicate if tsize should be requested.
|
||||
func (c *Client) RequestTSize(s bool) {
|
||||
c.tsize = s
|
||||
}
|
||||
|
||||
// Client stores data about a single TFTP client
|
||||
type Client struct {
|
||||
addr *net.UDPAddr
|
||||
timeout time.Duration
|
||||
retries int
|
||||
backoff backoffFunc
|
||||
blksize int
|
||||
tsize bool
|
||||
}
|
||||
|
||||
// Send starts outgoing file transmission. It returns io.ReaderFrom or error.
|
||||
func (c Client) Send(filename string, mode string) (io.ReaderFrom, error) {
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &sender{
|
||||
send: make([]byte, datagramLength),
|
||||
receive: make([]byte, datagramLength),
|
||||
conn: &connConnection{conn: conn},
|
||||
retry: &backoff{handler: c.backoff},
|
||||
timeout: c.timeout,
|
||||
retries: c.retries,
|
||||
addr: c.addr,
|
||||
mode: mode,
|
||||
}
|
||||
if c.blksize != 0 {
|
||||
s.opts = make(options)
|
||||
s.opts["blksize"] = strconv.Itoa(c.blksize)
|
||||
}
|
||||
n := packRQ(s.send, opWRQ, filename, mode, s.opts)
|
||||
addr, err := s.sendWithRetry(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.addr = addr
|
||||
s.opts = nil
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Receive starts incoming file transmission. It returns io.WriterTo or error.
|
||||
func (c Client) Receive(filename string, mode string) (io.WriterTo, error) {
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.timeout == 0 {
|
||||
c.timeout = defaultTimeout
|
||||
}
|
||||
r := &receiver{
|
||||
send: make([]byte, datagramLength),
|
||||
receive: make([]byte, datagramLength),
|
||||
conn: &connConnection{conn: conn},
|
||||
retry: &backoff{handler: c.backoff},
|
||||
timeout: c.timeout,
|
||||
retries: c.retries,
|
||||
addr: c.addr,
|
||||
autoTerm: true,
|
||||
block: 1,
|
||||
mode: mode,
|
||||
}
|
||||
if c.blksize != 0 || c.tsize {
|
||||
r.opts = make(options)
|
||||
}
|
||||
if c.blksize != 0 {
|
||||
r.opts["blksize"] = strconv.Itoa(c.blksize)
|
||||
// Clean it up so we don't send options twice
|
||||
defer func() { delete(r.opts, "blksize") }()
|
||||
}
|
||||
if c.tsize {
|
||||
r.opts["tsize"] = "0"
|
||||
}
|
||||
n := packRQ(r.send, opRRQ, filename, mode, r.opts)
|
||||
l, addr, err := r.receiveWithRetry(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.l = l
|
||||
r.addr = addr
|
||||
return r, nil
|
||||
}
|
101
core/protocol/tftp/libs/connection.go
Normal file
@ -0,0 +1,101 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type connectionError struct {
|
||||
error
|
||||
timeout bool
|
||||
temporary bool
|
||||
}
|
||||
|
||||
func (t *connectionError) Timeout() bool {
|
||||
return t.timeout
|
||||
}
|
||||
|
||||
func (t *connectionError) Temporary() bool {
|
||||
return t.temporary
|
||||
}
|
||||
|
||||
type connection interface {
|
||||
sendTo([]byte, *net.UDPAddr) error
|
||||
readFrom([]byte) (int, *net.UDPAddr, error)
|
||||
setDeadline(time.Duration) error
|
||||
close()
|
||||
}
|
||||
|
||||
type connConnection struct {
|
||||
conn *net.UDPConn
|
||||
}
|
||||
|
||||
type chanConnection struct {
|
||||
sendConn *net.UDPConn
|
||||
channel chan []byte
|
||||
addr *net.UDPAddr
|
||||
timeout time.Duration
|
||||
complete chan string
|
||||
}
|
||||
|
||||
func (c *chanConnection) sendTo(data []byte, addr *net.UDPAddr) error {
|
||||
_, err := c.sendConn.WriteToUDP(data, addr)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *chanConnection) readFrom(buffer []byte) (int, *net.UDPAddr, error) {
|
||||
select {
|
||||
case data := <-c.channel:
|
||||
for i := range data {
|
||||
buffer[i] = data[i]
|
||||
}
|
||||
return len(data), c.addr, nil
|
||||
case <-time.After(c.timeout):
|
||||
return 0, nil, makeError(c.addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *chanConnection) setDeadline(deadline time.Duration) error {
|
||||
c.timeout = deadline
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chanConnection) close() {
|
||||
close(c.channel)
|
||||
c.complete <- c.addr.String()
|
||||
}
|
||||
|
||||
func (c *connConnection) sendTo(data []byte, addr *net.UDPAddr) error {
|
||||
_, err := c.conn.WriteToUDP(data, addr)
|
||||
return err
|
||||
}
|
||||
|
||||
func makeError(addr string) net.Error {
|
||||
error := connectionError{
|
||||
timeout: true,
|
||||
temporary: true,
|
||||
}
|
||||
error.error = fmt.Errorf("Channel timeout: %v", addr)
|
||||
return &error
|
||||
}
|
||||
|
||||
func (c *connConnection) readFrom(buffer []byte) (int, *net.UDPAddr, error) {
|
||||
n, addr, err := c.conn.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return n, addr, nil
|
||||
}
|
||||
|
||||
func (c *connConnection) setDeadline(deadline time.Duration) error {
|
||||
err := c.conn.SetReadDeadline(time.Now().Add(deadline))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *connConnection) close() {
|
||||
c.conn.Close()
|
||||
}
|
108
core/protocol/tftp/libs/netascii/netascii.go
Normal file
@ -0,0 +1,108 @@
|
||||
package netascii
|
||||
|
||||
// TODO: make it work not only on linux
|
||||
|
||||
import "io"
|
||||
|
||||
const (
|
||||
CR = '\x0d'
|
||||
LF = '\x0a'
|
||||
NUL = '\x00'
|
||||
)
|
||||
|
||||
func ToReader(r io.Reader) io.Reader {
|
||||
return &toReader{
|
||||
r: r,
|
||||
buf: make([]byte, 256),
|
||||
}
|
||||
}
|
||||
|
||||
type toReader struct {
|
||||
r io.Reader
|
||||
buf []byte
|
||||
n int
|
||||
i int
|
||||
err error
|
||||
lf bool
|
||||
nul bool
|
||||
}
|
||||
|
||||
func (r *toReader) Read(p []byte) (int, error) {
|
||||
var n int
|
||||
for n < len(p) {
|
||||
if r.lf {
|
||||
p[n] = LF
|
||||
n++
|
||||
r.lf = false
|
||||
continue
|
||||
}
|
||||
if r.nul {
|
||||
p[n] = NUL
|
||||
n++
|
||||
r.nul = false
|
||||
continue
|
||||
}
|
||||
if r.i < r.n {
|
||||
if r.buf[r.i] == LF {
|
||||
p[n] = CR
|
||||
r.lf = true
|
||||
} else if r.buf[r.i] == CR {
|
||||
p[n] = CR
|
||||
r.nul = true
|
||||
|
||||
} else {
|
||||
p[n] = r.buf[r.i]
|
||||
}
|
||||
r.i++
|
||||
n++
|
||||
continue
|
||||
}
|
||||
if r.err == nil {
|
||||
r.n, r.err = r.r.Read(r.buf)
|
||||
r.i = 0
|
||||
} else {
|
||||
return n, r.err
|
||||
}
|
||||
}
|
||||
return n, r.err
|
||||
}
|
||||
|
||||
type fromWriter struct {
|
||||
w io.Writer
|
||||
buf []byte
|
||||
i int
|
||||
cr bool
|
||||
}
|
||||
|
||||
func FromWriter(w io.Writer) io.Writer {
|
||||
return &fromWriter{
|
||||
w: w,
|
||||
buf: make([]byte, 256),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fromWriter) Write(p []byte) (n int, err error) {
|
||||
for n < len(p) {
|
||||
if w.cr {
|
||||
if p[n] == LF {
|
||||
w.buf[w.i] = LF
|
||||
}
|
||||
if p[n] == NUL {
|
||||
w.buf[w.i] = CR
|
||||
}
|
||||
w.cr = false
|
||||
w.i++
|
||||
} else if p[n] == CR {
|
||||
w.cr = true
|
||||
} else {
|
||||
w.buf[w.i] = p[n]
|
||||
w.i++
|
||||
}
|
||||
n++
|
||||
if w.i == len(w.buf) || n == len(p) {
|
||||
_, err = w.w.Write(w.buf[:w.i])
|
||||
w.i = 0
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
89
core/protocol/tftp/libs/netascii/netascii_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
package netascii
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
)
|
||||
|
||||
var basic = map[string]string{
|
||||
"\r": "\r\x00",
|
||||
"\n": "\r\n",
|
||||
"la\nbu": "la\r\nbu",
|
||||
"la\rbu": "la\r\x00bu",
|
||||
"\r\r\r": "\r\x00\r\x00\r\x00",
|
||||
"\n\n\n": "\r\n\r\n\r\n",
|
||||
}
|
||||
|
||||
func TestTo(t *testing.T) {
|
||||
for text, netascii := range basic {
|
||||
to := ToReader(strings.NewReader(text))
|
||||
n, _ := ioutil.ReadAll(to)
|
||||
if bytes.Compare(n, []byte(netascii)) != 0 {
|
||||
t.Errorf("%q to netascii: %q != %q", text, n, netascii)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFrom(t *testing.T) {
|
||||
for text, netascii := range basic {
|
||||
r := bytes.NewReader([]byte(netascii))
|
||||
b := &bytes.Buffer{}
|
||||
from := FromWriter(b)
|
||||
r.WriteTo(from)
|
||||
n, _ := ioutil.ReadAll(b)
|
||||
if string(n) != text {
|
||||
t.Errorf("%q from netascii: %q != %q", netascii, n, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const text = `
|
||||
Therefore, the sequence "CR LF" must be treated as a single "new
|
||||
line" character and used whenever their combined action is
|
||||
intended; the sequence "CR NUL" must be used where a carriage
|
||||
return alone is actually desired; and the CR character must be
|
||||
avoided in other contexts. This rule gives assurance to systems
|
||||
which must decide whether to perform a "new line" function or a
|
||||
multiple-backspace that the TELNET stream contains a character
|
||||
following a CR that will allow a rational decision.
|
||||
(in the default ASCII mode), to preserve the symmetry of the
|
||||
NVT model. Even though it may be known in some situations
|
||||
(e.g., with remote echo and suppress go ahead options in
|
||||
effect) that characters are not being sent to an actual
|
||||
printer, nonetheless, for the sake of consistency, the protocol
|
||||
requires that a NUL be inserted following a CR not followed by
|
||||
a LF in the data stream. The converse of this is that a NUL
|
||||
received in the data stream after a CR (in the absence of
|
||||
options negotiations which explicitly specify otherwise) should
|
||||
be stripped out prior to applying the NVT to local character
|
||||
set mapping.
|
||||
`
|
||||
|
||||
func TestWriteRead(t *testing.T) {
|
||||
var one bytes.Buffer
|
||||
to := ToReader(strings.NewReader(text))
|
||||
one.ReadFrom(to)
|
||||
two := &bytes.Buffer{}
|
||||
from := FromWriter(two)
|
||||
one.WriteTo(from)
|
||||
text2, _ := ioutil.ReadAll(two)
|
||||
if text != string(text2) {
|
||||
t.Errorf("text mismatch \n%x \n%x", text, text2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOneByte(t *testing.T) {
|
||||
var one bytes.Buffer
|
||||
to := iotest.OneByteReader(ToReader(strings.NewReader(text)))
|
||||
one.ReadFrom(to)
|
||||
two := &bytes.Buffer{}
|
||||
from := FromWriter(two)
|
||||
one.WriteTo(from)
|
||||
text2, _ := ioutil.ReadAll(two)
|
||||
if text != string(text2) {
|
||||
t.Errorf("text mismatch \n%x \n%x", text, text2)
|
||||
}
|
||||
}
|
190
core/protocol/tftp/libs/packet.go
Normal file
@ -0,0 +1,190 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
opRRQ = uint16(1) // Read request (RRQ)
|
||||
opWRQ = uint16(2) // Write request (WRQ)
|
||||
opDATA = uint16(3) // Data
|
||||
opACK = uint16(4) // Acknowledgement
|
||||
opERROR = uint16(5) // Error
|
||||
opOACK = uint16(6) // Options Acknowledgment
|
||||
)
|
||||
|
||||
const (
|
||||
blockLength = 512
|
||||
datagramLength = 516
|
||||
)
|
||||
|
||||
type options map[string]string
|
||||
|
||||
// RRQ/WRQ packet
|
||||
//
|
||||
// 2 bytes string 1 byte string 1 byte
|
||||
// --------------------------------------------------
|
||||
// | Opcode | Filename | 0 | Mode | 0 |
|
||||
// --------------------------------------------------
|
||||
type pRRQ []byte
|
||||
type pWRQ []byte
|
||||
|
||||
// packRQ returns length of the packet in b
|
||||
func packRQ(p []byte, op uint16, filename, mode string, opts options) int {
|
||||
binary.BigEndian.PutUint16(p, op)
|
||||
n := 2
|
||||
n += copy(p[2:len(p)-10], filename)
|
||||
p[n] = 0
|
||||
n++
|
||||
n += copy(p[n:], mode)
|
||||
p[n] = 0
|
||||
n++
|
||||
for name, value := range opts {
|
||||
n += copy(p[n:], name)
|
||||
p[n] = 0
|
||||
n++
|
||||
n += copy(p[n:], value)
|
||||
p[n] = 0
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func unpackRQ(p []byte) (filename, mode string, opts options, err error) {
|
||||
bs := bytes.Split(p[2:], []byte{0})
|
||||
if len(bs) < 2 {
|
||||
return "", "", nil, fmt.Errorf("missing filename or mode")
|
||||
}
|
||||
filename = string(bs[0])
|
||||
mode = string(bs[1])
|
||||
if len(bs) < 4 {
|
||||
return filename, mode, nil, nil
|
||||
}
|
||||
opts = make(options)
|
||||
for i := 2; i+1 < len(bs); i += 2 {
|
||||
opts[string(bs[i])] = string(bs[i+1])
|
||||
}
|
||||
return filename, mode, opts, nil
|
||||
}
|
||||
|
||||
// OACK packet
|
||||
//
|
||||
// +----------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
|
||||
// | Opcode | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 |
|
||||
// +----------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
|
||||
type pOACK []byte
|
||||
|
||||
func packOACK(p []byte, opts options) int {
|
||||
binary.BigEndian.PutUint16(p, opOACK)
|
||||
n := 2
|
||||
for name, value := range opts {
|
||||
n += copy(p[n:], name)
|
||||
p[n] = 0
|
||||
n++
|
||||
n += copy(p[n:], value)
|
||||
p[n] = 0
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func unpackOACK(p []byte) (opts options, err error) {
|
||||
bs := bytes.Split(p[2:], []byte{0})
|
||||
opts = make(options)
|
||||
for i := 0; i+1 < len(bs); i += 2 {
|
||||
opts[string(bs[i])] = string(bs[i+1])
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// ERROR packet
|
||||
//
|
||||
// 2 bytes 2 bytes string 1 byte
|
||||
// ------------------------------------------
|
||||
// | Opcode | ErrorCode | ErrMsg | 0 |
|
||||
// ------------------------------------------
|
||||
type pERROR []byte
|
||||
|
||||
func packERROR(p []byte, code uint16, message string) int {
|
||||
binary.BigEndian.PutUint16(p, opERROR)
|
||||
binary.BigEndian.PutUint16(p[2:], code)
|
||||
n := copy(p[4:len(p)-2], message)
|
||||
p[4+n] = 0
|
||||
return n + 5
|
||||
}
|
||||
|
||||
func (p pERROR) code() uint16 {
|
||||
return binary.BigEndian.Uint16(p[2:])
|
||||
}
|
||||
|
||||
func (p pERROR) message() string {
|
||||
return string(p[4:])
|
||||
}
|
||||
|
||||
// DATA packet
|
||||
//
|
||||
// 2 bytes 2 bytes n bytes
|
||||
// ----------------------------------
|
||||
// | Opcode | Block # | Data |
|
||||
// ----------------------------------
|
||||
type pDATA []byte
|
||||
|
||||
func (p pDATA) block() uint16 {
|
||||
return binary.BigEndian.Uint16(p[2:])
|
||||
}
|
||||
|
||||
// ACK packet
|
||||
//
|
||||
// 2 bytes 2 bytes
|
||||
// -----------------------
|
||||
// | Opcode | Block # |
|
||||
// -----------------------
|
||||
type pACK []byte
|
||||
|
||||
func (p pACK) block() uint16 {
|
||||
return binary.BigEndian.Uint16(p[2:])
|
||||
}
|
||||
|
||||
func parsePacket(p []byte) (interface{}, error) {
|
||||
l := len(p)
|
||||
if l < 2 {
|
||||
return nil, fmt.Errorf("short packet")
|
||||
}
|
||||
opcode := binary.BigEndian.Uint16(p)
|
||||
switch opcode {
|
||||
case opRRQ:
|
||||
if l < 4 {
|
||||
return nil, fmt.Errorf("short RRQ packet: %d", l)
|
||||
}
|
||||
return pRRQ(p), nil
|
||||
case opWRQ:
|
||||
if l < 4 {
|
||||
return nil, fmt.Errorf("short WRQ packet: %d", l)
|
||||
}
|
||||
return pWRQ(p), nil
|
||||
case opDATA:
|
||||
if l < 4 {
|
||||
return nil, fmt.Errorf("short DATA packet: %d", l)
|
||||
}
|
||||
return pDATA(p), nil
|
||||
case opACK:
|
||||
if l < 4 {
|
||||
return nil, fmt.Errorf("short ACK packet: %d", l)
|
||||
}
|
||||
return pACK(p), nil
|
||||
case opERROR:
|
||||
if l < 5 {
|
||||
return nil, fmt.Errorf("short ERROR packet: %d", l)
|
||||
}
|
||||
return pERROR(p), nil
|
||||
case opOACK:
|
||||
if l < 6 {
|
||||
return nil, fmt.Errorf("short OACK packet: %d", l)
|
||||
}
|
||||
return pOACK(p), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown opcode: %d", opcode)
|
||||
}
|
||||
}
|
273
core/protocol/tftp/libs/receiver.go
Normal file
@ -0,0 +1,273 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pin/tftp/netascii"
|
||||
)
|
||||
|
||||
// IncomingTransfer provides methods that expose information associated with
|
||||
// an incoming transfer.
|
||||
type IncomingTransfer interface {
|
||||
// Size returns the size of an incoming file if the request included the
|
||||
// tsize option (see RFC2349). To differentiate a zero-sized file transfer
|
||||
// from a request without tsize use the second boolean "ok" return value.
|
||||
Size() (n int64, ok bool)
|
||||
|
||||
// RemoteAddr returns the remote peer's IP address and port.
|
||||
RemoteAddr() net.UDPAddr
|
||||
}
|
||||
|
||||
func (r *receiver) RemoteAddr() net.UDPAddr { return *r.addr }
|
||||
func (r *receiver) LocalIP() net.IP { return r.localIP }
|
||||
|
||||
func (r *receiver) Size() (n int64, ok bool) {
|
||||
if r.opts != nil {
|
||||
if s, ok := r.opts["tsize"]; ok {
|
||||
n, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type receiver struct {
|
||||
send []byte
|
||||
receive []byte
|
||||
addr *net.UDPAddr
|
||||
filename string
|
||||
localIP net.IP
|
||||
tid int
|
||||
conn connection
|
||||
block uint16
|
||||
retry *backoff
|
||||
timeout time.Duration
|
||||
retries int
|
||||
l int
|
||||
autoTerm bool
|
||||
dally bool
|
||||
mode string
|
||||
opts options
|
||||
singlePort bool
|
||||
maxBlockLen int
|
||||
hook Hook
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func (r *receiver) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if r.mode == "netascii" {
|
||||
w = netascii.FromWriter(w)
|
||||
}
|
||||
if r.opts != nil {
|
||||
if r.singlePort {
|
||||
// We can only support one incoming blocksize in single port mode
|
||||
if _, ok := r.opts["blksize"]; ok {
|
||||
r.opts["blksize"] = strconv.Itoa(blockLength)
|
||||
}
|
||||
}
|
||||
err := r.sendOptions()
|
||||
if err != nil {
|
||||
r.abort(err)
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
binary.BigEndian.PutUint16(r.send[0:2], opACK)
|
||||
for {
|
||||
if r.l > 0 {
|
||||
l, err := w.Write(r.receive[4:r.l])
|
||||
n += int64(l)
|
||||
if err != nil {
|
||||
r.abort(err)
|
||||
return n, err
|
||||
}
|
||||
if r.l < len(r.receive) {
|
||||
if r.autoTerm {
|
||||
r.terminate()
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
binary.BigEndian.PutUint16(r.send[2:4], r.block)
|
||||
r.block++ // send ACK for current block and expect next one
|
||||
ll, _, err := r.receiveWithRetry(4)
|
||||
if err != nil {
|
||||
r.abort(err)
|
||||
return n, err
|
||||
}
|
||||
r.l = ll
|
||||
}
|
||||
}
|
||||
|
||||
func (r *receiver) sendOptions() error {
|
||||
for name, value := range r.opts {
|
||||
if name == "blksize" {
|
||||
err := r.setBlockSize(value)
|
||||
if err != nil {
|
||||
delete(r.opts, name)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
delete(r.opts, name)
|
||||
}
|
||||
}
|
||||
if len(r.opts) > 0 {
|
||||
m := packOACK(r.send, r.opts)
|
||||
r.block = 1 // expect data block number 1
|
||||
ll, _, err := r.receiveWithRetry(m)
|
||||
if err != nil {
|
||||
r.abort(err)
|
||||
return err
|
||||
}
|
||||
r.l = ll
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *receiver) setBlockSize(blksize string) error {
|
||||
n, err := strconv.Atoi(blksize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < 512 {
|
||||
return fmt.Errorf("blksize too small: %d", n)
|
||||
}
|
||||
if n > 65464 {
|
||||
return fmt.Errorf("blksize too large: %d", n)
|
||||
}
|
||||
if r.maxBlockLen > 0 && n > r.maxBlockLen {
|
||||
n = r.maxBlockLen
|
||||
r.opts["blksize"] = strconv.Itoa(n)
|
||||
}
|
||||
r.receive = make([]byte, n+4)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *receiver) receiveWithRetry(l int) (int, *net.UDPAddr, error) {
|
||||
r.retry.reset()
|
||||
for {
|
||||
n, addr, err := r.receiveDatagram(l)
|
||||
if _, ok := err.(net.Error); ok && r.retry.count() < r.retries {
|
||||
r.retry.backoff()
|
||||
continue
|
||||
}
|
||||
return n, addr, err
|
||||
}
|
||||
}
|
||||
|
||||
func (r *receiver) receiveDatagram(l int) (int, *net.UDPAddr, error) {
|
||||
err := r.conn.setDeadline(r.timeout)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
err = r.conn.sendTo(r.send[:l], r.addr)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for {
|
||||
c, addr, err := r.conn.readFrom(r.receive)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if !addr.IP.Equal(r.addr.IP) || (r.tid != 0 && addr.Port != r.tid) {
|
||||
continue
|
||||
}
|
||||
p, err := parsePacket(r.receive[:c])
|
||||
if err != nil {
|
||||
return 0, addr, err
|
||||
}
|
||||
r.tid = addr.Port
|
||||
switch p := p.(type) {
|
||||
case pDATA:
|
||||
if p.block() == r.block {
|
||||
return c, addr, nil
|
||||
}
|
||||
case pOACK:
|
||||
opts, err := unpackOACK(p)
|
||||
if r.block != 1 {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
r.abort(err)
|
||||
return 0, addr, err
|
||||
}
|
||||
for name, value := range opts {
|
||||
if name == "blksize" {
|
||||
err := r.setBlockSize(value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
r.block = 0 // ACK with block number 0
|
||||
r.opts = opts
|
||||
return 0, addr, nil
|
||||
case pERROR:
|
||||
return 0, addr, fmt.Errorf("code: %d, message: %s",
|
||||
p.code(), p.message())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *receiver) terminate() error {
|
||||
if r.conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
if r.hook != nil {
|
||||
r.hook.OnSuccess(r.buildTransferStats())
|
||||
}
|
||||
r.conn.close()
|
||||
}()
|
||||
binary.BigEndian.PutUint16(r.send[2:4], r.block)
|
||||
if r.dally {
|
||||
for i := 0; i < 3; i++ {
|
||||
_, _, err := r.receiveDatagram(4)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("dallying termination failed")
|
||||
}
|
||||
err := r.conn.sendTo(r.send[:4], r.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *receiver) buildTransferStats() TransferStats {
|
||||
return TransferStats{
|
||||
RemoteAddr: r.addr.IP,
|
||||
Filename: r.filename,
|
||||
Tid: r.tid,
|
||||
TotalBlocks: r.block,
|
||||
Mode: r.mode,
|
||||
Opts: r.opts,
|
||||
Duration: time.Now().Sub(r.startTime),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *receiver) abort(err error) error {
|
||||
if r.conn == nil {
|
||||
return nil
|
||||
}
|
||||
if r.hook != nil {
|
||||
r.hook.OnFailure(r.buildTransferStats(), err)
|
||||
}
|
||||
n := packERROR(r.send, 1, err.Error())
|
||||
err = r.conn.sendTo(r.send[:n], r.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.conn.close()
|
||||
r.conn = nil
|
||||
return nil
|
||||
}
|
283
core/protocol/tftp/libs/sender.go
Normal file
@ -0,0 +1,283 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pin/tftp/netascii"
|
||||
)
|
||||
|
||||
// OutgoingTransfer provides methods to set the outgoing transfer size and
|
||||
// retrieve the remote address of the peer.
|
||||
type OutgoingTransfer interface {
|
||||
// SetSize is used to set the outgoing transfer size (tsize option: RFC2349)
|
||||
// manually in a server write transfer handler.
|
||||
//
|
||||
// It is not necessary in most cases; when the io.Reader provided to
|
||||
// ReadFrom also satisfies io.Seeker (e.g. os.File) the transfer size will
|
||||
// be determined automatically. Seek will not be attempted when the
|
||||
// transfer size option is set with SetSize.
|
||||
//
|
||||
// The value provided will be used only if SetSize is called before ReadFrom
|
||||
// and only on in a server read handler.
|
||||
SetSize(n int64)
|
||||
|
||||
// RemoteAddr returns the remote peer's IP address and port.
|
||||
RemoteAddr() net.UDPAddr
|
||||
}
|
||||
|
||||
type sender struct {
|
||||
conn connection
|
||||
addr *net.UDPAddr
|
||||
filename string
|
||||
localIP net.IP
|
||||
tid int
|
||||
send []byte
|
||||
sendA senderAnticipate
|
||||
receive []byte
|
||||
retry *backoff
|
||||
timeout time.Duration
|
||||
retries int
|
||||
block uint16
|
||||
maxBlockLen int
|
||||
mode string
|
||||
opts options
|
||||
hook Hook
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func (s *sender) RemoteAddr() net.UDPAddr { return *s.addr }
|
||||
func (s *sender) LocalIP() net.IP { return s.localIP }
|
||||
|
||||
func (s *sender) SetSize(n int64) {
|
||||
if s.opts != nil {
|
||||
if _, ok := s.opts["tsize"]; ok {
|
||||
s.opts["tsize"] = strconv.FormatInt(n, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sender) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
if s.mode == "netascii" {
|
||||
r = netascii.ToReader(r)
|
||||
}
|
||||
if s.opts != nil {
|
||||
// check that tsize is set
|
||||
if ts, ok := s.opts["tsize"]; ok {
|
||||
// check that tsize is not set with SetSize already
|
||||
i, err := strconv.ParseInt(ts, 10, 64)
|
||||
if err == nil && i == 0 {
|
||||
if rs, ok := r.(io.Seeker); ok {
|
||||
pos, err := rs.Seek(0, 1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size, err := rs.Seek(0, 2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
s.opts["tsize"] = strconv.FormatInt(size, 10)
|
||||
_, err = rs.Seek(pos, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
err = s.sendOptions()
|
||||
if err != nil {
|
||||
s.abort(err)
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if s.sendA.enabled { /* senderAnticipate */
|
||||
return readFromAnticipate(s, r)
|
||||
}
|
||||
s.block = 1 // start data transmission with block 1
|
||||
binary.BigEndian.PutUint16(s.send[0:2], opDATA)
|
||||
for {
|
||||
l, err := io.ReadFull(r, s.send[4:])
|
||||
n += int64(l)
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
if err == io.EOF {
|
||||
binary.BigEndian.PutUint16(s.send[2:4], s.block)
|
||||
_, err = s.sendWithRetry(4)
|
||||
if err != nil {
|
||||
s.abort(err)
|
||||
return n, err
|
||||
}
|
||||
if s.hook != nil {
|
||||
s.hook.OnSuccess(s.buildTransferStats())
|
||||
}
|
||||
s.conn.close()
|
||||
return n, nil
|
||||
}
|
||||
s.abort(err)
|
||||
return n, err
|
||||
}
|
||||
binary.BigEndian.PutUint16(s.send[2:4], s.block)
|
||||
_, err = s.sendWithRetry(4 + l)
|
||||
if err != nil {
|
||||
s.abort(err)
|
||||
return n, err
|
||||
}
|
||||
if l < len(s.send)-4 {
|
||||
if s.hook != nil {
|
||||
s.hook.OnSuccess(s.buildTransferStats())
|
||||
}
|
||||
s.conn.close()
|
||||
return n, nil
|
||||
}
|
||||
s.block++
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sender) sendOptions() error {
|
||||
for name, value := range s.opts {
|
||||
if name == "blksize" {
|
||||
err := s.setBlockSize(value)
|
||||
if err != nil {
|
||||
delete(s.opts, name)
|
||||
continue
|
||||
}
|
||||
} else if name == "tsize" {
|
||||
if value != "0" {
|
||||
s.opts["tsize"] = value
|
||||
} else {
|
||||
delete(s.opts, name)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
delete(s.opts, name)
|
||||
}
|
||||
}
|
||||
if len(s.opts) > 0 {
|
||||
m := packOACK(s.send, s.opts)
|
||||
_, err := s.sendWithRetry(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sender) setBlockSize(blksize string) error {
|
||||
n, err := strconv.Atoi(blksize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < 512 {
|
||||
return fmt.Errorf("blksize too small: %d", n)
|
||||
}
|
||||
if n > 65464 {
|
||||
return fmt.Errorf("blksize too large: %d", n)
|
||||
}
|
||||
if s.maxBlockLen > 0 && n > s.maxBlockLen {
|
||||
n = s.maxBlockLen
|
||||
s.opts["blksize"] = strconv.Itoa(n)
|
||||
}
|
||||
s.send = make([]byte, n+4)
|
||||
if s.sendA.enabled { /* senderAnticipate */
|
||||
sendAInit(&s.sendA, uint(n+4), s.sendA.winsz)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sender) sendWithRetry(l int) (*net.UDPAddr, error) {
|
||||
s.retry.reset()
|
||||
for {
|
||||
addr, err := s.sendDatagram(l)
|
||||
if _, ok := err.(net.Error); ok && s.retry.count() < s.retries {
|
||||
s.retry.backoff()
|
||||
continue
|
||||
}
|
||||
return addr, err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sender) sendDatagram(l int) (*net.UDPAddr, error) {
|
||||
err := s.conn.setDeadline(s.timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.conn.sendTo(s.send[:l], s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
n, addr, err := s.conn.readFrom(s.receive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !addr.IP.Equal(s.addr.IP) || (s.tid != 0 && addr.Port != s.tid) {
|
||||
continue
|
||||
}
|
||||
p, err := parsePacket(s.receive[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.tid = addr.Port
|
||||
switch p := p.(type) {
|
||||
case pACK:
|
||||
if p.block() == s.block {
|
||||
return addr, nil
|
||||
}
|
||||
case pOACK:
|
||||
opts, err := unpackOACK(p)
|
||||
if s.block != 0 {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
s.abort(err)
|
||||
return addr, err
|
||||
}
|
||||
for name, value := range opts {
|
||||
if name == "blksize" {
|
||||
err := s.setBlockSize(value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return addr, nil
|
||||
case pERROR:
|
||||
return nil, fmt.Errorf("sending block %d: code=%d, error: %s",
|
||||
s.block, p.code(), p.message())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sender) buildTransferStats() TransferStats {
|
||||
return TransferStats{
|
||||
RemoteAddr: s.addr.IP,
|
||||
Filename: s.filename,
|
||||
Tid: s.tid,
|
||||
SenderAnticipateEnabled: s.sendA.enabled,
|
||||
TotalBlocks: s.block,
|
||||
Mode: s.mode,
|
||||
Opts: s.opts,
|
||||
Duration: time.Now().Sub(s.startTime),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sender) abort(err error) error {
|
||||
if s.conn == nil {
|
||||
return nil
|
||||
}
|
||||
if s.hook != nil {
|
||||
s.hook.OnFailure(s.buildTransferStats(), err)
|
||||
}
|
||||
n := packERROR(s.send, 1, err.Error())
|
||||
err = s.conn.sendTo(s.send[:n], s.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.conn.close()
|
||||
s.conn = nil
|
||||
return nil
|
||||
}
|
197
core/protocol/tftp/libs/sender_anticipate.go
Normal file
@ -0,0 +1,197 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// the struct embedded into sender{} as sendA
|
||||
type senderAnticipate struct {
|
||||
enabled bool
|
||||
winsz uint /* init windows size in number of buffers */
|
||||
num uint /* actual packets to send. */
|
||||
sends [][]byte /* buffers for a number of packets */
|
||||
sendslens []uint /* data lens in buffers */
|
||||
}
|
||||
|
||||
const anticipateWindowDefMax = 60 /* 60 by 512 is about 30k */
|
||||
const anticipateDebug bool = false
|
||||
|
||||
func sendAInit(sA *senderAnticipate, ln uint, winSz uint) {
|
||||
var ksz uint
|
||||
if winSz > anticipateWindowDefMax {
|
||||
ksz = anticipateWindowDefMax
|
||||
} else if winSz < 2 {
|
||||
ksz = 2
|
||||
} else {
|
||||
ksz = winSz
|
||||
}
|
||||
sA.sends = make([][]byte, ksz)
|
||||
sA.sendslens = make([]uint, ksz)
|
||||
for k := uint(0); k < ksz; k++ {
|
||||
sA.sends[k] = make([]byte, ln)
|
||||
sA.sendslens[k] = 0
|
||||
}
|
||||
sA.winsz = ksz
|
||||
//fmt.Printf(" Set packet buffer size %v\n", ln)
|
||||
}
|
||||
|
||||
// derived from ReadFrom()
|
||||
func readFromAnticipate(s *sender, r io.Reader) (n int64, err error) {
|
||||
s.block = 1 // start data transmission with block 1
|
||||
ksz := uint(len(s.sendA.sends))
|
||||
for k := uint(0); k < ksz; k++ {
|
||||
binary.BigEndian.PutUint16(s.sendA.sends[k][0:2], opDATA)
|
||||
s.sendA.sendslens[k] = 0
|
||||
}
|
||||
s.sendA.num = 0
|
||||
for {
|
||||
nx := int64(0)
|
||||
knum := uint(0)
|
||||
kfillOk := true /* default ok */
|
||||
kfillPartial := false
|
||||
for k := uint(0); k < ksz; k++ {
|
||||
lx, err := io.ReadFull(r, s.sendA.sends[k][4:])
|
||||
nx += int64(lx)
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
if err == io.EOF {
|
||||
if kfillPartial {
|
||||
break /* short packet already sent in last loop */
|
||||
}
|
||||
binary.BigEndian.PutUint16(s.sendA.sends[k][2:4],
|
||||
s.block+uint16(k))
|
||||
s.sendA.sendslens[k] = 4
|
||||
knum = k + 1
|
||||
kfillPartial = true
|
||||
break
|
||||
}
|
||||
kfillOk = false
|
||||
break /* fail */
|
||||
} else if err != nil /* has to be io.ErrUnexpectedEOF now */ {
|
||||
kfillPartial = true /* set the flag and send the packet */
|
||||
}
|
||||
binary.BigEndian.PutUint16(s.sendA.sends[k][2:4],
|
||||
s.block+uint16(k))
|
||||
s.sendA.sendslens[k] = uint(4 + lx)
|
||||
knum = k + 1
|
||||
}
|
||||
if !kfillOk {
|
||||
s.abort(err)
|
||||
return n, err
|
||||
}
|
||||
s.sendA.num = knum
|
||||
n += int64(nx)
|
||||
if anticipateDebug {
|
||||
fmt.Printf(" **** sends s.block %v pkts %v ", s.block, knum)
|
||||
for k := uint(0); k < ksz; k++ {
|
||||
fmt.Printf(" %v ", s.sendA.sendslens[k])
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
_, err = s.sendWithRetryAnticipate()
|
||||
if err != nil {
|
||||
s.abort(err)
|
||||
return n, err
|
||||
}
|
||||
if kfillPartial {
|
||||
s.conn.close()
|
||||
return n, nil
|
||||
}
|
||||
s.block += uint16(knum)
|
||||
}
|
||||
}
|
||||
|
||||
// derived from sendWithRetry()
|
||||
func (s *sender) sendWithRetryAnticipate() (*net.UDPAddr, error) {
|
||||
s.retry.reset()
|
||||
for {
|
||||
addr, err := s.sendDatagramAnticipate()
|
||||
if _, ok := err.(net.Error); ok && s.retry.count() < s.retries {
|
||||
s.retry.backoff()
|
||||
continue
|
||||
}
|
||||
return addr, err
|
||||
}
|
||||
}
|
||||
|
||||
// derived from sendDatagram()
|
||||
func (s *sender) sendDatagramAnticipate() (*net.UDPAddr, error) {
|
||||
err1 := s.conn.setDeadline(s.timeout)
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
}
|
||||
var err error
|
||||
ksz := uint(len(s.sendA.sends))
|
||||
knum := s.sendA.num
|
||||
if knum > ksz {
|
||||
err = fmt.Errorf("knum %v bigger than ksz %v", knum, ksz)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k := uint(0); k < knum; k++ {
|
||||
lx := s.sendA.sendslens[k]
|
||||
if lx < 4 {
|
||||
err = fmt.Errorf("lx smaller than 4")
|
||||
break
|
||||
}
|
||||
errx := s.conn.sendTo(s.sendA.sends[k][:lx], s.addr)
|
||||
if errx != nil {
|
||||
err = fmt.Errorf("k %v errx %v", k, errx.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k := uint(0)
|
||||
for {
|
||||
n, addr, err := s.conn.readFrom(s.receive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !addr.IP.Equal(s.addr.IP) || (s.tid != 0 && addr.Port != s.tid) {
|
||||
continue
|
||||
}
|
||||
p, err := parsePacket(s.receive[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.tid = addr.Port
|
||||
switch p := p.(type) {
|
||||
case pACK:
|
||||
if anticipateDebug {
|
||||
fmt.Printf(" **** pACK p.block %v s.block %v k %v\n",
|
||||
p.block(), s.block, k)
|
||||
}
|
||||
if p.block() == s.block+uint16(k) {
|
||||
k++
|
||||
if k == knum {
|
||||
return addr, nil
|
||||
}
|
||||
}
|
||||
case pOACK:
|
||||
opts, err := unpackOACK(p)
|
||||
if s.block != 0 {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
s.abort(err)
|
||||
return addr, err
|
||||
}
|
||||
for name, value := range opts {
|
||||
if name == "blksize" {
|
||||
err := s.setBlockSize(value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return addr, nil
|
||||
case pERROR:
|
||||
return nil, fmt.Errorf("sending block %d: code=%d, error: %s",
|
||||
s.block, p.code(), p.message())
|
||||
}
|
||||
}
|
||||
}
|
488
core/protocol/tftp/libs/server.go
Normal file
@ -0,0 +1,488 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"strings"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
"HFish/core/report"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var clientData map[string]string
|
||||
|
||||
// NewServer creates TFTP server. It requires two functions to handle
|
||||
// read and write requests.
|
||||
// In case nil is provided for read or write handler the respective
|
||||
// operation is disabled.
|
||||
func NewServer(readHandler func(filename string, rf io.ReaderFrom) error,
|
||||
writeHandler func(filename string, wt io.WriterTo) error) *Server {
|
||||
s := &Server{
|
||||
timeout: defaultTimeout,
|
||||
retries: defaultRetries,
|
||||
runGC: make(chan []string),
|
||||
gcThreshold: 100,
|
||||
packetReadTimeout: 100 * time.Millisecond,
|
||||
readHandler: readHandler,
|
||||
writeHandler: writeHandler,
|
||||
}
|
||||
|
||||
clientData = make(map[string]string)
|
||||
return s
|
||||
}
|
||||
|
||||
// RequestPacketInfo provides a method of getting the local IP address
|
||||
// that is handling a UDP request. It relies for its accuracy on the
|
||||
// OS providing methods to inspect the underlying UDP and IP packets
|
||||
// directly.
|
||||
type RequestPacketInfo interface {
|
||||
// LocalAddr returns the IP address we are servicing the request on.
|
||||
// If it is unable to determine what address that is, the returned
|
||||
// net.IP will be nil.
|
||||
LocalIP() net.IP
|
||||
}
|
||||
|
||||
// Server is an instance of a TFTP server
|
||||
type Server struct {
|
||||
readHandler func(filename string, rf io.ReaderFrom) error
|
||||
writeHandler func(filename string, wt io.WriterTo) error
|
||||
hook Hook
|
||||
backoff backoffFunc
|
||||
conn *net.UDPConn
|
||||
conn6 *ipv6.PacketConn
|
||||
conn4 *ipv4.PacketConn
|
||||
quit chan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
timeout time.Duration
|
||||
retries int
|
||||
maxBlockLen int
|
||||
sendAEnable bool /* senderAnticipate enable by server */
|
||||
sendAWinSz uint
|
||||
// Single port fields
|
||||
singlePort bool
|
||||
bufPool sync.Pool
|
||||
handlers map[string]chan []byte
|
||||
runGC chan []string
|
||||
gcCollect chan string
|
||||
gcThreshold int
|
||||
packetReadTimeout time.Duration
|
||||
}
|
||||
|
||||
// TransferStats contains details about a single TFTP transfer
|
||||
type TransferStats struct {
|
||||
RemoteAddr net.IP
|
||||
Filename string
|
||||
Tid int
|
||||
SenderAnticipateEnabled bool
|
||||
TotalBlocks uint16
|
||||
Mode string
|
||||
Opts options
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
// Hook is an interface used to provide the server with success and failure hooks
|
||||
type Hook interface {
|
||||
OnSuccess(stats TransferStats)
|
||||
OnFailure(stats TransferStats, err error)
|
||||
}
|
||||
|
||||
// SetAnticipate provides an experimental feature in which when a packets
|
||||
// is requested the server will keep sending a number of packets before
|
||||
// checking whether an ack has been received. It improves tftp downloading
|
||||
// speed by a few times.
|
||||
// The argument winsz specifies how many packets will be sent before
|
||||
// waiting for an ack packet.
|
||||
// When winsz is bigger than 1, the feature is enabled, and the server
|
||||
// runs through a different experimental code path. When winsz is 0 or 1,
|
||||
// the feature is disabled.
|
||||
func (s *Server) SetAnticipate(winsz uint) {
|
||||
if winsz > 1 {
|
||||
s.sendAEnable = true
|
||||
s.sendAWinSz = winsz
|
||||
} else {
|
||||
s.sendAEnable = false
|
||||
s.sendAWinSz = 1
|
||||
}
|
||||
}
|
||||
|
||||
// SetHook sets the Hook for success and failure of transfers
|
||||
func (s *Server) SetHook(hook Hook) {
|
||||
s.hook = hook
|
||||
}
|
||||
|
||||
// EnableSinglePort enables an experimental mode where the server will
|
||||
// serve all connections on port 69 only. There will be no random TIDs
|
||||
// on the server side.
|
||||
//
|
||||
// Enabling this will negatively impact performance
|
||||
func (s *Server) EnableSinglePort() {
|
||||
s.singlePort = true
|
||||
s.handlers = make(map[string]chan []byte, datagramLength)
|
||||
s.gcCollect = make(chan string)
|
||||
s.bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, datagramLength)
|
||||
},
|
||||
}
|
||||
go s.internalGC()
|
||||
}
|
||||
|
||||
// SetTimeout sets maximum time server waits for single network
|
||||
// round-trip to succeed.
|
||||
// Default is 5 seconds.
|
||||
func (s *Server) SetTimeout(t time.Duration) {
|
||||
if t <= 0 {
|
||||
s.timeout = defaultTimeout
|
||||
} else {
|
||||
s.timeout = t
|
||||
}
|
||||
}
|
||||
|
||||
// SetBlockSize sets the maximum size of an individual data block.
|
||||
// This must be a value between 512 (the default block size for TFTP)
|
||||
// and 65456 (the max size a UDP packet payload can be).
|
||||
//
|
||||
// This is an advisory value -- it will be clamped to the smaller of
|
||||
// the block size the client wants and the MTU of the interface being
|
||||
// communicated over munis overhead.
|
||||
func (s *Server) SetBlockSize(i int) {
|
||||
if i > 512 && i < 65465 {
|
||||
s.maxBlockLen = i
|
||||
}
|
||||
}
|
||||
|
||||
// SetRetries sets maximum number of attempts server made to transmit a
|
||||
// packet.
|
||||
// Default is 5 attempts.
|
||||
func (s *Server) SetRetries(count int) {
|
||||
if count < 1 {
|
||||
s.retries = defaultRetries
|
||||
} else {
|
||||
s.retries = count
|
||||
}
|
||||
}
|
||||
|
||||
// SetBackoff sets a user provided function that is called to provide a
|
||||
// backoff duration prior to retransmitting an unacknowledged packet.
|
||||
func (s *Server) SetBackoff(h backoffFunc) {
|
||||
s.backoff = h
|
||||
}
|
||||
|
||||
// ListenAndServe binds to address provided and start the server.
|
||||
// ListenAndServe returns when Shutdown is called.
|
||||
func (s *Server) ListenAndServe(addr string) error {
|
||||
a, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Serve(conn)
|
||||
}
|
||||
|
||||
// Serve starts server provided already opened UDP connecton. It is
|
||||
// useful for the case when you want to run server in separate goroutine
|
||||
// but still want to be able to handle any errors opening connection.
|
||||
// Serve returns when Shutdown is called or connection is closed.
|
||||
func (s *Server) Serve(conn *net.UDPConn) error {
|
||||
defer conn.Close()
|
||||
laddr := conn.LocalAddr()
|
||||
host, _, err := net.SplitHostPort(laddr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.conn = conn
|
||||
// Having seperate control paths for IP4 and IP6 is annoying,
|
||||
// but necessary at this point.
|
||||
addr := net.ParseIP(host)
|
||||
if addr == nil {
|
||||
return fmt.Errorf("Failed to determine IP class of listening address")
|
||||
}
|
||||
if addr.To4() != nil {
|
||||
s.conn4 = ipv4.NewPacketConn(conn)
|
||||
if err := s.conn4.SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true); err != nil {
|
||||
s.conn4 = nil
|
||||
}
|
||||
} else {
|
||||
s.conn6 = ipv6.NewPacketConn(conn)
|
||||
if err := s.conn6.SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true); err != nil {
|
||||
s.conn6 = nil
|
||||
}
|
||||
}
|
||||
|
||||
s.quit = make(chan chan struct{})
|
||||
if s.singlePort {
|
||||
s.singlePortProcessRequests()
|
||||
} else {
|
||||
for {
|
||||
select {
|
||||
case q := <-s.quit:
|
||||
q <- struct{}{}
|
||||
return nil
|
||||
default:
|
||||
var err error
|
||||
if s.conn4 != nil {
|
||||
err = s.processRequest4()
|
||||
} else if s.conn6 != nil {
|
||||
err = s.processRequest6()
|
||||
} else {
|
||||
err = s.processRequest()
|
||||
}
|
||||
if err != nil && s.hook != nil {
|
||||
s.hook.OnFailure(TransferStats{
|
||||
SenderAnticipateEnabled: s.sendAEnable,
|
||||
}, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Yes, I don't really like having seperate IPv4 and IPv6 variants,
|
||||
// bit we are relying on the low-level packet control channel info to
|
||||
// get a reliable source address, and those have different types and
|
||||
// the struct itself is not easily interface-ized or embedded.
|
||||
//
|
||||
// If control is nil for whatever reason (either things not being
|
||||
// implemented on a target OS or whatever other reason), localIP
|
||||
// (and hence LocalIP()) will return a nil IP address.
|
||||
func (s *Server) processRequest4() error {
|
||||
buf := make([]byte, datagramLength)
|
||||
cnt, control, srcAddr, err := s.conn4.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading UDP: %v", err)
|
||||
}
|
||||
maxSz := blockLength
|
||||
var localAddr net.IP
|
||||
if control != nil {
|
||||
localAddr = control.Dst
|
||||
if intf, err := net.InterfaceByIndex(control.IfIndex); err == nil {
|
||||
// mtu - ipv4 overhead - udp overhead
|
||||
maxSz = intf.MTU - 28
|
||||
}
|
||||
}
|
||||
return s.handlePacket(localAddr, srcAddr.(*net.UDPAddr), buf, cnt, maxSz, nil)
|
||||
}
|
||||
|
||||
func (s *Server) processRequest6() error {
|
||||
buf := make([]byte, datagramLength)
|
||||
cnt, control, srcAddr, err := s.conn6.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading UDP: %v", err)
|
||||
}
|
||||
maxSz := blockLength
|
||||
var localAddr net.IP
|
||||
if control != nil {
|
||||
localAddr = control.Dst
|
||||
if intf, err := net.InterfaceByIndex(control.IfIndex); err == nil {
|
||||
// mtu - ipv6 overhead - udp overhead
|
||||
maxSz = intf.MTU - 48
|
||||
}
|
||||
}
|
||||
return s.handlePacket(localAddr, srcAddr.(*net.UDPAddr), buf, cnt, maxSz, nil)
|
||||
}
|
||||
|
||||
// Fallback if we had problems opening a ipv4/6 control channel
|
||||
func (s *Server) processRequest() error {
|
||||
buf := make([]byte, datagramLength)
|
||||
cnt, srcAddr, err := s.conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading UDP: %v", err)
|
||||
}
|
||||
return s.handlePacket(nil, srcAddr, buf, cnt, blockLength, nil)
|
||||
}
|
||||
|
||||
// Shutdown make server stop listening for new requests, allows
|
||||
// server to finish outstanding transfers and stops server.
|
||||
func (s *Server) Shutdown() {
|
||||
s.conn.Close()
|
||||
q := make(chan struct{})
|
||||
s.quit <- q
|
||||
<-q
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *Server) handlePacket(localAddr net.IP, remoteAddr *net.UDPAddr, buffer []byte, n, maxBlockLen int, listener chan []byte) error {
|
||||
if s.maxBlockLen > 0 && s.maxBlockLen < maxBlockLen {
|
||||
maxBlockLen = s.maxBlockLen
|
||||
}
|
||||
if maxBlockLen < blockLength {
|
||||
maxBlockLen = blockLength
|
||||
}
|
||||
p, err := parsePacket(buffer[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch p := p.(type) {
|
||||
case pWRQ:
|
||||
filename, mode, opts, err := unpackRQ(p)
|
||||
|
||||
arr := strings.Split(remoteAddr.String(), ":")
|
||||
info := "put " + filename
|
||||
|
||||
id, ok := clientData[remoteAddr.String()]
|
||||
|
||||
if ok {
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("TFTP", "", "", "&&"+info, id)
|
||||
} else {
|
||||
go report.ReportUpdateTFtp(id, "&&"+info)
|
||||
}
|
||||
} else {
|
||||
var idx string
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
idx = client.ReportResult("TFTP", "", arr[0], info, "0")
|
||||
} else {
|
||||
idx = strconv.FormatInt(report.ReportTFtp(arr[0], "本机", info), 10)
|
||||
}
|
||||
|
||||
fmt.Println(remoteAddr.String(),idx)
|
||||
|
||||
clientData[remoteAddr.String()] = idx
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unpack WRQ: %v", err)
|
||||
}
|
||||
wt := &receiver{
|
||||
send: make([]byte, datagramLength),
|
||||
receive: make([]byte, datagramLength),
|
||||
retry: &backoff{handler: s.backoff},
|
||||
timeout: s.timeout,
|
||||
retries: s.retries,
|
||||
addr: remoteAddr,
|
||||
localIP: localAddr,
|
||||
mode: mode,
|
||||
opts: opts,
|
||||
maxBlockLen: maxBlockLen,
|
||||
hook: s.hook,
|
||||
filename: filename,
|
||||
startTime: time.Now(),
|
||||
}
|
||||
if s.singlePort {
|
||||
wt.conn = &chanConnection{
|
||||
addr: remoteAddr,
|
||||
channel: listener,
|
||||
timeout: s.timeout,
|
||||
sendConn: s.conn,
|
||||
complete: s.gcCollect,
|
||||
}
|
||||
wt.singlePort = true
|
||||
} else {
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wt.conn = &connConnection{conn: conn}
|
||||
}
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
if s.writeHandler != nil {
|
||||
err := s.writeHandler(filename, wt)
|
||||
if err != nil {
|
||||
wt.abort(err)
|
||||
} else {
|
||||
wt.terminate()
|
||||
}
|
||||
} else {
|
||||
wt.abort(fmt.Errorf("server does not support write requests"))
|
||||
}
|
||||
s.wg.Done()
|
||||
}()
|
||||
case pRRQ:
|
||||
filename, mode, opts, err := unpackRQ(p)
|
||||
|
||||
arr := strings.Split(remoteAddr.String(), ":")
|
||||
info := "get " + filename
|
||||
|
||||
id, ok := clientData[remoteAddr.String()]
|
||||
|
||||
if ok {
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("TFTP", "", "", "&&"+info, id)
|
||||
} else {
|
||||
go report.ReportUpdateTFtp(id, "&&"+info)
|
||||
}
|
||||
} else {
|
||||
var idx string
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
idx = client.ReportResult("TFTP", "", arr[0], info, "0")
|
||||
} else {
|
||||
idx = strconv.FormatInt(report.ReportTFtp(arr[0], "本机", info), 10)
|
||||
}
|
||||
|
||||
clientData[remoteAddr.String()] = idx
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unpack RRQ: %v", err)
|
||||
}
|
||||
rf := &sender{
|
||||
send: make([]byte, datagramLength),
|
||||
sendA: senderAnticipate{enabled: false},
|
||||
receive: make([]byte, datagramLength),
|
||||
tid: remoteAddr.Port,
|
||||
retry: &backoff{handler: s.backoff},
|
||||
timeout: s.timeout,
|
||||
retries: s.retries,
|
||||
addr: remoteAddr,
|
||||
localIP: localAddr,
|
||||
mode: mode,
|
||||
opts: opts,
|
||||
maxBlockLen: maxBlockLen,
|
||||
hook: s.hook,
|
||||
filename: filename,
|
||||
startTime: time.Now(),
|
||||
}
|
||||
if s.singlePort {
|
||||
rf.conn = &chanConnection{
|
||||
addr: remoteAddr,
|
||||
channel: listener,
|
||||
timeout: s.timeout,
|
||||
sendConn: s.conn,
|
||||
complete: s.gcCollect,
|
||||
}
|
||||
} else {
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rf.conn = &connConnection{conn: conn}
|
||||
}
|
||||
if s.sendAEnable { /* senderAnticipate if enabled in server */
|
||||
rf.sendA.enabled = true /* pass enable from server to sender */
|
||||
sendAInit(&rf.sendA, datagramLength, s.sendAWinSz)
|
||||
}
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
if s.readHandler != nil {
|
||||
err := s.readHandler(filename, rf)
|
||||
if err != nil {
|
||||
rf.abort(err)
|
||||
}
|
||||
} else {
|
||||
rf.abort(fmt.Errorf("server does not support read requests"))
|
||||
}
|
||||
s.wg.Done()
|
||||
}()
|
||||
default:
|
||||
return fmt.Errorf("unexpected %T", p)
|
||||
}
|
||||
return nil
|
||||
}
|
119
core/protocol/tftp/libs/single_port.go
Normal file
@ -0,0 +1,119 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func (s *Server) singlePortProcessRequests() error {
|
||||
var (
|
||||
localAddr net.IP
|
||||
cnt int
|
||||
srcAddr net.Addr
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// We've received a new connection on the same IP+Port tuple
|
||||
// as a previous connection before garbage collection has occured
|
||||
s.handlers[srcAddr.String()] = make(chan []byte)
|
||||
go func(localAddr net.IP, remoteAddr *net.UDPAddr, buffer []byte, n, maxBlockLen int, listener chan []byte) {
|
||||
err := s.handlePacket(localAddr, remoteAddr, buffer, n, maxBlockLen, listener)
|
||||
if err != nil && s.hook != nil {
|
||||
s.hook.OnFailure(TransferStats{
|
||||
SenderAnticipateEnabled: s.sendAEnable,
|
||||
}, err)
|
||||
}
|
||||
|
||||
}(localAddr, srcAddr.(*net.UDPAddr), buf, cnt, blockLength, s.handlers[srcAddr.String()])
|
||||
s.singlePortProcessRequests()
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case q := <-s.quit:
|
||||
q <- struct{}{}
|
||||
return nil
|
||||
case handlersToFree := <-s.runGC:
|
||||
for _, handler := range handlersToFree {
|
||||
delete(s.handlers, handler)
|
||||
}
|
||||
default:
|
||||
buf = s.bufPool.Get().([]byte)
|
||||
cnt, localAddr, srcAddr, err = s.getPacket(buf)
|
||||
if err != nil || cnt == 0 {
|
||||
if s.hook != nil {
|
||||
s.hook.OnFailure(TransferStats{
|
||||
SenderAnticipateEnabled: s.sendAEnable,
|
||||
}, err)
|
||||
}
|
||||
s.bufPool.Put(buf)
|
||||
continue
|
||||
}
|
||||
if receiverChannel, ok := s.handlers[srcAddr.String()]; ok {
|
||||
select {
|
||||
case receiverChannel <- buf[:cnt]:
|
||||
default:
|
||||
// We don't want to block the main loop if a channel is full
|
||||
}
|
||||
} else {
|
||||
s.handlers[srcAddr.String()] = make(chan []byte, datagramLength)
|
||||
go func(localAddr net.IP, remoteAddr *net.UDPAddr, buffer []byte, n, maxBlockLen int, listener chan []byte) {
|
||||
err := s.handlePacket(localAddr, remoteAddr, buffer, n, maxBlockLen, listener)
|
||||
if err != nil && s.hook != nil {
|
||||
s.hook.OnFailure(TransferStats{
|
||||
SenderAnticipateEnabled: s.sendAEnable,
|
||||
}, err)
|
||||
}
|
||||
|
||||
}(localAddr, srcAddr.(*net.UDPAddr), buf, cnt, blockLength, s.handlers[srcAddr.String()])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) getPacket(buf []byte) (int, net.IP, *net.UDPAddr, error) {
|
||||
if s.conn6 != nil {
|
||||
cnt, control, srcAddr, err := s.conn6.ReadFrom(buf)
|
||||
if err != nil || cnt == 0 {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
var localAddr net.IP
|
||||
if control != nil {
|
||||
localAddr = control.Dst
|
||||
}
|
||||
return cnt, localAddr, srcAddr.(*net.UDPAddr), nil
|
||||
} else if s.conn4 != nil {
|
||||
cnt, control, srcAddr, err := s.conn4.ReadFrom(buf)
|
||||
if err != nil || cnt == 0 {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
var localAddr net.IP
|
||||
if control != nil {
|
||||
localAddr = control.Dst
|
||||
}
|
||||
return cnt, localAddr, srcAddr.(*net.UDPAddr), nil
|
||||
} else {
|
||||
cnt, srcAddr, err := s.conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
return cnt, nil, srcAddr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// internalGC collects all the finished signals from each connection's goroutine
|
||||
// The main loop is sent the key to be nil'ed after the gcInterval has passed
|
||||
func (s *Server) internalGC() {
|
||||
var completedHandlers []string
|
||||
for {
|
||||
select {
|
||||
case newHandler := <-s.gcCollect:
|
||||
completedHandlers = append(completedHandlers, newHandler)
|
||||
if len(completedHandlers) > s.gcThreshold {
|
||||
s.runGC <- completedHandlers
|
||||
completedHandlers = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
core/protocol/tftp/libs/single_port_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestZeroLengthSinglePort(t *testing.T) {
|
||||
s, c := makeTestServer(true)
|
||||
defer s.Shutdown()
|
||||
testSendReceive(t, c, 0)
|
||||
}
|
||||
|
||||
func TestSendReceiveSinglePort(t *testing.T) {
|
||||
s, c := makeTestServer(true)
|
||||
defer s.Shutdown()
|
||||
for i := 600; i < 1000; i++ {
|
||||
testSendReceive(t, c, 5000+int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendReceiveSinglePortWithBlockSize(t *testing.T) {
|
||||
s, c := makeTestServer(true)
|
||||
defer s.Shutdown()
|
||||
for i := 600; i < 1000; i++ {
|
||||
c.blksize = i
|
||||
testSendReceive(t, c, 5000+int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerSendTimeoutSinglePort(t *testing.T) {
|
||||
s, c := makeTestServer(true)
|
||||
serverTimeoutSendTest(s, c, t)
|
||||
}
|
||||
|
||||
func TestServerReceiveTimeoutSinglePort(t *testing.T) {
|
||||
s, c := makeTestServer(true)
|
||||
serverReceiveTimeoutTest(s, c, t)
|
||||
}
|
41
core/protocol/tftp/libs/tftp_anticipate_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// derived from Test900
|
||||
func TestAnticipateWindow900(t *testing.T) {
|
||||
s, c := makeTestServerAnticipateWindow()
|
||||
defer s.Shutdown()
|
||||
for i := 600; i < 4000; i++ {
|
||||
c.blksize = i
|
||||
testSendReceive(t, c, 9000+int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
// derived from makeTestServer
|
||||
func makeTestServerAnticipateWindow() (*Server, *Client) {
|
||||
b := &testBackend{}
|
||||
b.m = make(map[string][]byte)
|
||||
|
||||
// Create server
|
||||
s := NewServer(b.handleRead, b.handleWrite)
|
||||
s.SetAnticipate(16) /* senderAnticipate window size set to 16 */
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go s.Serve(conn)
|
||||
|
||||
// Create client for that server
|
||||
c, err := NewClient(localSystem(conn))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return s, c
|
||||
}
|
920
core/protocol/tftp/libs/tftp_test.go
Normal file
@ -0,0 +1,920 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var localhost = determineLocalhost()
|
||||
|
||||
func determineLocalhost() string {
|
||||
l, err := net.ListenTCP("tcp", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("ListenTCP error: %s", err))
|
||||
}
|
||||
_, lport, _ := net.SplitHostPort(l.Addr().String())
|
||||
defer l.Close()
|
||||
|
||||
lo := make(chan string)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
port, _ := strconv.Atoi(lport)
|
||||
for _, af := range []string{"tcp6", "tcp4"} {
|
||||
conn, err := net.DialTCP(af, &net.TCPAddr{}, &net.TCPAddr{Port: port})
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
host, _, _ := net.SplitHostPort(conn.LocalAddr().String())
|
||||
lo <- host
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("could not determine address family")
|
||||
}()
|
||||
|
||||
return <-lo
|
||||
}
|
||||
|
||||
func localSystem(c *net.UDPConn) string {
|
||||
_, port, _ := net.SplitHostPort(c.LocalAddr().String())
|
||||
return net.JoinHostPort(localhost, port)
|
||||
}
|
||||
|
||||
func TestPackUnpack(t *testing.T) {
|
||||
v := []string{"test-filename/with-subdir"}
|
||||
testOptsList := []options{
|
||||
nil,
|
||||
options{
|
||||
"tsize": "1234",
|
||||
"blksize": "22",
|
||||
},
|
||||
}
|
||||
for _, filename := range v {
|
||||
for _, mode := range []string{"octet", "netascii"} {
|
||||
for _, opts := range testOptsList {
|
||||
packUnpack(t, filename, mode, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func packUnpack(t *testing.T, filename, mode string, opts options) {
|
||||
b := make([]byte, datagramLength)
|
||||
for _, op := range []uint16{opRRQ, opWRQ} {
|
||||
n := packRQ(b, op, filename, mode, opts)
|
||||
f, m, o, err := unpackRQ(b[:n])
|
||||
if err != nil {
|
||||
t.Errorf("%s pack/unpack: %v", filename, err)
|
||||
}
|
||||
if f != filename {
|
||||
t.Errorf("filename mismatch (%s): '%x' vs '%x'",
|
||||
filename, f, filename)
|
||||
}
|
||||
if m != mode {
|
||||
t.Errorf("mode mismatch (%s): '%x' vs '%x'",
|
||||
mode, m, mode)
|
||||
}
|
||||
if opts != nil {
|
||||
for name, value := range opts {
|
||||
v, ok := o[name]
|
||||
if !ok {
|
||||
t.Errorf("missing %s option", name)
|
||||
}
|
||||
if v != value {
|
||||
t.Errorf("option %s mismatch: '%x' vs '%x'", name, v, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroLength(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
testSendReceive(t, c, 0)
|
||||
}
|
||||
|
||||
func Test900(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
for i := 600; i < 4000; i++ {
|
||||
c.SetBlockSize(i)
|
||||
s.SetBlockSize(4600 - i)
|
||||
testSendReceive(t, c, 9000+int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Test1000(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
for i := int64(0); i < 5000; i++ {
|
||||
filename := fmt.Sprintf("length-%d-bytes-%d", i, time.Now().UnixNano())
|
||||
rf, err := c.Send(filename, "octet")
|
||||
if err != nil {
|
||||
t.Fatalf("requesting %s write: %v", filename, err)
|
||||
}
|
||||
r := io.LimitReader(newRandReader(rand.NewSource(i)), i)
|
||||
n, err := rf.ReadFrom(r)
|
||||
if err != nil {
|
||||
t.Fatalf("sending %s: %v", filename, err)
|
||||
}
|
||||
if n != i {
|
||||
t.Errorf("%s length mismatch: %d != %d", filename, n, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test1810(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
c.SetBlockSize(1810)
|
||||
testSendReceive(t, c, 9000+1810)
|
||||
}
|
||||
|
||||
type fakeHook struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (f *fakeHook) OnSuccess(result TransferStats) {
|
||||
f.Called(result)
|
||||
return
|
||||
}
|
||||
func (f *fakeHook) OnFailure(result TransferStats, err error) {
|
||||
f.Called(result)
|
||||
return
|
||||
}
|
||||
|
||||
func TestHookSuccess(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
fakeHook := new(fakeHook)
|
||||
// Due to the way tests run there will be some errors
|
||||
fakeHook.On("OnFailure", mock.AnythingOfType("TransferStats")).Return()
|
||||
fakeHook.On("OnSuccess", mock.AnythingOfType("TransferStats")).Return()
|
||||
s.SetHook(fakeHook)
|
||||
defer s.Shutdown()
|
||||
c.SetBlockSize(1810)
|
||||
testSendReceive(t, c, 9000+1810)
|
||||
fakeHook.AssertCalled(t, "OnSuccess", mock.AnythingOfType("TransferStats"))
|
||||
fakeHook.AssertNumberOfCalls(t, "OnSuccess", 1)
|
||||
}
|
||||
|
||||
func TestHookFailure(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
fakeHook := new(fakeHook)
|
||||
fakeHook.On("OnFailure", mock.AnythingOfType("TransferStats")).Return()
|
||||
s.SetHook(fakeHook)
|
||||
defer s.Shutdown()
|
||||
filename := "test-not-exists"
|
||||
mode := "octet"
|
||||
_, err := c.Receive(filename, mode)
|
||||
if err == nil {
|
||||
t.Fatalf("file not exists: %v", err)
|
||||
}
|
||||
t.Logf("receiving file that does not exist: %v", err)
|
||||
fakeHook.AssertExpectations(t)
|
||||
fakeHook.AssertNumberOfCalls(t, "OnFailure", 1)
|
||||
}
|
||||
|
||||
func TestTSize(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
c.tsize = true
|
||||
testSendReceive(t, c, 640)
|
||||
}
|
||||
|
||||
func TestNearBlockLength(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
for i := 450; i < 520; i++ {
|
||||
testSendReceive(t, c, int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockWrapsAround(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
n := 65535 * 512
|
||||
for i := n - 2; i < n+2; i++ {
|
||||
testSendReceive(t, c, int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomLength(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
r := rand.New(rand.NewSource(42))
|
||||
for i := 0; i < 100; i++ {
|
||||
testSendReceive(t, c, r.Int63n(100000))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBigFile(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
testSendReceive(t, c, 3*1000*1000)
|
||||
}
|
||||
|
||||
func TestByOneByte(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
filename := "test-by-one-byte"
|
||||
mode := "octet"
|
||||
const length = 80000
|
||||
sender, err := c.Send(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting write: %v", err)
|
||||
}
|
||||
r := iotest.OneByteReader(io.LimitReader(
|
||||
newRandReader(rand.NewSource(42)), length))
|
||||
n, err := sender.ReadFrom(r)
|
||||
if err != nil {
|
||||
t.Fatalf("send error: %v", err)
|
||||
}
|
||||
if n != length {
|
||||
t.Errorf("%s read length mismatch: %d != %d", filename, n, length)
|
||||
}
|
||||
readTransfer, err := c.Receive(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting read %s: %v", filename, err)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
n, err = readTransfer.WriteTo(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("%s read error: %v", filename, err)
|
||||
}
|
||||
if n != length {
|
||||
t.Errorf("%s read length mismatch: %d != %d", filename, n, length)
|
||||
}
|
||||
bs, _ := ioutil.ReadAll(io.LimitReader(
|
||||
newRandReader(rand.NewSource(42)), length))
|
||||
if !bytes.Equal(bs, buf.Bytes()) {
|
||||
t.Errorf("\nsent: %x\nrcvd: %x", bs, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicate(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
filename := "test-duplicate"
|
||||
mode := "octet"
|
||||
bs := []byte("lalala")
|
||||
sender, err := c.Send(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting write: %v", err)
|
||||
}
|
||||
buf := bytes.NewBuffer(bs)
|
||||
_, err = sender.ReadFrom(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("send error: %v", err)
|
||||
}
|
||||
sender, err = c.Send(filename, mode)
|
||||
if err == nil {
|
||||
t.Fatalf("file already exists")
|
||||
}
|
||||
t.Logf("sending file that already exists: %v", err)
|
||||
}
|
||||
|
||||
func TestNotFound(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
defer s.Shutdown()
|
||||
filename := "test-not-exists"
|
||||
mode := "octet"
|
||||
_, err := c.Receive(filename, mode)
|
||||
if err == nil {
|
||||
t.Fatalf("file not exists: %v", err)
|
||||
}
|
||||
t.Logf("receiving file that does not exist: %v", err)
|
||||
}
|
||||
|
||||
func testSendReceive(t *testing.T, client *Client, length int64) {
|
||||
filename := fmt.Sprintf("length-%d-bytes", length)
|
||||
mode := "octet"
|
||||
writeTransfer, err := client.Send(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting write %s: %v", filename, err)
|
||||
}
|
||||
r := io.LimitReader(newRandReader(rand.NewSource(42)), length)
|
||||
n, err := writeTransfer.ReadFrom(r)
|
||||
if err != nil {
|
||||
t.Fatalf("%s write error: %v", filename, err)
|
||||
}
|
||||
if n != length {
|
||||
t.Errorf("%s write length mismatch: %d != %d", filename, n, length)
|
||||
}
|
||||
readTransfer, err := client.Receive(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting read %s: %v", filename, err)
|
||||
}
|
||||
if it, ok := readTransfer.(IncomingTransfer); ok {
|
||||
if n, ok := it.Size(); ok {
|
||||
fmt.Printf("Transfer size: %d\n", n)
|
||||
if n != length {
|
||||
t.Errorf("tsize mismatch: %d vs %d", n, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
n, err = readTransfer.WriteTo(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("%s read error: %v", filename, err)
|
||||
}
|
||||
if n != length {
|
||||
t.Errorf("%s read length mismatch: %d != %d", filename, n, length)
|
||||
}
|
||||
bs, _ := ioutil.ReadAll(io.LimitReader(
|
||||
newRandReader(rand.NewSource(42)), length))
|
||||
if !bytes.Equal(bs, buf.Bytes()) {
|
||||
t.Errorf("\nsent: %x\nrcvd: %x", bs, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendTsizeFromSeek(t *testing.T) {
|
||||
// create read-only server
|
||||
s := NewServer(func(filename string, rf io.ReaderFrom) error {
|
||||
b := make([]byte, 100)
|
||||
rr := newRandReader(rand.NewSource(42))
|
||||
rr.Read(b)
|
||||
// bytes.Reader implements io.Seek
|
||||
r := bytes.NewReader(b)
|
||||
_, err := rf.ReadFrom(r)
|
||||
if err != nil {
|
||||
t.Errorf("sending bytes: %v", err)
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
t.Fatalf("listening: %v", err)
|
||||
}
|
||||
|
||||
go s.Serve(conn)
|
||||
defer s.Shutdown()
|
||||
|
||||
c, _ := NewClient(localSystem(conn))
|
||||
c.RequestTSize(true)
|
||||
r, _ := c.Receive("f", "octet")
|
||||
var size int64
|
||||
if it, ok := r.(IncomingTransfer); ok {
|
||||
if n, ok := it.Size(); ok {
|
||||
size = n
|
||||
fmt.Printf("Transfer size: %d\n", n)
|
||||
}
|
||||
}
|
||||
|
||||
if size != 100 {
|
||||
t.Errorf("size expected: 100, got %d", size)
|
||||
}
|
||||
|
||||
r.WriteTo(ioutil.Discard)
|
||||
|
||||
c.RequestTSize(false)
|
||||
r, _ = c.Receive("f", "octet")
|
||||
if it, ok := r.(IncomingTransfer); ok {
|
||||
_, ok := it.Size()
|
||||
if ok {
|
||||
t.Errorf("unexpected size received")
|
||||
}
|
||||
}
|
||||
|
||||
r.WriteTo(ioutil.Discard)
|
||||
}
|
||||
|
||||
type testBackend struct {
|
||||
m map[string][]byte
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func makeTestServer(singlePort bool) (*Server, *Client) {
|
||||
b := &testBackend{}
|
||||
b.m = make(map[string][]byte)
|
||||
|
||||
// Create server
|
||||
s := NewServer(b.handleRead, b.handleWrite)
|
||||
|
||||
if singlePort {
|
||||
s.EnableSinglePort()
|
||||
}
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go s.Serve(conn)
|
||||
|
||||
// Create client for that server
|
||||
c, err := NewClient(localSystem(conn))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return s, c
|
||||
}
|
||||
|
||||
func TestNoHandlers(t *testing.T) {
|
||||
s := NewServer(nil, nil)
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go s.Serve(conn)
|
||||
|
||||
c, err := NewClient(localSystem(conn))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = c.Send("test", "octet")
|
||||
if err == nil {
|
||||
t.Errorf("error expected")
|
||||
}
|
||||
|
||||
_, err = c.Receive("test", "octet")
|
||||
if err == nil {
|
||||
t.Errorf("error expected")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *testBackend) handleWrite(filename string, wt io.WriterTo) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
_, ok := b.m[filename]
|
||||
if ok {
|
||||
fmt.Fprintf(os.Stderr, "File %s already exists\n", filename)
|
||||
return fmt.Errorf("file already exists")
|
||||
}
|
||||
if t, ok := wt.(IncomingTransfer); ok {
|
||||
if n, ok := t.Size(); ok {
|
||||
fmt.Printf("Transfer size: %d\n", n)
|
||||
}
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := wt.WriteTo(buf)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Can't receive %s: %v\n", filename, err)
|
||||
return err
|
||||
}
|
||||
b.m[filename] = buf.Bytes()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *testBackend) handleRead(filename string, rf io.ReaderFrom) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
bs, ok := b.m[filename]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "File %s not found\n", filename)
|
||||
return fmt.Errorf("file not found")
|
||||
}
|
||||
if t, ok := rf.(OutgoingTransfer); ok {
|
||||
t.SetSize(int64(len(bs)))
|
||||
}
|
||||
_, err := rf.ReadFrom(bytes.NewBuffer(bs))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Can't send %s: %v\n", filename, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type randReader struct {
|
||||
src rand.Source
|
||||
next int64
|
||||
i int8
|
||||
}
|
||||
|
||||
func newRandReader(src rand.Source) io.Reader {
|
||||
r := &randReader{
|
||||
src: src,
|
||||
next: src.Int63(),
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *randReader) Read(p []byte) (n int, err error) {
|
||||
next, i := r.next, r.i
|
||||
for n = 0; n < len(p); n++ {
|
||||
if i == 7 {
|
||||
next, i = r.src.Int63(), 0
|
||||
}
|
||||
p[n] = byte(next)
|
||||
next >>= 8
|
||||
i++
|
||||
}
|
||||
r.next, r.i = next, i
|
||||
return
|
||||
}
|
||||
|
||||
func serverTimeoutSendTest(s *Server, c *Client, t *testing.T) {
|
||||
s.SetTimeout(time.Second)
|
||||
s.SetRetries(2)
|
||||
var serverErr error
|
||||
s.readHandler = func(filename string, rf io.ReaderFrom) error {
|
||||
r := io.LimitReader(newRandReader(rand.NewSource(42)), 80000)
|
||||
_, serverErr = rf.ReadFrom(r)
|
||||
return serverErr
|
||||
}
|
||||
defer s.Shutdown()
|
||||
filename := "test-server-send-timeout"
|
||||
mode := "octet"
|
||||
readTransfer, err := c.Receive(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting read %s: %v", filename, err)
|
||||
}
|
||||
w := &slowWriter{
|
||||
n: 3,
|
||||
delay: 8 * time.Second,
|
||||
}
|
||||
_, _ = readTransfer.WriteTo(w)
|
||||
netErr, ok := serverErr.(net.Error)
|
||||
if !ok {
|
||||
t.Fatalf("network error expected: %T", serverErr)
|
||||
}
|
||||
if !netErr.Timeout() {
|
||||
t.Fatalf("timout is expected: %v", serverErr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestServerSendTimeout(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
serverTimeoutSendTest(s, c, t)
|
||||
}
|
||||
|
||||
func serverReceiveTimeoutTest(s *Server, c *Client, t *testing.T) {
|
||||
s.SetTimeout(time.Second)
|
||||
s.SetRetries(2)
|
||||
var serverErr error
|
||||
s.writeHandler = func(filename string, wt io.WriterTo) error {
|
||||
buf := &bytes.Buffer{}
|
||||
_, serverErr = wt.WriteTo(buf)
|
||||
return serverErr
|
||||
}
|
||||
defer s.Shutdown()
|
||||
filename := "test-server-receive-timeout"
|
||||
mode := "octet"
|
||||
writeTransfer, err := c.Send(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting write %s: %v", filename, err)
|
||||
}
|
||||
r := &slowReader{
|
||||
r: io.LimitReader(newRandReader(rand.NewSource(42)), 80000),
|
||||
n: 3,
|
||||
delay: 8 * time.Second,
|
||||
}
|
||||
_, _ = writeTransfer.ReadFrom(r)
|
||||
netErr, ok := serverErr.(net.Error)
|
||||
if !ok {
|
||||
t.Fatalf("network error expected: %T", serverErr)
|
||||
}
|
||||
if !netErr.Timeout() {
|
||||
t.Fatalf("timout is expected: %v", serverErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerReceiveTimeout(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
serverReceiveTimeoutTest(s, c, t)
|
||||
}
|
||||
|
||||
func TestClientReceiveTimeout(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
c.SetTimeout(time.Second)
|
||||
c.SetRetries(2)
|
||||
s.readHandler = func(filename string, rf io.ReaderFrom) error {
|
||||
r := &slowReader{
|
||||
r: io.LimitReader(newRandReader(rand.NewSource(42)), 80000),
|
||||
n: 3,
|
||||
delay: 8 * time.Second,
|
||||
}
|
||||
_, err := rf.ReadFrom(r)
|
||||
return err
|
||||
}
|
||||
defer s.Shutdown()
|
||||
filename := "test-client-receive-timeout"
|
||||
mode := "octet"
|
||||
readTransfer, err := c.Receive(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting read %s: %v", filename, err)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
_, err = readTransfer.WriteTo(buf)
|
||||
netErr, ok := err.(net.Error)
|
||||
if !ok {
|
||||
t.Fatalf("network error expected: %T", err)
|
||||
}
|
||||
if !netErr.Timeout() {
|
||||
t.Fatalf("timout is expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientSendTimeout(t *testing.T) {
|
||||
s, c := makeTestServer(false)
|
||||
c.SetTimeout(time.Second)
|
||||
c.SetRetries(2)
|
||||
s.writeHandler = func(filename string, wt io.WriterTo) error {
|
||||
w := &slowWriter{
|
||||
n: 3,
|
||||
delay: 8 * time.Second,
|
||||
}
|
||||
_, err := wt.WriteTo(w)
|
||||
return err
|
||||
}
|
||||
defer s.Shutdown()
|
||||
filename := "test-client-send-timeout"
|
||||
mode := "octet"
|
||||
writeTransfer, err := c.Send(filename, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("requesting write %s: %v", filename, err)
|
||||
}
|
||||
r := io.LimitReader(newRandReader(rand.NewSource(42)), 80000)
|
||||
_, err = writeTransfer.ReadFrom(r)
|
||||
netErr, ok := err.(net.Error)
|
||||
if !ok {
|
||||
t.Fatalf("network error expected: %T", err)
|
||||
}
|
||||
if !netErr.Timeout() {
|
||||
t.Fatalf("timout is expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type slowReader struct {
|
||||
r io.Reader
|
||||
n int64
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func (r *slowReader) Read(p []byte) (n int, err error) {
|
||||
if r.n > 0 {
|
||||
r.n--
|
||||
return r.r.Read(p)
|
||||
}
|
||||
time.Sleep(r.delay)
|
||||
return r.r.Read(p)
|
||||
}
|
||||
|
||||
type slowWriter struct {
|
||||
r io.Reader
|
||||
n int64
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func (r *slowWriter) Write(p []byte) (n int, err error) {
|
||||
if r.n > 0 {
|
||||
r.n--
|
||||
return len(p), nil
|
||||
}
|
||||
time.Sleep(r.delay)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// TestRequestPacketInfo checks that request packet destination address
|
||||
// obtained by server using out-of-band socket info is sane.
|
||||
// It creates server and tries to do transfers using different local interfaces.
|
||||
// NB: Test ignores transfer errors and validates RequestPacketInfo only
|
||||
// if transfer is completed successfully. So it checks that LocalIP returns
|
||||
// correct result if any result is returned, but does not check if result was
|
||||
// returned at all when it should.
|
||||
func TestRequestPacketInfo(t *testing.T) {
|
||||
// localIP keeps value received from RequestPacketInfo.LocalIP
|
||||
// call inside handler.
|
||||
// If RequestPacketInfo is not supported, value is set to unspecified
|
||||
// IP address.
|
||||
var localIP net.IP
|
||||
var localIPMu sync.Mutex
|
||||
|
||||
s := NewServer(
|
||||
func(_ string, rf io.ReaderFrom) error {
|
||||
localIPMu.Lock()
|
||||
if rpi, ok := rf.(RequestPacketInfo); ok {
|
||||
localIP = rpi.LocalIP()
|
||||
} else {
|
||||
localIP = net.IP{}
|
||||
}
|
||||
localIPMu.Unlock()
|
||||
_, err := rf.ReadFrom(io.LimitReader(
|
||||
newRandReader(rand.NewSource(42)), 42))
|
||||
if err != nil {
|
||||
t.Logf("sending to client: %v", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(_ string, wt io.WriterTo) error {
|
||||
localIPMu.Lock()
|
||||
if rpi, ok := wt.(RequestPacketInfo); ok {
|
||||
localIP = rpi.LocalIP()
|
||||
} else {
|
||||
localIP = net.IP{}
|
||||
}
|
||||
localIPMu.Unlock()
|
||||
_, err := wt.WriteTo(ioutil.Discard)
|
||||
if err != nil {
|
||||
t.Logf("receiving from client: %v", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
t.Fatalf("listen UDP: %v", err)
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("parsing server port: %v", err)
|
||||
}
|
||||
|
||||
// Start server
|
||||
go func() {
|
||||
err := s.Serve(conn)
|
||||
if err != nil {
|
||||
t.Fatalf("serve: %v", err)
|
||||
}
|
||||
}()
|
||||
defer s.Shutdown()
|
||||
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
t.Fatalf("listing interface addresses: %v", err)
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ip := networkIP(addr.(*net.IPNet))
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c, err := NewClient(net.JoinHostPort(ip.String(), port))
|
||||
if err != nil {
|
||||
t.Fatalf("new client: %v", err)
|
||||
}
|
||||
|
||||
// Skip re-tries to skip non-routable interfaces faster
|
||||
c.SetRetries(0)
|
||||
|
||||
ot, err := c.Send("a", "octet")
|
||||
if err != nil {
|
||||
t.Logf("start sending to %v: %v", ip, err)
|
||||
continue
|
||||
}
|
||||
_, err = ot.ReadFrom(io.LimitReader(
|
||||
newRandReader(rand.NewSource(42)), 42))
|
||||
if err != nil {
|
||||
t.Logf("sending to %v: %v", ip, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that read handler received IP that was used
|
||||
// to create the client.
|
||||
localIPMu.Lock()
|
||||
if localIP != nil && !localIP.IsUnspecified() { // Skip check if no packet info
|
||||
if !localIP.Equal(ip) {
|
||||
t.Errorf("sent to: %v, request packet: %v", ip, localIP)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Skip %v\n", ip)
|
||||
}
|
||||
localIPMu.Unlock()
|
||||
|
||||
it, err := c.Receive("a", "octet")
|
||||
if err != nil {
|
||||
t.Logf("start receiving from %v: %v", ip, err)
|
||||
continue
|
||||
}
|
||||
_, err = it.WriteTo(ioutil.Discard)
|
||||
if err != nil {
|
||||
t.Logf("receiving from %v: %v", ip, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that write handler received IP that was used
|
||||
// to create the client.
|
||||
localIPMu.Lock()
|
||||
if localIP != nil && !localIP.IsUnspecified() { // Skip check if no packet info
|
||||
if !localIP.Equal(ip) {
|
||||
t.Errorf("sent to: %v, request packet: %v", ip, localIP)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Skip %v\n", ip)
|
||||
}
|
||||
localIPMu.Unlock()
|
||||
|
||||
fmt.Printf("Done %v\n", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func networkIP(n *net.IPNet) net.IP {
|
||||
if ip := n.IP.To4(); ip != nil {
|
||||
return ip
|
||||
}
|
||||
if len(n.IP) == net.IPv6len {
|
||||
return n.IP
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestFileIOExceptions checks that errors returned by io.Reader or io.Writer used by
|
||||
// the handler are handled correctly.
|
||||
func TestReadWriteErrors(t *testing.T) {
|
||||
s := NewServer(
|
||||
func(_ string, rf io.ReaderFrom) error {
|
||||
_, err := rf.ReadFrom(&failingReader{}) // Read operation fails immediately.
|
||||
if err != errRead {
|
||||
t.Errorf("want: %v, got: %v", errRead, err)
|
||||
}
|
||||
// return no error from handler, client still should receive error
|
||||
return nil
|
||||
},
|
||||
func(_ string, wt io.WriterTo) error {
|
||||
_, err := wt.WriteTo(&failingWriter{}) // Write operation fails immediately.
|
||||
if err != errWrite {
|
||||
t.Errorf("want: %v, got: %v", errWrite, err)
|
||||
}
|
||||
// return no error from handler, client still should receive error
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{})
|
||||
if err != nil {
|
||||
t.Fatalf("listen UDP: %v", err)
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("parsing server port: %v", err)
|
||||
}
|
||||
|
||||
// Start server
|
||||
go func() {
|
||||
err := s.Serve(conn)
|
||||
if err != nil {
|
||||
t.Fatalf("running serve: %v", err)
|
||||
}
|
||||
}()
|
||||
defer s.Shutdown()
|
||||
|
||||
// Create client
|
||||
c, err := NewClient(net.JoinHostPort(localhost, port))
|
||||
if err != nil {
|
||||
t.Fatalf("creating new client: %v", err)
|
||||
}
|
||||
|
||||
ot, err := c.Send("a", "octet")
|
||||
if err != nil {
|
||||
t.Errorf("start sending: %v", err)
|
||||
}
|
||||
|
||||
_, err = ot.ReadFrom(io.LimitReader(
|
||||
newRandReader(rand.NewSource(42)), 42))
|
||||
if err == nil {
|
||||
t.Errorf("missing write error")
|
||||
}
|
||||
|
||||
_, err = c.Receive("a", "octet")
|
||||
if err == nil {
|
||||
t.Errorf("missing read error")
|
||||
}
|
||||
}
|
||||
|
||||
type failingReader struct{}
|
||||
|
||||
var errRead = errors.New("read error")
|
||||
|
||||
func (r *failingReader) Read(_ []byte) (int, error) {
|
||||
return 0, errRead
|
||||
}
|
||||
|
||||
type failingWriter struct{}
|
||||
|
||||
var errWrite = errors.New("write error")
|
||||
|
||||
func (r *failingWriter) Write(_ []byte) (int, error) {
|
||||
return 0, errWrite
|
||||
}
|
27
core/protocol/tftp/tftp.go
Normal file
@ -0,0 +1,27 @@
|
||||
package tftp
|
||||
|
||||
import (
|
||||
"HFish/core/protocol/tftp/libs"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func readHandler(filename string, rf io.ReaderFrom) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeHandler(filename string, wt io.WriterTo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Start(address string) {
|
||||
s := libs.NewServer(readHandler, writeHandler)
|
||||
s.SetTimeout(5 * time.Second)
|
||||
err := s.ListenAndServe(address)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "server: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
184
core/protocol/vnc/vnc.go
Normal file
@ -0,0 +1,184 @@
|
||||
package vnc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"fmt"
|
||||
"strings"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
"HFish/core/report"
|
||||
)
|
||||
|
||||
const VERSION = "RFB 003.008\n"
|
||||
const CHALLENGE = "\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
// 5900
|
||||
func Start(address string) {
|
||||
l, err := net.Listen("tcp", address)
|
||||
if nil != err {
|
||||
}
|
||||
log.Printf("Listening on %v", l.Addr())
|
||||
|
||||
/* Accept and handle clients */
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if nil != err {
|
||||
log.Fatalf("Error accepting connection: %v", err)
|
||||
}
|
||||
|
||||
arr := strings.Split(c.RemoteAddr().String(), ":")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("VNC", "VNC蜜罐", arr[0], "存在VNC扫描!", "0")
|
||||
} else {
|
||||
go report.ReportVnc("VNC蜜罐", "本机", arr[0], "存在VNC扫描!")
|
||||
}
|
||||
|
||||
go handle(c, )
|
||||
}
|
||||
}
|
||||
|
||||
func handle(c net.Conn) {
|
||||
defer c.Close()
|
||||
/* Send our version */
|
||||
if _, err := c.Write([]byte(VERSION)); nil != err {
|
||||
log.Printf(
|
||||
"%v Error before server version: %v",
|
||||
c.RemoteAddr(),
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
/* Get his version */
|
||||
ver := make([]byte, len(VERSION))
|
||||
n, err := io.ReadFull(c, ver)
|
||||
ver = ver[:n]
|
||||
if nil != err {
|
||||
log.Printf(
|
||||
"%v Disconnected before client version: %v",
|
||||
c.RemoteAddr(),
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
/* Handle versions 3 and 8 */
|
||||
var cver = string(ver)
|
||||
switch cver {
|
||||
case "RFB 003.008\n": /* Protocol version 3.8 */
|
||||
cver = "RFB 3.8"
|
||||
/* Send number of security types (1) and the offered type
|
||||
(2, VNC Auth) */
|
||||
/* TODO: Also, offer ALL the auths */
|
||||
if _, err := c.Write([]byte{
|
||||
0x01, /* We will send one offered auth type */
|
||||
0x02, /* VNC Auth */
|
||||
}); nil != err {
|
||||
log.Printf(
|
||||
"%v Unable to offer auth type (%v): %v",
|
||||
c.RemoteAddr(),
|
||||
cver,
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
/* Get security type client wants, which should be 2 for now */
|
||||
buf := make([]byte, 1)
|
||||
_, err = io.ReadFull(c, buf)
|
||||
if nil != err {
|
||||
log.Printf(
|
||||
"%v Unable to read accepted security type "+
|
||||
"(%v): %v",
|
||||
c.RemoteAddr(),
|
||||
cver,
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
if 0x02 != buf[0] {
|
||||
log.Printf(
|
||||
"%v Accepted unsupported security type "+
|
||||
"%v (%v)",
|
||||
c.RemoteAddr(),
|
||||
cver,
|
||||
buf[0],
|
||||
)
|
||||
return
|
||||
}
|
||||
case "RFB 003.003\n": /* Protocol version 3.3, which is ancient */
|
||||
cver = "RFB 3.3"
|
||||
/* Tell the client to use VNC auth */
|
||||
if _, err := c.Write([]byte{0, 0, 0, 2}); nil != err {
|
||||
log.Printf(
|
||||
"%v Unable to specify VNC auth (%v): %v",
|
||||
c.RemoteAddr(),
|
||||
cver,
|
||||
err,
|
||||
)
|
||||
}
|
||||
default:
|
||||
/* Send an error message */
|
||||
if _, err := c.Write(append(
|
||||
[]byte{
|
||||
0, /* 0 security types */
|
||||
0, 0, 0, 20, /* 20-character message */
|
||||
},
|
||||
/* Failure message */
|
||||
[]byte("Unsupported RFB version.")...,
|
||||
)); nil != err {
|
||||
log.Printf(
|
||||
"%v Unable to send unsupported version "+
|
||||
"message: %v",
|
||||
c.RemoteAddr(),
|
||||
err,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := c.Write([]byte(CHALLENGE)); nil != err {
|
||||
log.Printf(
|
||||
"%v Unable to send challenge: %v",
|
||||
c.RemoteAddr(),
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
/* Get response */
|
||||
buf := make([]byte, 16)
|
||||
n, err = io.ReadFull(c, buf)
|
||||
buf = buf[:n]
|
||||
if nil != err {
|
||||
if 0 == n {
|
||||
log.Printf(
|
||||
"%v Unable to read auth response: %v",
|
||||
c.RemoteAddr(),
|
||||
err,
|
||||
)
|
||||
} else {
|
||||
log.Printf(
|
||||
"%v Received incomplete auth response: "+
|
||||
"%q (%v)",
|
||||
c.RemoteAddr(),
|
||||
buf,
|
||||
err,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(c.RemoteAddr())
|
||||
|
||||
/* Tell client auth failed */
|
||||
c.Write(append(
|
||||
[]byte{
|
||||
0, 0, 0, 1, /* Failure word */
|
||||
0, 0, 0, 29, /* Message length */
|
||||
},
|
||||
/* Failure message */
|
||||
[]byte("Invalid username or password.")...,
|
||||
))
|
||||
}
|
@ -40,7 +40,7 @@ func alertx(id string, model string, typex string, projectName string, agent str
|
||||
}
|
||||
|
||||
// 上报 集群 状态
|
||||
func ReportAgentStatus(agentName string, agentIp string, webStatus string, deepStatus string, sshStatus string, redisStatus string, mysqlStatus string, httpStatus string, telnetStatus string, ftpStatus string, memCacheStatus string, plugStatus string) {
|
||||
func ReportAgentStatus(agentName string, agentIp string, webStatus string, deepStatus string, sshStatus string, redisStatus string, mysqlStatus string, httpStatus string, telnetStatus string, ftpStatus string, memCacheStatus string, plugStatus string, esStatus string, tftpStatus string, vncStatus string) {
|
||||
_, err := dbUtil.DB().Table("hfish_colony").Data(map[string]interface{}{
|
||||
"agent_name": agentName,
|
||||
"agent_ip": agentIp,
|
||||
@ -54,6 +54,9 @@ func ReportAgentStatus(agentName string, agentIp string, webStatus string, deepS
|
||||
"ftp_status": ftpStatus,
|
||||
"mem_cache_status": memCacheStatus,
|
||||
"plug_status": plugStatus,
|
||||
"es_status": esStatus,
|
||||
"tftp_status": tftpStatus,
|
||||
"vnc_status": vncStatus,
|
||||
"last_update_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}).InsertGetId()
|
||||
|
||||
@ -72,6 +75,9 @@ func ReportAgentStatus(agentName string, agentIp string, webStatus string, deepS
|
||||
"ftp_status": ftpStatus,
|
||||
"mem_cache_status": memCacheStatus,
|
||||
"plug_status": plugStatus,
|
||||
"es_status": esStatus,
|
||||
"tftp_status": tftpStatus,
|
||||
"vnc_status": vncStatus,
|
||||
"last_update_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}).Where("agent_name", agentName).Update()
|
||||
|
||||
@ -323,3 +329,52 @@ func ReportUpdateMemCche(id string, info string) {
|
||||
go alertx(id, "update", "MEMCACHE", "MemCache蜜罐", "", "", "", "", "", info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
// 上报 HTTP 代理
|
||||
func ReportHttp(projectName string, agent string, ipx string, info string) {
|
||||
// IP 不在白名单,进行上报
|
||||
if (isWhiteIp(ipx) == false) {
|
||||
country, region, city := ip.GetIp(ipx)
|
||||
id := insertInfo("HTTP", projectName, agent, ipx, country, region, city, info)
|
||||
go alertx(strconv.FormatInt(id, 10), "new", "HTTP", projectName, agent, ipx, country, region, city, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
// 上报 ES
|
||||
func ReportEs(projectName string, agent string, ipx string, info string) {
|
||||
// IP 不在白名单,进行上报
|
||||
if (isWhiteIp(ipx) == false) {
|
||||
country, region, city := ip.GetIp(ipx)
|
||||
id := insertInfo("ES", projectName, agent, ipx, country, region, city, info)
|
||||
go alertx(strconv.FormatInt(id, 10), "new", "ES", projectName, agent, ipx, country, region, city, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
// 上报 VNC
|
||||
func ReportVnc(projectName string, agent string, ipx string, info string) {
|
||||
// IP 不在白名单,进行上报
|
||||
if (isWhiteIp(ipx) == false) {
|
||||
country, region, city := ip.GetIp(ipx)
|
||||
id := insertInfo("VNC", projectName, agent, ipx, country, region, city, info)
|
||||
go alertx(strconv.FormatInt(id, 10), "new", "VNC", projectName, agent, ipx, country, region, city, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
// 上报 TFTP
|
||||
func ReportTFtp(ipx string, agent string, info string) int64 {
|
||||
if (isWhiteIp(ipx) == false) {
|
||||
country, region, city := ip.GetIp(ipx)
|
||||
id := insertInfo("TFTP", "TFTP蜜罐", agent, ipx, country, region, city, info)
|
||||
go alertx(strconv.FormatInt(id, 10), "new", "TFTP", "TFTP蜜罐", agent, ipx, country, region, city, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 更新 TFTP 操作
|
||||
func ReportUpdateTFtp(id string, info string) {
|
||||
if (id != "0") {
|
||||
go updateInfo(id, info)
|
||||
go alertx(id, "update", "TFTP", "TFTP蜜罐", "", "", "", "", "", info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
type Status struct {
|
||||
AgentIp string
|
||||
AgentName string
|
||||
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp, MemCahe, Plug string
|
||||
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp, MemCahe, Plug, ES, TFtp, Vnc string
|
||||
}
|
||||
|
||||
// 上报结果结构
|
||||
@ -39,7 +39,7 @@ func createClient() (*rpc.Client, string, bool) {
|
||||
return client, ipArr[0], true
|
||||
}
|
||||
|
||||
func reportStatus(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string, memCacheStatus string, plugStatus string) {
|
||||
func reportStatus(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string, memCacheStatus string, plugStatus string, esStatus string, tftpStatus string, vncStatus string) {
|
||||
client, addr, boolStatus := createClient()
|
||||
|
||||
if boolStatus {
|
||||
@ -58,6 +58,9 @@ func reportStatus(rpcName string, ftpStatus string, telnetStatus string, httpSta
|
||||
ftpStatus,
|
||||
memCacheStatus,
|
||||
plugStatus,
|
||||
esStatus,
|
||||
tftpStatus,
|
||||
vncStatus,
|
||||
}
|
||||
|
||||
var reply string
|
||||
@ -102,6 +105,6 @@ func ReportResult(typex string, projectName string, sourceIp string, info string
|
||||
return ""
|
||||
}
|
||||
|
||||
func Start(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string, memCacheStatus string, plugStatus string) {
|
||||
reportStatus(rpcName, ftpStatus, telnetStatus, httpStatus, mysqlStatus, redisStatus, sshStatus, webStatus, darkStatus, memCacheStatus, plugStatus)
|
||||
func Start(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string, memCacheStatus string, plugStatus string, esStatus string, tftpStatus string, vncStatus string) {
|
||||
reportStatus(rpcName, ftpStatus, telnetStatus, httpStatus, mysqlStatus, redisStatus, sshStatus, webStatus, darkStatus, memCacheStatus, plugStatus, esStatus, tftpStatus, vncStatus)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
type Status struct {
|
||||
AgentIp string
|
||||
AgentName string
|
||||
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp, MemCahe, Plug string
|
||||
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp, MemCahe, Plug, ES, TFtp, Vnc string
|
||||
}
|
||||
|
||||
// 上报结果结构
|
||||
@ -45,6 +45,9 @@ func (t *HFishRPCService) ReportStatus(s *Status, reply *string) error {
|
||||
s.Ftp,
|
||||
s.MemCahe,
|
||||
s.Plug,
|
||||
s.ES,
|
||||
s.TFtp,
|
||||
s.Vnc,
|
||||
)
|
||||
|
||||
return nil
|
||||
@ -61,8 +64,26 @@ func (t *HFishRPCService) ReportResult(r *Result, reply *string) error {
|
||||
go report.ReportWeb(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||
case "DEEP":
|
||||
go report.ReportDeepWeb(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||
case "HTTP":
|
||||
go report.ReportHttp(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||
case "ES":
|
||||
go report.ReportEs(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||
case "VNC":
|
||||
go report.ReportVnc(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||
case "TFTP":
|
||||
if r.Id == "0" {
|
||||
id := report.ReportTFtp(r.SourceIp, r.AgentName, r.Info)
|
||||
idx = strconv.FormatInt(id, 10)
|
||||
} else {
|
||||
go report.ReportUpdateTFtp(r.Id, r.Info)
|
||||
}
|
||||
case "SSH":
|
||||
go report.ReportSSH(r.SourceIp, r.AgentName, r.Info)
|
||||
if r.Id == "0" {
|
||||
id := report.ReportSSH(r.SourceIp, r.AgentName, r.Info)
|
||||
idx = strconv.FormatInt(id, 10)
|
||||
} else {
|
||||
go report.ReportUpdateSSH(r.Id, r.Info)
|
||||
}
|
||||
case "REDIS":
|
||||
if r.Id == "0" {
|
||||
id := report.ReportRedis(r.SourceIp, r.AgentName, r.Info)
|
||||
|
BIN
db/hfish.db
@ -16,6 +16,9 @@ CREATE TABLE `hfish_colony` (
|
||||
`ftp_status` int(2) NOT NULL DEFAULT '0',
|
||||
`mem_cache_status` int(2) NOT NULL DEFAULT '0',
|
||||
`plug_status` int(2) NOT NULL DEFAULT '0',
|
||||
`es_status` int(2) NOT NULL DEFAULT '0',
|
||||
`tftp_status` int(2) NOT NULL DEFAULT '0',
|
||||
`vnc_status` int(2) NOT NULL DEFAULT '0',
|
||||
`last_update_time` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `un_agent` (`agent_name`) USING BTREE
|
||||
|
BIN
images/es.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
images/hfish.png
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.3 MiB |
BIN
images/http.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
images/login.png
Before Width: | Height: | Size: 607 KiB After Width: | Height: | Size: 1.0 MiB |
BIN
images/tftp.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
images/vnc.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
@ -1,12 +1,6 @@
|
||||
[HFish] ::1 - [2019-09-15 20:35:25] "POST /login HTTP/1.1 200 169.667µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" "
|
||||
[GIN] 2019/09/15 - 20:35:25 | 200 | 404.526µs | ::1 | POST /login
|
||||
[HFish] ::1 - [2019-09-15 20:35:28] "POST /login HTTP/1.1 200 133.633µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" "
|
||||
[GIN] 2019/09/15 - 20:35:28 | 200 | 209.568µs | ::1 | POST /login
|
||||
[HFish] ::1 - [2019-09-15 20:35:28] "GET /dashboard HTTP/1.1 200 4.270281ms "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" "
|
||||
[GIN] 2019/09/15 - 20:35:28 | 200 | 4.306622ms | ::1 | GET /dashboard
|
||||
[HFish] ::1 - [2019-09-15 20:35:28] "GET /static/css/style.css HTTP/1.1 304 283.146µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" "
|
||||
[GIN] 2019/09/15 - 20:35:28 | 304 | 369.83µs | ::1 | GET /static/css/style.css
|
||||
[HFish] ::1 - [2019-09-15 20:35:28] "GET /get/dashboard/data HTTP/1.1 200 1.417361ms "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" "
|
||||
[GIN] 2019/09/15 - 20:35:28 | 200 | 1.46201ms | ::1 | GET /get/dashboard/data
|
||||
[HFish] ::1 - [2019-09-15 20:35:28] "GET /get/dashboard/pie_data HTTP/1.1 200 636.382µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" "
|
||||
[GIN] 2019/09/15 - 20:35:28 | 200 | 667.996µs | ::1 | GET /get/dashboard/pie_data
|
||||
[HFish] ::1 - [2019-10-28 22:09:47] "GET /logout HTTP/1.1 302 183.765µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36" "
|
||||
[GIN] 2019/10/28 - 22:09:47 | 302 | 253.81µs | ::1 | GET /logout
|
||||
[HFish] ::1 - [2019-10-28 22:09:47] "GET /login HTTP/1.1 200 1.775459ms "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36" "
|
||||
[GIN] 2019/10/28 - 22:09:47 | 200 | 1.808194ms | ::1 | GET /login
|
||||
[HFish] ::1 - [2019-10-28 22:09:47] "GET /static/images/hfish.png HTTP/1.1 200 8.204642ms "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36" "
|
||||
[GIN] 2019/10/28 - 22:09:47 | 200 | 8.262812ms | ::1 | GET /static/images/hfish.png
|
||||
|
2
main.go
@ -16,7 +16,7 @@ func main() {
|
||||
} else if args[1] == "init" || args[1] == "--init" {
|
||||
setting.Init()
|
||||
} else if args[1] == "version" || args[1] == "--version" {
|
||||
fmt.Println("v0.3.2")
|
||||
fmt.Println("v0.4")
|
||||
} else if args[1] == "run" || args[1] == "--run" {
|
||||
setting.Run()
|
||||
} else {
|
||||
|
7
make.sh
Executable file
@ -0,0 +1,7 @@
|
||||
../xgo --targets=darwin/amd64 -ldflags='-w -s' .
|
||||
../xgo --targets=darwin/386 -ldflags='-w -s' .
|
||||
../xgo --targets=linux/arm64 -ldflags='-w -s' .
|
||||
../xgo --targets=linux/amd64 -ldflags='-w -s' .
|
||||
../xgo --targets=linux/386 -ldflags='-w -s' .
|
||||
../xgo --targets=windows/amd64 -ldflags='-w -s' .
|
||||
../xgo --targets=windows/386 -ldflags='-w -s' .
|
@ -61,7 +61,7 @@ abbr[title] {
|
||||
}
|
||||
|
||||
.logoimg {
|
||||
width: 300px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
b,
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
static/images/avatar.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 39 KiB |
BIN
static/images/hfish.png
Normal file
After Width: | Height: | Size: 464 KiB |
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 3.6 KiB |
@ -21,6 +21,10 @@ import (
|
||||
"HFish/view/api"
|
||||
"HFish/utils/cors"
|
||||
"HFish/core/protocol/memcache"
|
||||
"HFish/core/protocol/tftp"
|
||||
"HFish/core/protocol/httpx"
|
||||
"HFish/core/protocol/elasticsearch"
|
||||
"HFish/core/protocol/vnc"
|
||||
)
|
||||
|
||||
func RunWeb(template string, index string, static string, url string) http.Handler {
|
||||
@ -135,14 +139,46 @@ func RunAdmin() http.Handler {
|
||||
}
|
||||
|
||||
func Run() {
|
||||
// 启动 vnc 蜜罐
|
||||
vncStatus := conf.Get("vnc", "status")
|
||||
|
||||
// 判断 vnc 蜜罐 是否开启
|
||||
if vncStatus == "1" {
|
||||
vncAddr := conf.Get("vnc", "addr")
|
||||
go vnc.Start(vncAddr)
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 elasticsearch 蜜罐
|
||||
esStatus := conf.Get("elasticsearch", "status")
|
||||
|
||||
// 判断 elasticsearch 蜜罐 是否开启
|
||||
if esStatus == "1" {
|
||||
esAddr := conf.Get("elasticsearch", "addr")
|
||||
go elasticsearch.Start(esAddr)
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 TFTP 蜜罐
|
||||
tftpStatus := conf.Get("tftp", "status")
|
||||
|
||||
// 判断 TFTP 蜜罐 是否开启
|
||||
if tftpStatus == "1" {
|
||||
tftpAddr := conf.Get("tftp", "addr")
|
||||
go tftp.Start(tftpAddr)
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 MemCache 蜜罐
|
||||
memCacheStatus := conf.Get("mem_cache", "status")
|
||||
|
||||
// 判断 MemCache 蜜罐 是否开启
|
||||
if memCacheStatus == "1" {
|
||||
memCacheAddr := conf.Get("mem_cache", "addr")
|
||||
memCacheRateLimit := conf.Get("mem_cache", "rate_limit")
|
||||
go memcache.Start(memCacheAddr, memCacheRateLimit)
|
||||
go memcache.Start(memCacheAddr, "4")
|
||||
}
|
||||
|
||||
//=========================//
|
||||
@ -169,14 +205,14 @@ func Run() {
|
||||
|
||||
//=========================//
|
||||
|
||||
//// 启动 HTTP 正向代理
|
||||
//httpStatus := conf.Get("http", "status")
|
||||
//
|
||||
//// 判断 HTTP 正向代理 是否开启
|
||||
//if httpStatus == "1" {
|
||||
// httpAddr := conf.Get("http", "addr")
|
||||
// go httpx.Start(httpAddr)
|
||||
//}
|
||||
// 启动 HTTP 正向代理
|
||||
httpStatus := conf.Get("http", "status")
|
||||
|
||||
// 判断 HTTP 正向代理 是否开启
|
||||
if httpStatus == "1" {
|
||||
httpAddr := conf.Get("http", "addr")
|
||||
go httpx.Start(httpAddr)
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
@ -298,7 +334,7 @@ func Run() {
|
||||
|
||||
for {
|
||||
// 这样写 提高IO读写性能
|
||||
go client.Start(rpcName, ftpStatus, telnetStatus, "0", mysqlStatus, redisStatus, sshStatus, webStatus, deepStatus, memCacheStatus, plugStatus)
|
||||
go client.Start(rpcName, ftpStatus, telnetStatus, httpStatus, mysqlStatus, redisStatus, sshStatus, webStatus, deepStatus, memCacheStatus, plugStatus, esStatus, tftpStatus, vncStatus)
|
||||
|
||||
time.Sleep(time.Duration(1) * time.Minute)
|
||||
}
|
||||
@ -335,7 +371,7 @@ func Help() {
|
||||
{K || __ _______ __
|
||||
| PP / // / __(_)__ / /
|
||||
| || / _ / _// (_-</ _ \
|
||||
(__\\ /_//_/_/ /_/___/_//_/ v0.3.2
|
||||
(__\\ /_//_/_/ /_/___/_//_/ v0.4
|
||||
`
|
||||
fmt.Println(color.Yellow(logo))
|
||||
fmt.Println(color.White(" A Safe and Active Attack Honeypot Fishing Framework System for Enterprises."))
|
||||
|
@ -32,6 +32,11 @@ func Html(c *gin.Context) {
|
||||
ftpStatus := conf.Get("ftp", "status")
|
||||
memCacheStatus := conf.Get("mem_cache", "status")
|
||||
|
||||
httpStatus := conf.Get("http", "status")
|
||||
tftpStatus := conf.Get("tftp", "status")
|
||||
esStatus := conf.Get("elasticsearch", "status")
|
||||
vncStatus := conf.Get("vnc", "status")
|
||||
|
||||
c.HTML(http.StatusOK, "dashboard.html", gin.H{
|
||||
"webSum": webSum,
|
||||
"sshSum": sshSum,
|
||||
@ -50,6 +55,10 @@ func Html(c *gin.Context) {
|
||||
"telnetStatus": telnetStatus,
|
||||
"ftpStatus": ftpStatus,
|
||||
"memCacheStatus": memCacheStatus,
|
||||
"httpStatus": httpStatus,
|
||||
"tftpStatus": tftpStatus,
|
||||
"esStatus": esStatus,
|
||||
"vncStatus": vncStatus,
|
||||
})
|
||||
}
|
||||
|
||||
|