更新0.4

This commit is contained in:
SanJin 2019-10-28 22:25:23 +08:00
parent 7b94e01e40
commit 8a53171076
53 changed files with 4010 additions and 79 deletions

View File

@ -101,7 +101,23 @@
## MemCache 蜜罐
![telnet.png](./images/memcache.png)
![memcache.png](./images/memcache.png)
## HTTP 蜜罐
![http.png](./images/http.png)
## TFTP 蜜罐
![tftp.png](./images/tftp.png)
## ES 蜜罐
![es.png](./images/es.png)
## VNC 蜜罐
![vnc.png](./images/vnc.png)
# 注意事项

View File

@ -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>';

View File

@ -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>

View File

@ -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>';
}

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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蜜罐 服务端地址 注意端口冲突

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

View File

@ -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
View 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

View File

@ -0,0 +1,8 @@
language: go
os:
- linux
- osx
before_install:
- ulimit -n 4096

View File

@ -0,0 +1,7 @@
Dmitri Popov
Mojo Talantikite
Giovanni Bajo
Andrew Danforth
Victor Lowther
minghuadev on github.com
Owen Mooney

View 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.

View File

@ -0,0 +1,180 @@
TFTP server and client library for Golang
=========================================
[![GoDoc](https://godoc.org/github.com/pin/tftp?status.svg)](https://godoc.org/github.com/pin/tftp)
[![Build Status](https://travis-ci.org/pin/tftp.svg?branch=master)](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 })
```

View 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++
}

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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
View 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.")...,
))
}

View File

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

View File

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

View File

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

Binary file not shown.

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
images/http.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/tftp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
images/vnc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -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

View File

@ -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
View 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' .

View File

@ -61,7 +61,7 @@ abbr[title] {
}
.logoimg {
width: 300px;
width: 200px;
}
b,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
static/images/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

BIN
static/images/hfish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -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."))

View File

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