conntrack.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package firewall
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "net"
  7. "os/exec"
  8. "strconv"
  9. "strings"
  10. )
  11. func ListActiveConnections() ([]ActiveConnection, error) {
  12. var conns []ActiveConnection
  13. for _, family := range []string{"ipv4", "ipv6"} {
  14. out, err := exec.Command("conntrack", "-L", "-f", family, "-o", "extended").CombinedOutput()
  15. if err != nil {
  16. return nil, fmt.Errorf("cannot read %s connection tracking: %s: %w", family, out, err)
  17. }
  18. scanner := bufio.NewScanner(bytes.NewReader(out))
  19. scanner.Buffer(make([]byte, 4096), 1024*1024)
  20. for scanner.Scan() {
  21. c := parseConntrackLine(scanner.Text())
  22. if c.Protocol != "" && c.SrcAddress != "" {
  23. conns = append(conns, c)
  24. }
  25. }
  26. if err := scanner.Err(); err != nil {
  27. return nil, err
  28. }
  29. }
  30. if conns == nil {
  31. conns = []ActiveConnection{}
  32. }
  33. return conns, nil
  34. }
  35. func parseConntrackLine(line string) ActiveConnection {
  36. var c ActiveConnection
  37. fields := strings.Fields(line)
  38. if len(fields) < 4 {
  39. return c
  40. }
  41. start := 0
  42. c.IPVersion = 4
  43. if fields[0] == "ipv4" || fields[0] == "ipv6" {
  44. if fields[0] == "ipv6" {
  45. c.IPVersion = 6
  46. }
  47. start = 2
  48. }
  49. if len(fields) <= start+2 {
  50. return c
  51. }
  52. c.Protocol = strings.ToUpper(fields[start])
  53. if c.Protocol == "ICMPV6" || c.Protocol == "IPV6-ICMP" {
  54. c.Protocol = "ICMPV6"
  55. }
  56. c.Timeout, _ = strconv.Atoi(fields[start+2])
  57. var src, dst []string
  58. var sport, dport []int
  59. for _, field := range fields[start+3:] {
  60. kv := strings.SplitN(field, "=", 2)
  61. if len(kv) == 2 {
  62. n, _ := strconv.Atoi(kv[1])
  63. switch kv[0] {
  64. case "src":
  65. src = append(src, kv[1])
  66. case "dst":
  67. dst = append(dst, kv[1])
  68. case "sport":
  69. sport = append(sport, n)
  70. case "dport":
  71. dport = append(dport, n)
  72. case "mark":
  73. c.ConnectionMark = kv[1]
  74. case "zone":
  75. c.Zone = n
  76. case "bytes":
  77. v, _ := strconv.ParseUint(kv[1], 10, 64)
  78. c.Bytes += v
  79. case "packets":
  80. v, _ := strconv.ParseUint(kv[1], 10, 64)
  81. c.Packets += v
  82. case "id":
  83. if c.ICMPID == nil {
  84. value := n
  85. c.ICMPID = &value
  86. }
  87. case "type":
  88. if c.ICMPType == nil {
  89. value := n
  90. c.ICMPType = &value
  91. }
  92. case "code":
  93. if c.ICMPCode == nil {
  94. value := n
  95. c.ICMPCode = &value
  96. }
  97. }
  98. continue
  99. }
  100. if field == "[ASSURED]" {
  101. c.Assured = true
  102. }
  103. switch field {
  104. case "ESTABLISHED", "TIME_WAIT", "CLOSE_WAIT", "FIN_WAIT", "SYN_SENT", "SYN_RECV", "CLOSE", "LAST_ACK", "LISTEN":
  105. c.TCPState = field
  106. }
  107. }
  108. if len(src) > 0 {
  109. c.SrcAddress = src[0]
  110. }
  111. if len(src) > 1 {
  112. c.ReplySrcAddress = src[1]
  113. }
  114. if len(dst) > 0 {
  115. c.DstAddress = dst[0]
  116. }
  117. if len(dst) > 1 {
  118. c.ReplyDstAddress = dst[1]
  119. }
  120. if len(sport) > 0 {
  121. c.SrcPort = sport[0]
  122. }
  123. if len(sport) > 1 {
  124. c.ReplySrcPort = sport[1]
  125. }
  126. if len(dport) > 0 {
  127. c.DstPort = dport[0]
  128. }
  129. if len(dport) > 1 {
  130. c.ReplyDstPort = dport[1]
  131. }
  132. if c.TCPState == "" {
  133. c.TCPState = c.Protocol
  134. }
  135. c.ID = fmt.Sprintf("%d-%d-%s-%s:%d-%s:%d", c.IPVersion, c.Zone, c.Protocol, c.SrcAddress, c.SrcPort, c.DstAddress, c.DstPort)
  136. if c.ICMPID != nil && c.ICMPType != nil && c.ICMPCode != nil {
  137. c.ID += fmt.Sprintf("-%d-%d-%d", *c.ICMPID, *c.ICMPType, *c.ICMPCode)
  138. }
  139. return c
  140. }
  141. type ConnectionSelector struct {
  142. Protocol string `json:"protocol"`
  143. Src string `json:"src"`
  144. Sport int `json:"sport"`
  145. Dst string `json:"dst"`
  146. Dport int `json:"dport"`
  147. IPVersion int `json:"ip_version"`
  148. Zone int `json:"zone"`
  149. ICMPID *int `json:"icmp_id"`
  150. ICMPType *int `json:"icmp_type"`
  151. ICMPCode *int `json:"icmp_code"`
  152. }
  153. func connectionDeleteArgs(s ConnectionSelector) ([]string, error) {
  154. src, dst := net.ParseIP(s.Src), net.ParseIP(s.Dst)
  155. if src == nil || dst == nil || (src.To4() == nil) != (dst.To4() == nil) {
  156. return nil, fmt.Errorf("a complete source/destination IP tuple is required")
  157. }
  158. family := "ipv4"
  159. version := 4
  160. if src.To4() == nil {
  161. family = "ipv6"
  162. version = 6
  163. }
  164. if s.IPVersion != 0 && s.IPVersion != version {
  165. return nil, fmt.Errorf("IP family does not match the addresses")
  166. }
  167. if s.Zone < 0 || s.Zone > 65535 {
  168. return nil, fmt.Errorf("invalid connection zone")
  169. }
  170. proto := strings.ToLower(s.Protocol)
  171. if proto == "ipv6-icmp" {
  172. proto = "icmpv6"
  173. }
  174. args := []string{"-D", "-f", family, "-p", proto, "-s", src.String(), "-d", dst.String(), "--zone", strconv.Itoa(s.Zone)}
  175. switch proto {
  176. case "tcp", "udp", "sctp", "dccp":
  177. if s.Sport < 1 || s.Sport > 65535 || s.Dport < 1 || s.Dport > 65535 {
  178. return nil, fmt.Errorf("both source and destination ports are required")
  179. }
  180. args = append(args, "--sport", strconv.Itoa(s.Sport), "--dport", strconv.Itoa(s.Dport))
  181. case "icmp", "icmpv6":
  182. if (proto == "icmpv6") != (version == 6) {
  183. return nil, fmt.Errorf("ICMP family mismatch")
  184. }
  185. if s.ICMPID == nil || s.ICMPType == nil || s.ICMPCode == nil || *s.ICMPID < 0 || *s.ICMPID > 65535 || *s.ICMPType < 0 || *s.ICMPType > 255 || *s.ICMPCode < 0 || *s.ICMPCode > 255 {
  186. return nil, fmt.Errorf("ICMP ID, type and code are required")
  187. }
  188. prefix := "--" + proto
  189. args = append(args, prefix+"-id", strconv.Itoa(*s.ICMPID), prefix+"-type", strconv.Itoa(*s.ICMPType), prefix+"-code", strconv.Itoa(*s.ICMPCode))
  190. case "gre", "esp", "ah":
  191. return nil, fmt.Errorf("deleting %s requires protocol-specific selectors and is not supported", proto)
  192. default:
  193. return nil, fmt.Errorf("unsupported connection protocol")
  194. }
  195. return args, nil
  196. }
  197. func KillFlow(s ConnectionSelector) error {
  198. args, err := connectionDeleteArgs(s)
  199. if err != nil {
  200. return err
  201. }
  202. out, err := exec.Command("conntrack", args...).CombinedOutput()
  203. if err != nil {
  204. return fmt.Errorf("failed to terminate connection: %s: %w", out, err)
  205. }
  206. return nil
  207. }
  208. func KillConnection(protocol, src string, sport int, dst string, dport int) error {
  209. return KillFlow(ConnectionSelector{Protocol: protocol, Src: src, Sport: sport, Dst: dst, Dport: dport})
  210. }
  211. func ValidateConnectionSelector(s ConnectionSelector) error {
  212. _, err := connectionDeleteArgs(s)
  213. return err
  214. }