@@ -2,6 +2,7 @@ package geoannotator
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "log"
78 "net"
@@ -15,10 +16,11 @@ import (
1516 "github.com/m-lab/uuid-annotator/rawfile"
1617)
1718
18- // ReloadingAnnotator is just a regular annotator with a Reload method.
19- type ReloadingAnnotator interface {
19+ // GeoAnnotator is just a regular annotator with a Reload method and an AnnotateIP method.
20+ type GeoAnnotator interface {
2021 annotator.Annotator
2122 Reload (context.Context )
23+ AnnotateIP (ip net.IP , geo * * annotator.Geolocation ) error
2224}
2325
2426// geoannotator is the central struct for this module.
@@ -41,9 +43,9 @@ func (g *geoannotator) Annotate(ID *inetdiag.SockID, annotations *annotator.Anno
4143
4244 switch dir {
4345 case annotator .DstIsServer :
44- err = g .annotate (ID .SrcIP , & annotations .Client .Geo )
46+ err = g .annotateHoldingLock (ID .SrcIP , & annotations .Client .Geo )
4547 case annotator .SrcIsServer :
46- err = g .annotate (ID .DstIP , & annotations .Client .Geo )
48+ err = g .annotateHoldingLock (ID .DstIP , & annotations .Client .Geo )
4749 }
4850 if err != nil {
4951 return annotator .ErrNoAnnotation
@@ -53,11 +55,24 @@ func (g *geoannotator) Annotate(ID *inetdiag.SockID, annotations *annotator.Anno
5355
5456var emptyResult = geoip2.City {}
5557
56- func (g * geoannotator ) annotate (src string , geo * * annotator.Geolocation ) error {
58+ func (g * geoannotator ) annotateHoldingLock (src string , geo * * annotator.Geolocation ) error {
5759 ip := net .ParseIP (src )
5860 if ip == nil {
5961 return fmt .Errorf ("failed to parse IP %q" , src )
6062 }
63+ return g .annotateIPHoldingLock (ip , geo )
64+ }
65+
66+ func (g * geoannotator ) AnnotateIP (ip net.IP , geo * * annotator.Geolocation ) error {
67+ g .mut .RLock ()
68+ defer g .mut .RUnlock ()
69+ return g .annotateIPHoldingLock (ip , geo )
70+ }
71+
72+ func (g * geoannotator ) annotateIPHoldingLock (ip net.IP , geo * * annotator.Geolocation ) error {
73+ if ip == nil {
74+ return errors .New ("can't annotate nil IP" )
75+ }
6176 record , err := g .maxmind .City (ip )
6277 if err != nil {
6378 return err
@@ -66,8 +81,14 @@ func (g *geoannotator) annotate(src string, geo **annotator.Geolocation) error {
6681 // Check for empty results because "not found" is not an error. Instead the
6782 // geoip2 package returns an empty result. May be fixed in a future version:
6883 // https://github.com/oschwald/geoip2-golang/issues/32
84+ //
85+ // "Not found" in a well-functioning database should not be an error.
86+ // Instead, it is an accurate reflection of data that is missing.
6987 if isEmpty (record ) {
70- return fmt .Errorf ("not found %q" , src )
88+ * geo = & annotator.Geolocation {
89+ Missing : true ,
90+ }
91+ return nil
7192 }
7293
7394 tmp := & annotator.Geolocation {
@@ -132,7 +153,7 @@ func (g *geoannotator) load(ctx context.Context) (*geoip2.Reader, error) {
132153// New makes a new Annotator that uses IP addresses to generate geolocation and
133154// ASNumber metadata for that IP based on the current copy of MaxMind data
134155// stored in GCS.
135- func New (ctx context.Context , geo rawfile.Provider , localIPs []net.IP ) ReloadingAnnotator {
156+ func New (ctx context.Context , geo rawfile.Provider , localIPs []net.IP ) GeoAnnotator {
136157 g := & geoannotator {
137158 backingDataSource : geo ,
138159 localIPs : localIPs ,
0 commit comments