11package main
22
33import (
4+ "fmt"
5+ "os"
6+ "os/signal"
47 "strconv"
58 "strings"
69 "sync"
10+ "syscall"
11+ "time"
712
813 "github.com/mkideal/cli"
914
@@ -25,14 +30,15 @@ var outputHeader = []string{
2530 "region" ,
2631 "latitude" ,
2732 "longitude" ,
28- "is_anonymous" ,
29- "is_anonymous_vpn" ,
33+ "is_vpn" ,
3034 "is_hosting" ,
3135 "is_proxy" ,
3236 "is_tor" ,
3337 "is_bot" ,
3438 "is_bogon" ,
3539 "has_other_threat" ,
40+ "threat_comment" ,
41+ "error" ,
3642 // "as_routes", append by code
3743}
3844
@@ -43,10 +49,19 @@ type listT struct {
4349 InputCSV string `cli:"*i,input" usage:"input csv/tsv file path --input='./input.csv'"`
4450 Output string `cli:"*o,output" usage:"output tsv file path --output='./output.tsv'"`
4551 UseRoute bool `cli:"route" usage:"set if you need route data from IRR (this might be slow) --route"`
52+ Interval string `cli:"interval" usage:"time interval after a API call to handle rate limit (ms=msec s=sec, m=min) --interval=1.5s"`
53+ Parallel int `cli:"m,parallel" usage:"parallel number (multiple API calls) --parallel=2" dft:"2"`
54+ Verbose bool `cli:"v,verbose" usage:"set if you need detail logs --verbose"`
4655 Debug bool `cli:"debug" usage:"set if you use HTTP debug feature --debug"`
4756}
4857
4958func (a * listT ) Validate (ctx * cli.Context ) error {
59+ if a .Interval != "" {
60+ if _ , err := time .ParseDuration (a .Interval ); err != nil {
61+ return fmt .Errorf ("invalid 'interval' format: [%w]" , err )
62+ }
63+ }
64+
5065 return validateProviderString (a .Provider )
5166}
5267
@@ -69,16 +84,24 @@ type ListRunner struct {
6984 Provider string
7085 InputCSV string
7186 Output string
87+ Parallel int
7288 UseRoute bool
89+ Interval string
90+ Verbose bool
7391 Debug bool
92+
93+ logger log.Logger
7494}
7595
7696func newListRunner (p listT ) ListRunner {
7797 return ListRunner {
7898 Provider : p .Provider ,
7999 InputCSV : p .InputCSV ,
80100 Output : p .Output ,
101+ Parallel : p .Parallel ,
81102 UseRoute : p .UseRoute ,
103+ Interval : p .Interval ,
104+ Verbose : p .Verbose ,
82105 Debug : p .Debug ,
83106 }
84107}
@@ -99,17 +122,26 @@ func (r *ListRunner) Run() error {
99122 return err
100123 }
101124
102- maxReqNum := 3
103- maxReq := make (chan struct {}, maxReqNum )
104-
105125 providerList , err := getProvidersFromString (r .Provider )
106126 if err != nil {
107127 panic (err )
108128 }
109129
130+ // parse interval duration
131+ var interval time.Duration
132+ if r .Interval != "" {
133+ v , err := time .ParseDuration (r .Interval )
134+ if err != nil {
135+ return err
136+ }
137+ interval = v
138+ }
139+
110140 logger := & log.StdLogger {}
141+ r .logger = logger
111142 svc , err := ipfraudcheck .New (ipfraudcheck.Config {
112143 UseRoute : r .UseRoute ,
144+ Interval : interval ,
113145 Debug : r .Debug ,
114146 Logger : logger ,
115147 }, providerList )
@@ -122,6 +154,20 @@ func (r *ListRunner) Run() error {
122154
123155 providerSize := len (providerList )
124156 result := make ([]string , len (lines )* providerSize )
157+
158+ // handle kill signal
159+ sig := make (chan os.Signal , 1 )
160+ signal .Notify (sig , os .Interrupt , syscall .SIGTERM )
161+ go func () {
162+ <- sig
163+ logger .Errorf ("Stop signal detected!" )
164+ logger .Errorf ("Saving intermediate results..." )
165+ result = append ([]string {strings .Join (outputHeader , "\t " )}, result ... )
166+ w .WriteAll (result )
167+ os .Exit (2 )
168+ }()
169+
170+ maxReq := make (chan struct {}, r .Parallel )
125171 var wg sync.WaitGroup
126172 for i , line := range lines {
127173 i = i * providerSize
@@ -133,38 +179,49 @@ func (r *ListRunner) Run() error {
133179 wg .Done ()
134180 }()
135181
136- logger .Infof ("exec #: [%d]\n " , i / providerSize )
137- rows , err := r .execAPI (svc , line )
182+ num := i / providerSize
183+ logger .Infof ("exec #: [%d]\n " , num )
184+ rows , err := r .execAPI (svc , line , num )
138185 if err != nil {
139186 logger .Errorf ("#: [%d]; err=[%v]\n " , i , err )
140187 return
141188 }
142189 for j , row := range rows {
143190 result [i + j ] = strings .Join (row , "\t " )
144191 }
192+ svc .WaitInterval ()
145193 }(i , line )
146194 }
147195 wg .Wait ()
148196
149197 result = append ([]string {strings .Join (outputHeader , "\t " )}, result ... )
198+ logger .Infof ("Finished" )
150199 return w .WriteAll (result )
151200}
152201
153- func (r * ListRunner ) execAPI (svc * ipfraudcheck.Client , param map [string ]string ) ([][]string , error ) {
202+ func (r * ListRunner ) execAPI (svc * ipfraudcheck.Client , param map [string ]string , num int ) ([][]string , error ) {
154203 resp , err := svc .CheckIP (param ["ip_address" ])
155204 if err != nil {
156205 return nil , err
157206 }
158207
159208 rows := make ([][]string , len (resp .List ))
160- for i , r := range resp .List {
209+ for i , res := range resp .List {
161210 row := make ([]string , 0 , len (outputHeader ))
162211 for _ , v := range outputHeader {
163212 if v == "as_routes" {
164213 row = append (row , strings .Join (resp .ASPrefix , " " ))
165214 continue
166215 }
167- row = append (row , getValue (param , r , v ))
216+ row = append (row , getValue (param , res , v ))
217+ }
218+ if r .Verbose {
219+ switch {
220+ case res .Err != "" :
221+ r .logger .Errorf ("#: [%d]; provider=[%s] ip=[%s] err=[%v]\n " , num , res .ServiceName , res .IP , res .Err )
222+ default :
223+ r .logger .Infof ("#: [%d]; provider=[%s] ip=[%s] row=[%v]\n " , num , res .ServiceName , res .IP , row )
224+ }
168225 }
169226 rows [i ] = row
170227 }
@@ -197,10 +254,8 @@ func getValue(param map[string]string, resp provider.FraudCheckResponse, name st
197254 return strconv .FormatFloat (resp .Latitude , 'f' , 5 , 64 )
198255 case "longitude" :
199256 return strconv .FormatFloat (resp .Longitude , 'f' , 5 , 64 )
200- case "is_anonymous" :
201- return strconv .FormatBool (resp .IsAnonymous )
202257 case "is_anonymous_vpn" :
203- return strconv .FormatBool (resp .IsAnonymousVPN )
258+ return strconv .FormatBool (resp .IsVPN )
204259 case "is_hosting" :
205260 return strconv .FormatBool (resp .IsHosting )
206261 case "is_proxy" :
@@ -213,6 +268,10 @@ func getValue(param map[string]string, resp provider.FraudCheckResponse, name st
213268 return strconv .FormatBool (resp .IsBogon )
214269 case "has_other_threat" :
215270 return strconv .FormatBool (resp .HasOtherThreat )
271+ case "threat_comment" :
272+ return resp .ThreatComment
273+ case "error" :
274+ return resp .Err
216275 }
217276 return ""
218277}
0 commit comments