portlist.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /*
  2. * Copyright (c) 2021, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package common
  20. import (
  21. "bytes"
  22. "encoding/json"
  23. "strconv"
  24. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  25. )
  26. // PortList provides a lookup for a configured list of IP ports and port
  27. // ranges. PortList is intended for use with JSON config files and is
  28. // initialized via UnmarshalJSON.
  29. //
  30. // A JSON port list field should look like:
  31. //
  32. // "FieldName": [1, 2, 3, [10, 20], [30, 40]]
  33. //
  34. // where the ports in the list are 1, 2, 3, 10-20, 30-40. UnmarshalJSON
  35. // validates that each port is in the range 1-65535 and that ranges have two
  36. // elements in increasing order. PortList is designed to be backwards
  37. // compatible with existing JSON config files where port list fields were
  38. // defined as `[]int`.
  39. type PortList struct {
  40. portRanges [][2]int
  41. lookup map[int]bool
  42. }
  43. const lookupThreshold = 10
  44. // OptimizeLookups converts the internal port list representation to use a
  45. // map, which increases the performance of lookups for longer lists with an
  46. // increased memory footprint tradeoff. OptimizeLookups is not safe to use
  47. // concurrently with Lookup and should be called immediately after
  48. // UnmarshalJSON and before performing lookups.
  49. func (p *PortList) OptimizeLookups() {
  50. if p == nil {
  51. return
  52. }
  53. // TODO: does the threshold take long ranges into account?
  54. if len(p.portRanges) > lookupThreshold {
  55. p.lookup = make(map[int]bool)
  56. for _, portRange := range p.portRanges {
  57. for i := portRange[0]; i <= portRange[1]; i++ {
  58. p.lookup[i] = true
  59. }
  60. }
  61. }
  62. }
  63. // IsEmpty returns true for a nil PortList or a PortList with no entries.
  64. func (p *PortList) IsEmpty() bool {
  65. if p == nil {
  66. return true
  67. }
  68. return len(p.portRanges) == 0
  69. }
  70. // Lookup returns true if the specified port is in the port list and false
  71. // otherwise. Lookups on a nil PortList are allowed and return false.
  72. func (p *PortList) Lookup(port int) bool {
  73. if p == nil {
  74. return false
  75. }
  76. if p.lookup != nil {
  77. return p.lookup[port]
  78. }
  79. for _, portRange := range p.portRanges {
  80. if port >= portRange[0] && port <= portRange[1] {
  81. return true
  82. }
  83. }
  84. return false
  85. }
  86. // UnmarshalJSON implements the json.Unmarshaler interface.
  87. func (p *PortList) UnmarshalJSON(b []byte) error {
  88. p.portRanges = nil
  89. p.lookup = nil
  90. if bytes.Equal(b, []byte("null")) {
  91. return nil
  92. }
  93. decoder := json.NewDecoder(bytes.NewReader(b))
  94. decoder.UseNumber()
  95. var array []interface{}
  96. err := decoder.Decode(&array)
  97. if err != nil {
  98. return errors.Trace(err)
  99. }
  100. p.portRanges = make([][2]int, len(array))
  101. for i, portRange := range array {
  102. var startPort, endPort int64
  103. if portNumber, ok := portRange.(json.Number); ok {
  104. port, err := portNumber.Int64()
  105. if err != nil {
  106. return errors.Trace(err)
  107. }
  108. startPort = port
  109. endPort = port
  110. } else if array, ok := portRange.([]interface{}); ok {
  111. if len(array) != 2 {
  112. return errors.TraceNew("invalid range size")
  113. }
  114. portNumber, ok := array[0].(json.Number)
  115. if !ok {
  116. return errors.TraceNew("invalid type")
  117. }
  118. port, err := portNumber.Int64()
  119. if err != nil {
  120. return errors.Trace(err)
  121. }
  122. startPort = port
  123. portNumber, ok = array[1].(json.Number)
  124. if !ok {
  125. return errors.TraceNew("invalid type")
  126. }
  127. port, err = portNumber.Int64()
  128. if err != nil {
  129. return errors.Trace(err)
  130. }
  131. endPort = port
  132. } else {
  133. return errors.TraceNew("invalid type")
  134. }
  135. if startPort < 1 || startPort > 65535 {
  136. return errors.TraceNew("invalid range start")
  137. }
  138. if endPort < 1 || endPort > 65535 || endPort < startPort {
  139. return errors.TraceNew("invalid range end")
  140. }
  141. p.portRanges[i] = [2]int{int(startPort), int(endPort)}
  142. }
  143. return nil
  144. }
  145. // MarshalJSON implements the json.Marshaler interface.
  146. func (p *PortList) MarshalJSON() ([]byte, error) {
  147. var json bytes.Buffer
  148. json.WriteString("[")
  149. for i, portRange := range p.portRanges {
  150. if i > 0 {
  151. json.WriteString(",")
  152. }
  153. if portRange[0] == portRange[1] {
  154. json.WriteString(strconv.Itoa(portRange[0]))
  155. } else {
  156. json.WriteString("[")
  157. json.WriteString(strconv.Itoa(portRange[0]))
  158. json.WriteString(",")
  159. json.WriteString(strconv.Itoa(portRange[1]))
  160. json.WriteString("]")
  161. }
  162. }
  163. json.WriteString("]")
  164. return json.Bytes(), nil
  165. }