route.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright 2019 the Kilo authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package route
  15. import (
  16. "errors"
  17. "fmt"
  18. "sync"
  19. "github.com/vishvananda/netlink"
  20. "golang.org/x/sys/unix"
  21. )
  22. // Table represents a routing table.
  23. // Table can safely be used concurrently.
  24. type Table struct {
  25. errors chan error
  26. mu sync.Mutex
  27. routes map[string]*netlink.Route
  28. subscribed bool
  29. // Make these functions fields to allow
  30. // for testing.
  31. add func(*netlink.Route) error
  32. del func(*netlink.Route) error
  33. }
  34. // NewTable generates a new table.
  35. func NewTable() *Table {
  36. return &Table{
  37. errors: make(chan error),
  38. routes: make(map[string]*netlink.Route),
  39. add: netlink.RouteReplace,
  40. del: func(r *netlink.Route) error {
  41. name := routeToString(r)
  42. if name == "" {
  43. return errors.New("attempting to delete invalid route")
  44. }
  45. routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
  46. if err != nil {
  47. return fmt.Errorf("failed to list routes before deletion: %v", err)
  48. }
  49. for _, route := range routes {
  50. if routeToString(&route) == name {
  51. return netlink.RouteDel(r)
  52. }
  53. }
  54. return nil
  55. },
  56. }
  57. }
  58. // Run watches for changes to routes in the table and reconciles
  59. // the table against the desired state.
  60. func (t *Table) Run(stop <-chan struct{}) (<-chan error, error) {
  61. t.mu.Lock()
  62. if t.subscribed {
  63. t.mu.Unlock()
  64. return t.errors, nil
  65. }
  66. // Ensure a given instance only subscribes once.
  67. t.subscribed = true
  68. t.mu.Unlock()
  69. events := make(chan netlink.RouteUpdate)
  70. if err := netlink.RouteSubscribe(events, stop); err != nil {
  71. return t.errors, fmt.Errorf("failed to subscribe to route events: %v", err)
  72. }
  73. go func() {
  74. defer close(t.errors)
  75. for {
  76. var e netlink.RouteUpdate
  77. select {
  78. case e = <-events:
  79. case <-stop:
  80. return
  81. }
  82. switch e.Type {
  83. // Watch for deleted routes to reconcile this table's routes.
  84. case unix.RTM_DELROUTE:
  85. t.mu.Lock()
  86. for _, r := range t.routes {
  87. // Filter out invalid routes.
  88. if r == nil || r.Dst == nil {
  89. continue
  90. }
  91. // If any deleted route's destination matches a destination
  92. // in the table, reset the corresponding route just in case.
  93. if r.Dst.IP.Equal(e.Route.Dst.IP) && r.Dst.Mask.String() == e.Route.Dst.Mask.String() {
  94. if err := t.add(r); err != nil {
  95. nonBlockingSend(t.errors, fmt.Errorf("failed add route: %v", err))
  96. }
  97. }
  98. }
  99. t.mu.Unlock()
  100. }
  101. }
  102. }()
  103. return t.errors, nil
  104. }
  105. // CleanUp will clean up any routes created by the instance.
  106. func (t *Table) CleanUp() error {
  107. t.mu.Lock()
  108. defer t.mu.Unlock()
  109. for k, route := range t.routes {
  110. if err := t.del(route); err != nil {
  111. return fmt.Errorf("failed to delete route: %v", err)
  112. }
  113. delete(t.routes, k)
  114. }
  115. return nil
  116. }
  117. // Set idempotently overwrites any routes previously defined
  118. // for the table with the given set of routes.
  119. func (t *Table) Set(routes []*netlink.Route) error {
  120. r := make(map[string]*netlink.Route)
  121. for _, route := range routes {
  122. if route == nil {
  123. continue
  124. }
  125. r[routeToString(route)] = route
  126. }
  127. t.mu.Lock()
  128. defer t.mu.Unlock()
  129. for k := range t.routes {
  130. if _, ok := r[k]; !ok {
  131. if err := t.del(t.routes[k]); err != nil {
  132. return fmt.Errorf("failed to delete route: %v", err)
  133. }
  134. delete(t.routes, k)
  135. }
  136. }
  137. // When adding routes, we need to compare against what is
  138. // actually on the Linux routing table. This is because
  139. // routes can be deleted by the kernel due to interface churn
  140. // causing a situation where the controller thinks it has a route
  141. // that is not actually there.
  142. existing := make(map[string]*netlink.Route)
  143. existingRoutes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
  144. if err != nil {
  145. return fmt.Errorf("failed to list existing routes: %v", err)
  146. }
  147. for k := range existingRoutes {
  148. existing[routeToString(&existingRoutes[k])] = &existingRoutes[k]
  149. }
  150. for k := range r {
  151. if _, ok := existing[k]; !ok {
  152. if err := t.add(r[k]); err != nil {
  153. return fmt.Errorf("failed to add route %q: %v", routeToString(r[k]), err)
  154. }
  155. t.routes[k] = r[k]
  156. }
  157. }
  158. return nil
  159. }
  160. func nonBlockingSend(errors chan<- error, err error) {
  161. select {
  162. case errors <- err:
  163. default:
  164. }
  165. }
  166. func routeToString(route *netlink.Route) string {
  167. if route == nil || route.Dst == nil {
  168. return ""
  169. }
  170. src := "-"
  171. if route.Src != nil {
  172. src = route.Src.String()
  173. }
  174. gw := "-"
  175. if route.Gw != nil {
  176. gw = route.Gw.String()
  177. }
  178. return fmt.Sprintf("dst: %s, via: %s, src: %s, dev: %d", route.Dst.String(), gw, src, route.LinkIndex)
  179. }