advanced.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. package firewall
  2. import (
  3. "fmt"
  4. "net"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "time"
  9. )
  10. func inferProtocol(v map[string]any) string {
  11. inv, p := plain(strings.ToLower(text(v, "protocol")))
  12. family, _ := v["ip_version"].(float64)
  13. if p == "6" {
  14. p = "tcp"
  15. }
  16. if p == "17" {
  17. p = "udp"
  18. }
  19. if p == "1" {
  20. p = "icmp"
  21. }
  22. if p == "58" || p == "icmpv6" {
  23. p = "ipv6-icmp"
  24. }
  25. if family == 6 && p == "icmp" {
  26. p = "ipv6-icmp"
  27. }
  28. if p == "" || p == "all" {
  29. if text(v, "icmp_options") != "" {
  30. p = "icmp"
  31. if family == 6 {
  32. p = "ipv6-icmp"
  33. }
  34. } else if text(v, "src_port") != "" || text(v, "dst_port") != "" || text(v, "any_port") != "" || text(v, "tcp_flags") != "" || text(v, "tcp_mss") != "" || text(v, "action") == "change-mss" {
  35. p = "tcp"
  36. }
  37. }
  38. if inv != "" {
  39. return "!" + p
  40. }
  41. return p
  42. }
  43. func parseRange(s string, max int) (string, error) {
  44. s = portValue(s)
  45. parts := strings.Split(s, ":")
  46. if len(parts) > 2 {
  47. return "", fmt.Errorf("invalid range")
  48. }
  49. for _, p := range parts {
  50. if e := intRange(p, 0, max); e != nil {
  51. return "", e
  52. }
  53. }
  54. if len(parts) == 2 {
  55. a, _ := strconv.Atoi(parts[0])
  56. b, _ := strconv.Atoi(parts[1])
  57. if a > b {
  58. return "", fmt.Errorf("reversed range")
  59. }
  60. }
  61. return s, nil
  62. }
  63. func tcpFlags(s string) (string, string, error) {
  64. var mask, on []string
  65. for _, part := range strings.Split(strings.ToUpper(s), ",") {
  66. inv, p := plain(strings.TrimSpace(part))
  67. switch p {
  68. case "SYN", "ACK", "FIN", "RST", "URG", "PSH":
  69. default:
  70. return "", "", fmt.Errorf("unsupported TCP flag %q", p)
  71. }
  72. mask = append(mask, p)
  73. if inv == "" {
  74. on = append(on, p)
  75. }
  76. }
  77. set := "NONE"
  78. if len(on) > 0 {
  79. set = strings.Join(on, ",")
  80. }
  81. return strings.Join(mask, ","), set, nil
  82. }
  83. func ttlParts(s string) (string, string, error) {
  84. op := "eq"
  85. n := s
  86. if i := strings.IndexByte(s, ':'); i >= 0 {
  87. n = s[i+1:]
  88. switch s[:i] {
  89. case "equal":
  90. op = "eq"
  91. case "less-than":
  92. op = "lt"
  93. case "greater-than":
  94. op = "gt"
  95. default:
  96. return "", "", fmt.Errorf("TTL expects equal:N, less-than:N, or greater-than:N")
  97. }
  98. }
  99. if err := intRange(n, 0, 255); err != nil {
  100. return "", "", err
  101. }
  102. return op, n, nil
  103. }
  104. var ratePattern = regexp.MustCompile(`^([0-9]+(?:\.[0-9]+)?)(?:/([0-9]*)(s|sec|second|m|min|minute|h|hour))?$`)
  105. func rateParts(s string) (string, string, error) {
  106. s = strings.TrimSuffix(s, ":packet")
  107. parts := strings.Split(s, ",")
  108. if len(parts) > 2 {
  109. return "", "", fmt.Errorf("limit expects rate[,burst], for example 100/second,5")
  110. }
  111. m := ratePattern.FindStringSubmatch(parts[0])
  112. if m == nil {
  113. return "", "", fmt.Errorf("invalid packet rate")
  114. }
  115. rate, _ := strconv.ParseFloat(m[1], 64)
  116. period := 1.0
  117. if m[2] != "" {
  118. period, _ = strconv.ParseFloat(m[2], 64)
  119. }
  120. switch m[3] {
  121. case "m", "min", "minute":
  122. period *= 60
  123. case "h", "hour":
  124. period *= 3600
  125. }
  126. if period <= 0 || rate <= 0 {
  127. return "", "", fmt.Errorf("rate must be positive")
  128. }
  129. burst := "5"
  130. if len(parts) == 2 {
  131. burst = parts[1]
  132. if e := intRange(burst, 1, 2147483647); e != nil {
  133. return "", "", e
  134. }
  135. }
  136. return strconv.FormatFloat(rate/period, 'f', 6, 64) + "/second", burst, nil
  137. }
  138. var weekdays = map[string]string{"sun": "Sun", "mon": "Mon", "tue": "Tue", "wed": "Wed", "thu": "Thu", "fri": "Fri", "sat": "Sat"}
  139. func clock(s string) (string, error) {
  140. layout := "15:04:05"
  141. if len(s) == 5 {
  142. layout = "15:04"
  143. }
  144. t, e := time.Parse(layout, s)
  145. if e != nil {
  146. return "", fmt.Errorf("time must use HH:MM:SS")
  147. }
  148. return t.Format("15:04:05"), nil
  149. }
  150. func validateAdvanced(v map[string]any) error {
  151. p := inferProtocol(v)
  152. _, proto := plain(p)
  153. if proto == "ipv6-icmp" && v["ip_version"] != float64(6) {
  154. return fmt.Errorf("ICMPv6 requires IPv6")
  155. }
  156. if p != "" && p != "all" && !safeIdentifier.MatchString(proto) {
  157. return fmt.Errorf("invalid protocol")
  158. }
  159. if strings.HasPrefix(p, "!") && proto == "all" {
  160. return fmt.Errorf("cannot invert the all-protocols selection")
  161. }
  162. ports := text(v, "src_port") != "" || text(v, "dst_port") != "" || text(v, "any_port") != ""
  163. if ports && (strings.HasPrefix(p, "!") || (proto != "tcp" && proto != "udp")) {
  164. return fmt.Errorf("port matching requires positive TCP or UDP protocol")
  165. }
  166. if text(v, "tcp_flags") != "" || text(v, "tcp_mss") != "" {
  167. if p != "tcp" {
  168. return fmt.Errorf("TCP flags/MSS matching requires TCP")
  169. }
  170. }
  171. if s := text(v, "tcp_flags"); s != "" {
  172. if _, _, e := tcpFlags(s); e != nil {
  173. return e
  174. }
  175. }
  176. if text(v, "action") == "change-mss" && text(v, "tcp_flags") != "" {
  177. _, on, _ := tcpFlags(text(v, "tcp_flags"))
  178. found := false
  179. for _, flag := range strings.Split(on, ",") {
  180. found = found || flag == "SYN"
  181. }
  182. if !found {
  183. return fmt.Errorf("MSS rewriting requires SYN in the TCP flags")
  184. }
  185. }
  186. if len(text(v, "log_prefix")) > 29 {
  187. return fmt.Errorf("log prefix is limited to 29 bytes")
  188. }
  189. if len(text(v, "content")) > 128 {
  190. return fmt.Errorf("content matching is limited to 128 bytes")
  191. }
  192. for _, key := range []string{"tcp_mss", "packet_size"} {
  193. if s := text(v, key); s != "" {
  194. if _, e := parseRange(s, 65535); e != nil {
  195. return fmt.Errorf("%s: %w", key, e)
  196. }
  197. }
  198. }
  199. if s := text(v, "src_mac_address"); s != "" {
  200. _, s = plain(s)
  201. if _, e := net.ParseMAC(s); e != nil {
  202. return fmt.Errorf("invalid source MAC")
  203. }
  204. }
  205. if s := text(v, "dscp"); s != "" {
  206. if e := intRange(s, 0, 63); e != nil {
  207. return e
  208. }
  209. }
  210. if s := text(v, "tos"); s != "" {
  211. s = normalizeTOS(s)
  212. parts := strings.Split(s, "/")
  213. if len(parts) > 2 {
  214. return fmt.Errorf("invalid TOS")
  215. }
  216. for _, n := range parts {
  217. if _, e := strconv.ParseUint(n, 0, 8); e != nil {
  218. return fmt.Errorf("invalid TOS byte or mask")
  219. }
  220. }
  221. }
  222. if s := text(v, "connection_type"); s != "" && !safeIdentifier.MatchString(s) {
  223. return fmt.Errorf("invalid connection helper")
  224. }
  225. if s := text(v, "ttl"); s != "" {
  226. if _, _, e := ttlParts(s); e != nil {
  227. return e
  228. }
  229. }
  230. if text(v, "action") == "change-ttl" {
  231. if e := intRange(strings.TrimLeft(text(v, "new_ttl"), "+-"), 0, 255); e != nil {
  232. return fmt.Errorf("invalid new TTL")
  233. }
  234. }
  235. if s := text(v, "random"); s != "" {
  236. n, e := strconv.ParseFloat(strings.TrimSuffix(s, "%"), 64)
  237. if e != nil || n < 0 || n > 100 {
  238. return fmt.Errorf("random must be 0–100 percent")
  239. }
  240. }
  241. if s := text(v, "icmp_options"); s != "" {
  242. if p != "icmp" && p != "ipv6-icmp" {
  243. return fmt.Errorf("ICMP options require ICMP protocol")
  244. }
  245. _, s = plain(s)
  246. for _, n := range strings.Split(s, ":") {
  247. if e := intRange(n, 0, 255); e != nil {
  248. return fmt.Errorf("invalid ICMP type/code")
  249. }
  250. }
  251. if len(strings.Split(s, ":")) > 2 {
  252. return fmt.Errorf("invalid ICMP options")
  253. }
  254. }
  255. if s := text(v, "ipsec_policy"); s != "" {
  256. parts := strings.Split(strings.ReplaceAll(s, ":", ","), ",")
  257. if len(parts) != 2 || (parts[0] != "in" && parts[0] != "out") || (parts[1] != "ipsec" && parts[1] != "none") {
  258. return fmt.Errorf("IPsec policy must be in,ipsec / out,ipsec / in,none / out,none")
  259. }
  260. }
  261. if s := text(v, "limit"); s != "" {
  262. if _, _, e := rateParts(s); e != nil {
  263. return e
  264. }
  265. }
  266. if s := text(v, "time"); s != "" {
  267. parts := strings.Split(s, "-")
  268. if len(parts) != 2 {
  269. return fmt.Errorf("time expects start-stop in UTC")
  270. }
  271. for _, part := range parts {
  272. if _, e := clock(part); e != nil {
  273. return e
  274. }
  275. }
  276. }
  277. if s := text(v, "days"); s != "" {
  278. for _, day := range strings.Split(s, ",") {
  279. if weekdays[strings.ToLower(day)] == "" {
  280. return fmt.Errorf("invalid weekday %q", day)
  281. }
  282. }
  283. }
  284. if s := text(v, "nth"); s != "" {
  285. parts := strings.Split(s, ",")
  286. if len(parts) != 2 {
  287. return fmt.Errorf("nth expects every,packet (packet starts at 1)")
  288. }
  289. every, e := strconv.Atoi(parts[0])
  290. packet, e2 := strconv.Atoi(parts[1])
  291. if e != nil || e2 != nil || every < 1 || packet < 1 || packet > every {
  292. return fmt.Errorf("invalid nth values")
  293. }
  294. }
  295. if s := text(v, "connection_limit"); s != "" {
  296. parts := strings.Split(s, ",")
  297. if len(parts) != 2 {
  298. return fmt.Errorf("connection limit expects count,mask")
  299. }
  300. if e := intRange(parts[0], 1, 2147483647); e != nil {
  301. return e
  302. }
  303. max := 32
  304. if v["ip_version"] == float64(6) {
  305. max = 128
  306. }
  307. if e := intRange(parts[1], 0, max); e != nil {
  308. return e
  309. }
  310. }
  311. for _, key := range []string{"src_address_type", "dst_address_type"} {
  312. s := text(v, key)
  313. if s != "" {
  314. switch strings.ToUpper(s) {
  315. case "LOCAL", "UNICAST", "BROADCAST", "MULTICAST", "ANYCAST", "BLACKHOLE", "UNREACHABLE", "PROHIBIT":
  316. default:
  317. return fmt.Errorf("unsupported address type")
  318. }
  319. }
  320. }
  321. if s := text(v, "ip_fragment"); s != "" {
  322. if s != "yes" && s != "no" {
  323. return fmt.Errorf("fragment expects yes/no")
  324. }
  325. if v["ip_version"] == float64(6) {
  326. return fmt.Errorf("fragment matching is IPv4-only")
  327. }
  328. }
  329. return nil
  330. }
  331. func appendAdvanced(args []string, rule any) []string {
  332. v := values(rule)
  333. if s := text(v, "any_port"); s != "" {
  334. inv, s := plain(s)
  335. args = append(args, "-m multiport "+inv+"--ports "+portValue(s))
  336. }
  337. if s := text(v, "packet_mark"); s != "" {
  338. inv, s := plain(s)
  339. args = append(args, fmt.Sprintf("-m mark %s--mark %d", inv, MarkNameToID(s)))
  340. }
  341. if s := text(v, "connection_type"); s != "" {
  342. args = append(args, "-m helper --helper "+s)
  343. }
  344. if items, ok := v["connection_nat_state"].([]any); ok && len(items) > 0 {
  345. var states []string
  346. for _, i := range items {
  347. state := strings.ToUpper(i.(string))
  348. state = strings.ReplaceAll(state, "SRCNAT", "SNAT")
  349. state = strings.ReplaceAll(state, "DSTNAT", "DNAT")
  350. states = append(states, state)
  351. }
  352. args = append(args, "-m conntrack --ctstate "+strings.Join(states, ","))
  353. }
  354. if s := text(v, "content"); s != "" {
  355. args = append(args, "-m string --algo bm --string "+quote(s))
  356. }
  357. if s := text(v, "src_mac_address"); s != "" {
  358. inv, s := plain(s)
  359. args = append(args, "-m mac "+inv+"--mac-source "+s)
  360. }
  361. for _, direction := range []string{"in", "out"} {
  362. if s := text(v, direction+"_bridge_port"); s != "" {
  363. inv, s := plain(s)
  364. args = append(args, "-m physdev "+inv+"--physdev-"+direction+" "+s)
  365. }
  366. }
  367. if s := text(v, "ipsec_policy"); s != "" {
  368. p := strings.Split(strings.ReplaceAll(s, ":", ","), ",")
  369. args = append(args, "-m policy --dir "+p[0]+" --pol "+p[1])
  370. }
  371. if s := text(v, "dscp"); s != "" {
  372. args = append(args, "-m dscp --dscp "+s)
  373. }
  374. if s := text(v, "tcp_mss"); s != "" {
  375. r, _ := parseRange(s, 65535)
  376. args = append(args, "-m tcpmss --mss "+r)
  377. }
  378. if s := text(v, "tos"); s != "" {
  379. args = append(args, "-m tos --tos "+normalizeTOS(s))
  380. }
  381. if s := text(v, "packet_size"); s != "" {
  382. r, _ := parseRange(s, 65535)
  383. args = append(args, "-m length --length "+r)
  384. }
  385. if s := text(v, "random"); s != "" {
  386. n, _ := strconv.ParseFloat(strings.TrimSuffix(s, "%"), 64)
  387. args = append(args, fmt.Sprintf("-m statistic --mode random --probability %.8f", n/100))
  388. }
  389. if s := text(v, "tcp_flags"); s != "" {
  390. mask, on, _ := tcpFlags(s)
  391. args = append(args, "--tcp-flags "+mask+" "+on)
  392. }
  393. if s := text(v, "icmp_options"); s != "" {
  394. inv, s := plain(s)
  395. flag := "--icmp-type"
  396. if inferProtocol(v) == "ipv6-icmp" {
  397. flag = "--icmpv6-type"
  398. }
  399. args = append(args, inv+flag+" "+strings.ReplaceAll(s, ":", "/"))
  400. }
  401. if s := text(v, "ttl"); s != "" {
  402. op, n, _ := ttlParts(s)
  403. module := "ttl"
  404. if v["ip_version"] == float64(6) {
  405. module = "hl"
  406. }
  407. args = append(args, "-m "+module+" --"+module+"-"+op+" "+n)
  408. }
  409. if s := text(v, "limit"); s != "" {
  410. rate, burst, _ := rateParts(s)
  411. args = append(args, "-m limit --limit "+rate+" --limit-burst "+burst)
  412. }
  413. if text(v, "time") != "" || text(v, "days") != "" {
  414. args = append(args, "-m time")
  415. if s := text(v, "time"); s != "" {
  416. p := strings.Split(s, "-")
  417. start, _ := clock(p[0])
  418. stop, _ := clock(p[1])
  419. args = append(args, "--timestart "+start+" --timestop "+stop)
  420. if start > stop {
  421. args = append(args, "--contiguous")
  422. }
  423. }
  424. if s := text(v, "days"); s != "" {
  425. var days []string
  426. for _, d := range strings.Split(s, ",") {
  427. days = append(days, weekdays[strings.ToLower(d)])
  428. }
  429. args = append(args, "--weekdays "+strings.Join(days, ","))
  430. }
  431. }
  432. if s := text(v, "nth"); s != "" {
  433. p := strings.Split(s, ",")
  434. packet, _ := strconv.Atoi(p[1])
  435. args = append(args, fmt.Sprintf("-m statistic --mode nth --every %s --packet %d", p[0], packet-1))
  436. }
  437. if s := text(v, "connection_limit"); s != "" {
  438. p := strings.Split(s, ",")
  439. args = append(args, "-m connlimit --connlimit-above "+p[0]+" --connlimit-mask "+p[1])
  440. }
  441. for _, direction := range []string{"src", "dst"} {
  442. if s := text(v, direction+"_address_type"); s != "" {
  443. args = append(args, "-m addrtype --"+direction+"-type "+strings.ToUpper(s))
  444. }
  445. }
  446. if s := text(v, "ip_fragment"); s != "" {
  447. if s == "yes" {
  448. args = append(args, "-f")
  449. } else {
  450. args = append(args, "! -f")
  451. }
  452. }
  453. return args
  454. }
  455. func normalizeTOS(s string) string {
  456. switch s {
  457. case "min-delay":
  458. return "0x10/0x1e"
  459. case "max-throughput":
  460. return "0x08/0x1e"
  461. case "max-reliability":
  462. return "0x04/0x1e"
  463. case "min-cost":
  464. return "0x02/0x1e"
  465. }
  466. return s
  467. }