Kau bisa bersembunyi dari kesalahanmu, tapi tidak dari penyesalanmu. Kau bisa bermain dengan dramamu, tapi tidak dengan karmamu.

  • About

    Seorang Teknisi Jaringan yang pernah berkecimpung di Internet Service Provider (ISP) dan akhirnya memutuskan diri untuk menjadi freelancer yang berdomisili di Solo, Surakarta Hadiningrat.

  • Services

    Melayani jasa pasang Internet berbasis Wireless maupun Fiber Optic, Jasa Pasang Tower/Pipa, Pointing Wireless, Setting Router, CCTV, Hotspot, Proxy, Web Server, Network Managed dan Monitoring

  • Contact

    Berbagai saran, kritikan, keluhan dan masukan akan sangat berarti bagi saya. Anda dapat menghubungi/whatsapp saya di nomor:08564-212-8686 atau melalui BBM:2128686

    Showing posts with label Router. Show all posts
    Showing posts with label Router. Show all posts

    Friday, May 6, 2022

    Berikut isi file /usr/local/etc/bird.conf 

    # Configure logging
    log syslog { debug, trace, info, remote, warning, error, auth, fatal, bug };
    log stderr all;
    #log "tmp" all;

    # Override router ID
    router id 10.60.60.2;
    protocol kernel {

    #       learn;                  # Learn all alien routes from the kernel
            persist;                # Don't remove routes on bird shutdown
            scan time 20;           # Scan kernel routing table every 20 seconds
    #       import none;            # Default is import all
            export all;             # Default is export none
    #       kernel table 5;         # Kernel table to synchronize with (default: main)
    }

    # This pseudo-protocol watches all interface up/down events.
    protocol device {
            scan time 10;           # Scan interfaces every 10 seconds
    }

    # Static routes (again, there can be multiple instances, so that you
    # can disable/enable various groups of static routes on the fly).
    protocol static {
    #       disabled;               # Disable by default
    #       table testable;         # Connect to a non-default table
    #       preference 1000;        # Default preference of routes
    #       debug { states, routes, filters, interfaces, events, packets };
    #       debug all;
    #       route 0.0.0.0/0 via 198.51.100.13;
    #       route 198.51.100.0/25 unreachable;
    #       route 10.0.0.0/8 unreachable;
    #       route 10.1.1.0:255.255.255.0 via 198.51.100.3;
    #       route 10.1.2.0:255.255.255.0 via 198.51.100.3;
    #       route 10.1.3.0:255.255.255.0 via 198.51.100.4;
    #       route 10.2.0.0/24 via "arc0";
            route 103.166.141.0/32 via 10.60.60.1;
    }

    protocol bgp CCR1036 {
            #disabled;
            description "BGP Peer to CCR1036";
            local as 65530;
            neighbor 10.60.60.1 as 139967;
            import filter {
            ##jika no label maka kasih label 65000:5678
            # bgp_community = -empty-; bgp_community = add(bgp_community,(65000,5678));

            ##jika terima aspath 7717 maka kasih tag 50310:7717
            if bgp_path ~ [= * 7717 * =] then
                    bgp_community = add(bgp_community,(50310,7717));
            if bgp_path ~ [= * 7597 * =] then
                    bgp_community = add(bgp_community,(50310,7597));
            ##jika origin AS7713 maka kasih tag 50310:7713
            if bgp_path ~ [= * 7713 =] then
                    bgp_community = add(bgp_community,(50310,7713));
            ##contoh reject AS20940
            #if bgp_path ~ [= * 20940 * =] then reject;
            ##contoh reject prefix 45.126.184.0/22 saja
            #if net ~ [ 45.126.184.0/22 ] then reject;
            ##contoh reject prefix 45.126.184.0/22 sampai prefix terkecil
            #if net ~ [ 45.126.184.0/22+ ] then reject;
            ##contoh reject prefix 45.126.184.0/22 sampai prefix terkecil (cara lain)
            #if net ~ 45.126.184.0/22 then reject;
            ##contoh reject prefix 36.90.0.0/16 sampai prefix terkecil
            #if net ~ 36.90.0.0/16 then reject;
            ##yg matching bgp community 50310:17451 ditambahkan tag 39967:17451
            if (50310,17451) ~ bgp_community then bgp_community.add((39967,17451));
            ##selain diatas maka accept
            accept;
            };
    }


    Berikut CheatSet untuk trobelshoting masalah routing bgp

    root@BSDRouter:~ # birdc
    BIRD 1.6.8 ready.
    bird> show protocols
    name     proto    table    state  since       info
    kernel1  Kernel   master   up     23:07:34
    device1  Device   master   up     23:07:34
    static1  Static   master   up     23:07:34
    CCR1036  BGP      master   up     04:44:52    Established
    bird>

    bird> show protocols all CCR1036
    name     proto    table    state  since       info
    CCR1036  BGP      master   up     04:44:52    Established
      Description:    BGP Peer to CCR1036
      Preference:     100
      Input filter:   (unnamed)
      Output filter:  REJECT
      Routes:         20994 imported, 0 exported, 20994 preferred
      Route change stats:     received   rejected   filtered    ignored   accepted
        Import updates:         231033          0         18     206404      24611
        Import withdraws:         1430          0        ---       1408         40
        Export updates:          24612      24611          1        ---          0
        Export withdraws:           40        ---        ---        ---          0
      BGP state:          Established
        Neighbor address: 10.60.60.1
        Neighbor AS:      139967
        Neighbor ID:      103.146.185.254
        Neighbor caps:    refresh AS4
        Session:          external AS4
        Source address:   10.60.60.2
        Hold timer:       138/180
        Keepalive timer:  3/60

    bird> show route where net ~ 45.126.184.0/22 all

    45.126.185.0/24    via 10.60.60.1 on vlan605 [CCR1036 05:24:46] * (100) [AS17451i]
            Type: BGP unicast univ
            BGP.origin: IGP
            BGP.as_path: 139967 17451
            BGP.next_hop: 10.60.60.1
            BGP.local_pref: 100
            BGP.community: (0,32934) (17451,7033) (50310,17451) (39967,17451)
    45.126.184.0/24    via 10.60.60.1 on vlan605 [CCR1036 05:24:45] * (100) [AS17451i]
            Type: BGP unicast univ
            BGP.origin: IGP
            BGP.as_path: 139967 17451
            BGP.next_hop: 10.60.60.1
            BGP.local_pref: 100
            BGP.community: (0,32934) (50310,17451) (39967,17451)
    45.126.184.0/22    via 10.60.60.1 on vlan605 [CCR1036 05:24:46] * (100) [AS17451?]
            Type: BGP unicast univ
            BGP.origin: Incomplete
            BGP.as_path: 139967 17451
            BGP.next_hop: 10.60.60.1
            BGP.local_pref: 100
            BGP.atomic_aggr:
            BGP.aggregator: 182.253.99.245 AS17451
            BGP.community: (0,32934) (50310,17451) (39967,17451)

    bonus aspath delete :

    ##bgp cut aspath 139967
    if (bgp_path ~ [= 139967 * =]) && (bgp_path.last != 139967) then
    bgp_path.delete(139967);

    ##bgp cut aspath 139967 dan 7717
    if (bgp_path ~ [= 7717 * =]) then
    bgp_path.delete(7717);

    Semoga berguna :))


    Sunday, January 10, 2021

    Dear all,

    Jumpa lagi sama saya. Kali ini saya akan bagi tips untuk membuat link aggregate BGP menggunakan Juniper Feat Mikrotik, dimana ada 2 link metro-e yakni ICONPLUS dan HSP

    Berikut skema-nya :

    [IDC3D] Juniper <> metro-e ICON <> Mikrotik sisi pelanggan
    [IDC3D] Juniper <> metro-e HSP <> Mikrotik sisi pelanggan

    dimana ip point to point-nya

    Juniper 10.0.101.17/30 <> 10.0.101.18/30 Mikrotik
    Juniper 10.0.101.21/30 <> 10.0.101.22/30 Mikrotik

    OK, sekarang kita set di sisi Junipernya

    Kita buat dulu policy LOADBALANCE

    # set policy-options policy-statement LOADBALANCE then load-balance consistent-hash

    nanti hasilnya gini

    policy-statement LOADBALANCE {

        then {

            load-balance consistent-hash;

        }

    }

     

    terus sekarang bikin policy routing filter IN

    # top show policy-options policy-statement DIST-MIRZA-IN

    inactive: term REJECT-TMP {

        then reject;

    }

    term ACCEPT {

        from {

            route-filter 103.146.184.0/23 prefix-length-range /25-/32;

            route-filter 129.9.0.0/16 prefix-length-range /16-/32;

        }

        then accept;

    }

    term REJECT {

        then reject;

    }


    lalu buat policy routing filter OUT

    # top show policy-options policy-statement DIST-MIRZA-OUT

    inactive: term REJECT-TMP {

        then reject;

    }

    term HSP {

        from {

            route-filter 0.0.0.0/0 exact;

        }

        then {

            next-hop 10.0.101.17;

            accept;

        }

    }

    term ICON {

        from {

            route-filter 0.0.0.0/0 exact;

        }

        then {

            next-hop 10.0.101.21;

            accept;

        }

    }

    inactive: term ACCEPT {

        from {

            route-filter 0.0.0.0/0 exact;

        }

        then accept;

    }

    term REJECT {

        then reject;

    }

    Jika sudah, lalu buat bgp peer neighbor

    group MIRZA {

        description Mirza;

        import DIST-MIRZA-IN;

        export DIST-MIRZA-OUT;

        peer-as 65530;

        multipath;

        neighbor 10.0.101.22;

        neighbor 10.0.101.18;

        neighbor 10.0.101.26;

    }

    kemudian cek confignya sudah bener apa belum
    CoreBorder# commit check
    CoreBorder# commit confirm 5 <= masa berlaku 5 menit, bebas silahkan tentukan
    misal sudah ok tinggal gini
    CoreBorder# commit

    Lalu untuk sisi Mikrotiknya setingan BGP Standar, ngga ada setingan khusus.

    Semoga Bermanfaat

    Tuesday, October 20, 2020

    Hai sobat, kali ini saya akan share tips cara untuk membuat auto update tunneling menggunakan protocol GRE.

    skema :

    [ Server Tunnel ] <> INET BROADBAND <> [ Tunnel Client IP Public ]

    jika menggunakan GRE-TUNNEL maka membutuhkan tiktok ip public yang dituangkan dalam remote-address

    mekanismenya :

    • selalu update ip public
    • selalu update remote-address pada gre tunnel

    # Start
    :global currentIP

    # ubah di sini
    :global iface "PPPOE200"
    :global ifacegre "gre-soni"

    # validasi file script
    :if ([:len [/file find name~"$ifacegre"]] = 0 ) do={
    /in gre export file=$ifacegre
    }

    :if ($currentIP  in 10.0.0.0/8) do={
    :log error "re-dial...";
    /in pppoe-client disable $iface
    :delay 2
    /in pppoe-client enable $iface
    }

    /in pppoe-client monitor $iface once do={:set currentIP $"local-address" }

    :global p2p  [/ip address get [:pick [find interface=$ifacegre] 0] network ] 
    :global target [/in gre get $ifacegre remote-address ]

    :global statusgre [/in gre get $ifacegre running ];
    :if ($statusgre != true) do={
    /in gre set $ifacegre local-address=$currentIP
    :log error "$ifacegre error, kirim file"
    /file set contents="/in gre set $ifacegre remote-address=$currentIP" [find name~"$ifacegre"]
    :delay 2

    /tool fetch mode=ftp upload=yes src-path="$ifacegre.rsc" dst-path="$ifacegre.rsc" user=ftp password=ftp port=2121 address="$target"
    }

    Tuesday, July 21, 2020

    :global iface ether5.WAN-ISP
    :global trafikawal
    :global trafik
    # reset tanggal 1
    #/system ntp client set primary-ntp=[:resolve id.pool.ntp.org] enabled=yes
    #/system clock set time-zone-name="Asia/Jakarta"
    :local date [/system clock get date]
    :local tanggal [ :pick $date 4 6 ]
    #:log warning $tanggal
    :if ($tanggal = 1) do={:set trafikawal 0}
    :set trafikawal [/interface get [find name=$iface] rx-byte]
    :if ($trafikawal > 1073741824) do={
    :set trafik ($trafikawal / 1073741824)
    :set trafik ($trafik." GB")
    } else={
    :set trafik ($trafikawal / 1048576)
    :set trafik ($trafik." MB")
    }
    :log warning "trafik $iface = $trafik"
    /in ethernet set $iface comment="$trafikawal"

    Wednesday, May 27, 2020


    WI-131(config-if)# int e1/35
    WI-131(config-if)# switchport
    WI-131(config-if)# channel-group 1 mode active
    WI-131(config-if)# int e1/36
    WI-131(config-if)# switchport
    WI-131(config-if)# channel-group 1 mode active
    WI-131(config-if)# int port-channel 1
    WI-131(config-if)# switchport
    WI-131(config-if)# switchport mode trunk
    WI-131(config-if)# switchport trunk allowed vlan 2011
    WI-131(config-if)# no sh
    WI-131(config-if)# sh port-channel summary
    Flags:  D - Down        P - Up in port-channel (members)
            I - Individual  H - Hot-standby (LACP only)
            s - Suspended   r - Module-removed
            S - Switched    R - Routed
            U - Up (port-channel)
            M - Not in use. Min-links not met
    --------------------------------------------------------------------------------
    Group Port-       Type     Protocol  Member Ports
          Channel
    --------------------------------------------------------------------------------
    1     Po1(SU)     Eth      LACP      Eth1/35(P)   Eth1/36(P)


    ether03 = e1/35
    ether04 = e1/36

    Thursday, May 7, 2020

    :local IFACE
    :set IFACE INDIHOME

    :local WANIP [/ip address get [find where interface=$IFACE] address];
    :set WANIP [:pick $WANIP 0 ([:len $WANIP]-3) ];

    :if ($WANIP ~"^10[.]") do={
    :log warning "Private ip address found !!!"
    /interface pppoe-client enable $IFACE
    :delay 1
    } else={

    :if ($WANIP ~"^172.") do={
    :log warning "Private ip address found !!!"
    /interface pppoe-client enable $IFACE
    :delay 1
    } else={
    :if ($WANIP ~"^192.") do={
    :log warning "Private ip address found !!!"
    /interface pppoe-client enable $IFACE
    :delay 1
    } else={
    :log warning "Public IP - $WANIP - Found, OK ! No action required"
    }
    }
    }
    :delay 10
    /ip cloud force-update

    Sunday, April 5, 2020

    OK, kali ini saya akan share script mentah untuk update ip list yang nantinya bikin script lagi buat dijalanin di mikrotik.

    #!/bin/bash
    wget -O all.txt https://lists.blocklist.de/lists/all.txt
    sleep 2
    rm -rf blocked.rsc
    touch blocked.rsc
    DATE=$(date '+%d/%m/%Y %H:%M:%S')
    LINE=$(wc -l all.txt | awk '{print $1}')
    echo -e "# Generated at $DATE ... $LINE lines\n# Sumber: https://lists.blocklist.de/lists/all.txt\n# Silahkan bisa ditambahkan di router masing-masing\n# Supported by: https://yam.net.id\n\n# Add scheduler\n:if ([:len [/system scheduler find name=\"autoupdate\"]]=0) do={\n/system scheduler add name=autoupdate on-event=\"/tool fetch url=http://103.146.184.12/dump/blocked.rsc;:delay 1;/import blocked.rsc\" interval=\"2d 00:00:00\" start-time=04:00:00}\n\n# Start blocking now\n:if ([:len [/ip fi fi find comment=BLOCKED]]=0) do={\n/ip fi fi add chain=input src-address-list=BLOCKED protocol=udp dst-port=!80,443 action=drop comment=BLOCKED\n/ip fi fi add chain=input src-address-list=BLOCKED protocol=tcp dst-port=!80,443 action=reject reject-with=icmp-network-unreachable comment=BLOCKED\n/ip fi fi add chain=forward src-address-list=BLOCKED protocol=tcp dst-port=!80,443 action=reject reject-with=icmp-network-unreachable comment=BLOCKED\n/ip fi fi add chain=forward src-address-list=BLOCKED protocol=udp dst-port=!80,443 action=drop comment=BLOCKED}\n\n# Clearing address-list\n/ip firewall address-list\nadd list=BLOCKED address=1.2.3.4\nremove [find list=BLOCKED]\n# Kasih jeda\n:delay 2\n" > blocked.rsc
    cat all.txt | grep "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]$" | awk '{print "/ip firewall address-list add list=BLOCKED address="$1 }' >> blocked.rsc

    
    
    
    
    reference :
    https://deadc0de.re/articles/unbound-blocking-ads.html

    Download the list you're interested to block (for example ads+malwares+social networks)
    $ wget https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/social/hosts
    
    Entries in the host file are in the form 0.0.0.0 <domain> and unbound needs something like this:
    local-zone: "<domain>" redirect
    local-data: "<domain> A 0.0.0.0"
    
    Following awk command will transform the list into something that unbound understands:
    $ cat hosts | grep '^0\.0\.0\.0' | awk '{print "local-zone: \""$2"\" redirect\nlocal-data: \""$2" A 0.0.0.0\""}' > ads.conf 
    Finally copy the resulting file to pfsense (or to your DNS resolver running unbound).

    Saturday, February 22, 2020

    Hi Brian,
    Can you explain the easiest way to construct a regular expression in BGP?
    Thanks,
    Rowan
    Hi Rowan,
    Regular expressions are strings of special characters that can be used to search and find character patterns. Within the scope of BGP in Cisco IOS regular expressions can be used in show commands and AS-Path access-lists to match BGP prefixes based on the information contained in their AS-Path.
    In order to understand how to build regular expressions we first need to know what the character definitions are for the regex function of IOS. The below table illustrates the regex characters and their usage. This information is contained in the Cisco IOS documentation under the Appendix of Cisco IOS Terminal Services Configuration Guide, Release 12.2.
    +------------------------------------------------------+
    
    | CHAR | USAGE                                         |
    
    +------------------------------------------------------|
    
    |  ^   | Start of string                               |
    
    |------|-----------------------------------------------|
    
    |  $   | End of string                                 |
    
    |------|-----------------------------------------------|
    
    |  []  | Range of characters                           |
    
    |------|-----------------------------------------------|
    
    |  -   | Used to specify range ( i.e. [0-9] )          |
    
    |------|-----------------------------------------------|
    
    |  ( ) | Logical grouping                              |
    
    |------|-----------------------------------------------|
    
    |  .   | Any single character                          |
    
    |------|-----------------------------------------------|
    
    |  *   | Zero or more instances                        |
    
    |------|-----------------------------------------------|
    
    |  +   | One or more instance                          |
    
    |------|-----------------------------------------------|
    
    |  ?   | Zero or one instance                          |
    
    |------|-----------------------------------------------|
    
    |  _   | Comma, open or close brace, open or close     |
    
    |      | parentheses, start or end of string, or space |
    
    +------------------------------------------------------+
    
    Some commonly used regular expressions include:
    +-------------+---------------------------+
    
    | Expression  | Meaning                   |
    
    |-------------+---------------------------|
    
    | .*          | Anything                  |
    
    |-------------+---------------------------|
    
    | ^$          | Locally originated routes |
    
    |-------------+---------------------------|
    
    | ^100_       | Learned from AS 100       |
    
    |-------------+---------------------------|
    
    | _100$       | Originated in AS 100      |
    
    |-------------+---------------------------|
    
    | _100_       | Any instance of AS 100    |
    
    |-------------+---------------------------|
    
    | ^[0-9]+$    | Directly connected ASes   |
    
    +-------------+---------------------------+
    
    Let’s break some of the above expressions down step-by-step. The first one “.*” says to match any single character (“.”), and then find zero or more instances of that single character (“*”). This means zero or more instances or any character, which effectively means anything.
    The next string “^$” says to match the beginning of the string (“^”), and then immediately match the end of the string (“$”). This means that the string is null. Within the scope of BGP the only time that the AS-Path is null is when you are looking at a route within your own AS that you or one of your iBGP peers has originated. Hence this matches locally originated routes.
    The next string “^100_” says to match the beginning of the string (“^”), the literal characters 100, and then a comma, an open or close brace, an open or close, a parentheses, the start or end of the string, or a space (“_”). This means that the string must start with the number 100 followed by any non-alphanumeric character. In the scope of BGP this means that routes which are learned from the AS 100 will be matched, as 100 will be the first AS in the path when AS 100 is sending us routes.
    The next string “_100$” is the exact opposite of the previous one. This string says to start with any non-alphanumeric character (“_”), followed by the literal characters 100, followed by the end of the string (“$”). This means that AS 100 is the last AS in the path, or in other words that the prefix in question was originated by AS 100.
    The next string “_100_” is the combination of the two previous strings with some extra matches. This string means that the literal characters 100 are set between any two non-alphanumeric characters. The first of these could be the start of the string, which would match routes learned from AS 100, while the second of these could be the end of the string, which would match routes originated in AS 100. Another case could be that the underscores represent spaces, in which the string would match any other AS path information as long as “ 100 ” is included somewhere. This would match any routes which transit AS 100, and therefore “_ASN_” is generally meant to match routes that transit a particular AS as defined by the number “ASN”.
    The final string “^[0-9]+$” is a little more complicated match. Immediately we can see that the string starts (“^”), and we can see later that it ends (“$”). In the middle we see a range of numbers 0-9 in brackets, followed by the plus sign. The numbers in brackets mean that any number from zero to nine can be matched, or in other words, any number. Next we have the plus sign which means one or more instances. This string “[0-9]+” therefore means one or more instance of any number, or in other words any number including numbers with multiple characters (i.e. 1, 12, 123, 1234, 12345678, etc.). When we combine these all together this string means routes originated in any directly connected single AS, or in other words, the routes directly originated by the peers of your AS.
    Now let’s look at a more complicated match, and using the above character patterns we will see how we can construct the expression step by step. Suppose we have the following topology below, where we are looking at the network from the perspective of AS 100.
    +--------+ +--------+ +--------+ +--------+
    
    | AS 200 |-| AS 201 |-| AS 202 |-| AS 203 |\
    
    +--------+ +--------+ +--------+ +--------+ \
    
                                                 \
    
               +--------+ +--------+ +--------+\  \
    
               | AS 300 |-| AS 301 |-| AS 302 | \  \
    
               +--------+ +--------+ +--------+  \  -+--------+
    
                                                  >--| AS 100 |
    
                          +--------+ +--------+  /  -+--------+
    
                          | AS 400 |-| AS 401 | /  /
    
                          +--------+ +--------+/  /
    
                                                 /
    
                                     +--------+ /
    
                                     | AS 500 |/
    
                                     +--------+
    
    AS 100 peers with ASes 203, 302, 401, and 500, who each have peers as diagramed above. AS 100 wants to match routes originated from its directly connected customers (ASes 203, 302, 401, and 500) in addition to routes originated from their directly connected customers (ASes 202, 301, and 400). The easiest way to create this regular expression would be to think about what we are first trying to match, and then write out all possibilities of these matches. In our case these possibilities are:
    203
    
    203 202
    
    302
    
    302 301
    
    401
    
    401 400
    
    500
    
    Now we could simply create an expression with multiple lines (7 lines to be exact) that would match all of the possible AS paths, but suppose that AS 100 wants to keep this match as flexible as possible so that it will apply to any other ASes in the future. Now let’s try to generalize the above AS-Path information into a regex.
    First off we know that each of the matches is going to start and going to end. This means that the first character we will have is “^” and the last character is “$”. Next we know that between the “^” and “$” there will be either one AS or two ASes. We don’t necessarily know what numbers these ASes will be, so for the time being let’s use the placeholder “X”. Based on this our new possible matches are:
    ^X$
    
    ^X X$
    
    Next let’s reason out what X can represent. Since X is only one single AS, there will be no spaces, commas, parentheses, or any other special type characters. In other words, X must be a number. However, since we don’t know what the exact path is, we must take into account that X may be a number with more than one character (i.e. 10, 123, or 10101). This essentially equates to one or more instance of any number zero through nine. In regular expression syntax our two matches would therefore now read:
    ^[0-9]+$
    
    ^[0-9]+ [0-9]+$
    
    This expressions reads that we either have a number consisting of one or more characters zero through nine, or a number consisting of one or more characters zero through nine followed by a space and then another number consisting of one or more characters zero through nine. This brings our expression down to two lines as opposed to our original seven, but let’s see how we can combine the above two as well. To combine them, first let us compare what is different between them.
    ^[0-9]+$
    
    ^[0-9]+ [0-9]+$
    
    From looking at the expressions it is evident that the sequence “ [0-9]+” is the difference. In the first case “ [0-9]+” does not exist in the expression. In the second case “ [0-9]+” does exist in the expression. In other words, “ [0-9]+” is either true or false. True or false (0 or 1) is represented by the character “?” in regex syntax. Therefore we can reduce our expression to:
    ^[0-9]+ [0-9]+?$
    
    At this point we run into a problem with the order of operations of the regex. As denoted above the question mark will apply only to the plus sign, and not to the range [0-9]. Instead, we want the question mark to apply to the string “ [0-9]+” as a whole. Therefore this string needs to be grouped together using parentheses. Parentheses are used in regular expressions as simply a logical grouping. Therefore our final expression reduces to:
    ^[0-9]+( [0-9]+)?$
    
    Note that to match a question mark in IOS, the escape sequence CTRL-V or ESC-Q must be entered first, otherwise the IOS parser will interpret the question mark as an attempt to invoke the context sensitive help.