croc
关于
croc
可以轻松安全地将内容从一台计算机发送到另一台计算机。一开始觉得这不就是飞鸽传书的功能吗?
其实他跟飞鸽出书还是有很多不同的,操作方式更加的简单而且发现对方更加的快速。我们来看怎么个用法。
croc
在github
下的地址在这里。
croc
是一种允许任何两台计算机简单安全地传输文件和文件夹的工具。AFAIK,croc是唯一可以执行以下所有操作的 CLI 文件传输工具:
- 允许任何两台计算机传输数据
- 提供端到端加密(使用 PAKE)
- 实现轻松的跨平台传输(Windows、Linux、Mac)
- 允许多个文件传输
- 允许恢复被中断的传输
- 不需要本地服务器或端口转发
- ipv6-优先和 ipv4 背后
- 可以使用代理,比如tor
安装
Windows
下的安装,直接就先下载exe
,如果是ubuntu下,可以使用deb
的安装方式。
dpkg -i croc_9.6.0_Linux-64bit.deb
使用–发送呵呵接收文件
croc
没有用户界面都是使用命令就能完成所有的动作,他可以在各个端进行传输,比如可以在windows
和ubuntu
下,相互发送数据。
比如在Windows
下:
我们想发送windows
下的bash_autocomplete
这个文件,我们在windows
的控制台下,输入croc send bash_autocomplete
表示发送bash_autocomplete
这个文件,这时候windows
生成了一个密钥的东西7584-flipper-tommy-tribal
,这个码就是发送码,接收端收到这个码就可以用来接收文件了。
croc send bash_autocomplete
Sending 'bash_autocomplete' (558 B)
Code is: 7584-flipper-tommy-tribal
On the other computer run
croc 7584-flipper-tommy-tribal
Sending (->192.168.100.170:49540)
bash_autocomplete 100% |████████████████████| (558/558 B, 132.038 kB/s)
ubuntu
下:
直接输入接收码·7584-flipper-tommy-tribal
,用来接收’bash_autocomplete’的文件。
root@ubuntu:~# croc 7584-flipper-tommy-tribal
Accept 'bash_autocomplete' (558 B)? (Y/n) y
Receiving (<-192.168.100.1:9009)
bash_autocomplete 100% |████████████████████| (558/558 B, 125.051 kB/s)
这样我们的文件就接收完成了。
如果还有别的需求可以看更多的文档。
代码分析
croc
是用go语言写的,看看核心代码,这里是接收文件的逻辑,可以大致的看下,从配对到文件的发送都写在这里。
// Receive will receive a file
func (c *Client) Receive() (err error) {
fmt.Fprintf(os.Stderr, "connecting...")
// recipient will look for peers first
// and continue if it doesn't find any within 100 ms
usingLocal := false
isIPset := false
if c.Options.OnlyLocal || c.Options.IP != "" {
c.Options.RelayAddress = ""
c.Options.RelayAddress6 = ""
}
if c.Options.IP != "" {
// check ip version
if strings.Count(c.Options.IP, ":") >= 2 {
log.Debug("assume ipv6")
c.Options.RelayAddress6 = c.Options.IP
}
if strings.Contains(c.Options.IP, ".") {
log.Debug("assume ipv4")
c.Options.RelayAddress = c.Options.IP
}
isIPset = true
}
if !c.Options.DisableLocal && !isIPset {
log.Debug("attempt to discover peers")
var discoveries []peerdiscovery.Discovered
var wgDiscovery sync.WaitGroup
var dmux sync.Mutex
wgDiscovery.Add(2)
go func() {
defer wgDiscovery.Done()
ipv4discoveries, err1 := peerdiscovery.Discover(peerdiscovery.Settings{
Limit: 1,
Payload: []byte("ok"),
Delay: 20 * time.Millisecond,
TimeLimit: 200 * time.Millisecond,
})
if err1 == nil && len(ipv4discoveries) > 0 {
dmux.Lock()
err = err1
discoveries = append(discoveries, ipv4discoveries...)
dmux.Unlock()
}
}()
go func() {
defer wgDiscovery.Done()
ipv6discoveries, err1 := peerdiscovery.Discover(peerdiscovery.Settings{
Limit: 1,
Payload: []byte("ok"),
Delay: 20 * time.Millisecond,
TimeLimit: 200 * time.Millisecond,
IPVersion: peerdiscovery.IPv6,
})
if err1 == nil && len(ipv6discoveries) > 0 {
dmux.Lock()
err = err1
discoveries = append(discoveries, ipv6discoveries...)
dmux.Unlock()
}
}()
wgDiscovery.Wait()
if err == nil && len(discoveries) > 0 {
log.Debugf("all discoveries: %+v", discoveries)
for i := 0; i < len(discoveries); i++ {
log.Debugf("discovery %d has payload: %+v", i, discoveries[i])
if !bytes.HasPrefix(discoveries[i].Payload, []byte("croc")) {
log.Debug("skipping discovery")
continue
}
log.Debug("switching to local")
portToUse := string(bytes.TrimPrefix(discoveries[i].Payload, []byte("croc")))
if portToUse == "" {
portToUse = models.DEFAULT_PORT
}
address := net.JoinHostPort(discoveries[i].Address, portToUse)
errPing := tcp.PingServer(address)
if errPing == nil {
log.Debugf("successfully pinged '%s'", address)
c.Options.RelayAddress = address
c.ExternalIPConnected = c.Options.RelayAddress
c.Options.RelayAddress6 = ""
usingLocal = true
break
} else {
log.Debugf("could not ping: %+v", errPing)
}
}
}
log.Debugf("discoveries: %+v", discoveries)
log.Debug("establishing connection")
}
var banner string
durations := []time.Duration{200 * time.Millisecond, 5 * time.Second}
err = fmt.Errorf("found no addresses to connect")
for i, address := range []string{c.Options.RelayAddress6, c.Options.RelayAddress} {
if address == "" {
continue
}
var host, port string
host, port, _ = net.SplitHostPort(address)
// Default port to :9009
if port == "" {
host = address
port = models.DEFAULT_PORT
}
log.Debugf("got host '%v' and port '%v'", host, port)
address = net.JoinHostPort(host, port)
log.Debugf("trying connection to %s", address)
c.conn[0], banner, c.ExternalIP, err = tcp.ConnectToTCPServer(address, c.Options.RelayPassword, c.Options.SharedSecret[:3], durations[i])
if err == nil {
c.Options.RelayAddress = address
break
}
log.Debugf("could not establish '%s'", address)
}
if err != nil {
err = fmt.Errorf("could not connect to %s: %w", c.Options.RelayAddress, err)
log.Debug(err)
return
}
log.Debugf("receiver connection established: %+v", c.conn[0])
log.Debugf("banner: %s", banner)
if !usingLocal && !c.Options.DisableLocal && !isIPset {
// ask the sender for their local ips and port
// and try to connect to them
log.Debug("sending ips?")
var data []byte
if err := c.conn[0].Send(ipRequest); err != nil {
log.Errorf("ips send error: %v", err)
}
data, err = c.conn[0].Receive()
if err != nil {
return
}
log.Debugf("ips data: %s", data)
var ips []string
if err := json.Unmarshal(data, &ips); err != nil {
log.Debugf("ips unmarshal error: %v", err)
}
if len(ips) > 1 {
port := ips[0]
ips = ips[1:]
for _, ip := range ips {
ipv4Addr, ipv4Net, errNet := net.ParseCIDR(fmt.Sprintf("%s/24", ip))
log.Debugf("ipv4Add4: %+v, ipv4Net: %+v, err: %+v", ipv4Addr, ipv4Net, errNet)
localIps, _ := utils.GetLocalIPs()
haveLocalIP := false
for _, localIP := range localIps {
localIPparsed := net.ParseIP(localIP)
if ipv4Net.Contains(localIPparsed) {
haveLocalIP = true
break
}
}
if !haveLocalIP {
log.Debugf("%s is not a local IP, skipping", ip)
continue
}
serverTry := net.JoinHostPort(ip, port)
conn, banner2, externalIP, errConn := tcp.ConnectToTCPServer(serverTry, c.Options.RelayPassword, c.Options.SharedSecret[:3], 500*time.Millisecond)
if errConn != nil {
log.Debug(errConn)
log.Debugf("could not connect to " + serverTry)
continue
}
log.Debugf("local connection established to %s", serverTry)
log.Debugf("banner: %s", banner2)
// reset to the local port
banner = banner2
c.Options.RelayAddress = serverTry
c.ExternalIP = externalIP
c.conn[0].Close()
c.conn[0] = nil
c.conn[0] = conn
break
}
}
}
if err := c.conn[0].Send(handshakeRequest); err != nil {
log.Errorf("handshake send error: %v", err)
}
c.Options.RelayPorts = strings.Split(banner, ",")
if c.Options.NoMultiplexing {
log.Debug("no multiplexing")
c.Options.RelayPorts = []string{c.Options.RelayPorts[0]}
}
log.Debug("exchanged header message")
fmt.Fprintf(os.Stderr, "\rsecuring channel...")
err = c.transfer()
if err == nil {
if c.numberOfTransferredFiles+len(c.EmptyFoldersToTransfer) == 0 {
fmt.Fprintf(os.Stderr, "\rNo files transferred.")
}
}
return
}