2018-03-24 00:18:00 +01:00
package dht
import (
"encoding/hex"
"net"
"time"
2018-04-03 20:14:59 +02:00
"github.com/lbryio/lbry.go/util"
2018-03-24 00:18:00 +01:00
"github.com/davecgh/go-spew/spew"
"github.com/lyoshenka/bencode"
log "github.com/sirupsen/logrus"
)
2018-04-03 18:14:04 +02:00
// handlePacket handles packets received from udp.
2018-03-24 00:18:00 +01:00
func handlePacket ( dht * DHT , pkt packet ) {
2018-04-03 18:14:04 +02:00
//log.Debugf("[%s] Received message from %s:%s (%d bytes) %s", dht.node.id.HexShort(), pkt.raddr.IP.String(), strconv.Itoa(pkt.raddr.Port), len(pkt.data), hex.EncodeToString(pkt.data))
2018-03-24 00:18:00 +01:00
2018-04-03 20:14:59 +02:00
if ! util . InSlice ( string ( pkt . data [ 0 : 5 ] ) , [ ] string { "d1:0i" , "di0ei" } ) {
log . Errorf ( "[%s] data is not a well-formatted dict: (%d bytes) %s" , dht . node . id . HexShort ( ) , len ( pkt . data ) , hex . EncodeToString ( pkt . data ) )
2018-03-24 00:18:00 +01:00
return
}
2018-04-03 20:14:59 +02:00
// TODO: test this stuff more thoroughly
// the following is a bit of a hack, but it lets us avoid decoding every message twice
// it depends on the data being a dict with 0 as the first key (so it starts with "d1:0i") and the message type as the first value
2018-03-24 00:18:00 +01:00
2018-04-03 20:14:59 +02:00
switch pkt . data [ 5 ] {
case '0' + requestType :
2018-03-24 00:18:00 +01:00
request := Request { }
2018-04-03 20:14:59 +02:00
err := bencode . DecodeBytes ( pkt . data , & request )
2018-03-24 00:18:00 +01:00
if err != nil {
2018-04-03 20:14:59 +02:00
log . Errorf ( "[%s] error decoding request: %s: (%d bytes) %s" , dht . node . id . HexShort ( ) , err . Error ( ) , len ( pkt . data ) , hex . EncodeToString ( pkt . data ) )
2018-03-24 00:18:00 +01:00
return
}
2018-04-04 17:43:27 +02:00
log . Debugf ( "[%s] query %s: received request from %s: %s(%s)" , dht . node . id . HexShort ( ) , request . ID . HexShort ( ) , request . NodeID . HexShort ( ) , request . Method , request . ArgsDebug ( ) )
2018-03-24 00:18:00 +01:00
handleRequest ( dht , pkt . raddr , request )
2018-04-03 20:14:59 +02:00
case '0' + responseType :
2018-03-24 00:18:00 +01:00
response := Response { }
2018-04-03 20:14:59 +02:00
err := bencode . DecodeBytes ( pkt . data , & response )
2018-03-24 00:18:00 +01:00
if err != nil {
2018-04-03 20:14:59 +02:00
log . Errorf ( "[%s] error decoding response: %s: (%d bytes) %s" , dht . node . id . HexShort ( ) , err . Error ( ) , len ( pkt . data ) , hex . EncodeToString ( pkt . data ) )
2018-03-24 00:18:00 +01:00
return
}
2018-04-03 19:38:01 +02:00
log . Debugf ( "[%s] query %s: received response from %s: %s" , dht . node . id . HexShort ( ) , response . ID . HexShort ( ) , response . NodeID . HexShort ( ) , response . ArgsDebug ( ) )
2018-03-24 00:18:00 +01:00
handleResponse ( dht , pkt . raddr , response )
2018-04-03 20:14:59 +02:00
case '0' + errorType :
2018-04-03 19:38:01 +02:00
e := Error { }
2018-04-03 20:14:59 +02:00
err := bencode . DecodeBytes ( pkt . data , & e )
2018-04-03 19:38:01 +02:00
if err != nil {
2018-04-03 20:14:59 +02:00
log . Errorf ( "[%s] error decoding error: %s: (%d bytes) %s" , dht . node . id . HexShort ( ) , err . Error ( ) , len ( pkt . data ) , hex . EncodeToString ( pkt . data ) )
2018-04-03 19:38:01 +02:00
return
2018-03-24 00:18:00 +01:00
}
2018-04-03 19:38:01 +02:00
log . Debugf ( "[%s] query %s: received error from %s: %s" , dht . node . id . HexShort ( ) , e . ID . HexShort ( ) , e . NodeID . HexShort ( ) , e . ExceptionType )
2018-03-24 00:18:00 +01:00
handleError ( dht , pkt . raddr , e )
default :
2018-04-03 20:14:59 +02:00
log . Errorf ( "[%s] invalid message type: %s" , dht . node . id . HexShort ( ) , pkt . data [ 5 ] )
2018-03-24 00:18:00 +01:00
return
}
}
// handleRequest handles the requests received from udp.
func handleRequest ( dht * DHT , addr * net . UDPAddr , request Request ) {
2018-04-03 19:38:01 +02:00
if request . NodeID . Equals ( dht . node . id ) {
2018-03-24 00:18:00 +01:00
log . Warn ( "ignoring self-request" )
return
}
switch request . Method {
case pingMethod :
2018-04-03 19:38:01 +02:00
send ( dht , addr , Response { ID : request . ID , NodeID : dht . node . id , Data : pingSuccessResponse } )
2018-03-24 00:18:00 +01:00
case storeMethod :
// TODO: we should be sending the IP in the request, not just using the sender's IP
// TODO: should we be using StoreArgs.NodeID or StoreArgs.Value.LbryID ???
dht . store . Upsert ( request . StoreArgs . BlobHash , Node { id : request . StoreArgs . NodeID , ip : addr . IP , port : request . StoreArgs . Value . Port } )
2018-04-03 19:38:01 +02:00
send ( dht , addr , Response { ID : request . ID , NodeID : dht . node . id , Data : storeSuccessResponse } )
2018-03-24 00:18:00 +01:00
case findNodeMethod :
2018-04-04 17:43:27 +02:00
if len ( request . Args ) != 1 {
log . Errorln ( "invalid number of args" )
2018-03-24 00:18:00 +01:00
return
}
if len ( request . Args [ 0 ] ) != nodeIDLength {
log . Errorln ( "invalid node id" )
return
}
doFindNodes ( dht , addr , request )
case findValueMethod :
2018-04-04 17:43:27 +02:00
if len ( request . Args ) != 1 {
log . Errorln ( "invalid number of args" )
2018-03-24 00:18:00 +01:00
return
}
if len ( request . Args [ 0 ] ) != nodeIDLength {
2018-04-04 17:43:27 +02:00
log . Errorln ( "invalid blob hash" )
2018-03-24 00:18:00 +01:00
return
}
2018-04-04 17:43:27 +02:00
if nodes := dht . store . Get ( newBitmapFromString ( request . Args [ 0 ] ) ) ; len ( nodes ) > 0 {
send ( dht , addr , Response {
ID : request . ID ,
NodeID : dht . node . id ,
FindValueKey : request . Args [ 0 ] ,
FindNodeData : nodes ,
} )
2018-03-24 00:18:00 +01:00
} else {
doFindNodes ( dht , addr , request )
}
default :
// send(dht, addr, makeError(t, protocolError, "invalid q"))
log . Errorln ( "invalid request method" )
return
}
2018-04-03 19:38:01 +02:00
node := Node { id : request . NodeID , ip : addr . IP , port : addr . Port }
2018-03-24 00:18:00 +01:00
dht . rt . Update ( node )
}
func doFindNodes ( dht * DHT , addr * net . UDPAddr , request Request ) {
nodeID := newBitmapFromString ( request . Args [ 0 ] )
2018-03-29 03:05:27 +02:00
closestNodes := dht . rt . GetClosest ( nodeID , bucketSize )
2018-03-24 00:18:00 +01:00
if len ( closestNodes ) > 0 {
2018-04-03 19:38:01 +02:00
response := Response { ID : request . ID , NodeID : dht . node . id , FindNodeData : make ( [ ] Node , len ( closestNodes ) ) }
2018-03-24 00:18:00 +01:00
for i , n := range closestNodes {
2018-03-29 03:05:27 +02:00
response . FindNodeData [ i ] = n
2018-03-24 00:18:00 +01:00
}
send ( dht , addr , response )
} else {
log . Warn ( "no nodes in routing table" )
}
}
// handleResponse handles responses received from udp.
func handleResponse ( dht * DHT , addr * net . UDPAddr , response Response ) {
tx := dht . tm . Find ( response . ID , addr )
if tx != nil {
2018-04-04 17:43:27 +02:00
tx . res <- response
2018-03-24 00:18:00 +01:00
}
2018-04-03 19:38:01 +02:00
node := Node { id : response . NodeID , ip : addr . IP , port : addr . Port }
2018-03-24 00:18:00 +01:00
dht . rt . Update ( node )
}
// handleError handles errors received from udp.
func handleError ( dht * DHT , addr * net . UDPAddr , e Error ) {
spew . Dump ( e )
2018-04-03 19:38:01 +02:00
node := Node { id : e . NodeID , ip : addr . IP , port : addr . Port }
2018-03-24 00:18:00 +01:00
dht . rt . Update ( node )
}
2018-03-29 03:05:27 +02:00
// send sends data to a udp address
2018-03-24 00:18:00 +01:00
func send ( dht * DHT , addr * net . UDPAddr , data Message ) error {
encoded , err := bencode . EncodeBytes ( data )
if err != nil {
return err
}
2018-04-03 18:14:04 +02:00
if req , ok := data . ( Request ) ; ok {
log . Debugf ( "[%s] query %s: sending request to %s (%d bytes) %s(%s)" ,
2018-04-04 17:43:27 +02:00
dht . node . id . HexShort ( ) , req . ID . HexShort ( ) , addr . String ( ) , len ( encoded ) , req . Method , req . ArgsDebug ( ) )
2018-04-03 18:14:04 +02:00
} else if res , ok := data . ( Response ) ; ok {
log . Debugf ( "[%s] query %s: sending response to %s (%d bytes) %s" ,
2018-04-03 19:38:01 +02:00
dht . node . id . HexShort ( ) , res . ID . HexShort ( ) , addr . String ( ) , len ( encoded ) , res . ArgsDebug ( ) )
2018-04-03 18:14:04 +02:00
} else {
log . Debugf ( "[%s] (%d bytes) %s" , dht . node . id . HexShort ( ) , len ( encoded ) , spew . Sdump ( data ) )
}
2018-03-24 00:18:00 +01:00
dht . conn . SetWriteDeadline ( time . Now ( ) . Add ( time . Second * 15 ) )
_ , err = dht . conn . WriteToUDP ( encoded , addr )
return err
}