@@ -44,13 +44,15 @@ type clientConfig struct {
4444 headerProvider HeaderProvider
4545 insecureConnection bool
4646 host string
47+ forceIPV4 bool
4748}
4849
4950func newClientConfig (host string ) * clientConfig {
5051 cfg := & clientConfig {
5152 headerProvider : nil ,
5253 perRPCCredentials : nil ,
5354 host : host ,
55+ forceIPV4 : false ,
5456 }
5557 WithInsecureConnection ()(cfg ) // Default to insecure connection
5658 return cfg
@@ -72,6 +74,11 @@ func NewClient(address string, opts ...Opt) (Client, error) {
7274 grpcOpts := []grpc.DialOption {
7375 grpc .WithTransportCredentials (cfg .transportCredentials ),
7476 }
77+ // Add our custom dialer if IPv4 is forced
78+ if cfg .forceIPV4 {
79+ grpcOpts = append (grpcOpts , grpc .WithContextDialer (forceIPV4Dialer ))
80+ }
81+
7582 // Auth
7683 if cfg .perRPCCredentials != nil {
7784 grpcOpts = append (grpcOpts , grpc .WithPerRPCCredentials (cfg .perRPCCredentials ))
@@ -110,6 +117,13 @@ func (c *client) Close() error {
110117 return c .conn .Close ()
111118}
112119
120+ // WithForceIPV4 forces the client to use IPv4 for connections.
121+ func WithForceIPV4 () Opt {
122+ return func (c * clientConfig ) {
123+ c .forceIPV4 = true
124+ }
125+ }
126+
113127// WithBasicAuth sets the basic-auth credentials for the ChipIngress service.
114128// Default is to require TLS for security.
115129func WithBasicAuth (user , pass string ) Opt {
@@ -172,6 +186,40 @@ func newHeaderInterceptor(provider HeaderProvider) grpc.UnaryClientInterceptor {
172186 }
173187}
174188
189+ // forceIPV4Dialer is a custom dialer that resolves a hostname and forces the connection over IPv4.
190+ func forceIPV4Dialer (ctx context.Context , addr string ) (net.Conn , error ) {
191+ host , port , err := net .SplitHostPort (addr )
192+ if err != nil {
193+ return nil , fmt .Errorf ("failed to split host and port: %w" , err )
194+ }
195+
196+ // Resolve the hostname to a list of IP addresses.
197+ ips , err := net .LookupIP (host )
198+ if err != nil {
199+ return nil , fmt .Errorf ("failed to resolve IP addresses for host %s: %w" , host , err )
200+ }
201+
202+ var ipv4Addr string
203+ // Find the first IPv4 address.
204+ for _ , ip := range ips {
205+ if ip .To4 () != nil {
206+ ipv4Addr = ip .String ()
207+ break
208+ }
209+ }
210+
211+ if ipv4Addr == "" {
212+ return nil , fmt .Errorf ("no IPv4 address found for host: %s" , host )
213+ }
214+
215+ // Create the new address with the resolved IPv4 and original port.
216+ ipv4AddrWithPort := net .JoinHostPort (ipv4Addr , port )
217+
218+ // Dial the new IPv4 address, explicitly using "tcp4".
219+ var d net.Dialer
220+ return d .DialContext (ctx , "tcp4" , ipv4AddrWithPort )
221+ }
222+
175223// NewEvent creates a new CloudEvent with the specified domain, entity, payload, and optional attributes.
176224func NewEvent (domain , entity string , payload []byte , attributes map [string ]any ) (CloudEvent , error ) {
177225
0 commit comments