firewall.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304
  1. package nebula
  2. import (
  3. "crypto/sha256"
  4. "encoding/binary"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "hash/fnv"
  9. "net/netip"
  10. "reflect"
  11. "slices"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. "github.com/gaissmai/bart"
  17. "github.com/rcrowley/go-metrics"
  18. "github.com/sirupsen/logrus"
  19. "github.com/slackhq/nebula/cert"
  20. "github.com/slackhq/nebula/config"
  21. "github.com/slackhq/nebula/firewall"
  22. )
  23. var ErrCannotSNAT = errors.New("cannot SNAT this packet")
  24. var ErrSNATIdentityMismatch = errors.New("refusing to SNAT for mismatched host")
  25. var ErrSNATAddressCollision = errors.New("refusing to accept an incoming packet with my SNAT address")
  26. const ipv4SourcePosition = 12
  27. const ipv4DestinationPosition = 16
  28. const sourcePortOffset = 0
  29. const destinationPortOffset = 2
  30. type FirewallInterface interface {
  31. AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr string, caName string, caSha string) error
  32. }
  33. type snatInfo struct {
  34. //Src is the source IP+port to write into unsafe-route-bound packet
  35. Src netip.AddrPort
  36. //SrcVpnIp is the overlay IP associated with this flow. It's needed to associate reply traffic so we can get it back to the right host.
  37. SrcVpnIp netip.Addr
  38. //SnatPort is the port to rewrite into an overlay-bound packet
  39. SnatPort uint16
  40. }
  41. func (s *snatInfo) Valid() bool {
  42. if s == nil {
  43. return false
  44. }
  45. return s.Src.IsValid()
  46. }
  47. type conn struct {
  48. Expires time.Time // Time when this conntrack entry will expire
  49. // record why the original connection passed the firewall, so we can re-validate
  50. // after ruleset changes. Note, rulesVersion is a uint16 so that these two
  51. // fields pack for free after the uint32 above
  52. incoming bool
  53. rulesVersion uint16
  54. //for SNAT support
  55. snat *snatInfo
  56. }
  57. // TODO: need conntrack max tracked connections handling
  58. type Firewall struct {
  59. Conntrack *FirewallConntrack
  60. InRules *FirewallTable
  61. OutRules *FirewallTable
  62. InSendReject bool
  63. OutSendReject bool
  64. //TODO: we should have many more options for TCP, an option for ICMP, and mimic the kernel a bit better
  65. // https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt
  66. TCPTimeout time.Duration //linux: 5 days max
  67. UDPTimeout time.Duration //linux: 180s max
  68. DefaultTimeout time.Duration //linux: 600s
  69. // routableNetworks describes the vpn addresses as well as any unsafe networks issued to us in the certificate.
  70. // The vpn addresses are a full bit match while the unsafe networks only match the prefix
  71. routableNetworks *bart.Lite
  72. // assignedNetworks is a list of vpn networks assigned to us in the certificate.
  73. assignedNetworks []netip.Prefix
  74. hasUnsafeNetworks bool
  75. rules string
  76. rulesVersion uint16
  77. defaultLocalCIDRAny bool
  78. incomingMetrics firewallMetrics
  79. outgoingMetrics firewallMetrics
  80. unsafeIPv4Origin netip.Addr
  81. snatAddr netip.Addr
  82. l *logrus.Logger
  83. }
  84. type firewallMetrics struct {
  85. droppedLocalAddr metrics.Counter
  86. droppedRemoteAddr metrics.Counter
  87. droppedNoRule metrics.Counter
  88. }
  89. type FirewallConntrack struct {
  90. sync.Mutex
  91. Conns map[firewall.Packet]*conn
  92. TimerWheel *TimerWheel[firewall.Packet]
  93. }
  94. // FirewallTable is the entry point for a rule, the evaluation order is:
  95. // Proto AND port AND (CA SHA or CA name) AND local CIDR AND (group OR groups OR name OR remote CIDR)
  96. type FirewallTable struct {
  97. TCP firewallPort
  98. UDP firewallPort
  99. ICMP firewallPort
  100. AnyProto firewallPort
  101. }
  102. func newFirewallTable() *FirewallTable {
  103. return &FirewallTable{
  104. TCP: firewallPort{},
  105. UDP: firewallPort{},
  106. ICMP: firewallPort{},
  107. AnyProto: firewallPort{},
  108. }
  109. }
  110. type FirewallCA struct {
  111. Any *FirewallRule
  112. CANames map[string]*FirewallRule
  113. CAShas map[string]*FirewallRule
  114. }
  115. type FirewallRule struct {
  116. // Any makes Hosts, Groups, and CIDR irrelevant
  117. Any *firewallLocalCIDR
  118. Hosts map[string]*firewallLocalCIDR
  119. Groups []*firewallGroups
  120. CIDR *bart.Table[*firewallLocalCIDR]
  121. }
  122. type firewallGroups struct {
  123. Groups []string
  124. LocalCIDR *firewallLocalCIDR
  125. }
  126. // Even though ports are uint16, int32 maps are faster for lookup
  127. // Plus we can use `-1` for fragment rules
  128. type firewallPort map[int32]*FirewallCA
  129. type firewallLocalCIDR struct {
  130. Any bool
  131. LocalCIDR *bart.Lite
  132. }
  133. // NewFirewall creates a new Firewall object. A TimerWheel is created for you from the provided timeouts.
  134. // The certificate provided should be the highest version loaded in memory.
  135. func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Duration, c cert.Certificate) *Firewall {
  136. //TODO: error on 0 duration
  137. var tmin, tmax time.Duration
  138. if tcpTimeout < UDPTimeout {
  139. tmin = tcpTimeout
  140. tmax = UDPTimeout
  141. } else {
  142. tmin = UDPTimeout
  143. tmax = tcpTimeout
  144. }
  145. if defaultTimeout < tmin {
  146. tmin = defaultTimeout
  147. } else if defaultTimeout > tmax {
  148. tmax = defaultTimeout
  149. }
  150. routableNetworks := new(bart.Lite)
  151. var assignedNetworks []netip.Prefix
  152. for _, network := range c.Networks() {
  153. nprefix := netip.PrefixFrom(network.Addr(), network.Addr().BitLen())
  154. routableNetworks.Insert(nprefix)
  155. assignedNetworks = append(assignedNetworks, network)
  156. }
  157. hasUnsafeNetworks := false
  158. for _, n := range c.UnsafeNetworks() {
  159. routableNetworks.Insert(n)
  160. hasUnsafeNetworks = true
  161. }
  162. return &Firewall{
  163. Conntrack: &FirewallConntrack{
  164. Conns: make(map[firewall.Packet]*conn),
  165. TimerWheel: NewTimerWheel[firewall.Packet](tmin, tmax),
  166. },
  167. InRules: newFirewallTable(),
  168. OutRules: newFirewallTable(),
  169. TCPTimeout: tcpTimeout,
  170. UDPTimeout: UDPTimeout,
  171. DefaultTimeout: defaultTimeout,
  172. routableNetworks: routableNetworks,
  173. assignedNetworks: assignedNetworks,
  174. hasUnsafeNetworks: hasUnsafeNetworks,
  175. l: l,
  176. incomingMetrics: firewallMetrics{
  177. droppedLocalAddr: metrics.GetOrRegisterCounter("firewall.incoming.dropped.local_addr", nil),
  178. droppedRemoteAddr: metrics.GetOrRegisterCounter("firewall.incoming.dropped.remote_addr", nil),
  179. droppedNoRule: metrics.GetOrRegisterCounter("firewall.incoming.dropped.no_rule", nil),
  180. },
  181. outgoingMetrics: firewallMetrics{
  182. droppedLocalAddr: metrics.GetOrRegisterCounter("firewall.outgoing.dropped.local_addr", nil),
  183. droppedRemoteAddr: metrics.GetOrRegisterCounter("firewall.outgoing.dropped.remote_addr", nil),
  184. droppedNoRule: metrics.GetOrRegisterCounter("firewall.outgoing.dropped.no_rule", nil),
  185. },
  186. }
  187. }
  188. func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firewall, error) {
  189. certificate := cs.getCertificate(cert.Version2)
  190. if certificate == nil { //todo if config.initiating_version is set to 1, and unsafe_networks differ, things will suck
  191. certificate = cs.getCertificate(cert.Version1)
  192. }
  193. if certificate == nil {
  194. return nil, errors.New("no certificate available to reconfigure the firewall")
  195. }
  196. fw := NewFirewall(
  197. l,
  198. c.GetDuration("firewall.conntrack.tcp_timeout", time.Minute*12),
  199. c.GetDuration("firewall.conntrack.udp_timeout", time.Minute*3),
  200. c.GetDuration("firewall.conntrack.default_timeout", time.Minute*10),
  201. certificate,
  202. )
  203. fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
  204. inboundAction := c.GetString("firewall.inbound_action", "drop")
  205. switch inboundAction {
  206. case "reject":
  207. fw.InSendReject = true
  208. case "drop":
  209. fw.InSendReject = false
  210. default:
  211. l.WithField("action", inboundAction).Warn("invalid firewall.inbound_action, defaulting to `drop`")
  212. fw.InSendReject = false
  213. }
  214. outboundAction := c.GetString("firewall.outbound_action", "drop")
  215. switch outboundAction {
  216. case "reject":
  217. fw.OutSendReject = true
  218. case "drop":
  219. fw.OutSendReject = false
  220. default:
  221. l.WithField("action", inboundAction).Warn("invalid firewall.outbound_action, defaulting to `drop`")
  222. fw.OutSendReject = false
  223. }
  224. err := AddFirewallRulesFromConfig(l, false, c, fw)
  225. if err != nil {
  226. return nil, err
  227. }
  228. err = AddFirewallRulesFromConfig(l, true, c, fw)
  229. if err != nil {
  230. return nil, err
  231. }
  232. return fw, nil
  233. }
  234. // AddRule properly creates the in memory rule structure for a firewall table.
  235. func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
  236. var (
  237. ft *FirewallTable
  238. fp firewallPort
  239. )
  240. if incoming {
  241. ft = f.InRules
  242. } else {
  243. ft = f.OutRules
  244. }
  245. switch proto {
  246. case firewall.ProtoTCP:
  247. fp = ft.TCP
  248. case firewall.ProtoUDP:
  249. fp = ft.UDP
  250. case firewall.ProtoICMP, firewall.ProtoICMPv6:
  251. //ICMP traffic doesn't have ports, so we always coerce to "any", even if a value is provided
  252. if startPort != firewall.PortAny {
  253. f.l.WithField("startPort", startPort).Warn("ignoring port specification for ICMP firewall rule")
  254. }
  255. startPort = firewall.PortAny
  256. endPort = firewall.PortAny
  257. fp = ft.ICMP
  258. case firewall.ProtoAny:
  259. fp = ft.AnyProto
  260. default:
  261. return fmt.Errorf("unknown protocol %v", proto)
  262. }
  263. // We need this rule string because we generate a hash. Removing this will break firewall reload.
  264. ruleString := fmt.Sprintf(
  265. "incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
  266. incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
  267. )
  268. f.rules += ruleString + "\n"
  269. direction := "incoming"
  270. if !incoming {
  271. direction = "outgoing"
  272. }
  273. f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
  274. Info("Firewall rule added")
  275. return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
  276. }
  277. // GetRuleHash returns a hash representation of all inbound and outbound rules
  278. func (f *Firewall) GetRuleHash() string {
  279. sum := sha256.Sum256([]byte(f.rules))
  280. return hex.EncodeToString(sum[:])
  281. }
  282. // GetRuleHashFNV returns a uint32 FNV-1 hash representation the rules, for use as a metric value
  283. func (f *Firewall) GetRuleHashFNV() uint32 {
  284. h := fnv.New32a()
  285. h.Write([]byte(f.rules))
  286. return h.Sum32()
  287. }
  288. // GetRuleHashes returns both the sha256 and FNV-1 hashes, suitable for logging
  289. func (f *Firewall) GetRuleHashes() string {
  290. return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
  291. }
  292. func (f *Firewall) SetSNATAddressFromInterface(i *Interface) {
  293. //address-mutation-avoidance is done inside Interface, the firewall doesn't need to care
  294. //todo should snatted conntracks get expired out? Probably not needed until if/when we allow reload
  295. f.snatAddr = i.inside.SNATAddress().Addr()
  296. f.unsafeIPv4Origin = i.inside.UnsafeIPv4OriginAddress().Addr()
  297. }
  298. func (f *Firewall) ShouldUnSNAT(fp *firewall.Packet) bool {
  299. // f.snatAddr is only valid if we're a snat-capable router
  300. return f.snatAddr.IsValid() && fp.RemoteAddr == f.snatAddr
  301. }
  302. func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw FirewallInterface) error {
  303. var table string
  304. if inbound {
  305. table = "firewall.inbound"
  306. } else {
  307. table = "firewall.outbound"
  308. }
  309. r := c.Get(table)
  310. if r == nil {
  311. return nil
  312. }
  313. rs, ok := r.([]any)
  314. if !ok {
  315. return fmt.Errorf("%s failed to parse, should be an array of rules", table)
  316. }
  317. for i, t := range rs {
  318. r, err := convertRule(l, t, table, i)
  319. if err != nil {
  320. return fmt.Errorf("%s rule #%v; %s", table, i, err)
  321. }
  322. if r.Code != "" && r.Port != "" {
  323. return fmt.Errorf("%s rule #%v; only one of port or code should be provided", table, i)
  324. }
  325. if r.Host == "" && len(r.Groups) == 0 && r.Cidr == "" && r.LocalCidr == "" && r.CAName == "" && r.CASha == "" {
  326. return fmt.Errorf("%s rule #%v; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided", table, i)
  327. }
  328. var sPort, errPort string
  329. if r.Code != "" {
  330. errPort = "code"
  331. sPort = r.Code
  332. } else {
  333. errPort = "port"
  334. sPort = r.Port
  335. }
  336. var proto uint8
  337. var startPort, endPort int32
  338. switch r.Proto {
  339. case "any":
  340. proto = firewall.ProtoAny
  341. startPort, endPort, err = parsePort(sPort)
  342. case "tcp":
  343. proto = firewall.ProtoTCP
  344. startPort, endPort, err = parsePort(sPort)
  345. case "udp":
  346. proto = firewall.ProtoUDP
  347. startPort, endPort, err = parsePort(sPort)
  348. case "icmp":
  349. proto = firewall.ProtoICMP
  350. startPort = firewall.PortAny
  351. endPort = firewall.PortAny
  352. if sPort != "" {
  353. l.WithField("port", sPort).Warn("ignoring port specification for ICMP firewall rule")
  354. }
  355. default:
  356. return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
  357. }
  358. if err != nil {
  359. return fmt.Errorf("%s rule #%v; %s %s", table, i, errPort, err)
  360. }
  361. if r.Cidr != "" && r.Cidr != "any" {
  362. _, err = netip.ParsePrefix(r.Cidr)
  363. if err != nil {
  364. return fmt.Errorf("%s rule #%v; cidr did not parse; %s", table, i, err)
  365. }
  366. }
  367. if r.LocalCidr != "" && r.LocalCidr != "any" {
  368. _, err = netip.ParsePrefix(r.LocalCidr)
  369. if err != nil {
  370. return fmt.Errorf("%s rule #%v; local_cidr did not parse; %s", table, i, err)
  371. }
  372. }
  373. if warning := r.sanity(); warning != nil {
  374. l.Warnf("%s rule #%v; %s", table, i, warning)
  375. }
  376. err = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
  377. if err != nil {
  378. return fmt.Errorf("%s rule #%v; `%s`", table, i, err)
  379. }
  380. }
  381. return nil
  382. }
  383. var ErrUnknownNetworkType = errors.New("unknown network type")
  384. var ErrPeerRejected = errors.New("remote address is not within a network that we handle")
  385. var ErrInvalidRemoteIP = errors.New("remote address is not in remote certificate networks")
  386. var ErrInvalidLocalIP = errors.New("local address is not in list of handled local addresses")
  387. var ErrNoMatchingRule = errors.New("no matching rule in firewall table")
  388. func (f *Firewall) unSnat(data []byte, fp *firewall.Packet) netip.Addr {
  389. c := f.peek(*fp) //unfortunately this needs to lock. Surely there's a better way.
  390. if c == nil {
  391. return netip.Addr{}
  392. }
  393. if !c.snat.Valid() {
  394. return netip.Addr{}
  395. }
  396. oldIP := netip.AddrPortFrom(f.snatAddr, fp.RemotePort)
  397. rewritePacket(data, fp, oldIP, c.snat.Src, ipv4DestinationPosition, destinationPortOffset)
  398. return c.snat.SrcVpnIp
  399. }
  400. func rewritePacket(data []byte, fp *firewall.Packet, oldIP netip.AddrPort, newIP netip.AddrPort, ipOffset int, portOffset int) {
  401. //change address
  402. copy(data[ipOffset:], newIP.Addr().AsSlice())
  403. recalcIPv4Checksum(data, oldIP.Addr(), newIP.Addr())
  404. ipHeaderLen := int(data[0]&0x0F) * 4
  405. switch fp.Protocol {
  406. case firewall.ProtoICMP:
  407. binary.BigEndian.PutUint16(data[ipHeaderLen+4:ipHeaderLen+6], newIP.Port()) //we use the ID field as a "port" for ICMP
  408. icmpCode := uint16(data[ipHeaderLen+1]) //todo not snatting on code yet (but Linux would)
  409. recalcICMPv4Checksum(data, icmpCode, icmpCode, oldIP.Port(), newIP.Port())
  410. case firewall.ProtoUDP:
  411. dstport := ipHeaderLen + portOffset
  412. binary.BigEndian.PutUint16(data[dstport:dstport+2], newIP.Port())
  413. recalcUDPv4Checksum(data, oldIP, newIP)
  414. case firewall.ProtoTCP:
  415. dstport := ipHeaderLen + portOffset
  416. binary.BigEndian.PutUint16(data[dstport:dstport+2], newIP.Port())
  417. recalcTCPv4Checksum(data, oldIP, newIP)
  418. }
  419. }
  420. func (f *Firewall) findUsableSNATPort(fp *firewall.Packet, c *conn) error {
  421. const halfThePorts = 0x7fff
  422. oldPort := fp.RemotePort
  423. conntrack := f.Conntrack
  424. conntrack.Lock()
  425. defer conntrack.Unlock()
  426. for numPortsChecked := 0; numPortsChecked < halfThePorts; numPortsChecked++ {
  427. _, ok := conntrack.Conns[*fp]
  428. if !ok {
  429. //yay, we can use this port
  430. //track the snatted flow with the same expiration as the unsnatted version
  431. c.snat.SnatPort = fp.RemotePort
  432. conntrack.Conns[*fp] = c
  433. return nil
  434. }
  435. //increment and retry. There's probably better strategies out there
  436. fp.RemotePort++
  437. if fp.RemotePort < halfThePorts {
  438. fp.RemotePort += halfThePorts // keep it ephemeral for now
  439. }
  440. }
  441. //if we made it here, we failed
  442. fp.RemotePort = oldPort
  443. return ErrCannotSNAT
  444. }
  445. func (f *Firewall) applySnat(data []byte, fp *firewall.Packet, c *conn, hostinfo *HostInfo) error {
  446. if !f.snatAddr.IsValid() {
  447. return ErrCannotSNAT
  448. }
  449. if f.snatAddr == fp.LocalAddr { //a packet that came from UDP (incoming) should never ever have our snat address on it
  450. return ErrSNATAddressCollision
  451. }
  452. if c.snat.Valid() {
  453. //old flow: make sure it came from the right place
  454. if !slices.Contains(hostinfo.vpnAddrs, c.snat.SrcVpnIp) {
  455. return ErrSNATIdentityMismatch
  456. }
  457. fp.RemoteAddr = f.snatAddr
  458. fp.RemotePort = c.snat.SnatPort
  459. } else if hostinfo.vpnAddrs[0].Is6() {
  460. //we got a new flow
  461. c.snat = &snatInfo{
  462. Src: netip.AddrPortFrom(fp.RemoteAddr, fp.RemotePort),
  463. SrcVpnIp: hostinfo.vpnAddrs[0],
  464. }
  465. fp.RemoteAddr = f.snatAddr
  466. //find a new port to use, if needed
  467. err := f.findUsableSNATPort(fp, c)
  468. if err != nil {
  469. c.snat = nil
  470. return err
  471. }
  472. } else {
  473. return ErrCannotSNAT
  474. }
  475. newIP := netip.AddrPortFrom(f.snatAddr, c.snat.SnatPort)
  476. rewritePacket(data, fp, c.snat.Src, newIP, ipv4SourcePosition, sourcePortOffset)
  477. return nil
  478. }
  479. func (f *Firewall) identifyRemoteNetworkType(h *HostInfo, fp firewall.Packet) NetworkType {
  480. if h.networks == nil {
  481. // Simple case: Certificate has one address and no unsafe networks
  482. if h.vpnAddrs[0] == fp.RemoteAddr {
  483. return NetworkTypeVPN
  484. } //else, fallthrough
  485. } else if nwType, ok := h.networks.Lookup(fp.RemoteAddr); ok {
  486. return nwType //will return NetworkTypeVPN or NetworkTypeUnsafe
  487. }
  488. //RemoteAddr not in our networks table
  489. if f.snatAddr.IsValid() && fp.IsIPv4() && h.HasOnlyV6Addresses() {
  490. return NetworkTypeUnverifiedSNATPeer
  491. } else {
  492. return NetworkTypeInvalidPeer
  493. }
  494. }
  495. func (f *Firewall) allowRemoteNetworkType(nwType NetworkType, fp firewall.Packet) error {
  496. switch nwType {
  497. case NetworkTypeVPN:
  498. return nil
  499. case NetworkTypeInvalidPeer:
  500. return ErrInvalidRemoteIP
  501. case NetworkTypeVPNPeer:
  502. //one day we might need a specialSnatMode case in here to handle routers with v4 addresses when we don't also have a v4 address?
  503. return ErrPeerRejected // reject for now, one day this may have different FW rules
  504. case NetworkTypeUnsafe:
  505. return nil // nothing special, one day this may have different FW rules
  506. case NetworkTypeUnverifiedSNATPeer:
  507. if f.unsafeIPv4Origin.IsValid() && fp.LocalAddr == f.unsafeIPv4Origin {
  508. return nil //the client case
  509. }
  510. if f.snatAddr.IsValid() {
  511. if fp.RemoteAddr == f.snatAddr {
  512. return ErrInvalidRemoteIP //we should never get a packet with our SNAT addr as the destination, or "from" our SNAT addr
  513. }
  514. return nil
  515. } else {
  516. return ErrInvalidRemoteIP
  517. }
  518. default:
  519. return ErrUnknownNetworkType //should never happen
  520. }
  521. }
  522. func (f *Firewall) willingToHandleLocalAddr(incoming bool, fp firewall.Packet, remoteNwType NetworkType) error {
  523. if f.routableNetworks.Contains(fp.LocalAddr) {
  524. return nil //easy, this should handle NetworkTypeVPN in all cases, and NetworkTypeUnsafe on the router side
  525. }
  526. if incoming { //at least for now, reject all traffic other than what we've already decided is locally routable
  527. return ErrInvalidLocalIP
  528. }
  529. //below this line, all traffic is outgoing. Outgoing traffic to NetworkTypeUnsafe is not required to be considered inbound-routable
  530. if remoteNwType == NetworkTypeUnsafe {
  531. return nil
  532. }
  533. return ErrInvalidLocalIP
  534. }
  535. // Drop returns an error if the packet should be dropped, explaining why. It
  536. // returns nil if the packet should not be dropped.
  537. func (f *Firewall) Drop(fp firewall.Packet, pkt []byte, incoming bool, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) error {
  538. table := f.OutRules
  539. if incoming {
  540. table = f.InRules
  541. }
  542. snatmode := fp.IsIPv4() && h.HasOnlyV6Addresses() && f.snatAddr.IsValid()
  543. if snatmode {
  544. //if this is an IPv4 packet from a V6 only host, and we're configured to snat that kind of traffic, it must be snatted,
  545. //so it can never be in the localcache, which lacks SNAT data
  546. //nil out the pointer to avoid ever using it
  547. localCache = nil
  548. }
  549. // Check if we spoke to this tuple, if we did then allow this packet
  550. if localCache != nil {
  551. if _, ok := localCache[fp]; ok {
  552. return nil //packet matched the cache, we're not snatting, we can return early
  553. }
  554. }
  555. c := f.inConns(fp, h, caPool, localCache)
  556. if c != nil {
  557. if incoming && snatmode {
  558. return f.applySnat(pkt, &fp, c, h)
  559. }
  560. return nil
  561. }
  562. // Make sure remote address matches nebula certificate, and determine how to treat it
  563. remoteNetworkType := f.identifyRemoteNetworkType(h, fp)
  564. if err := f.allowRemoteNetworkType(remoteNetworkType, fp); err != nil {
  565. f.metrics(incoming).droppedRemoteAddr.Inc(1)
  566. return err
  567. }
  568. // Make sure we are supposed to be handling this local ip address
  569. if err := f.willingToHandleLocalAddr(incoming, fp, remoteNetworkType); err != nil {
  570. f.metrics(incoming).droppedLocalAddr.Inc(1)
  571. return err
  572. }
  573. // We now know which firewall table to check against
  574. if !table.match(fp, incoming, h.ConnectionState.peerCert, caPool) {
  575. f.metrics(incoming).droppedNoRule.Inc(1)
  576. return ErrNoMatchingRule
  577. }
  578. // We always want to conntrack since it is a faster operation
  579. c = f.addConn(fp, incoming)
  580. if incoming && remoteNetworkType == NetworkTypeUnverifiedSNATPeer {
  581. return f.applySnat(pkt, &fp, c, h)
  582. } else {
  583. //outgoing snat is handled before this function is called
  584. return nil
  585. }
  586. }
  587. func (f *Firewall) metrics(incoming bool) firewallMetrics {
  588. if incoming {
  589. return f.incomingMetrics
  590. } else {
  591. return f.outgoingMetrics
  592. }
  593. }
  594. // Destroy cleans up any known cyclical references so the object can be freed by GC. This should be called if a new
  595. // firewall object is created
  596. func (f *Firewall) Destroy() {
  597. //TODO: clean references if/when needed
  598. }
  599. func (f *Firewall) EmitStats() {
  600. conntrack := f.Conntrack
  601. conntrack.Lock()
  602. conntrackCount := len(conntrack.Conns)
  603. conntrack.Unlock()
  604. metrics.GetOrRegisterGauge("firewall.conntrack.count", nil).Update(int64(conntrackCount))
  605. metrics.GetOrRegisterGauge("firewall.rules.version", nil).Update(int64(f.rulesVersion))
  606. metrics.GetOrRegisterGauge("firewall.rules.hash", nil).Update(int64(f.GetRuleHashFNV()))
  607. }
  608. func (f *Firewall) peek(fp firewall.Packet) *conn {
  609. f.Conntrack.Lock()
  610. c := f.Conntrack.Conns[fp]
  611. f.Conntrack.Unlock()
  612. return c
  613. }
  614. func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) *conn {
  615. conntrack := f.Conntrack
  616. conntrack.Lock()
  617. // Purge every time we test
  618. ep, has := conntrack.TimerWheel.Purge()
  619. if has {
  620. f.evict(ep)
  621. }
  622. c, ok := conntrack.Conns[fp]
  623. if !ok {
  624. conntrack.Unlock()
  625. return nil
  626. }
  627. if c.rulesVersion != f.rulesVersion {
  628. // This conntrack entry was for an older rule set, validate
  629. // it still passes with the current rule set
  630. table := f.OutRules
  631. if c.incoming {
  632. table = f.InRules
  633. }
  634. // We now know which firewall table to check against
  635. if !table.match(fp, c.incoming, h.ConnectionState.peerCert, caPool) {
  636. if f.l.Level >= logrus.DebugLevel {
  637. h.logger(f.l).
  638. WithField("fwPacket", fp).
  639. WithField("incoming", c.incoming).
  640. WithField("rulesVersion", f.rulesVersion).
  641. WithField("oldRulesVersion", c.rulesVersion).
  642. Debugln("dropping old conntrack entry, does not match new ruleset")
  643. }
  644. delete(conntrack.Conns, fp)
  645. conntrack.Unlock()
  646. return nil
  647. }
  648. if f.l.Level >= logrus.DebugLevel {
  649. h.logger(f.l).
  650. WithField("fwPacket", fp).
  651. WithField("incoming", c.incoming).
  652. WithField("rulesVersion", f.rulesVersion).
  653. WithField("oldRulesVersion", c.rulesVersion).
  654. Debugln("keeping old conntrack entry, does match new ruleset")
  655. }
  656. c.rulesVersion = f.rulesVersion
  657. }
  658. switch fp.Protocol {
  659. case firewall.ProtoTCP:
  660. c.Expires = time.Now().Add(f.TCPTimeout)
  661. case firewall.ProtoUDP:
  662. c.Expires = time.Now().Add(f.UDPTimeout)
  663. default:
  664. c.Expires = time.Now().Add(f.DefaultTimeout)
  665. }
  666. conntrack.Unlock()
  667. if localCache != nil {
  668. localCache[fp] = struct{}{}
  669. }
  670. return c
  671. }
  672. func (f *Firewall) packetTimeout(fp firewall.Packet) time.Duration {
  673. var timeout time.Duration
  674. switch fp.Protocol {
  675. case firewall.ProtoTCP:
  676. timeout = f.TCPTimeout
  677. case firewall.ProtoUDP:
  678. timeout = f.UDPTimeout
  679. default:
  680. timeout = f.DefaultTimeout
  681. }
  682. return timeout
  683. }
  684. func (f *Firewall) addConn(fp firewall.Packet, incoming bool) *conn {
  685. c := &conn{}
  686. timeout := f.packetTimeout(fp)
  687. conntrack := f.Conntrack
  688. conntrack.Lock()
  689. if _, ok := conntrack.Conns[fp]; !ok {
  690. conntrack.TimerWheel.Advance(time.Now())
  691. conntrack.TimerWheel.Add(fp, timeout)
  692. }
  693. // Record which rulesVersion allowed this connection, so we can retest after
  694. // firewall reload
  695. c.incoming = incoming
  696. c.rulesVersion = f.rulesVersion
  697. c.Expires = time.Now().Add(timeout)
  698. conntrack.Conns[fp] = c
  699. conntrack.Unlock()
  700. return c
  701. }
  702. // Evict checks if a conntrack entry has expired, if so it is removed, if not it is re-added to the wheel
  703. // Caller must own the connMutex lock!
  704. func (f *Firewall) evict(p firewall.Packet) {
  705. // Are we still tracking this conn?
  706. conntrack := f.Conntrack
  707. t, ok := conntrack.Conns[p]
  708. if !ok {
  709. return
  710. }
  711. newT := t.Expires.Sub(time.Now())
  712. // Timeout is in the future, re-add the timer
  713. if newT > 0 {
  714. conntrack.TimerWheel.Advance(time.Now())
  715. conntrack.TimerWheel.Add(p, newT)
  716. return
  717. }
  718. // This conn is done
  719. delete(conntrack.Conns, p)
  720. }
  721. func (ft *FirewallTable) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  722. if ft.AnyProto.match(p, incoming, c, caPool) {
  723. return true
  724. }
  725. switch p.Protocol {
  726. case firewall.ProtoTCP:
  727. if ft.TCP.match(p, incoming, c, caPool) {
  728. return true
  729. }
  730. case firewall.ProtoUDP:
  731. if ft.UDP.match(p, incoming, c, caPool) {
  732. return true
  733. }
  734. case firewall.ProtoICMP, firewall.ProtoICMPv6:
  735. if ft.ICMP.match(p, incoming, c, caPool) {
  736. return true
  737. }
  738. }
  739. return false
  740. }
  741. func (fp firewallPort) addRule(f *Firewall, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
  742. if startPort > endPort {
  743. return fmt.Errorf("start port was lower than end port")
  744. }
  745. for i := startPort; i <= endPort; i++ {
  746. if _, ok := fp[i]; !ok {
  747. fp[i] = &FirewallCA{
  748. CANames: make(map[string]*FirewallRule),
  749. CAShas: make(map[string]*FirewallRule),
  750. }
  751. }
  752. if err := fp[i].addRule(f, groups, host, cidr, localCidr, caName, caSha); err != nil {
  753. return err
  754. }
  755. }
  756. return nil
  757. }
  758. func (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  759. // We don't have any allowed ports, bail
  760. if fp == nil {
  761. return false
  762. }
  763. // this branch is here to catch traffic from FirewallTable.Any.match and FirewallTable.ICMP.match
  764. if p.Protocol == firewall.ProtoICMP || p.Protocol == firewall.ProtoICMPv6 {
  765. // port numbers are re-used for connection tracking of ICMP,
  766. // but we don't want to actually filter on them.
  767. return fp[firewall.PortAny].match(p, c, caPool)
  768. }
  769. var port int32
  770. if p.Fragment {
  771. port = firewall.PortFragment
  772. } else if incoming {
  773. port = int32(p.LocalPort)
  774. } else {
  775. port = int32(p.RemotePort)
  776. }
  777. if fp[port].match(p, c, caPool) {
  778. return true
  779. }
  780. return fp[firewall.PortAny].match(p, c, caPool)
  781. }
  782. func (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, cidr, localCidr, caName, caSha string) error {
  783. fr := func() *FirewallRule {
  784. return &FirewallRule{
  785. Hosts: make(map[string]*firewallLocalCIDR),
  786. Groups: make([]*firewallGroups, 0),
  787. CIDR: new(bart.Table[*firewallLocalCIDR]),
  788. }
  789. }
  790. if caSha == "" && caName == "" {
  791. if fc.Any == nil {
  792. fc.Any = fr()
  793. }
  794. return fc.Any.addRule(f, groups, host, cidr, localCidr)
  795. }
  796. if caSha != "" {
  797. if _, ok := fc.CAShas[caSha]; !ok {
  798. fc.CAShas[caSha] = fr()
  799. }
  800. err := fc.CAShas[caSha].addRule(f, groups, host, cidr, localCidr)
  801. if err != nil {
  802. return err
  803. }
  804. }
  805. if caName != "" {
  806. if _, ok := fc.CANames[caName]; !ok {
  807. fc.CANames[caName] = fr()
  808. }
  809. err := fc.CANames[caName].addRule(f, groups, host, cidr, localCidr)
  810. if err != nil {
  811. return err
  812. }
  813. }
  814. return nil
  815. }
  816. func (fc *FirewallCA) match(p firewall.Packet, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  817. if fc == nil {
  818. return false
  819. }
  820. if fc.Any.match(p, c) {
  821. return true
  822. }
  823. if t, ok := fc.CAShas[c.Certificate.Issuer()]; ok {
  824. if t.match(p, c) {
  825. return true
  826. }
  827. }
  828. s, err := caPool.GetCAForCert(c.Certificate)
  829. if err != nil {
  830. return false
  831. }
  832. return fc.CANames[s.Certificate.Name()].match(p, c)
  833. }
  834. func (fr *FirewallRule) addRule(f *Firewall, groups []string, host, cidr, localCidr string) error {
  835. flc := func() *firewallLocalCIDR {
  836. return &firewallLocalCIDR{
  837. LocalCIDR: new(bart.Lite),
  838. }
  839. }
  840. if fr.isAny(groups, host, cidr) {
  841. if fr.Any == nil {
  842. fr.Any = flc()
  843. }
  844. return fr.Any.addRule(f, localCidr)
  845. }
  846. if len(groups) > 0 {
  847. nlc := flc()
  848. err := nlc.addRule(f, localCidr)
  849. if err != nil {
  850. return err
  851. }
  852. fr.Groups = append(fr.Groups, &firewallGroups{
  853. Groups: groups,
  854. LocalCIDR: nlc,
  855. })
  856. }
  857. if host != "" {
  858. nlc := fr.Hosts[host]
  859. if nlc == nil {
  860. nlc = flc()
  861. }
  862. err := nlc.addRule(f, localCidr)
  863. if err != nil {
  864. return err
  865. }
  866. fr.Hosts[host] = nlc
  867. }
  868. if cidr != "" {
  869. c, err := netip.ParsePrefix(cidr)
  870. if err != nil {
  871. return err
  872. }
  873. nlc, _ := fr.CIDR.Get(c)
  874. if nlc == nil {
  875. nlc = flc()
  876. }
  877. err = nlc.addRule(f, localCidr)
  878. if err != nil {
  879. return err
  880. }
  881. fr.CIDR.Insert(c, nlc)
  882. }
  883. return nil
  884. }
  885. func (fr *FirewallRule) isAny(groups []string, host string, cidr string) bool {
  886. if len(groups) == 0 && host == "" && cidr == "" {
  887. return true
  888. }
  889. if slices.Contains(groups, "any") {
  890. return true
  891. }
  892. if host == "any" {
  893. return true
  894. }
  895. if cidr == "any" {
  896. return true
  897. }
  898. return false
  899. }
  900. func (fr *FirewallRule) match(p firewall.Packet, c *cert.CachedCertificate) bool {
  901. if fr == nil {
  902. return false
  903. }
  904. // Shortcut path for if groups, hosts, or cidr contained an `any`
  905. if fr.Any.match(p, c) {
  906. return true
  907. }
  908. // Need any of group, host, or cidr to match
  909. for _, sg := range fr.Groups {
  910. found := false
  911. for _, g := range sg.Groups {
  912. if _, ok := c.InvertedGroups[g]; !ok {
  913. found = false
  914. break
  915. }
  916. found = true
  917. }
  918. if found && sg.LocalCIDR.match(p, c) {
  919. return true
  920. }
  921. }
  922. if fr.Hosts != nil {
  923. if flc, ok := fr.Hosts[c.Certificate.Name()]; ok {
  924. if flc.match(p, c) {
  925. return true
  926. }
  927. }
  928. }
  929. for _, v := range fr.CIDR.Supernets(netip.PrefixFrom(p.RemoteAddr, p.RemoteAddr.BitLen())) {
  930. if v.match(p, c) {
  931. return true
  932. }
  933. }
  934. return false
  935. }
  936. func (flc *firewallLocalCIDR) addRule(f *Firewall, localCidr string) error {
  937. if localCidr == "any" {
  938. flc.Any = true
  939. return nil
  940. }
  941. if localCidr == "" {
  942. if !f.hasUnsafeNetworks || f.defaultLocalCIDRAny {
  943. flc.Any = true
  944. return nil
  945. }
  946. for _, network := range f.assignedNetworks {
  947. flc.LocalCIDR.Insert(network)
  948. }
  949. return nil
  950. }
  951. c, err := netip.ParsePrefix(localCidr)
  952. if err != nil {
  953. return err
  954. }
  955. flc.LocalCIDR.Insert(c)
  956. return nil
  957. }
  958. func (flc *firewallLocalCIDR) match(p firewall.Packet, c *cert.CachedCertificate) bool {
  959. if flc == nil {
  960. return false
  961. }
  962. if flc.Any {
  963. return true
  964. }
  965. return flc.LocalCIDR.Contains(p.LocalAddr)
  966. }
  967. type rule struct {
  968. Port string
  969. Code string
  970. Proto string
  971. Host string
  972. Groups []string
  973. Cidr string
  974. LocalCidr string
  975. CAName string
  976. CASha string
  977. }
  978. func convertRule(l *logrus.Logger, p any, table string, i int) (rule, error) {
  979. r := rule{}
  980. m, ok := p.(map[string]any)
  981. if !ok {
  982. return r, errors.New("could not parse rule")
  983. }
  984. toString := func(k string, m map[string]any) string {
  985. v, ok := m[k]
  986. if !ok {
  987. return ""
  988. }
  989. return fmt.Sprintf("%v", v)
  990. }
  991. r.Port = toString("port", m)
  992. r.Code = toString("code", m)
  993. r.Proto = toString("proto", m)
  994. r.Host = toString("host", m)
  995. r.Cidr = toString("cidr", m)
  996. r.LocalCidr = toString("local_cidr", m)
  997. r.CAName = toString("ca_name", m)
  998. r.CASha = toString("ca_sha", m)
  999. // Make sure group isn't an array
  1000. if v, ok := m["group"].([]any); ok {
  1001. if len(v) > 1 {
  1002. return r, errors.New("group should contain a single value, an array with more than one entry was provided")
  1003. }
  1004. l.Warnf("%s rule #%v; group was an array with a single value, converting to simple value", table, i)
  1005. m["group"] = v[0]
  1006. }
  1007. singleGroup := toString("group", m)
  1008. if rg, ok := m["groups"]; ok {
  1009. switch reflect.TypeOf(rg).Kind() {
  1010. case reflect.Slice:
  1011. v := reflect.ValueOf(rg)
  1012. r.Groups = make([]string, v.Len())
  1013. for i := 0; i < v.Len(); i++ {
  1014. r.Groups[i] = v.Index(i).Interface().(string)
  1015. }
  1016. case reflect.String:
  1017. r.Groups = []string{rg.(string)}
  1018. default:
  1019. r.Groups = []string{fmt.Sprintf("%v", rg)}
  1020. }
  1021. }
  1022. //flatten group vs groups
  1023. if singleGroup != "" {
  1024. // Check if we have both groups and group provided in the rule config
  1025. if len(r.Groups) > 0 {
  1026. return r, fmt.Errorf("only one of group or groups should be defined, both provided")
  1027. }
  1028. r.Groups = []string{singleGroup}
  1029. }
  1030. return r, nil
  1031. }
  1032. // sanity returns an error if the rule would be evaluated in a way that would short-circuit a configured check on a wildcard value
  1033. // rules are evaluated as "port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND local_cidr"
  1034. func (r *rule) sanity() error {
  1035. //port, proto, local_cidr are AND, no need to check here
  1036. //ca_sha and ca_name don't have a wildcard value, no need to check here
  1037. groupsEmpty := len(r.Groups) == 0
  1038. hostEmpty := r.Host == ""
  1039. cidrEmpty := r.Cidr == ""
  1040. if (groupsEmpty && hostEmpty && cidrEmpty) == true {
  1041. return nil //no content!
  1042. }
  1043. groupsHasAny := slices.Contains(r.Groups, "any")
  1044. if groupsHasAny && len(r.Groups) > 1 {
  1045. return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the other groups specified", r.Groups)
  1046. }
  1047. if r.Host == "any" {
  1048. if !groupsEmpty {
  1049. return fmt.Errorf("groups specified as %s, but host=any will match any host, regardless of groups", r.Groups)
  1050. }
  1051. if !cidrEmpty {
  1052. return fmt.Errorf("cidr specified as %s, but host=any will match any host, regardless of cidr", r.Cidr)
  1053. }
  1054. }
  1055. if groupsHasAny {
  1056. if !hostEmpty && r.Host != "any" {
  1057. return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the specified host %s", r.Groups, r.Host)
  1058. }
  1059. if !cidrEmpty {
  1060. return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the specified cidr %s", r.Groups, r.Cidr)
  1061. }
  1062. }
  1063. if r.Code != "" {
  1064. return fmt.Errorf("code specified as [%s]. Support for 'code' will be dropped in a future release, as it has never been functional", r.Code)
  1065. }
  1066. //todo alert on cidr-any
  1067. return nil
  1068. }
  1069. func parsePort(s string) (int32, int32, error) {
  1070. var err error
  1071. const notAPort int32 = -2
  1072. if s == "any" {
  1073. return firewall.PortAny, firewall.PortAny, nil
  1074. }
  1075. if s == "fragment" {
  1076. return firewall.PortFragment, firewall.PortFragment, nil
  1077. }
  1078. if !strings.Contains(s, `-`) {
  1079. rPort, err := strconv.Atoi(s)
  1080. if err != nil {
  1081. return notAPort, notAPort, fmt.Errorf("was not a number; `%s`", s)
  1082. }
  1083. return int32(rPort), int32(rPort), nil
  1084. }
  1085. sPorts := strings.SplitN(s, `-`, 2)
  1086. for i := range sPorts {
  1087. sPorts[i] = strings.Trim(sPorts[i], " ")
  1088. }
  1089. if len(sPorts) != 2 || sPorts[0] == "" || sPorts[1] == "" {
  1090. return notAPort, notAPort, fmt.Errorf("appears to be a range but could not be parsed; `%s`", s)
  1091. }
  1092. rStartPort, err := strconv.Atoi(sPorts[0])
  1093. if err != nil {
  1094. return notAPort, notAPort, fmt.Errorf("beginning range was not a number; `%s`", sPorts[0])
  1095. }
  1096. rEndPort, err := strconv.Atoi(sPorts[1])
  1097. if err != nil {
  1098. return notAPort, notAPort, fmt.Errorf("ending range was not a number; `%s`", sPorts[1])
  1099. }
  1100. startPort := int32(rStartPort)
  1101. endPort := int32(rEndPort)
  1102. if startPort == firewall.PortAny {
  1103. endPort = firewall.PortAny
  1104. }
  1105. return startPort, endPort, nil
  1106. }