No Description

bgpdump.go 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package internet
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "log"
  9. "net/http"
  10. "os"
  11. "os/exec"
  12. "path/filepath"
  13. "strings"
  14. "time"
  15. "github.com/garyburd/redigo/redis"
  16. )
  17. // RefreshBGPDump ensures that the latest dump available is the one which is installed.
  18. func RefreshBGPDump(conn redis.Conn) (int, error) {
  19. for _, b := range []BGPDump{
  20. {Date: time.Now()},
  21. {Date: time.Now().Add(-time.Duration(time.Hour * 24))},
  22. } {
  23. err := b.Download()
  24. if err != nil {
  25. return 0, err
  26. }
  27. if b.IsDownloaded() {
  28. return b.Import(conn)
  29. }
  30. }
  31. return 0, nil
  32. }
  33. // BGPDump encapuslates downloading and importing of BGP dumps.
  34. type BGPDump struct {
  35. Date time.Time
  36. }
  37. // Import stores the contents of a downloaded BGP dump into a redis server.
  38. // -1 is returned if the dump is alredy imported into redis.
  39. func (b *BGPDump) Import(conn redis.Conn) (int, error) {
  40. alreadyImported, err := redis.Bool(conn.Do("SISMEMBER", "i2a:imported_dates", b.day()))
  41. if err != nil {
  42. return 0, err
  43. }
  44. if alreadyImported {
  45. return -1, nil
  46. }
  47. c := exec.Command("bgpdump", "-m", b.Path())
  48. stdout, err := c.StdoutPipe()
  49. if err != nil {
  50. return 0, err
  51. }
  52. type nErr struct {
  53. n int
  54. err error
  55. }
  56. parseC := make(chan nErr)
  57. go func(r io.Reader) {
  58. defer func() {
  59. if err := recover(); err != nil {
  60. log.Println(err)
  61. switch err.(type) {
  62. case error:
  63. parseC <- nErr{
  64. err: err.(error),
  65. }
  66. default:
  67. parseC <- nErr{err: errors.New("unknown error")}
  68. }
  69. }
  70. }()
  71. n, err := b.parseBGPCSV(r, conn)
  72. parseC <- nErr{n, err}
  73. }(stdout)
  74. execC := make(chan error)
  75. go func() {
  76. err = c.Run()
  77. if err != nil {
  78. execC <- err
  79. }
  80. }()
  81. select {
  82. case err := <-execC:
  83. return 0, err
  84. case ne := <-parseC:
  85. return ne.n, ne.err
  86. }
  87. }
  88. // IsDownloaded returns true if the BGPDump archive is downloaded locally.
  89. func (b *BGPDump) IsDownloaded() bool {
  90. p := b.Path()
  91. if _, err := os.Stat(p); err == nil {
  92. return true
  93. }
  94. return false
  95. }
  96. // Download fetches an bgpdump archive from http://data.ris.ripe.net/rrc00.
  97. // A http 404 status code does not generate an error, the isDownloaded() to check success after fetching.
  98. // Download returns early with no error if the file already is downloaded to disk.
  99. func (b *BGPDump) Download() error {
  100. dt := b.Date
  101. dumpDir := b.dir()
  102. err := os.MkdirAll(dumpDir, 0777)
  103. if err != nil {
  104. return err
  105. }
  106. if b.IsDownloaded() {
  107. return nil
  108. }
  109. err = os.MkdirAll(filepath.Join(dataDir, "spool"), 0777)
  110. if err != nil {
  111. return err
  112. }
  113. tempFile, err := ioutil.TempFile(
  114. filepath.Join(dataDir, "spool"), b.day())
  115. if err != nil {
  116. return err
  117. }
  118. defer tempFile.Close()
  119. dlURL := fmt.Sprintf(
  120. "http://data.ris.ripe.net/rrc00/%s/bview.%s.%s.gz",
  121. dt.Format("2006.01"), b.day(), "0000")
  122. resp, err := http.Get(dlURL)
  123. if err != nil {
  124. return err
  125. }
  126. // Dumps from ??? to 2010-06-14 are named timestamped 2359 so do a check
  127. // for that if 0000 fails. For very early dumps the format is not static so those will fail.
  128. if resp.StatusCode == 404 && dt.Before(time.Date(2010, 06, 15, 0, 0, 0, 0, time.UTC)) {
  129. // log.Printf("trying different url, got 404 for %s", dlURL)
  130. dlURL = fmt.Sprintf(
  131. "http://data.ris.ripe.net/rrc00/%s/bview.%s.%s.gz",
  132. dt.Format("2006.01"), b.day(), "2359")
  133. resp, err = http.Get(dlURL)
  134. if err != nil {
  135. return err
  136. }
  137. }
  138. if resp.StatusCode != http.StatusOK {
  139. if resp.StatusCode == 404 {
  140. // log.Printf("Skipping download, got 404 for %s", dlURL)
  141. return nil
  142. }
  143. return fmt.Errorf("Got http status code %s response for %s", resp.Status, dlURL)
  144. }
  145. // log.Printf("Downloading %s\n", dlURL)
  146. defer resp.Body.Close()
  147. _, err = io.Copy(tempFile, resp.Body)
  148. if err != nil {
  149. return err
  150. }
  151. err = os.Rename(tempFile.Name(), b.Path())
  152. if err != nil {
  153. return err
  154. }
  155. return nil
  156. }
  157. func (b *BGPDump) parseBGPCSV(r io.Reader, conn redis.Conn) (int, error) {
  158. day := b.day()
  159. s := bufio.NewScanner(r)
  160. n := 0
  161. var asn string
  162. for s.Scan() {
  163. cols := strings.Split(s.Text(), "|")
  164. if len(cols) < 7 {
  165. return n, ParseError{
  166. Message: "too few columns",
  167. Path: filepath.Base(b.Path()),
  168. LineNum: n,
  169. Line: s.Text(),
  170. }
  171. }
  172. block := cols[5]
  173. if _, ok := asn12654blocks[block]; ok {
  174. asn = "12654"
  175. } else {
  176. asPath := cols[6]
  177. asns := strings.Split(asPath, " ")
  178. asn = asns[len(asns)-1]
  179. if asn == "" {
  180. return n, ParseError{
  181. Message: "no ASPATH data",
  182. Path: filepath.Base(b.Path()),
  183. LineNum: n,
  184. Line: s.Text(),
  185. }
  186. }
  187. }
  188. conn.Send("HSET", fmt.Sprintf("i2a:%s", block), day, asn)
  189. n++
  190. if n%10000 == 0 {
  191. err := conn.Flush()
  192. if err != nil {
  193. return 0, err
  194. }
  195. }
  196. }
  197. conn.Send("SADD", "i2a:imported_dates", day)
  198. err := conn.Flush()
  199. if err != nil {
  200. return 0, err
  201. }
  202. return n, nil
  203. }
  204. // Path returns the absolute path to the target archive dump download file.
  205. func (b *BGPDump) Path() string {
  206. return filepath.Join(
  207. b.dir(), fmt.Sprintf("%s.gz", b.Date.Format("20060102")))
  208. }
  209. func (b *BGPDump) dir() string {
  210. return filepath.Join(
  211. dataDir, "cache", b.Date.Format("200601"))
  212. }
  213. func (b *BGPDump) day() string {
  214. return b.Date.Format("20060102")
  215. }