Hello BIRD maintainers,

### Summary

BIRD appears to miss the mandatory RFC 7607 validation for AS 0 in received BGP OPEN messages when the peer is configured in accept-any external-AS mode, including dynamic BGP peers created from a `neighbor range ... external` configuration.

RFC 7607 Section 2 says that AS 0 is reserved, must not be used by a BGP speaker, and must not be accepted in a BGP OPEN message. When a BGP speaker receives an OPEN whose peer AS is 0, it must abort the connection and send an OPEN Message Error with subcode Bad Peer AS.

In the tested BIRD configuration, a dynamic external peer was able to complete session establishment while claiming peer AS 0.

### Affected Configuration

This issue is relevant when BIRD is configured to accept any external AS, for example:

```bird
protocol bgp as0_open_test {
    local as 65001;
    neighbor 10.0.0.2 external;
    ipv4 { import all; export none; };
}
```

or with dynamic BGP peers:

```bird
protocol bgp dyn_as0_test {
    local as 65001;
    neighbor range 10.100.0.0/24 external;
    ipv4 { import all; export none; };
}
```

The dynamic-peer case was tested locally.

### Expected Behavior

If a peer sends a BGP OPEN with My Autonomous System set to 0, BIRD should reject the OPEN, abort the connection, and send:

```text
Error Code:    OPEN Message Error
Error Subcode: Bad Peer AS
```

That is, BIRD should send NOTIFICATION 2/2 rather than allowing the BGP FSM to reach Established.

### Actual Behavior

With a dynamic external peer configuration, BIRD accepted an incoming OPEN with peer AS 0 and established the session.

`birdc show protocols all` showed the spawned dynamic BGP instance in Established state:

```text
as0_dyn_1  BGP        ---        up     ...  Established
  BGP state:          Established
    Neighbor address: 10.100.0.2
    Neighbor AS:      0
    Local AS:         65001
    Neighbor ID:      10.0.0.2
```

The BIRD log also showed the AS 0 OPEN being accepted and the session becoming established:

```text
as0_dyn_1: Got OPEN(as=0,hold=180,id=10.0.0.2)
as0_dyn_1: Sending OPEN(ver=4,as=65001,hold=240,id=0a000001)
as0_dyn_1: Sending KEEPALIVE
as0_dyn_1: Got KEEPALIVE
as0_dyn_1: BGP session established
```

For comparison, FRR rejects the same OPEN with NOTIFICATION 2/2.

### Root Cause

The relevant validation is in `bgp_rx_open()` in `proto/bgp/packets.c`.

For accept-any external-AS mode, where the configured remote AS is unspecified, the current check rejects a peer only when the effective peer AS equals the local AS. There does not appear to be a separate check for the reserved value 0.

The effective logic is:

```c
if (caps->as4_support) {
  u32 as4 = caps->as4_number;

  if (p->remote_as ? (as4 != p->remote_as) : (as4 == p->local_as))
    bgp_error(conn, 2, 2, ...);

  conn->received_as = as4;
} else {
  if (p->remote_as ? (asn != p->remote_as) : (asn == p->local_as))
    bgp_error(conn, 2, 2, ...);

  conn->received_as = asn;
}
```

In accept-any external mode, `asn == 0` is not equal to the non-zero local AS, so it can pass this validation and become `conn->received_as`.

### AS4 Capability Case

The AS4 Capability path should also be considered. If the OPEN carries AS4 Capability and the AS4 number is 0, that effective peer AS should also be rejected.

A fix should therefore reject both cases:

```c
if (asn == 0)
  reject with OPEN Message Error / Bad Peer AS;

if (caps->as4_support && caps->as4_number == 0)
  reject with OPEN Message Error / Bad Peer AS;
```

### Impact

This is best described as an RFC 7607 compliance bug in BGP OPEN validation for accept-any external and dynamic peers.

Practical impact depends on deployments using accept-any external-AS or dynamic BGP peering, but the observed behavior can:

1. Allow an external peer to establish a session while claiming the reserved AS number 0.
2. Violate RFC 7607's mandatory OPEN handling.
3. Create inconsistent peer-AS state for logging, policy, and later route processing.
4. Cause interoperability issues with implementations that correctly reject AS 0.

The issue does not require a configured neighbor AS of 0. It occurs in accept-any external-AS mode, where BIRD accepts an external peer AS dynamically.

### Version Tested

This was reproduced against:

```text
BIRD version 2.19.0+branch.master.880200b1f94c
```
Best regards,
Xinzhe Liu