Ejemplo n.º 1
0
func TestUDPDestinationEquals(t *testing.T) {
	assert := assert.On(t)

	dest := v2net.UDPDestination(v2net.IPAddress([]byte{1, 2, 3, 4}), 80)
	assert.Bool(dest.Equals(nil)).IsFalse()

	dest2 := v2net.UDPDestination(v2net.IPAddress([]byte{1, 2, 3, 4}), 80)
	assert.Bool(dest.Equals(dest2)).IsTrue()

	dest3 := v2net.TCPDestination(v2net.IPAddress([]byte{1, 2, 3, 4}), 80)
	assert.Bool(dest.Equals(dest3)).IsFalse()

	dest4 := v2net.UDPDestination(v2net.DomainAddress("v2ray.com"), 80)
	assert.Bool(dest.Equals(dest4)).IsFalse()
}
Ejemplo n.º 2
0
func (this *Config) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		Servers []v2net.AddressJson          `json:"servers"`
		Hosts   map[string]v2net.AddressJson `json:"hosts"`
	}
	jsonConfig := new(JsonConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return err
	}
	this.NameServers = make([]v2net.Destination, len(jsonConfig.Servers))
	for idx, server := range jsonConfig.Servers {
		this.NameServers[idx] = v2net.UDPDestination(server.Address, v2net.Port(53))
	}

	if jsonConfig.Hosts != nil {
		this.Hosts = make(map[string]net.IP)
		for domain, ip := range jsonConfig.Hosts {
			if ip.Address.IsDomain() {
				return errors.New(ip.Address.String() + " is not an IP.")
			}
			this.Hosts[domain] = ip.Address.IP()
		}
	}

	return nil
}
Ejemplo n.º 3
0
func (this *DokodemoDoor) handleUDPPackets() {
	for this.accepting {
		buffer := alloc.NewBuffer()
		this.udpMutex.RLock()
		if !this.accepting {
			this.udpMutex.RUnlock()
			return
		}
		nBytes, addr, err := this.udpConn.ReadFromUDP(buffer.Value)
		this.udpMutex.RUnlock()
		buffer.Slice(0, nBytes)
		if err != nil {
			buffer.Release()
			log.Error("Dokodemo failed to read from UDP: ", err)
			return
		}

		packet := v2net.NewPacket(v2net.UDPDestination(this.address, this.port), buffer, false)
		ray := this.space.PacketDispatcher().DispatchToOutbound(packet)
		close(ray.InboundInput())

		for payload := range ray.InboundOutput() {
			this.udpMutex.RLock()
			if !this.accepting {
				this.udpMutex.RUnlock()
				return
			}
			this.udpConn.WriteToUDP(payload.Value, addr)
			this.udpMutex.RUnlock()
		}
	}
}
Ejemplo n.º 4
0
// Destination is the final destination of this request.
func (this *VMessRequest) Destination() v2net.Destination {
	if this.Command == CmdTCP {
		return v2net.TCPDestination(this.Address, this.Port)
	} else {
		return v2net.UDPDestination(this.Address, this.Port)
	}
}
Ejemplo n.º 5
0
func TestUDPDestination(t *testing.T) {
	assert := assert.On(t)

	dest := v2net.UDPDestination(v2net.IPAddress([]byte{0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88}), 53)
	assert.Destination(dest).IsNotTCP()
	assert.Destination(dest).IsUDP()
	assert.Destination(dest).EqualsString("udp:[2001:4860:4860::8888]:53")
}
Ejemplo n.º 6
0
func (this *SocksServer) ListenUDP(port v2net.Port) error {
	this.udpServer = hub.NewUDPServer(this.packetDispatcher)
	udpHub, err := hub.ListenUDP(port, this.handleUDPPayload)
	if err != nil {
		log.Error("Socks: Failed to listen on udp port ", port)
		return err
	}
	this.udpMutex.Lock()
	this.udpAddress = v2net.UDPDestination(this.config.Address, port)
	this.udpHub = udpHub
	this.udpMutex.Unlock()
	return nil
}
Ejemplo n.º 7
0
func (server *Server) Start() (v2net.Destination, error) {
	conn, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   []byte{0, 0, 0, 0},
		Port: int(server.Port),
		Zone: "",
	})
	if err != nil {
		return nil, err
	}
	go server.handleConnection(conn)
	localAddr := conn.LocalAddr().(*net.UDPAddr)
	return v2net.UDPDestination(v2net.IPAddress(localAddr.IP), v2net.Port(localAddr.Port)), nil
}
Ejemplo n.º 8
0
func (this *DokodemoDoor) handleUDPPackets(payload *alloc.Buffer, dest v2net.Destination) {
	packet := v2net.NewPacket(v2net.UDPDestination(this.address, this.port), payload, false)
	this.udpServer.Dispatch(dest, packet, func(packet v2net.Packet) {
		defer packet.Chunk().Release()
		this.udpMutex.RLock()
		if !this.accepting {
			this.udpMutex.RUnlock()
			return
		}
		this.udpHub.WriteTo(packet.Chunk().Value, packet.Destination())
		this.udpMutex.RUnlock()
	})
}
Ejemplo n.º 9
0
func (this *UDPHub) start() {
	this.accepting = true
	for this.accepting {
		buffer := alloc.NewBuffer()
		nBytes, addr, err := this.conn.ReadFromUDP(buffer.Value)
		if err != nil {
			buffer.Release()
			continue
		}
		buffer.Slice(0, nBytes)
		dest := v2net.UDPDestination(v2net.IPAddress(addr.IP), v2net.Port(addr.Port))
		go this.callback(buffer, dest)
	}
}
Ejemplo n.º 10
0
// @Private
func (this *Router) ResolveIP(dest v2net.Destination) []v2net.Destination {
	ips := this.dnsServer.Get(dest.Address().Domain())
	if len(ips) == 0 {
		return nil
	}
	dests := make([]v2net.Destination, len(ips))
	for idx, ip := range ips {
		if dest.IsTCP() {
			dests[idx] = v2net.TCPDestination(v2net.IPAddress(ip), dest.Port())
		} else {
			dests[idx] = v2net.UDPDestination(v2net.IPAddress(ip), dest.Port())
		}
	}
	return dests
}
Ejemplo n.º 11
0
func DialKCP(src v2net.Address, dest v2net.Destination) (internet.Connection, error) {
	udpDest := v2net.UDPDestination(dest.Address(), dest.Port())
	log.Info("Dialling KCP to ", udpDest)
	conn, err := internet.DialToDest(src, udpDest)
	if err != nil {
		return nil, err
	}

	cpip := NewSimpleAuthenticator()
	conv := uint16(atomic.AddUint32(&globalConv, 1))
	session := NewConnection(conv, conn, conn.LocalAddr().(*net.UDPAddr), conn.RemoteAddr().(*net.UDPAddr), cpip)
	session.FetchInputFrom(conn)

	return session, nil
}
Ejemplo n.º 12
0
func (this *SocksServer) ListenUDP(port v2net.Port) error {
	addr := &net.UDPAddr{
		IP:   net.IP{0, 0, 0, 0},
		Port: int(port),
		Zone: "",
	}
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		log.Error("Socks failed to listen UDP on port %d: %v", port, err)
		return err
	}
	udpAddress = v2net.UDPDestination(v2net.IPAddress(this.config.IP()), port)

	go this.AcceptPackets(conn)
	return nil
}
Ejemplo n.º 13
0
func (this *DokodemoDoor) handleUDPPackets(payload *alloc.Buffer, dest v2net.Destination) {
	packet := v2net.NewPacket(v2net.UDPDestination(this.address, this.port), payload, false)
	ray := this.packetDispatcher.DispatchToOutbound(packet)
	close(ray.InboundInput())

	for resp := range ray.InboundOutput() {
		this.udpMutex.RLock()
		if !this.accepting {
			this.udpMutex.RUnlock()
			resp.Release()
			return
		}
		this.udpHub.WriteTo(resp.Value, dest)
		this.udpMutex.RUnlock()
		resp.Release()
	}
}
Ejemplo n.º 14
0
func (this *SocksServer) ListenUDP(port v2net.Port) error {
	addr := &net.UDPAddr{
		IP:   net.IP{0, 0, 0, 0},
		Port: int(port),
		Zone: "",
	}
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		log.Error("Socks: failed to listen UDP on port ", port, ": ", err)
		return err
	}
	this.udpMutex.Lock()
	this.udpAddress = v2net.UDPDestination(this.config.Address, port)
	this.udpConn = conn
	this.udpMutex.Unlock()

	go this.AcceptPackets()
	return nil
}
Ejemplo n.º 15
0
func (this *Config) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		Port            v2net.Port                `json:"port"` // Port of this Point server.
		LogConfig       *LogConfig                `json:"log"`
		RouterConfig    *router.Config            `json:"routing"`
		DNSConfig       *dns.Config               `json:"dns"`
		InboundConfig   *InboundConnectionConfig  `json:"inbound"`
		OutboundConfig  *OutboundConnectionConfig `json:"outbound"`
		InboundDetours  []*InboundDetourConfig    `json:"inboundDetour"`
		OutboundDetours []*OutboundDetourConfig   `json:"outboundDetour"`
		Transport       *transport.Config         `json:"transport"`
	}
	jsonConfig := new(JsonConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse config: " + err.Error())
	}
	this.Port = jsonConfig.Port
	this.LogConfig = jsonConfig.LogConfig
	this.RouterConfig = jsonConfig.RouterConfig

	if jsonConfig.InboundConfig == nil {
		return errors.New("Point: Inbound config is not specified.")
	}
	this.InboundConfig = jsonConfig.InboundConfig

	if jsonConfig.OutboundConfig == nil {
		return errors.New("Point: Outbound config is not specified.")
	}
	this.OutboundConfig = jsonConfig.OutboundConfig
	this.InboundDetours = jsonConfig.InboundDetours
	this.OutboundDetours = jsonConfig.OutboundDetours
	if jsonConfig.DNSConfig == nil {
		jsonConfig.DNSConfig = &dns.Config{
			NameServers: []v2net.Destination{
				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
			},
		}
	}
	this.DNSConfig = jsonConfig.DNSConfig
	this.TransportConfig = jsonConfig.Transport
	return nil
}
Ejemplo n.º 16
0
// @Private
func (this *FreedomConnection) ResolveIP(destination v2net.Destination) v2net.Destination {
	if !destination.Address().IsDomain() {
		return destination
	}

	ips := this.dns.Get(destination.Address().Domain())
	if len(ips) == 0 {
		log.Info("Freedom: DNS returns nil answer. Keep domain as is.")
		return destination
	}

	ip := ips[dice.Roll(len(ips))]
	var newDest v2net.Destination
	if destination.IsTCP() {
		newDest = v2net.TCPDestination(v2net.IPAddress(ip), destination.Port())
	} else {
		newDest = v2net.UDPDestination(v2net.IPAddress(ip), destination.Port())
	}
	log.Info("Freedom: Changing destination from ", destination, " to ", newDest)
	return newDest
}
Ejemplo n.º 17
0
func TestDialAndListen(t *testing.T) {
	assert := assert.On(t)

	port := v2nettesting.PickPort()
	listerner, err := NewListener(v2net.LocalHostIP, port)
	assert.Error(err).IsNil()

	go func() {
		for {
			conn, err := listerner.Accept()
			if err != nil {
				break
			}
			go func() {
				payload := make([]byte, 4096)
				for {
					nBytes, err := conn.Read(payload)
					if err != nil {
						break
					}
					for idx, b := range payload[:nBytes] {
						payload[idx] = b ^ 'c'
					}
					conn.Write(payload[:nBytes])
				}
				conn.Close()
			}()
		}
	}()

	wg := new(sync.WaitGroup)
	for i := 0; i < 10; i++ {
		clientConn, err := DialKCP(v2net.LocalHostIP, v2net.UDPDestination(v2net.LocalHostIP, port))
		assert.Error(err).IsNil()
		wg.Add(1)

		go func() {
			clientSend := make([]byte, 1024*1024)
			rand.Read(clientSend)
			go clientConn.Write(clientSend)

			clientReceived := make([]byte, 1024*1024)
			nBytes, _ := io.ReadFull(clientConn, clientReceived)
			assert.Int(nBytes).Equals(len(clientReceived))
			clientConn.Close()

			clientExpected := make([]byte, 1024*1024)
			for idx, b := range clientSend {
				clientExpected[idx] = b ^ 'c'
			}
			assert.Bytes(clientReceived).Equals(clientExpected)

			wg.Done()
		}()
	}

	wg.Wait()
	time.Sleep(15 * time.Second)
	assert.Int(listerner.ActiveConnections()).Equals(0)

	listerner.Close()
}
Ejemplo n.º 18
0
func (this *Server) handlerUDPPayload(payload *alloc.Buffer, source v2net.Destination) {
	defer payload.Release()

	ivLen := this.config.Cipher.IVSize()
	iv := payload.Value[:ivLen]
	key := this.config.Key
	payload.SliceFrom(ivLen)

	stream, err := this.config.Cipher.NewDecodingStream(key, iv)
	if err != nil {
		log.Error("Shadowsocks: Failed to create decoding stream: ", err)
		return
	}

	reader := crypto.NewCryptionReader(stream, payload)

	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(key, iv)), true)
	if err != nil {
		if err != io.EOF {
			log.Access(source, "", log.AccessRejected, err)
			log.Warning("Shadowsocks: Invalid request from ", source, ": ", err)
		}
		return
	}
	//defer request.Release()

	dest := v2net.UDPDestination(request.Address, request.Port)
	log.Access(source, dest, log.AccessAccepted, "")
	log.Info("Shadowsocks: Tunnelling request to ", dest)

	this.udpServer.Dispatch(source, dest, request.DetachUDPPayload(), func(destination v2net.Destination, payload *alloc.Buffer) {
		defer payload.Release()

		response := alloc.NewBuffer().Slice(0, ivLen)
		defer response.Release()

		rand.Read(response.Value)
		respIv := response.Value

		stream, err := this.config.Cipher.NewEncodingStream(key, respIv)
		if err != nil {
			log.Error("Shadowsocks: Failed to create encoding stream: ", err)
			return
		}

		writer := crypto.NewCryptionWriter(stream, response)

		switch {
		case request.Address.IsIPv4():
			writer.Write([]byte{AddrTypeIPv4})
			writer.Write(request.Address.IP())
		case request.Address.IsIPv6():
			writer.Write([]byte{AddrTypeIPv6})
			writer.Write(request.Address.IP())
		case request.Address.IsDomain():
			writer.Write([]byte{AddrTypeDomain, byte(len(request.Address.Domain()))})
			writer.Write([]byte(request.Address.Domain()))
		}

		writer.Write(request.Port.Bytes())
		writer.Write(payload.Value)

		if request.OTA {
			respAuth := NewAuthenticator(HeaderKeyGenerator(key, respIv))
			respAuth.Authenticate(response.Value, response.Value[ivLen:])
		}

		this.udpHub.WriteTo(response.Value, source)
	})
}
Ejemplo n.º 19
0
func TestUDPAssociate(t *testing.T) {
	v2testing.Current(t)

	targetPort := v2nettesting.PickPort()
	udpServer := &udp.Server{
		Port: targetPort,
		MsgProcessor: func(data []byte) []byte {
			buffer := make([]byte, 0, 2048)
			buffer = append(buffer, []byte("Processed: ")...)
			buffer = append(buffer, data...)
			return buffer
		},
	}
	_, err := udpServer.Start()
	assert.Error(err).IsNil()
	defer udpServer.Close()

	assert.Error(InitializeServerSetOnce("test_1")).IsNil()

	socksPort := v2net.Port(50000)

	conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: int(socksPort),
	})

	authRequest := socks5AuthMethodRequest(byte(0))
	nBytes, err := conn.Write(authRequest)
	assert.Int(nBytes).Equals(len(authRequest))
	assert.Error(err).IsNil()

	authResponse := make([]byte, 1024)
	nBytes, err = conn.Read(authResponse)
	assert.Error(err).IsNil()
	assert.Bytes(authResponse[:nBytes]).Equals([]byte{socks5Version, 0})

	connectRequest := socks5Request(byte(3), v2net.TCPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), targetPort))
	nBytes, err = conn.Write(connectRequest)
	assert.Int(nBytes).Equals(len(connectRequest))
	assert.Error(err).IsNil()

	connectResponse := make([]byte, 1024)
	nBytes, err = conn.Read(connectResponse)
	assert.Error(err).IsNil()
	assert.Bytes(connectResponse[:nBytes]).Equals([]byte{socks5Version, 0, 0, 1, 127, 0, 0, 1, byte(socksPort >> 8), byte(socksPort)})

	udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: int(socksPort),
	})
	assert.Error(err).IsNil()

	for i := 0; i < 100; i++ {
		udpPayload := "UDP request to udp server."
		udpRequest := socks5UDPRequest(v2net.UDPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), targetPort), []byte(udpPayload))

		nBytes, err = udpConn.Write(udpRequest)
		assert.Int(nBytes).Equals(len(udpRequest))
		assert.Error(err).IsNil()

		udpResponse := make([]byte, 1024)
		nBytes, err = udpConn.Read(udpResponse)
		assert.Error(err).IsNil()
		assert.Bytes(udpResponse[:nBytes]).Equals(
			socks5UDPRequest(v2net.UDPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), targetPort), []byte("Processed: UDP request to udp server.")))
	}

	udpConn.Close()
	conn.Close()

	CloseAllServers()
}
Ejemplo n.º 20
0
func (this *Shadowsocks) handlerUDPPayload(payload *alloc.Buffer, source v2net.Destination) {
	defer payload.Release()

	iv := payload.Value[:this.config.Cipher.IVSize()]
	key := this.config.Key
	payload.SliceFrom(this.config.Cipher.IVSize())

	reader, err := this.config.Cipher.NewDecodingStream(key, iv, payload)
	if err != nil {
		log.Error("Shadowsocks: Failed to create decoding stream: ", err)
		return
	}

	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(key, iv)), true)
	if err != nil {
		log.Access(source, serial.StringLiteral(""), log.AccessRejected, serial.StringLiteral(err.Error()))
		log.Warning("Shadowsocks: Invalid request from ", source, ": ", err)
		return
	}

	dest := v2net.UDPDestination(request.Address, request.Port)
	log.Access(source, dest, log.AccessAccepted, serial.StringLiteral(""))
	log.Info("Shadowsocks: Tunnelling request to ", dest)

	packet := v2net.NewPacket(dest, request.UDPPayload, false)
	this.udpServer.Dispatch(source, packet, func(packet v2net.Packet) {
		defer packet.Chunk().Release()

		response := alloc.NewBuffer().Slice(0, this.config.Cipher.IVSize())
		defer response.Release()

		rand.Read(response.Value)
		respIv := response.Value

		writer, err := this.config.Cipher.NewEncodingStream(key, respIv, response)
		if err != nil {
			log.Error("Shadowsocks: Failed to create encoding stream: ", err)
			return
		}

		switch {
		case request.Address.IsIPv4():
			writer.Write([]byte{AddrTypeIPv4})
			writer.Write(request.Address.IP())
		case request.Address.IsIPv6():
			writer.Write([]byte{AddrTypeIPv6})
			writer.Write(request.Address.IP())
		case request.Address.IsDomain():
			writer.Write([]byte{AddrTypeDomain, byte(len(request.Address.Domain()))})
			writer.Write([]byte(request.Address.Domain()))
		}

		writer.Write(request.Port.Bytes())
		writer.Write(packet.Chunk().Value)

		if request.OTA {
			respAuth := NewAuthenticator(HeaderKeyGenerator(key, respIv))
			respAuth.Authenticate(response.Value, response.Value[this.config.Cipher.IVSize():])
		}

		this.udpHub.WriteTo(response.Value, source)
	})
}
Ejemplo n.º 21
0
func (request *Socks5UDPRequest) Destination() v2net.Destination {
	return v2net.UDPDestination(request.Address, request.Port)
}
Ejemplo n.º 22
0
func (this *RequestHeader) Destination() v2net.Destination {
	if this.Command == RequestCommandUDP {
		return v2net.UDPDestination(this.Address, this.Port)
	}
	return v2net.TCPDestination(this.Address, this.Port)
}
Ejemplo n.º 23
0
func (this *SocksServer) AcceptPackets() error {
	for this.accepting {
		buffer := alloc.NewBuffer()
		this.udpMutex.RLock()
		if !this.accepting {
			this.udpMutex.RUnlock()
			return nil
		}
		nBytes, addr, err := this.udpConn.ReadFromUDP(buffer.Value)
		this.udpMutex.RUnlock()
		if err != nil {
			log.Error("Socks: failed to read UDP packets: ", err)
			buffer.Release()
			continue
		}
		log.Info("Socks: Client UDP connection from ", addr)
		request, err := protocol.ReadUDPRequest(buffer.Value[:nBytes])
		buffer.Release()
		if err != nil {
			log.Error("Socks: failed to parse UDP request: ", err)
			continue
		}
		if request.Data == nil || request.Data.Len() == 0 {
			continue
		}
		if request.Fragment != 0 {
			log.Warning("Socks: Dropping fragmented UDP packets.")
			// TODO handle fragments
			request.Data.Release()
			continue
		}

		udpPacket := v2net.NewPacket(request.Destination(), request.Data, false)
		log.Info("Socks: Send packet to ", udpPacket.Destination(), " with ", request.Data.Len(), " bytes")
		this.udpServer.Dispatch(
			v2net.UDPDestination(v2net.IPAddress(addr.IP), v2net.Port(addr.Port)), udpPacket,
			func(packet v2net.Packet) {
				response := &protocol.Socks5UDPRequest{
					Fragment: 0,
					Address:  udpPacket.Destination().Address(),
					Port:     udpPacket.Destination().Port(),
					Data:     packet.Chunk(),
				}
				log.Info("Socks: Writing back UDP response with ", response.Data.Len(), " bytes to ", packet.Destination())

				udpMessage := alloc.NewSmallBuffer().Clear()
				response.Write(udpMessage)

				this.udpMutex.RLock()
				if !this.accepting {
					this.udpMutex.RUnlock()
					return
				}
				nBytes, err := this.udpConn.WriteToUDP(udpMessage.Value, &net.UDPAddr{
					IP:   packet.Destination().Address().IP(),
					Port: int(packet.Destination().Port()),
				})
				this.udpMutex.RUnlock()
				udpMessage.Release()
				response.Data.Release()
				if err != nil {
					log.Error("Socks: failed to write UDP message (", nBytes, " bytes) to ", packet.Destination(), ": ", err)
				}
			})
	}
	return nil
}
Ejemplo n.º 24
0
func (this *DokodemoDoor) handleUDPPackets(payload *alloc.Buffer, dest v2net.Destination) {
	this.udpServer.Dispatch(dest, v2net.UDPDestination(this.address, this.port), payload, this.handleUDPResponse)
}
Ejemplo n.º 25
0
	"github.com/v2ray/v2ray-core/common/dice"
	"github.com/v2ray/v2ray-core/common/log"
	v2net "github.com/v2ray/v2ray-core/common/net"
	"github.com/v2ray/v2ray-core/transport/internet/udp"

	"github.com/miekg/dns"
)

const (
	DefaultTTL       = uint32(3600)
	CleanupInterval  = time.Second * 120
	CleanupThreshold = 512
)

var (
	pseudoDestination = v2net.UDPDestination(v2net.LocalHostIP, v2net.Port(53))
)

type ARecord struct {
	IPs    []net.IP
	Expire time.Time
}

type NameServer interface {
	QueryA(domain string) <-chan *ARecord
}

type PendingRequest struct {
	expire   time.Time
	response chan<- *ARecord
}