123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- package maxminddb
- import (
- "bytes"
- "errors"
- "fmt"
- "net"
- "reflect"
- )
- const (
- // NotFound is returned by LookupOffset when a matched root record offset
- // cannot be found.
- NotFound = ^uintptr(0)
- dataSectionSeparatorSize = 16
- )
- var metadataStartMarker = []byte("\xAB\xCD\xEFMaxMind.com")
- // Reader holds the data corresponding to the MaxMind DB file. Its only public
- // field is Metadata, which contains the metadata from the MaxMind DB file.
- type Reader struct {
- hasMappedFile bool
- buffer []byte
- nodeReader nodeReader
- decoder decoder
- Metadata Metadata
- ipv4Start uint
- ipv4StartBitDepth int
- nodeOffsetMult uint
- }
- // Metadata holds the metadata decoded from the MaxMind DB file. In particular
- // in has the format version, the build time as Unix epoch time, the database
- // type and description, the IP version supported, and a slice of the natural
- // languages included.
- type Metadata struct {
- BinaryFormatMajorVersion uint `maxminddb:"binary_format_major_version"`
- BinaryFormatMinorVersion uint `maxminddb:"binary_format_minor_version"`
- BuildEpoch uint `maxminddb:"build_epoch"`
- DatabaseType string `maxminddb:"database_type"`
- Description map[string]string `maxminddb:"description"`
- IPVersion uint `maxminddb:"ip_version"`
- Languages []string `maxminddb:"languages"`
- NodeCount uint `maxminddb:"node_count"`
- RecordSize uint `maxminddb:"record_size"`
- }
- // FromBytes takes a byte slice corresponding to a MaxMind DB file and returns
- // a Reader structure or an error.
- func FromBytes(buffer []byte) (*Reader, error) {
- metadataStart := bytes.LastIndex(buffer, metadataStartMarker)
- if metadataStart == -1 {
- return nil, newInvalidDatabaseError("error opening database: invalid MaxMind DB file")
- }
- metadataStart += len(metadataStartMarker)
- metadataDecoder := decoder{buffer[metadataStart:]}
- var metadata Metadata
- rvMetdata := reflect.ValueOf(&metadata)
- _, err := metadataDecoder.decode(0, rvMetdata, 0)
- if err != nil {
- return nil, err
- }
- searchTreeSize := metadata.NodeCount * metadata.RecordSize / 4
- dataSectionStart := searchTreeSize + dataSectionSeparatorSize
- dataSectionEnd := uint(metadataStart - len(metadataStartMarker))
- if dataSectionStart > dataSectionEnd {
- return nil, newInvalidDatabaseError("the MaxMind DB contains invalid metadata")
- }
- d := decoder{
- buffer[searchTreeSize+dataSectionSeparatorSize : metadataStart-len(metadataStartMarker)],
- }
- nodeBuffer := buffer[:searchTreeSize]
- var nodeReader nodeReader
- switch metadata.RecordSize {
- case 24:
- nodeReader = nodeReader24{buffer: nodeBuffer}
- case 28:
- nodeReader = nodeReader28{buffer: nodeBuffer}
- case 32:
- nodeReader = nodeReader32{buffer: nodeBuffer}
- default:
- return nil, newInvalidDatabaseError("unknown record size: %d", metadata.RecordSize)
- }
- reader := &Reader{
- buffer: buffer,
- nodeReader: nodeReader,
- decoder: d,
- Metadata: metadata,
- ipv4Start: 0,
- nodeOffsetMult: metadata.RecordSize / 4,
- }
- reader.setIPv4Start()
- return reader, err
- }
- func (r *Reader) setIPv4Start() {
- if r.Metadata.IPVersion != 6 {
- return
- }
- nodeCount := r.Metadata.NodeCount
- node := uint(0)
- i := 0
- for ; i < 96 && node < nodeCount; i++ {
- node = r.nodeReader.readLeft(node * r.nodeOffsetMult)
- }
- r.ipv4Start = node
- r.ipv4StartBitDepth = i
- }
- // Lookup retrieves the database record for ip and stores it in the value
- // pointed to by result. If result is nil or not a pointer, an error is
- // returned. If the data in the database record cannot be stored in result
- // because of type differences, an UnmarshalTypeError is returned. If the
- // database is invalid or otherwise cannot be read, an InvalidDatabaseError
- // is returned.
- func (r *Reader) Lookup(ip net.IP, result interface{}) error {
- if r.buffer == nil {
- return errors.New("cannot call Lookup on a closed database")
- }
- pointer, _, _, err := r.lookupPointer(ip)
- if pointer == 0 || err != nil {
- return err
- }
- return r.retrieveData(pointer, result)
- }
- // LookupNetwork retrieves the database record for ip and stores it in the
- // value pointed to by result. The network returned is the network associated
- // with the data record in the database. The ok return value indicates whether
- // the database contained a record for the ip.
- //
- // If result is nil or not a pointer, an error is returned. If the data in the
- // database record cannot be stored in result because of type differences, an
- // UnmarshalTypeError is returned. If the database is invalid or otherwise
- // cannot be read, an InvalidDatabaseError is returned.
- func (r *Reader) LookupNetwork(ip net.IP, result interface{}) (network *net.IPNet, ok bool, err error) {
- if r.buffer == nil {
- return nil, false, errors.New("cannot call Lookup on a closed database")
- }
- pointer, prefixLength, ip, err := r.lookupPointer(ip)
- network = r.cidr(ip, prefixLength)
- if pointer == 0 || err != nil {
- return network, false, err
- }
- return network, true, r.retrieveData(pointer, result)
- }
- // LookupOffset maps an argument net.IP to a corresponding record offset in the
- // database. NotFound is returned if no such record is found, and a record may
- // otherwise be extracted by passing the returned offset to Decode. LookupOffset
- // is an advanced API, which exists to provide clients with a means to cache
- // previously-decoded records.
- func (r *Reader) LookupOffset(ip net.IP) (uintptr, error) {
- if r.buffer == nil {
- return 0, errors.New("cannot call LookupOffset on a closed database")
- }
- pointer, _, _, err := r.lookupPointer(ip)
- if pointer == 0 || err != nil {
- return NotFound, err
- }
- return r.resolveDataPointer(pointer)
- }
- func (r *Reader) cidr(ip net.IP, prefixLength int) *net.IPNet {
- // This is necessary as the node that the IPv4 start is at may
- // be at a bit depth that is less that 96, i.e., ipv4Start points
- // to a leaf node. For instance, if a record was inserted at ::/8,
- // the ipv4Start would point directly at the leaf node for the
- // record and would have a bit depth of 8. This would not happen
- // with databases currently distributed by MaxMind as all of them
- // have an IPv4 subtree that is greater than a single node.
- if r.Metadata.IPVersion == 6 &&
- len(ip) == net.IPv4len &&
- r.ipv4StartBitDepth != 96 {
- return &net.IPNet{IP: net.ParseIP("::"), Mask: net.CIDRMask(r.ipv4StartBitDepth, 128)}
- }
- mask := net.CIDRMask(prefixLength, len(ip)*8)
- return &net.IPNet{IP: ip.Mask(mask), Mask: mask}
- }
- // Decode the record at |offset| into |result|. The result value pointed to
- // must be a data value that corresponds to a record in the database. This may
- // include a struct representation of the data, a map capable of holding the
- // data or an empty interface{} value.
- //
- // If result is a pointer to a struct, the struct need not include a field
- // for every value that may be in the database. If a field is not present in
- // the structure, the decoder will not decode that field, reducing the time
- // required to decode the record.
- //
- // As a special case, a struct field of type uintptr will be used to capture
- // the offset of the value. Decode may later be used to extract the stored
- // value from the offset. MaxMind DBs are highly normalized: for example in
- // the City database, all records of the same country will reference a
- // single representative record for that country. This uintptr behavior allows
- // clients to leverage this normalization in their own sub-record caching.
- func (r *Reader) Decode(offset uintptr, result interface{}) error {
- if r.buffer == nil {
- return errors.New("cannot call Decode on a closed database")
- }
- return r.decode(offset, result)
- }
- func (r *Reader) decode(offset uintptr, result interface{}) error {
- rv := reflect.ValueOf(result)
- if rv.Kind() != reflect.Ptr || rv.IsNil() {
- return errors.New("result param must be a pointer")
- }
- _, err := r.decoder.decode(uint(offset), rv, 0)
- return err
- }
- func (r *Reader) lookupPointer(ip net.IP) (uint, int, net.IP, error) {
- if ip == nil {
- return 0, 0, ip, errors.New("IP passed to Lookup cannot be nil")
- }
- ipV4Address := ip.To4()
- if ipV4Address != nil {
- ip = ipV4Address
- }
- if len(ip) == 16 && r.Metadata.IPVersion == 4 {
- return 0, 0, ip, fmt.Errorf("error looking up '%s': you attempted to look up an IPv6 address in an IPv4-only database", ip.String())
- }
- bitCount := uint(len(ip) * 8)
- var node uint
- if bitCount == 32 {
- node = r.ipv4Start
- }
- nodeCount := r.Metadata.NodeCount
- i := uint(0)
- for ; i < bitCount && node < nodeCount; i++ {
- bit := uint(1) & (uint(ip[i>>3]) >> (7 - (i % 8)))
- offset := node * r.nodeOffsetMult
- if bit == 0 {
- node = r.nodeReader.readLeft(offset)
- } else {
- node = r.nodeReader.readRight(offset)
- }
- }
- if node == nodeCount {
- // Record is empty
- return 0, int(i), ip, nil
- } else if node > nodeCount {
- return node, int(i), ip, nil
- }
- return 0, int(i), ip, newInvalidDatabaseError("invalid node in search tree")
- }
- func (r *Reader) retrieveData(pointer uint, result interface{}) error {
- offset, err := r.resolveDataPointer(pointer)
- if err != nil {
- return err
- }
- return r.decode(offset, result)
- }
- func (r *Reader) resolveDataPointer(pointer uint) (uintptr, error) {
- var resolved = uintptr(pointer - r.Metadata.NodeCount - dataSectionSeparatorSize)
- if resolved > uintptr(len(r.buffer)) {
- return 0, newInvalidDatabaseError("the MaxMind DB file's search tree is corrupt")
- }
- return resolved, nil
- }
|