diff options
-rw-r--r-- | docs/sources/flowspec.md | 7 | ||||
-rw-r--r-- | gobgp/cmd/global.go | 2 | ||||
-rw-r--r-- | packet/bgp/bgp.go | 119 | ||||
-rw-r--r-- | packet/bgp/constant.go | 46 | ||||
-rw-r--r-- | test/scenario_test/flow_spec_test.py | 2 |
5 files changed, 133 insertions, 43 deletions
diff --git a/docs/sources/flowspec.md b/docs/sources/flowspec.md index 8fddcccc..e2b02fcb 100644 --- a/docs/sources/flowspec.md +++ b/docs/sources/flowspec.md @@ -45,11 +45,11 @@ CLI syntax to add ipv4/ipv6 flowspec rule is ```shell % global rib add match <MATCH_EXPR> then <THEN_EXPR> -a [ipv4-flowspec|ipv6-flowspec] <MATCH_EXPR> : { destination <PREFIX> [<OFFSET>] | source <PREFIX> [<OFFSET>] | - protocol <PROTO>... | fragment <FRAGMENT_TYPE> | tcp-flags [not] [match] <TCPFLAG>... | + protocol <PROTO>... | fragment <FRAGMENT_TYPE> | tcp-flags [!] [=] <TCPFLAG>... | { port | destination-port | source-port | icmp-type | icmp-code | packet-length | dscp | label } <ITEM>... }... <PROTO> : ospf, pim, igp, udp, igmp, tcp, egp, rsvp, gre, ipip, unknown, icmp, sctp, <VALUE> <FRAGMENT_TYPE> : dont-fragment, is-fragment, first-fragment, last-fragment, not-a-fragment - <TCPFLAG> : rst, push, ack, urgent, fin, syn + <TCPFLAG> : U, C, E, F, S, R, P, A <ITEM> : &?{<|>|=}<value> <THEN_EXPR> : { accept | discard | rate-limit <value> | redirect <RT> | mark <value> | action { sample | terminal | sample-terminal } | rt <RT>... }... <RT> : xxx:yyy, xx.xx.xx.xx:yyy, xxx.xxx:yyy @@ -72,6 +72,9 @@ that for l2vpn flowspec rule is # add a flowspec rule which redirect flows with dst 10.0.0.0/24 and src 20.0.0.0/24 to VRF with RT 10:10 % gobgp global rib -a ipv4-flowspec add match destination 10.0.0.0/24 source 20.0.0.0/24 then redirect 10:10 +# add a flowspec rule wich discard flows with dst 2001::2/128 and port equals 80 and with TCP flags not match SA (SYN/ACK) and not match U (URG) +% gobgp global rib -a ipv6-flowspec add match destination 2001::2/128 port '=80' tcp-flags '=!SA&=!U' then discard + # show flowspec table % gobgp global rib -a ipv4-flowspec Network Next Hop AS_PATH Age Attrs diff --git a/gobgp/cmd/global.go b/gobgp/cmd/global.go index 097847e8..72295e65 100644 --- a/gobgp/cmd/global.go +++ b/gobgp/cmd/global.go @@ -863,7 +863,7 @@ usage: %s rib %s%%smatch <MATCH_EXPR> then <THEN_EXPR> -a %%s ExtCommNameMap[RATE], ExtCommNameMap[REDIRECT], ExtCommNameMap[MARK], ExtCommNameMap[ACTION], ExtCommNameMap[RT]) ipFsMatchExpr := fmt.Sprintf(` <MATCH_EXPR> : { %s <PREFIX> [<OFFSET>] | %s <PREFIX> [<OFFSET>] | - %s <PROTO>... | %s <FRAGMENT_TYPE> | %s [not] [match] <TCPFLAG>... | + %s <PROTO>... | %s <FRAGMENT_TYPE> | %s [!] [=] <TCPFLAG>... | { %s | %s | %s | %s | %s | %s | %s | %s } <ITEM>... }... <PROTO> : %s <FRAGMENT_TYPE> : dont-fragment, is-fragment, first-fragment, last-fragment, not-a-fragment diff --git a/packet/bgp/bgp.go b/packet/bgp/bgp.go index b66e197f..ece8692e 100644 --- a/packet/bgp/bgp.go +++ b/packet/bgp/bgp.go @@ -2489,32 +2489,84 @@ func flowSpecIpProtoParser(rf RouteFamily, args []string) (FlowSpecComponentInte } func flowSpecTcpFlagParser(rf RouteFamily, args []string) (FlowSpecComponentInterface, error) { - ss := make([]string, 0, len(TCPFlagNameMap)) - for _, v := range TCPFlagNameMap { - ss = append(ss, v) - } - protos := strings.Join(ss, "|") - exp := regexp.MustCompile(fmt.Sprintf("^%s (not )?(match )?((((%s)\\&)*(%s) )*(((%s)\\&)*(%s)))$", FlowSpecNameMap[FLOW_SPEC_TYPE_TCP_FLAG], protos, protos, protos, protos)) - elems := exp.FindStringSubmatch(strings.Join(args, " ")) - if len(elems) < 1 { - return nil, fmt.Errorf("invalid flag format") + args = append(args[:0], args[1:]...) // removing tcp-flags string + fullCmd := strings.Join(args, " ") // rebuiling tcp filters + opsFlags, err := parseTcpFlagCmd(fullCmd) + if err != nil { + return nil, err } items := make([]*FlowSpecComponentItem, 0) - op := 0 - if elems[2] != "" { - op |= 0x1 - } - if elems[1] != "" { - op |= 0x2 + for _, opFlag := range opsFlags { + items = append(items, NewFlowSpecComponentItem(opFlag[0], opFlag[1])) } - for _, v := range strings.Split(elems[3], " ") { - flag := 0 - for _, e := range strings.Split(v, "&") { - flag |= int(TCPFlagValueMap[e]) + return NewFlowSpecComponent(FLOW_SPEC_TYPE_TCP_FLAG, items), nil +} + +func parseTcpFlagCmd(myCmd string) ([][2]int, error) { + var index int = 0 + var tcpOperatorsFlagsValues [][2]int + var operatorValue [2]int + for index < len(myCmd) { + myCmdChar := myCmd[index : index+1] + switch myCmdChar { + case TCPFlagOpNameMap[TCP_FLAG_OP_MATCH]: + if bit := TCPFlagOpValueMap[myCmdChar]; bit&TCPFlagOp(operatorValue[0]) == 0 { + operatorValue[0] |= int(bit) + index++ + } else { + err := fmt.Errorf("Match flag appears multiple time") + return nil, err + } + case TCPFlagOpNameMap[TCP_FLAG_OP_NOT]: + if bit := TCPFlagOpValueMap[myCmdChar]; bit&TCPFlagOp(operatorValue[0]) == 0 { + operatorValue[0] |= int(bit) + index++ + } else { + err := fmt.Errorf("Not flag appears multiple time") + return nil, err + } + case TCPFlagOpNameMap[TCP_FLAG_OP_AND], TCPFlagOpNameMap[TCP_FLAG_OP_OR]: + if bit := TCPFlagOpValueMap[myCmdChar]; bit&TCPFlagOp(operatorValue[0]) == 0 { + operatorValue[0] |= int(bit) + tcpOperatorsFlagsValues = append(tcpOperatorsFlagsValues, operatorValue) + operatorValue[0] = 0 + operatorValue[1] = 0 + index++ + } else { + err := fmt.Errorf("AND or OR (space) operator appears multiple time") + return nil, err + } + case TCPFlagNameMap[TCP_FLAG_ACK], TCPFlagNameMap[TCP_FLAG_SYN], TCPFlagNameMap[TCP_FLAG_FIN], + TCPFlagNameMap[TCP_FLAG_URGENT], TCPFlagNameMap[TCP_FLAG_ECE], TCPFlagNameMap[TCP_FLAG_RST], + TCPFlagNameMap[TCP_FLAG_CWR], TCPFlagNameMap[TCP_FLAG_PUSH]: + myLoopChar := myCmdChar + loopIndex := index + // we loop till we reach the end of TCP flags description + // exit conditions : we reach the end of tcp flags (we find & or ' ') or we reach the end of the line + for loopIndex < len(myCmd) && + (myLoopChar != TCPFlagOpNameMap[TCP_FLAG_OP_AND] && myLoopChar != TCPFlagOpNameMap[TCP_FLAG_OP_OR]) { + // we check if inspected charater is a well known tcp flag and if it doesn't appear twice + if bit, isPresent := TCPFlagValueMap[myLoopChar]; isPresent && (bit&TCPFlag(operatorValue[1]) == 0) { + operatorValue[1] |= int(bit) // we set this flag + loopIndex++ // we move to next character + if loopIndex < len(myCmd) { + myLoopChar = myCmd[loopIndex : loopIndex+1] // we move to the next character only if we didn't reach the end of cmd + } + } else { + err := fmt.Errorf("flag %s appears multiple time or is not part of TCP flags", myLoopChar) + return nil, err + } + } + // we are done with flags, we give back the next cooming charater to the main loop + index = loopIndex + default: + err := fmt.Errorf("flag %s not part of tcp flags", myCmdChar) + return nil, err } - items = append(items, NewFlowSpecComponentItem(op, flag)) } - return NewFlowSpecComponent(FLOW_SPEC_TYPE_TCP_FLAG, items), nil + operatorValue[0] |= int(TCPFlagOpValueMap["E"]) + tcpOperatorsFlagsValues = append(tcpOperatorsFlagsValues, operatorValue) + return tcpOperatorsFlagsValues, nil } func flowSpecEtherTypeParser(rf RouteFamily, args []string) (FlowSpecComponentInterface, error) { @@ -3084,21 +3136,24 @@ func formatProto(op int, value int) string { } func formatFlag(op int, value int) string { - and := " " - ss := make([]string, 0, 2) - if op&0x40 > 0 { - and = "&" + var retString string + if op&TCP_FLAG_OP_MATCH > 0 { + retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_MATCH]) } - if op&0x1 > 0 { - ss = append(ss, "match") + if op&TCP_FLAG_OP_NOT > 0 { + retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_NOT]) } - if op&0x2 > 0 { - ss = append(ss, "not") + for flag, valueFlag := range TCPFlagValueMap { + if value&int(valueFlag) > 0 { + retString = fmt.Sprintf("%s%s", retString, flag) + } } - if len(ss) > 0 { - return fmt.Sprintf("%s(%s)%s", and, strings.Join(ss, "|"), TCPFlag(value).String()) + if op&TCP_FLAG_OP_AND > 0 { + retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_AND]) + } else { // default is or + retString = fmt.Sprintf("%s%s", retString, TCPFlagOpNameMap[TCP_FLAG_OP_OR]) } - return fmt.Sprintf("%s%s", and, TCPFlag(value).String()) + return retString } func formatFragment(op int, value int) string { diff --git a/packet/bgp/constant.go b/packet/bgp/constant.go index aa9b17b9..4888df7f 100644 --- a/packet/bgp/constant.go +++ b/packet/bgp/constant.go @@ -102,15 +102,19 @@ const ( TCP_FLAG_PUSH = 0x08 TCP_FLAG_ACK = 0x10 TCP_FLAG_URGENT = 0x20 + TCP_FLAG_ECE = 0x40 + TCP_FLAG_CWR = 0x80 ) var TCPFlagNameMap = map[TCPFlag]string{ - TCP_FLAG_FIN: "fin", - TCP_FLAG_SYN: "syn", - TCP_FLAG_RST: "rst", - TCP_FLAG_PUSH: "push", - TCP_FLAG_ACK: "ack", - TCP_FLAG_URGENT: "urgent", + TCP_FLAG_FIN: "F", + TCP_FLAG_SYN: "S", + TCP_FLAG_RST: "R", + TCP_FLAG_PUSH: "P", + TCP_FLAG_ACK: "A", + TCP_FLAG_URGENT: "U", + TCP_FLAG_CWR: "C", + TCP_FLAG_ECE: "E", } var TCPFlagValueMap = map[string]TCPFlag{ @@ -120,11 +124,39 @@ var TCPFlagValueMap = map[string]TCPFlag{ TCPFlagNameMap[TCP_FLAG_PUSH]: TCP_FLAG_PUSH, TCPFlagNameMap[TCP_FLAG_ACK]: TCP_FLAG_ACK, TCPFlagNameMap[TCP_FLAG_URGENT]: TCP_FLAG_URGENT, + TCPFlagNameMap[TCP_FLAG_CWR]: TCP_FLAG_CWR, + TCPFlagNameMap[TCP_FLAG_ECE]: TCP_FLAG_ECE, +} + +type TCPFlagOp int + +const ( + TCP_FLAG_OP_OR = 0x00 + TCP_FLAG_OP_AND = 0x40 + TCP_FLAG_OP_END = 0x80 + TCP_FLAG_OP_NOT = 0x02 + TCP_FLAG_OP_MATCH = 0x01 +) + +var TCPFlagOpNameMap = map[TCPFlagOp]string{ + TCP_FLAG_OP_OR: " ", + TCP_FLAG_OP_AND: "&", + TCP_FLAG_OP_END: "E", + TCP_FLAG_OP_NOT: "!", + TCP_FLAG_OP_MATCH: "=", +} + +var TCPFlagOpValueMap = map[string]TCPFlagOp{ + TCPFlagOpNameMap[TCP_FLAG_OP_OR]: TCP_FLAG_OP_OR, + TCPFlagOpNameMap[TCP_FLAG_OP_AND]: TCP_FLAG_OP_AND, + TCPFlagOpNameMap[TCP_FLAG_OP_END]: TCP_FLAG_OP_END, + TCPFlagOpNameMap[TCP_FLAG_OP_NOT]: TCP_FLAG_OP_NOT, + TCPFlagOpNameMap[TCP_FLAG_OP_MATCH]: TCP_FLAG_OP_MATCH, } func (f TCPFlag) String() string { ss := make([]string, 0, 6) - for _, v := range []TCPFlag{TCP_FLAG_FIN, TCP_FLAG_SYN, TCP_FLAG_RST, TCP_FLAG_PUSH, TCP_FLAG_ACK, TCP_FLAG_URGENT} { + for _, v := range []TCPFlag{TCP_FLAG_FIN, TCP_FLAG_SYN, TCP_FLAG_RST, TCP_FLAG_PUSH, TCP_FLAG_ACK, TCP_FLAG_URGENT, TCP_FLAG_CWR, TCP_FLAG_ECE} { if f&v > 0 { ss = append(ss, TCPFlagNameMap[v]) } diff --git a/test/scenario_test/flow_spec_test.py b/test/scenario_test/flow_spec_test.py index f3c6bff2..c8fa9068 100644 --- a/test/scenario_test/flow_spec_test.py +++ b/test/scenario_test/flow_spec_test.py @@ -44,7 +44,7 @@ class GoBGPTestBase(unittest.TestCase): matchs = ['destination 10.0.0.0/24', 'source 20.0.0.0/24'] thens = ['discard'] e1.add_route(route='flow1', rf='ipv4-flowspec', matchs=matchs, thens=thens) - matchs2 = ['tcp-flags syn', 'protocol tcp udp', "packet-length '>1000&<2000'"] + matchs2 = ['tcp-flags S', 'protocol tcp udp', "packet-length '>1000&<2000'"] thens2 = ['rate-limit 9600', 'redirect 0.10:100', 'mark 20', 'action sample'] g1.add_route(route='flow1', rf='ipv4-flowspec', matchs=matchs2, thens=thens2) matchs3 = ['destination 2001::/24/10', 'source 2002::/24/15'] |