reader.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. // Package maxminddb provides a reader for the MaxMind DB file format.
  2. package maxminddb
  3. import (
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "reflect"
  9. )
  10. const (
  11. // NotFound is returned by LookupOffset when a matched root record offset
  12. // cannot be found.
  13. NotFound = ^uintptr(0)
  14. dataSectionSeparatorSize = 16
  15. )
  16. var metadataStartMarker = []byte("\xAB\xCD\xEFMaxMind.com")
  17. // Reader holds the data corresponding to the MaxMind DB file. Its only public
  18. // field is Metadata, which contains the metadata from the MaxMind DB file.
  19. //
  20. // All of the methods on Reader are thread-safe. The struct may be safely
  21. // shared across goroutines.
  22. type Reader struct {
  23. nodeReader nodeReader
  24. buffer []byte
  25. decoder decoder
  26. Metadata Metadata
  27. ipv4Start uint
  28. ipv4StartBitDepth int
  29. nodeOffsetMult uint
  30. hasMappedFile bool
  31. }
  32. // Metadata holds the metadata decoded from the MaxMind DB file. In particular
  33. // it has the format version, the build time as Unix epoch time, the database
  34. // type and description, the IP version supported, and a slice of the natural
  35. // languages included.
  36. type Metadata struct {
  37. Description map[string]string `maxminddb:"description"`
  38. DatabaseType string `maxminddb:"database_type"`
  39. Languages []string `maxminddb:"languages"`
  40. BinaryFormatMajorVersion uint `maxminddb:"binary_format_major_version"`
  41. BinaryFormatMinorVersion uint `maxminddb:"binary_format_minor_version"`
  42. BuildEpoch uint `maxminddb:"build_epoch"`
  43. IPVersion uint `maxminddb:"ip_version"`
  44. NodeCount uint `maxminddb:"node_count"`
  45. RecordSize uint `maxminddb:"record_size"`
  46. }
  47. // FromBytes takes a byte slice corresponding to a MaxMind DB file and returns
  48. // a Reader structure or an error.
  49. func FromBytes(buffer []byte) (*Reader, error) {
  50. metadataStart := bytes.LastIndex(buffer, metadataStartMarker)
  51. if metadataStart == -1 {
  52. return nil, newInvalidDatabaseError("error opening database: invalid MaxMind DB file")
  53. }
  54. metadataStart += len(metadataStartMarker)
  55. metadataDecoder := decoder{buffer[metadataStart:]}
  56. var metadata Metadata
  57. rvMetdata := reflect.ValueOf(&metadata)
  58. _, err := metadataDecoder.decode(0, rvMetdata, 0)
  59. if err != nil {
  60. return nil, err
  61. }
  62. searchTreeSize := metadata.NodeCount * metadata.RecordSize / 4
  63. dataSectionStart := searchTreeSize + dataSectionSeparatorSize
  64. dataSectionEnd := uint(metadataStart - len(metadataStartMarker))
  65. if dataSectionStart > dataSectionEnd {
  66. return nil, newInvalidDatabaseError("the MaxMind DB contains invalid metadata")
  67. }
  68. d := decoder{
  69. buffer[searchTreeSize+dataSectionSeparatorSize : metadataStart-len(metadataStartMarker)],
  70. }
  71. nodeBuffer := buffer[:searchTreeSize]
  72. var nodeReader nodeReader
  73. switch metadata.RecordSize {
  74. case 24:
  75. nodeReader = nodeReader24{buffer: nodeBuffer}
  76. case 28:
  77. nodeReader = nodeReader28{buffer: nodeBuffer}
  78. case 32:
  79. nodeReader = nodeReader32{buffer: nodeBuffer}
  80. default:
  81. return nil, newInvalidDatabaseError("unknown record size: %d", metadata.RecordSize)
  82. }
  83. reader := &Reader{
  84. buffer: buffer,
  85. nodeReader: nodeReader,
  86. decoder: d,
  87. Metadata: metadata,
  88. ipv4Start: 0,
  89. nodeOffsetMult: metadata.RecordSize / 4,
  90. }
  91. reader.setIPv4Start()
  92. return reader, err
  93. }
  94. func (r *Reader) setIPv4Start() {
  95. if r.Metadata.IPVersion != 6 {
  96. return
  97. }
  98. nodeCount := r.Metadata.NodeCount
  99. node := uint(0)
  100. i := 0
  101. for ; i < 96 && node < nodeCount; i++ {
  102. node = r.nodeReader.readLeft(node * r.nodeOffsetMult)
  103. }
  104. r.ipv4Start = node
  105. r.ipv4StartBitDepth = i
  106. }
  107. // Lookup retrieves the database record for ip and stores it in the value
  108. // pointed to by result. If result is nil or not a pointer, an error is
  109. // returned. If the data in the database record cannot be stored in result
  110. // because of type differences, an UnmarshalTypeError is returned. If the
  111. // database is invalid or otherwise cannot be read, an InvalidDatabaseError
  112. // is returned.
  113. func (r *Reader) Lookup(ip net.IP, result any) error {
  114. if r.buffer == nil {
  115. return errors.New("cannot call Lookup on a closed database")
  116. }
  117. pointer, _, _, err := r.lookupPointer(ip)
  118. if pointer == 0 || err != nil {
  119. return err
  120. }
  121. return r.retrieveData(pointer, result)
  122. }
  123. // LookupNetwork retrieves the database record for ip and stores it in the
  124. // value pointed to by result. The network returned is the network associated
  125. // with the data record in the database. The ok return value indicates whether
  126. // the database contained a record for the ip.
  127. //
  128. // If result is nil or not a pointer, an error is returned. If the data in the
  129. // database record cannot be stored in result because of type differences, an
  130. // UnmarshalTypeError is returned. If the database is invalid or otherwise
  131. // cannot be read, an InvalidDatabaseError is returned.
  132. func (r *Reader) LookupNetwork(
  133. ip net.IP,
  134. result any,
  135. ) (network *net.IPNet, ok bool, err error) {
  136. if r.buffer == nil {
  137. return nil, false, errors.New("cannot call Lookup on a closed database")
  138. }
  139. pointer, prefixLength, ip, err := r.lookupPointer(ip)
  140. network = r.cidr(ip, prefixLength)
  141. if pointer == 0 || err != nil {
  142. return network, false, err
  143. }
  144. return network, true, r.retrieveData(pointer, result)
  145. }
  146. // LookupOffset maps an argument net.IP to a corresponding record offset in the
  147. // database. NotFound is returned if no such record is found, and a record may
  148. // otherwise be extracted by passing the returned offset to Decode. LookupOffset
  149. // is an advanced API, which exists to provide clients with a means to cache
  150. // previously-decoded records.
  151. func (r *Reader) LookupOffset(ip net.IP) (uintptr, error) {
  152. if r.buffer == nil {
  153. return 0, errors.New("cannot call LookupOffset on a closed database")
  154. }
  155. pointer, _, _, err := r.lookupPointer(ip)
  156. if pointer == 0 || err != nil {
  157. return NotFound, err
  158. }
  159. return r.resolveDataPointer(pointer)
  160. }
  161. func (r *Reader) cidr(ip net.IP, prefixLength int) *net.IPNet {
  162. // This is necessary as the node that the IPv4 start is at may
  163. // be at a bit depth that is less that 96, i.e., ipv4Start points
  164. // to a leaf node. For instance, if a record was inserted at ::/8,
  165. // the ipv4Start would point directly at the leaf node for the
  166. // record and would have a bit depth of 8. This would not happen
  167. // with databases currently distributed by MaxMind as all of them
  168. // have an IPv4 subtree that is greater than a single node.
  169. if r.Metadata.IPVersion == 6 &&
  170. len(ip) == net.IPv4len &&
  171. r.ipv4StartBitDepth != 96 {
  172. return &net.IPNet{IP: net.ParseIP("::"), Mask: net.CIDRMask(r.ipv4StartBitDepth, 128)}
  173. }
  174. mask := net.CIDRMask(prefixLength, len(ip)*8)
  175. return &net.IPNet{IP: ip.Mask(mask), Mask: mask}
  176. }
  177. // Decode the record at |offset| into |result|. The result value pointed to
  178. // must be a data value that corresponds to a record in the database. This may
  179. // include a struct representation of the data, a map capable of holding the
  180. // data or an empty any value.
  181. //
  182. // If result is a pointer to a struct, the struct need not include a field
  183. // for every value that may be in the database. If a field is not present in
  184. // the structure, the decoder will not decode that field, reducing the time
  185. // required to decode the record.
  186. //
  187. // As a special case, a struct field of type uintptr will be used to capture
  188. // the offset of the value. Decode may later be used to extract the stored
  189. // value from the offset. MaxMind DBs are highly normalized: for example in
  190. // the City database, all records of the same country will reference a
  191. // single representative record for that country. This uintptr behavior allows
  192. // clients to leverage this normalization in their own sub-record caching.
  193. func (r *Reader) Decode(offset uintptr, result any) error {
  194. if r.buffer == nil {
  195. return errors.New("cannot call Decode on a closed database")
  196. }
  197. return r.decode(offset, result)
  198. }
  199. func (r *Reader) decode(offset uintptr, result any) error {
  200. rv := reflect.ValueOf(result)
  201. if rv.Kind() != reflect.Ptr || rv.IsNil() {
  202. return errors.New("result param must be a pointer")
  203. }
  204. if dser, ok := result.(deserializer); ok {
  205. _, err := r.decoder.decodeToDeserializer(uint(offset), dser, 0, false)
  206. return err
  207. }
  208. _, err := r.decoder.decode(uint(offset), rv, 0)
  209. return err
  210. }
  211. func (r *Reader) lookupPointer(ip net.IP) (uint, int, net.IP, error) {
  212. if ip == nil {
  213. return 0, 0, nil, errors.New("IP passed to Lookup cannot be nil")
  214. }
  215. ipV4Address := ip.To4()
  216. if ipV4Address != nil {
  217. ip = ipV4Address
  218. }
  219. if len(ip) == 16 && r.Metadata.IPVersion == 4 {
  220. return 0, 0, ip, fmt.Errorf(
  221. "error looking up '%s': you attempted to look up an IPv6 address in an IPv4-only database",
  222. ip.String(),
  223. )
  224. }
  225. bitCount := uint(len(ip) * 8)
  226. var node uint
  227. if bitCount == 32 {
  228. node = r.ipv4Start
  229. }
  230. node, prefixLength := r.traverseTree(ip, node, bitCount)
  231. nodeCount := r.Metadata.NodeCount
  232. if node == nodeCount {
  233. // Record is empty
  234. return 0, prefixLength, ip, nil
  235. } else if node > nodeCount {
  236. return node, prefixLength, ip, nil
  237. }
  238. return 0, prefixLength, ip, newInvalidDatabaseError("invalid node in search tree")
  239. }
  240. func (r *Reader) traverseTree(ip net.IP, node, bitCount uint) (uint, int) {
  241. nodeCount := r.Metadata.NodeCount
  242. i := uint(0)
  243. for ; i < bitCount && node < nodeCount; i++ {
  244. bit := uint(1) & (uint(ip[i>>3]) >> (7 - (i % 8)))
  245. offset := node * r.nodeOffsetMult
  246. if bit == 0 {
  247. node = r.nodeReader.readLeft(offset)
  248. } else {
  249. node = r.nodeReader.readRight(offset)
  250. }
  251. }
  252. return node, int(i)
  253. }
  254. func (r *Reader) retrieveData(pointer uint, result any) error {
  255. offset, err := r.resolveDataPointer(pointer)
  256. if err != nil {
  257. return err
  258. }
  259. return r.decode(offset, result)
  260. }
  261. func (r *Reader) resolveDataPointer(pointer uint) (uintptr, error) {
  262. resolved := uintptr(pointer - r.Metadata.NodeCount - dataSectionSeparatorSize)
  263. if resolved >= uintptr(len(r.buffer)) {
  264. return 0, newInvalidDatabaseError("the MaxMind DB file's search tree is corrupt")
  265. }
  266. return resolved, nil
  267. }