A REST API for the DN42 registry, written in Go, to provide a bridge between interactive applications and the registry. https://explorer.burble.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

481 lines
11KB

  1. //////////////////////////////////////////////////////////////////////////
  2. // DN42 Registry API Server
  3. //////////////////////////////////////////////////////////////////////////
  4. package main
  5. //////////////////////////////////////////////////////////////////////////
  6. import (
  7. "fmt"
  8. "github.com/gorilla/mux"
  9. log "github.com/sirupsen/logrus"
  10. // "math/big"
  11. "bufio"
  12. "net"
  13. "net/http"
  14. "os"
  15. "sort"
  16. "strconv"
  17. "strings"
  18. "time"
  19. )
  20. //////////////////////////////////////////////////////////////////////////
  21. // register the api
  22. func init() {
  23. EventBus.Listen("APIEndpoint", InitROAAPI)
  24. EventBus.Listen("RegistryUpdate", ROAUpdate)
  25. }
  26. //////////////////////////////////////////////////////////////////////////
  27. // data model
  28. type PrefixROA struct {
  29. Prefix string `json:"prefix"`
  30. MaxLen uint8 `json:"maxLength"`
  31. ASN string `json:"asn"`
  32. }
  33. type ROAFilter struct {
  34. Number uint `json:"nr"`
  35. Action string `json:"action"`
  36. Prefix string `json:"prefix"`
  37. MinLen uint8 `json:"minlen"`
  38. MaxLen uint8 `json:"maxlen"`
  39. Network *net.IPNet `json:"-"`
  40. IPType uint8 `json:"-"`
  41. }
  42. type ROA struct {
  43. CTime time.Time
  44. Commit string
  45. Filters []*ROAFilter
  46. IPv4 []*PrefixROA
  47. IPv6 []*PrefixROA
  48. }
  49. var ROAData *ROA
  50. // set validity period for one week
  51. // this might appear to be a long time, but is intended to provide
  52. // enough time to prevent expiry of the data between real registry
  53. // updates (which may only happen infrequently)
  54. const ROA_JSON_VALIDITY_PERIOD = (7 * 24)
  55. type ROAMetaData struct {
  56. Counts uint `json:"counts"`
  57. Generated uint32 `json:"generated"`
  58. Valid uint32 `json:"valid"`
  59. Signature string `json:"signature,omitempty"`
  60. SignatureDate string `json:"signatureDate,omitempty"`
  61. }
  62. type ROAJSON struct {
  63. MetaData ROAMetaData `json:"metadata"`
  64. Roas []*PrefixROA `json:"roas"`
  65. }
  66. var ROAJSONResponse *ROAJSON
  67. //////////////////////////////////////////////////////////////////////////
  68. // called from main to initialise the API routing
  69. func InitROAAPI(params ...interface{}) {
  70. router := params[0].(*mux.Router)
  71. s := router.
  72. Methods("GET").
  73. PathPrefix("/roa").
  74. Subrouter()
  75. s.HandleFunc("/filter/{ipv}", roaFilterHandler)
  76. s.HandleFunc("/json", roaJSONHandler)
  77. s.HandleFunc("/bird/{birdv}/{ipv}", roaBirdHandler)
  78. log.Info("ROA API installed")
  79. }
  80. //////////////////////////////////////////////////////////////////////////
  81. // api handlers
  82. // return JSON formatted version of filter{,6}.txt
  83. func roaFilterHandler(w http.ResponseWriter, r *http.Request) {
  84. vars := mux.Vars(r)
  85. ipv := vars["ipv"]
  86. // pre-create an array to hold the result
  87. filters := make([]*ROAFilter, 0, len(ROAData.Filters))
  88. // helper closure to select from the filter array
  89. fselect := func(a []*ROAFilter, t uint8) []*ROAFilter {
  90. for _, f := range ROAData.Filters {
  91. if f.IPType == t {
  92. a = append(a, f)
  93. }
  94. }
  95. return a
  96. }
  97. // add ipv4 filters if required
  98. if strings.ContainsRune(ipv, '4') {
  99. filters = fselect(filters, 4)
  100. }
  101. // add ipv6 filters if required
  102. if strings.ContainsRune(ipv, '6') {
  103. filters = fselect(filters, 6)
  104. }
  105. ResponseJSON(w, filters)
  106. }
  107. // return JSON formatted ROA data suitable for use with GoRTR
  108. func roaJSONHandler(w http.ResponseWriter, r *http.Request) {
  109. // check validity period of returned data
  110. tnow := uint32(time.Now().Unix())
  111. valid := ROAJSONResponse.MetaData.Valid
  112. // check if validity period is close to expiry
  113. if (tnow > valid) ||
  114. ((valid - tnow) < (ROA_JSON_VALIDITY_PERIOD / 4)) {
  115. // if so extend the validity period
  116. ROAJSONResponse.MetaData.Valid += (ROA_JSON_VALIDITY_PERIOD * 3600)
  117. }
  118. ResponseJSON(w, ROAJSONResponse)
  119. }
  120. // return the roa in bird format
  121. func roaBirdHandler(w http.ResponseWriter, r *http.Request) {
  122. vars := mux.Vars(r)
  123. birdv := vars["birdv"]
  124. ipv := vars["ipv"]
  125. // bird 1 or bird 2 format
  126. birdf := "roa %s max %d as %s;\n"
  127. if birdv == "2" {
  128. birdf = "route %s max %d as %s;\n"
  129. }
  130. var roa []*PrefixROA
  131. if strings.ContainsRune(ipv, '4') {
  132. roa = append(roa, ROAData.IPv4...)
  133. }
  134. if strings.ContainsRune(ipv, '6') {
  135. roa = append(roa, ROAData.IPv6...)
  136. }
  137. w.Header().Set("Content-Type", "text/plain")
  138. w.Header().Set("Access-Control-Allow-Origin", "*")
  139. fmt.Fprintf(w, "#\n# dn42regsrv ROA Generator\n# Last Updated: %s\n"+
  140. "# Commit: %s\n#\n", ROAData.CTime.String(), ROAData.Commit)
  141. for _, r := range roa {
  142. fmt.Fprintf(w, birdf, r.Prefix, r.MaxLen, r.ASN[2:])
  143. }
  144. }
  145. //////////////////////////////////////////////////////////////////////////
  146. // called whenever the registry is updated
  147. func ROAUpdate(params ...interface{}) {
  148. registry := params[0].(*Registry)
  149. path := params[1].(string)
  150. // initiate new ROA data
  151. roa := &ROA{
  152. CTime: time.Now(),
  153. Commit: registry.Commit,
  154. }
  155. // load filter{,6}.txt files
  156. if roa.loadFilter(path+"/filter.txt", 4) != nil {
  157. // error loading IPv4 filter, don't update
  158. return
  159. }
  160. if roa.loadFilter(path+"/filter6.txt", 6) != nil {
  161. // error loading IPv6 filter, don't update
  162. return
  163. }
  164. // compile ROA prefixes
  165. roa.IPv4 = roa.CompileROA(registry, "route")
  166. roa.IPv6 = roa.CompileROA(registry, "route6")
  167. // swap in the new data
  168. ROAData = roa
  169. log.WithFields(log.Fields{
  170. "ipv4": len(roa.IPv4),
  171. "ipv6": len(roa.IPv6),
  172. }).Debug("ROA data updated")
  173. // pre-compute the JSON return struct
  174. utime := uint32(roa.CTime.Unix())
  175. response := &ROAJSON{
  176. MetaData: ROAMetaData{
  177. Generated: utime,
  178. Valid: utime + (ROA_JSON_VALIDITY_PERIOD * 3600),
  179. },
  180. }
  181. response.Roas = append(roa.IPv4, roa.IPv6...)
  182. response.MetaData.Counts = uint(len(response.Roas))
  183. ROAJSONResponse = response
  184. }
  185. //////////////////////////////////////////////////////////////////////////
  186. // load network filter definitions from a filter file
  187. func (roa *ROA) loadFilter(path string, iptype uint8) error {
  188. // open the file for reading
  189. file, err := os.Open(path)
  190. if err != nil {
  191. log.WithFields(log.Fields{
  192. "path": path,
  193. "error": err,
  194. }).Error("Unable to open filter file")
  195. return err
  196. }
  197. defer file.Close()
  198. // helper closure to convert strings to numbers
  199. var cerr error
  200. convert := func(s string) int {
  201. if cerr != nil {
  202. return 0
  203. }
  204. val, cerr := strconv.Atoi(s)
  205. if cerr != nil {
  206. log.WithFields(log.Fields{
  207. "number": s,
  208. "error": err,
  209. }).Error("Unable to parse number in filter file")
  210. return 0
  211. }
  212. return val
  213. }
  214. filters := make([]*ROAFilter, 0)
  215. // read the file line by line
  216. scanner := bufio.NewScanner(file)
  217. for scanner.Scan() {
  218. line := strings.TrimSpace(scanner.Text())
  219. // remove any comments
  220. if ix := strings.IndexRune(line, '#'); ix != -1 {
  221. line = line[:ix]
  222. }
  223. fields := strings.Fields(line)
  224. if len(fields) >= 5 {
  225. // parse the prefix in to a NetIP structure
  226. prefix := fields[2]
  227. _, network, err := net.ParseCIDR(prefix)
  228. if err != nil {
  229. log.WithFields(log.Fields{
  230. "path": path,
  231. "prefix": prefix,
  232. "error": err,
  233. }).Error("Unable to parse CIDR in filter file")
  234. } else {
  235. // construct the filter object
  236. roaf := &ROAFilter{
  237. Number: uint(convert(fields[0])),
  238. Action: fields[1],
  239. Prefix: prefix,
  240. MinLen: uint8(convert(fields[3])),
  241. MaxLen: uint8(convert(fields[4])),
  242. Network: network,
  243. IPType: iptype,
  244. }
  245. // add to list if no strconv error
  246. if cerr == nil {
  247. filters = append(filters, roaf)
  248. }
  249. }
  250. }
  251. }
  252. // did something go wrong ?
  253. if err := scanner.Err(); err != nil {
  254. log.WithFields(log.Fields{
  255. "path": path,
  256. "error": err,
  257. }).Error("Scanner error reading filter file")
  258. return err
  259. }
  260. // filter.txt should be in order,
  261. // but still sort by number just in case
  262. sort.Slice(filters, func(i, j int) bool {
  263. return filters[i].Number < filters[j].Number
  264. })
  265. // add to the roa object
  266. roa.Filters = append(roa.Filters, filters...)
  267. return nil
  268. }
  269. //////////////////////////////////////////////////////////////////////////
  270. // return the filter object that matches an IP address
  271. func (roa *ROA) MatchFilter(ip net.IP) *ROAFilter {
  272. for _, filter := range roa.Filters {
  273. if filter.Network.Contains(ip) {
  274. return filter
  275. }
  276. }
  277. log.WithFields(log.Fields{
  278. "IP": ip,
  279. }).Error("Couldn't match address to filter !")
  280. return nil
  281. }
  282. //////////////////////////////////////////////////////////////////////////
  283. // compile ROA data
  284. func (roa *ROA) CompileROA(registry *Registry,
  285. tname string) []*PrefixROA {
  286. // prepare indices to the route object keys
  287. stype := registry.Schema[tname]
  288. routeIX := stype.KeyIndex[tname]
  289. originIX := stype.KeyIndex["origin"]
  290. mlenIX := stype.KeyIndex["max-length"]
  291. roalist := make([]*PrefixROA, 0, len(routeIX.Objects))
  292. // for each object that has a route key
  293. for object, rattribs := range routeIX.Objects {
  294. if len(rattribs) > 1 {
  295. log.WithFields(log.Fields{
  296. "object": object.Ref,
  297. }).Warn("Found object with multiple route attributes")
  298. }
  299. // extract the prefix
  300. prefix := rattribs[0].RawValue
  301. prefIP, prefNet, err := net.ParseCIDR(prefix)
  302. if err != nil {
  303. log.WithFields(log.Fields{
  304. "object": object.Ref,
  305. "prefix": prefix,
  306. "error": err,
  307. }).Error("Unable to parse CIDR in ROA")
  308. continue
  309. }
  310. // check for CIDR errors
  311. if !prefIP.Equal(prefNet.IP) {
  312. log.WithFields(log.Fields{
  313. "prefix": prefix,
  314. }).Warn("Denied ROA: invalid CIDR")
  315. continue
  316. }
  317. // match the prefix to the prefix filters
  318. filter := roa.MatchFilter(prefNet.IP)
  319. if filter == nil {
  320. continue
  321. }
  322. // don't allow routes that are denied in the filter rules
  323. if filter.Action == "deny" {
  324. log.WithFields(log.Fields{
  325. "object": object.Ref,
  326. "prefix": prefix,
  327. "filter": filter.Prefix,
  328. }).Warn("Denied ROA: through filter rule")
  329. continue
  330. }
  331. mlen := filter.MaxLen
  332. prefLen, _ := prefNet.Mask.Size()
  333. // calculate the max-length for this object
  334. // check if the attribute has max-length defined
  335. mattrib := mlenIX.Objects[object]
  336. if mattrib != nil {
  337. // use the local max-length value
  338. tmp, err := strconv.ParseUint(mattrib[0].RawValue, 10, 8)
  339. if err != nil {
  340. log.WithFields(log.Fields{
  341. "object": object.Ref,
  342. "max-length": mattrib[0].RawValue,
  343. "error": err,
  344. }).Warn("Unable to convert max-length attribute")
  345. } else {
  346. // filter rules still have precedence over local values
  347. if (uint8(tmp) < mlen) && (uint8(tmp) > filter.MinLen) {
  348. mlen = uint8(tmp)
  349. }
  350. }
  351. }
  352. // if the prefix is greater than the max length
  353. // then don't emit an ROA route (making the route invalid)
  354. if prefLen > int(mlen) {
  355. log.WithFields(log.Fields{
  356. "object": object.Ref,
  357. "prefix": prefix,
  358. "maxlen": mlen,
  359. }).Warn("Denied ROA: Prefix > filter MaxLen")
  360. continue
  361. }
  362. // look up the origin key for this object
  363. oattribs := originIX.Objects[object]
  364. if oattribs == nil {
  365. log.WithFields(log.Fields{
  366. "object": object.Ref,
  367. }).Warn("Route Object without Origin")
  368. } else {
  369. // then for origin that can announce this prefix
  370. for _, oattrib := range oattribs {
  371. // add the ROA
  372. roalist = append(roalist, &PrefixROA{
  373. Prefix: prefNet.String(),
  374. MaxLen: mlen,
  375. ASN: oattrib.RawValue,
  376. })
  377. }
  378. }
  379. }
  380. return roalist
  381. }
  382. //////////////////////////////////////////////////////////////////////////
  383. // end of code