package sgfw import ( "fmt" "strings" "sync" // "encoding/binary" // nfnetlink "github.com/subgraph/go-nfnetlink" nfqueue "github.com/subgraph/go-nfnetlink/nfqueue" // "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/subgraph/go-procsnitch" "net" ) var _interpreters = []string{ "python", "ruby", "bash", } type pendingConnection interface { policy() *Policy procInfo() *procsnitch.Info hostname() string dst() net.IP dstPort() uint16 accept() drop() print() string } type pendingPkt struct { pol *Policy name string pkt *nfqueue.NFQPacket pinfo *procsnitch.Info } func (pp *pendingPkt) policy() *Policy { return pp.pol } func (pp *pendingPkt) procInfo() *procsnitch.Info { return pp.pinfo } func (pp *pendingPkt) hostname() string { return pp.name } func (pp *pendingPkt) dst() net.IP { dst := pp.pkt.Packet.NetworkLayer().NetworkFlow().Dst() if dst.EndpointType() != layers.EndpointIPv4 { return nil } return dst.Raw() // pp.pkt.NetworkLayer().Layer } func (pp *pendingPkt) dstPort() uint16 { /* dst := pp.pkt.Packet.TransportLayer().TransportFlow().Dst() if dst.EndpointType() != layers.EndpointTCPPort { return 0 } return binary.BigEndian.Uint16(dst.Raw()) */ _, dstp := getPacketTCPPorts(pp.pkt) return dstp // return pp.pkt.DstPort } func (pp *pendingPkt) accept() { pp.pkt.Accept() } func (pp *pendingPkt) drop() { // XXX: This needs to be fixed // pp.pkt.Mark = 1 pp.pkt.Accept() } func (pp *pendingPkt) print() string { return printPacket(pp.pkt, pp.name, pp.pinfo) } type Policy struct { fw *Firewall path string application string icon string rules RuleList pendingQueue []pendingConnection promptInProgress bool lock sync.Mutex } func (fw *Firewall) PolicyForPath(path string) *Policy { fw.lock.Lock() defer fw.lock.Unlock() return fw.policyForPath(path) } func (fw *Firewall) policyForPath(path string) *Policy { if _, ok := fw.policyMap[path]; !ok { p := new(Policy) p.fw = fw p.path = path p.application = path entry := entryForPath(path) if entry != nil { p.application = entry.name p.icon = entry.icon } fw.policyMap[path] = p fw.policies = append(fw.policies, p) } return fw.policyMap[path] } func (p *Policy) processPacket(pkt *nfqueue.NFQPacket, pinfo *procsnitch.Info) { p.lock.Lock() defer p.lock.Unlock() dstb := pkt.Packet.NetworkLayer().NetworkFlow().Dst().Raw() dstip := net.IP(dstb) name := p.fw.dns.Lookup(dstip) // name := p.fw.dns.Lookup(pkt.Dst) if !FirewallConfig.LogRedact { log.Infof("Lookup(%s): %s", dstip.String(), name) } result := p.rules.filterPacket(pkt, pinfo, name) switch result { case FILTER_DENY: // XXX: this needs to be fixed // pkt.Mark = 1 pkt.Accept() case FILTER_ALLOW: pkt.Accept() case FILTER_PROMPT: p.processPromptResult(&pendingPkt{pol: p, name: name, pkt: pkt, pinfo: pinfo}) default: log.Warningf("Unexpected filter result: %d", result) } } func (p *Policy) processPromptResult(pc pendingConnection) { p.pendingQueue = append(p.pendingQueue, pc) if !p.promptInProgress { p.promptInProgress = true go p.fw.dbus.prompt(p) } } func (p *Policy) nextPending() pendingConnection { p.lock.Lock() defer p.lock.Unlock() if len(p.pendingQueue) == 0 { return nil } return p.pendingQueue[0] } func (p *Policy) removePending(pc pendingConnection) { p.lock.Lock() defer p.lock.Unlock() remaining := []pendingConnection{} for _, c := range p.pendingQueue { if c != pc { remaining = append(remaining, c) } } if len(remaining) != len(p.pendingQueue) { p.pendingQueue = remaining } } func (p *Policy) processNewRule(r *Rule, scope FilterScope) bool { p.lock.Lock() defer p.lock.Unlock() if scope != APPLY_ONCE { p.rules = append(p.rules, r) } p.filterPending(r) if len(p.pendingQueue) == 0 { p.promptInProgress = false } return p.promptInProgress } func (p *Policy) parseRule(s string, add bool) (*Rule, error) { r := new(Rule) r.mode = RULE_MODE_PERMANENT r.policy = p if !r.parse(s) { return nil, parseError(s) } if add { p.lock.Lock() defer p.lock.Unlock() p.rules = append(p.rules, r) } p.fw.addRule(r) return r, nil } func (p *Policy) removeRule(r *Rule) { p.lock.Lock() defer p.lock.Unlock() var newRules RuleList for _, rr := range p.rules { if rr.id != r.id { newRules = append(newRules, rr) } } p.rules = newRules } func (p *Policy) filterPending(rule *Rule) { remaining := []pendingConnection{} for _, pc := range p.pendingQueue { if rule.match(pc.dst(), pc.dstPort(), pc.hostname()) { log.Infof("Adding rule for: %s", rule.getString(FirewallConfig.LogRedact)) log.Noticef("%s > %s", rule.getString(FirewallConfig.LogRedact), pc.print()) if rule.rtype == RULE_ACTION_ALLOW { pc.accept() } else { pc.drop() } } else { remaining = append(remaining, pc) } } if len(remaining) != len(p.pendingQueue) { p.pendingQueue = remaining } } func (p *Policy) hasPersistentRules() bool { for _, r := range p.rules { if r.mode != RULE_MODE_SESSION { return true } } return false } func printPacket(pkt *nfqueue.NFQPacket, hostname string, pinfo *procsnitch.Info) string { proto := "???" SrcPort, DstPort := uint16(0), uint16(0) SrcIp, DstIp := getPacketIP4Addrs(pkt) switch pkt.Packet.TransportLayer().TransportFlow().EndpointType() { case 4: proto = "TCP" case 5: proto = "UDP" default: } if proto == "TCP" { SrcPort, DstPort = getPacketTCPPorts(pkt) } else if proto == "UDP" { SrcPort, DstPort = getPacketUDPPorts(pkt) } if FirewallConfig.LogRedact { hostname = STR_REDACTED } name := hostname if name == "" { name = DstIp.String() } if pinfo == nil { return fmt.Sprintf("(%s %s:%d -> %s:%d)", proto, SrcIp, SrcPort, name, DstPort) } return fmt.Sprintf("%s %s %s:%d -> %s:%d", pinfo.ExePath, proto, SrcIp, SrcPort, name, DstPort) } func (fw *Firewall) filterPacket(pkt *nfqueue.NFQPacket) { if pkt.Packet.Layer(layers.LayerTypeUDP) != nil { srcport, _ := getPacketUDPPorts(pkt) if srcport == 53 { pkt.Accept() fw.dns.processDNS(pkt.Packet) return } } _, dstip := getPacketIP4Addrs(pkt) pinfo := findProcessForPacket(pkt) if pinfo == nil { log.Warningf("No proc found for %s", printPacket(pkt, fw.dns.Lookup(dstip), nil)) pkt.Accept() return } ppath := pinfo.ExePath cf := strings.Fields(pinfo.CmdLine) if len(cf) > 1 && strings.HasPrefix(cf[1], "/") { for _, intp := range _interpreters { if strings.Contains(pinfo.ExePath, intp) { ppath = cf[1] break } } } log.Debugf("filterPacket [%s] %s", ppath, printPacket(pkt, fw.dns.Lookup(dstip), nil)) if basicAllowPacket(pkt) { pkt.Accept() return } policy := fw.PolicyForPath(ppath) policy.processPacket(pkt, pinfo) } func findProcessForPacket(pkt *nfqueue.NFQPacket) *procsnitch.Info { _, dstip := getPacketIP4Addrs(pkt) srcp, dstp := getPacketPorts(pkt) if pkt.Packet.Layer(layers.LayerTypeTCP) != nil { return procsnitch.LookupTCPSocketProcess(srcp, dstip, dstp) } else if pkt.Packet.Layer(layers.LayerTypeUDP) != nil { return procsnitch.LookupUDPSocketProcess(srcp) } log.Warningf("Packet has unknown protocol: %d", pkt.Packet.NetworkLayer().LayerType()) //log.Warningf("Packet has unknown protocol: %d", pkt.Protocol) return nil } func basicAllowPacket(pkt *nfqueue.NFQPacket) bool { _, dstip := getPacketIP4Addrs(pkt) return dstip.IsLoopback() || dstip.IsLinkLocalMulticast() || pkt.Packet.Layer(layers.LayerTypeTCP) == nil // pkt.Protocol != nfqueue.TCP } func getPacketIP4Addrs(pkt *nfqueue.NFQPacket) (net.IP, net.IP) { ipLayer := pkt.Packet.Layer(layers.LayerTypeIPv4) if ipLayer == nil { return net.IP{0,0,0,0}, net.IP{0,0,0,0} } ip, _ := ipLayer.(*layers.IPv4) return ip.SrcIP, ip.DstIP } func getPacketTCPPorts(pkt *nfqueue.NFQPacket) (uint16, uint16) { tcpLayer := pkt.Packet.Layer(layers.LayerTypeTCP) if tcpLayer == nil { return 0, 0 } tcp, _ := tcpLayer.(*layers.TCP) return uint16(tcp.SrcPort), uint16(tcp.DstPort) } func getPacketUDPPorts(pkt *nfqueue.NFQPacket) (uint16, uint16) { udpLayer := pkt.Packet.Layer(layers.LayerTypeUDP) if udpLayer == nil { return 0, 0 } udp, _ := udpLayer.(*layers.UDP) return uint16(udp.SrcPort), uint16(udp.DstPort) } func getPacketPorts(pkt *nfqueue.NFQPacket) (uint16, uint16) { s, d := getPacketTCPPorts(pkt) if s == 0 && d == 0 { s, d = getPacketUDPPorts(pkt) } return s, d }