udp: Rotate connection ID generation IVs every hour
This commit is contained in:
parent
afb22c3df6
commit
4c3cd6cc0a
4 changed files with 102 additions and 33 deletions
|
@ -5,31 +5,37 @@
|
||||||
package udp
|
package udp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var connectionKey, connectionIV []byte
|
type ConnectionIDGenerator struct {
|
||||||
|
iv, iv2 []byte
|
||||||
|
block cipher.Block
|
||||||
|
}
|
||||||
|
|
||||||
func InitConnectionIDEncryption() error {
|
func (g *ConnectionIDGenerator) Init() error {
|
||||||
connectionKey = make([]byte, 16)
|
key := make([]byte, 16)
|
||||||
_, err := rand.Read(connectionKey)
|
_, err := rand.Read(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionIV = make([]byte, 16)
|
g.block, err = aes.NewCipher(key)
|
||||||
_, err = rand.Read(connectionIV)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateConnectionID(ip []byte) []byte {
|
|
||||||
block, err := aes.NewCipher(connectionKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return g.NewIV()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ConnectionIDGenerator) Generate(ip []byte) []byte {
|
||||||
|
return g.generate(ip, g.iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ConnectionIDGenerator) generate(ip []byte, iv []byte) []byte {
|
||||||
if len(ip) > 16 {
|
if len(ip) > 16 {
|
||||||
panic("IP larger than 16 bytes")
|
panic("IP larger than 16 bytes")
|
||||||
}
|
}
|
||||||
|
@ -39,7 +45,7 @@ func GenerateConnectionID(ip []byte) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
ct := make([]byte, 16)
|
ct := make([]byte, 16)
|
||||||
stream := cipher.NewCFBDecrypter(block, connectionIV)
|
stream := cipher.NewCFBDecrypter(g.block, iv)
|
||||||
stream.XORKeyStream(ct, ip)
|
stream.XORKeyStream(ct, ip)
|
||||||
|
|
||||||
for i := len(ip) - 1; i >= 8; i-- {
|
for i := len(ip) - 1; i >= 8; i-- {
|
||||||
|
@ -49,6 +55,28 @@ func GenerateConnectionID(ip []byte) []byte {
|
||||||
return ct[:8]
|
return ct[:8]
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func (g *ConnectionIDGenerator) Matches(id []byte, ip []byte) bool {
|
||||||
InitConnectionIDEncryption()
|
if expected := g.generate(ip, g.iv); bytes.Equal(id, expected) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if iv2 := g.iv2; iv2 != nil {
|
||||||
|
if expected := g.generate(ip, iv2); bytes.Equal(id, expected) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ConnectionIDGenerator) NewIV() error {
|
||||||
|
newiv := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(newiv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.iv2 = g.iv
|
||||||
|
g.iv = newiv
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInitReturnsNoError(t *testing.T) {
|
func TestInitReturnsNoError(t *testing.T) {
|
||||||
if err := InitConnectionIDEncryption(); err != nil {
|
gen := &ConnectionIDGenerator{}
|
||||||
t.Error("InitConnectionIDEncryption returned", err)
|
if err := gen.Init(); err != nil {
|
||||||
|
t.Error("Init returned", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGenerateConnectionID(t *testing.T, ip net.IP) {
|
func testGenerateConnectionID(t *testing.T, ip net.IP) {
|
||||||
InitConnectionIDEncryption()
|
gen := &ConnectionIDGenerator{}
|
||||||
|
gen.Init()
|
||||||
|
|
||||||
id1 := GenerateConnectionID(ip)
|
id1 := gen.Generate(ip)
|
||||||
id2 := GenerateConnectionID(ip)
|
id2 := gen.Generate(ip)
|
||||||
|
|
||||||
if !bytes.Equal(id1, id2) {
|
if !bytes.Equal(id1, id2) {
|
||||||
t.Errorf("Connection ID mismatch: %x != %x", id1, id2)
|
t.Errorf("Connection ID mismatch: %x != %x", id1, id2)
|
||||||
|
@ -42,3 +44,30 @@ func TestGenerateConnectionIDIPv4(t *testing.T) {
|
||||||
func TestGenerateConnectionIDIPv6(t *testing.T) {
|
func TestGenerateConnectionIDIPv6(t *testing.T) {
|
||||||
testGenerateConnectionID(t, net.ParseIP("1:2:3:4::5:6"))
|
testGenerateConnectionID(t, net.ParseIP("1:2:3:4::5:6"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMatchesWorksWithPreviousIV(t *testing.T) {
|
||||||
|
gen := &ConnectionIDGenerator{}
|
||||||
|
gen.Init()
|
||||||
|
ip := net.ParseIP("192.168.1.123").To4()
|
||||||
|
|
||||||
|
id1 := gen.Generate(ip)
|
||||||
|
if !gen.Matches(id1, ip) {
|
||||||
|
t.Errorf("Connection ID mismatch for current IV")
|
||||||
|
}
|
||||||
|
|
||||||
|
gen.NewIV()
|
||||||
|
if !gen.Matches(id1, ip) {
|
||||||
|
t.Errorf("Connection ID mismatch for previous IV")
|
||||||
|
}
|
||||||
|
|
||||||
|
id2 := gen.Generate(ip)
|
||||||
|
gen.NewIV()
|
||||||
|
|
||||||
|
if gen.Matches(id1, ip) {
|
||||||
|
t.Errorf("Connection ID matched for discarded IV")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gen.Matches(id2, ip) {
|
||||||
|
t.Errorf("Connection ID mismatch for previous IV")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,6 @@ func (s *Server) handlePacket(packet []byte, addr *net.UDPAddr) (response []byte
|
||||||
connID := packet[0:8]
|
connID := packet[0:8]
|
||||||
action := binary.BigEndian.Uint32(packet[8:12])
|
action := binary.BigEndian.Uint32(packet[8:12])
|
||||||
transactionID := packet[12:16]
|
transactionID := packet[12:16]
|
||||||
generatedConnID := GenerateConnectionID(addr.IP)
|
|
||||||
|
|
||||||
writer := &Writer{
|
writer := &Writer{
|
||||||
buf: new(bytes.Buffer),
|
buf: new(bytes.Buffer),
|
||||||
|
@ -62,7 +61,7 @@ func (s *Server) handlePacket(packet []byte, addr *net.UDPAddr) (response []byte
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if action != 0 && !bytes.Equal(connID, generatedConnID) {
|
if action != 0 && !s.connIDGen.Matches(connID, addr.IP) {
|
||||||
writer.WriteError(errBadConnectionID)
|
writer.WriteError(errBadConnectionID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -75,7 +74,7 @@ func (s *Server) handlePacket(packet []byte, addr *net.UDPAddr) (response []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.writeHeader(0)
|
writer.writeHeader(0)
|
||||||
writer.buf.Write(generatedConnID)
|
writer.buf.Write(s.connIDGen.Generate(addr.IP))
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
actionName = "announce"
|
actionName = "announce"
|
||||||
|
@ -120,13 +119,13 @@ func (s *Server) newAnnounce(packet []byte, ip net.IP) (*models.Announce, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipbuf := packet[84:88]
|
ipbuf := packet[84:88]
|
||||||
if !bytes.Equal(ipbuf, []byte{0, 0, 0, 0}) {
|
if s.config.AllowIPSpoofing && !bytes.Equal(ipbuf, []byte{0, 0, 0, 0}) {
|
||||||
ip = net.ParseIP(string(ipbuf))
|
ip = net.ParseIP(string(ipbuf))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return nil, errMalformedIP
|
return nil, errMalformedIP
|
||||||
}
|
} else if ipv4 := ip.To4(); ipv4 != nil {
|
||||||
if ipv4 := ip.To4(); ipv4 != nil {
|
|
||||||
ip = ipv4
|
ip = ipv4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
udp/udp.go
27
udp/udp.go
|
@ -19,10 +19,10 @@ import (
|
||||||
|
|
||||||
// Server represents a UDP torrent tracker.
|
// Server represents a UDP torrent tracker.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
tracker *tracker.Tracker
|
tracker *tracker.Tracker
|
||||||
|
connIDGen *ConnectionIDGenerator
|
||||||
done bool
|
done bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) serve() error {
|
func (s *Server) serve() error {
|
||||||
|
@ -66,7 +66,7 @@ func (s *Server) serve() error {
|
||||||
|
|
||||||
if glog.V(2) {
|
if glog.V(2) {
|
||||||
duration := time.Since(start)
|
duration := time.Since(start)
|
||||||
glog.Infof("[UDP - %9s] %s", duration, action)
|
glog.Infof("[UDP - %9s] %s %s", duration, action, addr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,13 @@ func (s *Server) serve() error {
|
||||||
func (s *Server) Serve() {
|
func (s *Server) Serve() {
|
||||||
glog.V(0).Info("Starting UDP on ", s.config.UDPListenAddr)
|
glog.V(0).Info("Starting UDP on ", s.config.UDPListenAddr)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Generate a new IV every hour.
|
||||||
|
for range time.Tick(time.Hour) {
|
||||||
|
s.connIDGen.NewIV()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if err := s.serve(); err != nil {
|
if err := s.serve(); err != nil {
|
||||||
glog.Errorf("Failed to run UDP server: %s", err.Error())
|
glog.Errorf("Failed to run UDP server: %s", err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -92,8 +99,14 @@ func (s *Server) Stop() {
|
||||||
|
|
||||||
// NewServer returns a new UDP server for a given configuration and tracker.
|
// NewServer returns a new UDP server for a given configuration and tracker.
|
||||||
func NewServer(cfg *config.Config, tkr *tracker.Tracker) *Server {
|
func NewServer(cfg *config.Config, tkr *tracker.Tracker) *Server {
|
||||||
|
gen := &ConnectionIDGenerator{}
|
||||||
|
if err := gen.Init(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
tracker: tkr,
|
tracker: tkr,
|
||||||
|
connIDGen: gen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue